summaryrefslogtreecommitdiff
path: root/hwcryptohal/server/service_encryption_key.rs
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 /hwcryptohal/server/service_encryption_key.rs
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
Diffstat (limited to 'hwcryptohal/server/service_encryption_key.rs')
-rw-r--r--hwcryptohal/server/service_encryption_key.rs324
1 files changed, 324 insertions, 0 deletions
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");
+ }
+}