[hwcrypto] Add generic support for opaque key handles

An opaque key handle uniquely identifies and gives adccess to a keyslot
for a particular client without exposing the key material to that
client. Given a handle, a delegated process (e.g. hwkey) will be able
to, if allowed, retrieve the key material.

Add structures and handler for opaque key handles. Key retrieval for
hwaes will follow in a separate change.

Bug: 185483587
Test: build.py --test com.android.trusty.hwcrypto.test
Change-Id: I8e0fcfc24bb8f03b9075d06418cf9132ebfe4884
diff --git a/hwcrypto-unittest/main.c b/hwcrypto-unittest/main.c
index e4139b0..d3bc768 100644
--- a/hwcrypto-unittest/main.c
+++ b/hwcrypto-unittest/main.c
@@ -43,17 +43,26 @@
 #define HWCRYPTO_UNITTEST_KEYBOX_ID "com.android.trusty.hwcrypto.unittest.key32"
 #define HWCRYPTO_UNITTEST_DERIVED_KEYBOX_ID \
     "com.android.trusty.hwcrypto.unittest.derived_key32"
+#define HWCRYPTO_UNITTEST_OPAQUE_HANDLE_ID \
+    "com.android.trusty.hwcrypto.unittest.opaque_handle"
+#define HWCRYPTO_UNITTEST_OPAQUE_HANDLE2_ID \
+    "com.android.trusty.hwcrypto.unittest.opaque_handle2"
+#define HWCRYPTO_UNITTEST_OPAQUE_HANDLE_NOACCESS_ID \
+    "com.android.trusty.hwcrypto.unittest.opaque_handle_noaccess"
 
 #define STORAGE_AUTH_KEY_SIZE 32
 
-#if WITH_HWCRYPTO_UNITTEST
 static const uint8_t UNITTEST_KEYSLOT[] = "unittestkeyslotunittestkeyslotun";
 static const uint8_t UNITTEST_DERIVED_KEYSLOT[] =
         "unittestderivedkeyslotunittestde";
+
+#if WITH_HWCRYPTO_UNITTEST
+#define DISABLED_WITHOUT_HWCRYPTO_UNITTEST(name) name
 #else
 #pragma message                                                                          \
         "hwcrypto-unittest is built with the WITH_HWCRYPTO_UNITTEST define not enabled." \
         "Hwkey tests will not test anything."
