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 const WRAPPED_CONTENT_TYPE_COSE_LABEL: i64 = -65541;
44
45 // `EncryptionHeaderKey` defines if we derive a device key bound to HwCrypto service or if we use a
46 // provided key for that. This key will then be used to encrypt some content.
47 pub(crate) enum EncryptionHeaderKey<'a> {
48 // `KeyGenerationContext` will be used to derive a key from a key bounded to HwCrypto service
49 KeyGenerationContext(&'a [u8]),
50 // `ProvidedHkdfKey` will be directly fed to the hkdf algorithm.
51 ProvidedHkdfKey(Vec<u8>),
52 }
53
54 #[derive(Copy, Clone, Debug, PartialEq)]
55 pub(crate) enum EncryptedContent {
56 DicePolicy = 1,
57 WrappedKeyMaterial = 2,
58 KeyMaterial = 3,
59 }
60
61 impl TryFrom<u64> for EncryptedContent {
62 type Error = HwCryptoError;
63
try_from(value: u64) -> Result<Self, Self::Error>64 fn try_from(value: u64) -> Result<Self, Self::Error> {
65 match value {
66 x if x == EncryptedContent::DicePolicy as u64 => Ok(EncryptedContent::DicePolicy),
67 x if x == EncryptedContent::WrappedKeyMaterial as u64 => {
68 Ok(EncryptedContent::WrappedKeyMaterial)
69 }
70 x if x == EncryptedContent::KeyMaterial as u64 => Ok(EncryptedContent::KeyMaterial),
71 _ => Err(hwcrypto_err!(
72 SERIALIZATION_ERROR,
73 "invalid value for EncryptedContent: {}",
74 value
75 )),
76 }
77 }
78 }
79
80 impl TryFrom<ciborium::value::Integer> for EncryptedContent {
81 type Error = HwCryptoError;
82
try_from(value: ciborium::value::Integer) -> Result<Self, Self::Error>83 fn try_from(value: ciborium::value::Integer) -> Result<Self, Self::Error> {
84 let value: u64 = value.try_into().map_err(|_| {
85 hwcrypto_err!(SERIALIZATION_ERROR, "couldn't convert CBOR integer into u64")
86 })?;
87 Ok(value.try_into().map_err(|_| {
88 hwcrypto_err!(
89 SERIALIZATION_ERROR,
90 "Error converting encrypted content type from ciborium value"
91 )
92 })?)
93 }
94 }
95
96 impl From<EncryptedContent> for ciborium::value::Integer {
from(value: EncryptedContent) -> Self97 fn from(value: EncryptedContent) -> Self {
98 (value as u64).into()
99 }
100 }
101
102 // Header used to derive a different key per each encrypted context. Encryption of the context is
103 // similar to what KeyMint does to wrap keys.
104 pub(crate) struct EncryptionHeader {
105 key_derivation_context: [u8; KEY_DERIVATION_CTX_LENGTH],
106 header_version: u32,
107 wrapped_content_type: EncryptedContent,
108 }
109
110 impl EncryptionHeader {
new( key_derivation_context: [u8; KEY_DERIVATION_CTX_LENGTH], header_version: u32, wrapped_content_type: EncryptedContent, ) -> Self111 fn new(
112 key_derivation_context: [u8; KEY_DERIVATION_CTX_LENGTH],
113 header_version: u32,
114 wrapped_content_type: EncryptedContent,
115 ) -> Self {
116 Self { key_derivation_context, header_version, wrapped_content_type }
117 }
118
generate(wrapped_content_type: EncryptedContent) -> Result<Self, HwCryptoError>119 pub(crate) fn generate(wrapped_content_type: EncryptedContent) -> Result<Self, HwCryptoError> {
120 let header_version = get_service_current_version()?;
121 Ok(Self::generate_with_version(header_version, wrapped_content_type))
122 }
123
generate_with_version( header_version: u32, wrapped_content_type: EncryptedContent, ) -> Self124 pub(crate) fn generate_with_version(
125 header_version: u32,
126 wrapped_content_type: EncryptedContent,
127 ) -> Self {
128 let key_derivation_context = get_new_key_derivation_context();
129 Self::new(key_derivation_context, header_version, wrapped_content_type)
130 }
131
132 // Function used to generate different device bound encryption keys tied to the HWCrypto service
133 // to be used for different purposes, which include VersionContext encryption and key wrapping.
derive_raw_service_encryption_key( &self, encryption_key: EncryptionHeaderKey, ) -> Result<Vec<u8>, HwCryptoError>134 pub(crate) fn derive_raw_service_encryption_key(
135 &self,
136 encryption_key: EncryptionHeaderKey,
137 ) -> Result<Vec<u8>, HwCryptoError> {
138 let encryption_key = match encryption_key {
139 EncryptionHeaderKey::KeyGenerationContext(key_context) => {
140 get_encryption_key(self.header_version, key_context)?
141 }
142 EncryptionHeaderKey::ProvidedHkdfKey(key) => {
143 if key.len() != SERVICE_KEK_LENGTH {
144 return Err(hwcrypto_err!(
145 INVALID_KEY,
146 "We only support hkdf keys of length {}",
147 SERVICE_KEK_LENGTH
148 ));
149 }
150 key
151 }
152 };
153 derive_key_hkdf(&encryption_key, &self.key_derivation_context[..])
154 }
155
derive_service_encryption_key( &self, encryption_key: EncryptionHeaderKey, ) -> Result<crypto::aes::Key, HwCryptoError>156 pub(crate) fn derive_service_encryption_key(
157 &self,
158 encryption_key: EncryptionHeaderKey,
159 ) -> Result<crypto::aes::Key, HwCryptoError> {
160 let raw_key = self.derive_raw_service_encryption_key(encryption_key)?;
161 let key_material = crypto::aes::Key::Aes256(
162 raw_key
163 .try_into()
164 .expect("should not fail, call with SERVICE_KEK_LENGTH returns 32 bytes"),
165 );
166 Ok(key_material)
167 }
168
169 /// Encrypt CBOR serializable data using a device key derived using `key_context`
encrypt_content_service_encryption_key<T: AsCborValue>( &self, key_context: EncryptionHeaderKey, content: T, ) -> Result<Vec<u8>, HwCryptoError>170 pub(crate) fn encrypt_content_service_encryption_key<T: AsCborValue>(
171 &self,
172 key_context: EncryptionHeaderKey,
173 content: T,
174 ) -> Result<Vec<u8>, HwCryptoError> {
175 let kek = self.derive_service_encryption_key(key_context)?;
176 let aes = crypto_provider::AesImpl;
177 let cose_encrypt = coset::CoseEncrypt0Builder::new()
178 .protected(self.try_into()?)
179 .try_create_ciphertext::<_, HwCryptoError>(
180 &content.to_cbor_value()?.to_vec()?,
181 &[],
182 move |pt, aad| {
183 let mut op = aes.begin_aead(
184 kek.into(),
185 crypto::aes::GcmMode::GcmTag16 { nonce: ZERO_NONCE },
186 crypto::SymmetricOperation::Encrypt,
187 )?;
188 op.update_aad(aad)?;
189 let mut ct = op.update(pt)?;
190 ct.try_extend_from_slice(&op.finish()?)?;
191 Ok(ct)
192 },
193 )?
194 .build();
195 Ok(cose_encrypt.to_vec()?)
196 }
197
198 /// Decrypt CBOR serializable data using a device key derived using `key_context`. Data needs to
199 /// include an `EncryptionHeader` on the COSE protected header.
decrypt_content_service_encryption_key( encrypted_context: &[u8], key_context: EncryptionHeaderKey, wrapped_content_type: EncryptedContent, ) -> Result<(Self, Vec<u8>), HwCryptoError>200 pub(crate) fn decrypt_content_service_encryption_key(
201 encrypted_context: &[u8],
202 key_context: EncryptionHeaderKey,
203 wrapped_content_type: EncryptedContent,
204 ) -> Result<(Self, Vec<u8>), HwCryptoError> {
205 let context: coset::CoseEncrypt0 = coset::CborSerializable::from_slice(encrypted_context)?;
206 let encryption_header: EncryptionHeader = (&context.protected).try_into()?;
207 let kek = encryption_header.derive_service_encryption_key(key_context)?;
208
209 if encryption_header.wrapped_content_type != wrapped_content_type {
210 return Err(hwcrypto_err!(
211 BAD_PARAMETER,
212 "provided content has wrong content. Expected {:?}",
213 wrapped_content_type
214 ));
215 }
216
217 let aes = crypto_provider::AesImpl;
218 let mut op = aes.begin_aead(
219 kek.into(),
220 crypto::aes::GcmMode::GcmTag16 { nonce: ZERO_NONCE },
221 crypto::SymmetricOperation::Decrypt,
222 )?;
223 let extended_aad = coset::enc_structure_data(
224 coset::EncryptionContext::CoseEncrypt0,
225 context.protected.clone(),
226 &[], // no external AAD
227 );
228 op.update_aad(&extended_aad)?;
229 let mut pt_data = op.update(&context.ciphertext.unwrap_or_default())?;
230 pt_data.try_extend_from_slice(
231 &op.finish()
232 .map_err(|e| hwcrypto_err!(INVALID_KEY, "failed to decrypt context: {:?}", e))?,
233 )?;
234 Ok((encryption_header, pt_data))
235 }
236 }
237
238 // Implementing conversion functions to easily convert from/to COSE headers and `EncryptionHeader`s
239 impl TryFrom<&ProtectedHeader> for EncryptionHeader {
240 type Error = HwCryptoError;
241
try_from(value: &ProtectedHeader) -> Result<EncryptionHeader, Self::Error>242 fn try_from(value: &ProtectedHeader) -> Result<EncryptionHeader, Self::Error> {
243 let cose_header_rest = &value.header.rest;
244 if cose_header_rest.len() != 3 {
245 return Err(hwcrypto_err!(
246 BAD_PARAMETER,
247 "header length was {} instead of 3",
248 cose_header_rest.len()
249 ));
250 }
251 let mut key_derivation_context = None;
252 let mut header_version = None;
253 let mut wrapped_content_type: Option<EncryptedContent> = None;
254 for element in cose_header_rest {
255 let label: i64 = element
256 .0
257 .clone()
258 .to_cbor_value()?
259 .into_integer()
260 .map_err(|_| {
261 hwcrypto_err!(
262 SERIALIZATION_ERROR,
263 "unsupported string header label {:?}",
264 element.0
265 )
266 })?
267 .try_into()
268 .map_err(|_| {
269 hwcrypto_err!(
270 SERIALIZATION_ERROR,
271 "error converting cose label {:?}",
272 element.0
273 )
274 })?;
275 match label {
276 KEY_DERIVATION_CTX_COSE_LABEL => {
277 key_derivation_context =
278 Some(parse_cborium_bytes_to_fixed_array(&element.1, "KEK context")?);
279 }
280 KEY_DERIVATION_VERSION_COSE_LABEL => {
281 header_version = Some(parse_cborium_u32(&element.1, "header version")?);
282 }
283 WRAPPED_CONTENT_TYPE_COSE_LABEL => {
284 wrapped_content_type = Some(
285 element
286 .1
287 .as_integer()
288 .ok_or(hwcrypto_err!(
289 BAD_PARAMETER,
290 "wrapped_content_type was not an integer"
291 ))?
292 .try_into()?,
293 );
294 }
295 _ => return Err(hwcrypto_err!(BAD_PARAMETER, "unknown label {}", label)),
296 }
297 }
298 let key_derivation_context = key_derivation_context
299 .ok_or(hwcrypto_err!(SERIALIZATION_ERROR, "couldn't parse key context"))?;
300 let header_version = header_version
301 .ok_or(hwcrypto_err!(SERIALIZATION_ERROR, "couldn't parse header version"))?;
302 let wrapped_content_type = wrapped_content_type
303 .ok_or(hwcrypto_err!(SERIALIZATION_ERROR, "couldn't parse content type"))?;
304 Ok(Self::new(key_derivation_context, header_version, wrapped_content_type))
305 }
306 }
307
308 impl TryFrom<&EncryptionHeader> for Header {
309 type Error = HwCryptoError;
310
try_from(value: &EncryptionHeader) -> Result<Header, Self::Error>311 fn try_from(value: &EncryptionHeader) -> Result<Header, Self::Error> {
312 let mut key_derivation_context = Vec::<u8>::new();
313 key_derivation_context.try_extend_from_slice(&value.key_derivation_context[..])?;
314 let cose_header = coset::HeaderBuilder::new()
315 .algorithm(coset::iana::Algorithm::A256GCM)
316 .value(KEY_DERIVATION_CTX_COSE_LABEL, Value::Bytes(key_derivation_context))
317 .value(KEY_DERIVATION_VERSION_COSE_LABEL, Value::Integer(value.header_version.into()))
318 .value(
319 WRAPPED_CONTENT_TYPE_COSE_LABEL,
320 Value::Integer(value.wrapped_content_type.into()),
321 )
322 .build();
323 Ok(cose_header)
324 }
325 }
326
327 /// Get the base versioned encryption key used by the service to derive other versioned context
328 /// encryption keys
get_encryption_key(header_version: u32, key_context: &[u8]) -> Result<Vec<u8>, HwCryptoError>329 fn get_encryption_key(header_version: u32, key_context: &[u8]) -> Result<Vec<u8>, HwCryptoError> {
330 let mut key = Vec::<u8>::new();
331 key.try_reserve(SERVICE_KEK_LENGTH)?;
332 key.resize(SERVICE_KEK_LENGTH, 0);
333 let hwkey_session = Hwkey::open()
334 .map_err(|e| hwcrypto_err!(GENERIC_ERROR, "could not connect to hwkey service {:?}", e))?;
335 hwkey_session
336 .derive_key_req()
337 .unique_key()
338 .rollback_version_source(RollbackVersionSource::CommittedVersion)
339 .os_rollback_version(OsRollbackVersion::Version(header_version))
340 .derive(key_context, &mut key[..])
341 .map_err(|e| hwcrypto_err!(GENERIC_ERROR, "could derive key {:?}", e))?;
342 Ok(key)
343 }
344
345 // Create an AES key compatible with the current crypto backend used
derive_key_hkdf( derivation_key: &[u8], derivation_context: &[u8], ) -> Result<Vec<u8>, HwCryptoError>346 fn derive_key_hkdf(
347 derivation_key: &[u8],
348 derivation_context: &[u8],
349 ) -> Result<Vec<u8>, HwCryptoError> {
350 let kdf = crypto_provider::HmacImpl;
351 Ok(kdf.hkdf(&[], &derivation_key, &derivation_context, SERVICE_KEK_LENGTH)?)
352 }
353
get_new_key_derivation_context() -> [u8; KEY_DERIVATION_CTX_LENGTH]354 fn get_new_key_derivation_context() -> [u8; KEY_DERIVATION_CTX_LENGTH] {
355 let mut rng = crypto_provider::RngImpl::default();
356 let mut key_ctx = [0u8; KEY_DERIVATION_CTX_LENGTH];
357 rng.fill_bytes(&mut key_ctx[..]);
358 key_ctx
359 }
360
parse_cborium_bytes_to_fixed_array( value: &ciborium::value::Value, name: &str, ) -> Result<[u8; KEY_DERIVATION_CTX_LENGTH], HwCryptoError>361 pub(crate) fn parse_cborium_bytes_to_fixed_array(
362 value: &ciborium::value::Value,
363 name: &str,
364 ) -> Result<[u8; KEY_DERIVATION_CTX_LENGTH], HwCryptoError> {
365 let value_bytes = value.as_bytes().ok_or(hwcrypto_err!(
366 SERIALIZATION_ERROR,
367 "wrong type when trying to parse bytes for {}",
368 name,
369 ))?;
370 if value_bytes.len() != KEY_DERIVATION_CTX_LENGTH {
371 return Err(hwcrypto_err!(
372 SERIALIZATION_ERROR,
373 "wrong number of bytes for {}, found {}, expected {}",
374 name,
375 value_bytes.len(),
376 KEY_DERIVATION_CTX_LENGTH
377 ));
378 }
379 Ok(value_bytes.as_slice().try_into().expect("Shouldn't fail, we checked size already"))
380 }
381
parse_cborium_u32( value: &ciborium::value::Value, value_name: &str, ) -> Result<u32, HwCryptoError>382 fn parse_cborium_u32(
383 value: &ciborium::value::Value,
384 value_name: &str,
385 ) -> Result<u32, HwCryptoError> {
386 let integer_value = value.as_integer().ok_or(hwcrypto_err!(
387 SERIALIZATION_ERROR,
388 "wrong type when trying to parse a u32 from {}",
389 value_name
390 ))?;
391 integer_value.try_into().map_err(|e| {
392 hwcrypto_err!(SERIALIZATION_ERROR, "Error converting {} to u32: {}", value_name, e)
393 })
394 }
395
get_service_current_version() -> Result<u32, HwCryptoError>396 pub(crate) fn get_service_current_version() -> Result<u32, HwCryptoError> {
397 let hwkey_session = Hwkey::open()?;
398
399 match hwkey_session.query_current_os_version(RollbackVersionSource::CommittedVersion) {
400 Ok(OsRollbackVersion::Version(n)) => Ok(n),
401 _ => Err(hwcrypto_err!(GENERIC_ERROR, "error communicating with HwKey service")),
402 }
403 }
404
405 #[cfg(test)]
406 mod tests {
407 use super::*;
408 use test::{expect, expect_eq};
409
410 #[test]
header_encryption_decryption()411 fn header_encryption_decryption() {
412 let header = EncryptionHeader::generate(EncryptedContent::DicePolicy);
413 expect!(header.is_ok(), "couldn't generate header");
414 let header = header.unwrap();
415 let encrypted_content = header.encrypt_content_service_encryption_key(
416 EncryptionHeaderKey::KeyGenerationContext(b"fake_context"),
417 Value::Bytes(b"test_data".to_vec()),
418 );
419 expect!(encrypted_content.is_ok(), "couldn't generate header");
420 let encrypted_content = encrypted_content.unwrap();
421 let decrypted_data = EncryptionHeader::decrypt_content_service_encryption_key(
422 &encrypted_content[..],
423 EncryptionHeaderKey::KeyGenerationContext(b"fake_context"),
424 EncryptedContent::DicePolicy,
425 );
426 expect!(decrypted_data.is_ok(), "couldn't generate header");
427 let (decrypted_header, decrypted_content) = decrypted_data.unwrap();
428 let decrypted_content =
429 Value::from_slice(&decrypted_content[..]).unwrap().into_bytes().unwrap();
430 expect_eq!(
431 header.key_derivation_context,
432 decrypted_header.key_derivation_context,
433 "header key derivation context do not match"
434 );
435 expect_eq!(
436 header.header_version,
437 decrypted_header.header_version,
438 "header version do not match"
439 );
440 expect_eq!(decrypted_content, b"test_data", "decrypted data do not match");
441 }
442 }
443