diff options
author | Orlando Arbildo <oarbildo@google.com> | 2024-02-13 23:36:12 +0000 |
---|---|---|
committer | Orlando Arbildo <oarbildo@google.com> | 2024-04-11 23:45:52 +0000 |
commit | eaf64a867e83362fa230fb0de3419ea5e79e3267 (patch) | |
tree | 6ebc1b349d60c6ca091949280859bd1c9d788a8b | |
parent | 34991a36f2aa778ad56da805bed6f86c453641f3 (diff) | |
download | sample-eaf64a867e83362fa230fb0de3419ea5e79e3267.tar.gz |
Initial implementation of hwkeyDeriveVersioned
Implement HWCryptoDeviceKeyAccess::hwkeyDeriveVersioned.
Currently it doesn't uses a DICE policy for versioning,
but a custom encrypted blob that can be consumed by Trusty
Bug: 284156656
Test: build & unit test to connect to server
Change-Id: I582a21115c7a150721a81e1cbe3cce1057e8491a
-rw-r--r-- | hwcrypto/hwcrypto_consts.json | 10 | ||||
-rw-r--r-- | hwcrypto/hwkey_srv_fake_provider.c | 10 | ||||
-rw-r--r-- | hwcryptohal/common/err.rs | 7 | ||||
-rw-r--r-- | hwcryptohal/server/helpers.rs | 55 | ||||
-rw-r--r-- | hwcryptohal/server/hwcrypto_device_key.rs | 290 | ||||
-rw-r--r-- | hwcryptohal/server/lib.rs | 2 | ||||
-rw-r--r-- | hwcryptohal/server/opaque_key.rs | 106 | ||||
-rw-r--r-- | hwcryptohal/server/rules.mk | 1 | ||||
-rw-r--r-- | hwcryptohal/server/service_encryption_key.rs | 324 |
9 files changed, 780 insertions, 25 deletions
diff --git a/hwcrypto/hwcrypto_consts.json b/hwcrypto/hwcrypto_consts.json index 9e81027..91e586d 100644 --- a/hwcrypto/hwcrypto_consts.json +++ b/hwcrypto/hwcrypto_consts.json @@ -75,6 +75,16 @@ "name": "HWBCC_UNITTEST_RUST_APP_UUID", "value": "67925337-2c03-49ed-9240-d51b6fea3e30", "type": "uuid" + }, + { + "name": "HWCRYPTOHAL_UNITTEST_RUST_APP_UUID", + "value": "f41a7796-975a-4279-8cc4-b73f8820430d", + "type": "uuid" + }, + { + "name": "HWCRYPTOHAL_RUST_APP_UUID", + "value": "f49e28c4-d8b0-41c2-8197-11f27402c0f8", + "type": "uuid" } ] } diff --git a/hwcrypto/hwkey_srv_fake_provider.c b/hwcrypto/hwkey_srv_fake_provider.c index d8b8435..af34468 100644 --- a/hwcrypto/hwkey_srv_fake_provider.c +++ b/hwcrypto/hwkey_srv_fake_provider.c @@ -604,8 +604,15 @@ static const uuid_t km_rust_uuid = KM_RUST_APP_UUID; #if TEST_BUILD /* KM rust unit test uuid */ static const uuid_t km_rust_unittest_uuid = KM_RUST_UNITTEST_UUID; + +/* HWCrypto HAL rust unit test uuid */ +static const uuid_t hwcryptohal_rust_unittest_uuid = + HWCRYPTOHAL_UNITTEST_RUST_APP_UUID; #endif +/* HWCrypto HAL rust unit test uuid */ +static const uuid_t hwcryptohal_rust_uuid = HWCRYPTOHAL_RUST_APP_UUID; + static uint8_t kak_salt[KM_KAK_SIZE] = { 0x70, 0xc4, 0x7c, 0xfa, 0x2c, 0xb1, 0xee, 0xdc, 0xa5, 0xdf, 0xbc, 0x8d, 0xd4, 0xf7, 0x0d, 0x42, 0x93, 0x3b, 0x7f, 0x7b, 0xc2, 0x9e, @@ -817,7 +824,10 @@ static const uuid_t* allowed_clients[] = { &km_rust_uuid, #if TEST_BUILD &km_rust_unittest_uuid, + /*HWCrypto HAL needs to derive key and access keylots*/ + &hwcryptohal_rust_unittest_uuid, #endif + &hwcryptohal_rust_uuid, /* Needs access to opaque keys */ &hwaes_uuid, /* Needs to derive keys */ diff --git a/hwcryptohal/common/err.rs b/hwcryptohal/common/err.rs index 50192b9..eb49044 100644 --- a/hwcryptohal/common/err.rs +++ b/hwcryptohal/common/err.rs @@ -21,6 +21,7 @@ pub use android_hardware_security_see::aidl::android::hardware::security::see::h use android_hardware_security_see::binder; use core::array::TryFromSliceError; use coset::CoseError; +use tipc::TipcError; /// Macro used to create a `HwCryptoError::HalError` by providing the AIDL `HalErrorCode` and a /// message: `hwcrypto_err!(UNSUPPORTED, "unsupported operation")` @@ -53,6 +54,12 @@ impl From<kmr_wire::CborError> for HwCryptoError { } } +impl From<TipcError> for HwCryptoError { + fn from(e: TipcError) -> Self { + hwcrypto_err!(GENERIC_ERROR, "tipc communication error: {:?}", e) + } +} + impl From<kmr_common::Error> for HwCryptoError { fn from(e: kmr_common::Error) -> Self { HwCryptoError::KmError(e) diff --git a/hwcryptohal/server/helpers.rs b/hwcryptohal/server/helpers.rs new file mode 100644 index 0000000..500d4cf --- /dev/null +++ b/hwcryptohal/server/helpers.rs @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Helper functions that includes data transformation for AIDL types. + +/// Macro to create enums that can easily be used as cose labels for serialization +/// It expects the macro definition to have the following form: +/// +/// cose_enum_gen! { +/// enum CoseEnumName { +/// CoseEnumField1 = value1, +/// CoseEnumField2 = value2, +/// } +/// } +#[macro_export] +macro_rules! cose_enum_gen { + (enum $name:ident {$($field:ident = $field_val:literal),+ $(,)*}) => { + enum $name { + $($field = $field_val),+ + } + + impl TryFrom<i64> for $name { + type Error = hwcryptohal_common::err::HwCryptoError; + + fn try_from(value: i64) -> Result<Self, Self::Error> { + match value { + $(x if x == $name::$field as i64 => Ok($name::$field)),+, + _ => Err(hwcrypto_err!(SERIALIZATION_ERROR, "unsupported COSE enum label val {}", value)), + } + } + } + + impl TryFrom<ciborium::value::Integer> for $name { + type Error = coset::CoseError; + + fn try_from(value: ciborium::value::Integer) -> Result<Self, Self::Error> { + let value: i64 = value.try_into()?; + Ok(value.try_into().map_err(|_| coset::CoseError::EncodeFailed)?) + } + } + } +} diff --git a/hwcryptohal/server/hwcrypto_device_key.rs b/hwcryptohal/server/hwcrypto_device_key.rs index 0a65036..6fefdcd 100644 --- a/hwcryptohal/server/hwcrypto_device_key.rs +++ b/hwcryptohal/server/hwcrypto_device_key.rs @@ -18,16 +18,165 @@ //! retrieve device specific keys. use android_hardware_security_see::aidl::android::hardware::security::see::hwcrypto::{ - IHwCryptoKey::BnHwCryptoKey, + types::{KeyLifetime::KeyLifetime, KeyType::KeyType, KeyUse::KeyUse}, IHwCryptoKey::{ - DerivedKey::DerivedKey, DerivedKeyParameters::DerivedKeyParameters, - DiceBoundDerivationKey::DiceBoundDerivationKey, DiceBoundKeyResult::DiceBoundKeyResult, + BnHwCryptoKey, DerivedKey::DerivedKey, DerivedKeyParameters::DerivedKeyParameters, + DeviceKeyId::DeviceKeyId, DiceBoundDerivationKey::DiceBoundDerivationKey, + DiceBoundKeyResult::DiceBoundKeyResult, DiceCurrentBoundKeyResult::DiceCurrentBoundKeyResult, IHwCryptoKey, }, + KeyPolicy::KeyPolicy, }; use android_hardware_security_see::binder; +use ciborium::{cbor, Value}; +use coset::{AsCborValue, CborSerializable, CoseError}; +use hwcryptohal_common::{err::HwCryptoError, hwcrypto_err}; +use hwkey::{Hwkey, KdfVersion}; use tipc::Uuid; +use crate::cose_enum_gen; +use crate::opaque_key; +use crate::service_encryption_key::{self, EncryptionHeader}; + +const DEVICE_KEY_CTX: &[u8] = b"device_key_derivation_contextKEK"; +const DICE_BOUND_POLICY_CTX: &[u8] = b"dice_bound"; + +// enum used for serializing the `VersionContext` +cose_enum_gen! { + enum VersionContextCoseLabels { + Uuid = -65537, + Version = -65538, + } +} + +// TODO: `ConnectionInformation` will be opaque to the HwCrypto service once we have a connection +// manager. +struct ConnectionInformation { + uuid: Uuid, +} + +// Mock version object to be used until we have more DICE support. It is based on the trusty version +// retrievable from HwKey and the uuid of the caller. `VersionContext`` encryption is similar to +// what KeyMint uses to wrap keys. +struct VersionContext { + uuid: Uuid, + version: u32, + header: Option<EncryptionHeader>, +} + +impl VersionContext { + fn get_current_version() -> Result<u32, HwCryptoError> { + service_encryption_key::get_service_current_version() + } + + fn new_current(uuid: Uuid) -> Result<Self, HwCryptoError> { + let header = Some(EncryptionHeader::generate()?); + let version = Self::get_current_version()?; + Ok(VersionContext { uuid, version, header }) + } + + fn new_current_encrypted(uuid: Uuid) -> Result<Vec<u8>, HwCryptoError> { + let ctx = Self::new_current(uuid)?; + Ok(ctx.encrypt_context()?) + } + + fn check_version(&self) -> Result<(), HwCryptoError> { + let current_version = Self::get_current_version()?; + if self.version > current_version { + return Err(hwcrypto_err!(BAD_PARAMETER, "version is not valid")); + } + Ok(()) + } + + fn check_context(&self, connection: ConnectionInformation) -> Result<(), HwCryptoError> { + if connection.uuid != self.uuid { + return Err(hwcrypto_err!(BAD_PARAMETER, "uuid mismatch")); + } + self.check_version() + } + + fn check_encrypted_context( + encrypted_ctx: &[u8], + connection: ConnectionInformation, + ) -> Result<(), HwCryptoError> { + let context = Self::decrypt_context(encrypted_ctx)?; + context.check_context(connection) + } + + fn is_context_current(encrypted_ctx: &[u8]) -> Result<bool, HwCryptoError> { + let context = Self::decrypt_context(encrypted_ctx)?; + let current_version = Self::get_current_version()?; + Ok(context.version >= current_version) + } + + fn decrypt_context(encrypted_context: &[u8]) -> Result<Self, HwCryptoError> { + let (version_ctx_header, decrypted_data) = + EncryptionHeader::decrypt_content_service_encryption_key( + encrypted_context, + DEVICE_KEY_CTX, + )?; + + let mut version_context = + VersionContext::from_cbor_value(Value::from_slice(&decrypted_data[..])?)?; + version_context.header = Some(version_ctx_header); + Ok(version_context) + } + + fn encrypt_context(mut self) -> Result<Vec<u8>, HwCryptoError> { + let header = self.header.take().ok_or(hwcrypto_err!(BAD_PARAMETER, "no header found"))?; + header.encrypt_content_service_encryption_key(DEVICE_KEY_CTX, self) + } + + fn get_stable_context(encrypted_context: &[u8]) -> Result<Vec<u8>, HwCryptoError> { + let decrypted_context = Self::decrypt_context(encrypted_context)?; + Ok(decrypted_context.to_cbor_value()?.to_vec()?) + } +} + +impl AsCborValue for VersionContext { + fn to_cbor_value(self) -> Result<Value, CoseError> { + cbor!({ + (VersionContextCoseLabels::Uuid as i64) => self.uuid.to_string(), + (VersionContextCoseLabels::Version as i64) => self.version, + }) + .map_err(|_| CoseError::ExtraneousData) + } + + fn from_cbor_value(value: Value) -> Result<Self, CoseError> { + let version_context = value.into_map().map_err(|_| CoseError::ExtraneousData)?; + let mut uuid: Option<Uuid> = None; + let mut version: Option<u32> = None; + for (map_key, map_val) in version_context { + match map_key { + Value::Integer(key) => { + match key.try_into().map_err(|_| CoseError::EncodeFailed)? { + VersionContextCoseLabels::Uuid => { + let uuid_str = + map_val.into_text().map_err(|_| CoseError::EncodeFailed)?; + let parsed_uuid = Uuid::new_from_string(&uuid_str) + .map_err(|_| CoseError::EncodeFailed)?; + uuid = Some(parsed_uuid); + } + VersionContextCoseLabels::Version => { + let parsed_version = map_val + .into_integer() + .map_err(|_| CoseError::EncodeFailed)? + .try_into() + .map_err(|_| CoseError::ExtraneousData)?; + version = Some(parsed_version); + } + } + } + _ => return Err(CoseError::ExtraneousData), + } + } + let uuid = uuid.ok_or(CoseError::EncodeFailed)?; + let version = version.ok_or(CoseError::EncodeFailed)?; + // Header travels in the clear, the decoded section only contains the encrypted fields + Ok(VersionContext { uuid, version, header: None }) + } +} + /// The `IHwCryptoKey` implementation. #[derive(Debug)] pub struct HwCryptoKey { @@ -42,28 +191,100 @@ impl HwCryptoKey { let hwcrypto_device_key = HwCryptoKey { uuid }; BnHwCryptoKey::new_binder(hwcrypto_device_key, binder::BinderFeatures::default()) } + + fn derive_dice_policy_bound_key( + &self, + derivation_key: &DiceBoundDerivationKey, + dice_policy_for_key_version: &[u8], + ) -> Result<DiceBoundKeyResult, HwCryptoError> { + // Verifying provided DICE policy + let connection_info = ConnectionInformation { uuid: self.uuid.clone() }; + VersionContext::check_encrypted_context(dice_policy_for_key_version, connection_info)?; + // Getting back a stable DICE policy for context, so keys derived with the same version will + // match + let dice_context = VersionContext::get_stable_context(dice_policy_for_key_version)?; + let mut concat_context = Vec::<u8>::new(); + concat_context.try_reserve(DICE_BOUND_POLICY_CTX.len())?; + concat_context.extend_from_slice(DICE_BOUND_POLICY_CTX); + concat_context.try_reserve(dice_context.len())?; + concat_context.extend_from_slice(dice_context.as_slice()); + + // The returned key will only be used for derivation, so fixing tis type to HMAC_SHA256 + let key_type = KeyType::HMAC_SHA256; + let key_size = opaque_key::get_key_size_in_bytes(&key_type)?; + // Create an array big enough to hold the bytes of the derived key material + let mut derived_key = Vec::<u8>::new(); + derived_key.try_reserve(key_size)?; + derived_key.resize(key_size, 0); + + match derivation_key { + DiceBoundDerivationKey::KeyId(key_id) => { + let hwkey_session = Hwkey::open().map_err(|e| { + hwcrypto_err!(GENERIC_ERROR, "could not connect to hwkey service {:?}", e) + })?; + let session_req = match *key_id { + DeviceKeyId::DEVICE_BOUND_KEY => { + Ok(hwkey_session.derive_key_req().unique_key()) + } + DeviceKeyId::BATCH_KEY => Ok(hwkey_session.derive_key_req().shared_key()), + _ => Err(hwcrypto_err!(UNSUPPORTED, "unknown key id {:?}", key_id)), + }?; + + session_req + .kdf(KdfVersion::Best) + .derive(&concat_context, &mut derived_key[..]) + .map_err(|e| hwcrypto_err!(GENERIC_ERROR, "failed to derive key {:?}", e))?; + + let policy = KeyPolicy { + usage: KeyUse::DERIVE, + keyLifetime: KeyLifetime::EPHEMERAL, + keyPermissions: Vec::new(), + keyType: key_type, + keyManagementKey: false, + }; + // Create a new opaque key from the generated key material + let km = opaque_key::generate_key_material(&policy.keyType, Some(derived_key))?; + let key = opaque_key::OpaqueKey::new_binder(&policy, km) + .map_err(|e| hwcrypto_err!(GENERIC_ERROR, "failed to create key {:?}", e))?; + let dice_policy_current = + VersionContext::is_context_current(dice_policy_for_key_version)?; + Ok(DiceBoundKeyResult { + diceBoundKey: Some(key), + dicePolicyWasCurrent: dice_policy_current, + }) + } + DiceBoundDerivationKey::OpaqueKey(_opaque_key) => Err(hwcrypto_err!( + UNSUPPORTED, + "derivation of DICE bound keys using opaque keys not supported yet" + )), + } + } } impl IHwCryptoKey for HwCryptoKey { fn deriveCurrentDicePolicyBoundKey( &self, - _derivation_key: &DiceBoundDerivationKey, + derivation_key: &DiceBoundDerivationKey, ) -> binder::Result<DiceCurrentBoundKeyResult> { - Err(binder::Status::new_exception_str( - binder::ExceptionCode::UNSUPPORTED_OPERATION, - Some("operation has not been implemented yet"), - )) + let dice_policy = VersionContext::new_current_encrypted(self.uuid.clone())?; + let derived_key_result = self.derive_dice_policy_bound_key(derivation_key, &dice_policy)?; + let DiceBoundKeyResult { diceBoundKey: key, dicePolicyWasCurrent: policy_current } = + derived_key_result; + if !policy_current { + return Err(binder::Status::new_exception_str( + binder::ExceptionCode::UNSUPPORTED_OPERATION, + Some("generated a policy that was not the latest"), + )); + } + Ok(DiceCurrentBoundKeyResult { diceBoundKey: key, dicePolicyForKeyVersion: dice_policy }) } fn deriveDicePolicyBoundKey( &self, - _derivation_key: &DiceBoundDerivationKey, - _dice_policy_for_key_version: &[u8], + derivation_key: &DiceBoundDerivationKey, + dice_policy_for_key_version: &[u8], ) -> binder::Result<DiceBoundKeyResult> { - Err(binder::Status::new_exception_str( - binder::ExceptionCode::UNSUPPORTED_OPERATION, - Some("operation has not been implemented yet"), - )) + Ok(self.derive_dice_policy_bound_key(derivation_key, dice_policy_for_key_version)?) } fn deriveKey(&self, _parameters: &DerivedKeyParameters) -> binder::Result<DerivedKey> { @@ -73,3 +294,44 @@ impl IHwCryptoKey for HwCryptoKey { )) } } + +#[cfg(test)] +mod tests { + use super::*; + use test::{assert_ok, expect}; + + #[test] + fn derived_dice_bound_keys() { + let hw_device_key = HwCryptoKey::new_binder( + Uuid::new_from_string("f41a7796-975a-4279-8cc4-b73f8820430d").unwrap(), + ); + + let derivation_key = DiceBoundDerivationKey::KeyId(DeviceKeyId::DEVICE_BOUND_KEY); + let key_and_policy = + assert_ok!(hw_device_key.deriveCurrentDicePolicyBoundKey(&derivation_key)); + let DiceCurrentBoundKeyResult { diceBoundKey: key, dicePolicyForKeyVersion: policy } = + key_and_policy; + expect!(key.is_some(), "should have received a key"); + expect!(policy.len() > 0, "should have received a DICE policy"); + let key_and_policy = + assert_ok!(hw_device_key.deriveDicePolicyBoundKey(&derivation_key, &policy)); + let DiceBoundKeyResult { diceBoundKey: key, dicePolicyWasCurrent: current_policy } = + key_and_policy; + expect!(key.is_some(), "should have received a key"); + expect!(current_policy, "policy should have been current"); + + let derivation_key = DiceBoundDerivationKey::KeyId(DeviceKeyId::BATCH_KEY); + let key_and_policy = + assert_ok!(hw_device_key.deriveCurrentDicePolicyBoundKey(&derivation_key)); + let DiceCurrentBoundKeyResult { diceBoundKey: key, dicePolicyForKeyVersion: policy } = + key_and_policy; + expect!(key.is_some(), "should have received a key"); + expect!(policy.len() > 0, "should have received a DICE policy"); + let key_and_policy = + assert_ok!(hw_device_key.deriveDicePolicyBoundKey(&derivation_key, &policy)); + let DiceBoundKeyResult { diceBoundKey: key, dicePolicyWasCurrent: current_policy } = + key_and_policy; + expect!(key.is_some(), "should have received a key"); + expect!(current_policy, "policy should have been current"); + } +} diff --git a/hwcryptohal/server/lib.rs b/hwcryptohal/server/lib.rs index 8b47b3d..d19bfe7 100644 --- a/hwcryptohal/server/lib.rs +++ b/hwcryptohal/server/lib.rs @@ -22,9 +22,11 @@ pub mod hwcrypto_ipc_server; mod crypto_provider; mod ffi_bindings; +mod helpers; mod hwcrypto_device_key; mod opaque_key; mod platform_functions; +mod service_encryption_key; #[cfg(test)] mod tests { diff --git a/hwcryptohal/server/opaque_key.rs b/hwcryptohal/server/opaque_key.rs index c5b2cba..36e9d15 100644 --- a/hwcryptohal/server/opaque_key.rs +++ b/hwcryptohal/server/opaque_key.rs @@ -146,7 +146,7 @@ impl OpaqueKey { pub(crate) fn generate_opaque_key( policy: &KeyPolicy, ) -> binder::Result<binder::Strong<dyn IOpaqueKey>> { - let key_material = generate_key_material(&policy.keyType)?; + let key_material = generate_key_material(&policy.keyType, None)?; OpaqueKey::new_binder(policy, key_material) } } @@ -273,6 +273,16 @@ pub(crate) fn check_key_material_with_policy( } } +// Get key size in bytesgiven the backend AES key type. Used to check if we received enough bytes +// from the caller for an AES key. +fn get_aes_variant_key_size(variant: &crypto::aes::Variant) -> usize { + match variant { + crypto::aes::Variant::Aes128 => 16, + crypto::aes::Variant::Aes192 => 24, + crypto::aes::Variant::Aes256 => 32, + } +} + // Translating a policy AES `KeyType` into the type understood by the cryptographic backend we are // currently used // TODO: change this into a `TryFrom` once we refactor `KeyType` to be a newtype. @@ -292,9 +302,40 @@ fn get_aes_variant(key_type: &KeyType) -> Result<crypto::aes::Variant, HwCryptoE } } -// Use the cryptographic backend to create key material based on the policy `KeyType`. Because HMAC -// keys can have arbitrary sizes, include an optional `key_size_bits` for that case. -fn generate_key_material(key_type: &KeyType) -> Result<KeyMaterial, HwCryptoError> { +// Return a keysize given a `KeyType`. Because HMAC key sizes can be defined by the +// caller, `key_size_bits` is needed to cover all cases. +pub(crate) fn get_key_size_in_bytes(key_type: &KeyType) -> Result<usize, HwCryptoError> { + match *key_type { + KeyType::AES_128_CBC_NO_PADDING + | KeyType::AES_128_CBC_PKCS7_PADDING + | KeyType::AES_128_CTR + | KeyType::AES_128_GCM + | KeyType::AES_128_CMAC => Ok(16), + KeyType::AES_256_CBC_NO_PADDING + | KeyType::AES_256_CBC_PKCS7_PADDING + | KeyType::AES_256_CTR + | KeyType::AES_256_GCM + | KeyType::AES_256_CMAC => Ok(32), + KeyType::HMAC_SHA256 => Ok(32), + KeyType::HMAC_SHA512 => Ok(64), + _ => unimplemented!("Only AES and HMAC has been implemented"), + } +} + +fn key_vec_to_array<T, U: std::convert::TryInto<T>>(input: U) -> Result<T, HwCryptoError> { + input + .try_into() + .map_err(|_| hwcrypto_err!(BAD_PARAMETER, "couldn't transform vector into array")) +} + +// Create a backend-compatible cryptographic key from either a provided vector of uniform random +// bytes or, if this not provided, use the cryptographic backend to create it. The type is based on +// the policy `KeyType`. Because HMAC keys can have arbitrary sizes, include an optional +// `key_size_bits` for that case. +pub(crate) fn generate_key_material( + key_type: &KeyType, + key_random_bytes: Option<Vec<u8>>, +) -> Result<KeyMaterial, HwCryptoError> { let aes = crypto_provider::AesImpl; let hmac = crypto_provider::HmacImpl; let mut rng = crypto_provider::RngImpl::default(); @@ -310,13 +351,56 @@ fn generate_key_material(key_type: &KeyType) -> Result<KeyMaterial, HwCryptoErro | KeyType::AES_256_GCM | KeyType::AES_256_CMAC => { let variant = get_aes_variant(key_type)?; - Ok(aes.generate_key(&mut rng, variant, &[])?) - } - KeyType::HMAC_SHA256 => { - Ok(hmac.generate_key(&mut rng, kmr_wire::KeySizeInBits(256), &[])?) + if let Some(key_bytes) = key_random_bytes { + if key_bytes.len() != get_aes_variant_key_size(&variant) { + return Err(hwcrypto_err!( + BAD_PARAMETER, + "for aes key needed {} bytes, received {}", + key_bytes.len(), + get_aes_variant_key_size(&variant) + )); + } + match variant { + crypto::aes::Variant::Aes128 => Ok(KeyMaterial::Aes( + crypto::aes::Key::Aes128(key_vec_to_array(key_bytes)?).into(), + )), + crypto::aes::Variant::Aes192 => Err(hwcrypto_err!( + BAD_PARAMETER, + "AES keys of length 192 are not supported", + )), + crypto::aes::Variant::Aes256 => Ok(KeyMaterial::Aes( + crypto::aes::Key::Aes256(key_vec_to_array(key_bytes)?).into(), + )), + } + } else { + Ok(aes.generate_key(&mut rng, variant, &[])?) + } } - KeyType::HMAC_SHA512 => { - Ok(hmac.generate_key(&mut rng, kmr_wire::KeySizeInBits(512), &[])?) + KeyType::HMAC_SHA256 | KeyType::HMAC_SHA512 => { + let key_size_bytes = get_key_size_in_bytes(key_type)?; + if let Some(key_bytes) = key_random_bytes { + if key_bytes.len() != key_size_bytes { + Err(hwcrypto_err!( + BAD_PARAMETER, + "for hmac key needed {} bytes, received {}", + key_bytes.len(), + key_size_bytes + )) + } else { + Ok(KeyMaterial::Hmac(crypto::hmac::Key::new(key_bytes).into())) + } + } else { + Ok(hmac.generate_key( + &mut rng, + kmr_wire::KeySizeInBits((key_size_bytes * 8).try_into().map_err(|_| { + hwcrypto_err!( + GENERIC_ERROR, + "shouldn't happen, key_size_bytes * 8 should fit on an u32" + ) + })?), + &[], + )?) + } } _ => unimplemented!("key material other than AES and HMAC not implemented yet"), } @@ -345,7 +429,7 @@ mod tests { keyType: key_type, keyManagementKey: false, }; - let key_material = generate_key_material(&policy.keyType); + let key_material = generate_key_material(&policy.keyType, None); expect!(key_material.is_ok(), "couldn't retrieve key material"); let key_material = key_material.unwrap(); let check_result = check_key_material_with_policy(&key_material, &policy); diff --git a/hwcryptohal/server/rules.mk b/hwcryptohal/server/rules.mk index a2b8cb9..608d3c6 100644 --- a/hwcryptohal/server/rules.mk +++ b/hwcryptohal/server/rules.mk @@ -30,6 +30,7 @@ MODULE_LIBRARY_DEPS += \ frameworks/native/libs/binder/trusty/rust/rpcbinder \ trusty/user/app/sample/hwcryptohal/aidl/rust \ trusty/user/app/sample/hwcryptohal/common \ + trusty/user/base/lib/hwkey/rust \ trusty/user/base/lib/keymint-rust/boringssl \ trusty/user/base/lib/keymint-rust/common \ trusty/user/base/lib/openssl-rust \ diff --git a/hwcryptohal/server/service_encryption_key.rs b/hwcryptohal/server/service_encryption_key.rs new file mode 100644 index 0000000..61b3d3d --- /dev/null +++ b/hwcryptohal/server/service_encryption_key.rs @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Module that provides access to a device specific key for this application and serialize the +//! necessary information to derive the same key to a cose-encrypt0 type header + +use ciborium::Value; +use coset::{AsCborValue, CborSerializable, Header, ProtectedHeader}; +use hwcryptohal_common::{err::HwCryptoError, hwcrypto_err}; +use hwkey::{Hwkey, OsRollbackVersion, RollbackVersionSource}; +use kmr_common::{ + crypto::{self, Aes, Hkdf, Rng}, + FallibleAllocExt, +}; + +use crate::crypto_provider; + +/// Size of the base encryption key used by the service to derive other versioned context encryption +/// keys +const SERVICE_KEK_LENGTH: usize = 32; + +/// Size of the random context used to derive a versioned context specific encryption key +pub(crate) const KEY_DERIVATION_CTX_LENGTH: usize = 32; + +/// Nonce value of all zeroes used in AES-GCM key encryption. +const ZERO_NONCE: [u8; 12] = [0u8; 12]; + +const KEY_DERIVATION_CTX_COSE_LABEL: i64 = -65539; +const KEY_DERIVATION_VERSION_COSE_LABEL: i64 = -65540; + +// Header used to derive a different key per each encrypted context. Encryption of the context is +// similar to what KeyMint does to wrap keys. +pub(crate) struct EncryptionHeader { + key_derivation_context: [u8; KEY_DERIVATION_CTX_LENGTH], + header_version: u32, +} + +impl EncryptionHeader { + fn new(key_derivation_context: [u8; KEY_DERIVATION_CTX_LENGTH], header_version: u32) -> Self { + Self { key_derivation_context, header_version } + } + + pub(crate) fn generate() -> Result<Self, HwCryptoError> { + let header_version = get_service_current_version()?; + Ok(Self::generate_with_version(header_version)) + } + + pub(crate) fn generate_with_version(header_version: u32) -> Self { + let key_derivation_context = get_new_key_derivation_context(); + Self::new(key_derivation_context, header_version) + } + + // Function used to generate different device bound encryption keys tied to the HWCrypto service + // to be used for different purposes, which include VersionContext encryption and key wrapping. + fn derive_service_encryption_key( + &self, + key_context: &[u8], + ) -> Result<crypto::aes::Key, HwCryptoError> { + let encryption_key = get_encryption_key(self.header_version, key_context)?; + derive_key_hkdf(&encryption_key, &self.key_derivation_context[..]) + } + + /// Encrypt CBOR serializable data using a device key derived using `key_context` + pub(crate) fn encrypt_content_service_encryption_key<T: AsCborValue>( + &self, + key_context: &[u8], + content: T, + ) -> Result<Vec<u8>, HwCryptoError> { + let kek = self.derive_service_encryption_key(key_context)?; + let aes = crypto_provider::AesImpl; + let cose_encrypt = coset::CoseEncrypt0Builder::new() + .protected(self.try_into()?) + .try_create_ciphertext::<_, HwCryptoError>( + &content.to_cbor_value()?.to_vec()?, + &[], + move |pt, aad| { + let mut op = aes.begin_aead( + kek.into(), + crypto::aes::GcmMode::GcmTag16 { nonce: ZERO_NONCE }, + crypto::SymmetricOperation::Encrypt, + )?; + op.update_aad(aad)?; + let mut ct = op.update(pt)?; + ct.try_extend_from_slice(&op.finish()?)?; + Ok(ct) + }, + )? + .build(); + Ok(cose_encrypt.to_vec()?) + } + + /// Decrypt CBOR serializable data using a device key derived using `key_context`. Data needs to + /// include an `EncryptionHeader` on the COSE protected header. + pub(crate) fn decrypt_content_service_encryption_key( + encrypted_context: &[u8], + key_context: &[u8], + ) -> Result<(Self, Vec<u8>), HwCryptoError> { + let context: coset::CoseEncrypt0 = coset::CborSerializable::from_slice(encrypted_context)?; + let encryption_header: EncryptionHeader = (&context.protected).try_into()?; + let kek = encryption_header.derive_service_encryption_key(key_context)?; + + let aes = crypto_provider::AesImpl; + let mut op = aes.begin_aead( + kek.into(), + crypto::aes::GcmMode::GcmTag16 { nonce: ZERO_NONCE }, + crypto::SymmetricOperation::Decrypt, + )?; + let extended_aad = coset::enc_structure_data( + coset::EncryptionContext::CoseEncrypt0, + context.protected.clone(), + &[], // no external AAD + ); + op.update_aad(&extended_aad)?; + let mut pt_data = op.update(&context.ciphertext.unwrap_or_default())?; + pt_data.try_extend_from_slice( + &op.finish() + .map_err(|e| hwcrypto_err!(INVALID_KEY, "failed to decrypt context: {:?}", e))?, + )?; + Ok((encryption_header, pt_data)) + } +} + +// Implementing conversion functions to easily convert from/to COSE headers and `EncryptionHeader`s +impl TryFrom<&ProtectedHeader> for EncryptionHeader { + type Error = HwCryptoError; + + fn try_from(value: &ProtectedHeader) -> Result<EncryptionHeader, Self::Error> { + let cose_header_rest = &value.header.rest; + if cose_header_rest.len() != 2 { + return Err(hwcrypto_err!( + BAD_PARAMETER, + "header length was {} instead of 2", + cose_header_rest.len() + )); + } + let mut key_derivation_context = None; + let mut header_version = None; + for element in cose_header_rest { + let label: i64 = element + .0 + .clone() + .to_cbor_value()? + .into_integer() + .map_err(|_| { + hwcrypto_err!( + SERIALIZATION_ERROR, + "unsupported string header label {:?}", + element.0 + ) + })? + .try_into() + .map_err(|_| { + hwcrypto_err!( + SERIALIZATION_ERROR, + "error converting cose label {:?}", + element.0 + ) + })?; + match label { + KEY_DERIVATION_CTX_COSE_LABEL => { + key_derivation_context = + Some(parse_cborium_bytes_to_fixed_array(&element.1, "KEK context")?); + } + KEY_DERIVATION_VERSION_COSE_LABEL => { + header_version = Some(parse_cborium_u32(&element.1, "header version")?); + } + _ => return Err(hwcrypto_err!(BAD_PARAMETER, "unknown label {}", label)), + } + } + let key_derivation_context = key_derivation_context + .ok_or(hwcrypto_err!(SERIALIZATION_ERROR, "couldn't parse key context"))?; + let header_version = header_version + .ok_or(hwcrypto_err!(SERIALIZATION_ERROR, "couldn't parse header version"))?; + Ok(Self::new(key_derivation_context, header_version)) + } +} + +impl TryFrom<&EncryptionHeader> for Header { + type Error = HwCryptoError; + + fn try_from(value: &EncryptionHeader) -> Result<Header, Self::Error> { + let mut key_derivation_context = Vec::<u8>::new(); + key_derivation_context.try_extend_from_slice(&value.key_derivation_context[..])?; + let cose_header = coset::HeaderBuilder::new() + .algorithm(coset::iana::Algorithm::A256GCM) + .value(KEY_DERIVATION_CTX_COSE_LABEL, Value::Bytes(key_derivation_context)) + .value(KEY_DERIVATION_VERSION_COSE_LABEL, Value::Integer(value.header_version.into())) + .build(); + Ok(cose_header) + } +} + +/// Get the base versioned encryption key used by the service to derive other versioned context +/// encryption keys +fn get_encryption_key(header_version: u32, key_context: &[u8]) -> Result<Vec<u8>, HwCryptoError> { + let mut key = Vec::<u8>::new(); + key.try_reserve(SERVICE_KEK_LENGTH)?; + key.resize(SERVICE_KEK_LENGTH, 0); + let hwkey_session = Hwkey::open() + .map_err(|e| hwcrypto_err!(GENERIC_ERROR, "could not connect to hwkey service {:?}", e))?; + hwkey_session + .derive_key_req() + .unique_key() + .rollback_version_source(RollbackVersionSource::CommittedVersion) + .os_rollback_version(OsRollbackVersion::Version(header_version)) + .derive(key_context, &mut key[..]) + .map_err(|e| hwcrypto_err!(GENERIC_ERROR, "could derive key {:?}", e))?; + Ok(key) +} + +// Create an AES key compatible with the current crypto backend used +fn derive_key_hkdf( + derivation_key: &[u8], + derivation_context: &[u8], +) -> Result<crypto::aes::Key, HwCryptoError> { + let kdf = crypto_provider::HmacImpl; + let raw_key = kdf.hkdf(&[], &derivation_key, &derivation_context, SERVICE_KEK_LENGTH)?; + let key_material = crypto::aes::Key::Aes256( + raw_key.try_into().expect("should not fail, call with SERVICE_KEK_LENGTH returns 32 bytes"), + ); + Ok(key_material) +} + +fn get_new_key_derivation_context() -> [u8; KEY_DERIVATION_CTX_LENGTH] { + let mut rng = crypto_provider::RngImpl::default(); + let mut key_ctx = [0u8; KEY_DERIVATION_CTX_LENGTH]; + rng.fill_bytes(&mut key_ctx[..]); + key_ctx +} + +fn parse_cborium_bytes_to_fixed_array( + value: &ciborium::value::Value, + name: &str, +) -> Result<[u8; KEY_DERIVATION_CTX_LENGTH], HwCryptoError> { + let value_bytes = value.as_bytes().ok_or(hwcrypto_err!( + SERIALIZATION_ERROR, + "wrong type when trying to parse bytes for {}", + name, + ))?; + if value_bytes.len() != KEY_DERIVATION_CTX_LENGTH { + return Err(hwcrypto_err!( + SERIALIZATION_ERROR, + "wrong number of bytes for {}, found {}, expected {}", + name, + value_bytes.len(), + KEY_DERIVATION_CTX_LENGTH + )); + } + Ok(value_bytes.as_slice().try_into().expect("Shouldn't fail, we checked size already")) +} + +fn parse_cborium_u32( + value: &ciborium::value::Value, + value_name: &str, +) -> Result<u32, HwCryptoError> { + let integer_value = value.as_integer().ok_or(hwcrypto_err!( + SERIALIZATION_ERROR, + "wrong type when trying to parse a u32 from {}", + value_name + ))?; + integer_value.try_into().map_err(|e| { + hwcrypto_err!(SERIALIZATION_ERROR, "Error converting {} to u32: {}", value_name, e) + }) +} + +pub(crate) fn get_service_current_version() -> Result<u32, HwCryptoError> { + let hwkey_session = Hwkey::open()?; + + match hwkey_session.query_current_os_version(RollbackVersionSource::CommittedVersion) { + Ok(OsRollbackVersion::Version(n)) => Ok(n), + _ => Err(hwcrypto_err!(GENERIC_ERROR, "error communicating with HwKey service")), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use test::{expect, expect_eq}; + + #[test] + fn header_encryption_decryption() { + let header = EncryptionHeader::generate(); + expect!(header.is_ok(), "couldn't generate header"); + let header = header.unwrap(); + let encrypted_content = header.encrypt_content_service_encryption_key( + b"fake_context", + Value::Bytes(b"test_data".to_vec()), + ); + expect!(encrypted_content.is_ok(), "couldn't generate header"); + let encrypted_content = encrypted_content.unwrap(); + let decrypted_data = EncryptionHeader::decrypt_content_service_encryption_key( + &encrypted_content[..], + b"fake_context", + ); + expect!(decrypted_data.is_ok(), "couldn't generate header"); + let (decrypted_header, decrypted_content) = decrypted_data.unwrap(); + let decrypted_content = + Value::from_slice(&decrypted_content[..]).unwrap().into_bytes().unwrap(); + expect_eq!( + header.key_derivation_context, + decrypted_header.key_derivation_context, + "header key derivation context do not match" + ); + expect_eq!( + header.header_version, + decrypted_header.header_version, + "header version do not match" + ); + expect_eq!(decrypted_content, b"test_data", "decrypted data do not match"); + } +} |