+#define DISABLED_WITHOUT_HWCRYPTO_UNITTEST(name) DISABLED_##name
 #endif
 
 /*
@@ -234,6 +243,228 @@
 #endif
 }
 
+TEST_F(hwkey, get_opaque_handle) {
+    uint8_t dest[HWKEY_OPAQUE_HANDLE_MAX_SIZE] = {0};
+    uint32_t actual_size = sizeof(dest);
+    long rc = hwkey_get_keyslot_data(_state->hwkey_session,
+                                     HWCRYPTO_UNITTEST_OPAQUE_HANDLE_ID, dest,
+                                     &actual_size);
+#if WITH_HWCRYPTO_UNITTEST
+    EXPECT_EQ(NO_ERROR, rc, "get hwcrypto-unittest opaque keybox");
+    EXPECT_LE(actual_size, HWKEY_OPAQUE_HANDLE_MAX_SIZE);
+
+    rc = strnlen((const char*)dest, HWKEY_OPAQUE_HANDLE_MAX_SIZE);
+    EXPECT_LT(rc, HWKEY_OPAQUE_HANDLE_MAX_SIZE,
+              "opaque handle is unexpected size");
+#else
+    EXPECT_EQ(ERR_NOT_FOUND, rc, "hwcrypto-unittest not enabled");
+#endif
+}
+
+/* The following tests require hwcrpyto-unittest to do anything useful. */
+
+TEST_F(hwkey, DISABLED_WITHOUT_HWCRYPTO_UNITTEST(get_opaque_key)) {
+    uint8_t handle[HWKEY_OPAQUE_HANDLE_MAX_SIZE] = {0};
+    uint32_t actual_size = sizeof(handle);
+    long rc = hwkey_get_keyslot_data(_state->hwkey_session,
+                                     HWCRYPTO_UNITTEST_OPAQUE_HANDLE_ID, handle,
+                                     &actual_size);
+
+    EXPECT_EQ(NO_ERROR, rc, "get hwcrypto-unittest opaque keybox");
+    EXPECT_LE(actual_size, HWKEY_OPAQUE_HANDLE_MAX_SIZE);
+    rc = strnlen((const char*)handle, HWKEY_OPAQUE_HANDLE_MAX_SIZE);
+    EXPECT_LT(rc, HWKEY_OPAQUE_HANDLE_MAX_SIZE,
+              "Unexpected opaque handle size");
+
+    uint8_t key_buf[sizeof(UNITTEST_KEYSLOT) - 1] = {0};
+    actual_size = sizeof(key_buf);
+    rc = hwkey_get_keyslot_data(_state->hwkey_session, (const char*)handle,
+                                key_buf, &actual_size);
+    EXPECT_EQ(NO_ERROR, rc, "get hwcrypto-unittest opaque key failed");
+
+    rc = memcmp(UNITTEST_KEYSLOT, key_buf, sizeof(UNITTEST_KEYSLOT) - 1);
+    EXPECT_EQ(0, rc, "opaque key did not match expected value");
+}
+
+TEST_F(hwkey, DISABLED_WITHOUT_HWCRYPTO_UNITTEST(get_multiple_opaque_handles)) {
+    uint8_t handle1[HWKEY_OPAQUE_HANDLE_MAX_SIZE] = {0};
+    uint32_t actual_size = sizeof(handle1);
+    long rc = hwkey_get_keyslot_data(_state->hwkey_session,
+                                     HWCRYPTO_UNITTEST_OPAQUE_HANDLE_ID,
+                                     handle1, &actual_size);
+    EXPECT_EQ(NO_ERROR, rc, "get hwcrypto-unittest opaque keybox");
+    EXPECT_LE(actual_size, HWKEY_OPAQUE_HANDLE_MAX_SIZE);
+
+    uint8_t handle2[HWKEY_OPAQUE_HANDLE_MAX_SIZE] = {0};
+    actual_size = sizeof(handle2);
+    rc = hwkey_get_keyslot_data(_state->hwkey_session,
+                                HWCRYPTO_UNITTEST_OPAQUE_HANDLE_NOACCESS_ID,
+                                handle2, &actual_size);
+    EXPECT_EQ(NO_ERROR, rc, "get hwcrypto-unittest opaque keybox");
+    EXPECT_LE(actual_size, HWKEY_OPAQUE_HANDLE_MAX_SIZE);
+
+    rc = memcmp(handle1, handle2, HWKEY_OPAQUE_HANDLE_MAX_SIZE);
+    EXPECT_NE(0, rc, "opaque handles should not be the same");
+
+    uint8_t key_buf[sizeof(UNITTEST_KEYSLOT)] = {0};
+    actual_size = sizeof(key_buf);
+    rc = hwkey_get_keyslot_data(_state->hwkey_session, (const char*)handle1,
+                                key_buf, &actual_size);
+    EXPECT_EQ(NO_ERROR, rc, "handle was not valid");
+    EXPECT_EQ(actual_size, sizeof(UNITTEST_KEYSLOT) - 1, "wrong key length");
+    rc = memcmp(UNITTEST_KEYSLOT, key_buf, sizeof(UNITTEST_KEYSLOT) - 1);
+    EXPECT_EQ(0, rc, "opaque key did not match expected value");
+
+    /* we are not allowed to retrieve key material for the NOACCESS handle */
+    memset(key_buf, 0, sizeof(UNITTEST_KEYSLOT));
+    actual_size = sizeof(key_buf);
+    rc = hwkey_get_keyslot_data(_state->hwkey_session, (const char*)handle2,
+                                key_buf, &actual_size);
+    EXPECT_EQ(ERR_NOT_FOUND, rc,
+              "should not be able to retrieve key for second handle");
+
+    /*
+     * We need to reconnect to ensure that the tokens have been dropped and
+     * cleared.
+     */
+    hwkey_close(_state->hwkey_session);
+    int new_sess = hwkey_open();
+    ASSERT_GE(new_sess, 0);
+    _state->hwkey_session = (hwkey_session_t)new_sess;
+
+    /* Has the keyslot data been cleared? */
+    memset(key_buf, 0, sizeof(UNITTEST_KEYSLOT));
+    actual_size = sizeof(key_buf);
+    rc = hwkey_get_keyslot_data(_state->hwkey_session, (const char*)handle1,
+                                key_buf, &actual_size);
+    EXPECT_EQ(ERR_NOT_FOUND, rc, "handle was still valid");
+
+    actual_size = sizeof(key_buf);
+    rc = hwkey_get_keyslot_data(_state->hwkey_session, (const char*)handle2,
+                                key_buf, &actual_size);
+    EXPECT_EQ(ERR_NOT_FOUND, rc, "handle was still valid");
+
+test_abort:;
+}
+
+/*
+ * Make sure that attempting to get the same handle from multiple concurrent
+ * sessions doesn't break things.
+ */
+TEST_F(hwkey,
+       DISABLED_WITHOUT_HWCRYPTO_UNITTEST(opaque_handle_multiple_sessions)) {
+    uint8_t handle1[HWKEY_OPAQUE_HANDLE_MAX_SIZE] = {0};
+    uint32_t actual_size = sizeof(handle1);
+    long rc = hwkey_get_keyslot_data(_state->hwkey_session,
+                                     HWCRYPTO_UNITTEST_OPAQUE_HANDLE_ID,
+                                     handle1, &actual_size);
+    EXPECT_EQ(NO_ERROR, rc, "get hwcrypto-unittest opaque keybox");
+    EXPECT_LE(actual_size, HWKEY_OPAQUE_HANDLE_MAX_SIZE);
+
+    int new_sess = hwkey_open();
+    ASSERT_GE(new_sess, 0);
+
+    uint8_t handle2[HWKEY_OPAQUE_HANDLE_MAX_SIZE] = {0};
+    actual_size = sizeof(handle2);
+    rc = hwkey_get_keyslot_data(new_sess, HWCRYPTO_UNITTEST_OPAQUE_HANDLE_ID,
+                                handle2, &actual_size);
+    EXPECT_EQ(ERR_ALREADY_EXISTS, rc, "retrieve same handle twice");
+
+    /* Fetch a new handle with a different keyslot from the second session */
+    actual_size = sizeof(handle2);
+    rc = hwkey_get_keyslot_data(new_sess, HWCRYPTO_UNITTEST_OPAQUE_HANDLE2_ID,
+                                handle2, &actual_size);
+    EXPECT_EQ(NO_ERROR, rc, "get hwcrypto-unittest opaque keybox");
+    EXPECT_LE(actual_size, HWKEY_OPAQUE_HANDLE_MAX_SIZE);
+
+    uint8_t key_buf[sizeof(UNITTEST_KEYSLOT)] = {0};
+
+    /* Fetch the keys via the first session */
+    actual_size = sizeof(key_buf);
+    rc = hwkey_get_keyslot_data(_state->hwkey_session, (const char*)handle1,
+                                key_buf, &actual_size);
+    EXPECT_EQ(NO_ERROR, rc, "handle was not valid");
+    EXPECT_EQ(actual_size, sizeof(UNITTEST_KEYSLOT) - 1, "wrong key length");
+    rc = memcmp(UNITTEST_KEYSLOT, key_buf, sizeof(UNITTEST_KEYSLOT) - 1);
+    EXPECT_EQ(0, rc, "opaque key did not match expected value");
+
+    memset(key_buf, 0, sizeof(UNITTEST_KEYSLOT));
+    actual_size = sizeof(key_buf);
+    rc = hwkey_get_keyslot_data(_state->hwkey_session, (const char*)handle2,
+                                key_buf, &actual_size);
+    EXPECT_EQ(NO_ERROR, rc, "handle was not valid");
+    EXPECT_EQ(actual_size, sizeof(UNITTEST_KEYSLOT) - 1, "wrong key length");
+    rc = memcmp(UNITTEST_KEYSLOT, key_buf, sizeof(UNITTEST_KEYSLOT) - 1);
+    EXPECT_EQ(0, rc, "opaque key did not match expected value");
+
+    /* Fetch the same key via the second session */
+    memset(key_buf, 0, sizeof(UNITTEST_KEYSLOT));
+    actual_size = sizeof(key_buf);
+    rc = hwkey_get_keyslot_data(new_sess, (const char*)handle1, key_buf,
+                                &actual_size);
+    EXPECT_EQ(NO_ERROR, rc, "handle was not valid");
+    EXPECT_EQ(actual_size, sizeof(UNITTEST_KEYSLOT) - 1, "wrong key length");
+    rc = memcmp(UNITTEST_KEYSLOT, key_buf, sizeof(UNITTEST_KEYSLOT) - 1);
+    EXPECT_EQ(0, rc, "opaque key did not match expected value");
+
+    memset(key_buf, 0, sizeof(UNITTEST_KEYSLOT));
+    actual_size = sizeof(key_buf);
+    rc = hwkey_get_keyslot_data(new_sess, (const char*)handle2, key_buf,
+                                &actual_size);
+    EXPECT_EQ(NO_ERROR, rc, "handle was not valid");
+    EXPECT_EQ(actual_size, sizeof(UNITTEST_KEYSLOT) - 1, "wrong key length");
+    rc = memcmp(UNITTEST_KEYSLOT, key_buf, sizeof(UNITTEST_KEYSLOT) - 1);
+    EXPECT_EQ(0, rc, "opaque key did not match expected value");
+
+    hwkey_close(new_sess);
+
+    /* Has the keyslot data been cleared? */
+    actual_size = sizeof(key_buf);
+    rc = hwkey_get_keyslot_data(_state->hwkey_session, (const char*)handle1,
+                                key_buf, &actual_size);
+    EXPECT_EQ(NO_ERROR, rc, "first session handle wasn't valid");
+
+    actual_size = sizeof(key_buf);
+    rc = hwkey_get_keyslot_data(_state->hwkey_session, (const char*)handle2,
+                                key_buf, &actual_size);
+    EXPECT_EQ(ERR_NOT_FOUND, rc, "second session handle was still valid");
+
+    /* Disconnect the original session which retrieved the handle */
+    hwkey_close(_state->hwkey_session);
+    new_sess = hwkey_open();
+    ASSERT_GE(new_sess, 0);
+    _state->hwkey_session = (hwkey_session_t)new_sess;
+
+    actual_size = sizeof(key_buf);
+    rc = hwkey_get_keyslot_data(_state->hwkey_session, (const char*)handle1,
+                                key_buf, &actual_size);
+    EXPECT_EQ(ERR_NOT_FOUND, rc, "handle was still valid");
+
+    actual_size = sizeof(key_buf);
+    rc = hwkey_get_keyslot_data(_state->hwkey_session, (const char*)handle2,
+                                key_buf, &actual_size);
+    EXPECT_EQ(ERR_NOT_FOUND, rc, "handle was still valid");
+
+test_abort:;
+}
+
+TEST_F(hwkey, DISABLED_WITHOUT_HWCRYPTO_UNITTEST(try_empty_opaque_handle)) {
+    /* Reconnect just to make sure there is no spurious handles remaining. */
+    hwkey_close(_state->hwkey_session);
+    int new_sess = hwkey_open();
+    ASSERT_GE(new_sess, 0);
+    _state->hwkey_session = (hwkey_session_t)new_sess;
+
+    uint8_t key_buf[sizeof(UNITTEST_KEYSLOT) - 1] = {0};
+    uint32_t actual_size = sizeof(key_buf);
+    long rc = hwkey_get_keyslot_data(_state->hwkey_session, "", key_buf,
+                                     &actual_size);
+    EXPECT_EQ(ERR_NOT_FOUND, rc,
+              "retrieving a key with an empty access token succeeded");
+
+test_abort:;
+}
+
 /***********************   HWRNG  UNITTEST  ***********************/
 
 static uint32_t _hist[256];
