/*
* Copyright (C) 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 "include/ese/app/weaver.h"
/* Non-static, but visibility=hidden so they can be used in test. */
const uint8_t kManageChannelOpen[] = {0x00, 0x70, 0x00, 0x00, 0x01};
const uint32_t kManageChannelOpenLength = (uint32_t)sizeof(kManageChannelOpen);
const uint8_t kManageChannelClose[] = {0x00, 0x70, 0x80, 0x00, 0x00};
const uint8_t kSelectApplet[] = {0x00, 0xA4, 0x04, 0x00, 0x0D, 0xA0,
0x00, 0x00, 0x04, 0x76, 0x57, 0x56,
0x52, 0x43, 0x4F, 0x4D, 0x4D, 0x30};
const uint32_t kSelectAppletLength = (uint32_t)sizeof(kSelectApplet);
// Supported commands.
const uint8_t kGetNumSlots[] = {0x80, 0x02, 0x00, 0x00, 0x04};
const uint8_t kWrite[] = {0x80, 0x04, 0x00, 0x00,
4 + kEseWeaverKeySize +
kEseWeaverValueSize}; // slotid + key + value
const uint8_t kRead[] = {0x80, 0x06, 0x00, 0x00,
4 + kEseWeaverKeySize}; // slotid + key
const uint8_t kEraseValue[] = {0x80, 0x08, 0x00, 0x00, 4}; // slotid
const uint8_t kEraseAll[] = {0x80, 0x0a, 0x00, 0x00};
// Build 32-bit int from big endian bytes
static uint32_t get_uint32(uint8_t buf[4]) {
uint32_t x = buf[3];
x |= buf[2] << 8;
x |= buf[1] << 16;
x |= buf[0] << 24;
return x;
}
static void put_uint32(uint8_t buf[4], uint32_t val) {
buf[0] = 0xff & (val >> 24);
buf[1] = 0xff & (val >> 16);
buf[2] = 0xff & (val >> 8);
buf[3] = 0xff & val;
}
EseAppResult check_apdu_status(uint8_t code[2]) {
if (code[0] == 0x90 && code[1] == 0x00) {
return ESE_APP_RESULT_OK;
}
if (code[0] == 0x66 && code[1] == 0xA5) {
return ESE_APP_RESULT_ERROR_COOLDOWN;
}
if (code[0] == 0x6A && code[1] == 0x83) {
return ESE_APP_RESULT_ERROR_UNCONFIGURED;
}
/* TODO(wad) Bubble up the error code if needed. */
ALOGE("unhandled response %.2x %.2x", code[0], code[1]);
return ese_make_os_result(code[0], code[1]);
}
ESE_API void ese_weaver_session_init(struct EseWeaverSession *session) {
session->ese = NULL;
session->active = false;
session->channel_id = 0;
}
ESE_API EseAppResult ese_weaver_session_open(struct EseInterface *ese,
struct EseWeaverSession *session) {
struct EseSgBuffer tx[2];
struct EseSgBuffer rx;
uint8_t rx_buf[32];
int rx_len;
if (!ese || !session) {
ALOGE("Invalid |ese| or |session|");
return ESE_APP_RESULT_ERROR_ARGUMENTS;
}
if (session->active == true) {
ALOGE("|session| is already active");
return ESE_APP_RESULT_ERROR_ARGUMENTS;
}
/* Instantiate a logical channel */
rx_len = ese_transceive(ese, kManageChannelOpen, sizeof(kManageChannelOpen),
rx_buf, sizeof(rx_buf));
if (ese_error(ese)) {
ALOGE("transceive error: code:%d message:'%s'", ese_error_code(ese),
ese_error_message(ese));
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
if (rx_len < 0) {
ALOGE("transceive error: rx_len: %d", rx_len);
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
if (rx_len < 2) {
ALOGE("transceive error: reply too short");
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
EseAppResult ret;
ret = check_apdu_status(&rx_buf[rx_len - 2]);
if (ret != ESE_APP_RESULT_OK) {
ALOGE("MANAGE CHANNEL OPEN failed with error code: %x %x",
rx_buf[rx_len - 2], rx_buf[rx_len - 1]);
return ret;
}
if (rx_len < 3) {
ALOGE("transceive error: successful reply unexpectedly short");
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
session->ese = ese;
session->channel_id = rx_buf[rx_len - 3];
/* Select Weaver Applet. */
uint8_t chan = kSelectApplet[0] | session->channel_id;
tx[0].base = &chan;
tx[0].len = 1;
tx[1].base = (uint8_t *)&kSelectApplet[1];
tx[1].len = sizeof(kSelectApplet) - 1;
rx.base = &rx_buf[0];
rx.len = sizeof(rx_buf);
rx_len = ese_transceive_sg(ese, tx, 2, &rx, 1);
if (rx_len < 0 || ese_error(ese)) {
ALOGE("transceive error: caller should check ese_error()");
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
if (rx_len < 2) {
ALOGE("transceive error: reply too short");
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
ret = check_apdu_status(&rx_buf[rx_len - 2]);
if (ret != ESE_APP_RESULT_OK) {
ALOGE("SELECT failed with error code: %x %x", rx_buf[rx_len - 2],
rx_buf[rx_len - 1]);
return ret;
}
session->active = true;
return ESE_APP_RESULT_OK;
}
ESE_API EseAppResult
ese_weaver_session_close(struct EseWeaverSession *session) {
uint8_t rx_buf[32];
int rx_len;
if (!session || !session->ese) {
return ESE_APP_RESULT_ERROR_ARGUMENTS;
}
if (!session->active || session->channel_id == 0) {
return ESE_APP_RESULT_ERROR_ARGUMENTS;
}
/* Release the channel */
uint8_t close_channel[sizeof(kManageChannelClose)];
ese_memcpy(close_channel, kManageChannelClose, sizeof(kManageChannelClose));
close_channel[0] |= session->channel_id;
close_channel[3] |= session->channel_id;
rx_len = ese_transceive(session->ese, close_channel, sizeof(close_channel),
rx_buf, sizeof(rx_buf));
if (rx_len < 0 || ese_error(session->ese)) {
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
if (rx_len < 2) {
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
EseAppResult ret;
ret = check_apdu_status(&rx_buf[rx_len - 2]);
if (ret != ESE_APP_RESULT_OK) {
return ret;
}
session->channel_id = 0;
session->active = false;
return ESE_APP_RESULT_OK;
}
ESE_API EseAppResult ese_weaver_get_num_slots(struct EseWeaverSession *session,
uint32_t *numSlots) {
if (!session || !session->ese || !session->active) {
return ESE_APP_RESULT_ERROR_ARGUMENTS;
}
if (!session->active || session->channel_id == 0) {
return ESE_APP_RESULT_ERROR_ARGUMENTS;
}
if (!numSlots) {
return ESE_APP_RESULT_ERROR_ARGUMENTS;
}
// Prepare command
uint8_t get_num_slots[sizeof(kGetNumSlots)];
ese_memcpy(get_num_slots, kGetNumSlots, sizeof(kGetNumSlots));
get_num_slots[0] |= session->channel_id;
// Send command
uint8_t rx_buf[6];
const int rx_len =
ese_transceive(session->ese, get_num_slots, sizeof(get_num_slots), rx_buf,
sizeof(rx_buf));
// Check for errors
if (rx_len < 2 || ese_error(session->ese)) {
ALOGE("Failed to get num slots");
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
if (rx_len == 2) {
ALOGE("ese_weaver_get_num_slots: SE exception");
EseAppResult ret = check_apdu_status(rx_buf);
return ret;
}
if (rx_len != 6) {
ALOGE("Unexpected response from Weaver applet");
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
*numSlots = get_uint32(rx_buf);
return ESE_APP_RESULT_OK;
}
ESE_API EseAppResult ese_weaver_write(struct EseWeaverSession *session,
uint32_t slotId, const uint8_t *key,
const uint8_t *value) {
if (!session || !session->ese || !session->active) {
return ESE_APP_RESULT_ERROR_ARGUMENTS;
}
if (!session->active || session->channel_id == 0) {
return ESE_APP_RESULT_ERROR_ARGUMENTS;
}
if (!key || !value) {
return ESE_APP_RESULT_ERROR_ARGUMENTS;
}
// Prepare data to send
struct EseSgBuffer tx[5];
uint8_t chan = kWrite[0] | session->channel_id;
tx[0].base = &chan;
tx[0].len = 1;
tx[1].base = (uint8_t *)&kWrite[1];
tx[1].len = sizeof(kWrite) - 1;
// Slot ID in big endian
uint8_t slot_id[4];
put_uint32(slot_id, slotId);
tx[2].base = slot_id;
tx[2].len = sizeof(slot_id);
// Key and value
tx[3].c_base = key;
tx[3].len = kEseWeaverKeySize;
tx[4].c_base = value;
tx[4].len = kEseWeaverValueSize;
// Prepare buffer for result
struct EseSgBuffer rx;
uint8_t rx_buf[2];
rx.base = rx_buf;
rx.len = sizeof(rx_buf);
// Send the command
const int rx_len = ese_transceive_sg(session->ese, tx, 5, &rx, 1);
// Check for errors
if (rx_len < 2 || ese_error(session->ese)) {
ALOGE("Failed to write to slot");
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
if (rx_len > 2) {
ALOGE("Unexpected response from Weaver applet");
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
return check_apdu_status(rx_buf);
}
ESE_API EseAppResult ese_weaver_read(struct EseWeaverSession *session,
uint32_t slotId, const uint8_t *key,
uint8_t *value, uint32_t *timeout) {
if (!session || !session->ese || !session->active) {
return ESE_APP_RESULT_ERROR_ARGUMENTS;
}
if (!session->active || session->channel_id == 0) {
return ESE_APP_RESULT_ERROR_ARGUMENTS;
}
if (!key || !value || !timeout) {
return ESE_APP_RESULT_ERROR_ARGUMENTS;
}
// Prepare data to send
struct EseSgBuffer tx[5];
uint8_t chan = kRead[0] | session->channel_id;
tx[0].base = &chan;
tx[0].len = 1;
tx[1].base = (uint8_t *)&kRead[1];
tx[1].len = sizeof(kRead) - 1;
// Slot ID in big endian
uint8_t slot_id[4];
put_uint32(slot_id, slotId);
tx[2].base = slot_id;
tx[2].len = sizeof(slot_id);
// Key of 16 bytes
tx[3].c_base = key;
tx[3].len = kEseWeaverKeySize;
// Value response is 16 bytes
const uint8_t maxResponse = 1 + kEseWeaverValueSize;
tx[4].c_base = &maxResponse;
tx[4].len = 1;
// Prepare buffer for result
struct EseSgBuffer rx[3];
uint8_t appletStatus;
rx[0].base = &appletStatus;
rx[0].len = 1;
rx[1].base = value;
rx[1].len = kEseWeaverValueSize;
uint8_t rx_buf[2];
rx[2].base = rx_buf;
rx[2].len = sizeof(rx_buf);
// Send the command
const int rx_len = ese_transceive_sg(session->ese, tx, 5, rx, 3);
// Check for errors
if (rx_len < 2 || ese_error(session->ese)) {
ALOGE("Failed to write to slot");
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
if (rx_len == 2) {
rx_buf[0] = appletStatus;
rx_buf[1] = value[0];
ALOGE("ese_weaver_read: SE exception");
EseAppResult ret = check_apdu_status(rx_buf);
return ret;
}
if (rx_len < 7) {
ALOGE("Unexpected response from Weaver applet");
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
const uint8_t READ_SUCCESS = 0x00;
const uint8_t READ_WRONG_KEY = 0x7f;
const uint8_t READ_BACK_OFF = 0x76;
const uint32_t millisInSecond = 1000;
// wrong key
if (appletStatus == READ_WRONG_KEY) {
ALOGI("ese_weaver_read wrong key provided");
*timeout = get_uint32(value) * millisInSecond;
return ESE_WEAVER_READ_WRONG_KEY;
}
// backoff
if (appletStatus == READ_BACK_OFF) {
ALOGI("ese_weaver_read wrong key provided");
*timeout = get_uint32(value) * millisInSecond;
return ESE_WEAVER_READ_TIMEOUT;
}
if (rx_len != 19) {
ALOGE("Unexpected response from Weaver applet");
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
return ESE_APP_RESULT_OK;
}
ESE_API EseAppResult ese_weaver_erase_value(struct EseWeaverSession *session,
uint32_t slotId) {
if (!session || !session->ese || !session->active) {
return ESE_APP_RESULT_ERROR_ARGUMENTS;
}
if (!session->active || session->channel_id == 0) {
return ESE_APP_RESULT_ERROR_ARGUMENTS;
}
// Prepare data to send
struct EseSgBuffer tx[3];
uint8_t chan = kEraseValue[0] | session->channel_id;
tx[0].base = &chan;
tx[0].len = 1;
tx[1].base = (uint8_t *)&kEraseValue[1];
tx[1].len = sizeof(kEraseValue) - 1;
// Slot ID in big endian
uint8_t slot_id[4];
put_uint32(slot_id, slotId);
tx[2].base = slot_id;
tx[2].len = sizeof(slot_id);
// Prepare buffer for result
struct EseSgBuffer rx;
uint8_t rx_buf[2];
rx.base = rx_buf;
rx.len = sizeof(rx_buf);
// Send the command
const int rx_len = ese_transceive_sg(session->ese, tx, 3, &rx, 1);
// Check for errors
if (rx_len < 2 || ese_error(session->ese)) {
ALOGE("Failed to write to slot");
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
if (rx_len > 2) {
ALOGE("Unexpected response from Weaver applet");
return ESE_APP_RESULT_ERROR_COMM_FAILED;
}
return check_apdu_status(rx_buf);
}
// TODO: erase all, not currently used