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