underhill_attestation/
hardware_key_sealing.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Implementation of key derivation using hardware secret and the VMGS data encryption key (DEK)
5//! sealing using the derived key. The sealed DEK is written to the [FileId::HW_KEY_PROTECTOR`]
6//! entry of the VMGS file, which can be unsealed later.
7
8use crate::crypto;
9use cvm_tracing::CVM_ALLOWED;
10use openhcl_attestation_protocol::igvm_attest;
11use openhcl_attestation_protocol::vmgs;
12use openhcl_attestation_protocol::vmgs::HardwareKeyProtector;
13use openssl_kdf::kdf::Kbkdf;
14use thiserror::Error;
15use zerocopy::IntoBytes;
16
17#[derive(Debug, Error)]
18pub(crate) enum HardwareDerivedKeysError {
19    #[error("failed to initialize hardware secret")]
20    InitializeHardwareSecret(#[source] tee_call::Error),
21    #[error("KDF derivation with hardware secret failed")]
22    KdfWithHardwareSecret(#[source] openssl_kdf::kdf::KdfError),
23}
24
25#[derive(Debug, Error)]
26pub(crate) enum HardwareKeySealingError {
27    #[error("failed to encrypt the egress key")]
28    EncryptEgressKey(#[source] crypto::Aes256CbcError),
29    #[error("invalid egress key encryption size {0}, expected {1}")]
30    InvalidEgressKeyEncryptionSize(usize, usize),
31    #[error("HMAC-SHA-256 after encryption failed")]
32    HmacAfterEncrypt(#[source] crypto::HmacSha256Error),
33    #[error("HMAC-SHA-256 before ecryption failed")]
34    HmacBeforeDecrypt(#[source] crypto::HmacSha256Error),
35    #[error("Hardware key protector HMAC verification failed")]
36    HardwareKeyProtectorHmacVerificationFailed,
37    #[error("failed to decrypt the ingress key")]
38    DecryptIngressKey(#[source] crypto::Aes256CbcError),
39    #[error("invalid ingress key decryption size {0}, expected {1}")]
40    InvalidIngressKeyDecryptionSize(usize, usize),
41}
42
43/// Hold the hardware-derived keys.
44pub struct HardwareDerivedKeys {
45    tcb_version: u64,
46    aes_key: [u8; vmgs::AES_CBC_KEY_LENGTH],
47    hmac_key: [u8; vmgs::HMAC_SHA_256_KEY_LENGTH],
48}
49
50impl HardwareDerivedKeys {
51    /// Derive an AES and HMAC keys based on the hardware secret for key sealing.
52    pub fn derive_key(
53        tee_call: &dyn tee_call::TeeCallGetDerivedKey,
54        vm_config: &igvm_attest::get::runtime_claims::AttestationVmConfig,
55        tcb_version: u64,
56    ) -> Result<Self, HardwareDerivedKeysError> {
57        let hardware_secret = tee_call
58            .get_derived_key(tcb_version)
59            .map_err(HardwareDerivedKeysError::InitializeHardwareSecret)?;
60        let label = b"ISOHWKEY";
61
62        let vm_config = serde_json::to_string(vm_config).expect("JSON serialization failed");
63
64        let mut kdf = Kbkdf::new(
65            openssl::hash::MessageDigest::sha256(),
66            label.to_vec(),
67            hardware_secret.to_vec(),
68        );
69        kdf.set_context(vm_config.as_bytes().to_vec());
70
71        let mut output = [0u8; vmgs::AES_CBC_KEY_LENGTH + vmgs::HMAC_SHA_256_KEY_LENGTH];
72        openssl_kdf::kdf::derive(kdf, &mut output)
73            .map_err(HardwareDerivedKeysError::KdfWithHardwareSecret)?;
74
75        let mut aes_key = [0u8; vmgs::AES_CBC_KEY_LENGTH];
76        let mut hmac_key = [0u8; vmgs::HMAC_SHA_256_KEY_LENGTH];
77
78        aes_key.copy_from_slice(&output[..vmgs::AES_CBC_KEY_LENGTH]);
79        hmac_key.copy_from_slice(&output[vmgs::AES_CBC_KEY_LENGTH..]);
80
81        Ok(Self {
82            tcb_version,
83            aes_key,
84            hmac_key,
85        })
86    }
87}
88
89/// Extension trait of [`HardwareKeyProtector`].
90pub trait HardwareKeyProtectorExt: Sized {
91    /// Seal the `egress_key` with encrypt-then-mac.
92    fn seal_key(
93        hardware_derived_keys: &HardwareDerivedKeys,
94        egress_key: &[u8],
95    ) -> Result<Self, HardwareKeySealingError>;
96
97    /// Unseal the `inress_key` with verify-mac-then-decrypt.
98    fn unseal_key(
99        &self,
100        hardware_derived_keys: &HardwareDerivedKeys,
101    ) -> Result<[u8; vmgs::AES_CBC_KEY_LENGTH], HardwareKeySealingError>;
102}
103
104impl HardwareKeyProtectorExt for HardwareKeyProtector {
105    fn seal_key(
106        hardware_derived_keys: &HardwareDerivedKeys,
107        egress_key: &[u8],
108    ) -> Result<Self, HardwareKeySealingError> {
109        let header = vmgs::HardwareKeyProtectorHeader::new(
110            vmgs::HW_KEY_VERSION,
111            vmgs::HW_KEY_PROTECTOR_SIZE as u32,
112            hardware_derived_keys.tcb_version,
113        );
114
115        let mut iv = [0u8; vmgs::AES_CBC_IV_LENGTH];
116        getrandom::fill(&mut iv).expect("rng failure");
117
118        let mut encrypted_egress_key = [0u8; vmgs::AES_GCM_KEY_LENGTH];
119        let output = crypto::aes_256_cbc_encrypt(&hardware_derived_keys.aes_key, egress_key, &iv)
120            .map_err(HardwareKeySealingError::EncryptEgressKey)?;
121        if output.len() != vmgs::AES_GCM_KEY_LENGTH {
122            Err(HardwareKeySealingError::InvalidEgressKeyEncryptionSize(
123                output.len(),
124                vmgs::AES_GCM_KEY_LENGTH,
125            ))?
126        }
127        encrypted_egress_key.copy_from_slice(&output[..vmgs::AES_GCM_KEY_LENGTH]);
128
129        let mut hardware_key_protector = Self {
130            header,
131            iv,
132            ciphertext: encrypted_egress_key,
133            hmac: [0u8; vmgs::HMAC_SHA_256_KEY_LENGTH],
134        };
135        let offset = std::mem::offset_of!(Self, hmac);
136        hardware_key_protector.hmac = crypto::hmac_sha_256(
137            &hardware_derived_keys.hmac_key,
138            &hardware_key_protector.as_bytes()[..offset],
139        )
140        .map_err(HardwareKeySealingError::HmacAfterEncrypt)?;
141
142        tracing::info!(CVM_ALLOWED, "encrypt egress_key using hardware derived key");
143
144        Ok(hardware_key_protector)
145    }
146
147    fn unseal_key(
148        &self,
149        hardware_derived_keys: &HardwareDerivedKeys,
150    ) -> Result<[u8; vmgs::AES_CBC_KEY_LENGTH], HardwareKeySealingError> {
151        let offset = std::mem::offset_of!(HardwareKeyProtector, hmac);
152        let hmac =
153            crypto::hmac_sha_256(&hardware_derived_keys.hmac_key, &self.as_bytes()[..offset])
154                .map_err(HardwareKeySealingError::HmacBeforeDecrypt)?;
155
156        if hmac != self.hmac {
157            Err(HardwareKeySealingError::HardwareKeyProtectorHmacVerificationFailed)?
158        }
159
160        let mut decrypted_ingress_key = [0u8; vmgs::AES_GCM_KEY_LENGTH];
161        let output =
162            crypto::aes_256_cbc_decrypt(&hardware_derived_keys.aes_key, &self.ciphertext, &self.iv)
163                .map_err(HardwareKeySealingError::DecryptIngressKey)?;
164        if output.len() != vmgs::AES_GCM_KEY_LENGTH {
165            Err(HardwareKeySealingError::InvalidIngressKeyDecryptionSize(
166                output.len(),
167                vmgs::AES_GCM_KEY_LENGTH,
168            ))?
169        }
170        decrypted_ingress_key.copy_from_slice(&output[..vmgs::AES_GCM_KEY_LENGTH]);
171
172        tracing::info!(
173            CVM_ALLOWED,
174            "decrypt ingress_key using hardware derived key"
175        );
176
177        Ok(decrypted_ingress_key)
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use zerocopy::FromBytes;
185
186    struct MockTeeCall;
187
188    impl tee_call::TeeCall for MockTeeCall {
189        fn get_attestation_report(
190            &self,
191            _report_data: &[u8; 64],
192        ) -> Result<tee_call::GetAttestationReportResult, tee_call::Error> {
193            Ok(tee_call::GetAttestationReportResult {
194                report: vec![],
195                tcb_version: None,
196            })
197        }
198
199        fn supports_get_derived_key(&self) -> Option<&dyn tee_call::TeeCallGetDerivedKey> {
200            Some(self)
201        }
202
203        fn tee_type(&self) -> tee_call::TeeType {
204            tee_call::TeeType::Snp
205        }
206    }
207
208    impl tee_call::TeeCallGetDerivedKey for MockTeeCall {
209        fn get_derived_key(&self, _tcb_version: u64) -> Result<[u8; 32], tee_call::Error> {
210            const TEST_HW_DERIVED_KEY: [u8; tee_call::HW_DERIVED_KEY_LENGTH] = [
211                0xe0, 0xd8, 0x29, 0x04, 0xd6, 0x19, 0xd8, 0xdb, 0xd5, 0xd3, 0xba, 0x1c, 0x3c, 0x07,
212                0x2f, 0xaa, 0x56, 0x90, 0xa8, 0x95, 0x3e, 0x66, 0x69, 0x2e, 0xb9, 0xe7, 0xb4, 0xca,
213                0xaa, 0x3a, 0x92, 0x47,
214            ];
215
216            Ok(TEST_HW_DERIVED_KEY)
217        }
218    }
219
220    #[test]
221    fn hardware_derived_keys() {
222        const PLAINTEXT: [u8; 32] = [
223            0x5e, 0xd7, 0xf3, 0xd4, 0x9e, 0xcf, 0xb5, 0x6c, 0x05, 0x54, 0x7c, 0x87, 0xe7, 0x30,
224            0x59, 0xb1, 0x91, 0xcb, 0xa6, 0xc4, 0x0e, 0x4e, 0x30, 0x77, 0x65, 0x19, 0x71, 0xf5,
225            0x20, 0x83, 0x2a, 0xc0,
226        ];
227
228        let vm_config = igvm_attest::get::runtime_claims::AttestationVmConfig {
229            current_time: None,
230            root_cert_thumbprint: "".to_string(),
231            console_enabled: false,
232            secure_boot: false,
233            tpm_enabled: false,
234            tpm_persisted: false,
235            filtered_vpci_devices_allowed: true,
236            vm_unique_id: "".to_string(),
237        };
238        let mock_call = Box::new(MockTeeCall {}) as Box<dyn tee_call::TeeCall>;
239        let mock_get_derived_key_call = mock_call.supports_get_derived_key().unwrap();
240        let result = HardwareDerivedKeys::derive_key(
241            mock_get_derived_key_call,
242            &vm_config,
243            0x7308000000000003,
244        );
245        assert!(result.is_ok());
246        let hardware_derived_keys = result.unwrap();
247
248        let result = HardwareKeyProtector::seal_key(&hardware_derived_keys, &PLAINTEXT);
249        assert!(result.is_ok());
250        let output = result.unwrap();
251
252        let result = HardwareKeyProtector::read_from_prefix(output.as_bytes());
253        assert!(result.is_ok());
254        let hardware_key_protector = result.unwrap().0;
255
256        let result = hardware_key_protector.unseal_key(&hardware_derived_keys);
257        assert!(result.is_ok());
258        let plaintext = result.unwrap();
259        assert_eq!(plaintext, PLAINTEXT);
260    }
261}