summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOrlando Arbildo <oarbildo@google.com>2024-02-13 23:36:12 +0000
committerOrlando Arbildo <oarbildo@google.com>2024-04-11 23:45:52 +0000
commiteaf64a867e83362fa230fb0de3419ea5e79e3267 (patch)
tree6ebc1b349d60c6ca091949280859bd1c9d788a8b
parent34991a36f2aa778ad56da805bed6f86c453641f3 (diff)
downloadsample-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.json10
-rw-r--r--hwcrypto/hwkey_srv_fake_provider.c10
-rw-r--r--hwcryptohal/common/err.rs7
-rw-r--r--hwcryptohal/server/helpers.rs55
-rw-r--r--hwcryptohal/server/hwcrypto_device_key.rs290
-rw-r--r--hwcryptohal/server/lib.rs2
-rw-r--r--hwcryptohal/server/opaque_key.rs106
-rw-r--r--hwcryptohal/server/rules.mk1
-rw-r--r--hwcryptohal/server/service_encryption_key.rs324
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");
+ }
+}