blob: 4abb29354f89d71c02e725bf3a3d9c99d420f53f [file] [log] [blame]
/*
* 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 "common.h"
#include "hwkey_srv_priv.h"
#include "uuids.h"
#define TLOG_LVL TLOG_LVL_DEFAULT
#define TLOG_TAG "hwkey_srv"
#include "tlog.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 uint 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,
uint slot_cnt,
uint8_t* kbuf,
size_t kbuf_len,
size_t* klen) {
if (!slots)
return HWKEY_ERR_NOT_FOUND;
for (uint 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, uint 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;
TLOGI("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;
}