HAL service implementation

Bug: 239476214
Bug: 197891150
Test: VtsAidlKeyMintTargetTest (with rest of code)
Change-Id: I87265ddd9e5d4a6d063a4593d5b04aee13156846
diff --git a/hal/Android.bp b/hal/Android.bp
new file mode 100644
index 0000000..264754b
--- /dev/null
+++ b/hal/Android.bp
@@ -0,0 +1,60 @@
+// Copyright 2022, 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.
+
+package {
+    default_applicable_licenses: ["system_keymint_license"],
+}
+
+rust_defaults {
+    name: "kmr_hal_defaults",
+    edition: "2021",
+    lints: "vendor",
+    defaults: [
+        "keymint_use_latest_hal_aidl_rust",
+    ],
+    rustlibs: [
+        "android.hardware.security.secureclock-V1-rust",
+        "android.hardware.security.sharedsecret-V1-rust",
+        "libbinder_rs",
+        "libciborium",
+        "libciborium_io",
+        "libkmr_common",
+        "liblog_rust",
+        "libregex",
+        "librustutils",
+    ],
+    proc_macros: [
+        "libkmr_derive",
+    ],
+}
+
+rust_library {
+    name: "libkmr_hal",
+    crate_name: "kmr_hal",
+    srcs: ["src/lib.rs"],
+    vendor_available: true,
+    defaults: [
+        "kmr_hal_defaults",
+    ],
+}
+
+rust_test {
+    name: "libkmr_hal_test",
+    crate_name: "libkeymint_rust_test",
+    srcs: ["src/lib.rs"],
+    defaults: [
+        "kmr_hal_defaults",
+    ],
+    test_suites: ["general-tests"],
+}
diff --git a/hal/TEST_MAPPING b/hal/TEST_MAPPING
new file mode 100644
index 0000000..2b34c31
--- /dev/null
+++ b/hal/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "libkmr_hal_test"
+    }
+  ]
+}
diff --git a/hal/src/env.rs b/hal/src/env.rs
new file mode 100644
index 0000000..060509c
--- /dev/null
+++ b/hal/src/env.rs
@@ -0,0 +1,188 @@
+//! Retrieve and populate information about userspace.
+
+use kmr_common::wire::SetHalInfoRequest;
+use regex::Regex;
+
+// The OS version property is of form "12" or "12.1" or "12.1.3".
+const OS_VERSION_PROPERTY: &str = "ro.build.version.release";
+const OS_VERSION_REGEX: &str = r"^(?P<major>\d{1,2})(\.(?P<minor>\d{1,2}))?(\.(?P<sub>\d{1,2}))?$";
+
+// The patchlevel properties are of form "YYYY-MM-DD".
+pub const OS_PATCHLEVEL_PROPERTY: &str = "ro.build.version.security_patch";
+const VENDOR_PATCHLEVEL_PROPERTY: &str = "ro.vendor.build.security_patch";
+const PATCHLEVEL_REGEX: &str = r"^(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})$";
+
+// Just use [`String`] for errors here.
+type Error = String;
+
+/// Retrieve a numeric value from a possible match.
+fn extract_u32(value: Option<regex::Match>) -> Result<u32, Error> {
+    match value {
+        Some(m) => {
+            let s = m.as_str();
+            match s.parse::<u32>() {
+                Ok(v) => Ok(v),
+                Err(e) => Err(format!("failed to parse integer: {:?}", e)),
+            }
+        }
+        None => Err("failed to find match".to_string()),
+    }
+}
+
+pub fn get_property(name: &str) -> Result<String, Error> {
+    match rustutils::system_properties::read(name) {
+        Ok(Some(value)) => Ok(value),
+        Ok(None) => Err(format!("no value for property {}", name)),
+        Err(e) => Err(format!("failed to get property {}: {:?}", name, e)),
+    }
+}
+
+/// Extract a patchlevel in form YYYYMM from a "YYYY-MM-DD" property value.
+pub fn extract_truncated_patchlevel(prop_value: &str) -> Result<u32, Error> {
+    let patchlevel_regex = Regex::new(PATCHLEVEL_REGEX)
+        .map_err(|e| format!("failed to compile patchlevel regexp: {:?}", e))?;
+
+    let captures = patchlevel_regex
+        .captures(prop_value)
+        .ok_or_else(|| "failed to match patchlevel regex".to_string())?;
+    let year = extract_u32(captures.name("year"))?;
+    let month = extract_u32(captures.name("month"))?;
+    if !(1..=12).contains(&month) {
+        return Err(format!("month out of range: {}", month));
+    }
+    // no day
+    Ok(year * 100 + month)
+}
+
+/// Extract a patchlevel in form YYYYMMDD from a "YYYY-MM-DD" property value.
+pub fn extract_patchlevel(prop_value: &str) -> Result<u32, Error> {
+    let patchlevel_regex = Regex::new(PATCHLEVEL_REGEX)
+        .map_err(|e| format!("failed to compile patchlevel regexp: {:?}", e))?;
+
+    let captures = patchlevel_regex
+        .captures(prop_value)
+        .ok_or_else(|| "failed to match patchlevel regex".to_string())?;
+    let year = extract_u32(captures.name("year"))?;
+    let month = extract_u32(captures.name("month"))?;
+    if !(1..=12).contains(&month) {
+        return Err(format!("month out of range: {}", month));
+    }
+    let day = extract_u32(captures.name("day"))?;
+    if !(1..=31).contains(&day) {
+        return Err(format!("day out of range: {}", day));
+    }
+    Ok(year * 10000 + month * 100 + day)
+}
+
+/// Generate HAL information from property values.
+fn populate_hal_info_from(
+    os_version_prop: &str,
+    os_patchlevel_prop: &str,
+    vendor_patchlevel_prop: &str,
+) -> Result<SetHalInfoRequest, Error> {
+    let os_version_regex = Regex::new(OS_VERSION_REGEX)
+        .map_err(|e| format!("failed to compile version regexp: {:?}", e))?;
+    let captures = os_version_regex
+        .captures(os_version_prop)
+        .ok_or_else(|| "failed to match OS version regex".to_string())?;
+    let major = extract_u32(captures.name("major"))?;
+    let minor = extract_u32(captures.name("minor")).unwrap_or(0u32);
+    let sub = extract_u32(captures.name("sub")).unwrap_or(0u32);
+    let os_version = (major * 10000) + (minor * 100) + sub;
+
+    Ok(SetHalInfoRequest {
+        os_version,
+        os_patchlevel: extract_truncated_patchlevel(os_patchlevel_prop)?,
+        vendor_patchlevel: extract_patchlevel(vendor_patchlevel_prop)?,
+    })
+}
+
+/// Populate a [`SetHalInfoRequest`] based on property values read from the environment.
+pub fn populate_hal_info() -> Result<SetHalInfoRequest, Error> {
+    let os_version_prop = get_property(OS_VERSION_PROPERTY)
+        .map_err(|e| format!("failed to retrieve property: {:?}", e))?;
+    let os_patchlevel_prop = get_property(OS_PATCHLEVEL_PROPERTY)
+        .map_err(|e| format!("failed to retrieve property: {:?}", e))?;
+    let vendor_patchlevel_prop = get_property(VENDOR_PATCHLEVEL_PROPERTY)
+        .map_err(|e| format!("failed to retrieve property: {:?}", e))?;
+
+    populate_hal_info_from(&os_version_prop, &os_patchlevel_prop, &vendor_patchlevel_prop)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use kmr_common::wire::SetHalInfoRequest;
+    #[test]
+    fn test_hal_info() {
+        let tests = vec![
+            (
+                "12",
+                "2021-02-02",
+                "2022-03-04",
+                SetHalInfoRequest {
+                    os_version: 120000,
+                    os_patchlevel: 202102,
+                    vendor_patchlevel: 20220304,
+                },
+            ),
+            (
+                "12.5",
+                "2021-02-02",
+                "2022-03-04",
+                SetHalInfoRequest {
+                    os_version: 120500,
+                    os_patchlevel: 202102,
+                    vendor_patchlevel: 20220304,
+                },
+            ),
+            (
+                "12.5.7",
+                "2021-02-02",
+                "2022-03-04",
+                SetHalInfoRequest {
+                    os_version: 120507,
+                    os_patchlevel: 202102,
+                    vendor_patchlevel: 20220304,
+                },
+            ),
+        ];
+        for (os_version, os_patch, vendor_patch, want) in tests {
+            let got = populate_hal_info_from(os_version, os_patch, vendor_patch).unwrap();
+            assert_eq!(
+                got, want,
+                "Mismatch for input ({}, {}, {})",
+                os_version, os_patch, vendor_patch
+            );
+        }
+    }
+
+    #[test]
+    fn test_invalid_hal_info() {
+        let tests = vec![
+            ("xx", "2021-02-02", "2022-03-04", "failed to match OS version"),
+            ("12.xx", "2021-02-02", "2022-03-04", "failed to match OS version"),
+            ("12.5.xx", "2021-02-02", "2022-03-04", "failed to match OS version"),
+            ("12", "20212-02-02", "2022-03-04", "failed to match patchlevel regex"),
+            ("12", "2021-xx-02", "2022-03-04", "failed to match patchlevel"),
+            ("12", "2021-13-02", "2022-03-04", "month out of range"),
+            ("12", "2022-03-04", "2021-xx-02", "failed to match patchlevel"),
+            ("12", "2022-03-04", "2021-13-02", "month out of range"),
+            ("12", "2022-03-04", "2021-03-32", "day out of range"),
+        ];
+        for (os_version, os_patch, vendor_patch, want_err) in tests {
+            let result = populate_hal_info_from(os_version, os_patch, vendor_patch);
+            assert!(result.is_err());
+            let err = result.unwrap_err();
+            assert!(
+                err.contains(want_err),
+                "Mismatch for input ({}, {}, {}), got error '{}', want '{}'",
+                os_version,
+                os_patch,
+                vendor_patch,
+                err,
+                want_err
+            );
+        }
+    }
+}
diff --git a/hal/src/hal.rs b/hal/src/hal.rs
new file mode 100644
index 0000000..b419bc6
--- /dev/null
+++ b/hal/src/hal.rs
@@ -0,0 +1,710 @@
+//! Code for dealing with HAL-defined types, especially conversions to/from internal types.
+//!
+//! The internal code for KeyMint uses its own type definitions, not the HAL-defined autogenerated
+//! types, for two reasons:
+//!
+//! - The auto-generated types impose a dependency on Binder which is not appropriate for
+//!   code being built for a secure environment.
+//! - The auto-generated types are not idiomatic Rust, and have reduced type safety.
+//!
+//! This module includes code to convert between HAL types (re-used under `kmr_hal::hal`) and
+//! internal types (under `kmr_common::wire`), via the [`Fromm`] / [`TryFromm`], [`Innto`] and
+//! [`TryInnto`] traits (which are deliberately misspelled to avoid a clash with standard
+//! traits -- see below).
+//!
+//! - Going from wire=>HAL is an infallible conversion, as the wire types are stricter.
+//! - Going from HAL=>wire is often a fallible conversion, as there may be "enum" values
+//!   that are not in range.
+//!
+//! This module (and `kmr_common::wire`) must be kept in sync with the Android KeyMint HAL
+//! definition.
+
+#![allow(non_snake_case)]
+
+use crate::binder;
+use keymint::{KeyParameterValue::KeyParameterValue, Tag::Tag, TagType::TagType};
+use kmr_common::{
+    crypto::rsa, crypto::KeySizeInBits, wire, wire::keymint::DateTime, wire::keymint::KeyParam,
+};
+use log::{error, warn};
+use std::convert::TryFrom;
+use std::ffi::CString;
+
+pub use android_hardware_security_keymint::aidl::android::hardware::security::keymint;
+pub use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock;
+pub use android_hardware_security_sharedsecret::aidl::android::hardware::security::sharedsecret;
+
+#[cfg(test)]
+mod tests;
+
+/// Emit a failure for a failed type conversion.
+#[inline]
+pub fn failed_conversion(_err: wire::ValueNotRecognized) -> binder::Status {
+    binder::Status::new_service_specific_error(
+        keymint::ErrorCode::ErrorCode::INVALID_ARGUMENT.0,
+        Some(&CString::new("conversion from HAL type to internal type failed").unwrap()),
+    )
+}
+
+/// Determine the tag type for a tag, based on the top 4 bits of the tag number.
+pub fn tag_type(tag: Tag) -> TagType {
+    match ((tag.0 as u32) & 0xf0000000u32) as i32 {
+        x if x == TagType::ENUM.0 => TagType::ENUM,
+        x if x == TagType::ENUM_REP.0 => TagType::ENUM_REP,
+        x if x == TagType::UINT.0 => TagType::UINT,
+        x if x == TagType::UINT_REP.0 => TagType::UINT_REP,
+        x if x == TagType::ULONG.0 => TagType::ULONG,
+        x if x == TagType::DATE.0 => TagType::DATE,
+        x if x == TagType::BOOL.0 => TagType::BOOL,
+        x if x == TagType::BIGNUM.0 => TagType::BIGNUM,
+        x if x == TagType::BYTES.0 => TagType::BYTES,
+        x if x == TagType::ULONG_REP.0 => TagType::ULONG_REP,
+        _ => TagType::INVALID,
+    }
+}
+
+// Neither the `kmr_common::wire` types nor the `hal` types are local to this crate, which means
+// that Rust's orphan rule means we cannot implement the standard conversion traits.  So instead
+// define our own equivalent conversion traits that are local, and for which we're allowed to
+// provide implementations.  Give them an odd name to avoid confusion with the standard traits.
+
+/// Local equivalent of `From` trait, with a different name to avoid clashes.
+pub trait Fromm<T>: Sized {
+    fn fromm(val: T) -> Self;
+}
+/// Local equivalent of `TryFrom` trait, with a different name to avoid clashes.
+pub trait TryFromm<T>: Sized {
+    type Error;
+    fn try_fromm(val: T) -> Result<Self, Self::Error>;
+}
+/// Local equivalent of `Into` trait, with a different name to avoid clashes.
+pub trait Innto<T> {
+    fn innto(self) -> T;
+}
+/// Local equivalent of `TryInto` trait, with a different name to avoid clashes.
+pub trait TryInnto<T> {
+    type Error;
+    fn try_innto(self) -> Result<T, Self::Error>;
+}
+/// Blanket implementation of `Innto` from `Fromm`
+impl<T, U> Innto<U> for T
+where
+    U: Fromm<T>,
+{
+    fn innto(self) -> U {
+        U::fromm(self)
+    }
+}
+/// Blanket implementation of `TryInnto` from `TryFromm`
+impl<T, U> TryInnto<U> for T
+where
+    U: TryFromm<T>,
+{
+    type Error = U::Error;
+    fn try_innto(self) -> Result<U, Self::Error> {
+        U::try_fromm(self)
+    }
+}
+/// Blanket implementation of `Fromm<Vec<T>>` from `Fromm<T>`
+impl<T, U> Fromm<Vec<T>> for Vec<U>
+where
+    U: Fromm<T>,
+{
+    fn fromm(val: Vec<T>) -> Vec<U> {
+        val.into_iter().map(|t| <U>::fromm(t)).collect()
+    }
+}
+
+// Conversions from `kmr_common::wire` types into the equivalent types in the auto-generated HAL
+// code. These conversions are infallible, because the range of the `wire` types is strictly
+// contained within the HAL types.
+
+impl Fromm<wire::sharedsecret::SharedSecretParameters>
+    for sharedsecret::SharedSecretParameters::SharedSecretParameters
+{
+    fn fromm(val: wire::sharedsecret::SharedSecretParameters) -> Self {
+        Self { seed: val.seed, nonce: val.nonce }
+    }
+}
+impl Fromm<wire::secureclock::Timestamp> for secureclock::Timestamp::Timestamp {
+    fn fromm(val: wire::secureclock::Timestamp) -> Self {
+        Self { milliSeconds: val.milliseconds }
+    }
+}
+impl Fromm<wire::secureclock::TimeStampToken> for secureclock::TimeStampToken::TimeStampToken {
+    fn fromm(val: wire::secureclock::TimeStampToken) -> Self {
+        Self { challenge: val.challenge, timestamp: val.timestamp.innto(), mac: val.mac }
+    }
+}
+impl Fromm<wire::keymint::Certificate> for keymint::Certificate::Certificate {
+    fn fromm(val: wire::keymint::Certificate) -> Self {
+        Self { encodedCertificate: val.encoded_certificate }
+    }
+}
+impl Fromm<wire::keymint::DeviceInfo> for keymint::DeviceInfo::DeviceInfo {
+    fn fromm(val: wire::keymint::DeviceInfo) -> Self {
+        Self { deviceInfo: val.device_info }
+    }
+}
+impl Fromm<wire::keymint::HardwareAuthToken> for keymint::HardwareAuthToken::HardwareAuthToken {
+    fn fromm(val: wire::keymint::HardwareAuthToken) -> Self {
+        Self {
+            challenge: val.challenge,
+            userId: val.user_id,
+            authenticatorId: val.authenticator_id,
+            authenticatorType: val.authenticator_type.innto(),
+            timestamp: val.timestamp.innto(),
+            mac: val.mac,
+        }
+    }
+}
+impl Fromm<wire::keymint::KeyCharacteristics> for keymint::KeyCharacteristics::KeyCharacteristics {
+    fn fromm(val: wire::keymint::KeyCharacteristics) -> Self {
+        Self {
+            securityLevel: val.security_level.innto(),
+            authorizations: val.authorizations.innto(),
+        }
+    }
+}
+impl Fromm<wire::keymint::KeyCreationResult> for keymint::KeyCreationResult::KeyCreationResult {
+    fn fromm(val: wire::keymint::KeyCreationResult) -> Self {
+        Self {
+            keyBlob: val.key_blob,
+            keyCharacteristics: val.key_characteristics.innto(),
+            certificateChain: val.certificate_chain.innto(),
+        }
+    }
+}
+impl Fromm<wire::keymint::KeyMintHardwareInfo>
+    for keymint::KeyMintHardwareInfo::KeyMintHardwareInfo
+{
+    fn fromm(val: wire::keymint::KeyMintHardwareInfo) -> Self {
+        Self {
+            versionNumber: val.version_number,
+            securityLevel: val.security_level.innto(),
+            keyMintName: val.key_mint_name,
+            keyMintAuthorName: val.key_mint_author_name,
+            timestampTokenRequired: val.timestamp_token_required,
+        }
+    }
+}
+impl Fromm<wire::keymint::MacedPublicKey> for keymint::MacedPublicKey::MacedPublicKey {
+    fn fromm(val: wire::keymint::MacedPublicKey) -> Self {
+        Self { macedKey: val.maced_key }
+    }
+}
+impl Fromm<wire::keymint::ProtectedData> for keymint::ProtectedData::ProtectedData {
+    fn fromm(val: wire::keymint::ProtectedData) -> Self {
+        Self { protectedData: val.protected_data }
+    }
+}
+impl Fromm<wire::keymint::RpcHardwareInfo> for keymint::RpcHardwareInfo::RpcHardwareInfo {
+    fn fromm(val: wire::keymint::RpcHardwareInfo) -> Self {
+        Self {
+            versionNumber: val.version_number,
+            rpcAuthorName: val.rpc_author_name,
+            supportedEekCurve: val.supported_eek_curve as i32,
+            uniqueId: val.unique_id,
+        }
+    }
+}
+
+impl Fromm<wire::keymint::KeyParam> for keymint::KeyParameter::KeyParameter {
+    fn fromm(val: wire::keymint::KeyParam) -> Self {
+        let (tag, value) = match val {
+            // Enum-holding variants.
+            KeyParam::Purpose(v) => (Tag::PURPOSE, KeyParameterValue::KeyPurpose(v.innto())),
+            KeyParam::Algorithm(v) => (Tag::ALGORITHM, KeyParameterValue::Algorithm(v.innto())),
+            KeyParam::BlockMode(v) => (Tag::BLOCK_MODE, KeyParameterValue::BlockMode(v.innto())),
+            KeyParam::Digest(v) => (Tag::DIGEST, KeyParameterValue::Digest(v.innto())),
+            KeyParam::Padding(v) => (Tag::PADDING, KeyParameterValue::PaddingMode(v.innto())),
+            KeyParam::EcCurve(v) => (Tag::EC_CURVE, KeyParameterValue::EcCurve(v.innto())),
+            KeyParam::RsaOaepMgfDigest(v) => {
+                (Tag::RSA_OAEP_MGF_DIGEST, KeyParameterValue::Digest(v.innto()))
+            }
+            KeyParam::Origin(v) => (Tag::ORIGIN, KeyParameterValue::Origin(v.innto())),
+
+            // `u32`-holding variants.
+            KeyParam::KeySize(v) => (Tag::KEY_SIZE, KeyParameterValue::Integer(v.0 as i32)),
+            KeyParam::MinMacLength(v) => {
+                (Tag::MIN_MAC_LENGTH, KeyParameterValue::Integer(v as i32))
+            }
+            KeyParam::MaxUsesPerBoot(v) => {
+                (Tag::MAX_USES_PER_BOOT, KeyParameterValue::Integer(v as i32))
+            }
+            KeyParam::UsageCountLimit(v) => {
+                (Tag::USAGE_COUNT_LIMIT, KeyParameterValue::Integer(v as i32))
+            }
+            KeyParam::UserId(v) => (Tag::USER_ID, KeyParameterValue::Integer(v as i32)),
+            KeyParam::UserAuthType(v) => {
+                (Tag::USER_AUTH_TYPE, KeyParameterValue::Integer(v as i32))
+            }
+            KeyParam::AuthTimeout(v) => (Tag::AUTH_TIMEOUT, KeyParameterValue::Integer(v as i32)),
+            KeyParam::OsVersion(v) => (Tag::OS_VERSION, KeyParameterValue::Integer(v as i32)),
+            KeyParam::OsPatchlevel(v) => (Tag::OS_PATCHLEVEL, KeyParameterValue::Integer(v as i32)),
+            KeyParam::VendorPatchlevel(v) => {
+                (Tag::VENDOR_PATCHLEVEL, KeyParameterValue::Integer(v as i32))
+            }
+            KeyParam::BootPatchlevel(v) => {
+                (Tag::BOOT_PATCHLEVEL, KeyParameterValue::Integer(v as i32))
+            }
+            KeyParam::MacLength(v) => (Tag::MAC_LENGTH, KeyParameterValue::Integer(v as i32)),
+            KeyParam::MaxBootLevel(v) => {
+                (Tag::MAX_BOOT_LEVEL, KeyParameterValue::Integer(v as i32))
+            }
+
+            // `u64`-holding variants.
+            KeyParam::RsaPublicExponent(v) => {
+                (Tag::RSA_PUBLIC_EXPONENT, KeyParameterValue::LongInteger(v.0 as i64))
+            }
+            KeyParam::UserSecureId(v) => {
+                (Tag::USER_SECURE_ID, KeyParameterValue::LongInteger(v as i64))
+            }
+
+            // `true`-holding variants.
+            KeyParam::CallerNonce => (Tag::CALLER_NONCE, KeyParameterValue::BoolValue(true)),
+            KeyParam::IncludeUniqueId => {
+                (Tag::INCLUDE_UNIQUE_ID, KeyParameterValue::BoolValue(true))
+            }
+            KeyParam::BootloaderOnly => (Tag::BOOTLOADER_ONLY, KeyParameterValue::BoolValue(true)),
+            KeyParam::RollbackResistance => {
+                (Tag::ROLLBACK_RESISTANCE, KeyParameterValue::BoolValue(true))
+            }
+            KeyParam::EarlyBootOnly => (Tag::EARLY_BOOT_ONLY, KeyParameterValue::BoolValue(true)),
+            KeyParam::AllowWhileOnBody => {
+                (Tag::ALLOW_WHILE_ON_BODY, KeyParameterValue::BoolValue(true))
+            }
+            KeyParam::NoAuthRequired => (Tag::NO_AUTH_REQUIRED, KeyParameterValue::BoolValue(true)),
+            KeyParam::TrustedUserPresenceRequired => {
+                (Tag::TRUSTED_USER_PRESENCE_REQUIRED, KeyParameterValue::BoolValue(true))
+            }
+            KeyParam::TrustedConfirmationRequired => {
+                (Tag::TRUSTED_CONFIRMATION_REQUIRED, KeyParameterValue::BoolValue(true))
+            }
+            KeyParam::UnlockedDeviceRequired => {
+                (Tag::UNLOCKED_DEVICE_REQUIRED, KeyParameterValue::BoolValue(true))
+            }
+            KeyParam::DeviceUniqueAttestation => {
+                (Tag::DEVICE_UNIQUE_ATTESTATION, KeyParameterValue::BoolValue(true))
+            }
+            KeyParam::StorageKey => (Tag::STORAGE_KEY, KeyParameterValue::BoolValue(true)),
+            KeyParam::ResetSinceIdRotation => {
+                (Tag::RESET_SINCE_ID_ROTATION, KeyParameterValue::BoolValue(true))
+            }
+
+            // `DateTime`-holding variants.
+            KeyParam::ActiveDatetime(v) => {
+                (Tag::ACTIVE_DATETIME, KeyParameterValue::DateTime(v.ms_since_epoch))
+            }
+            KeyParam::OriginationExpireDatetime(v) => {
+                (Tag::ORIGINATION_EXPIRE_DATETIME, KeyParameterValue::DateTime(v.ms_since_epoch))
+            }
+            KeyParam::UsageExpireDatetime(v) => {
+                (Tag::USAGE_EXPIRE_DATETIME, KeyParameterValue::DateTime(v.ms_since_epoch))
+            }
+            KeyParam::CreationDatetime(v) => {
+                (Tag::CREATION_DATETIME, KeyParameterValue::DateTime(v.ms_since_epoch))
+            }
+            KeyParam::CertificateNotBefore(v) => {
+                (Tag::CERTIFICATE_NOT_BEFORE, KeyParameterValue::DateTime(v.ms_since_epoch))
+            }
+            KeyParam::CertificateNotAfter(v) => {
+                (Tag::CERTIFICATE_NOT_AFTER, KeyParameterValue::DateTime(v.ms_since_epoch))
+            }
+
+            // `Vec<u8>`-holding variants.
+            KeyParam::ApplicationId(v) => (Tag::APPLICATION_ID, KeyParameterValue::Blob(v)),
+            KeyParam::ApplicationData(v) => (Tag::APPLICATION_DATA, KeyParameterValue::Blob(v)),
+            KeyParam::AttestationChallenge(v) => {
+                (Tag::ATTESTATION_CHALLENGE, KeyParameterValue::Blob(v))
+            }
+            KeyParam::AttestationApplicationId(v) => {
+                (Tag::ATTESTATION_APPLICATION_ID, KeyParameterValue::Blob(v))
+            }
+            KeyParam::AttestationIdBrand(v) => {
+                (Tag::ATTESTATION_ID_BRAND, KeyParameterValue::Blob(v))
+            }
+            KeyParam::AttestationIdDevice(v) => {
+                (Tag::ATTESTATION_ID_DEVICE, KeyParameterValue::Blob(v))
+            }
+            KeyParam::AttestationIdProduct(v) => {
+                (Tag::ATTESTATION_ID_PRODUCT, KeyParameterValue::Blob(v))
+            }
+            KeyParam::AttestationIdSerial(v) => {
+                (Tag::ATTESTATION_ID_SERIAL, KeyParameterValue::Blob(v))
+            }
+            KeyParam::AttestationIdImei(v) => {
+                (Tag::ATTESTATION_ID_IMEI, KeyParameterValue::Blob(v))
+            }
+            KeyParam::AttestationIdMeid(v) => {
+                (Tag::ATTESTATION_ID_MEID, KeyParameterValue::Blob(v))
+            }
+            KeyParam::AttestationIdManufacturer(v) => {
+                (Tag::ATTESTATION_ID_MANUFACTURER, KeyParameterValue::Blob(v))
+            }
+            KeyParam::AttestationIdModel(v) => {
+                (Tag::ATTESTATION_ID_MODEL, KeyParameterValue::Blob(v))
+            }
+            KeyParam::Nonce(v) => (Tag::NONCE, KeyParameterValue::Blob(v)),
+            KeyParam::RootOfTrust(v) => (Tag::ROOT_OF_TRUST, KeyParameterValue::Blob(v)),
+            KeyParam::CertificateSerial(v) => (Tag::CERTIFICATE_SERIAL, KeyParameterValue::Blob(v)),
+            KeyParam::CertificateSubject(v) => {
+                (Tag::CERTIFICATE_SUBJECT, KeyParameterValue::Blob(v))
+            }
+        };
+        Self { tag, value }
+    }
+}
+
+// Conversions from auto-generated HAL types into the equivalent types from `kmr_common::wire`.
+// These conversions are generally fallible, because the "enum" types generated for the HAL are
+// actually `i32` values, which may contain invalid values.
+
+impl Fromm<secureclock::TimeStampToken::TimeStampToken> for wire::secureclock::TimeStampToken {
+    fn fromm(val: secureclock::TimeStampToken::TimeStampToken) -> Self {
+        Self { challenge: val.challenge, timestamp: val.timestamp.innto(), mac: val.mac }
+    }
+}
+impl Fromm<secureclock::Timestamp::Timestamp> for wire::secureclock::Timestamp {
+    fn fromm(val: secureclock::Timestamp::Timestamp) -> Self {
+        Self { milliseconds: val.milliSeconds }
+    }
+}
+impl Fromm<sharedsecret::SharedSecretParameters::SharedSecretParameters>
+    for wire::sharedsecret::SharedSecretParameters
+{
+    fn fromm(val: sharedsecret::SharedSecretParameters::SharedSecretParameters) -> Self {
+        Self { seed: val.seed, nonce: val.nonce }
+    }
+}
+impl TryFromm<keymint::AttestationKey::AttestationKey> for wire::keymint::AttestationKey {
+    type Error = wire::ValueNotRecognized;
+    fn try_fromm(val: keymint::AttestationKey::AttestationKey) -> Result<Self, Self::Error> {
+        Ok(Self {
+            key_blob: val.keyBlob,
+            attest_key_params: val
+                .attestKeyParams // Vec<KeyParameter>
+                .into_iter() // Iter<KeyParameter>
+                .filter_map(|p| (&p).try_innto().transpose())
+                .collect::<Result<Vec<KeyParam>, _>>()?,
+            issuer_subject_name: val.issuerSubjectName,
+        })
+    }
+}
+impl TryFromm<keymint::HardwareAuthToken::HardwareAuthToken> for wire::keymint::HardwareAuthToken {
+    type Error = wire::ValueNotRecognized;
+    fn try_fromm(val: keymint::HardwareAuthToken::HardwareAuthToken) -> Result<Self, Self::Error> {
+        Ok(Self {
+            challenge: val.challenge,
+            user_id: val.userId,
+            authenticator_id: val.authenticatorId,
+            authenticator_type: val.authenticatorType.try_innto()?,
+            timestamp: val.timestamp.innto(),
+            mac: val.mac,
+        })
+    }
+}
+impl Fromm<keymint::MacedPublicKey::MacedPublicKey> for wire::keymint::MacedPublicKey {
+    fn fromm(val: keymint::MacedPublicKey::MacedPublicKey) -> Self {
+        Self { maced_key: val.macedKey }
+    }
+}
+impl Fromm<&keymint::MacedPublicKey::MacedPublicKey> for wire::keymint::MacedPublicKey {
+    fn fromm(val: &keymint::MacedPublicKey::MacedPublicKey) -> Self {
+        Self { maced_key: val.macedKey.to_vec() }
+    }
+}
+
+macro_rules! value_of {
+    {
+        $val:expr, $variant:ident
+    } => {
+        if let keymint::KeyParameterValue::KeyParameterValue::$variant(v) = $val.value {
+            Ok(v)
+        } else {
+            Err(wire::ValueNotRecognized)
+        }
+    }
+}
+
+macro_rules! check_bool {
+    {
+        $val:expr
+    } => {
+        if let keymint::KeyParameterValue::KeyParameterValue::BoolValue(true) = $val.value {
+            Ok(())
+        } else {
+            Err(wire::ValueNotRecognized)
+        }
+    }
+}
+
+macro_rules! clone_blob {
+    {
+        $val:expr
+    } => {
+        if let keymint::KeyParameterValue::KeyParameterValue::Blob(b) = &$val.value {
+            Ok(b.clone())
+        } else {
+            Err(wire::ValueNotRecognized)
+        }
+    }
+}
+
+/// Converting a HAL `KeyParameter` to a wire `KeyParam` may fail (producing an `Err`) but may also
+/// silently drop unknown tags (producing `Ok(None)`)
+impl TryFromm<&keymint::KeyParameter::KeyParameter> for Option<KeyParam> {
+    type Error = wire::ValueNotRecognized;
+    fn try_fromm(val: &keymint::KeyParameter::KeyParameter) -> Result<Self, Self::Error> {
+        Ok(match val.tag {
+            // Enum-holding variants.
+            keymint::Tag::Tag::PURPOSE => {
+                Some(KeyParam::Purpose(value_of!(val, KeyPurpose)?.try_innto()?))
+            }
+            keymint::Tag::Tag::ALGORITHM => {
+                Some(KeyParam::Algorithm(value_of!(val, Algorithm)?.try_innto()?))
+            }
+            keymint::Tag::Tag::BLOCK_MODE => {
+                Some(KeyParam::BlockMode(value_of!(val, BlockMode)?.try_innto()?))
+            }
+            keymint::Tag::Tag::DIGEST => {
+                Some(KeyParam::Digest(value_of!(val, Digest)?.try_innto()?))
+            }
+            keymint::Tag::Tag::PADDING => {
+                Some(KeyParam::Padding(value_of!(val, PaddingMode)?.try_innto()?))
+            }
+            keymint::Tag::Tag::EC_CURVE => {
+                Some(KeyParam::EcCurve(value_of!(val, EcCurve)?.try_innto()?))
+            }
+            keymint::Tag::Tag::RSA_OAEP_MGF_DIGEST => {
+                Some(KeyParam::RsaOaepMgfDigest(value_of!(val, Digest)?.try_innto()?))
+            }
+            keymint::Tag::Tag::ORIGIN => {
+                Some(KeyParam::Origin(value_of!(val, Origin)?.try_innto()?))
+            }
+
+            // `u32`-holding variants.
+            keymint::Tag::Tag::KEY_SIZE => {
+                Some(KeyParam::KeySize(KeySizeInBits(value_of!(val, Integer)? as u32)))
+            }
+            keymint::Tag::Tag::MIN_MAC_LENGTH => {
+                Some(KeyParam::MinMacLength(value_of!(val, Integer)? as u32))
+            }
+            keymint::Tag::Tag::MAX_USES_PER_BOOT => {
+                Some(KeyParam::MaxUsesPerBoot(value_of!(val, Integer)? as u32))
+            }
+            keymint::Tag::Tag::USAGE_COUNT_LIMIT => {
+                Some(KeyParam::UsageCountLimit(value_of!(val, Integer)? as u32))
+            }
+            keymint::Tag::Tag::USER_ID => Some(KeyParam::UserId(value_of!(val, Integer)? as u32)),
+            keymint::Tag::Tag::USER_AUTH_TYPE => {
+                Some(KeyParam::UserAuthType(value_of!(val, Integer)? as u32))
+            }
+            keymint::Tag::Tag::AUTH_TIMEOUT => {
+                Some(KeyParam::AuthTimeout(value_of!(val, Integer)? as u32))
+            }
+            keymint::Tag::Tag::OS_VERSION => {
+                Some(KeyParam::OsVersion(value_of!(val, Integer)? as u32))
+            }
+            keymint::Tag::Tag::OS_PATCHLEVEL => {
+                Some(KeyParam::OsPatchlevel(value_of!(val, Integer)? as u32))
+            }
+            keymint::Tag::Tag::VENDOR_PATCHLEVEL => {
+                Some(KeyParam::VendorPatchlevel(value_of!(val, Integer)? as u32))
+            }
+            keymint::Tag::Tag::BOOT_PATCHLEVEL => {
+                Some(KeyParam::BootPatchlevel(value_of!(val, Integer)? as u32))
+            }
+            keymint::Tag::Tag::MAC_LENGTH => {
+                Some(KeyParam::MacLength(value_of!(val, Integer)? as u32))
+            }
+            keymint::Tag::Tag::MAX_BOOT_LEVEL => {
+                Some(KeyParam::MaxBootLevel(value_of!(val, Integer)? as u32))
+            }
+
+            // `u64`-holding variants.
+            keymint::Tag::Tag::RSA_PUBLIC_EXPONENT => Some(KeyParam::RsaPublicExponent(
+                rsa::Exponent(value_of!(val, LongInteger)? as u64),
+            )),
+            keymint::Tag::Tag::USER_SECURE_ID => {
+                Some(KeyParam::UserSecureId(value_of!(val, LongInteger)? as u64))
+            }
+
+            // `bool`-holding variants; only `true` is allowed.
+            keymint::Tag::Tag::CALLER_NONCE => {
+                check_bool!(val)?;
+                Some(KeyParam::CallerNonce)
+            }
+            keymint::Tag::Tag::INCLUDE_UNIQUE_ID => {
+                check_bool!(val)?;
+                Some(KeyParam::IncludeUniqueId)
+            }
+            keymint::Tag::Tag::BOOTLOADER_ONLY => {
+                check_bool!(val)?;
+                Some(KeyParam::BootloaderOnly)
+            }
+            keymint::Tag::Tag::ROLLBACK_RESISTANCE => {
+                check_bool!(val)?;
+                Some(KeyParam::RollbackResistance)
+            }
+            keymint::Tag::Tag::EARLY_BOOT_ONLY => {
+                check_bool!(val)?;
+                Some(KeyParam::EarlyBootOnly)
+            }
+            keymint::Tag::Tag::NO_AUTH_REQUIRED => {
+                check_bool!(val)?;
+                Some(KeyParam::NoAuthRequired)
+            }
+            keymint::Tag::Tag::ALLOW_WHILE_ON_BODY => {
+                check_bool!(val)?;
+                Some(KeyParam::AllowWhileOnBody)
+            }
+            keymint::Tag::Tag::TRUSTED_USER_PRESENCE_REQUIRED => {
+                check_bool!(val)?;
+                Some(KeyParam::TrustedUserPresenceRequired)
+            }
+            keymint::Tag::Tag::TRUSTED_CONFIRMATION_REQUIRED => {
+                check_bool!(val)?;
+                Some(KeyParam::TrustedConfirmationRequired)
+            }
+            keymint::Tag::Tag::UNLOCKED_DEVICE_REQUIRED => {
+                check_bool!(val)?;
+                Some(KeyParam::UnlockedDeviceRequired)
+            }
+            keymint::Tag::Tag::DEVICE_UNIQUE_ATTESTATION => {
+                check_bool!(val)?;
+                Some(KeyParam::DeviceUniqueAttestation)
+            }
+            keymint::Tag::Tag::STORAGE_KEY => {
+                check_bool!(val)?;
+                Some(KeyParam::StorageKey)
+            }
+            keymint::Tag::Tag::RESET_SINCE_ID_ROTATION => {
+                check_bool!(val)?;
+                Some(KeyParam::ResetSinceIdRotation)
+            }
+
+            // `DateTime`-holding variants.
+            keymint::Tag::Tag::ACTIVE_DATETIME => Some(KeyParam::ActiveDatetime(DateTime {
+                ms_since_epoch: value_of!(val, DateTime)?,
+            })),
+            keymint::Tag::Tag::ORIGINATION_EXPIRE_DATETIME => {
+                Some(KeyParam::OriginationExpireDatetime(DateTime {
+                    ms_since_epoch: value_of!(val, DateTime)?,
+                }))
+            }
+            keymint::Tag::Tag::USAGE_EXPIRE_DATETIME => {
+                Some(KeyParam::UsageExpireDatetime(DateTime {
+                    ms_since_epoch: value_of!(val, DateTime)?,
+                }))
+            }
+            keymint::Tag::Tag::CREATION_DATETIME => Some(KeyParam::CreationDatetime(DateTime {
+                ms_since_epoch: value_of!(val, DateTime)?,
+            })),
+            keymint::Tag::Tag::CERTIFICATE_NOT_BEFORE => {
+                Some(KeyParam::CertificateNotBefore(DateTime {
+                    ms_since_epoch: value_of!(val, DateTime)?,
+                }))
+            }
+            keymint::Tag::Tag::CERTIFICATE_NOT_AFTER => {
+                Some(KeyParam::CertificateNotAfter(DateTime {
+                    ms_since_epoch: value_of!(val, DateTime)?,
+                }))
+            }
+
+            // `Vec<u8>`-holding variants.
+            keymint::Tag::Tag::APPLICATION_ID => Some(KeyParam::ApplicationId(clone_blob!(val)?)),
+            keymint::Tag::Tag::APPLICATION_DATA => {
+                Some(KeyParam::ApplicationData(clone_blob!(val)?))
+            }
+            keymint::Tag::Tag::ROOT_OF_TRUST => Some(KeyParam::RootOfTrust(clone_blob!(val)?)),
+            keymint::Tag::Tag::ATTESTATION_CHALLENGE => {
+                Some(KeyParam::AttestationChallenge(clone_blob!(val)?))
+            }
+            keymint::Tag::Tag::ATTESTATION_APPLICATION_ID => {
+                Some(KeyParam::AttestationApplicationId(clone_blob!(val)?))
+            }
+            keymint::Tag::Tag::ATTESTATION_ID_BRAND => {
+                Some(KeyParam::AttestationIdBrand(clone_blob!(val)?))
+            }
+            keymint::Tag::Tag::ATTESTATION_ID_DEVICE => {
+                Some(KeyParam::AttestationIdDevice(clone_blob!(val)?))
+            }
+            keymint::Tag::Tag::ATTESTATION_ID_PRODUCT => {
+                Some(KeyParam::AttestationIdProduct(clone_blob!(val)?))
+            }
+            keymint::Tag::Tag::ATTESTATION_ID_SERIAL => {
+                Some(KeyParam::AttestationIdSerial(clone_blob!(val)?))
+            }
+            keymint::Tag::Tag::ATTESTATION_ID_IMEI => {
+                Some(KeyParam::AttestationIdImei(clone_blob!(val)?))
+            }
+            keymint::Tag::Tag::ATTESTATION_ID_MEID => {
+                Some(KeyParam::AttestationIdMeid(clone_blob!(val)?))
+            }
+            keymint::Tag::Tag::ATTESTATION_ID_MANUFACTURER => {
+                Some(KeyParam::AttestationIdManufacturer(clone_blob!(val)?))
+            }
+            keymint::Tag::Tag::ATTESTATION_ID_MODEL => {
+                Some(KeyParam::AttestationIdModel(clone_blob!(val)?))
+            }
+            keymint::Tag::Tag::NONCE => Some(KeyParam::Nonce(clone_blob!(val)?)),
+            keymint::Tag::Tag::CERTIFICATE_SERIAL => {
+                Some(KeyParam::CertificateSerial(clone_blob!(val)?))
+            }
+            keymint::Tag::Tag::CERTIFICATE_SUBJECT => {
+                Some(KeyParam::CertificateSubject(clone_blob!(val)?))
+            }
+
+            // Unsupported variants
+            keymint::Tag::Tag::UNIQUE_ID
+            | keymint::Tag::Tag::HARDWARE_TYPE
+            | keymint::Tag::Tag::MIN_SECONDS_BETWEEN_OPS
+            | keymint::Tag::Tag::IDENTITY_CREDENTIAL_KEY
+            | keymint::Tag::Tag::ASSOCIATED_DATA
+            | keymint::Tag::Tag::CONFIRMATION_TOKEN => {
+                error!("Unsupported tag {:?} encountered", val.tag);
+                return Err(wire::ValueNotRecognized);
+            }
+            _ => {
+                warn!("Unknown tag {:?} silently dropped", val.tag);
+                None
+            }
+        })
+    }
+}
+
+/// Macro that emits conversion implementations for `wire` and HAL enums.
+/// - The `hal::keymint` version of the enum is a newtype holding `i32`
+/// - The `wire::keymint` version of the enum is an exhaustive enum with `[repr(i32)]`
+macro_rules! enum_convert {
+    {
+        $wenum:ty => $henum:ty
+    } => {
+        impl Fromm<$wenum> for $henum {
+            fn fromm(val: $wenum) -> Self {
+                Self(val as i32)
+            }
+        }
+        impl TryFromm<$henum> for $wenum {
+            type Error = wire::ValueNotRecognized;
+            fn try_fromm(val: $henum) -> Result<Self, Self::Error> {
+                Self::try_from(val.0)
+            }
+        }
+    };
+}
+enum_convert! { wire::keymint::ErrorCode => keymint::ErrorCode::ErrorCode }
+enum_convert! { wire::keymint::Algorithm => keymint::Algorithm::Algorithm }
+enum_convert! { wire::keymint::BlockMode => keymint::BlockMode::BlockMode }
+enum_convert! { wire::keymint::Digest => keymint::Digest::Digest }
+enum_convert! { wire::keymint::EcCurve => keymint::EcCurve::EcCurve }
+enum_convert! { wire::keymint::HardwareAuthenticatorType =>
+keymint::HardwareAuthenticatorType::HardwareAuthenticatorType }
+enum_convert! { wire::keymint::KeyFormat => keymint::KeyFormat::KeyFormat }
+enum_convert! { wire::keymint::KeyOrigin => keymint::KeyOrigin::KeyOrigin }
+enum_convert! { wire::keymint::KeyPurpose => keymint::KeyPurpose::KeyPurpose }
+enum_convert! { wire::keymint::PaddingMode => keymint::PaddingMode::PaddingMode }
+enum_convert! { wire::keymint::SecurityLevel => keymint::SecurityLevel::SecurityLevel }
+enum_convert! { wire::keymint::Tag => keymint::Tag::Tag }
+enum_convert! { wire::keymint::TagType => keymint::TagType::TagType }
diff --git a/hal/src/hal/tests.rs b/hal/src/hal/tests.rs
new file mode 100644
index 0000000..f08f477
--- /dev/null
+++ b/hal/src/hal/tests.rs
@@ -0,0 +1,151 @@
+use crate::cbor::value::Value;
+use kmr_common::{cbor_type_error, AsCborValue, CborError};
+use kmr_derive::AsCborValue;
+
+#[derive(Debug, Clone, PartialEq, Eq, AsCborValue)]
+struct Timestamp {
+    milliseconds: i64,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, AsCborValue)]
+struct NamedFields {
+    challenge: i64,
+    timestamp: Timestamp,
+    mac: Vec<u8>,
+}
+
+#[test]
+fn test_cbor_value_cddl() {
+    assert_eq!(<NamedFields>::cddl_typename().unwrap(), "NamedFields");
+    assert_eq!(
+        <NamedFields>::cddl_schema().unwrap(),
+        r#"[
+    challenge: int,
+    timestamp: Timestamp,
+    mac: bstr,
+]"#
+    );
+}
+
+#[test]
+fn test_cbor_value_roundtrip() {
+    let obj = NamedFields {
+        challenge: 42,
+        timestamp: Timestamp { milliseconds: 10_000_000 },
+        mac: vec![1, 2, 3, 4],
+    };
+
+    let obj_val = obj.clone().to_cbor_value().unwrap();
+    let recovered_obj = <NamedFields>::from_cbor_value(obj_val).unwrap();
+    assert_eq!(obj, recovered_obj);
+}
+
+#[test]
+fn test_cbor_parse_fail() {
+    let tests = vec![
+        (Value::Map(vec![]), "expected arr"),
+        (Value::Integer(0.into()), "expected arr"),
+        (Value::Array(vec![]), "expected arr len 3"),
+        (
+            Value::Array(vec![
+                Value::Integer(0.into()),
+                Value::Integer(0.into()),
+                Value::Integer(0.into()),
+                Value::Integer(0.into()),
+            ]),
+            "expected arr len 3",
+        ),
+        (
+            Value::Array(vec![
+                Value::Integer(0.into()),
+                Value::Array(vec![Value::Integer(0.into())]),
+                Value::Integer(0.into()),
+            ]),
+            "expected bstr",
+        ),
+        (
+            Value::Array(vec![
+                Value::Integer(0.into()),
+                Value::Array(vec![Value::Integer(0.into()), Value::Integer(0.into())]),
+                Value::Bytes(vec![1, 2, 3]),
+            ]),
+            "expected arr len 1",
+        ),
+    ];
+    for (val, wanterr) in tests {
+        let result = <NamedFields>::from_cbor_value(val);
+        expect_err(result, wanterr);
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, AsCborValue)]
+struct UnnamedFields(i64, Timestamp);
+
+#[test]
+fn test_unnamed_cbor_value_cddl() {
+    assert_eq!(<UnnamedFields>::cddl_typename().unwrap(), "UnnamedFields");
+    assert_eq!(
+        <UnnamedFields>::cddl_schema().unwrap(),
+        r#"[
+    int,
+    Timestamp,
+]"#
+    );
+}
+
+#[test]
+fn test_unnamed_cbor_value_roundtrip() {
+    let obj = UnnamedFields(42, Timestamp { milliseconds: 10_000_000 });
+
+    let obj_val = obj.clone().to_cbor_value().unwrap();
+    let recovered_obj = <UnnamedFields>::from_cbor_value(obj_val).unwrap();
+    assert_eq!(obj, recovered_obj);
+}
+
+#[test]
+fn test_unnamed_cbor_parse_fail() {
+    let tests = vec![
+        (Value::Map(vec![]), "expected arr"),
+        (Value::Integer(0.into()), "expected arr"),
+        (Value::Array(vec![]), "expected arr len 2"),
+        (
+            Value::Array(vec![
+                Value::Integer(0.into()),
+                Value::Integer(0.into()),
+                Value::Integer(0.into()),
+            ]),
+            "expected arr len 2",
+        ),
+        (
+            Value::Array(vec![
+                Value::Bytes(vec![1, 2, 3]),
+                Value::Array(vec![Value::Integer(0.into())]),
+            ]),
+            "expected i64",
+        ),
+        (
+            Value::Array(vec![
+                Value::Integer(0.into()),
+                Value::Array(vec![Value::Integer(0.into()), Value::Integer(0.into())]),
+            ]),
+            "expected arr len 1",
+        ),
+    ];
+    for (val, wanterr) in tests {
+        let result = <UnnamedFields>::from_cbor_value(val);
+        expect_err(result, wanterr);
+    }
+}
+
+/// Check for an expected error.
+#[cfg(test)]
+pub fn expect_err<T, E: core::fmt::Debug>(result: Result<T, E>, err_msg: &str) {
+    assert!(result.is_err(), "unexpected success; wanted error containing '{}'", err_msg);
+    let err = result.err();
+    assert!(
+        format!("{:?}", err).contains(err_msg),
+        "unexpected error {:?}, doesn't contain '{}'",
+        err,
+        err_msg
+    );
+}
diff --git a/hal/src/keymint.rs b/hal/src/keymint.rs
new file mode 100644
index 0000000..5515513
--- /dev/null
+++ b/hal/src/keymint.rs
@@ -0,0 +1,465 @@
+//! KeyMint HAL device implementation.
+
+use crate::binder;
+use crate::hal::{
+    failed_conversion, keymint, keymint::IKeyMintOperation::IKeyMintOperation,
+    secureclock::TimeStampToken::TimeStampToken, Innto, TryInnto,
+};
+use crate::{ChannelHalService, SerializedChannel};
+use kmr_common::{wire::keymint::KeyParam, wire::*, AsCborValue};
+use std::ffi::CString;
+use std::{
+    ops::DerefMut,
+    sync::{Arc, Mutex, MutexGuard, RwLock},
+};
+
+/// Maximum size of input data in operation messages, allowing for overhead.
+///
+/// A serialized `FinishRequest` includes the following additional bytes over and
+/// above the size of the input (at most):
+/// -    1: array wrapper (0x86)
+///   -  9: int (0x1b + u64) [op_handle]
+///   -  1: array wrapper (0x81) [input]
+///      -  9: input data length
+///      - XX:  input data
+///   -  1: array wrapper (0x81) [signature]
+///      - 5: signature data length
+///      - 132: signature data (P-521 point)
+///   -  1: array wrapper (0x81) [auth_token]
+///      -  9: int (0x1b + u64) [challenge]
+///      -  9: int (0x1b + u64) [user_id]
+///      -  9: int (0x1b + u64) [authenticator_id]
+///      -  9: int (0x1b + u64) [authenticator_type]
+///      -  1: array wrapper (0x81)[timestamp]
+///         -  9: int (0x1b + u64) [user_id]
+///      -  2: bstr header [mac]
+///      - 32: bstr [mac]
+///   -  1: array wrapper (0x81) [timestamp_token]
+///      -  1: array wrapper [TimeStampToken]
+///         -  9: int (0x1b + u64) [challenge]
+///         -  1: array wrapper (0x81)[timestamp]
+///            -  9: int (0x1b + u64) [user_id]
+///         -  2: bstr header [mac]
+///         - 32: bstr [mac]
+///   -  1: array wrapper (0x81) [confirmation_token]
+///      -  2: bstr header [confirmation token]
+///      - 32: bstr [confirmation token (HMAC-SHA256)]
+///
+/// Add some leeway in case encodings change.
+pub const MAX_DATA_SIZE: usize = MAX_SIZE - 350;
+
+/// IKeyMintDevice implementation which converts all method invocations to serialized
+/// requests that are sent down the associated channel.
+pub struct Device<T: SerializedChannel + 'static> {
+    channel: Arc<Mutex<T>>,
+}
+
+impl<T: SerializedChannel + 'static> Device<T> {
+    /// Construct a new instance that uses the provided channel.
+    pub fn new(channel: Arc<Mutex<T>>) -> Self {
+        Self { channel }
+    }
+
+    /// Create a new instance wrapped in a proxy object.
+    pub fn new_as_binder(
+        channel: Arc<Mutex<T>>,
+    ) -> binder::Strong<dyn keymint::IKeyMintDevice::IKeyMintDevice> {
+        keymint::IKeyMintDevice::BnKeyMintDevice::new_binder(
+            Self::new(channel),
+            binder::BinderFeatures::default(),
+        )
+    }
+}
+
+impl<T: SerializedChannel> ChannelHalService<T> for Device<T> {
+    fn channel(&self) -> MutexGuard<T> {
+        self.channel.lock().unwrap()
+    }
+}
+
+impl<T: SerializedChannel> binder::Interface for Device<T> {}
+
+impl<T: SerializedChannel> keymint::IKeyMintDevice::IKeyMintDevice for Device<T> {
+    fn getHardwareInfo(&self) -> binder::Result<keymint::KeyMintHardwareInfo::KeyMintHardwareInfo> {
+        let rsp: GetHardwareInfoResponse = self.execute(GetHardwareInfoRequest {})?;
+        Ok(rsp.ret.innto())
+    }
+    fn addRngEntropy(&self, data: &[u8]) -> binder::Result<()> {
+        let _rsp: AddRngEntropyResponse =
+            self.execute(AddRngEntropyRequest { data: data.to_vec() })?;
+        Ok(())
+    }
+    fn generateKey(
+        &self,
+        keyParams: &[keymint::KeyParameter::KeyParameter],
+        attestationKey: Option<&keymint::AttestationKey::AttestationKey>,
+    ) -> binder::Result<keymint::KeyCreationResult::KeyCreationResult> {
+        let rsp: GenerateKeyResponse = self.execute(GenerateKeyRequest {
+            key_params: keyParams
+                .iter()
+                .filter_map(|p| p.try_innto().transpose())
+                .collect::<Result<Vec<KeyParam>, _>>()
+                .map_err(failed_conversion)?,
+            attestation_key: match attestationKey {
+                None => None,
+                Some(k) => Some(k.clone().try_innto().map_err(failed_conversion)?),
+            },
+        })?;
+        Ok(rsp.ret.innto())
+    }
+    fn importKey(
+        &self,
+        keyParams: &[keymint::KeyParameter::KeyParameter],
+        keyFormat: keymint::KeyFormat::KeyFormat,
+        keyData: &[u8],
+        attestationKey: Option<&keymint::AttestationKey::AttestationKey>,
+    ) -> binder::Result<keymint::KeyCreationResult::KeyCreationResult> {
+        let rsp: ImportKeyResponse = self.execute(ImportKeyRequest {
+            key_params: keyParams
+                .iter()
+                .filter_map(|p| p.try_innto().transpose())
+                .collect::<Result<Vec<KeyParam>, _>>()
+                .map_err(failed_conversion)?,
+            key_format: keyFormat.try_innto().map_err(failed_conversion)?,
+            key_data: keyData.to_vec(),
+            attestation_key: match attestationKey {
+                None => None,
+                Some(k) => Some(k.clone().try_innto().map_err(failed_conversion)?),
+            },
+        })?;
+        Ok(rsp.ret.innto())
+    }
+    fn importWrappedKey(
+        &self,
+        wrappedKeyData: &[u8],
+        wrappingKeyBlob: &[u8],
+        maskingKey: &[u8],
+        unwrappingParams: &[keymint::KeyParameter::KeyParameter],
+        passwordSid: i64,
+        biometricSid: i64,
+    ) -> binder::Result<keymint::KeyCreationResult::KeyCreationResult> {
+        let rsp: ImportWrappedKeyResponse = self.execute(ImportWrappedKeyRequest {
+            wrapped_key_data: wrappedKeyData.to_vec(),
+            wrapping_key_blob: wrappingKeyBlob.to_vec(),
+            masking_key: maskingKey.to_vec(),
+            unwrapping_params: unwrappingParams
+                .iter()
+                .filter_map(|p| p.try_innto().transpose())
+                .collect::<Result<Vec<KeyParam>, _>>()
+                .map_err(failed_conversion)?,
+            password_sid: passwordSid,
+            biometric_sid: biometricSid,
+        })?;
+        Ok(rsp.ret.innto())
+    }
+    fn upgradeKey(
+        &self,
+        keyBlobToUpgrade: &[u8],
+        upgradeParams: &[keymint::KeyParameter::KeyParameter],
+    ) -> binder::Result<Vec<u8>> {
+        let rsp: UpgradeKeyResponse = self.execute(UpgradeKeyRequest {
+            key_blob_to_upgrade: keyBlobToUpgrade.to_vec(),
+            upgrade_params: upgradeParams
+                .iter()
+                .filter_map(|p| p.try_innto().transpose())
+                .collect::<Result<Vec<KeyParam>, _>>()
+                .map_err(failed_conversion)?,
+        })?;
+        Ok(rsp.ret)
+    }
+    fn deleteKey(&self, keyBlob: &[u8]) -> binder::Result<()> {
+        let _rsp: DeleteKeyResponse =
+            self.execute(DeleteKeyRequest { key_blob: keyBlob.to_vec() })?;
+        Ok(())
+    }
+    fn deleteAllKeys(&self) -> binder::Result<()> {
+        let _rsp: DeleteAllKeysResponse = self.execute(DeleteAllKeysRequest {})?;
+        Ok(())
+    }
+    fn destroyAttestationIds(&self) -> binder::Result<()> {
+        let _rsp: DestroyAttestationIdsResponse = self.execute(DestroyAttestationIdsRequest {})?;
+        Ok(())
+    }
+    fn begin(
+        &self,
+        purpose: keymint::KeyPurpose::KeyPurpose,
+        keyBlob: &[u8],
+        params: &[keymint::KeyParameter::KeyParameter],
+        authToken: Option<&keymint::HardwareAuthToken::HardwareAuthToken>,
+    ) -> binder::Result<keymint::BeginResult::BeginResult> {
+        let rsp: BeginResponse = self.execute(BeginRequest {
+            purpose: purpose.try_innto().map_err(failed_conversion)?,
+            key_blob: keyBlob.to_vec(),
+            params: params
+                .iter()
+                .filter_map(|p| p.try_innto().transpose())
+                .collect::<Result<Vec<KeyParam>, _>>()
+                .map_err(failed_conversion)?,
+            auth_token: match authToken {
+                None => None,
+                Some(t) => Some(t.clone().try_innto().map_err(failed_conversion)?),
+            },
+        })?;
+        // The `begin()` method is a special case.
+        // - Internally, the in-progress operation is identified by an opaque handle value.
+        // - Externally, the in-progress operation is represented as an `IKeyMintOperation` Binder
+        //   object.
+        // The `WireOperation` struct contains the former, and acts as the latter.
+        let op = Operation::new_as_binder(self.channel.clone(), rsp.ret.op_handle);
+        Ok(keymint::BeginResult::BeginResult {
+            challenge: rsp.ret.challenge,
+            params: rsp.ret.params.innto(),
+            operation: Some(op),
+        })
+    }
+    fn deviceLocked(
+        &self,
+        passwordOnly: bool,
+        timestampToken: Option<&TimeStampToken>,
+    ) -> binder::Result<()> {
+        let _rsp: DeviceLockedResponse = self.execute(DeviceLockedRequest {
+            password_only: passwordOnly,
+            timestamp_token: timestampToken.map(|t| t.clone().innto()),
+        })?;
+        Ok(())
+    }
+    fn earlyBootEnded(&self) -> binder::Result<()> {
+        let _rsp: EarlyBootEndedResponse = self.execute(EarlyBootEndedRequest {})?;
+        Ok(())
+    }
+    fn convertStorageKeyToEphemeral(&self, storageKeyBlob: &[u8]) -> binder::Result<Vec<u8>> {
+        let rsp: ConvertStorageKeyToEphemeralResponse =
+            self.execute(ConvertStorageKeyToEphemeralRequest {
+                storage_key_blob: storageKeyBlob.to_vec(),
+            })?;
+        Ok(rsp.ret)
+    }
+    fn getKeyCharacteristics(
+        &self,
+        keyBlob: &[u8],
+        appId: &[u8],
+        appData: &[u8],
+    ) -> binder::Result<Vec<keymint::KeyCharacteristics::KeyCharacteristics>> {
+        let rsp: GetKeyCharacteristicsResponse = self.execute(GetKeyCharacteristicsRequest {
+            key_blob: keyBlob.to_vec(),
+            app_id: appId.to_vec(),
+            app_data: appData.to_vec(),
+        })?;
+        Ok(rsp.ret.innto())
+    }
+    fn getRootOfTrustChallenge(&self) -> binder::Result<[u8; 16]> {
+        let rsp: GetRootOfTrustChallengeResponse =
+            self.execute(GetRootOfTrustChallengeRequest {})?;
+        Ok(rsp.ret)
+    }
+    fn getRootOfTrust(&self, challenge: &[u8; 16]) -> binder::Result<Vec<u8>> {
+        let rsp: GetRootOfTrustResponse =
+            self.execute(GetRootOfTrustRequest { challenge: *challenge })?;
+        Ok(rsp.ret)
+    }
+    fn sendRootOfTrust(&self, root_of_trust: &[u8]) -> binder::Result<()> {
+        let _rsp: SendRootOfTrustResponse =
+            self.execute(SendRootOfTrustRequest { root_of_trust: root_of_trust.to_vec() })?;
+        Ok(())
+    }
+}
+
+/// Representation of an in-progress KeyMint operation on a `SerializedChannel`.
+#[derive(Debug)]
+struct Operation<T: SerializedChannel + 'static> {
+    channel: Arc<Mutex<T>>,
+    op_handle: RwLock<Option<i64>>,
+}
+
+impl<T: SerializedChannel + 'static> Drop for Operation<T> {
+    fn drop(&mut self) {
+        // Ensure that the TA is kept up-to-date by calling `abort()`, but ignore the result.
+        let _ = self.abort();
+    }
+}
+
+impl<T: SerializedChannel> ChannelHalService<T> for Operation<T> {
+    fn channel(&self) -> MutexGuard<T> {
+        self.channel.lock().unwrap()
+    }
+
+    /// Execute the given request as part of the operation.  If the request fails, the operation is
+    /// invalidated (and any future requests for the operation will fail).
+    fn execute<R, S>(&self, req: R) -> binder::Result<S>
+    where
+        R: AsCborValue + Code<KeyMintOperation>,
+        S: AsCborValue + Code<KeyMintOperation>,
+    {
+        let result = super::channel_execute(self.channel().deref_mut(), req);
+        if result.is_err() {
+            // Any failed method on an operation terminates the operation.
+            self.invalidate();
+        }
+        result
+    }
+}
+
+impl<T: SerializedChannel> binder::Interface for Operation<T> {}
+
+impl<T: SerializedChannel + 'static> Operation<T> {
+    /// Create a new `Operation` wrapped in a proxy object.
+    fn new_as_binder(
+        channel: Arc<Mutex<T>>,
+        op_handle: i64,
+    ) -> binder::Strong<dyn keymint::IKeyMintOperation::IKeyMintOperation> {
+        let op = Self { channel, op_handle: RwLock::new(Some(op_handle)) };
+        keymint::IKeyMintOperation::BnKeyMintOperation::new_binder(
+            op,
+            binder::BinderFeatures::default(),
+        )
+    }
+}
+
+impl<T: SerializedChannel> Operation<T> {
+    /// Invalidate the operation.
+    fn invalidate(&self) {
+        *self.op_handle.write().unwrap() = None;
+    }
+
+    /// Retrieve the operation handle, if not already failed.
+    fn validate_handle(&self) -> binder::Result<i64> {
+        self.op_handle.read().unwrap().ok_or_else(|| {
+            binder::Status::new_service_specific_error(
+                keymint::ErrorCode::ErrorCode::INVALID_OPERATION_HANDLE.0,
+                Some(&CString::new("Operation handle not valid").unwrap()),
+            )
+        })
+    }
+}
+
+/// Implement the `IKeyMintOperation` interface for a [`Operation`].  Each method invocation is
+/// serialized into a request message that is sent over the `Operation`'s channel, and a
+/// corresponding response message is read.  This response message is deserialized back into the
+/// method's output value(s).
+impl<T: SerializedChannel + 'static> keymint::IKeyMintOperation::IKeyMintOperation
+    for Operation<T>
+{
+    fn updateAad(
+        &self,
+        mut input: &[u8],
+        authToken: Option<&keymint::HardwareAuthToken::HardwareAuthToken>,
+        timeStampToken: Option<&TimeStampToken>,
+    ) -> binder::Result<()> {
+        let req_template = UpdateAadRequest {
+            op_handle: self.validate_handle()?,
+            input: vec![],
+            auth_token: match authToken {
+                None => None,
+                Some(t) => Some(t.clone().try_innto().map_err(failed_conversion)?),
+            },
+            timestamp_token: timeStampToken.map(|t| t.clone().innto()),
+        };
+        while !input.is_empty() {
+            let mut req = req_template.clone();
+            let batch_len = core::cmp::min(MAX_DATA_SIZE, input.len());
+            req.input = input[..batch_len].to_vec();
+            input = &input[batch_len..];
+            let _rsp: UpdateAadResponse = self.execute(req).map_err(|e| {
+                // Any failure invalidates the operation
+                self.invalidate();
+                e
+            })?;
+        }
+        Ok(())
+    }
+    fn update(
+        &self,
+        mut input: &[u8],
+        authToken: Option<&keymint::HardwareAuthToken::HardwareAuthToken>,
+        timeStampToken: Option<&TimeStampToken>,
+    ) -> binder::Result<Vec<u8>> {
+        let req_template = UpdateRequest {
+            op_handle: self.validate_handle()?,
+            input: input.to_vec(),
+            auth_token: match authToken {
+                None => None,
+                Some(t) => Some(t.clone().try_innto().map_err(failed_conversion)?),
+            },
+            timestamp_token: timeStampToken.map(|t| t.clone().innto()),
+        };
+        let mut output = vec![];
+        while !input.is_empty() {
+            let mut req = req_template.clone();
+            let batch_len = core::cmp::min(MAX_DATA_SIZE, input.len());
+            req.input = input[..batch_len].to_vec();
+            input = &input[batch_len..];
+            let rsp: UpdateResponse = self.execute(req).map_err(|e| {
+                self.invalidate();
+                e
+            })?;
+            output.extend_from_slice(&rsp.ret);
+        }
+        Ok(output)
+    }
+    fn finish(
+        &self,
+        input: Option<&[u8]>,
+        signature: Option<&[u8]>,
+        authToken: Option<&keymint::HardwareAuthToken::HardwareAuthToken>,
+        timestampToken: Option<&TimeStampToken>,
+        confirmationToken: Option<&[u8]>,
+    ) -> binder::Result<Vec<u8>> {
+        let op_handle = self.validate_handle()?;
+        let auth_token = match authToken {
+            None => None,
+            Some(t) => Some(t.clone().try_innto().map_err(failed_conversion)?),
+        };
+        let timestamp_token = timestampToken.map(|t| t.clone().innto());
+        let confirmation_token = confirmationToken.map(|v| v.to_vec());
+
+        let mut output = vec![];
+        let result: binder::Result<FinishResponse> = if let Some(mut input) = input {
+            while input.len() > MAX_DATA_SIZE {
+                let req = UpdateRequest {
+                    op_handle,
+                    input: input[..MAX_DATA_SIZE].to_vec(),
+                    auth_token: auth_token.clone(),
+                    timestamp_token: timestamp_token.clone(),
+                };
+                input = &input[MAX_DATA_SIZE..];
+                let rsp: UpdateResponse = self.execute(req).map_err(|e| {
+                    self.invalidate();
+                    e
+                })?;
+                output.extend_from_slice(&rsp.ret);
+            }
+
+            self.execute(FinishRequest {
+                op_handle,
+                input: Some(input.to_vec()),
+                signature: signature.map(|v| v.to_vec()),
+                auth_token,
+                timestamp_token,
+                confirmation_token,
+            })
+        } else {
+            self.execute(FinishRequest {
+                op_handle,
+                input: None,
+                signature: signature.map(|v| v.to_vec()),
+                auth_token,
+                timestamp_token,
+                confirmation_token,
+            })
+        };
+        // Finish always invalidates the operation.
+        self.invalidate();
+        result.map(|rsp| {
+            output.extend_from_slice(&rsp.ret);
+            output
+        })
+    }
+    fn abort(&self) -> binder::Result<()> {
+        let result: binder::Result<AbortResponse> =
+            self.execute(AbortRequest { op_handle: self.validate_handle()? });
+        // Abort always invalidates the operation.
+        self.invalidate();
+        let _ = result?;
+        Ok(())
+    }
+}
diff --git a/hal/src/lib.rs b/hal/src/lib.rs
new file mode 100644
index 0000000..cd9e216
--- /dev/null
+++ b/hal/src/lib.rs
@@ -0,0 +1,262 @@
+//! Implementation of a HAL service for KeyMint.
+//!
+//! This implementation relies on a `SerializedChannel` abstraction for a communication channel to
+//! the trusted application (TA).  Incoming method invocations for the HAL service are converted
+//! into corresponding request structures, which are then serialized (using CBOR) and send down the
+//! channel.  A serialized response is then read from the channel, which is deserialized into a
+//! response structure.  The contents of this response structure are then used to populate the
+//! return values of the HAL service method.
+
+#![allow(non_snake_case)]
+
+use core::{convert::TryInto, fmt::Debug};
+use kmr_common::{
+    cbor,
+    wire::{keymint::ErrorCode, Code, KeyMintOperation},
+    AsCborValue, CborError,
+};
+use log::{error, info};
+use std::{
+    ffi::CString,
+    io::{Read, Write},
+    ops::DerefMut,
+    sync::MutexGuard,
+};
+
+pub use binder;
+
+pub mod env;
+pub mod hal;
+pub mod keymint;
+pub mod rpc;
+pub mod secureclock;
+pub mod sharedsecret;
+#[cfg(test)]
+mod tests;
+
+/// Emit a failure for a failed CBOR conversion.
+#[inline]
+pub fn failed_cbor(err: CborError) -> binder::Status {
+    binder::Status::new_service_specific_error(
+        ErrorCode::UnknownError as i32,
+        Some(&CString::new(format!("CBOR conversion failed: {:?}", err)).unwrap()),
+    )
+}
+
+/// Abstraction of a channel to a secure world TA implementation, which accepts serialized request
+/// messages and returns serialized return values (or an error if communication via the channel is
+/// lost).
+pub trait SerializedChannel: Debug + Send {
+    fn execute(&mut self, serialized_req: &[u8]) -> binder::Result<Vec<u8>>;
+}
+
+/// Write a message to a stream-oriented [`Write`] item, with length framing.
+pub fn write_msg<W: Write>(w: &mut W, data: &[u8]) -> binder::Result<()> {
+    // The underlying `Write` item does not guarantee delivery of complete messages.
+    // Make this possible by adding framing in the form of a big-endian `u32` holding
+    // the message length.
+    let data_len: u32 = data.len().try_into().map_err(|_e| {
+        binder::Status::new_exception(
+            binder::ExceptionCode::BAD_PARCELABLE,
+            Some(&CString::new("encoded request message too large").unwrap()),
+        )
+    })?;
+    let data_len_data = data_len.to_be_bytes();
+    w.write_all(&data_len_data[..]).map_err(|e| {
+        error!("Failed to write length to stream: {}", e);
+        binder::Status::new_exception(
+            binder::ExceptionCode::BAD_PARCELABLE,
+            Some(&CString::new("failed to write framing length").unwrap()),
+        )
+    })?;
+    w.write_all(data).map_err(|e| {
+        error!("Failed to write data to stream: {}", e);
+        binder::Status::new_exception(
+            binder::ExceptionCode::BAD_PARCELABLE,
+            Some(&CString::new("failed to write data").unwrap()),
+        )
+    })?;
+    Ok(())
+}
+
+/// Read a message from a stream-oriented [`Read`] item, with length framing.
+pub fn read_msg<R: Read>(r: &mut R) -> binder::Result<Vec<u8>> {
+    // The data read from the `Read` item has a 4-byte big-endian length prefix.
+    let mut len_data = [0u8; 4];
+    r.read_exact(&mut len_data).map_err(|e| {
+        error!("Failed to read length from stream: {}", e);
+        binder::Status::new_exception(binder::ExceptionCode::TRANSACTION_FAILED, None)
+    })?;
+    let len = u32::from_be_bytes(len_data);
+    let mut data = vec![0; len as usize];
+    r.read_exact(&mut data).map_err(|e| {
+        error!("Failed to read data from stream: {}", e);
+        binder::Status::new_exception(binder::ExceptionCode::TRANSACTION_FAILED, None)
+    })?;
+    Ok(data)
+}
+
+/// Message-oriented wrapper around a pair of stream-oriented channels.  This allows a pair of
+/// uni-directional channels that don't necessarily preserve message boundaries to appear as a
+/// single bi-directional channel that does preserve message boundaries.
+#[derive(Debug)]
+pub struct MessageChannel<R: Read, W: Write> {
+    r: R,
+    w: W,
+}
+
+impl<R: Read + Debug + Send, W: Write + Debug + Send> SerializedChannel for MessageChannel<R, W> {
+    fn execute(&mut self, serialized_req: &[u8]) -> binder::Result<Vec<u8>> {
+        write_msg(&mut self.w, serialized_req)?;
+        read_msg(&mut self.r)
+    }
+}
+
+/// Execute an operation by serializing and sending a request structure down a channel, and
+/// deserializing and returning the response.
+///
+/// This implementation relies on the internal serialization format for `PerformOpReq` and
+/// `PerformOpRsp` to allow direct use of the specific request/response types.
+fn channel_execute<T, R, S>(channel: &mut T, req: R) -> binder::Result<S>
+where
+    T: SerializedChannel,
+    R: AsCborValue + Code<KeyMintOperation>,
+    S: AsCborValue + Code<KeyMintOperation>,
+{
+    // Manually build an array that includes the opcode and the encoded request and encode it.
+    // This is equivalent to `PerformOpReq::to_vec()`.
+    let req_arr = cbor::value::Value::Array(vec![
+        <R>::CODE.to_cbor_value().map_err(failed_cbor)?,
+        req.to_cbor_value().map_err(failed_cbor)?,
+    ]);
+    let mut req_data = Vec::new();
+    cbor::ser::into_writer(&req_arr, &mut req_data).map_err(|e| {
+        binder::Status::new_service_specific_error(
+            ErrorCode::UnknownError as i32,
+            Some(
+                &CString::new(format!("failed to write CBOR request to buffer: {:?}", e)).unwrap(),
+            ),
+        )
+    })?;
+
+    if req_data.len() > kmr_common::wire::MAX_SIZE {
+        error!(
+            "HAL operation {:?} encodes bigger {} than max size {}",
+            <R>::CODE,
+            req_data.len(),
+            kmr_common::wire::MAX_SIZE
+        );
+        return Err(binder::Status::new_service_specific_error(
+            ErrorCode::InvalidInputLength as i32,
+            Some(&CString::new("encoded request message too large").unwrap()),
+        ));
+    }
+
+    // Send in request bytes, get back response bytes.
+    let rsp_data = channel.execute(&req_data)?;
+
+    // Convert the raw response data to an array of [error code, opt_response].
+    let rsp_value = kmr_common::read_to_value(&rsp_data).map_err(failed_cbor)?;
+    let mut rsp_array = match rsp_value {
+        cbor::value::Value::Array(a) if a.len() == 2 => a,
+        _ => {
+            error!("HAL: failed to parse response data 2-array!");
+            return kmr_common::cbor_type_error(&rsp_value, "arr of len 2").map_err(failed_cbor);
+        }
+    };
+    let opt_response = rsp_array.remove(1);
+    let error_code = <ErrorCode>::from_cbor_value(rsp_array.remove(0)).map_err(failed_cbor)?;
+    if error_code != ErrorCode::Ok {
+        error!("HAL: command {:?} failed: {:?}", <R>::CODE, error_code);
+        return Err(binder::Status::new_service_specific_error(error_code as i32, None));
+    }
+
+    // The optional response should be an array of exactly 1 element (because the 0-element case
+    // corresponds to a non-OK error code, which has just been dealt with).
+    let rsp = match opt_response {
+        cbor::value::Value::Array(mut a) if a.len() == 1 => a.remove(0),
+        _ => {
+            error!("HAL: failed to parse response data structure!");
+            return kmr_common::cbor_type_error(&opt_response, "arr of len 1").map_err(failed_cbor);
+        }
+    };
+
+    // The response is expected to be an array of 2 elements: a op_type code and an encoded response
+    // structure.  The op_type code indicates the type of response structure, which should be what
+    // we expect.
+    let mut inner_rsp_array = match rsp {
+        cbor::value::Value::Array(a) if a.len() == 2 => a,
+        _ => {
+            error!("HAL: failed to parse inner response data structure!");
+            return kmr_common::cbor_type_error(&rsp, "arr of len 2").map_err(failed_cbor);
+        }
+    };
+    let inner_rsp = inner_rsp_array.remove(1);
+    let op_type =
+        <KeyMintOperation>::from_cbor_value(inner_rsp_array.remove(0)).map_err(failed_cbor)?;
+    if op_type != <S>::CODE {
+        error!("HAL: inner response data for unexpected opcode {:?}!", op_type);
+        return Err(failed_cbor(CborError::UnexpectedItem("wrong ret code", "rsp ret code")));
+    }
+
+    <S>::from_cbor_value(inner_rsp).map_err(failed_cbor)
+}
+
+/// Abstraction of a HAL service that uses an underlying [`SerializedChannel`] to communicate with
+/// an associated TA.
+trait ChannelHalService<T: SerializedChannel> {
+    /// Return the underlying channel.
+    fn channel(&self) -> MutexGuard<T>;
+
+    /// Execute the given request, by serializing it and sending it down the internal channel.  Then
+    /// read and deserialize the response.
+    fn execute<R, S>(&self, req: R) -> binder::Result<S>
+    where
+        R: AsCborValue + Code<KeyMintOperation>,
+        S: AsCborValue + Code<KeyMintOperation>,
+    {
+        channel_execute(self.channel().deref_mut(), req)
+    }
+}
+
+/// Let the TA know information about the userspace environment.
+pub fn send_hal_info<T: SerializedChannel>(channel: &mut T) -> binder::Result<()> {
+    let req = env::populate_hal_info().map_err(|e| {
+        binder::Status::new_exception(
+            binder::ExceptionCode::BAD_PARCELABLE,
+            Some(&CString::new(format!("failed to determine HAL environment: {}", e)).unwrap()),
+        )
+    })?;
+    info!("HAL->TA: environment info is {:?}", req);
+    let _rsp: kmr_common::wire::SetHalInfoResponse = channel_execute(channel, req)?;
+    Ok(())
+}
+
+/// Let the TA know information about the boot environment.
+pub fn send_boot_info<T: SerializedChannel>(
+    channel: &mut T,
+    req: kmr_common::wire::SetBootInfoRequest,
+) -> binder::Result<()> {
+    info!("boot->TA: boot info is {:?}", req);
+    let _rsp: kmr_common::wire::SetBootInfoResponse = channel_execute(channel, req)?;
+    Ok(())
+}
+
+/// Provision the TA with attestation ID information.
+pub fn send_attest_ids<T: SerializedChannel>(
+    channel: &mut T,
+    ids: kmr_common::wire::AttestationIdInfo,
+) -> binder::Result<()> {
+    let req = kmr_common::wire::SetAttestationIdsRequest { ids };
+    info!("provision->attestation IDs are {:?}", req);
+    let _rsp: kmr_common::wire::SetAttestationIdsResponse = channel_execute(channel, req)?;
+    Ok(())
+}
+
+/// Let the TA know that early boot has ended
+pub fn early_boot_ended<T: SerializedChannel>(channel: &mut T) -> binder::Result<()> {
+    info!("boot->TA: early boot ended");
+    let req = kmr_common::wire::EarlyBootEndedRequest {};
+    let _rsp: kmr_common::wire::EarlyBootEndedResponse = channel_execute(channel, req)?;
+    Ok(())
+}
diff --git a/hal/src/rpc.rs b/hal/src/rpc.rs
new file mode 100644
index 0000000..2af05e2
--- /dev/null
+++ b/hal/src/rpc.rs
@@ -0,0 +1,78 @@
+//! RemotelyProvisionedComponent HAL device implementation.
+
+use super::{ChannelHalService, SerializedChannel};
+use crate::binder;
+use crate::hal::{keymint, Innto};
+use kmr_common::wire::*;
+use std::sync::{Arc, Mutex, MutexGuard};
+
+/// `IRemotelyProvisionedComponent` implementation which converts all method invocations to
+/// serialized requests that are sent down the associated channel.
+pub struct Device<T: SerializedChannel + 'static> {
+    channel: Arc<Mutex<T>>,
+}
+
+impl<T: SerializedChannel + 'static> Device<T> {
+    /// Construct a new instance that uses the provided channel.
+    pub fn new(channel: Arc<Mutex<T>>) -> Self {
+        Self { channel }
+    }
+
+    /// Create a new instance wrapped in a proxy object.
+    pub fn new_as_binder(
+        channel: Arc<Mutex<T>>,
+    ) -> binder::Strong<dyn keymint::IRemotelyProvisionedComponent::IRemotelyProvisionedComponent>
+    {
+        keymint::IRemotelyProvisionedComponent::BnRemotelyProvisionedComponent::new_binder(
+            Self::new(channel),
+            binder::BinderFeatures::default(),
+        )
+    }
+}
+
+impl<T: SerializedChannel> ChannelHalService<T> for Device<T> {
+    fn channel(&self) -> MutexGuard<T> {
+        self.channel.lock().unwrap()
+    }
+}
+
+impl<T: SerializedChannel> binder::Interface for Device<T> {}
+
+impl<T: SerializedChannel> keymint::IRemotelyProvisionedComponent::IRemotelyProvisionedComponent
+    for Device<T>
+{
+    fn getHardwareInfo(&self) -> binder::Result<keymint::RpcHardwareInfo::RpcHardwareInfo> {
+        let rsp: GetRpcHardwareInfoResponse = self.execute(GetRpcHardwareInfoRequest {})?;
+        Ok(rsp.ret.innto())
+    }
+    fn generateEcdsaP256KeyPair(
+        &self,
+        testMode: bool,
+        macedPublicKey: &mut keymint::MacedPublicKey::MacedPublicKey,
+    ) -> binder::Result<Vec<u8>> {
+        let rsp: GenerateEcdsaP256KeyPairResponse =
+            self.execute(GenerateEcdsaP256KeyPairRequest { test_mode: testMode })?;
+        *macedPublicKey = rsp.maced_public_key.innto();
+        Ok(rsp.ret)
+    }
+    fn generateCertificateRequest(
+        &self,
+        testMode: bool,
+        keysToSign: &[keymint::MacedPublicKey::MacedPublicKey],
+        endpointEncryptionCertChain: &[u8],
+        challenge: &[u8],
+        deviceInfo: &mut keymint::DeviceInfo::DeviceInfo,
+        protectedData: &mut keymint::ProtectedData::ProtectedData,
+    ) -> binder::Result<Vec<u8>> {
+        let rsp: GenerateCertificateRequestResponse =
+            self.execute(GenerateCertificateRequestRequest {
+                test_mode: testMode,
+                keys_to_sign: keysToSign.iter().map(|k| k.innto()).collect(),
+                endpoint_encryption_cert_chain: endpointEncryptionCertChain.to_vec(),
+                challenge: challenge.to_vec(),
+            })?;
+        *deviceInfo = rsp.device_info.innto();
+        *protectedData = rsp.protected_data.innto();
+        Ok(rsp.ret)
+    }
+}
diff --git a/hal/src/secureclock.rs b/hal/src/secureclock.rs
new file mode 100644
index 0000000..cfa9e78
--- /dev/null
+++ b/hal/src/secureclock.rs
@@ -0,0 +1,44 @@
+//! SecureClock HAL device implementation.
+
+use super::{ChannelHalService, SerializedChannel};
+use crate::binder;
+use crate::hal::secureclock::{ISecureClock, TimeStampToken::TimeStampToken};
+use crate::hal::Innto;
+use kmr_common::wire::*;
+use std::sync::{Arc, Mutex, MutexGuard};
+
+/// `ISecureClock` implementation which converts all method invocations to serialized requests that
+/// are sent down the associated channel.
+pub struct Device<T: SerializedChannel + 'static> {
+    channel: Arc<Mutex<T>>,
+}
+
+impl<T: SerializedChannel + Send> binder::Interface for Device<T> {}
+
+impl<T: SerializedChannel + 'static> Device<T> {
+    /// Construct a new instance that uses the provided channel.
+    pub fn new(channel: Arc<Mutex<T>>) -> Self {
+        Self { channel }
+    }
+    /// Create a new instance wrapped in a proxy object.
+    pub fn new_as_binder(channel: Arc<Mutex<T>>) -> binder::Strong<dyn ISecureClock::ISecureClock> {
+        ISecureClock::BnSecureClock::new_binder(
+            Self::new(channel),
+            binder::BinderFeatures::default(),
+        )
+    }
+}
+
+impl<T: SerializedChannel> ChannelHalService<T> for Device<T> {
+    fn channel(&self) -> MutexGuard<T> {
+        self.channel.lock().unwrap()
+    }
+}
+
+impl<T: SerializedChannel> ISecureClock::ISecureClock for Device<T> {
+    fn generateTimeStamp(&self, challenge: i64) -> binder::Result<TimeStampToken> {
+        let rsp: GenerateTimeStampResponse =
+            self.execute(GenerateTimeStampRequest { challenge })?;
+        Ok(rsp.ret.innto())
+    }
+}
diff --git a/hal/src/sharedsecret.rs b/hal/src/sharedsecret.rs
new file mode 100644
index 0000000..2a66ae4
--- /dev/null
+++ b/hal/src/sharedsecret.rs
@@ -0,0 +1,53 @@
+//! SharedSecret HAL device implementation.
+
+use crate::binder;
+use crate::hal::{
+    sharedsecret::{ISharedSecret, SharedSecretParameters::SharedSecretParameters},
+    Innto,
+};
+use crate::{ChannelHalService, SerializedChannel};
+use kmr_common::wire::*;
+use std::sync::{Arc, Mutex, MutexGuard};
+
+/// `ISharedSecret` implementation which converts all method invocations to serialized requests that
+/// are sent down the associated channel.
+pub struct Device<T: SerializedChannel + 'static> {
+    channel: Arc<Mutex<T>>,
+}
+
+impl<T: SerializedChannel + Send> binder::Interface for Device<T> {}
+
+impl<T: SerializedChannel + 'static> Device<T> {
+    /// Construct a new instance that uses the provided channel.
+    pub fn new(channel: Arc<Mutex<T>>) -> Self {
+        Self { channel }
+    }
+    /// Create a new instance wrapped in a proxy object.
+    pub fn new_as_binder(
+        channel: Arc<Mutex<T>>,
+    ) -> binder::Strong<dyn ISharedSecret::ISharedSecret> {
+        ISharedSecret::BnSharedSecret::new_binder(
+            Self::new(channel),
+            binder::BinderFeatures::default(),
+        )
+    }
+}
+
+impl<T: SerializedChannel> ChannelHalService<T> for Device<T> {
+    fn channel(&self) -> MutexGuard<T> {
+        self.channel.lock().unwrap()
+    }
+}
+
+impl<T: SerializedChannel> ISharedSecret::ISharedSecret for Device<T> {
+    fn getSharedSecretParameters(&self) -> binder::Result<SharedSecretParameters> {
+        let rsp: GetSharedSecretParametersResponse =
+            self.execute(GetSharedSecretParametersRequest {})?;
+        Ok(rsp.ret.innto())
+    }
+    fn computeSharedSecret(&self, params: &[SharedSecretParameters]) -> binder::Result<Vec<u8>> {
+        let rsp: ComputeSharedSecretResponse =
+            self.execute(ComputeSharedSecretRequest { params: params.to_vec().innto() })?;
+        Ok(rsp.ret)
+    }
+}
diff --git a/hal/src/tests.rs b/hal/src/tests.rs
new file mode 100644
index 0000000..1a645b3
--- /dev/null
+++ b/hal/src/tests.rs
@@ -0,0 +1,87 @@
+use super::*;
+use crate::{
+    binder,
+    hal::keymint::{ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice},
+};
+use kmr_common::hex_decode;
+use std::sync::{Arc, Mutex};
+
+#[derive(Clone, Debug)]
+struct TestChannel {
+    req: Arc<Mutex<Vec<u8>>>,
+    rsp: Vec<u8>,
+}
+
+impl TestChannel {
+    fn new(rsp: &str) -> Self {
+        Self { req: Arc::new(Mutex::new(vec![])), rsp: hex_decode(rsp).unwrap() }
+    }
+    fn req_data(&self) -> Vec<u8> {
+        self.req.lock().unwrap().clone()
+    }
+}
+
+impl SerializedChannel for TestChannel {
+    fn execute(&mut self, serialized_req: &[u8]) -> binder::Result<Vec<u8>> {
+        *self.req.lock().unwrap() = serialized_req.to_vec();
+        Ok(self.rsp.clone())
+    }
+}
+
+#[test]
+fn test_method_roundtrip() {
+    let channel = TestChannel::new(concat!(
+        "82", // 2-arr (PerformOpResponse)
+        "00", // int   (PerformOpResponse.error_code == ErrorCode::Ok)
+        "81", // 1-arr (PerformOpResponse.rsp)
+        "82", // 2-arr (PerformOpResponse.rsp.0 : PerformOpRsp)
+        "13", // 0x13 = KeyMintOperation::DEVICE_GENERATE_KEY
+        "81", // 1-arr (GenerateKeyResponse)
+        "83", // 3-arr (ret: KeyCreationResult)
+        "41", "01", // 1-bstr (KeyCreationResult.keyBlob)
+        "80", // 0-arr (KeyCreationResult.keyCharacteristics)
+        "80", // 0-arr (KeyCreationResult.certificateChain)
+    ));
+    let imp = keymint::Device::new(Arc::new(Mutex::new(channel.clone())));
+
+    let result = imp.generateKey(&[], None).unwrap();
+
+    let want_req = concat!(
+        "82", // 2-arr (PerformOpReq)
+        "13", // 0x13 = DEVICE_GENERATE_KEY
+        "82", // 1-arr (GenerateKeyRequest)
+        "80", // 0-arr (* KeyParameter)
+        "80", // 0-arr (? AttestationKey)
+    );
+    assert_eq!(channel.req_data(), hex_decode(want_req).unwrap());
+
+    assert_eq!(result.keyBlob, vec![0x01]);
+    assert!(result.keyCharacteristics.is_empty());
+    assert!(result.certificateChain.is_empty());
+}
+
+#[test]
+fn test_method_err_roundtrip() {
+    let channel = TestChannel::new(concat!(
+        "82", // 2-arr (PerformOpResponse)
+        "21", // (PerformOpResponse.error_code = ErrorCode::UNSUPPORTED_PURPOSE)
+        "80", // 0-arr (PerformOpResponse.rsp)
+    ));
+    let imp = keymint::Device::new(Arc::new(Mutex::new(channel.clone())));
+
+    let result = imp.generateKey(&[], None);
+
+    let want_req = concat!(
+        "82", // 2-arr (PerformOpReq)
+        "13", // 0x13 = DEVICE_GENERATE_KEY
+        "82", // 1-arr (GenerateKeyRequest)
+        "80", // 0-arr (* KeyParameter)
+        "80", // 0-arr (? AttestationKey)
+    );
+    assert_eq!(channel.req_data(), hex_decode(want_req).unwrap());
+
+    assert!(result.is_err());
+    let status = result.unwrap_err();
+    assert_eq!(status.exception_code(), binder::ExceptionCode::SERVICE_SPECIFIC);
+    assert_eq!(status.service_specific_error(), ErrorCode::UNSUPPORTED_PURPOSE.0);
+}