1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 //! Module that provides access to a device specific key for this application and serialize the
18 //! necessary information to derive the same key to a cose-encrypt0 type header
19 
20 use ciborium::Value;
21 use coset::{AsCborValue, CborSerializable, Header, ProtectedHeader};
22 use hwcryptohal_common::{err::HwCryptoError, hwcrypto_err};
23 use hwkey::{Hwkey, OsRollbackVersion, RollbackVersionSource};
24 use kmr_common::{
25     crypto::{self, Aes, Hkdf, Rng},
26     FallibleAllocExt,
27 };
28 
29 use crate::crypto_provider;
30 
31 /// Size of the base encryption key used by the service to derive other versioned context encryption
32 /// keys
33 const SERVICE_KEK_LENGTH: usize = 32;
34 
35 /// Size of the random context used to derive a versioned context specific encryption key
36 pub(crate) const KEY_DERIVATION_CTX_LENGTH: usize = 32;
37 
38 /// Nonce value of all zeroes used in AES-GCM key encryption.
39 const ZERO_NONCE: [u8; 12] = [0u8; 12];
40 
41 const KEY_DERIVATION_CTX_COSE_LABEL: i64 = -65539;
42 const KEY_DERIVATION_VERSION_COSE_LABEL: i64 = -65540;
43 
44 // Header used to derive a different key per each encrypted context. Encryption of the context is
45 // similar to what KeyMint does to wrap keys.
46 pub(crate) struct EncryptionHeader {
47     key_derivation_context: [u8; KEY_DERIVATION_CTX_LENGTH],
48     header_version: u32,
49 }
50 
51 impl EncryptionHeader {
new(key_derivation_context: [u8; KEY_DERIVATION_CTX_LENGTH], header_version: u32) -> Self52     fn new(key_derivation_context: [u8; KEY_DERIVATION_CTX_LENGTH], header_version: u32) -> Self {
53         Self { key_derivation_context, header_version }
54     }
55 
generate() -> Result<Self, HwCryptoError>56     pub(crate) fn generate() -> Result<Self, HwCryptoError> {
57         let header_version = get_service_current_version()?;
58         Ok(Self::generate_with_version(header_version))
59     }
60 
generate_with_version(header_version: u32) -> Self61     pub(crate) fn generate_with_version(header_version: u32) -> Self {
62         let key_derivation_context = get_new_key_derivation_context();
63         Self::new(key_derivation_context, header_version)
64     }
65 
66     // Function used to generate different device bound encryption keys tied to the HWCrypto service
67     // to be used for different purposes, which include VersionContext encryption and key wrapping.
derive_service_encryption_key( &self, key_context: &[u8], ) -> Result<crypto::aes::Key, HwCryptoError>68     fn derive_service_encryption_key(
69         &self,
70         key_context: &[u8],
71     ) -> Result<crypto::aes::Key, HwCryptoError> {
72         let encryption_key = get_encryption_key(self.header_version, key_context)?;
73         derive_key_hkdf(&encryption_key, &self.key_derivation_context[..])
74     }
75 
76     /// Encrypt CBOR serializable data using a device key derived using `key_context`
encrypt_content_service_encryption_key<T: AsCborValue>( &self, key_context: &[u8], content: T, ) -> Result<Vec<u8>, HwCryptoError>77     pub(crate) fn encrypt_content_service_encryption_key<T: AsCborValue>(
78         &self,
79         key_context: &[u8],
80         content: T,
81     ) -> Result<Vec<u8>, HwCryptoError> {
82         let kek = self.derive_service_encryption_key(key_context)?;
83         let aes = crypto_provider::AesImpl;
84         let cose_encrypt = coset::CoseEncrypt0Builder::new()
85             .protected(self.try_into()?)
86             .try_create_ciphertext::<_, HwCryptoError>(
87                 &content.to_cbor_value()?.to_vec()?,
88                 &[],
89                 move |pt, aad| {
90                     let mut op = aes.begin_aead(
91                         kek.into(),
92                         crypto::aes::GcmMode::GcmTag16 { nonce: ZERO_NONCE },
93                         crypto::SymmetricOperation::Encrypt,
94                     )?;
95                     op.update_aad(aad)?;
96                     let mut ct = op.update(pt)?;
97                     ct.try_extend_from_slice(&op.finish()?)?;
98                     Ok(ct)
99                 },
100             )?
101             .build();
102         Ok(cose_encrypt.to_vec()?)
103     }
104 
105     /// Decrypt CBOR serializable data using a device key derived using `key_context`. Data needs to
106     /// include an `EncryptionHeader` on the COSE protected header.
decrypt_content_service_encryption_key( encrypted_context: &[u8], key_context: &[u8], ) -> Result<(Self, Vec<u8>), HwCryptoError>107     pub(crate) fn decrypt_content_service_encryption_key(
108         encrypted_context: &[u8],
109         key_context: &[u8],
110     ) -> Result<(Self, Vec<u8>), HwCryptoError> {
111         let context: coset::CoseEncrypt0 = coset::CborSerializable::from_slice(encrypted_context)?;
112         let encryption_header: EncryptionHeader = (&context.protected).try_into()?;
113         let kek = encryption_header.derive_service_encryption_key(key_context)?;
114 
115         let aes = crypto_provider::AesImpl;
116         let mut op = aes.begin_aead(
117             kek.into(),
118             crypto::aes::GcmMode::GcmTag16 { nonce: ZERO_NONCE },
119             crypto::SymmetricOperation::Decrypt,
120         )?;
121         let extended_aad = coset::enc_structure_data(
122             coset::EncryptionContext::CoseEncrypt0,
123             context.protected.clone(),
124             &[], // no external AAD
125         );
126         op.update_aad(&extended_aad)?;
127         let mut pt_data = op.update(&context.ciphertext.unwrap_or_default())?;
128         pt_data.try_extend_from_slice(
129             &op.finish()
130                 .map_err(|e| hwcrypto_err!(INVALID_KEY, "failed to decrypt context: {:?}", e))?,
131         )?;
132         Ok((encryption_header, pt_data))
133     }
134 }
135 
136 // Implementing conversion functions to easily convert from/to COSE headers and  `EncryptionHeader`s
137 impl TryFrom<&ProtectedHeader> for EncryptionHeader {
138     type Error = HwCryptoError;
139 
try_from(value: &ProtectedHeader) -> Result<EncryptionHeader, Self::Error>140     fn try_from(value: &ProtectedHeader) -> Result<EncryptionHeader, Self::Error> {
141         let cose_header_rest = &value.header.rest;
142         if cose_header_rest.len() != 2 {
143             return Err(hwcrypto_err!(
144                 BAD_PARAMETER,
145                 "header length was {} instead of 2",
146                 cose_header_rest.len()
147             ));
148         }
149         let mut key_derivation_context = None;
150         let mut header_version = None;
151         for element in cose_header_rest {
152             let label: i64 = element
153                 .0
154                 .clone()
155                 .to_cbor_value()?
156                 .into_integer()
157                 .map_err(|_| {
158                     hwcrypto_err!(
159                         SERIALIZATION_ERROR,
160                         "unsupported string header label {:?}",
161                         element.0
162                     )
163                 })?
164                 .try_into()
165                 .map_err(|_| {
166                     hwcrypto_err!(
167                         SERIALIZATION_ERROR,
168                         "error converting cose label {:?}",
169                         element.0
170                     )
171                 })?;
172             match label {
173                 KEY_DERIVATION_CTX_COSE_LABEL => {
174                     key_derivation_context =
175                         Some(parse_cborium_bytes_to_fixed_array(&element.1, "KEK context")?);
176                 }
177                 KEY_DERIVATION_VERSION_COSE_LABEL => {
178                     header_version = Some(parse_cborium_u32(&element.1, "header version")?);
179                 }
180                 _ => return Err(hwcrypto_err!(BAD_PARAMETER, "unknown label {}", label)),
181             }
182         }
183         let key_derivation_context = key_derivation_context
184             .ok_or(hwcrypto_err!(SERIALIZATION_ERROR, "couldn't parse key context"))?;
185         let header_version = header_version
186             .ok_or(hwcrypto_err!(SERIALIZATION_ERROR, "couldn't parse header version"))?;
187         Ok(Self::new(key_derivation_context, header_version))
188     }
189 }
190 
191 impl TryFrom<&EncryptionHeader> for Header {
192     type Error = HwCryptoError;
193 
try_from(value: &EncryptionHeader) -> Result<Header, Self::Error>194     fn try_from(value: &EncryptionHeader) -> Result<Header, Self::Error> {
195         let mut key_derivation_context = Vec::<u8>::new();
196         key_derivation_context.try_extend_from_slice(&value.key_derivation_context[..])?;
197         let cose_header = coset::HeaderBuilder::new()
198             .algorithm(coset::iana::Algorithm::A256GCM)
199             .value(KEY_DERIVATION_CTX_COSE_LABEL, Value::Bytes(key_derivation_context))
200             .value(KEY_DERIVATION_VERSION_COSE_LABEL, Value::Integer(value.header_version.into()))
201             .build();
202         Ok(cose_header)
203     }
204 }
205 
206 /// Get the base versioned encryption key used by the service to derive other versioned context
207 /// encryption keys
get_encryption_key(header_version: u32, key_context: &[u8]) -> Result<Vec<u8>, HwCryptoError>208 fn get_encryption_key(header_version: u32, key_context: &[u8]) -> Result<Vec<u8>, HwCryptoError> {
209     let mut key = Vec::<u8>::new();
210     key.try_reserve(SERVICE_KEK_LENGTH)?;
211     key.resize(SERVICE_KEK_LENGTH, 0);
212     let hwkey_session = Hwkey::open()
213         .map_err(|e| hwcrypto_err!(GENERIC_ERROR, "could not connect to hwkey service {:?}", e))?;
214     hwkey_session
215         .derive_key_req()
216         .unique_key()
217         .rollback_version_source(RollbackVersionSource::CommittedVersion)
218         .os_rollback_version(OsRollbackVersion::Version(header_version))
219         .derive(key_context, &mut key[..])
220         .map_err(|e| hwcrypto_err!(GENERIC_ERROR, "could derive key {:?}", e))?;
221     Ok(key)
222 }
223 
224 // Create an AES key compatible with the current crypto backend used
derive_key_hkdf( derivation_key: &[u8], derivation_context: &[u8], ) -> Result<crypto::aes::Key, HwCryptoError>225 fn derive_key_hkdf(
226     derivation_key: &[u8],
227     derivation_context: &[u8],
228 ) -> Result<crypto::aes::Key, HwCryptoError> {
229     let kdf = crypto_provider::HmacImpl;
230     let raw_key = kdf.hkdf(&[], &derivation_key, &derivation_context, SERVICE_KEK_LENGTH)?;
231     let key_material = crypto::aes::Key::Aes256(
232         raw_key.try_into().expect("should not fail, call with SERVICE_KEK_LENGTH returns 32 bytes"),
233     );
234     Ok(key_material)
235 }
236 
get_new_key_derivation_context() -> [u8; KEY_DERIVATION_CTX_LENGTH]237 fn get_new_key_derivation_context() -> [u8; KEY_DERIVATION_CTX_LENGTH] {
238     let mut rng = crypto_provider::RngImpl::default();
239     let mut key_ctx = [0u8; KEY_DERIVATION_CTX_LENGTH];
240     rng.fill_bytes(&mut key_ctx[..]);
241     key_ctx
242 }
243 
parse_cborium_bytes_to_fixed_array( value: &ciborium::value::Value, name: &str, ) -> Result<[u8; KEY_DERIVATION_CTX_LENGTH], HwCryptoError>244 fn parse_cborium_bytes_to_fixed_array(
245     value: &ciborium::value::Value,
246     name: &str,
247 ) -> Result<[u8; KEY_DERIVATION_CTX_LENGTH], HwCryptoError> {
248     let value_bytes = value.as_bytes().ok_or(hwcrypto_err!(
249         SERIALIZATION_ERROR,
250         "wrong type when trying to parse bytes for {}",
251         name,
252     ))?;
253     if value_bytes.len() != KEY_DERIVATION_CTX_LENGTH {
254         return Err(hwcrypto_err!(
255             SERIALIZATION_ERROR,
256             "wrong number of bytes for {}, found {}, expected {}",
257             name,
258             value_bytes.len(),
259             KEY_DERIVATION_CTX_LENGTH
260         ));
261     }
262     Ok(value_bytes.as_slice().try_into().expect("Shouldn't fail, we checked size already"))
263 }
264 
parse_cborium_u32( value: &ciborium::value::Value, value_name: &str, ) -> Result<u32, HwCryptoError>265 fn parse_cborium_u32(
266     value: &ciborium::value::Value,
267     value_name: &str,
268 ) -> Result<u32, HwCryptoError> {
269     let integer_value = value.as_integer().ok_or(hwcrypto_err!(
270         SERIALIZATION_ERROR,
271         "wrong type when trying to parse a u32 from {}",
272         value_name
273     ))?;
274     integer_value.try_into().map_err(|e| {
275         hwcrypto_err!(SERIALIZATION_ERROR, "Error converting {} to u32: {}", value_name, e)
276     })
277 }
278 
get_service_current_version() -> Result<u32, HwCryptoError>279 pub(crate) fn get_service_current_version() -> Result<u32, HwCryptoError> {
280     let hwkey_session = Hwkey::open()?;
281 
282     match hwkey_session.query_current_os_version(RollbackVersionSource::CommittedVersion) {
283         Ok(OsRollbackVersion::Version(n)) => Ok(n),
284         _ => Err(hwcrypto_err!(GENERIC_ERROR, "error communicating with HwKey service")),
285     }
286 }
287 
288 #[cfg(test)]
289 mod tests {
290     use super::*;
291     use test::{expect, expect_eq};
292 
293     #[test]
header_encryption_decryption()294     fn header_encryption_decryption() {
295         let header = EncryptionHeader::generate();
296         expect!(header.is_ok(), "couldn't generate header");
297         let header = header.unwrap();
298         let encrypted_content = header.encrypt_content_service_encryption_key(
299             b"fake_context",
300             Value::Bytes(b"test_data".to_vec()),
301         );
302         expect!(encrypted_content.is_ok(), "couldn't generate header");
303         let encrypted_content = encrypted_content.unwrap();
304         let decrypted_data = EncryptionHeader::decrypt_content_service_encryption_key(
305             &encrypted_content[..],
306             b"fake_context",
307         );
308         expect!(decrypted_data.is_ok(), "couldn't generate header");
309         let (decrypted_header, decrypted_content) = decrypted_data.unwrap();
310         let decrypted_content =
311             Value::from_slice(&decrypted_content[..]).unwrap().into_bytes().unwrap();
312         expect_eq!(
313             header.key_derivation_context,
314             decrypted_header.key_derivation_context,
315             "header key derivation context do not match"
316         );
317         expect_eq!(
318             header.header_version,
319             decrypted_header.header_version,
320             "header version do not match"
321         );
322         expect_eq!(decrypted_content, b"test_data", "decrypted data do not match");
323     }
324 }
325