diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0c0d678 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,66 @@ + # + # This file is part of the Pico CCID distribution (https://github.com/polhenarejos/pico-ccid). + # Copyright (c) 2022 Pol Henarejos. + # + # This program is free software: you can redistribute it and/or modify + # it under the terms of the GNU General Public License as published by + # the Free Software Foundation, version 3. + # + # This program is distributed in the hope that it will be useful, but + # WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + # General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with this program. If not, see . + # + +cmake_minimum_required(VERSION 3.13) + +include(pico_sdk_import.cmake) + +project(pico_ccid C CXX ASM) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +pico_sdk_init() + +add_executable(pico_ccid) + +if (NOT DEFINED USB_VID) + set(USB_VID 0xFEFF) +endif() +add_definitions(-DUSB_VID=${USB_VID}) +if (NOT DEFINED USB_PID) + set(USB_PID 0xFCFD) +endif() +add_definitions(-DUSB_PID=${USB_PID}) + +set_source_files_properties( + ${CMAKE_CURRENT_LIST_DIR}/OpenSC/src/libopensc/ctx.c + PROPERTIES COMPILE_DEFINITIONS "PACKAGE_VERSION=\"0.22.0\";OPENSC_CONF_PATH=\".\"" + ) + +target_sources(pico_ccid PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/src/ccid/ccid2040.c + ${CMAKE_CURRENT_LIST_DIR}/src/usb/usb_descriptors.c + ${CMAKE_CURRENT_LIST_DIR}/src/fs/file.c + ${CMAKE_CURRENT_LIST_DIR}/src/fs/flash.c + ${CMAKE_CURRENT_LIST_DIR}/src/fs/low_flash.c + ${CMAKE_CURRENT_LIST_DIR}/src/rng/random.c + ${CMAKE_CURRENT_LIST_DIR}/src/rng/neug.c + ) + +target_include_directories(pico_ccid PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/src/fs + ${CMAKE_CURRENT_LIST_DIR}/src/ccid + ${CMAKE_CURRENT_LIST_DIR}/src/rng + ${CMAKE_CURRENT_LIST_DIR}/src/usb + ) + +pico_add_extra_outputs(pico_ccid) + +#target_compile_definitions(pico_ccid PRIVATE MBEDTLS_ECDSA_DETERMINISTIC=1) + +target_link_libraries(pico_ccid PRIVATE pico_stdlib tinyusb_device tinyusb_board pico_multicore hardware_flash hardware_sync hardware_adc pico_unique_id hardware_rtc) diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake new file mode 100644 index 0000000..28efe9e --- /dev/null +++ b/pico_sdk_import.cmake @@ -0,0 +1,62 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/src/ccid/ccid2040.c b/src/ccid/ccid2040.c new file mode 100644 index 0000000..92ac0d5 --- /dev/null +++ b/src/ccid/ccid2040.c @@ -0,0 +1,1575 @@ +/* + * This file is part of the Pico CCID distribution (https://github.com/polhenarejos/pico-ccid). + * Copyright (c) 2022 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include + +// Pico +#include "pico/stdlib.h" +#include + +// For memcpy +#include + +#include "bsp/board.h" +#include "tusb.h" +#include "usb_descriptors.h" +#include "device/usbd_pvt.h" +#include "pico/multicore.h" +#include "random.h" +#include "ccid2040.h" +#include "hardware/rtc.h" + +extern void do_flash(); +extern void low_flash_init(); + +static uint8_t itf_num; + +#if MAX_RES_APDU_DATA_SIZE > MAX_CMD_APDU_DATA_SIZE +#define USB_BUF_SIZE (MAX_RES_APDU_DATA_SIZE+20+9) +#else +#define USB_BUF_SIZE (MAX_CMD_APDU_DATA_SIZE+20+9) +#endif + +struct apdu apdu; +static struct ccid ccid; + +static uint8_t ccid_buffer[USB_BUF_SIZE]; + +#define CCID_SET_PARAMS 0x61 /* non-ICCD command */ +#define CCID_POWER_ON 0x62 +#define CCID_POWER_OFF 0x63 +#define CCID_SLOT_STATUS 0x65 /* non-ICCD command */ +#define CCID_SECURE 0x69 /* non-ICCD command */ +#define CCID_GET_PARAMS 0x6C /* non-ICCD command */ +#define CCID_RESET_PARAMS 0x6D /* non-ICCD command */ +#define CCID_XFR_BLOCK 0x6F +#define CCID_DATA_BLOCK_RET 0x80 +#define CCID_SLOT_STATUS_RET 0x81 /* non-ICCD result */ +#define CCID_PARAMS_RET 0x82 /* non-ICCD result */ + +#define CCID_MSG_SEQ_OFFSET 6 +#define CCID_MSG_STATUS_OFFSET 7 +#define CCID_MSG_ERROR_OFFSET 8 +#define CCID_MSG_CHAIN_OFFSET 9 +#define CCID_MSG_DATA_OFFSET 10 /* == CCID_MSG_HEADER_SIZE */ +#define CCID_MAX_MSG_DATA_SIZE USB_BUF_SIZE + +#define CCID_STATUS_RUN 0x00 +#define CCID_STATUS_PRESENT 0x01 +#define CCID_STATUS_NOTPRESENT 0x02 +#define CCID_CMD_STATUS_OK 0x00 +#define CCID_CMD_STATUS_ERROR 0x40 +#define CCID_CMD_STATUS_TIMEEXT 0x80 + +#define CCID_ERROR_XFR_OVERRUN 0xFC + +/* + * Since command-byte is at offset 0, + * error with offset 0 means "command not supported". + */ +#define CCID_OFFSET_CMD_NOT_SUPPORTED 0 +#define CCID_OFFSET_DATA_LEN 1 +#define CCID_OFFSET_PARAM 8 + +static app_t apps[4]; +static uint8_t num_apps = 0; + +app_t *current_app = NULL; + +extern void card_thread(); + +queue_t *card_comm = NULL; +queue_t *ccid_comm = NULL; +extern void low_flash_init_core1(); + +int register_app(app_t * (*select_aid)()) { + if (num_apps < sizeof(apps)/sizeof(app_t)) { + apps[num_apps].select_aid = select_aid; + num_apps++; + return 1; + } + return 0; +} + +struct ep_in { + uint8_t ep_num; + uint8_t tx_done; + const uint8_t *buf; + size_t cnt; + size_t buf_len; + void *priv; + void (*next_buf) (struct ep_in *epi, size_t len); +}; + +static void epi_init (struct ep_in *epi, int ep_num, void *priv) { + epi->ep_num = ep_num; + epi->tx_done = 0; + epi->buf = NULL; + epi->cnt = 0; + epi->buf_len = 0; + epi->priv = priv; + epi->next_buf = NULL; +} + +struct ep_out { + uint8_t ep_num; + uint8_t err; + uint8_t *buf; + size_t cnt; + size_t buf_len; + void *priv; + void (*next_buf) (struct ep_out *epo, size_t len); + int (*end_rx) (struct ep_out *epo, size_t orig_len); + uint8_t ready; +}; + +static struct ep_out endpoint_out; +static struct ep_in endpoint_in; + +static void epo_init (struct ep_out *epo, int ep_num, void *priv) { + epo->ep_num = ep_num; + epo->err = 0; + epo->buf = NULL; + epo->cnt = 0; + epo->buf_len = 0; + epo->priv = priv; + epo->next_buf = NULL; + epo->end_rx = NULL; + epo->ready = 0; +} + +struct ccid_header { + uint8_t msg_type; + uint32_t data_len; + uint8_t slot; + uint8_t seq; + uint8_t rsvd; + uint16_t param; +} __attribute__((packed)); + + +/* Data structure handled by CCID layer */ +struct ccid { + uint32_t ccid_state : 4; + uint32_t state : 4; + uint32_t err : 1; + uint32_t tx_busy : 1; + uint32_t timeout_cnt: 3; + + uint8_t *p; + size_t len; + + struct ccid_header ccid_header; + + uint8_t sw1sw2[2]; + uint8_t chained_cls_ins_p1_p2[4]; + + struct ep_out *epo; + struct ep_in *epi; + + queue_t ccid_comm; + queue_t card_comm; + + uint8_t application; + + struct apdu *a; +}; + +static uint8_t endp1_rx_buf[64]; +static uint8_t endp1_tx_buf[64]; + +#define APDU_STATE_WAIT_COMMAND 0 +#define APDU_STATE_COMMAND_CHAINING 1 +#define APDU_STATE_COMMAND_RECEIVED 2 +#define APDU_STATE_RESULT 3 +#define APDU_STATE_RESULT_GET_RESPONSE 4 + +static void ccid_prepare_receive (struct ccid *c); +static void apdu_init (struct apdu *a); + +static void ccid_reset (struct ccid *c) { + apdu_init(c->a); + c->err = 0; + c->tx_busy = 0; + c->state = APDU_STATE_WAIT_COMMAND; + c->p = c->a->cmd_apdu_data; + c->len = MAX_CMD_APDU_DATA_SIZE; + c->a->cmd_apdu_data_len = 0; + c->a->expected_res_size = 0; +} + +static void ccid_init(struct ccid *c, struct ep_in *epi, struct ep_out *epo, struct apdu *a) { + c->ccid_state = CCID_STATE_START; + c->err = 0; + c->tx_busy = 0; + c->state = APDU_STATE_WAIT_COMMAND; + c->p = a->cmd_apdu_data; + c->len = MAX_CMD_APDU_DATA_SIZE; + memset (&c->ccid_header, 0, sizeof (struct ccid_header)); + c->sw1sw2[0] = 0x90; + c->sw1sw2[1] = 0x00; + c->application = 0; + c->epi = epi; + c->epo = epo; + c->a = a; + + queue_init(&c->card_comm, sizeof(uint32_t), 64); + queue_init(&c->ccid_comm, sizeof(uint32_t), 64); +} + +#define CMD_APDU_HEAD_SIZE 5 + +static void apdu_init (struct apdu *a) { + a->seq = 0; /* will be set by lower layer */ + a->cmd_apdu_head = &ccid_buffer[0]; + a->cmd_apdu_data = &ccid_buffer[5]; + a->cmd_apdu_data_len = 0; /* will be set by lower layer */ + a->expected_res_size = 0; /* will be set by lower layer */ + + a->sw = 0x9000; /* will be set by upper layer */ + a->res_apdu_data = &ccid_buffer[5]; /* will be set by upper layer */ + a->res_apdu_data_len = 0; /* will be set by upper layer */ +} + +/* +!!!! IT USES ENDP2, Interruption + +#define NOTIFY_SLOT_CHANGE 0x50 +static void ccid_notify_slot_change(struct ccid *c) +{ + uint8_t msg; + uint8_t notification[2]; + + if (c->ccid_state == CCID_STATE_NOCARD) + msg = 0x02; + else + msg = 0x03; + + notification[0] = NOTIFY_SLOT_CHANGE; + notification[1] = msg; + + tud_vendor_write(notification, sizeof(notification)); +} +*/ + +#define USB_CCID_TIMEOUT (50) + +#define GPG_THREAD_TERMINATED 0xffff +#define GPG_ACK_TIMEOUT 0x6600 + +static void ccid_init_cb(void) { + struct ccid *c = &ccid; + TU_LOG1("-------- CCID INIT\r\n"); + vendord_init(); + + //ccid_notify_slot_change(c); +} + +static void ccid_reset_cb(uint8_t rhport) { + TU_LOG1("-------- CCID RESET\r\n"); + itf_num = 0; + vendord_reset(rhport); +} + +static uint16_t ccid_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + uint8_t *itf_vendor = (uint8_t *)malloc(sizeof(uint8_t)*max_len); + TU_LOG1("-------- CCID OPEN\r\n"); + TU_VERIFY(itf_desc->bInterfaceClass == TUSB_CLASS_SMART_CARD && itf_desc->bInterfaceSubClass == 0 && itf_desc->bInterfaceProtocol == 0, 0); + + //vendord_open expects a CLASS_VENDOR interface class + memcpy(itf_vendor, itf_desc, sizeof(uint8_t)*max_len); + ((tusb_desc_interface_t *)itf_vendor)->bInterfaceClass = TUSB_CLASS_VENDOR_SPECIFIC; + vendord_open(rhport, (tusb_desc_interface_t *)itf_vendor, max_len); + free(itf_vendor); + + uint16_t const drv_len = sizeof(tusb_desc_interface_t) + sizeof(struct ccid_class_descriptor) + 2*sizeof(tusb_desc_endpoint_t); + TU_VERIFY(max_len >= drv_len, 0); + + itf_num = itf_desc->bInterfaceNumber; + return drv_len; +} + +// Support for parameterized reset via vendor interface control request +static bool ccid_control_xfer_cb(uint8_t __unused rhport, uint8_t stage, tusb_control_request_t const * request) { + // nothing to do with DATA & ACK stage + TU_LOG2("-------- CCID CTRL XFER\r\n"); + if (stage != CONTROL_STAGE_SETUP) return true; + + if (request->wIndex == itf_num) + { + TU_LOG2("-------- bmRequestType %x, bRequest %x, wValue %x, wLength %x\r\n",request->bmRequestType,request->bRequest, request->wValue, request->wLength); +/* +#if PICO_STDIO_USB_RESET_INTERFACE_SUPPORT_RESET_TO_BOOTSEL + if (request->bRequest == RESET_REQUEST_BOOTSEL) { +#ifdef PICO_STDIO_USB_RESET_BOOTSEL_ACTIVITY_LED + uint gpio_mask = 1u << PICO_STDIO_USB_RESET_BOOTSEL_ACTIVITY_LED; +#else + uint gpio_mask = 0u; +#endif +#if !PICO_STDIO_USB_RESET_BOOTSEL_FIXED_ACTIVITY_LED + if (request->wValue & 0x100) { + gpio_mask = 1u << (request->wValue >> 9u); + } +#endif + reset_usb_boot(gpio_mask, (request->wValue & 0x7f) | PICO_STDIO_USB_RESET_BOOTSEL_INTERFACE_DISABLE_MASK); + // does not return, otherwise we'd return true + } +#endif + +#if PICO_STDIO_USB_RESET_INTERFACE_SUPPORT_RESET_TO_FLASH_BOOT + if (request->bRequest == RESET_REQUEST_FLASH) { + watchdog_reboot(0, 0, PICO_STDIO_USB_RESET_RESET_TO_FLASH_DELAY_MS); + return true; + } +#endif +*/ + return true; + } + return false; +} + +static bool ccid_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + //TU_LOG2("------ CALLED XFER_CB\r\n"); + return vendord_xfer_cb(rhport, ep_addr, result, xferred_bytes); + //return true; +} + + +static usbd_class_driver_t const ccid_driver = { +#if CFG_TUSB_DEBUG >= 2 + .name = "CCID", +#endif + .init = ccid_init_cb, + .reset = ccid_reset_cb, + .open = ccid_open, + .control_xfer_cb = ccid_control_xfer_cb, + .xfer_cb = ccid_xfer_cb, + .sof = NULL +}; + +// Implement callback to add our custom driver +usbd_class_driver_t const *usbd_app_driver_get_cb(uint8_t *driver_count) { + *driver_count = 1; + return &ccid_driver; +} + + +static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED; + +void led_set_blink(uint32_t mode) { + blink_interval_ms = mode; +} + +void execute_tasks(); + +static void wait_button() { + led_set_blink((1000 << 16) | 100); + while (board_button_read() == false) { + execute_tasks(); + //sleep_ms(10); + } + while (board_button_read() == true) { + execute_tasks(); + //sleep_ms(10); + } + led_set_blink(BLINK_PROCESSING); +} + +void usb_tx_enable(const uint8_t *buf, uint32_t len) { + if (len > 0) { + if (buf[0] != 0x81) + DEBUG_PAYLOAD(buf,len); + //DEBUG_PAYLOAD(buf,len); + tud_vendor_write(buf, len); + } +} + +/* + * ATR (Answer To Reset) string + * + * TS = 0x3b: Direct conversion + * T0 = 0xda: TA1, TC1 and TD1 follow, 10 historical bytes + * TA1 = 0x11: FI=1, DI=1 + * TC1 = 0xff + * TD1 = 0x81: TD2 follows, T=1 + * TD2 = 0xb1: TA3, TB3 and TD3 follow, T=1 + * TA3 = 0xFE: IFSC = 254 bytes + * TB3 = 0x55: BWI = 5, CWI = 5 (BWT timeout 3.2 sec) + * TD3 = 0x1f: TA4 follows, T=15 + * TA4 = 0x03: 5V or 3.3V + * + * Minimum: 0x3b, 0x8a, 0x80, 0x01 + * + */ +static const uint8_t ATR_head[] = { + 0x3b, 0xda, 0x11, 0xff, 0x81, 0xb1, 0xfe, 0x55, 0x1f, 0x03, + //0x3B,0xFE,0x18,0x00,0x00,0x81,0x31,0xFE,0x45,0x80,0x31,0x81,0x54,0x48,0x53,0x4D,0x31,0x73,0x80,0x21,0x40,0x81,0x07,0xFA +}; + +/* Send back ATR (Answer To Reset) */ +static enum ccid_state ccid_power_on(struct ccid *c) { + TU_LOG1("!!! CCID POWER ON %d\r\n",c->application); + uint8_t p[CCID_MSG_HEADER_SIZE+1]; /* >= size of historical_bytes -1 */ + int hist_len = historical_bytes[0]; + + //char atr_sc_hsm[] = { 0x3B,0x8E,0x80,0x01,0x80,0x31,0x81,0x54,0x48,0x53,0x4D,0x31,0x73,0x80,0x21,0x40,0x81,0x07,0x18 }; + //char atr_sc_hsm[] = { 0x3B, 0xDE, 0x18, 0xFF, 0x81, 0x91, 0xFE, 0x1F, 0xC3, 0x80, 0x31, 0x81, 0x54, 0x48, 0x53, 0x4D, 0x31, 0x73, 0x80, 0x21, 0x40, 0x81, 0x07, 0x1C }; + char atr_sc_hsm[] = { 0x3B,0xFE,0x18,0x00,0x00,0x81,0x31,0xFE,0x45,0x80,0x31,0x81,0x54,0x48,0x53,0x4D,0x31,0x73,0x80,0x21,0x40,0x81,0x07,0xFA }; + uint8_t mode = 1; //1 sc-hsm, 0 openpgp + size_t size_atr; + if (mode == 1) + size_atr = sizeof(atr_sc_hsm); + else + size_atr = sizeof (ATR_head) + hist_len + 1; + uint8_t xor_check = 0; + int i; + if (c->application == 0) { + multicore_reset_core1(); + multicore_launch_core1(card_thread); + multicore_fifo_push_blocking((uint32_t)&c->ccid_comm); + multicore_fifo_push_blocking((uint32_t)&c->card_comm); + c->application = 1; + } + p[0] = CCID_DATA_BLOCK_RET; + p[1] = size_atr; + p[2] = 0x00; + p[3] = 0x00; + p[4] = 0x00; + p[5] = 0x00; /* Slot */ + p[CCID_MSG_SEQ_OFFSET] = c->ccid_header.seq; + p[CCID_MSG_STATUS_OFFSET] = 0x00; + p[CCID_MSG_ERROR_OFFSET] = 0x00; + p[CCID_MSG_CHAIN_OFFSET] = 0x00; + + memcpy(endp1_tx_buf, p, CCID_MSG_HEADER_SIZE); + if (mode == 1) { + memcpy(endp1_tx_buf+CCID_MSG_HEADER_SIZE, atr_sc_hsm, sizeof(atr_sc_hsm)); + } + else { + memcpy(endp1_tx_buf+CCID_MSG_HEADER_SIZE, ATR_head, sizeof (ATR_head)); + + for (i = 1; i < (int)sizeof (ATR_head); i++) + xor_check ^= ATR_head[i]; + memcpy (p, historical_bytes + 1, hist_len); +#ifdef LIFE_CYCLE_MANAGEMENT_SUPPORT + if (file_selection == 255) + p[7] = 0x03; +#endif + for (i = 0; i < hist_len; i++) + xor_check ^= p[i]; + p[i] = xor_check; + memcpy(endp1_tx_buf+CCID_MSG_HEADER_SIZE+sizeof (ATR_head), p, hist_len+1); + } + + /* This is a single packet Bulk-IN transaction */ + c->epi->buf = NULL; + c->epi->tx_done = 1; + + usb_tx_enable(endp1_tx_buf, CCID_MSG_HEADER_SIZE + size_atr); + + DEBUG_INFO("ON\r\n"); + c->tx_busy = 1; + led_set_blink(BLINK_MOUNTED); + return CCID_STATE_WAIT; +} + +static void ccid_send_status(struct ccid *c) { + uint8_t ccid_reply[CCID_MSG_HEADER_SIZE]; + + ccid_reply[0] = CCID_SLOT_STATUS_RET; + ccid_reply[1] = 0x00; + ccid_reply[2] = 0x00; + ccid_reply[3] = 0x00; + ccid_reply[4] = 0x00; + ccid_reply[5] = 0x00; /* Slot */ + ccid_reply[CCID_MSG_SEQ_OFFSET] = c->ccid_header.seq; + if (c->ccid_state == CCID_STATE_NOCARD) + ccid_reply[CCID_MSG_STATUS_OFFSET] = 2; /* 2: No ICC present */ + else if (c->ccid_state == CCID_STATE_START) + /* 1: ICC present but not activated */ + ccid_reply[CCID_MSG_STATUS_OFFSET] = 1; + else + ccid_reply[CCID_MSG_STATUS_OFFSET] = 0; /* An ICC is present and active */ + ccid_reply[CCID_MSG_ERROR_OFFSET] = 0x00; + ccid_reply[CCID_MSG_CHAIN_OFFSET] = 0x00; + + /* This is a single packet Bulk-IN transaction */ + c->epi->buf = NULL; + c->epi->tx_done = 1; + + memcpy(endp1_tx_buf, ccid_reply, CCID_MSG_HEADER_SIZE); + usb_tx_enable(endp1_tx_buf, CCID_MSG_HEADER_SIZE); + c->tx_busy = 1; +} + +static enum ccid_state ccid_power_off(struct ccid *c) { + if (c->application) { + uint32_t flag = EV_EXIT; + queue_try_add(&c->card_comm, &flag); + c->application = 0; + } + + c->ccid_state = CCID_STATE_START; /* This status change should be here */ + ccid_send_status (c); + DEBUG_INFO ("OFF\r\n"); + c->tx_busy = 1; + led_set_blink(BLINK_SUSPENDED); + return CCID_STATE_START; +} + +static void no_buf(struct ep_in *epi, size_t len) { + (void)len; + epi->buf = NULL; + epi->cnt = 0; + epi->buf_len = 0; +} + +static void set_sw1sw2(struct ccid *c, size_t chunk_len) { + if (c->a->expected_res_size >= c->len) { + c->sw1sw2[0] = 0x90; + c->sw1sw2[1] = 0x00; + } + else + { + c->sw1sw2[0] = 0x61; + if (c->len - chunk_len >= 256) + c->sw1sw2[1] = 0; + else + c->sw1sw2[1] = (uint8_t)(c->len - chunk_len); + } +} + +static void get_sw1sw2(struct ep_in *epi, size_t len) { + struct ccid *c = (struct ccid *)epi->priv; + + (void)len; + epi->buf = c->sw1sw2; + epi->cnt = 0; + epi->buf_len = 2; + epi->next_buf = no_buf; +} + +static void ccid_send_data_block_internal(struct ccid *c, uint8_t status, uint8_t error) { + int tx_size = USB_LL_BUF_SIZE; + uint8_t p[CCID_MSG_HEADER_SIZE]; + size_t len; + + if (status == 0) + len = c->a->res_apdu_data_len + 2; + else + len = 0; + + p[0] = CCID_DATA_BLOCK_RET; + p[1] = len & 0xFF; + p[2] = (len >> 8)& 0xFF; + p[3] = (len >> 16)& 0xFF; + p[4] = (len >> 24)& 0xFF; + p[5] = 0x00; /* Slot */ + p[CCID_MSG_SEQ_OFFSET] = c->a->seq; + p[CCID_MSG_STATUS_OFFSET] = status; + p[CCID_MSG_ERROR_OFFSET] = error; + p[CCID_MSG_CHAIN_OFFSET] = 0; + + memcpy(endp1_tx_buf, p, CCID_MSG_HEADER_SIZE); + + if (len == 0) { + c->epi->buf = NULL; + c->epi->tx_done = 1; + usb_tx_enable(endp1_tx_buf, CCID_MSG_HEADER_SIZE); + c->tx_busy = 1; + return; + } + + if (CCID_MSG_HEADER_SIZE + len <= USB_LL_BUF_SIZE) { + memcpy(endp1_tx_buf+CCID_MSG_HEADER_SIZE, c->a->res_apdu_data, c->a->res_apdu_data_len); + memcpy(endp1_tx_buf+CCID_MSG_HEADER_SIZE+c->a->res_apdu_data_len, c->sw1sw2, 2); + + c->epi->buf = NULL; + if (CCID_MSG_HEADER_SIZE + len < USB_LL_BUF_SIZE) + c->epi->tx_done = 1; + tx_size = CCID_MSG_HEADER_SIZE + len; + } + else if (CCID_MSG_HEADER_SIZE + len - 1 == USB_LL_BUF_SIZE) { + memcpy(endp1_tx_buf+CCID_MSG_HEADER_SIZE, c->a->res_apdu_data, c->a->res_apdu_data_len); + memcpy(endp1_tx_buf+CCID_MSG_HEADER_SIZE+c->a->res_apdu_data_len, c->sw1sw2, 1); + + c->epi->buf = &c->sw1sw2[1]; + c->epi->cnt = 1; + c->epi->buf_len = 1; + c->epi->next_buf = no_buf; + } + else if (CCID_MSG_HEADER_SIZE + len - 2 == USB_LL_BUF_SIZE) { + memcpy(endp1_tx_buf+CCID_MSG_HEADER_SIZE, c->a->res_apdu_data, c->a->res_apdu_data_len); + + c->epi->buf = &c->sw1sw2[0]; + c->epi->cnt = 0; + c->epi->buf_len = 2; + c->epi->next_buf = no_buf; + } + else { + memcpy(endp1_tx_buf+CCID_MSG_HEADER_SIZE, c->a->res_apdu_data, USB_LL_BUF_SIZE - CCID_MSG_HEADER_SIZE); + + c->epi->buf = c->a->res_apdu_data + USB_LL_BUF_SIZE - CCID_MSG_HEADER_SIZE; + c->epi->cnt = USB_LL_BUF_SIZE - CCID_MSG_HEADER_SIZE; + c->epi->buf_len = c->a->res_apdu_data_len - (USB_LL_BUF_SIZE - CCID_MSG_HEADER_SIZE); + c->epi->next_buf = get_sw1sw2; + } + usb_tx_enable(endp1_tx_buf, tx_size); + c->tx_busy = 1; +} + +static void ccid_send_data_block(struct ccid *c) { + ccid_send_data_block_internal (c, 0, 0); +} + +static void ccid_send_data_block_time_extension(struct ccid *c) { + ccid_send_data_block_internal (c, CCID_CMD_STATUS_TIMEEXT, c->ccid_state == CCID_STATE_EXECUTE? 1: 0xff); +} + +static void ccid_send_data_block_0x9000(struct ccid *c) { + uint8_t p[CCID_MSG_HEADER_SIZE+2]; + size_t len = 2; + + p[0] = CCID_DATA_BLOCK_RET; + p[1] = len & 0xFF; + p[2] = (len >> 8)& 0xFF; + p[3] = (len >> 16)& 0xFF; + p[4] = (len >> 24)& 0xFF; + p[5] = 0x00; /* Slot */ + p[CCID_MSG_SEQ_OFFSET] = c->a->seq; + p[CCID_MSG_STATUS_OFFSET] = 0; + p[CCID_MSG_ERROR_OFFSET] = 0; + p[CCID_MSG_CHAIN_OFFSET] = 0; + p[CCID_MSG_CHAIN_OFFSET+1] = 0x90; + p[CCID_MSG_CHAIN_OFFSET+2] = 0x00; + + memcpy (endp1_tx_buf, p, CCID_MSG_HEADER_SIZE + len); + + c->epi->buf = NULL; + c->epi->tx_done = 1; + usb_tx_enable (endp1_tx_buf, CCID_MSG_HEADER_SIZE + len); + c->tx_busy = 1; +} + +static void ccid_send_data_block_gr(struct ccid *c, size_t chunk_len) { + int tx_size = USB_LL_BUF_SIZE; + uint8_t p[CCID_MSG_HEADER_SIZE]; + size_t len = chunk_len + 2; + + p[0] = CCID_DATA_BLOCK_RET; + p[1] = len & 0xFF; + p[2] = (len >> 8)& 0xFF; + p[3] = (len >> 16)& 0xFF; + p[4] = (len >> 24)& 0xFF; + p[5] = 0x00; /* Slot */ + p[CCID_MSG_SEQ_OFFSET] = c->a->seq; + p[CCID_MSG_STATUS_OFFSET] = 0; + p[CCID_MSG_ERROR_OFFSET] = 0; + p[CCID_MSG_CHAIN_OFFSET] = 0; + + memcpy (endp1_tx_buf, p, CCID_MSG_HEADER_SIZE); + + set_sw1sw2 (c, chunk_len); + + if (chunk_len <= USB_LL_BUF_SIZE - CCID_MSG_HEADER_SIZE) { + int size_for_sw; + + if (chunk_len <= USB_LL_BUF_SIZE - CCID_MSG_HEADER_SIZE - 2) + size_for_sw = 2; + else if (chunk_len == USB_LL_BUF_SIZE - CCID_MSG_HEADER_SIZE - 1) + size_for_sw = 1; + else + size_for_sw = 0; + + memcpy (endp1_tx_buf+CCID_MSG_HEADER_SIZE, c->p, chunk_len); + + if (size_for_sw) + memcpy (endp1_tx_buf+CCID_MSG_HEADER_SIZE+chunk_len, c->sw1sw2, size_for_sw); + + tx_size = CCID_MSG_HEADER_SIZE + chunk_len + size_for_sw; + if (size_for_sw == 2) { + c->epi->buf = NULL; + if (tx_size < USB_LL_BUF_SIZE) + c->epi->tx_done = 1; + /* Don't set epi->tx_done = 1, when it requires ZLP */ + } + else { + c->epi->buf = c->sw1sw2 + size_for_sw; + c->epi->cnt = size_for_sw; + c->epi->buf_len = 2 - size_for_sw; + c->epi->next_buf = no_buf; + } + } + else { + memcpy (endp1_tx_buf+CCID_MSG_HEADER_SIZE, c->p, USB_LL_BUF_SIZE - CCID_MSG_HEADER_SIZE); + + c->epi->buf = c->p + USB_LL_BUF_SIZE - CCID_MSG_HEADER_SIZE; + c->epi->cnt = 0; + c->epi->buf_len = chunk_len - (USB_LL_BUF_SIZE - CCID_MSG_HEADER_SIZE); + c->epi->next_buf = get_sw1sw2; + } + + c->p += chunk_len; + c->len -= chunk_len; + usb_tx_enable (endp1_tx_buf, tx_size); + c->tx_busy = 1; +} + +static void ccid_send_params(struct ccid *c) { + uint8_t p[CCID_MSG_HEADER_SIZE]; + const uint8_t params[] = { + 0x11, /* bmFindexDindex */ + 0x11, /* bmTCCKST1 */ + 0xFE, /* bGuardTimeT1 */ + 0x55, /* bmWaitingIntegersT1 */ + 0x03, /* bClockStop */ + 0xFE, /* bIFSC */ + 0 /* bNadValue */ + }; + + p[0] = CCID_PARAMS_RET; + p[1] = 0x07; /* Length = 0x00000007 */ + p[2] = 0; + p[3] = 0; + p[4] = 0; + p[5] = 0x00; /* Slot */ + p[CCID_MSG_SEQ_OFFSET] = c->ccid_header.seq; + p[CCID_MSG_STATUS_OFFSET] = 0; + p[CCID_MSG_ERROR_OFFSET] = 0; + p[CCID_MSG_CHAIN_OFFSET] = 0x01; /* ProtocolNum: T=1 */ + + memcpy (endp1_tx_buf, p, CCID_MSG_HEADER_SIZE); + memcpy (endp1_tx_buf+CCID_MSG_HEADER_SIZE, params, sizeof params); + + /* This is a single packet Bulk-IN transaction */ + c->epi->buf = NULL; + c->epi->tx_done = 1; + usb_tx_enable (endp1_tx_buf, CCID_MSG_HEADER_SIZE + sizeof params); + c->tx_busy = 1; +} + +static void ccid_error(struct ccid *c, int offset) { + uint8_t ccid_reply[CCID_MSG_HEADER_SIZE]; + + ccid_reply[0] = CCID_SLOT_STATUS_RET; /* Any value should be OK */ + ccid_reply[1] = 0x00; + ccid_reply[2] = 0x00; + ccid_reply[3] = 0x00; + ccid_reply[4] = 0x00; + ccid_reply[5] = 0x00; /* Slot */ + ccid_reply[CCID_MSG_SEQ_OFFSET] = c->ccid_header.seq; + if (c->ccid_state == CCID_STATE_NOCARD) + ccid_reply[CCID_MSG_STATUS_OFFSET] = 2; /* 2: No ICC present */ + else if (c->ccid_state == CCID_STATE_START) + /* 1: ICC present but not activated */ + ccid_reply[CCID_MSG_STATUS_OFFSET] = 1; + else + ccid_reply[CCID_MSG_STATUS_OFFSET] = 0; /* An ICC is present and active */ + ccid_reply[CCID_MSG_STATUS_OFFSET] |= CCID_CMD_STATUS_ERROR; /* Failed */ + ccid_reply[CCID_MSG_ERROR_OFFSET] = offset; + ccid_reply[CCID_MSG_CHAIN_OFFSET] = 0x00; + + /* This is a single packet Bulk-IN transaction */ + c->epi->buf = NULL; + c->epi->tx_done = 1; + memcpy (endp1_tx_buf, ccid_reply, CCID_MSG_HEADER_SIZE); + usb_tx_enable (endp1_tx_buf, CCID_MSG_HEADER_SIZE); + c->tx_busy = 1; +} + +#define INS_GET_RESPONSE 0xc0 + +static enum ccid_state ccid_handle_data(struct ccid *c) +{ + enum ccid_state next_state = c->ccid_state; + + TU_LOG3("---- CCID STATE %d,msg_type %x,start %d\r\n",c->ccid_state,c->ccid_header.msg_type,CCID_STATE_START); + if (c->err != 0) { + ccid_reset(c); + ccid_error(c, CCID_OFFSET_DATA_LEN); + return next_state; + } + switch (c->ccid_state) { + case CCID_STATE_NOCARD: + if (c->ccid_header.msg_type == CCID_SLOT_STATUS) + ccid_send_status(c); + else { + DEBUG_INFO ("ERR00\r\n"); + ccid_error(c, CCID_OFFSET_CMD_NOT_SUPPORTED); + } + break; + case CCID_STATE_START: + if (c->ccid_header.msg_type == CCID_POWER_ON) { + ccid_reset(c); + next_state = ccid_power_on(c); + } + else if (c->ccid_header.msg_type == CCID_POWER_OFF) { + ccid_reset(c); + next_state = ccid_power_off(c); + } + else if (c->ccid_header.msg_type == CCID_SLOT_STATUS) + ccid_send_status (c); + else { + DEBUG_INFO("ERR01\r\n"); + ccid_error(c, CCID_OFFSET_CMD_NOT_SUPPORTED); + } + break; + case CCID_STATE_WAIT: + if (c->ccid_header.msg_type == CCID_POWER_ON) { + /* Not in the spec., but pcscd/libccid */ + ccid_reset(c); + next_state = ccid_power_on(c); + } + else if (c->ccid_header.msg_type == CCID_POWER_OFF) { + ccid_reset(c); + next_state = ccid_power_off(c); + } + else if (c->ccid_header.msg_type == CCID_SLOT_STATUS) + ccid_send_status(c); + else if (c->ccid_header.msg_type == CCID_XFR_BLOCK) { + if (c->ccid_header.param == 0) { + if ((c->a->cmd_apdu_head[0] & 0x10) == 0) { + if (c->state == APDU_STATE_COMMAND_CHAINING) { /* command chaining finished */ + c->p += c->a->cmd_apdu_head[4]; + c->a->cmd_apdu_head[4] = 0; + DEBUG_INFO ("CMD chaning finished.\r\n"); + } + + if (c->a->cmd_apdu_head[1] == INS_GET_RESPONSE && c->state == APDU_STATE_RESULT_GET_RESPONSE) { + size_t len = c->a->expected_res_size; + + if (c->len <= c->a->expected_res_size) + len = c->len; + + ccid_send_data_block_gr (c, len); + if (c->len == 0) + c->state = APDU_STATE_RESULT; + c->ccid_state = CCID_STATE_WAIT; + DEBUG_INFO ("GET Response.\r\n"); + } + else { /* Give this message to GPG thread */ + c->state = APDU_STATE_COMMAND_RECEIVED; + + c->a->sw = 0x9000; + c->a->res_apdu_data_len = 0; + c->a->res_apdu_data = &ccid_buffer[5]; + + uint32_t flag = EV_CMD_AVAILABLE; + queue_try_add(&c->card_comm, &flag); + + next_state = CCID_STATE_EXECUTE; + } + } + else { + if (c->state == APDU_STATE_WAIT_COMMAND) { /* command chaining is started */ + c->a->cmd_apdu_head[0] &= ~0x10; + memcpy (c->chained_cls_ins_p1_p2, c->a->cmd_apdu_head, 4); + c->state = APDU_STATE_COMMAND_CHAINING; + } + + c->p += c->a->cmd_apdu_head[4]; + c->len -= c->a->cmd_apdu_head[4]; + ccid_send_data_block_0x9000 (c); + DEBUG_INFO ("CMD chaning...\r\n"); + } + } + else { /* ICC block chaining is not supported. */ + DEBUG_INFO ("ERR02\r\n"); + ccid_error (c, CCID_OFFSET_PARAM); + } + } + else if (c->ccid_header.msg_type == CCID_SET_PARAMS || c->ccid_header.msg_type == CCID_GET_PARAMS || c->ccid_header.msg_type == CCID_RESET_PARAMS) + ccid_send_params(c); + else { + DEBUG_INFO ("ERR03\r\n"); + DEBUG_BYTE (c->ccid_header.msg_type); + ccid_error (c, CCID_OFFSET_CMD_NOT_SUPPORTED); + } + break; + case CCID_STATE_EXECUTE: + case CCID_STATE_ACK_REQUIRED_0: + case CCID_STATE_ACK_REQUIRED_1: + if (c->ccid_header.msg_type == CCID_POWER_OFF) + next_state = ccid_power_off (c); + else if (c->ccid_header.msg_type == CCID_SLOT_STATUS) + ccid_send_status (c); + else { + DEBUG_INFO ("ERR04\r\n"); + DEBUG_BYTE (c->ccid_header.msg_type); + ccid_error (c, CCID_OFFSET_CMD_NOT_SUPPORTED); + } + break; + default: + next_state = CCID_STATE_START; + DEBUG_INFO ("ERR10\r\n"); + break; + } + + return next_state; +} + +static enum ccid_state ccid_handle_timeout(struct ccid *c) { + enum ccid_state next_state = c->ccid_state; + switch (c->ccid_state) + { + case CCID_STATE_EXECUTE: + case CCID_STATE_ACK_REQUIRED_0: + case CCID_STATE_ACK_REQUIRED_1: + ccid_send_data_block_time_extension(c); + break; + default: + break; + } + + return next_state; +} + + +static void notify_icc(struct ep_out *epo) { + struct ccid *c = (struct ccid *)epo->priv; + c->err = epo->err; + uint32_t val = EV_RX_DATA_READY; + queue_try_add(&c->ccid_comm, &val); +} + +static int end_ccid_rx(struct ep_out *epo, size_t orig_len) { + (void)orig_len; + if (epo->cnt < sizeof (struct ccid_header)) + /* short packet, just ignore */ + return 1; + + /* icc message with no abdata */ + return 0; +} + +static int end_abdata(struct ep_out *epo, size_t orig_len) { + struct ccid *c = (struct ccid *)epo->priv; + size_t len = epo->cnt; + + if (orig_len == USB_LL_BUF_SIZE && len < c->ccid_header.data_len) + /* more packet comes */ + return 1; + + if (len != c->ccid_header.data_len) + epo->err = 1; + + return 0; +} + +static int end_cmd_apdu_head(struct ep_out *epo, size_t orig_len) { + struct ccid *c = (struct ccid *)epo->priv; + + (void)orig_len; + + if (epo->cnt < 4 || epo->cnt != c->ccid_header.data_len) { + epo->err = 1; + return 0; + } + + if ((c->state == APDU_STATE_COMMAND_CHAINING) + && (c->chained_cls_ins_p1_p2[0] != (c->a->cmd_apdu_head[0] & ~0x10) + || c->chained_cls_ins_p1_p2[1] != c->a->cmd_apdu_head[1] + || c->chained_cls_ins_p1_p2[2] != c->a->cmd_apdu_head[2] + || c->chained_cls_ins_p1_p2[3] != c->a->cmd_apdu_head[3])) + /* + * Handling exceptional request. + * + * Host stops sending command APDU using command chaining, + * and start another command APDU. + * + * Discard old one, and start handling new one. + */ + { + c->state = APDU_STATE_WAIT_COMMAND; + c->p = c->a->cmd_apdu_data; + c->len = MAX_CMD_APDU_DATA_SIZE; + } + + if (epo->cnt == 4) + /* No Lc and Le */ + c->a->expected_res_size = 0; + else if (epo->cnt == 5) { + /* No Lc but Le */ + c->a->expected_res_size = c->a->cmd_apdu_head[4]; + if (c->a->expected_res_size == 0) + c->a->expected_res_size = 256; + c->a->cmd_apdu_head[4] = 0; + } + else if (epo->cnt == 9) { //extended + c->a->expected_res_size = (c->a->cmd_apdu_head[7] << 8) | c->a->cmd_apdu_head[8]; + if (c->a->expected_res_size == 0) + c->a->expected_res_size = 65536; + } + + c->a->cmd_apdu_data_len = 0; + return 0; +} + +static int end_nomore_data(struct ep_out *epo, size_t orig_len) { + (void)epo; + if (orig_len == USB_LL_BUF_SIZE) + return 1; + else + return 0; +} + +static int end_cmd_apdu_data(struct ep_out *epo, size_t orig_len) { + struct ccid *c = (struct ccid *)epo->priv; + size_t len = epo->cnt; + + if (orig_len == USB_LL_BUF_SIZE && CMD_APDU_HEAD_SIZE + len < c->ccid_header.data_len) + /* more packet comes */ + return 1; + + if (CMD_APDU_HEAD_SIZE + len != c->ccid_header.data_len) + goto error; + //len is the length after lc (whole APDU = len+5) + if (c->a->cmd_apdu_head[4] == 0 && len >= 2) { //extended + if (len == 2) { + c->a->expected_res_size = (c->a->cmd_apdu_head[5] << 8) | c->a->cmd_apdu_head[6]; + if (c->a->expected_res_size == 0) + c->a->expected_res_size = 0xffff+1; + } + else { + c->a->cmd_apdu_data_len = (c->a->cmd_apdu_data[0] << 8) | c->a->cmd_apdu_data[1]; + len -= 2; + if (len < c->a->cmd_apdu_data_len) + goto error; + c->a->cmd_apdu_data += 2; + if (len == c->a->cmd_apdu_data_len) //no LE + c->a->expected_res_size = 0; + else { + if (len - c->a->cmd_apdu_data_len < 2) + goto error; + c->a->expected_res_size = (c->a->cmd_apdu_data[c->a->cmd_apdu_data_len] << 8) | c->a->cmd_apdu_data[c->a->cmd_apdu_data_len+1]; + if (c->a->expected_res_size == 0) + c->a->expected_res_size = 0xffff+1; + } + } + } + else { + + if (len == c->a->cmd_apdu_head[4]) + /* No Le field*/ + c->a->expected_res_size = 0; + else if (len == (size_t)c->a->cmd_apdu_head[4] + 1) + { + /* it has Le field*/ + c->a->expected_res_size = epo->buf[-1]; + if (c->a->expected_res_size == 0) + c->a->expected_res_size = 256; + len--; + } + else + { + error: + DEBUG_INFO("APDU header size error"); + epo->err = 1; + return 0; + } + + c->a->cmd_apdu_data_len += len; + } + return 0; +} + +static void nomore_data(struct ep_out *epo, size_t len) { + (void)len; + epo->err = 1; + epo->end_rx = end_nomore_data; + epo->buf = NULL; + epo->buf_len = 0; + epo->cnt = 0; + epo->next_buf = nomore_data; + epo->ready = 0; +} + +static void ccid_cmd_apdu_data(struct ep_out *epo, size_t len) { + struct ccid *c = (struct ccid *)epo->priv; + + (void)len; + if (c->state == APDU_STATE_RESULT_GET_RESPONSE && c->a->cmd_apdu_head[1] != INS_GET_RESPONSE) { + /* + * Handling exceptional request. + * + * Host didn't finish receiving the whole response APDU by GET RESPONSE, + * but initiates another command. + */ + + c->state = APDU_STATE_WAIT_COMMAND; + c->p = c->a->cmd_apdu_data; + c->len = MAX_CMD_APDU_DATA_SIZE; + } + else if (c->state == APDU_STATE_COMMAND_CHAINING) { + if (c->chained_cls_ins_p1_p2[0] != (c->a->cmd_apdu_head[0] & ~0x10) + || c->chained_cls_ins_p1_p2[1] != c->a->cmd_apdu_head[1] + || c->chained_cls_ins_p1_p2[2] != c->a->cmd_apdu_head[2] + || c->chained_cls_ins_p1_p2[3] != c->a->cmd_apdu_head[3]) + /* + * Handling exceptional request. + * + * Host stops sending command APDU using command chaining, + * and start another command APDU. + * + * Discard old one, and start handling new one. + */ + { + c->state = APDU_STATE_WAIT_COMMAND; + c->p = c->a->cmd_apdu_data; + c->len = MAX_CMD_APDU_DATA_SIZE; + c->a->cmd_apdu_data_len = 0; + } + } + + epo->end_rx = end_cmd_apdu_data; + epo->buf = c->p; + epo->buf_len = c->len; + epo->cnt = 0; + epo->next_buf = nomore_data; +} + +static void ccid_abdata(struct ep_out *epo, size_t len) { + struct ccid *c = (struct ccid *)epo->priv; + + (void)len; + c->a->seq = c->ccid_header.seq; + if (c->ccid_header.msg_type == CCID_XFR_BLOCK) { + c->a->seq = c->ccid_header.seq; + epo->end_rx = end_cmd_apdu_head; + epo->buf = c->a->cmd_apdu_head; + epo->buf_len = 5; + epo->cnt = 0; + epo->next_buf = ccid_cmd_apdu_data; + } + else { + epo->end_rx = end_abdata; + epo->buf = c->p; + epo->buf_len = c->len; + epo->cnt = 0; + epo->next_buf = nomore_data; + } +} + +static void ccid_prepare_receive(struct ccid *c) { + c->epo->err = 0; + c->epo->buf = (uint8_t *)&c->ccid_header; + c->epo->buf_len = sizeof (struct ccid_header); + c->epo->cnt = 0; + c->epo->next_buf = ccid_abdata; + c->epo->end_rx = end_ccid_rx; + c->epo->ready = 1; +} + +static void ccid_rx_ready(uint16_t len) { + /* + * If we support multiple CCID interfaces, we select endpoint object + * by EP_NUM. Because it has only single CCID interface now, it's + * hard-coded, here. + */ + struct ep_out *epo = &endpoint_out; + int offset = 0; + int cont; + size_t orig_len = len; + while (epo->err == 0) { + if (len == 0) + break; + else if (len <= epo->buf_len) { + memcpy (epo->buf, endp1_rx_buf + offset, len); + epo->buf += len; + epo->cnt += len; + epo->buf_len -= len; + break; + } + else /* len > buf_len */ { + memcpy (epo->buf, endp1_rx_buf + offset, epo->buf_len); + len -= epo->buf_len; + offset += epo->buf_len; + epo->next_buf (epo, len); /* Update epo->buf, cnt, buf_len */ + } + } + + /* + * ORIG_LEN to distingush ZLP and the end of transaction + * (ORIG_LEN != USB_LL_BUF_SIZE) + */ + cont = epo->end_rx (epo, orig_len); + + if (cont == 0) + notify_icc (epo); + else + epo->ready = 1; +} + +static void notify_tx(struct ep_in *epi) { + struct ccid *c = (struct ccid *)epi->priv; + + /* The sequence of Bulk-IN transactions finished */ + uint32_t flag = EV_TX_FINISHED; + queue_try_add(&c->ccid_comm, &flag); + c->tx_busy = 0; +} + +static void ccid_tx_done () { + /* + * If we support multiple CCID interfaces, we select endpoint object + * by EP_NUM. Because it has only single CCID interface now, it's + * hard-coded, here. + */ + struct ep_in *epi = &endpoint_in; + if (epi->buf == NULL){ + if (epi->tx_done) + notify_tx (epi); + else { + epi->tx_done = 1; + usb_tx_enable (endp1_tx_buf, 0); + } + } + else { + int tx_size = 0; + size_t remain = USB_LL_BUF_SIZE; + int offset = 0; + + while (epi->buf) { + if (epi->buf_len < remain) { + memcpy (endp1_tx_buf+offset, epi->buf, epi->buf_len); + offset += epi->buf_len; + remain -= epi->buf_len; + tx_size += epi->buf_len; + epi->next_buf (epi, remain); /* Update epi->buf, cnt, buf_len */ + } + else { + memcpy (endp1_tx_buf+offset, epi->buf, remain); + epi->buf += remain; + epi->cnt += remain; + epi->buf_len -= remain; + tx_size += remain; + break; + } + } + if (tx_size < USB_LL_BUF_SIZE) + epi->tx_done = 1; + + usb_tx_enable (endp1_tx_buf, tx_size); + } +} + +static int usb_event_handle(struct ccid *c) { + TU_LOG3("!!! tx %d, vendor %d, cfg %d, rx %d\r\n",c->tx_busy,tud_vendor_n_write_available(0),CFG_TUD_VENDOR_TX_BUFSIZE,tud_vendor_available()); + if (c->tx_busy == 1 && tud_vendor_n_write_available(0) == CFG_TUD_VENDOR_TX_BUFSIZE) { + ccid_tx_done (); + } + if (tud_vendor_available() && c->epo->ready) { + uint32_t count = tud_vendor_read(endp1_rx_buf, sizeof(endp1_rx_buf)); + if (endp1_rx_buf[0] != 0x65) + DEBUG_PAYLOAD(endp1_rx_buf, count); + //DEBUG_PAYLOAD(endp1_rx_buf, count); + ccid_rx_ready(count); + } + return 0; +} + +uint32_t timeout = USB_CCID_TIMEOUT; +static uint32_t prev_millis = 0; + +void prepare_ccid() { + struct ep_in *epi = &endpoint_in; + struct ep_out *epo = &endpoint_out; + struct ccid *c = &ccid; + struct apdu *a = &apdu; + + epi_init (epi, 1, c); + epo_init (epo, 2, c); + + apdu_init(a); + ccid_init (c, epi, epo, a); +} + +int process_apdu() { + led_set_blink(BLINK_PROCESSING); + if (!current_app) { + if (INS(apdu) == 0xA4 && P1(apdu) == 0x04 && (P2(apdu) == 0x00 || P2(apdu) == 0x4)) { //select by AID + for (int a = 0; a < num_apps; a++) { + if ((current_app = apps[a].select_aid(&apps[a]))) { + return set_res_sw(0x90,0x00); + } + } + } + return set_res_sw(0x6a, 0x82); + } + if (current_app->process_apdu) + return current_app->process_apdu(); + return set_res_sw (0x6D, 0x00); +} + +uint16_t set_res_sw(uint8_t sw1, uint8_t sw2) { + apdu.sw = (sw1 << 8) | sw2; + return make_uint16_t(sw1, sw2); +} + +static void card_init(void) { + //gpg_data_scan (flash_do_start, flash_do_end); + low_flash_init_core1(); +} + +void card_thread() { + ccid_comm = (queue_t *)multicore_fifo_pop_blocking(); + card_comm = (queue_t *)multicore_fifo_pop_blocking(); + + card_init (); + + while (1) { + uint32_t m; + queue_remove_blocking(card_comm, &m); + + if (m == EV_VERIFY_CMD_AVAILABLE || m == EV_MODIFY_CMD_AVAILABLE) + { + set_res_sw (0x6f, 0x00); + goto done; + } + else if (m == EV_EXIT) { + if (current_app && current_app->unload) + current_app->unload(); + break; + } + + process_apdu(); + + done:; + uint32_t flag = EV_EXEC_FINISHED; + queue_add_blocking(ccid_comm, &flag); + } + + if (current_app && current_app->unload) + current_app->unload(); +} + + +void ccid_task(void) { + struct ccid *c = &ccid; + if (tud_vendor_mounted()) { + // connected and there are data available + if ((c->epo->ready && tud_vendor_available()) || (tud_vendor_n_write_available(0) == CFG_TUD_VENDOR_TX_BUFSIZE && c->tx_busy == 1)) { + if (usb_event_handle (c) != 0) { + if (c->application) { + uint32_t flag = EV_EXIT; + queue_try_add(&c->ccid_comm, &flag); + c->application = 0; + } + prepare_ccid(); + return; + } + } + if (timeout == 0) { + timeout = USB_CCID_TIMEOUT; + c->timeout_cnt++; + } + uint32_t m = 0x0; + bool has_m = queue_try_remove(&c->ccid_comm, &m); + if (m != 0) + TU_LOG3("\r\n ------ M = %d\r\n",m); + if (has_m) { + if (m == EV_CARD_CHANGE) { + if (c->ccid_state == CCID_STATE_NOCARD) + /* Inserted! */ + c->ccid_state = CCID_STATE_START; + else { /* Removed! */ + if (c->application) { + uint32_t flag = EV_EXIT; + queue_try_add(&c->card_comm, &flag); + c->application = 0; + } + c->ccid_state = CCID_STATE_NOCARD; + } + //ccid_notify_slot_change (c); + } + else if (m == EV_RX_DATA_READY) { + c->ccid_state = ccid_handle_data(c); + timeout = 0; + c->timeout_cnt = 0; + } + else if (m == EV_EXEC_FINISHED) { + if (c->ccid_state == CCID_STATE_EXECUTE) { + exec_done: + if (c->a->sw == GPG_THREAD_TERMINATED) { + c->sw1sw2[0] = 0x90; + c->sw1sw2[1] = 0x00; + c->state = APDU_STATE_RESULT; + ccid_send_data_block(c); + c->ccid_state = CCID_STATE_EXITED; + c->application = 0; + return; + } + + c->a->cmd_apdu_data_len = 0; + c->sw1sw2[0] = c->a->sw >> 8; + c->sw1sw2[1] = c->a->sw & 0xff; + if (c->a->res_apdu_data_len <= c->a->expected_res_size) { + c->state = APDU_STATE_RESULT; + ccid_send_data_block(c); + c->ccid_state = CCID_STATE_WAIT; + } + else { + c->state = APDU_STATE_RESULT_GET_RESPONSE; + c->p = c->a->res_apdu_data; + c->len = c->a->res_apdu_data_len; + ccid_send_data_block_gr(c, c->a->expected_res_size); + c->ccid_state = CCID_STATE_WAIT; + } + } + else { + DEBUG_INFO ("ERR05\r\n"); + } + led_set_blink(BLINK_MOUNTED); + } + else if (m == EV_TX_FINISHED){ + if (c->state == APDU_STATE_RESULT) + ccid_reset(c); + else + c->tx_busy = 0; + if (c->state == APDU_STATE_WAIT_COMMAND || c->state == APDU_STATE_COMMAND_CHAINING || c->state == APDU_STATE_RESULT_GET_RESPONSE) + ccid_prepare_receive(c); + } + else if (m == EV_PRESS_BUTTON) { + wait_button(); + uint32_t flag = EV_BUTTON_PRESSED; + queue_try_add(&c->card_comm, &flag); + } + } + else { + timeout -= MIN(board_millis()-prev_millis,timeout); + if (timeout == 0) { + if (c->timeout_cnt == 7 && c->ccid_state == CCID_STATE_ACK_REQUIRED_1) { + c->a->sw = GPG_ACK_TIMEOUT; + c->a->res_apdu_data_len = 0; + c->a->sw = GPG_ACK_TIMEOUT; + c->a->res_apdu_data_len = 0; + + goto exec_done; + } + else + c->ccid_state = ccid_handle_timeout(c); + } + } + } +} + +void tud_mount_cb() { + ccid_prepare_receive (&ccid); +} + +void led_blinking_task() { +#ifdef PICO_DEFAULT_LED_PIN + static uint32_t start_ms = 0; + static uint8_t led_state = false; + static uint8_t led_color = PICO_DEFAULT_LED_PIN; +#ifdef PICO_DEFAULT_LED_PIN_INVERTED + uint32_t interval = !led_state ? blink_interval_ms & 0xffff : blink_interval_ms >> 16; +#else + uint32_t interval = led_state ? blink_interval_ms & 0xffff : blink_interval_ms >> 16; +#endif + + + // Blink every interval ms + if (board_millis() - start_ms < interval) + return; // not enough time + start_ms += interval; + + gpio_put(led_color, led_state); + led_state ^= 1; // toggle +#endif +} + +void led_off_all() { +#ifdef PIMORONI_TINY2040 + gpio_put(TINY2040_LED_R_PIN, 1); + gpio_put(TINY2040_LED_G_PIN, 1); + gpio_put(TINY2040_LED_B_PIN, 1); +#else +#ifdef PICO_DEFAULT_LED_PIN + gpio_put(PICO_DEFAULT_LED_PIN, 0); +#endif +#endif +} + +void init_rtc() { + + rtc_init(); + datetime_t dt = { + .year = 2020, + .month = 1, + .day = 1, + .dotw = 3, // 0 is Sunday, so 5 is Friday + .hour = 00, + .min = 00, + .sec = 00 + }; + rtc_set_datetime(&dt); +} + +extern void neug_task(); + +pico_unique_board_id_t unique_id; + +void execute_tasks() { + prev_millis = board_millis(); + ccid_task(); + tud_task(); // tinyusb device task + led_blinking_task(); +} + +int main(void) { + struct apdu *a = &apdu; + struct ccid *c = &ccid; + + board_init(); + +#ifdef PIMORONI_TINY2040 + gpio_init(TINY2040_LED_R_PIN); + gpio_set_dir(TINY2040_LED_R_PIN, GPIO_OUT); + gpio_init(TINY2040_LED_G_PIN); + gpio_set_dir(TINY2040_LED_G_PIN, GPIO_OUT); + gpio_init(TINY2040_LED_B_PIN); + gpio_set_dir(TINY2040_LED_B_PIN, GPIO_OUT); +#else +#ifdef PICO_DEFAULT_LED_PIN + gpio_init(PICO_DEFAULT_LED_PIN); + gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); +#endif +#endif + + + led_off_all(); + + tusb_init(); + + prepare_ccid(); + + random_init(); + + low_flash_init(); + + init_rtc(); + + while (1) { + execute_tasks(); + neug_task(); + do_flash(); + } + + return 0; +} \ No newline at end of file diff --git a/src/ccid/ccid2040.h b/src/ccid/ccid2040.h new file mode 100644 index 0000000..194454b --- /dev/null +++ b/src/ccid/ccid2040.h @@ -0,0 +1,250 @@ +/* + * This file is part of the Pico CCID distribution (https://github.com/polhenarejos/pico-ccid). + * Copyright (c) 2022 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _CCID2040_H_ +#define _CCID2040_H_ + +#include "ccid.h" +#include "tusb.h" +#include "file.h" +#include "pico/unique_id.h" +#include "pico/util/queue.h" + +#define USB_REQ_CCID 0xA1 + +typedef struct app { + const uint8_t *aid; + int (*process_apdu)(); + struct app* (*select_aid)(); + int (*unload)(); +} app_t; + +extern int register_app(app_t * (*)()); + +extern const uint8_t historical_bytes[]; + +#define DEBUG_PAYLOAD(p,s) { \ + printf("Payload %s (%d bytes):\r\n", #p,s);\ + for (int i = 0; i < s; i += 16) {\ + printf("%07Xh : ",i+p);\ + for (int j = 0; j < 16; j++) {\ + if (j < s-i) printf("%02X ",(p)[i+j]);\ + else printf(" ");\ + if (j == 7) printf(" ");\ + } printf(": "); \ + for (int j = 0; j < MIN(16,s-i); j++) {\ + printf("%c",(p)[i+j] == 0x0a || (p)[i+j] == 0x0d ? '\\' : (p)[i+j]);\ + if (j == 7) printf(" ");\ + }\ + printf("\r\n");\ + } printf("\r\n"); \ + } + +struct apdu { + uint8_t seq; + + /* command APDU */ + uint8_t *cmd_apdu_head; /* CLS INS P1 P2 [ internal Lc ] */ + uint8_t *cmd_apdu_data; + size_t cmd_apdu_data_len; /* Nc, calculated by Lc field */ + size_t expected_res_size; /* Ne, calculated by Le field */ + + /* response APDU */ + uint16_t sw; + uint16_t res_apdu_data_len; + uint8_t *res_apdu_data; +}; + +#define MAX_CMD_APDU_DATA_SIZE (24+4+512*4) +#define MAX_RES_APDU_DATA_SIZE (5+9+512*4) +#define CCID_MSG_HEADER_SIZE 10 +#define USB_LL_BUF_SIZE 64 + +/* CCID thread */ +#define EV_CARD_CHANGE 1 +#define EV_TX_FINISHED 2 /* CCID Tx finished */ +#define EV_EXEC_ACK_REQUIRED 4 /* OpenPGPcard Execution ACK required */ +#define EV_EXEC_FINISHED 8 /* OpenPGPcard Execution finished */ +#define EV_RX_DATA_READY 16 /* USB Rx data available */ +#define EV_PRESS_BUTTON 32 + +/* SC HSM thread */ +#define EV_MODIFY_CMD_AVAILABLE 1 +#define EV_VERIFY_CMD_AVAILABLE 2 +#define EV_CMD_AVAILABLE 4 +#define EV_EXIT 8 +#define EV_BUTTON_PRESSED 16 + +//Variables set by core1 +extern queue_t *ccid_comm; +extern queue_t *card_comm; + +enum ccid_state { + CCID_STATE_NOCARD, /* No card available */ + CCID_STATE_START, /* Initial */ + CCID_STATE_WAIT, /* Waiting APDU */ + + CCID_STATE_EXECUTE, /* Executing command */ + CCID_STATE_ACK_REQUIRED_0, /* Ack required (executing)*/ + CCID_STATE_ACK_REQUIRED_1, /* Waiting user's ACK (execution finished) */ + + CCID_STATE_EXITED, /* CCID Thread Terminated */ + CCID_STATE_EXEC_REQUESTED, /* Exec requested */ +}; + +#define CLA(a) a.cmd_apdu_head[0] +#define INS(a) a.cmd_apdu_head[1] +#define P1(a) a.cmd_apdu_head[2] +#define P2(a) a.cmd_apdu_head[3] + +#define res_APDU apdu.res_apdu_data +#define res_APDU_size apdu.res_apdu_data_len + +extern struct apdu apdu; + +uint16_t set_res_sw (uint8_t sw1, uint8_t sw2); + + +static inline const uint16_t make_uint16_t(uint8_t b1, uint8_t b2) { + return (b1 << 8) | b2; +} +static inline const uint16_t get_uint16_t(const uint8_t *b, uint16_t offset) { + return make_uint16_t(b[offset], b[offset+1]); +} +static inline const void put_uint16_t(uint16_t n, uint8_t *b) { + *b++ = (n >> 8) & 0xff; + *b = n & 0xff; +} + + +#ifdef DEBUG +void stdout_init (void); +#define DEBUG_MORE 1 +/* + * Debug functions in debug.c + */ +void put_byte (uint8_t b); +void put_byte_with_no_nl (uint8_t b); +void put_short (uint16_t x); +void put_word (uint32_t x); +void put_int (uint32_t x); +void put_string (const char *s); +void put_binary (const char *s, int len); + +#define DEBUG_INFO(msg) put_string (msg) +#define DEBUG_WORD(w) put_word (w) +#define DEBUG_SHORT(h) put_short (h) +#define DEBUG_BYTE(b) put_byte (b) +#define DEBUG_BINARY(s,len) put_binary ((const char *)s,len) +#else +#define DEBUG_INFO(msg) +#define DEBUG_WORD(w) +#define DEBUG_SHORT(h) +#define DEBUG_BYTE(b) +#define DEBUG_BINARY(s,len) +#endif + +extern int flash_write_data_to_file(file_t *file, const uint8_t *data, uint16_t len); +extern void low_flash_available(); +extern int flash_clear_file(file_t *file); + +extern pico_unique_board_id_t unique_id; + +enum { + BLINK_NOT_MOUNTED = (250 << 16) | 250, + BLINK_MOUNTED = (250 << 16) | 250, + BLINK_SUSPENDED = (500 << 16) | 1000, + BLINK_PROCESSING = (50 << 16) | 50, + + BLINK_ALWAYS_ON = UINT32_MAX, + BLINK_ALWAYS_OFF = 0 +}; +extern void led_set_blink(uint32_t mode); + + +#define SW_BYTES_REMAINING_00() set_res_sw (0x61, 0x00) +#define SW_WARNING_STATE_UNCHANGED() set_res_sw (0x62, 0x00) +#define SW_WARNING_CORRUPTED() set_res_sw (0x62, 0x81) +#define SW_WARNING_EOF() set_res_sw (0x62, 0x82) +#define SW_WARNING_EF_DEACTIVATED() set_res_sw (0x62, 0x83) +#define SW_WARNING_WRONG_FCI() set_res_sw (0x62, 0x84) +#define SW_WARNING_EF_TERMINATED() set_res_sw (0x62, 0x85) + +#define SW_WARNING_NOINFO() set_res_sw (0x63, 0x00) +#define SW_WARNING_FILLUP() set_res_sw (0x63, 0x81) + +#define SW_EXEC_ERROR() set_res_sw (0x64, 0x00) + +#define SW_MEMORY_FAILURE() set_res_sw (0x65, 0x81) + +#define SW_SECURE_MESSAGE_EXEC_ERROR() set_res_sw (0x66, 0x00) + +#define SW_WRONG_LENGTH() set_res_sw (0x67, 0x00) +#define SW_WRONG_DATA() set_res_sw (0x67, 0x00) + +#define SW_LOGICAL_CHANNEL_NOT_SUPPORTED() set_res_sw (0x68, 0x81) +#define SW_SECURE_MESSAGING_NOT_SUPPORTED() set_res_sw (0x68, 0x82) + +#define SW_COMMAND_INCOMPATIBLE() set_res_sw (0x69, 0x81) +#define SW_SECURITY_STATUS_NOT_SATISFIED() set_res_sw (0x69, 0x82) +#define SW_PIN_BLOCKED() set_res_sw (0x69, 0x83) +#define SW_DATA_INVALID() set_res_sw (0x69, 0x84) +#define SW_CONDITIONS_NOT_SATISFIED() set_res_sw (0x69, 0x85) +#define SW_COMMAND_NOT_ALLOWED() set_res_sw (0x69, 0x86) +#define SW_SECURE_MESSAGING_MISSING_DO() set_res_sw (0x69, 0x87) +#define SW_SECURE_MESSAGING_INCORRECT_DO() set_res_sw (0x69, 0x88) +#define SW_APPLET_SELECT_FAILED() set_res_sw (0x69, 0x99) + +#define SW_INCORRECT_PARAMS() set_res_sw (0x6A, 0x80) +#define SW_FUNC_NOT_SUPPORTED() set_res_sw (0x6A, 0x81) +#define SW_FILE_NOT_FOUND() set_res_sw (0x6A, 0x82) +#define SW_RECORD_NOT_FOUND() set_res_sw (0x6A, 0x83) +#define SW_FILE_FULL() set_res_sw (0x6A, 0x84) +#define SW_WRONG_NE() set_res_sw (0x6A, 0x85) +#define SW_INCORRECT_P1P2() set_res_sw (0x6A, 0x86) +#define SW_WRONG_NC() set_res_sw (0x6A, 0x87) +#define SW_REFERENCE_NOT_FOUND() set_res_sw (0x6A, 0x88) +#define SW_FILE_EXISTS() set_res_sw (0x6A, 0x89) + +#define SW_WRONG_P1P2() set_res_sw (0x6B, 0x00) + +#define SW_CORRECT_LENGTH_00() set_res_sw (0x6C, 0x00) + +#define SW_INS_NOT_SUPPORTED() set_res_sw (0x6D, 0x00) + +#define SW_CLA_NOT_SUPPORTED() set_res_sw (0x6E, 0x00) + +#define SW_UNKNOWN() set_res_sw (0x6F, 0x00) + +#define SW_OK() set_res_sw (0x90, 0x00) + +#define CCID_OK 0 +#define CCID_ERR_NO_MEMORY -1000 +#define CCID_ERR_MEMORY_FATAL -1001 +#define CCID_ERR_NULL_PARAM -1002 +#define CCID_ERR_FILE_NOT_FOUND -1003 +#define CCID_ERR_BLOCKED -1004 +#define CCID_NO_LOGIN -1005 +#define CCID_EXEC_ERROR -1006 +#define CCID_WRONG_LENGTH -1007 +#define CCID_WRONG_DATA -1008 +#define CCID_WRONG_DKEK -1009 +#define CCID_WRONG_SIGNATURE -1010 +#define CCID_WRONG_PADDING -1011 +#define CCID_VERIFICATION_FAILED -1012 + +#endif \ No newline at end of file diff --git a/src/ccid/version.h b/src/ccid/version.h new file mode 100644 index 0000000..c031e22 --- /dev/null +++ b/src/ccid/version.h @@ -0,0 +1,27 @@ +/* + * This file is part of the Pico CCID distribution (https://github.com/polhenarejos/pico-ccid). + * Copyright (c) 2022 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __VERSION_H_ +#define __VERSION_H_ + +#define HSM_VERSION 0x010C + +#define HSM_VERSION_MAJOR ((HSM_VERSION >> 8) & 0xff) +#define HSM_VERSION_MINOR (HSM_VERSION & 0xff) + +#endif + diff --git a/src/fs/file.c b/src/fs/file.c new file mode 100644 index 0000000..49922f1 --- /dev/null +++ b/src/fs/file.c @@ -0,0 +1,294 @@ +/* + * This file is part of the Pico CCID distribution (https://github.com/polhenarejos/pico-ccid). + * Copyright (c) 2022 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "file.h" +#include "tusb.h" +#include "ccid2040.h" +#include + +extern const uintptr_t end_data_pool; +extern const uintptr_t start_data_pool; +extern int flash_write_data_to_file(file_t *file, const uint8_t *data, uint16_t len); +extern int flash_program_halfword (uintptr_t addr, uint16_t data); +extern int flash_program_word (uintptr_t addr, uint32_t data); +extern int flash_program_uintptr (uintptr_t addr, uintptr_t data); +extern int flash_program_block(uintptr_t addr, const uint8_t *data, size_t len); +extern uintptr_t flash_read_uintptr(uintptr_t addr); +extern uint16_t flash_read_uint16(uintptr_t addr); +extern uint8_t flash_read_uint8(uintptr_t addr); +extern uint8_t *flash_read(uintptr_t addr); +extern void low_flash_available(); + +//puts FCI in the RAPDU +void process_fci(const file_t *pe) { + uint8_t *p = res_APDU; + uint8_t buf[64]; + res_APDU_size = 0; + res_APDU[res_APDU_size++] = 0x6f; + res_APDU[res_APDU_size++] = 0x00; //computed later + + res_APDU[res_APDU_size++] = 0x81; + res_APDU[res_APDU_size++] = 2; + if (pe->data) { + if ((pe->type & FILE_DATA_FUNC) == FILE_DATA_FUNC) { + uint16_t len = ((int (*)(const file_t *, int))(pe->data))(pe, 0); + res_APDU[res_APDU_size++] = (len >> 8) & 0xff; + res_APDU[res_APDU_size++] = len & 0xff; + } + else { + res_APDU[res_APDU_size++] = pe->data[1]; + res_APDU[res_APDU_size++] = pe->data[0]; + } + } + else { + memset(res_APDU+res_APDU_size, 0, 2); + res_APDU_size += 2; + } + + res_APDU[res_APDU_size++] = 0x82; + res_APDU[res_APDU_size++] = 1; + res_APDU[res_APDU_size] = 0; + if (pe->type == FILE_TYPE_INTERNAL_EF) + res_APDU[res_APDU_size++] |= 0x08; + else if (pe->type == FILE_TYPE_WORKING_EF) + res_APDU[res_APDU_size++] |= pe->ef_structure & 0x7; + else if (pe->type == FILE_TYPE_DF) + res_APDU[res_APDU_size++] |= 0x38; + + res_APDU[res_APDU_size++] = 0x83; + res_APDU[res_APDU_size++] = 2; + put_uint16_t(pe->fid, res_APDU+res_APDU_size); + res_APDU_size += 2; + res_APDU[1] = res_APDU_size-2; +} + +#define MAX_DYNAMIC_FILES 64 +uint16_t dynamic_files = 0; +file_t dynamic_file[MAX_DYNAMIC_FILES]; + +bool card_terminated = false; + +bool is_parent(const file_t *child, const file_t *parent) { + if (child == parent) + return true; + if (child == MF) + return false; + return is_parent(&file_entries[child->parent], parent); +} + +file_t *get_parent(file_t *f) { + return &file_entries[f->parent]; +} + +file_t *search_by_name(uint8_t *name, uint16_t namelen) { + for (file_t *p = file_entries; p != file_last; p++) { + if (p->name && *p->name == apdu.cmd_apdu_data_len && memcmp(p->name+1, name, namelen) == 0) { + return p; + } + } + return NULL; +} + +file_t *search_by_fid(const uint16_t fid, const file_t *parent, const uint8_t sp) { + + for (file_t *p = file_entries; p != file_last; p++) { + if (p->fid != 0x0000 && p->fid == fid) { + if (!parent || (parent && is_parent(p, parent))) { + if (!sp || sp == SPECIFY_ANY || (((sp & SPECIFY_EF) && (p->type & FILE_TYPE_INTERNAL_EF)) || ((sp & SPECIFY_DF) && p->type == FILE_TYPE_DF))) + return p; + } + } + } + return NULL; +} + +uint8_t make_path_buf(const file_t *pe, uint8_t *buf, uint8_t buflen, const file_t *top) { + if (!buflen) + return 0; + if (pe == top) //MF or relative DF + return 0; + put_uint16_t(pe->fid, buf); + return make_path_buf(&file_entries[pe->parent], buf+2, buflen-2, top)+2; +} + +uint8_t make_path(const file_t *pe, const file_t *top, uint8_t *path) { + uint8_t buf[MAX_DEPTH*2], *p = path; + put_uint16_t(pe->fid, buf); + uint8_t depth = make_path_buf(&file_entries[pe->parent], buf+2, sizeof(buf)-2, top)+2; + for (int d = depth-2; d >= 0; d -= 2) { + memcpy(p, buf+d, 2); + p += 2; + } + return depth; +} + +file_t *search_by_path(const uint8_t *pe_path, uint8_t pathlen, const file_t *parent) { + uint8_t path[MAX_DEPTH*2]; + if (pathlen > sizeof(path)) { + return NULL; + } + for (file_t *p = file_entries; p != file_last; p++) { + uint8_t depth = make_path(p, parent, path); + if (pathlen == depth && memcmp(path, pe_path, depth) == 0) + return p; + } + return NULL; +} + +file_t *currentEF = NULL; +file_t *currentDF = NULL; +const file_t *selected_applet = NULL; +bool isUserAuthenticated = false; + +bool authenticate_action(const file_t *ef, uint8_t op) { + uint8_t acl = ef->acl[op]; + if (acl == 0x0) + return true; + else if (acl == 0xff) + return false; + else if (acl == 0x90 || acl & 0x9F == 0x10) { + // PIN required. + if(isUserAuthenticated) { + return true; + } + else { + return false; + } + } + return false; +} + +void initialize_chain(file_chain_t **chain) { + file_chain_t *next; + for (file_chain_t *f = *chain; f; f = next) { + next = f->next; + free(f); + } + *chain = NULL; +} + +void initialize_flash(bool hard) { + if (hard) { + const uint8_t empty[8] = { 0 }; + flash_program_block(end_data_pool, empty, sizeof(empty)); + low_flash_available(); + } + for (file_t *f = file_entries; f != file_last; f++) { + if ((f->type & FILE_DATA_FLASH) == FILE_DATA_FLASH) + f->data = NULL; + } + dynamic_files = 0; +} + +void scan_flash() { + initialize_flash(false); //soft initialization + if (*(uintptr_t *)end_data_pool == 0xffffffff && *(uintptr_t *)(end_data_pool+sizeof(uintptr_t)) == 0xffffffff) + { + printf("First initialization (or corrupted!)\r\n"); + const uint8_t empty[8] = { 0 }; + flash_program_block(end_data_pool, empty, sizeof(empty)); + //low_flash_available(); + //wait_flash_finish(); + } + printf("SCAN\r\n"); + + uintptr_t base = flash_read_uintptr(end_data_pool); + for (uintptr_t base = flash_read_uintptr(end_data_pool); base >= start_data_pool; base = flash_read_uintptr(base)) { + if (base == 0x0) //all is empty + break; + + uint16_t fid = flash_read_uint16(base+sizeof(uintptr_t)+sizeof(uintptr_t)); + printf("[%x] scan fid %x, len %d\r\n",base,fid,flash_read_uint16(base+sizeof(uintptr_t)+sizeof(uintptr_t)+sizeof(uint16_t))); + file_t *file = (file_t *)search_by_fid(fid, NULL, SPECIFY_EF); + if (file) + file->data = (uint8_t *)(base+sizeof(uintptr_t)+sizeof(uintptr_t)+sizeof(uint16_t)); + if (flash_read_uintptr(base) == 0x0) { + break; + } + } +} + +uint8_t *file_read(const uint8_t *addr) { + return flash_read((uintptr_t)addr); +} +uint16_t file_read_uint16(const uint8_t *addr) { + return flash_read_uint16((uintptr_t)addr); +} +uint8_t file_read_uint8(const uint8_t *addr) { + return flash_read_uint8((uintptr_t)addr); +} + +file_t *search_dynamic_file(uint16_t fid) { + for (int i = 0; i < dynamic_files; i++) { + if (dynamic_file[i].fid == fid) + return &dynamic_file[i]; + } + return NULL; +} + +int delete_dynamic_file(file_t *f) { + for (int i = 0; i < dynamic_files; i++) { + if (dynamic_file[i].fid == f->fid) { + for (int j = i+1; j < dynamic_files; j++) + memcpy(&dynamic_file[j-1], &dynamic_file[j], sizeof(file_t)); + dynamic_files--; + return CCID_OK; + } + } + return CCID_ERR_FILE_NOT_FOUND; +} + +file_t *file_new(uint16_t fid) { + file_t *f; + if ((f = search_dynamic_file(fid))) + return f; + if (dynamic_files == MAX_DYNAMIC_FILES) + return NULL; + f = &dynamic_file[dynamic_files]; + dynamic_files++; + file_t file = { + .fid = fid, + .parent = 5, + .name = NULL, + .type = FILE_TYPE_WORKING_EF, + .ef_structure = FILE_EF_TRANSPARENT, + .data = NULL, + .acl = {0} + }; + memcpy(f, &file, sizeof(file_t)); + //memset((uint8_t *)f->acl, 0x90, sizeof(f->acl)); + return f; +} + +file_chain_t *add_file_to_chain(file_t *file, file_chain_t **chain) { + if (search_file_chain(file->fid, *chain)) + return NULL; + file_chain_t *fc = (file_chain_t *)malloc(sizeof(file_chain_t)); + fc->file = file; + fc->next = *chain; + *chain = fc; + return fc; +} + +file_t *search_file_chain(uint16_t fid, file_chain_t *chain) { + for (file_chain_t *fc = chain; fc; fc = fc->next) { + if (fid == fc->file->fid) { + return fc->file; + } + } + return NULL; +} \ No newline at end of file diff --git a/src/fs/file.h b/src/fs/file.h new file mode 100644 index 0000000..a64f6f6 --- /dev/null +++ b/src/fs/file.h @@ -0,0 +1,124 @@ +/* + * This file is part of the Pico CCID distribution (https://github.com/polhenarejos/pico-ccid). + * Copyright (c) 2022 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#ifndef _FILE_H_ +#define _FILE_H_ + +#include +#include "pico/stdlib.h" + +#define FILE_TYPE_UNKNOWN 0x00 +#define FILE_TYPE_DF 0x04 +#define FILE_TYPE_INTERNAL_EF 0x03 +#define FILE_TYPE_WORKING_EF 0x01 +#define FILE_TYPE_BSO 0x10 +#define FILE_PERSISTENT 0x20 +#define FILE_DATA_FLASH 0x40 +#define FILE_DATA_FUNC 0x80 + +/* EF structures */ +#define FILE_EF_UNKNOWN 0x00 +#define FILE_EF_TRANSPARENT 0x01 +#define FILE_EF_LINEAR_FIXED 0x02 +#define FILE_EF_LINEAR_FIXED_TLV 0x03 +#define FILE_EF_LINEAR_VARIABLE 0x04 +#define FILE_EF_LINEAR_VARIABLE_TLV 0x05 +#define FILE_EF_CYCLIC 0x06 +#define FILE_EF_CYCLIC_TLV 0x07 + +#define ACL_OP_DELETE_SELF 0x00 +#define ACL_OP_CREATE_DF 0x01 +#define ACL_OP_CREATE_EF 0x02 +#define ACL_OP_DELETE_CHILD 0x03 +#define ACL_OP_WRITE 0x04 +#define ACL_OP_UPDATE_ERASE 0x05 +#define ACL_OP_READ_SEARCH 0x06 + +#define SPECIFY_EF 0x1 +#define SPECIFY_DF 0x2 +#define SPECIFY_ANY 0x3 + +#define EF_DKEK 0x108F +#define EF_PRKDFS 0x6040 +#define EF_PUKDFS 0x6041 +#define EF_CDFS 0x6042 +#define EF_AODFS 0x6043 +#define EF_DODFS 0x6044 +#define EF_SKDFS 0x6045 +#define EF_DEVOPS 0x100E + +#define MAX_DEPTH 4 + +typedef struct file +{ + const uint16_t fid; + const uint8_t parent; //entry number in the whole table!! + const uint8_t *name; + const uint8_t type; + const uint8_t ef_structure; + uint8_t *data; //should include 2 bytes len at begining + const uint8_t acl[7]; +} __attribute__((packed)) file_t; + +typedef struct file_chain +{ + file_t *file; + struct file_chain *next; +} file_chain_t; + +extern file_t *currentEF; +extern file_t *currentDF; +extern const file_t *selected_applet; + +extern const file_t *MF; +extern const file_t *file_last; +extern const file_t *file_openpgp; +extern const file_t *file_sc_hsm; +extern bool card_terminated; +extern file_t *file_pin1; +extern file_t *file_retries_pin1; +extern file_t *file_sopin; +extern file_t *file_retries_sopin; + +extern file_t *search_by_fid(const uint16_t fid, const file_t *parent, const uint8_t sp); +extern file_t *search_by_name(uint8_t *name, uint16_t namelen); +extern file_t *search_by_path(const uint8_t *pe_path, uint8_t pathlen, const file_t *parent); +extern bool authenticate_action(const file_t *ef, uint8_t op); +extern void process_fci(const file_t *pe); +extern void scan_flash(); +extern void initialize_flash(bool); + +extern file_t file_entries[]; + +extern uint8_t *file_read(const uint8_t *addr); +extern uint16_t file_read_uint16(const uint8_t *addr); +extern uint8_t file_read_uint8(const uint8_t *addr); +extern file_t *file_new(uint16_t); +file_t *get_parent(file_t *f); + +extern uint16_t dynamic_files; +extern file_t dynamic_file[]; +extern file_t *search_dynamic_file(uint16_t); +extern int delete_dynamic_file(file_t *f); + +extern file_chain_t *add_file_to_chain(file_t *file, file_chain_t **chain); +extern file_t *search_file_chain(uint16_t fid, file_chain_t *chain); +extern bool isUserAuthenticated; + +#endif + diff --git a/src/fs/flash.c b/src/fs/flash.c new file mode 100644 index 0000000..e69fa99 --- /dev/null +++ b/src/fs/flash.c @@ -0,0 +1,133 @@ +/* + * This file is part of the Pico CCID distribution (https://github.com/polhenarejos/pico-ccid). + * Copyright (c) 2022 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include +#include + +#include "pico/stdlib.h" +#include "hardware/flash.h" +#include "ccid2040.h" +#include "tusb.h" +#include "file.h" + +/* + * ------------------------------------------------------ + * | | + * | next_addr | prev_addr | fid | data (len + payload) | + * | | + * ------------------------------------------------------ + */ +#define FLASH_TARGET_OFFSET (PICO_FLASH_SIZE_BYTES >> 1) // DATA starts at the mid of flash +#define FLASH_DATA_HEADER_SIZE (sizeof(uintptr_t)+sizeof(uint32_t)) +#define FLASH_PERMANENT_REGION (4*FLASH_SECTOR_SIZE) // 4 sectors (16kb) of permanent memory + +//To avoid possible future allocations, data region starts at the end of flash and goes upwards to the center region + +const uintptr_t start_data_pool = (XIP_BASE + FLASH_TARGET_OFFSET); +const uintptr_t end_data_pool = (XIP_BASE + PICO_FLASH_SIZE_BYTES)-FLASH_DATA_HEADER_SIZE-FLASH_PERMANENT_REGION; //This is a fixed value. DO NOT CHANGE +#define FLASH_ADDR_DATA_STORAGE_START start_data_pool + +extern int flash_program_block(uintptr_t addr, const uint8_t *data, size_t len); +extern int flash_program_halfword (uintptr_t addr, uint16_t data); +extern int flash_program_uintptr(uintptr_t, uintptr_t); +extern uintptr_t flash_read_uintptr(uintptr_t addr); +extern uint16_t flash_read_uint16(uintptr_t addr); + +extern void low_flash_available(); + +uintptr_t allocate_free_addr(uint16_t size) { + if (size > FLASH_SECTOR_SIZE) + return 0x0; //ERROR + size_t real_size = size+sizeof(uint16_t)+sizeof(uintptr_t)+sizeof(uint16_t)+sizeof(uintptr_t); //len+len size+next address+fid+prev_addr size + uintptr_t next_base = 0x0; + for (uintptr_t base = end_data_pool; base >= start_data_pool; base = next_base) { + uintptr_t addr_alg = base & -FLASH_SECTOR_SIZE; //start address of sector + uintptr_t potential_addr = base-real_size; + next_base = flash_read_uintptr(base); + //printf("nb %x %x %x %x\r\n",base,next_base,addr_alg,potential_addr); + //printf("fid %x\r\n",flash_read_uint16(next_base+sizeof(uintptr_t))); + if (next_base == 0x0) { //we are at the end + //now we check if we fit in the current sector + if (addr_alg <= potential_addr) //it fits in the current sector + { + flash_program_uintptr(potential_addr, 0x0); + flash_program_uintptr(potential_addr+sizeof(uintptr_t), base); + flash_program_uintptr(base, potential_addr); + return potential_addr; + } + else if (addr_alg-FLASH_SECTOR_SIZE >= start_data_pool) { //check whether it fits in the next sector, so we take addr_aligned as the base + potential_addr = addr_alg-real_size; + flash_program_uintptr(potential_addr, 0x0); + flash_program_uintptr(potential_addr+sizeof(uintptr_t), base); + flash_program_uintptr(base, potential_addr); + return potential_addr; + } + return 0x0; + } + //we check if |base-(next_addr+size_next_addr)| > |base-potential_addr| only if fid != 1xxx (not size blocked) + else if (addr_alg <= potential_addr && base-(next_base+flash_read_uint16(next_base+sizeof(uintptr_t)+sizeof(uintptr_t)+sizeof(uint16_t))+2*sizeof(uint16_t)+2*sizeof(uintptr_t)) > base-potential_addr && flash_read_uint16(next_base+sizeof(uintptr_t)) & 0x1000 != 0x1000) { + flash_program_uintptr(potential_addr, next_base); + flash_program_uintptr(potential_addr+sizeof(uintptr_t), base); + flash_program_uintptr(base, potential_addr); + return potential_addr; + } + } + return 0x0; //probably never reached +} + +int flash_clear_file(file_t *file) { + uintptr_t base_addr = (uintptr_t)(file->data-sizeof(uintptr_t)-sizeof(uint16_t)-sizeof(uintptr_t)); + uintptr_t prev_addr = flash_read_uintptr(base_addr+sizeof(uintptr_t)); + uintptr_t next_addr = flash_read_uintptr(base_addr); + //printf("nc %x->%x %x->%x\r\n",prev_addr,flash_read_uintptr(prev_addr),base_addr,next_addr); + flash_program_uintptr(prev_addr, next_addr); + flash_program_halfword((uintptr_t)file->data, 0); + if (next_addr > 0) + flash_program_uintptr(next_addr+sizeof(uintptr_t), prev_addr); + //printf("na %x->%x\r\n",prev_addr,flash_read_uintptr(prev_addr)); + return CCID_OK; +} + +int flash_write_data_to_file(file_t *file, const uint8_t *data, uint16_t len) { + if (!file) + return CCID_ERR_NULL_PARAM; + if (len > FLASH_SECTOR_SIZE) + return CCID_ERR_NO_MEMORY; + if (file->data) { //already in flash + uint16_t size_file_flash = flash_read_uint16((uintptr_t)file->data); + if (len <= size_file_flash) { //it fits, no need to move it + flash_program_halfword((uintptr_t)file->data, len); + if (data) + flash_program_block((uintptr_t)file->data+sizeof(uint16_t), data, len); + return CCID_OK; + } + else { //we clear the old file + flash_clear_file(file); + } + } + uintptr_t new_addr = allocate_free_addr(len); + //printf("na %x\r\n",new_addr); + if (new_addr == 0x0) + return CCID_ERR_NO_MEMORY; + file->data = (uint8_t *)new_addr+sizeof(uintptr_t)+sizeof(uint16_t)+sizeof(uintptr_t); //next addr+fid+prev addr + flash_program_halfword(new_addr+sizeof(uintptr_t)+sizeof(uintptr_t), file->fid); + flash_program_halfword((uintptr_t)file->data, len); + if (data) + flash_program_block((uintptr_t)file->data+sizeof(uint16_t), data, len); + return CCID_OK; +} diff --git a/src/fs/low_flash.c b/src/fs/low_flash.c new file mode 100644 index 0000000..2e0d823 --- /dev/null +++ b/src/fs/low_flash.c @@ -0,0 +1,247 @@ +/* + * This file is part of the Pico CCID distribution (https://github.com/polhenarejos/pico-ccid). + * Copyright (c) 2022 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include +#include +#include + +#include "pico/stdlib.h" +#include "hardware/flash.h" +#include "hardware/sync.h" +#include "pico/mutex.h" +#include "pico/sem.h" +#include "pico/multicore.h" +#include "ccid2040.h" +#include + +#define TOTAL_FLASH_PAGES 4 + +typedef struct page_flash { + uint8_t page[FLASH_SECTOR_SIZE]; + uintptr_t address; + bool ready; + bool erase; + size_t page_size; //this param is for easy erase. It allows to erase with a single call. IT DOES NOT APPLY TO WRITE +} page_flash_t; + +static page_flash_t flash_pages[TOTAL_FLASH_PAGES]; + +static mutex_t mtx_flash; +static semaphore_t sem_wait; + +static uint8_t ready_pages = 0; + +bool flash_available = false; +static bool locked_out = false; + + +//this function has to be called from the core 0 +void do_flash() +{ + if (mutex_try_enter(&mtx_flash, NULL) == true) { + if (locked_out == true && flash_available == true && ready_pages > 0) { + //printf(" DO_FLASH AVAILABLE\r\n"); + for (int r = 0; r < TOTAL_FLASH_PAGES; r++) { + if (flash_pages[r].ready == true) { + //printf("WRITTING %X\r\n",flash_pages[r].address-XIP_BASE); + while (multicore_lockout_start_timeout_us(1000) == false); + //printf("WRITTING %X\r\n",flash_pages[r].address-XIP_BASE); + uint32_t ints = save_and_disable_interrupts(); + flash_range_erase(flash_pages[r].address-XIP_BASE, FLASH_SECTOR_SIZE); + flash_range_program(flash_pages[r].address-XIP_BASE, flash_pages[r].page, FLASH_SECTOR_SIZE); + restore_interrupts (ints); + while (multicore_lockout_end_timeout_us(1000) == false); + //printf("WRITEN %X !\r\n",flash_pages[r].address); + + flash_pages[r].ready = false; + ready_pages--; + } + else if (flash_pages[r].erase == true) { + while (multicore_lockout_start_timeout_us(1000) == false); + //printf("WRITTING\r\n"); + flash_range_erase(flash_pages[r].address-XIP_BASE, flash_pages[r].page_size ? ((int)(flash_pages[r].page_size/FLASH_SECTOR_SIZE))*FLASH_SECTOR_SIZE : FLASH_SECTOR_SIZE); + while (multicore_lockout_end_timeout_us(1000) == false); + flash_pages[r].erase = false; + ready_pages--; + } + } + flash_available = false; + if (ready_pages != 0) { + DEBUG_INFO("ERROR: DO FLASH DOES NOT HAVE ZERO PAGES"); + } + } + mutex_exit(&mtx_flash); + } + sem_release(&sem_wait); +} + +//this function has to be called from the core 0 +void low_flash_init() { + mutex_init(&mtx_flash); + sem_init(&sem_wait, 0, 1); + memset(flash_pages, 0, sizeof(page_flash_t)*TOTAL_FLASH_PAGES); +} + +void low_flash_init_core1() { + mutex_enter_blocking(&mtx_flash); + multicore_lockout_victim_init(); + locked_out = true; + mutex_exit(&mtx_flash); +} + +void wait_flash_finish() { + sem_acquire_blocking(&sem_wait); //blocks until released + //wake up + sem_acquire_blocking(&sem_wait); //decrease permits +} + +void low_flash_available() { + mutex_enter_blocking(&mtx_flash); + flash_available = true; + mutex_exit(&mtx_flash); +} + +page_flash_t *find_free_page(uintptr_t addr) { + uintptr_t addr_alg = addr & -FLASH_SECTOR_SIZE; + page_flash_t *p = NULL; + for (int r = 0; r < TOTAL_FLASH_PAGES; r++) + { + if ((!flash_pages[r].ready && !flash_pages[r].erase) || flash_pages[r].address == addr_alg) //first available + { + p = &flash_pages[r]; + if (!flash_pages[r].ready && !flash_pages[r].erase) + { + memcpy(p->page, (uint8_t *)addr_alg, FLASH_SECTOR_SIZE); + ready_pages++; + p->address = addr_alg; + p->ready = true; + } + return p; + } + } + return NULL; +} + +int flash_program_block(uintptr_t addr, const uint8_t *data, size_t len) { + uintptr_t addr_alg = addr & -FLASH_SECTOR_SIZE; + page_flash_t *p = NULL; + + if (!data || len == 0) + return CCID_ERR_NULL_PARAM; + + mutex_enter_blocking(&mtx_flash); + if (ready_pages == TOTAL_FLASH_PAGES) { + mutex_exit(&mtx_flash); + DEBUG_INFO("ERROR: ALL FLASH PAGES CACHED\r\n"); + return CCID_ERR_NO_MEMORY; + } + if (!(p = find_free_page(addr))) + { + mutex_exit(&mtx_flash); + DEBUG_INFO("ERROR: FLASH CANNOT FIND A PAGE (rare error)\r\n"); + return CCID_ERR_MEMORY_FATAL; + } + memcpy(&p->page[addr&(FLASH_SECTOR_SIZE-1)], data, len); + //printf("Flash: modified page %X with data %x at [%x] (top page %X)\r\n",addr_alg,data,addr&(FLASH_SECTOR_SIZE-1),addr); + mutex_exit(&mtx_flash); + return CCID_OK; +} + +int flash_program_halfword (uintptr_t addr, uint16_t data) { + return flash_program_block(addr, (const uint8_t *)&data, sizeof(uint16_t)); +} + +int flash_program_word (uintptr_t addr, uint32_t data) { + return flash_program_block(addr, (const uint8_t *)&data, sizeof(uint32_t)); +} + +int flash_program_uintptr (uintptr_t addr, uintptr_t data) { + return flash_program_block(addr, (const uint8_t *)&data, sizeof(uintptr_t)); +} + +uint8_t *flash_read(uintptr_t addr) { + uintptr_t addr_alg = addr & -FLASH_SECTOR_SIZE; + mutex_enter_blocking(&mtx_flash); + if (ready_pages > 0) { + for (int r = 0; r < TOTAL_FLASH_PAGES; r++) + { + if (flash_pages[r].ready && flash_pages[r].address == addr_alg) { + uint8_t *v = &flash_pages[r].page[addr&(FLASH_SECTOR_SIZE-1)]; + mutex_exit(&mtx_flash); + return v; + } + } + } + uint8_t *v = (uint8_t *)addr; + mutex_exit(&mtx_flash); + return v; +} + +uintptr_t flash_read_uintptr(uintptr_t addr) { + uint8_t *p = flash_read(addr); + uintptr_t v = 0x0; + for (int i = 0; i < sizeof(uintptr_t); i++) { + v |= (uintptr_t)p[i]<<(8*i); + } + return v; +} +uint16_t flash_read_uint16(uintptr_t addr) { + uint8_t *p = flash_read(addr); + uint16_t v = 0x0; + for (int i = 0; i < sizeof(uint16_t); i++) { + v |= p[i]<<(8*i); + } + return v; +} +uint8_t flash_read_uint8(uintptr_t addr) { + return *flash_read(addr); +} + +int flash_erase_page (uintptr_t addr, size_t page_size) { + uintptr_t addr_alg = addr & -FLASH_SECTOR_SIZE; + page_flash_t *p = NULL; + + mutex_enter_blocking(&mtx_flash); + if (ready_pages == TOTAL_FLASH_PAGES) { + mutex_exit(&mtx_flash); + DEBUG_INFO("ERROR: ALL FLASH PAGES CACHED\r\n"); + return CCID_ERR_NO_MEMORY; + } + if (!(p = find_free_page(addr))) { + DEBUG_INFO("ERROR: FLASH CANNOT FIND A PAGE (rare error)\r\n"); + mutex_exit(&mtx_flash); + return CCID_ERR_MEMORY_FATAL; + } + p->erase = true; + p->ready = false; + p->page_size = page_size; + mutex_exit(&mtx_flash); + + return CCID_OK; +} + +bool flash_check_blank(const uint8_t *p_start, size_t size) +{ + const uint8_t *p; + + for (p = p_start; p < p_start + size; p++) { + if (*p != 0xff) + return false; + } + return true; +} diff --git a/src/rng/neug.c b/src/rng/neug.c new file mode 100644 index 0000000..63c731a --- /dev/null +++ b/src/rng/neug.c @@ -0,0 +1,182 @@ +/* + * This file is part of the Pico CCID distribution (https://github.com/polhenarejos/pico-ccid). + * Copyright (c) 2022 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +//Part of the code is taken from GnuK (GPLv3) + + +#include +#include +#include +#include "pico/stdlib.h" + +#include "neug.h" +#include "hardware/structs/rosc.h" +#include "hardware/gpio.h" +#include "hardware/adc.h" +#include "bsp/board.h" +#include "pico/unique_id.h" + +void adc_start() { + adc_init(); + adc_gpio_init(27); + adc_select_input(1); +} + +void adc_stop() { +} + +static uint64_t random_word = 0xcbf29ce484222325; +static uint8_t ep_round = 0; + +static void ep_init() { + random_word = 0xcbf29ce484222325; + ep_round = 0; +} + +/* Here, we assume a little endian architecture. */ +static int ep_process () { + if (ep_round == 0) { + ep_init(); + } + uint64_t word = 0x0; + for (int n = 0; n < 64; n++) { + uint8_t bit1, bit2; + do + { + bit1 = rosc_hw->randombit&0xff; + //sleep_ms(1); + bit2 = rosc_hw->randombit&0xff; + } while(bit1 == bit2); + word = (word << 1) | bit1; + } + random_word ^= word^board_millis()^adc_read(); + random_word *= 0x00000100000001B3; + if (++ep_round == 8) { + ep_round = 0; + return 2; //2 words + } + return 0; +} + +static const uint32_t *ep_output() { + return (uint32_t *)&random_word; +} + +struct rng_rb { + uint32_t *buf; + uint8_t head, tail; + uint8_t size; + unsigned int full :1; + unsigned int empty :1; +}; + +static void rb_init(struct rng_rb *rb, uint32_t *p, uint8_t size) { + rb->buf = p; + rb->size = size; + rb->head = rb->tail = 0; + rb->full = 0; + rb->empty = 1; +} + +static void rb_add(struct rng_rb *rb, uint32_t v) { + rb->buf[rb->tail++] = v; + if (rb->tail == rb->size) + rb->tail = 0; + if (rb->tail == rb->head) + rb->full = 1; + rb->empty = 0; +} + +static uint32_t rb_del(struct rng_rb *rb) { + uint32_t v = rb->buf[rb->head++]; + + if (rb->head == rb->size) + rb->head = 0; + if (rb->head == rb->tail) + rb->empty = 1; + rb->full = 0; + + return v; +} + +static struct rng_rb the_ring_buffer; + +void *neug_task() { + struct rng_rb *rb = &the_ring_buffer; + + int n; + + if ((n = ep_process())) { + int i; + const uint32_t *vp; + + vp = ep_output(); + + for (i = 0; i < n; i++) { + rb_add (rb, *vp++); + if (rb->full) + break; + } + } + + return NULL; +} + +void neug_init(uint32_t *buf, uint8_t size) { + pico_unique_board_id_t unique_id; + pico_get_unique_board_id(&unique_id); + const uint32_t *u = (const uint32_t *)unique_id.id; + struct rng_rb *rb = &the_ring_buffer; + int i; + + rb_init(rb, buf, size); + + adc_start(); + + ep_init(); +} + +void neug_flush(void) { + struct rng_rb *rb = &the_ring_buffer; + + while (!rb->empty) + rb_del (rb); +} + +uint32_t neug_get(int kick) { + struct rng_rb *rb = &the_ring_buffer; + uint32_t v; + + while (rb->empty) + neug_task(); + v = rb_del(rb); + + return v; +} + +void neug_wait_full(void) { //should be called only on core1 + struct rng_rb *rb = &the_ring_buffer; + + while (!rb->full) { + sleep_ms(1); + } +} + +void neug_fini(void) { + neug_get(1); +} + diff --git a/src/rng/neug.h b/src/rng/neug.h new file mode 100644 index 0000000..2ced6e3 --- /dev/null +++ b/src/rng/neug.h @@ -0,0 +1,29 @@ +/* + * This file is part of the Pico CCID distribution (https://github.com/polhenarejos/pico-ccid). + * Copyright (c) 2022 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _NEUG_H_ +#define _NEUG_H_ + +#define NEUG_PRE_LOOP 32 + +void neug_init(uint32_t *buf, uint8_t size); +uint32_t neug_get(); +void neug_flush(void); +void neug_wait_full(void); +void neug_fini(void); + +#endif \ No newline at end of file diff --git a/src/rng/random.c b/src/rng/random.c new file mode 100644 index 0000000..62531b5 --- /dev/null +++ b/src/rng/random.c @@ -0,0 +1,109 @@ +/* + * This file is part of the Pico CCID distribution (https://github.com/polhenarejos/pico-ccid). + * Copyright (c) 2022 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include +#include + +#include "neug.h" + +#define RANDOM_BYTES_LENGTH 32 +static uint32_t random_word[RANDOM_BYTES_LENGTH/sizeof (uint32_t)]; + +void random_init(void) { + int i; + + neug_init(random_word, RANDOM_BYTES_LENGTH/sizeof (uint32_t)); + + for (i = 0; i < NEUG_PRE_LOOP; i++) + neug_get(); +} + +void random_fini(void) { + neug_fini (); +} + +/* + * Return pointer to random 32-byte + */ +void random_bytes_free (const uint8_t *p); +#define MAX_RANDOM_BUFFER 1024 +const uint8_t * random_bytes_get(size_t len) { + if (len > MAX_RANDOM_BUFFER) + return NULL; + static uint32_t return_word[MAX_RANDOM_BUFFER/sizeof(uint32_t)]; + for (int ix = 0; ix < len; ix += RANDOM_BYTES_LENGTH) { + neug_wait_full(); + memcpy(return_word+ix/sizeof(uint32_t), random_word, RANDOM_BYTES_LENGTH); + random_bytes_free((const uint8_t *)random_word); + } + return (const uint8_t *)return_word; +} + +/* + * Free pointer to random 32-byte + */ +void random_bytes_free(const uint8_t *p) { + (void)p; + memset(random_word, 0, RANDOM_BYTES_LENGTH); + neug_flush(); +} + +/* + * Return 4-byte salt + */ +void random_get_salt(uint8_t *p) { + uint32_t rnd; + + rnd = neug_get(); + memcpy(p, &rnd, sizeof (uint32_t)); + rnd = neug_get(); + memcpy(p + sizeof (uint32_t), &rnd, sizeof (uint32_t)); +} + + +/* + * Random byte iterator + */ +int random_gen(void *arg, unsigned char *out, size_t out_len) { + uint8_t *index_p = (uint8_t *)arg; + uint8_t index = index_p ? *index_p : 0; + size_t n; + + while (out_len) { + neug_wait_full(); + + n = RANDOM_BYTES_LENGTH - index; + if (n > out_len) + n = out_len; + + memcpy(out, ((unsigned char *)random_word) + index, n); + out += n; + out_len -= n; + index += n; + + if (index >= RANDOM_BYTES_LENGTH) { + index = 0; + neug_flush(); + } + } + + if (index_p) + *index_p = index; + + return 0; +} diff --git a/src/rng/random.h b/src/rng/random.h new file mode 100644 index 0000000..446edbf --- /dev/null +++ b/src/rng/random.h @@ -0,0 +1,38 @@ +/* + * This file is part of the Pico CCID distribution (https://github.com/polhenarejos/pico-ccid). + * Copyright (c) 2022 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#ifndef _RANDOM_H_ +#define _RANDOM_H_ + +#include "stdlib.h" +#include "pico/stdlib.h" + +void random_init (void); +void random_fini (void); + +/* 32-byte random bytes */ +const uint8_t *random_bytes_get (size_t); +void random_bytes_free (const uint8_t *p); + +/* 8-byte salt */ +void random_get_salt (uint8_t *p); + +/* iterator returning a byta at a time */ +int random_gen (void *arg, unsigned char *output, size_t output_len); + +#endif \ No newline at end of file diff --git a/src/usb/ccid.h b/src/usb/ccid.h new file mode 100644 index 0000000..3090e02 --- /dev/null +++ b/src/usb/ccid.h @@ -0,0 +1,73 @@ +/* + * This file is part of the Pico CCID distribution (https://github.com/polhenarejos/pico-ccid). + * Copyright (c) 2022 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _CCID_H_ +#define _CCID_H_ + +struct ccid_class_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdCCID; + uint8_t bMaxSlotIndex; + uint8_t bVoltageSupport; + uint32_t dwProtocols; + uint32_t dwDefaultClock; + uint32_t dwMaximumClock; + uint8_t bNumClockSupport; + uint32_t dwDataRate; + uint32_t dwMaxDataRate; + uint8_t bNumDataRatesSupported; + uint32_t dwMaxIFSD; + uint32_t dwSynchProtocols; + uint32_t dwMechanical; + uint32_t dwFeatures; + uint32_t dwMaxCCIDMessageLength; + uint8_t bClassGetResponse; + uint8_t bclassEnvelope; + uint16_t wLcdLayout; + uint8_t bPINSupport; + uint8_t bMaxCCIDBusySlots; +} __attribute__ ((__packed__)); + +static const struct ccid_class_descriptor desc_ccid = { + .bLength = sizeof(struct ccid_class_descriptor), + .bDescriptorType = 0x21, + .bcdCCID = (0x0110), + .bMaxSlotIndex = 0, + .bVoltageSupport = 0x01, // 5.0V + .dwProtocols = ( + 0x01| // T=0 + 0x02), // T=1 + .dwDefaultClock = (0xDFC), + .dwMaximumClock = (0xDFC), + .bNumClockSupport = 0, + .dwDataRate = (0x2580), + .dwMaxDataRate = (0x2580), + .bNumDataRatesSupported = 0, + .dwMaxIFSD = (0xFE), // IFSD is handled by the real reader driver + .dwSynchProtocols = (0), + .dwMechanical = (0), + .dwFeatures = 0x40840, //USB-ICC, short & extended APDU + .dwMaxCCIDMessageLength = 65544+10, + .bClassGetResponse = 0xFF, + .bclassEnvelope = 0xFF, + .wLcdLayout = 0x0, + .bPINSupport = 0x0, + .bMaxCCIDBusySlots = 0x01, +}; + +#endif \ No newline at end of file diff --git a/src/usb/tusb_config.h b/src/usb/tusb_config.h new file mode 100644 index 0000000..69b5534 --- /dev/null +++ b/src/usb/tusb_config.h @@ -0,0 +1,119 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by board.mk +#ifndef CFG_TUSB_MCU + #error CFG_TUSB_MCU must be defined +#endif + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_DEVICE_RHPORT_NUM + #define BOARD_DEVICE_RHPORT_NUM 0 +#endif + +// RHPort max operational speed can defined by board.mk +// Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed +#ifndef BOARD_DEVICE_RHPORT_SPEED + #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ + CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56 || CFG_TUSB_MCU == OPT_MCU_SAMX7X) + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED + #else + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED + #endif +#endif + +// Device mode with rhport and speed defined by board.mk +#if BOARD_DEVICE_RHPORT_NUM == 0 + #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#elif BOARD_DEVICE_RHPORT_NUM == 1 + #define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#else + #error "Incorrect RHPort configuration" +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_PICO +#endif + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +// #define CFG_TUSB_DEBUG 0 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_HID 0 +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 1 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_EP_BUFSIZE 16 + +#define CFG_TUD_VENDOR_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_VENDOR_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +#include "pico/types.h" + +static inline uint16_t tu_u32_high16(uint32_t ui32) { return (uint16_t) (ui32 >> 16); } +static inline uint16_t tu_u32_low16 (uint32_t ui32) { return (uint16_t) (ui32 & 0x0000ffffu); } + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/src/usb/usb_descriptors.c b/src/usb/usb_descriptors.c new file mode 100644 index 0000000..7a8b376 --- /dev/null +++ b/src/usb/usb_descriptors.c @@ -0,0 +1,209 @@ +/* + * This file is part of the Pico CCID distribution (https://github.com/polhenarejos/pico-ccid). + * Copyright (c) 2022 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "tusb.h" +#include "usb_descriptors.h" +#include "ccid.h" +#include "pico/unique_id.h" +#include "version.h" + + +#ifndef USB_VID +#define USB_VID 0xFEFF +#endif +#ifndef USB_PID +#define USB_PID 0xFCFD +#endif + +#define USB_BCD 0x0200 + +#define USB_CONFIG_ATT_ONE TU_BIT(7) + +#define MAX_USB_POWER 1 + + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = (USB_BCD), + + .bDeviceClass = 0x00, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = (USB_VID), + .idProduct = (USB_PID), + .bcdDevice = HSM_VERSION, + + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + + .bNumConfigurations = 1 +}; + +uint8_t const * tud_descriptor_device_cb(void) +{ + return (uint8_t const *) &desc_device; +} + +tusb_desc_interface_t const desc_interface = +{ + .bLength = sizeof(tusb_desc_interface_t), + .bDescriptorType = TUSB_DESC_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = TUSB_CLASS_SMART_CARD, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = 5, +}; + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ + +tusb_desc_configuration_t const desc_config = +{ + .bLength = sizeof(tusb_desc_configuration_t), + .bDescriptorType = TUSB_DESC_CONFIGURATION, + .wTotalLength = (sizeof(tusb_desc_configuration_t) + sizeof(tusb_desc_interface_t) + sizeof(struct ccid_class_descriptor) + 2*sizeof(tusb_desc_endpoint_t)), + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = 4, + .bmAttributes = USB_CONFIG_ATT_ONE | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, + .bMaxPower = TUSB_DESC_CONFIG_POWER_MA(MAX_USB_POWER+1), +}; + +tusb_desc_endpoint_t const desc_ep1 = +{ + .bLength = sizeof(tusb_desc_endpoint_t), + .bDescriptorType = TUSB_DESC_ENDPOINT, + .bEndpointAddress = TUSB_DIR_IN_MASK | 1, + .bmAttributes.xfer = TUSB_XFER_BULK, + .wMaxPacketSize.size = (64), + .bInterval = 0 +}; + +tusb_desc_endpoint_t const desc_ep2 = +{ + .bLength = sizeof(tusb_desc_endpoint_t), + .bDescriptorType = TUSB_DESC_ENDPOINT, + .bEndpointAddress = 2, + .bmAttributes.xfer = TUSB_XFER_BULK, + .wMaxPacketSize.size = (64), + .bInterval = 0 +}; + +static uint8_t desc_config_extended[sizeof(tusb_desc_configuration_t) + sizeof(tusb_desc_interface_t) + sizeof(struct ccid_class_descriptor) + 2*sizeof(tusb_desc_endpoint_t)]; + +uint8_t const * tud_descriptor_configuration_cb(uint8_t index) +{ + (void) index; // for multiple configurations + + static uint8_t initd = 0; + if (initd == 0) + { + uint8_t *p = desc_config_extended; + memcpy(p, &desc_config, sizeof(tusb_desc_configuration_t)); p += sizeof(tusb_desc_configuration_t); + memcpy(p, &desc_interface, sizeof(tusb_desc_interface_t)); p += sizeof(tusb_desc_interface_t); + memcpy(p, &desc_ccid, sizeof(struct ccid_class_descriptor)); p += sizeof(struct ccid_class_descriptor); + memcpy(p, &desc_ep1, sizeof(tusb_desc_endpoint_t)); p += sizeof(tusb_desc_endpoint_t); + memcpy(p, &desc_ep2, sizeof(tusb_desc_endpoint_t)); p += sizeof(tusb_desc_endpoint_t); + initd = 1; + } + return (const uint8_t *)desc_config_extended; +} + +#define BOS_TOTAL_LEN (TUD_BOS_DESC_LEN) + +#define MS_OS_20_DESC_LEN 0xB2 + +uint8_t const desc_bos[] = +{ + // total length, number of device caps + TUD_BOS_DESCRIPTOR(BOS_TOTAL_LEN, 2) +}; + +uint8_t const * tud_descriptor_bos_cb(void) +{ + return desc_bos; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// array of pointer to string descriptors +char const* string_desc_arr [] = +{ + (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) + "Pol Henarejos", // 1: Manufacturer + "Pico HSM", // 2: Product + "11223344", // 3: Serials, should use chip ID + "Pico HSM Config", // 4: Vendor Interface + "Pico HSM Interface" +}; + +static uint16_t _desc_str[32]; + +uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + (void) langid; + + uint8_t chr_count; + + if (index == 0) { + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + } + else { + // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. + // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors + + if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) + return NULL; + + const char* str = string_desc_arr[index]; + char unique_id_str[2 * PICO_UNIQUE_BOARD_ID_SIZE_BYTES + 1]; + if (index == 3) { + pico_unique_board_id_t unique_id; + pico_get_unique_board_id(&unique_id); + pico_get_unique_board_id_string(unique_id_str, 2 * PICO_UNIQUE_BOARD_ID_SIZE_BYTES + 1); + str = unique_id_str; + } + + chr_count = strlen(str); + if ( chr_count > 31 ) + chr_count = 31; + + // Convert ASCII string into UTF-16 + for(uint8_t i=0; i. + */ + +#ifndef USB_DESCRIPTORS_H_ +#define USB_DESCRIPTORS_H_ + +enum +{ + VENDOR_REQUEST_WEBUSB = 1, + VENDOR_REQUEST_MICROSOFT = 2 +}; + +extern uint8_t const desc_ms_os_20[]; + +#endif /* USB_DESCRIPTORS_H_ */