| /* |
| * 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 kEraseAll[] = {0x80, 0x08, 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; |
| } |
| |
| // TODO: erase all, not currently used |