diff --git a/hwcrypto/hwkey_srv.c b/hwcrypto/hwkey_srv.c
index 5ce917c..15589ed 100644
--- a/hwcrypto/hwkey_srv.c
+++ b/hwcrypto/hwkey_srv.c
@@ -17,6 +17,8 @@
 #define TLOG_TAG "hwkey_srv"
 
 #include <assert.h>
+#include <lk/list.h>
+#include <stdbool.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -26,8 +28,10 @@
 #include <interface/hwkey/hwkey.h>
 #include <lib/tipc/tipc.h>
 #include <openssl/evp.h>
+#include <openssl/mem.h>
 #include <trusty_log.h>
 
+#include <hwcrypto/hwrng_dev.h>
 #include <hwcrypto_consts.h>
 #include "hwkey_srv_priv.h"
 
@@ -39,6 +43,30 @@
     uuid_t uuid;
 };
 
+/**
+ * An opaque key access token.
+ *
+ * Clients can retrieve an opaque access token as a handle to a key they are
+ * allowed to use but not read directly. This handle can then be passed to other
+ * crypto services which can use the token to retrieve the actual key from
+ * hwkey.
+ */
+typedef char access_token_t[HWKEY_OPAQUE_HANDLE_SIZE];
+
+struct opaque_handle_node {
+    const struct hwkey_keyslot* key_slot;
+    struct hwkey_chan_ctx* owner;
+    access_token_t token;
+    struct list_node node;
+};
+
+/*
+ * Global list of currently valid opaque handles. Each client may only have a
+ * single entry in this list for a given key slot, and this entry will be
+ * cleaned up when the connection it was created for is closed.
+ */
+static struct list_node opaque_handles = LIST_INITIAL_VALUE(opaque_handles);
+
 static void hwkey_port_handler(const uevent_t* ev, void* priv);
 static void hwkey_chan_handler(const uevent_t* ev, void* priv);
 
