| /* |
| * Copyright (C) 2016-2017 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. |
| */ |
| |
| #include <assert.h> |
| #include <lk/compiler.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <trusty_ipc.h> |
| #include <uapi/err.h> |
| |
| #include <interface/hwkey/hwkey.h> |
| #include <nxp_hwcrypto_uuid_consts.h> |
| |
| #include "common.h" |
| #include "hwkey_srv_priv.h" |
| |
| #define TLOG_TAG "hwkey_srv" |
| #include <trusty_log.h> |
| |
| #define HWKEY_MAX_PAYLOAD_SIZE 2048 |
| |
| struct hwkey_chan_ctx { |
| tipc_event_handler_t evt_handler; |
| handle_t chan; |
| uuid_t uuid; |
| }; |
| |
| static void hwkey_port_handler(const uevent_t* ev, void* priv); |
| static void hwkey_chan_handler(const uevent_t* ev, void* priv); |
| |
| static tipc_event_handler_t hwkey_port_evt_handler = { |
| .proc = hwkey_port_handler, |
| }; |
| |
| /* Make sure that key_data and reg_data buffers are not crossing page boundary |
| * so it is safe to pass them to DMA. An extra byte for req_data buf is used to |
| * zero terminate string so it is OK to have it on separate page as it will |
| * never be accesed by DMA engine. |
| */ |
| static uint8_t key_data[HWKEY_MAX_PAYLOAD_SIZE] |
| __attribute__((aligned(HWKEY_MAX_PAYLOAD_SIZE))); |
| static uint8_t req_data[HWKEY_MAX_PAYLOAD_SIZE + 1] |
| __attribute__((aligned(HWKEY_MAX_PAYLOAD_SIZE))); |
| |
| static unsigned int key_slot_cnt; |
| static const struct hwkey_keyslot* key_slots; |
| |
| #if WITH_HWCRYPTO_UNITTEST |
| /* |
| * Support for hwcrypto unittest keys should be only enabled |
| * to test hwcrypto related APIs |
| */ |
| |
| /* UUID of HWCRYPTO_UNITTEST application */ |
| static const uuid_t hwcrypto_unittest_uuid = HWCRYPTO_UNITTEST_APP_UUID; |
| |
| static uint8_t _unittest_key32[32] = "unittestkeyslotunittestkeyslotun"; |
| static uint32_t get_unittest_key32(const struct hwkey_keyslot* slot, |
| uint8_t* kbuf, |
| size_t kbuf_len, |
| size_t* klen) { |
| assert(kbuf); |
| assert(klen); |
| assert(kbuf_len >= sizeof(_unittest_key32)); |
| |
| /* just return predefined key */ |
| memcpy(kbuf, _unittest_key32, sizeof(_unittest_key32)); |
| *klen = sizeof(_unittest_key32); |
| |
| return HWKEY_NO_ERROR; |
| } |
| |
| static const struct hwkey_keyslot test_key_slots[] = { |
| { |
| .uuid = &hwcrypto_unittest_uuid, |
| .key_id = "com.android.trusty.hwcrypto.unittest.key32", |
| .handler = get_unittest_key32, |
| }, |
| }; |
| #endif /* WITH_HWCRYPTO_UNITTEST */ |
| |
| /* |
| * Close specified hwkey context |
| */ |
| static void hwkey_ctx_close(struct hwkey_chan_ctx* ctx) { |
| close(ctx->chan); |
| free(ctx); |
| } |
| |
| /* |
| * Send response message |
| */ |
| static int hwkey_send_rsp(struct hwkey_chan_ctx* ctx, |
| struct hwkey_msg* rsp_hdr, |
| uint8_t* rsp_data, |
| size_t rsp_data_len) { |
| rsp_hdr->cmd |= HWKEY_RESP_BIT; |
| return tipc_send_two_segments(ctx->chan, rsp_hdr, sizeof(*rsp_hdr), |
| rsp_data, rsp_data_len); |
| } |
| |
| static uint32_t _handle_slots(struct hwkey_chan_ctx* ctx, |
| const char* slot_id, |
| const struct hwkey_keyslot* slots, |
| unsigned int slot_cnt, |
| uint8_t* kbuf, |
| size_t kbuf_len, |
| size_t* klen) { |
| if (!slots) |
| return HWKEY_ERR_NOT_FOUND; |
| |
| for (unsigned int i = 0; i < slot_cnt; i++, slots++) { |
| /* check key id */ |
| if (strcmp(slots->key_id, slot_id)) |
| continue; |
| |
| /* Check if the caller is allowed to get that key */ |
| if (memcmp(&ctx->uuid, slots->uuid, sizeof(uuid_t)) == 0) { |
| if (slots->handler) { |
| return slots->handler(slots, kbuf, kbuf_len, klen); |
| } |
| } |
| } |
| return HWKEY_ERR_NOT_FOUND; |
| } |
| |
| /* |
| * Handle get key slot command |
| */ |
| static int hwkey_handle_get_keyslot_cmd(struct hwkey_chan_ctx* ctx, |
| struct hwkey_msg* hdr, |
| const char* slot_id) { |
| int rc; |
| size_t klen = 0; |
| |
| hdr->status = _handle_slots(ctx, slot_id, key_slots, key_slot_cnt, key_data, |
| sizeof(key_data), &klen); |
| |
| #if WITH_HWCRYPTO_UNITTEST |
| if (hdr->status == HWKEY_ERR_NOT_FOUND) { |
| /* also search test keys */ |
| hdr->status = _handle_slots(ctx, slot_id, test_key_slots, |
| countof(test_key_slots), key_data, |
| sizeof(key_data), &klen); |
| } |
| #endif |
| |
| rc = hwkey_send_rsp(ctx, hdr, key_data, klen); |
| if (klen) { |
| /* sanitize key buffer */ |
| memset(key_data, 0, klen); |
| } |
| return rc; |
| } |
| |
| /* |
| * Handle Derive key cmd |
| */ |
| static int hwkey_handle_derive_key_cmd(struct hwkey_chan_ctx* ctx, |
| struct hwkey_msg* hdr, |
| const uint8_t* ikm_data, |
| size_t ikm_len) { |
| int rc; |
| size_t key_len = sizeof(key_data); |
| |
| /* check requested key derivation function */ |
| if (hdr->arg1 == HWKEY_KDF_VERSION_BEST) |
| hdr->arg1 = HWKEY_KDF_VERSION_1; /* we only support V1 */ |
| |
| switch (hdr->arg1) { |
| case HWKEY_KDF_VERSION_1: |
| hdr->status = derive_key_v1(&ctx->uuid, ikm_data, ikm_len, key_data, |
| &key_len); |
| break; |
| |
| default: |
| TLOGE("%u is unsupported KDF function\n", hdr->arg1); |
| key_len = 0; |
| hdr->status = HWKEY_ERR_NOT_IMPLEMENTED; |
| } |
| |
| rc = hwkey_send_rsp(ctx, hdr, key_data, key_len); |
| if (key_len) { |
| /* sanitize key buffer */ |
| memset(key_data, 0, key_len); |
| } |
| return rc; |
| } |
| |
| /* |
| * Read and queue HWKEY request message |
| */ |
| static int hwkey_chan_handle_msg(struct hwkey_chan_ctx* ctx) { |
| int rc; |
| size_t req_data_len; |
| struct hwkey_msg hdr; |
| |
| rc = tipc_recv_two_segments(ctx->chan, &hdr, sizeof(hdr), req_data, |
| sizeof(req_data) - 1); |
| if (rc < 0) { |
| TLOGE("failed (%d) to recv msg from chan %d\n", rc, ctx->chan); |
| return rc; |
| } |
| |
| /* calculate payload length */ |
| req_data_len = (size_t)rc - sizeof(hdr); |
| |
| /* handle it */ |
| switch (hdr.cmd) { |
| case HWKEY_GET_KEYSLOT: |
| req_data[req_data_len] = 0; /* force zero termination */ |
| rc = hwkey_handle_get_keyslot_cmd(ctx, &hdr, (const char*)req_data); |
| break; |
| |
| case HWKEY_DERIVE: |
| rc = hwkey_handle_derive_key_cmd(ctx, &hdr, req_data, req_data_len); |
| memset(req_data, 0, req_data_len); /* sanitize request buffer */ |
| break; |
| |
| default: |
| TLOGE("Unsupported request: %d\n", (int)hdr.cmd); |
| hdr.status = HWKEY_ERR_NOT_IMPLEMENTED; |
| rc = hwkey_send_rsp(ctx, &hdr, NULL, 0); |
| } |
| |
| return rc; |
| } |
| |
| /* |
| * HWKEY service channel event handler |
| */ |
| static void hwkey_chan_handler(const uevent_t* ev, void* priv) { |
| struct hwkey_chan_ctx* ctx = priv; |
| |
| assert(ctx); |
| assert(ev->handle == ctx->chan); |
| |
| tipc_handle_chan_errors(ev); |
| |
| if (ev->event & IPC_HANDLE_POLL_HUP) { |
| /* closed by peer. */ |
| hwkey_ctx_close(ctx); |
| return; |
| } |
| |
| if (ev->event & IPC_HANDLE_POLL_MSG) { |
| int rc = hwkey_chan_handle_msg(ctx); |
| if (rc < 0) { |
| /* report an error and close channel */ |
| TLOGE("failed (%d) to handle event on channel %d\n", rc, |
| ev->handle); |
| hwkey_ctx_close(ctx); |
| } |
| } |
| } |
| |
| /* |
| * HWKEY service port event handler |
| */ |
| static void hwkey_port_handler(const uevent_t* ev, void* priv) { |
| uuid_t peer_uuid; |
| |
| tipc_handle_port_errors(ev); |
| |
| if (ev->event & IPC_HANDLE_POLL_READY) { |
| /* incoming connection: accept it */ |
| int rc = accept(ev->handle, &peer_uuid); |
| if (rc < 0) { |
| TLOGE("failed (%d) to accept on port %d\n", rc, ev->handle); |
| return; |
| } |
| |
| handle_t chan = (handle_t)rc; |
| struct hwkey_chan_ctx* ctx = calloc(1, sizeof(*ctx)); |
| if (!ctx) { |
| TLOGE("failed (%d) to allocate context on chan %d\n", rc, chan); |
| close(chan); |
| return; |
| } |
| |
| /* init channel state */ |
| ctx->evt_handler.priv = ctx; |
| ctx->evt_handler.proc = hwkey_chan_handler; |
| ctx->chan = chan; |
| ctx->uuid = peer_uuid; |
| |
| rc = set_cookie(chan, &ctx->evt_handler); |
| if (rc < 0) { |
| TLOGE("failed (%d) to set_cookie on chan %d\n", rc, chan); |
| hwkey_ctx_close(ctx); |
| return; |
| } |
| } |
| } |
| |
| /* |
| * Install Key slot provider |
| */ |
| void hwkey_install_keys(const struct hwkey_keyslot* keys, unsigned int kcnt) { |
| assert(key_slots == NULL); |
| assert(key_slot_cnt == 0); |
| assert(keys && kcnt); |
| |
| key_slots = keys; |
| key_slot_cnt = kcnt; |
| } |
| |
| /* |
| * Initialize HWKEY service |
| */ |
| int hwkey_start_service(void) { |
| int rc; |
| handle_t port; |
| |
| TLOGD("Start HWKEY service\n"); |
| |
| /* Initialize service */ |
| rc = port_create(HWKEY_PORT, 1, |
| sizeof(struct hwkey_msg) + HWKEY_MAX_PAYLOAD_SIZE, |
| IPC_PORT_ALLOW_TA_CONNECT); |
| if (rc < 0) { |
| TLOGE("Failed (%d) to create port %s\n", rc, HWKEY_PORT); |
| return rc; |
| } |
| |
| port = (handle_t)rc; |
| rc = set_cookie(port, &hwkey_port_evt_handler); |
| if (rc) { |
| TLOGE("failed (%d) to set_cookie on port %d\n", rc, port); |
| close(port); |
| return rc; |
| } |
| |
| return NO_ERROR; |
| } |