@@ -52,10 +80,33 @@
 static unsigned int key_slot_cnt;
 static const struct hwkey_keyslot* key_slots;
 
+static bool is_opaque_handle(const struct hwkey_keyslot* key_slot) {
+    assert(key_slot);
+    return key_slot->handler == get_key_handle;
+}
+
+static void delete_opaque_handle(struct opaque_handle_node* node) {
+    assert(node);
+
+    /* Zero out the access token just in case the memory is reused */
+    memset(node->token, 0, HWKEY_OPAQUE_HANDLE_SIZE);
+
+    list_delete(&node->node);
+    free(node);
+}
+
 /*
  * Close specified hwkey context
  */
 static void hwkey_ctx_close(struct hwkey_chan_ctx* ctx) {
+    struct opaque_handle_node* entry;
+    struct opaque_handle_node* temp;
+    list_for_every_entry_safe(&opaque_handles, entry, temp,
+                              struct opaque_handle_node, node) {
+        if (entry->owner == ctx) {
+            delete_opaque_handle(entry);
+        }
+    }
     close(ctx->chan);
     free(ctx);
 }
@@ -72,6 +123,56 @@
                       rsp_data_len);
 }
 
+static bool is_allowed_to_read_opaque_key(const uuid_t* uuid,
+                                          const struct hwkey_keyslot* slot) {
+    assert(slot);
+    const struct hwkey_opaque_handle_data* handle = slot->priv;
+    assert(handle);
+
+    for (size_t i = 0; i < handle->allowed_uuids_len; ++i) {
+        if (memcmp(handle->allowed_uuids[i], uuid, sizeof(uuid_t)) == 0) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static struct opaque_handle_node* find_opaque_handle_for_slot(
+        const struct hwkey_keyslot* slot) {
+    struct opaque_handle_node* entry;
+    list_for_every_entry(&opaque_handles, entry, struct opaque_handle_node,
+                         node) {
+        if (entry->key_slot == slot) {
+            return entry;
+        }
+    }
+
+    return NULL;
+}
+
+/*
+ * If a handle doesn't exist yet for the given slot, create and insert a new one
+ * in the global list.
+ */
+static uint32_t insert_handle_node(struct hwkey_chan_ctx* ctx,
+                                   const struct hwkey_keyslot* slot) {
+    struct opaque_handle_node* entry = find_opaque_handle_for_slot(slot);
+
+    if (!entry) {
+        entry = calloc(1, sizeof(struct opaque_handle_node));
+        if (!entry) {
+            TLOGE("Could not allocate new opaque_handle_node\n");
+            return HWKEY_ERR_GENERIC;
+        }
+
+        entry->owner = ctx;
+        entry->key_slot = slot;
+        list_add_tail(&opaque_handles, &entry->node);
+    }
+
+    return HWKEY_NO_ERROR;
+}
+
 static uint32_t _handle_slots(struct hwkey_chan_ctx* ctx,
                               const char* slot_id,
                               const struct hwkey_keyslot* slots,
@@ -90,11 +191,21 @@
         /* Check if the caller is allowed to get that key */
         if (memcmp(&ctx->uuid, slots->uuid, sizeof(uuid_t)) == 0) {
             if (slots->handler) {
+                if (is_opaque_handle(slots)) {
+                    uint32_t rc = insert_handle_node(ctx, slots);
+                    if (rc != HWKEY_NO_ERROR)
+                        return rc;
+                }
                 return slots->handler(slots, kbuf, kbuf_len, klen);
             }
         }
     }
-    return HWKEY_ERR_NOT_FOUND;
+
+    /*
+     * We couldn't match a key ID, so try to treat the id as an opaque access
+     * handle
+     */
+    return get_opaque_key(&ctx->uuid, slot_id, kbuf, kbuf_len, klen);
 }
 
 uint32_t hwkey_derived_keyslot_handler(const struct hwkey_keyslot* slot,
@@ -379,6 +490,108 @@
     key_slot_cnt = kcnt;
 }
 
+static bool is_empty_token(const char* access_token) {
+    for (int i = 0; i < HWKEY_OPAQUE_HANDLE_SIZE; i++) {
+        if (access_token[i] != 0) {
+            assert(strnlen(access_token, HWKEY_OPAQUE_HANDLE_SIZE) ==
+                   HWKEY_OPAQUE_HANDLE_SIZE - 1);
+            return false;
+        }
+    }
+    return true;
+}
+
+uint32_t get_key_handle(const struct hwkey_keyslot* slot,
+                        uint8_t* kbuf,
+                        size_t kbuf_len,
+                        size_t* klen) {
+    assert(kbuf);
+    assert(klen);
+
+    const struct hwkey_opaque_handle_data* handle = slot->priv;
+    assert(handle);
+    assert(kbuf_len >= HWKEY_OPAQUE_HANDLE_SIZE);
+
+    struct opaque_handle_node* entry = find_opaque_handle_for_slot(slot);
+    /* _handle_slots should have already created an entry for this slot */
+    assert(entry);
+
+    if (!is_empty_token(entry->token)) {
+        /*
+         * We do not allow fetching a token again for the same slot again after
+         * the token is first created and returned
+         */
+        return HWKEY_ERR_ALREADY_EXISTS;
+    }
+
+    /*
+     * We want to generate a null-terminated opaque handle with no interior null
+     * bytes, so we generate extra randomness and only use the non-zero bytes.
+     */
+    uint8_t random_buf[HWKEY_OPAQUE_HANDLE_SIZE + 2];
+    while (1) {
+        int rc = hwrng_dev_get_rng_data(random_buf, sizeof(random_buf));
+        if (rc != NO_ERROR) {
+            /* Don't leave an empty entry if we couldn't generate a token */
+            delete_opaque_handle(entry);
+            return rc;
+        }
+
+        size_t token_offset = 0;
+        for (size_t i = 0; i < sizeof(random_buf) &&
+                           token_offset < HWKEY_OPAQUE_HANDLE_SIZE - 1;
+             ++i) {
+            if (random_buf[i] != 0) {
+                entry->token[token_offset] = random_buf[i];
+                token_offset++;
+            }
+        }
+        if (token_offset == HWKEY_OPAQUE_HANDLE_SIZE - 1) {
+            break;
+        }
+    }
+
+    /* ensure that token is properly null-terminated */
+    assert(entry->token[HWKEY_OPAQUE_HANDLE_SIZE - 1] == 0);
+
+    memcpy(kbuf, entry->token, HWKEY_OPAQUE_HANDLE_SIZE);
+    *klen = HWKEY_OPAQUE_HANDLE_SIZE;
+
+    return HWKEY_NO_ERROR;
+}
+
+uint32_t get_opaque_key(const uuid_t* uuid,
+                        const char* access_token,
+                        uint8_t* kbuf,
+                        size_t kbuf_len,
+                        size_t* klen) {
+    struct opaque_handle_node* entry;
+    list_for_every_entry(&opaque_handles, entry, struct opaque_handle_node,
+                         node) {
+        /* get_key_handle should never leave an empty token in the list */
+        assert(!is_empty_token(entry->token));
+
+        if (!is_allowed_to_read_opaque_key(uuid, entry->key_slot))
+            continue;
+
+        /*
+         * We are using a constant-time memcmp here to avoid side-channel
+         * leakage of the access token. Even if we trust the service that is
+         * allowed to retrieve this key, one of its clients may be trying to
+         * brute force the token, so this comparison must be constant-time.
+         */
+        if (CRYPTO_memcmp(entry->token, access_token,
+                          HWKEY_OPAQUE_HANDLE_SIZE) == 0) {
+            const struct hwkey_opaque_handle_data* handle =
+                    entry->key_slot->priv;
+            assert(handle);
+            return handle->retriever(entry->key_slot, kbuf, kbuf_len, klen);
+        }
+    }
+
+    return HWKEY_ERR_NOT_FOUND;
+}
+
 /*
  *  Initialize HWKEY service
  */
diff --git a/hwcrypto/hwkey_srv_fake_provider.c b/hwcrypto/hwkey_srv_fake_provider.c
index 5873502..d970366 100644
--- a/hwcrypto/hwkey_srv_fake_provider.c
+++ b/hwcrypto/hwkey_srv_fake_provider.c
@@ -179,6 +179,28 @@
                 .encrypted_key_size_ptr = &_unittest_encrypted_key32_size,
                 .retriever = get_unittest_derived_key32,
 };
+
+static const uuid_t* unittest_allowed_opaque_key_uuids[] = {
+        &hwcrypto_unittest_uuid,
+};
+
+static struct hwkey_opaque_handle_data unittest_opaque_handle_data = {
+        .allowed_uuids = unittest_allowed_opaque_key_uuids,
+        .allowed_uuids_len = countof(unittest_allowed_opaque_key_uuids),
+        .retriever = get_unittest_key32,
+};
+
+static struct hwkey_opaque_handle_data unittest_opaque_handle_data2 = {
+        .allowed_uuids = unittest_allowed_opaque_key_uuids,
+        .allowed_uuids_len = countof(unittest_allowed_opaque_key_uuids),
+        .retriever = get_unittest_key32,
+};
+
+static struct hwkey_opaque_handle_data unittest_opaque_handle_data_noaccess = {
+        .allowed_uuids = NULL,
+        .allowed_uuids_len = 0,
+        .retriever = get_unittest_key32,
+};
 #endif /* WITH_HWCRYPTO_UNITTEST */
 
 /*
@@ -461,6 +483,25 @@
                 .priv = &hwcrypto_unittest_derived_data,
                 .handler = hwkey_derived_keyslot_handler,
         },
+        {
+                .uuid = &hwcrypto_unittest_uuid,
+                .key_id = "com.android.trusty.hwcrypto.unittest.opaque_handle",
+                .handler = get_key_handle,
+                .priv = &unittest_opaque_handle_data,
+        },
+        {
+                .uuid = &hwcrypto_unittest_uuid,
+                .key_id = "com.android.trusty.hwcrypto.unittest.opaque_handle2",
+                .handler = get_key_handle,
+                .priv = &unittest_opaque_handle_data2,
+        },
+        {
+                .uuid = &hwcrypto_unittest_uuid,
+                .key_id =
+                        "com.android.trusty.hwcrypto.unittest.opaque_handle_noaccess",
+                .handler = get_key_handle,
+                .priv = &unittest_opaque_handle_data_noaccess,
+        },
 #endif /* WITH_HWCRYPTO_UNITTEST */
 };
 
diff --git a/hwcrypto/hwkey_srv_priv.h b/hwcrypto/hwkey_srv_priv.h
index f744ee2..3ea3a52 100644
--- a/hwcrypto/hwkey_srv_priv.h
+++ b/hwcrypto/hwkey_srv_priv.h
@@ -15,6 +15,7 @@
  */
 #pragma once
 
+#include <interface/hwkey/hwkey.h>
 #include <lk/compiler.h>
 #include <sys/types.h>
 #include <uapi/trusty_uuid.h>
@@ -55,6 +56,37 @@
  */
 #define HWKEY_DERIVED_KEY_MAX_SIZE 32
 
+#define HWKEY_OPAQUE_HANDLE_SIZE 32
+STATIC_ASSERT(HWKEY_OPAQUE_HANDLE_SIZE <= HWKEY_OPAQUE_HANDLE_MAX_SIZE);
+
+/**
+ * struct hwkey_opaque_handle_data - Opaque handle data for keyslots that allow
+ * opaque usage in hwaes.
+ *
+ * Intended for use in the @hwkey_keyslot.priv field. The retriever function is
+ * equivalent to the generic &hwkey_keyslot->handler but is called only when a
+ * service allowed to unwrap opaque requests this handle.
+ *
+ * @token:             The access token used as an opaque handle to
+ *                     reference this keyslot
+ * @allowed_uuids:     Array of UUIDs that are allowed to retrieve the
+ *                     plaintext key corresponding to an opaque handle
+ *                     for this slot
+ * @allowed_uuids_len: Length of the @allowed_reader_uuids array
+ * @priv:              Opaque pointer to keyslot-specific data
+ * @retriever:         Keyslot-specific callback which retrieves the
+ *                     actual key corresponding to this opaque handle.
+ */
+struct hwkey_opaque_handle_data {
+    const uuid_t** allowed_uuids;
+    size_t allowed_uuids_len;
+    const void* priv;
+    uint32_t (*retriever)(const struct hwkey_keyslot* slot,
+                          uint8_t* kbuf,
+                          size_t kbuf_len,
+                          size_t* klen);
+};
+
 __BEGIN_CDECLS
 
 /**
@@ -80,6 +112,29 @@
                                        size_t kbuf_len,
                                        size_t* klen);
 
+/**
+ * get_key_handle() - Handler for opaque keys
+ *
+ * Create and return an access token for a key slot. This key slot must contain
+ * a pointer to a &struct hwkey_opaque_handle_data in the &hwkey_keyslot.priv
+ * field.
+ */
+uint32_t get_key_handle(const struct hwkey_keyslot* slot,
+                        uint8_t* kbuf,
+                        size_t kbuf_len,
+                        size_t* klen);
+
+/**
+ * get_opaque_key() - Get an opaque key given an access handle
+ *
+ * @access_token: pointer to an access_token_t
+ */
+uint32_t get_opaque_key(const uuid_t* uuid,
+                        const char* access_token,
+                        uint8_t* kbuf,
+                        size_t kbuf_len,
+                        size_t* klen);
+
 void hwkey_init_srv_provider(void);
 
 void hwkey_install_keys(const struct hwkey_keyslot* keys, unsigned int kcnt);