diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index c3e301a..bffe402 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,8 +1,10 @@ idf_component_register( - SRCS "device.c" "ctaphid.c" "secret.c" "ctap-parser.c" "main.c" "secret.c" "u2f.c" "ctap.c" "common.c" "fs.c" - "crypto/ecc.c" "crypto/hmac.c" "crypto/algo.c" "crypto/sha.c" "crypto/sha3.c" "crypto/memzero.c" "crypto/rand.c" "crypto/sm3.c" "crypto/block-cipher.c" "crypto/aes.c" "crypto/esp32_ed25519.c" + SRCS "device.c" "main.c" "common.c" "fs.c" "apdu.c" "applets.c" "fs.c" "key.c" "pin.c" + "usb/ccid/ccid_device.c" "usb/ccid/ccid.c" "usb/ctaphid/ctaphid.c" + "applets/admin/admin.c" "applets/ctap/ctap.c" "applets/ctap/ctap-parser.c" "applets/ctap/secret.c" "applets/ctap/u2f.c" "applets/meta/meta.c" "applets/oath/oath.c" "applets/ndef/ndef.c" "applets/openpgp/key.c" "applets/openpgp/openpgp.c" "applets/piv/piv.c" + "crypto/ecc.c" "crypto/hmac.c" "crypto/algo.c" "crypto/sha.c" "crypto/sha3.c" "crypto/memzero.c" "crypto/rand.c" "crypto/sm3.c" "crypto/block-cipher.c" "crypto/aes.c" "crypto/rsa.c" "crypto/des.c" "crypto/esp32_ed25519.c" "littlefs/lfs.c" "littlefs/lfs_util.c" - INCLUDE_DIRS "." "crypto/include" "littlefs" + INCLUDE_DIRS "include" "crypto/include" "littlefs" REQUIRES driver mbedtls efuse esp_partition esp_timer EMBED_FILES "cert/u2f_cert.bin" "cert/u2f_cert_key.bin" "cert/u2f_aaguid.bin" ) diff --git a/main/apdu.c b/main/apdu.c new file mode 100644 index 0000000..cdfa536 --- /dev/null +++ b/main/apdu.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum APPLET { + APPLET_NULL, + APPLET_PIV, + APPLET_FIDO, + APPLET_OATH, + APPLET_ADMIN, + APPLET_OPENPGP, + APPLET_NDEF, + APPLET_META, + APPLET_ENUM_END, +} current_applet; + +enum PIV_STATE { + PIV_STATE_GET_DATA, + PIV_STATE_GET_DATA_RESPONSE, + PIV_STATE_OTHER, +}; + +static const uint8_t PIV_AID[] = {0xA0, 0x00, 0x00, 0x03, 0x08}; +static const uint8_t OATH_AID[] = {0xA0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01}; +static const uint8_t ADMIN_AID[] = {0xF0, 0x00, 0x00, 0x00, 0x00}; +static const uint8_t OPENPGP_AID[] = {0xD2, 0x76, 0x00, 0x01, 0x24, 0x01}; +static const uint8_t FIDO_AID[] = {0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01}; +static const uint8_t NDEF_AID[] = {0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01}; +static const uint8_t META_AID[] = {0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17}; + +static const uint8_t *const AID[] = { + [APPLET_NULL] = NULL, [APPLET_PIV] = PIV_AID, [APPLET_FIDO] = FIDO_AID, [APPLET_OATH] = OATH_AID, + [APPLET_ADMIN] = ADMIN_AID, [APPLET_OPENPGP] = OPENPGP_AID, [APPLET_NDEF] = NDEF_AID, [APPLET_META] = META_AID, +}; + +static const uint8_t AID_Size[] = { + [APPLET_NULL] = 0, + [APPLET_PIV] = sizeof(PIV_AID), + [APPLET_FIDO] = sizeof(FIDO_AID), + [APPLET_OATH] = sizeof(OATH_AID), + [APPLET_ADMIN] = sizeof(ADMIN_AID), + [APPLET_OPENPGP] = sizeof(OPENPGP_AID), + [APPLET_NDEF] = sizeof(NDEF_AID), + [APPLET_META] = sizeof(META_AID), +}; + +static volatile uint32_t buffer_owner = BUFFER_OWNER_NONE; +static uint8_t chaining_buffer[APDU_BUFFER_SIZE]; +static CAPDU_CHAINING capdu_chaining = { + .capdu.data = chaining_buffer, +}; +static RAPDU_CHAINING rapdu_chaining = { + .rapdu.data = chaining_buffer, +}; + +int build_capdu(CAPDU *capdu, const uint8_t *cmd, uint16_t len) { + if (len < 4) return -1; + CLA = cmd[0]; + INS = cmd[1]; + P1 = cmd[2]; + P2 = cmd[3]; + LC = 0; + LE = 0; + + if (len == 4) // Case 1 + return 0; + LC = cmd[4]; + if (len == 5) { // Case 2S + LE = LC; + LC = 0; + if (LE == 0) LE = 0x100; + } else if (LC > 0 && len == 5 + LC) { // Case 3S + memmove(DATA, cmd + 5, LC); + LE = 0x100; + } else if (LC > 0 && len == 6 + LC) { // Case 4S + memmove(DATA, cmd + 5, LC); + LE = cmd[5 + LC]; + if (LE == 0) LE = 0x100; + } else if (len == 7) { // Case 2E + if (LC != 0) return -1; + LE = (cmd[5] << 8) | cmd[6]; + if (LE == 0) LE = 0x10000; + } else { + if (LC != 0 || len < 7) return -1; + LC = (cmd[5] << 8) | cmd[6]; + if (LC == 0) return -1; + if (len == 7 + LC) { // Case 3E + memmove(DATA, cmd + 7, LC); + LE = 0x10000; + return 0; + } else if (len == 9 + LC) { // Case 4E + memmove(DATA, cmd + 7, LC); + LE = (cmd[7 + LC] << 8) | cmd[8 + LC]; + if (LE == 0) LE = 0x10000; + } else + return -1; + } + return 0; +} + +int apdu_input(CAPDU_CHAINING *ex, const CAPDU *sh) { +restart: + if (!ex->in_chaining) { + ex->capdu.cla = sh->cla & 0xEF; + ex->capdu.ins = sh->ins; + ex->capdu.p1 = sh->p1; + ex->capdu.p2 = sh->p2; + ex->capdu.lc = 0; + } else if (ex->capdu.cla != (sh->cla & 0xEF) || ex->capdu.ins != sh->ins || ex->capdu.p1 != sh->p1 || + ex->capdu.p2 != sh->p2) { + ex->in_chaining = 0; + goto restart; + } + ex->in_chaining = 1; + if (ex->capdu.lc + sh->lc > APDU_BUFFER_SIZE) return APDU_CHAINING_OVERFLOW; + memcpy(ex->capdu.data + ex->capdu.lc, sh->data, sh->lc); + ex->capdu.lc += sh->lc; + + if (sh->cla & 0x10) // not last block + return APDU_CHAINING_NOT_LAST_BLOCK; + else { + ex->in_chaining = 0; + ex->capdu.le = sh->le; + return APDU_CHAINING_LAST_BLOCK; + } +} + +int apdu_output(RAPDU_CHAINING *ex, RAPDU *sh) { + uint16_t to_send = ex->rapdu.len - ex->sent; + if (to_send > sh->len) to_send = sh->len; + memcpy(sh->data, ex->rapdu.data + ex->sent, to_send); + sh->len = to_send; + ex->sent += to_send; + if (ex->sent < ex->rapdu.len) { + if (ex->rapdu.len - ex->sent > 0xFF) + sh->sw = 0x61FF; + else + sh->sw = 0x6100 + (ex->rapdu.len - ex->sent); + } else + sh->sw = ex->rapdu.sw; + return 0; +} + +void process_apdu(CAPDU *capdu, RAPDU *rapdu) { + static enum PIV_STATE piv_state; + if (current_applet == APPLET_PIV) { + // Offload some APDU chaining commands of PIV applet, + // because the length of concatenated payloads may exceed chaining buffer size. + if (INS == PIV_INS_GET_DATA) + piv_state = PIV_STATE_GET_DATA; + else if ((piv_state == PIV_STATE_GET_DATA || piv_state == PIV_STATE_GET_DATA_RESPONSE) && INS == 0xC0) + piv_state = PIV_STATE_GET_DATA_RESPONSE; + else + piv_state = PIV_STATE_OTHER; + if (piv_state == PIV_STATE_GET_DATA || piv_state == PIV_STATE_GET_DATA_RESPONSE || INS == PIV_INS_PUT_DATA) { + LE = MIN(LE, APDU_BUFFER_SIZE); // Always clamp the Le to valid range + piv_process_apdu(capdu, rapdu); + return; + } + } + int ret = apdu_input(&capdu_chaining, capdu); + if (ret == APDU_CHAINING_NOT_LAST_BLOCK) { + LL = 0; + SW = SW_NO_ERROR; + } else if (ret == APDU_CHAINING_LAST_BLOCK) { + capdu = &capdu_chaining.capdu; + LE = MIN(LE, APDU_BUFFER_SIZE); + if ((CLA == 0x80 || CLA == 0x00) && INS == 0xC0) { // GET RESPONSE + rapdu->len = LE; + apdu_output(&rapdu_chaining, rapdu); + return; + } + rapdu_chaining.sent = 0; + if (CLA == 0x00 && INS == 0xA4 && P1 == 0x04 && P2 == 0x00) { + uint8_t i, end = APPLET_ENUM_END; + for (i = APPLET_NULL + 1; i != end; ++i) { + if (LC >= AID_Size[i] && memcmp(DATA, AID[i], AID_Size[i]) == 0) { + if (i == APPLET_NDEF && !cfg_is_ndef_enable()) { + LL = 0; + SW = SW_FILE_NOT_FOUND; + DBG_MSG("NDEF is disable\n"); + return; + } + if (i == APPLET_PIV) piv_state = PIV_STATE_OTHER; // Reset `piv_state` + if (i != current_applet) applets_poweroff(); + current_applet = i; + DBG_MSG("applet switched to: %d\n", current_applet); + break; + } + } + if (i == end) { + LL = 0; + SW = SW_FILE_NOT_FOUND; + DBG_MSG("applet not found\n"); + return; + } + } + switch (current_applet) { + case APPLET_OPENPGP: + openpgp_process_apdu(capdu, &rapdu_chaining.rapdu); + rapdu->len = LE; + apdu_output(&rapdu_chaining, rapdu); + break; + case APPLET_PIV: + piv_process_apdu(capdu, &rapdu_chaining.rapdu); + rapdu->len = LE; + apdu_output(&rapdu_chaining, rapdu); + break; + case APPLET_FIDO: +#ifdef TEST + if (CLA == 0x00 && INS == 0xEE && LC == 0x04 && memcmp(DATA, "\x12\x56\xAB\xF0", 4) == 0) { + printf("MAGIC REBOOT command received!\r\n"); + testmode_set_initial_ticks(0); + testmode_set_initial_ticks(device_get_tick()); + ctap_install(0); + SW = 0x9000; + LL = 0; + break; + } + if (CLA == 0x00 && INS == 0xEF) { + testmode_inject_error(P1, P2, LC, DATA); + SW = 0x9000; + LL = 0; + break; + } +#endif + ctap_process_apdu(capdu, &rapdu_chaining.rapdu); + rapdu->len = LE; + apdu_output(&rapdu_chaining, rapdu); + break; + case APPLET_OATH: + oath_process_apdu(capdu, rapdu); + break; + case APPLET_ADMIN: + admin_process_apdu(capdu, rapdu); + break; + case APPLET_NDEF: + ndef_process_apdu(capdu, rapdu); + break; + case APPLET_META: + meta_process_apdu(capdu, rapdu); + break; + default: + LL = 0; + SW = SW_FILE_NOT_FOUND; + } + } else { + LL = 0; + SW = SW_CHECKING_ERROR; + } +} + +int acquire_apdu_buffer(uint8_t owner) { + device_atomic_compare_and_swap(&buffer_owner, BUFFER_OWNER_NONE, owner); + return buffer_owner == owner ? 0 : -1; +} + +int release_apdu_buffer(uint8_t owner) { + device_atomic_compare_and_swap(&buffer_owner, owner, BUFFER_OWNER_NONE); + return buffer_owner == BUFFER_OWNER_NONE ? 0 : -1; +} diff --git a/main/applets.c b/main/applets.c new file mode 100644 index 0000000..b73855b --- /dev/null +++ b/main/applets.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include +#include + +void applets_install(void) { + openpgp_install(0); + piv_install(0); + oath_install(0); + ctap_install(0); + admin_install(0); + ndef_install(0); +} + +void applets_poweroff(void) { + piv_poweroff(); + oath_poweroff(); + admin_poweroff(); + openpgp_poweroff(); + ndef_poweroff(); +} diff --git a/main/applets/admin/admin.c b/main/applets/admin/admin.c new file mode 100644 index 0000000..4e5f998 --- /dev/null +++ b/main/applets/admin/admin.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PIN_RETRY_COUNTER 3 +#define SN_FILE "sn" +#define CFG_FILE "admin_cfg" + +static pin_t pin = {.min_length = 6, .max_length = PIN_MAX_LENGTH, .is_validated = 0, .path = "admin-pin"}; + +static const admin_device_config_t default_cfg = {.led_normally_on = 1, .ndef_en = 1, .webusb_landing_en = 1, .kbd_with_return_en = 1}; + +static admin_device_config_t current_config; + +__attribute__((weak)) int admin_vendor_specific(const CAPDU *capdu, RAPDU *rapdu) { + UNUSED(capdu); + UNUSED(rapdu); + return 0; +} + +__attribute__((weak)) int admin_vendor_version(const CAPDU *capdu, RAPDU *rapdu) { + UNUSED(capdu); + UNUSED(rapdu); + return 0; +} + +__attribute__((weak)) int admin_vendor_hw_variant(const CAPDU *capdu, RAPDU *rapdu) { + UNUSED(capdu); + UNUSED(rapdu); + return 0; +} + +__attribute__((weak)) int admin_vendor_hw_sn(const CAPDU *capdu, RAPDU *rapdu) { + UNUSED(capdu); + UNUSED(rapdu); + return 0; +} + +uint8_t cfg_is_led_normally_on(void) { return current_config.led_normally_on; } + +uint8_t cfg_is_kbd_interface_enable(void) { return current_config.kbd_interface_en; } + +uint8_t cfg_is_ndef_enable(void) { return current_config.ndef_en; } + +uint8_t cfg_is_webusb_landing_enable(void) { return current_config.webusb_landing_en; } + +uint8_t cfg_is_kbd_with_return_enable(void) { return current_config.kbd_with_return_en; } + +void admin_poweroff(void) { pin.is_validated = 0; } + +int admin_install(const uint8_t reset) { + admin_poweroff(); + if (reset || get_file_size(CFG_FILE) != sizeof(admin_device_config_t)) { + current_config = default_cfg; + if (write_file(CFG_FILE, ¤t_config, 0, sizeof(current_config), 1) < 0) return -1; + } else { + if (read_file(CFG_FILE, ¤t_config, 0, sizeof(current_config)) < 0) return -1; + } + if (reset || get_file_size(pin.path) < 0) { + if (pin_create(&pin, "123456", 6, PIN_RETRY_COUNTER) < 0) return -1; + } + return 0; +} + +static int admin_verify(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (LC == 0) { + if (pin.is_validated) return 0; + const int retries = pin_get_retries(&pin); + if (retries < 0) return -1; + EXCEPT(SW_PIN_RETRIES + retries); + } + uint8_t ctr; + const int err = pin_verify(&pin, DATA, LC, &ctr); + if (err == PIN_IO_FAIL) return -1; + if (err == PIN_LENGTH_INVALID) EXCEPT(SW_WRONG_LENGTH); + if (ctr == 0) EXCEPT(SW_AUTHENTICATION_BLOCKED); + if (err == PIN_AUTH_FAIL) EXCEPT(SW_PIN_RETRIES + ctr); + return 0; +} + +static int admin_change_pin(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + const int err = pin_update(&pin, DATA, LC); + if (err == PIN_IO_FAIL) return -1; + if (err == PIN_LENGTH_INVALID) EXCEPT(SW_WRONG_LENGTH); + return 0; +} + +static int admin_write_sn(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (LC != 0x04) EXCEPT(SW_WRONG_LENGTH); + if (get_file_size(SN_FILE) >= 0) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + return write_file(SN_FILE, DATA, 0, LC, 1); +} + +static int admin_read_sn(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (LE < 4) EXCEPT(SW_WRONG_LENGTH); + + fill_sn(RDATA); + LL = 4; + + return 0; +} + +static int admin_config(const CAPDU *capdu, RAPDU *rapdu) { + switch (P1) { + case ADMIN_P1_CFG_LED_ON: + current_config.led_normally_on = P2 & 1; + break; + case ADMIN_P1_CFG_KBDIFACE: + current_config.kbd_interface_en = P2 & 1; + break; + case ADMIN_P1_CFG_NDEF: + current_config.ndef_en = P2 & 1; + break; + case ADMIN_P1_CFG_WEBUSB_LANDING: + current_config.webusb_landing_en = P2 & 1; + break; + case ADMIN_P1_CFG_KBD_WITH_RETURN: + current_config.kbd_with_return_en = P2 & 1; + break; + default: + EXCEPT(SW_WRONG_P1P2); + } + const int ret = write_file(CFG_FILE, ¤t_config, 0, sizeof(current_config), 1); + stop_blinking(); + return ret; +} + +static int admin_read_config(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (LE < 5) EXCEPT(SW_WRONG_LENGTH); + + RDATA[0] = current_config.led_normally_on; + RDATA[1] = current_config.kbd_interface_en; + RDATA[2] = ndef_get_read_only(); + RDATA[3] = current_config.ndef_en; + RDATA[4] = current_config.webusb_landing_en; + RDATA[5] = current_config.kbd_with_return_en; + LL = 6; + + return 0; +} + +static int admin_flash_usage(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (LE < 2) EXCEPT(SW_WRONG_LENGTH); + + RDATA[0] = get_fs_usage(); + RDATA[1] = get_fs_size(); + LL = 2; + + return 0; +} + +static int admin_factory_reset(const CAPDU *capdu, RAPDU *rapdu) { + int ret; + if (P1 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (LC != 5) EXCEPT(SW_WRONG_LENGTH); + if (memcmp(DATA, "RESET", 5) != 0) EXCEPT(SW_WRONG_DATA); +#ifndef FUZZ + ret = pin_get_retries(&pin); + if (ret > 0) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + + if (is_nfc()) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + if (strong_user_presence_test() < 0) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); +#endif + + DBG_MSG("factory reset begins\n"); + ret = openpgp_install(1); + if (ret < 0) return ret; + ret = piv_install(1); + if (ret < 0) return ret; + ret = oath_install(1); + if (ret < 0) return ret; + ret = ctap_install(1); + if (ret < 0) return ret; + ret = ndef_install(1); + if (ret < 0) return ret; + ret = admin_install(1); + if (ret < 0) return ret; + return 0; +} + +void fill_sn(uint8_t *buf) { + const int err = read_file(SN_FILE, buf, 0, 4); + if (err != 4) memset(buf, 0, 4); +} + +int admin_process_apdu(const CAPDU *capdu, RAPDU *rapdu) { + LL = 0; + SW = SW_NO_ERROR; + + int ret = 0; + switch (INS) { + case ADMIN_INS_SELECT: + if (P1 != 0x04 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + return 0; + + case ADMIN_INS_READ_VERSION: + if (P1 > 1 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (P1 == 0) + ret = admin_vendor_version(capdu, rapdu); + else if (P1 == 1) + ret = admin_vendor_hw_variant(capdu, rapdu); + goto done; + + case ADMIN_INS_READ_SN: + if (P1 > 1 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (P1 == 0) + ret = admin_read_sn(capdu, rapdu); + else if (P1 == 1) + ret = admin_vendor_hw_sn(capdu, rapdu); + goto done; + + case ADMIN_INS_FACTORY_RESET: + ret = admin_factory_reset(capdu, rapdu); + goto done; + + case ADMIN_INS_VERIFY: + ret = admin_verify(capdu, rapdu); + goto done; + + default: + break; + } + +#ifndef FUZZ + if (!pin.is_validated) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); +#endif + + switch (INS) { + case ADMIN_INS_WRITE_FIDO_PRIVATE_KEY: + ret = ctap_install_private_key(capdu, rapdu); + break; + case ADMIN_INS_WRITE_FIDO_CERT: + ret = ctap_install_cert(capdu, rapdu); + break; + case ADMIN_INS_RESET_OPENPGP: + ret = openpgp_install(1); + break; + case ADMIN_INS_RESET_PIV: + ret = piv_install(1); + break; + case ADMIN_INS_RESET_OATH: + ret = oath_install(1); + break; + case ADMIN_INS_RESET_NDEF: + ret = ndef_install(1); + break; + case ADMIN_INS_TOGGLE_NDEF_READ_ONLY: + ret = ndef_toggle_read_only(capdu, rapdu); + break; + case ADMIN_INS_RESET_CTAP: + ret = ctap_install(1); + break; + case ADMIN_INS_READ_CTAP_SM2_CONFIG: + ret = ctap_read_sm2_config(capdu, rapdu); + break; + case ADMIN_INS_WRITE_CTAP_SM2_CONFIG: + ret = ctap_write_sm2_config(capdu, rapdu); + break; + case ADMIN_INS_CHANGE_PIN: + ret = admin_change_pin(capdu, rapdu); + break; + case ADMIN_INS_WRITE_SN: + ret = admin_write_sn(capdu, rapdu); + break; + case ADMIN_INS_CONFIG: + ret = admin_config(capdu, rapdu); + break; + case ADMIN_INS_FLASH_USAGE: + ret = admin_flash_usage(capdu, rapdu); + break; + case ADMIN_INS_READ_CONFIG: + ret = admin_read_config(capdu, rapdu); + break; + case ADMIN_INS_VENDOR_SPECIFIC: + ret = admin_vendor_specific(capdu, rapdu); + break; + default: + EXCEPT(SW_INS_NOT_SUPPORTED); + } + +done: + if (ret < 0) EXCEPT(SW_UNABLE_TO_PROCESS); + return 0; +} diff --git a/main/cose-key.h b/main/applets/ctap/cose-key.h similarity index 100% rename from main/cose-key.h rename to main/applets/ctap/cose-key.h diff --git a/main/ctap-errors.h b/main/applets/ctap/ctap-errors.h similarity index 100% rename from main/ctap-errors.h rename to main/applets/ctap/ctap-errors.h diff --git a/main/ctap-internal.h b/main/applets/ctap/ctap-internal.h similarity index 100% rename from main/ctap-internal.h rename to main/applets/ctap/ctap-internal.h diff --git a/main/ctap-parser.c b/main/applets/ctap/ctap-parser.c similarity index 100% rename from main/ctap-parser.c rename to main/applets/ctap/ctap-parser.c diff --git a/main/ctap-parser.h b/main/applets/ctap/ctap-parser.h similarity index 100% rename from main/ctap-parser.h rename to main/applets/ctap/ctap-parser.h diff --git a/main/ctap.c b/main/applets/ctap/ctap.c similarity index 99% rename from main/ctap.c rename to main/applets/ctap/ctap.c index c3a1ea4..92de9e7 100644 --- a/main/ctap.c +++ b/main/applets/ctap/ctap.c @@ -53,6 +53,8 @@ send_keepalive_during_processing(WAIT_ENTRY_CTAPHID); \ } while (0) +static const uint8_t aaguid[] = {0x24, 0x4e, 0xb2, 0x9e, 0xe0, 0x90, 0x4e, 0x49, + 0x81, 0xfe, 0x1f, 0x20, 0xf8, 0xd3, 0xb8, 0xf4}; // pin & command states static uint8_t consecutive_pin_counter, last_cmd; @@ -88,17 +90,19 @@ uint8_t ctap_install(uint8_t reset) { return 0; } -int ctap_install_private_key(const uint8_t* cert_key,lfs_size_t len) { +int ctap_install_private_key(const CAPDU *capdu, RAPDU *rapdu) { + if (LC != PRI_KEY_SIZE) EXCEPT(SW_WRONG_LENGTH); // initialize SM2 config ctap_sm2_attr.enabled = 0; ctap_sm2_attr.curve_id = 9; // An unused one. See https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves ctap_sm2_attr.algo_id = -48; // An unused one. See https://www.iana.org/assignments/cose/cose.xhtml#algorithms if (write_attr(CTAP_CERT_FILE, SM2_ATTR, &ctap_sm2_attr, sizeof(ctap_sm2_attr)) < 0) return CTAP2_ERR_UNHANDLED_REQUEST; - return write_attr(CTAP_CERT_FILE, KEY_ATTR, cert_key, len); + return write_attr(CTAP_CERT_FILE, KEY_ATTR, DATA, LC); } -int ctap_install_cert(const uint8_t* cert,lfs_size_t len) { - return write_file(CTAP_CERT_FILE, cert, 0, len, 1); +int ctap_install_cert(const CAPDU *capdu, RAPDU *rapdu) { + if (LC > MAX_CERT_SIZE) EXCEPT(SW_WRONG_LENGTH); + return write_file(CTAP_CERT_FILE, DATA, 0, LC, 1); } int ctap_read_sm2_config(const CAPDU *capdu, RAPDU *rapdu) { @@ -270,7 +274,7 @@ uint8_t ctap_make_auth_data(uint8_t *rp_id_hash, uint8_t *buf, uint8_t flags, co // If no credProtect extension was included in the request the authenticator SHOULD use the default value of 1 for compatibility with CTAP2.0 platforms. if (cred_protect == CRED_PROTECT_ABSENT) cred_protect = CRED_PROTECT_VERIFICATION_OPTIONAL; - device_get_aaguid(ad->at.aaguid,16); + memcpy(ad->at.aaguid, aaguid, sizeof(aaguid)); ad->at.credential_id_length = htobe16(sizeof(credential_id)); memcpy(ad->at.credential_id.rp_id_hash, rp_id_hash, sizeof(ad->at.credential_id.rp_id_hash)); if (generate_key_handle(&ad->at.credential_id, ad->at.public_key, alg_type, (uint8_t)dc, cred_protect) < 0) { diff --git a/main/secret.c b/main/applets/ctap/secret.c similarity index 100% rename from main/secret.c rename to main/applets/ctap/secret.c diff --git a/main/secret.h b/main/applets/ctap/secret.h similarity index 100% rename from main/secret.h rename to main/applets/ctap/secret.h diff --git a/main/u2f.c b/main/applets/ctap/u2f.c similarity index 100% rename from main/u2f.c rename to main/applets/ctap/u2f.c diff --git a/main/u2f.h b/main/applets/ctap/u2f.h similarity index 100% rename from main/u2f.h rename to main/applets/ctap/u2f.h diff --git a/main/applets/meta/meta.c b/main/applets/meta/meta.c new file mode 100644 index 0000000..9a41ade --- /dev/null +++ b/main/applets/meta/meta.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#define TAG_USB_SUPPORT 0x01 +#define TAG_SN 0x02 +#define TAG_USB_ENABLED 0x03 +#define TAG_FORM_FACTOR 0x04 +#define TAG_NFC_SUPPORT 0x0D +#define TAG_NFC_ENABLED 0x0E + +int meta_process_apdu(const CAPDU *capdu, RAPDU *rapdu) { + LL = 0; + SW = SW_NO_ERROR; + + switch (INS) { + case META_INS_SELECT: + if (P1 != 0x04 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + memcpy(RDATA, "5.5.5", 5); // a fake version + LL = 5; + break; + + case META_INS_READ_META: + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + RDATA[0] = 25; + RDATA[1] = TAG_USB_SUPPORT; // FIDO2|OATH|PIV|OPENPGP|U2F + RDATA[2] = 2; + RDATA[3] = 0x02; + RDATA[4] = 0x3A; + RDATA[5] = TAG_SN; + RDATA[6] = 4; + fill_sn(RDATA + 7); + RDATA[11] = TAG_FORM_FACTOR; + RDATA[12] = 1; + RDATA[13] = 0x41; + RDATA[14] = TAG_USB_ENABLED; // FIDO2|OATH|PIV|OPENPGP|U2F + RDATA[15] = 2; + RDATA[16] = 0x02; + RDATA[17] = 0x3A; + RDATA[18] = TAG_NFC_SUPPORT; // FIDO2|OATH|PIV|OPENPGP|U2F + RDATA[19] = 2; + RDATA[20] = 0x02; + RDATA[21] = 0x3A; + RDATA[22] = TAG_NFC_ENABLED; // FIDO2|OATH|PIV|OPENPGP|U2F + RDATA[23] = 2; + RDATA[24] = 0x02; + RDATA[25] = 0x3A; + LL = 26; + break; + + default: + EXCEPT(SW_INS_NOT_SUPPORTED); + } + return 0; +} diff --git a/main/applets/ndef/ndef.c b/main/applets/ndef/ndef.c new file mode 100644 index 0000000..e5062a8 --- /dev/null +++ b/main/applets/ndef/ndef.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#define CC_FILE "E103" // file identifier also 0xE103 +#define NDEF_FILE "NDEF" +#define NDEF_MSG_MAX_LENGTH 1022 +#define NDEF_FILE_MAX_LENGTH (NDEF_MSG_MAX_LENGTH + 2) +#define CC_LENGTH 15 + +static uint8_t current_cc[CC_LENGTH]; +static const uint8_t default_cc[CC_LENGTH] = { + 0x00, 0x0F, // len + 0x20, // version, 2.0 + HI(NDEF_FILE_MAX_LENGTH), LO(NDEF_FILE_MAX_LENGTH), // mle + HI(NDEF_FILE_MAX_LENGTH), LO(NDEF_FILE_MAX_LENGTH), // mlc + // the following are tlv data + 0x04, // t + 0x06, // l + 0x00, 0x01, // file id + HI(NDEF_FILE_MAX_LENGTH), LO(NDEF_FILE_MAX_LENGTH), // max_size + 0x00, // read access without any security + 0x00 // write access without any security +}; + +#define CC_R (current_cc[13]) +#define CC_W (current_cc[14]) + +static enum { NONE, CC, NDEF } selected; + +void ndef_poweroff(void) { selected = NONE; } + +int ndef_get_read_only(void) { + return CC_W == 0xFF ? 1 : 0; +} + +int ndef_toggle_read_only(const CAPDU *capdu, RAPDU *rapdu) { + switch (P1) { + case 0x00: // read and write + CC_W = 0x00; + break; + case 0x01: // read only + CC_W = 0xFF; + break; + default: + EXCEPT(SW_WRONG_P1P2); + } + if (write_file(CC_FILE, ¤t_cc, 0, sizeof(current_cc), 1) < 0) return -1; + return 0; +} + +int ndef_create_init_ndef() { + const char *init_data = "\x00\x11\xD1\x01\x0D\x55\x04""canokeys.org"; + if (write_file(NDEF_FILE, init_data, 0, 19, 1) < -1) return -1; + if (truncate_file(NDEF_FILE, NDEF_FILE_MAX_LENGTH) < -1) return -1; // Fill the file with zeros + return 0; +} + +int ndef_install(const uint8_t reset) { + ndef_poweroff(); + if (reset || get_file_size(CC_FILE) != sizeof(current_cc) || get_file_size(NDEF_FILE) <= 0) { + memcpy(current_cc, default_cc, sizeof(current_cc)); + if (ndef_create_init_ndef() < 0) return -1; + if (write_file(CC_FILE, ¤t_cc, 0, sizeof(current_cc), 1) < 0) return -1; + } else { + if (read_file(CC_FILE, ¤t_cc, 0, sizeof(current_cc)) < 0) return -1; + // should check sanity, by standard + } + return 0; +} + +int ndef_select(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 == 0x04 && P2 == 0x00) return 0; + if (P1 != 0x00 || P2 != 0x0C) EXCEPT(SW_WRONG_P1P2); + if (LC < 2) EXCEPT(SW_WRONG_LENGTH); + if (DATA[0] == 0xE1 && DATA[1] == 0x03) + selected = CC; + else if (DATA[0] == 0x00 && DATA[1] == 0x01) + selected = NDEF; + else + EXCEPT(SW_FILE_NOT_FOUND); + return 0; +} + +int ndef_read_binary(const CAPDU *capdu, RAPDU *rapdu) { + const uint16_t offset = (uint16_t)(P1 << 8) | P2; + if (offset > NDEF_FILE_MAX_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (LE > NDEF_FILE_MAX_LENGTH) EXCEPT(SW_WRONG_LENGTH); + + switch (selected) { + case CC: + if (offset + LE > CC_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (read_file(CC_FILE, RDATA, offset, LE) < 0) return -1; + LL = LE; + break; + case NDEF: + if (CC_R != 0x00) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + if (offset + LE > NDEF_FILE_MAX_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (read_file(NDEF_FILE, RDATA, offset, LE) < 0) return -1; + LL = LE; + break; + case NONE: + EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + } + return 0; +} + +int ndef_update(const CAPDU *capdu, RAPDU *rapdu) { + const uint16_t offset = (uint16_t)(P1 << 8) | P2; + if (offset > NDEF_FILE_MAX_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (LC > NDEF_FILE_MAX_LENGTH) EXCEPT(SW_WRONG_LENGTH); + + switch (selected) { + case CC: + // do not allow change CC, only modified via admin + EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + case NDEF: + if (CC_W != 0x00) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + if (offset + LC > NDEF_FILE_MAX_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (write_file(NDEF_FILE, DATA, offset, LC, 0) < 0) return -1; + break; + case NONE: + EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + } + return 0; +} + +int ndef_process_apdu(const CAPDU *capdu, RAPDU *rapdu) { + LL = 0; + SW = SW_NO_ERROR; + + int ret; + switch (INS) { + case NDEF_INS_SELECT: + ret = ndef_select(capdu, rapdu); + break; + case NDEF_INS_READ_BINARY: + ret = ndef_read_binary(capdu, rapdu); + break; + case NDEF_INS_UPDATE: + ret = ndef_update(capdu, rapdu); + break; + default: + EXCEPT(SW_INS_NOT_SUPPORTED); + } + if (ret < 0) EXCEPT(SW_UNABLE_TO_PROCESS); + return 0; +} diff --git a/main/applets/oath/oath.c b/main/applets/oath/oath.c new file mode 100644 index 0000000..6e101bf --- /dev/null +++ b/main/applets/oath/oath.c @@ -0,0 +1,689 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OATH_FILE "oath" +#define MAX_RECORDS 100 + +static enum { + REMAINING_NONE, + REMAINING_CALC_FULL, + REMAINING_CALC_TRUNC, + REMAINING_LIST, +} oath_remaining_type; + +static uint8_t auth_challenge[MAX_CHALLENGE_LEN], record_idx, is_validated; + +void oath_poweroff(void) { + oath_remaining_type = REMAINING_NONE; + is_validated = false; +} + +int oath_install(const uint8_t reset) { + oath_poweroff(); + if (!reset && get_file_size(OATH_FILE) >= 0) return 0; + if (write_file(OATH_FILE, NULL, 0, 0, 1) < 0) return -1; + const uint32_t default_item = 0xffffffff; + if (write_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &default_item, sizeof(default_item)) < 0) return -1; + if (write_attr(OATH_FILE, ATTR_KEY, NULL, 0) < 0) return -1; + uint8_t handle[HANDLE_LEN]; + random_buffer(handle, sizeof(handle)); + if (write_attr(OATH_FILE, ATTR_HANDLE, handle, sizeof(handle)) < 0) return -1; + return 0; +} + +static int oath_select(const CAPDU *capdu, RAPDU *rapdu) { + if (P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + + memcpy(RDATA, (uint8_t[]){OATH_TAG_VERSION, 3, 0x05, 0x05, 0x05, OATH_TAG_NAME, HANDLE_LEN}, 7); + if (read_attr(OATH_FILE, ATTR_HANDLE, RDATA + 7, HANDLE_LEN) < 0) return -1; + LL = 7 + HANDLE_LEN; + + // check if there is a key + uint8_t dummy; + const int32_t ret = read_attr(OATH_FILE, ATTR_KEY, &dummy, 1); + if (ret < 0) return -1; + + if (ret == 0) { // no key is set + is_validated = true; + } else { + random_buffer(auth_challenge, sizeof(auth_challenge)); + RDATA[7 + HANDLE_LEN] = OATH_TAG_CHALLENGE; + RDATA[8 + HANDLE_LEN] = sizeof(auth_challenge); + memcpy(RDATA + 9 + HANDLE_LEN, auth_challenge, sizeof(auth_challenge)); + RDATA[9 + HANDLE_LEN + sizeof(auth_challenge)] = OATH_TAG_ALGORITHM; + RDATA[10 + HANDLE_LEN + sizeof(auth_challenge)] = 1; + RDATA[11 + HANDLE_LEN + sizeof(auth_challenge)] = OATH_ALG_SHA1; + LL += 5 + sizeof(auth_challenge); + } + + return 0; +} + +static int oath_put(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + + // parse name + uint16_t offset = 0; + if (LC < 2) EXCEPT(SW_WRONG_LENGTH); + if (DATA[offset++] != OATH_TAG_NAME) EXCEPT(SW_WRONG_DATA); + const uint8_t name_len = DATA[offset++]; + const uint8_t *name_ptr = &DATA[offset]; + if (name_len > MAX_NAME_LEN || name_len == 0) EXCEPT(SW_WRONG_DATA); + offset += name_len; + + // parse key + if (LC <= offset + 4) EXCEPT(SW_WRONG_LENGTH); + if (DATA[offset++] != OATH_TAG_KEY) EXCEPT(SW_WRONG_DATA); + const uint8_t key_len = DATA[offset++]; + const uint8_t *key_ptr = &DATA[offset]; + if (key_len > MAX_KEY_LEN || key_len <= 2) // 2 for algo & digits + EXCEPT(SW_WRONG_DATA); + const uint8_t alg = DATA[offset]; + if ((alg & OATH_TYPE_MASK) != OATH_TYPE_HOTP && (alg & OATH_TYPE_MASK) != OATH_TYPE_TOTP) EXCEPT(SW_WRONG_DATA); + if ((alg & OATH_ALG_MASK) != OATH_ALG_SHA1 && (alg & OATH_ALG_MASK) != OATH_ALG_SHA256 && + (alg & OATH_ALG_MASK) != OATH_ALG_SHA512) + EXCEPT(SW_WRONG_DATA); + const uint8_t digits = DATA[offset + 1]; + if (digits < 4 || digits > 8) EXCEPT(SW_WRONG_DATA); + offset += key_len; + + // parse property (optional tag) + uint8_t prop = 0; + if (LC > offset && DATA[offset] == OATH_TAG_PROPERTY) { + if (LC <= ++offset) EXCEPT(SW_WRONG_LENGTH); + prop = DATA[offset++]; + if ((prop & ~OATH_PROP_ALL_FLAGS) != 0) EXCEPT(SW_WRONG_DATA); + } + + // parse HOTP counter (optional tag) + uint8_t chal[MAX_CHALLENGE_LEN] = {0}; + if (offset < LC && DATA[offset] == OATH_TAG_COUNTER) { + if (LC <= ++offset) EXCEPT(SW_WRONG_LENGTH); + if (4 != DATA[offset++]) EXCEPT(SW_WRONG_DATA); + if ((alg & OATH_TYPE_MASK) != OATH_TYPE_HOTP) EXCEPT(SW_WRONG_DATA); + if (LC < offset + 4) EXCEPT(SW_WRONG_LENGTH); + memcpy(chal + 4, DATA + offset, 4); + offset += 4; + } + + if (LC != offset) EXCEPT(SW_WRONG_LENGTH); + + // find an empty slot to save the record + const int size = get_file_size(OATH_FILE); + if (size < 0) return -1; + const size_t n_records = size / sizeof(OATH_RECORD); + OATH_RECORD record; + size_t unoccupied = n_records; // append by default + for (size_t i = 0; i != n_records; ++i) { + if (read_file(OATH_FILE, &record, i * sizeof(OATH_RECORD), sizeof(OATH_RECORD)) < 0) return -1; + // duplicated name found + if (record.name_len == name_len && memcmp(record.name, name_ptr, name_len) == 0) { + DBG_MSG("dup name\n"); + EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + } + // empty slot found + if (record.name_len == 0 && unoccupied == n_records) unoccupied = i; + } + DBG_MSG("unoccupied=%zu n_records=%zu\n", unoccupied, n_records); + if (unoccupied == n_records && // empty slot not found + unoccupied >= MAX_RECORDS) // number of records exceeded the limit + EXCEPT(SW_NOT_ENOUGH_SPACE); + + record.name_len = name_len; + memcpy(record.name, name_ptr, name_len); + record.key_len = key_len; + memcpy(record.key, key_ptr, key_len); + record.prop = prop; + memcpy(record.challenge, chal, MAX_CHALLENGE_LEN); + return write_file(OATH_FILE, &record, unoccupied * sizeof(OATH_RECORD), sizeof(OATH_RECORD), 0); +} + +static int oath_delete(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + + uint16_t offset = 0; + if (LC < 2) EXCEPT(SW_WRONG_LENGTH); + if (DATA[offset++] != OATH_TAG_NAME) EXCEPT(SW_WRONG_DATA); + const uint8_t name_len = DATA[offset++]; + const uint8_t *name_ptr = &DATA[offset]; + if (name_len > MAX_NAME_LEN || name_len == 0) EXCEPT(SW_WRONG_DATA); + offset += name_len; + if (LC < offset) EXCEPT(SW_WRONG_LENGTH); + + // find and delete the record + const int size = get_file_size(OATH_FILE); + if (size < 0) return -1; + const size_t n_records = size / sizeof(OATH_RECORD); + OATH_RECORD record; + for (size_t i = 0; i != n_records; ++i) { + if (read_file(OATH_FILE, &record, i * sizeof(OATH_RECORD), sizeof(OATH_RECORD)) < 0) return -1; + if (record.name_len == name_len && memcmp(record.name, name_ptr, name_len) == 0) { + uint32_t default_item; + if (read_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &default_item, sizeof(default_item)) < 0) return -1; + if (default_item == i) { // clear the default set if it is to be deleted + default_item = 0xffffffff; + if (write_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &default_item, sizeof(default_item)) < 0) return -1; + } + + record.name_len = 0; + return write_file(OATH_FILE, &record, i * sizeof(OATH_RECORD), sizeof(OATH_RECORD), 0); + } + } + EXCEPT(SW_DATA_INVALID); +} + +static int oath_rename(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + + uint16_t offset = 0; + if (LC <= 2) EXCEPT(SW_WRONG_LENGTH); + if (DATA[offset++] != OATH_TAG_NAME) EXCEPT(SW_WRONG_DATA); + const uint8_t old_name_len = DATA[offset++]; + const uint8_t *old_name_ptr = &DATA[offset]; + if (old_name_len > MAX_NAME_LEN || old_name_len == 0) EXCEPT(SW_WRONG_DATA); + offset += old_name_len; + if (LC <= offset + 2) EXCEPT(SW_WRONG_LENGTH); + if (DATA[offset++] != OATH_TAG_NAME) EXCEPT(SW_WRONG_DATA); + const uint8_t new_name_len = DATA[offset++]; + const uint8_t *new_name_ptr = &DATA[offset]; + if (new_name_len > MAX_NAME_LEN || new_name_len == 0) EXCEPT(SW_WRONG_DATA); + offset += new_name_len; + if (LC < offset) EXCEPT(SW_WRONG_LENGTH); + + // find the record + const int size = get_file_size(OATH_FILE); + if (size < 0) return -1; + const uint32_t n_records = size / sizeof(OATH_RECORD); + uint32_t i, idx_old; + OATH_RECORD record; + for (i = 0, idx_old = n_records; i < n_records; ++i) { + const uint32_t file_offset = i * sizeof(OATH_RECORD); + if (read_file(OATH_FILE, &record, file_offset, sizeof(OATH_RECORD)) < 0) return -1; + if (idx_old == n_records && record.name_len == old_name_len && memcmp(record.name, old_name_ptr, old_name_len) == 0) idx_old = i; + if (record.name_len == new_name_len && memcmp(record.name, new_name_ptr, new_name_len) == 0) { + DBG_MSG("dup name\n"); + EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + } + } + if (idx_old == n_records) EXCEPT(SW_DATA_INVALID); + + // update the name + if (read_file(OATH_FILE, &record, idx_old * sizeof(OATH_RECORD), sizeof(OATH_RECORD)) < 0) return -1; + record.name_len = new_name_len; + memcpy(record.name, new_name_ptr, new_name_len); + return write_file(OATH_FILE, &record, idx_old * sizeof(OATH_RECORD), sizeof(OATH_RECORD), 0); +} + +static int oath_set_code(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + + // check input data first + uint16_t offset = 0; + if (LC == 0) goto clear_code; + if (LC < 2) EXCEPT(SW_WRONG_LENGTH); + if (DATA[offset++] != OATH_TAG_KEY) EXCEPT(SW_WRONG_DATA); + const uint8_t key_len = DATA[offset++]; + const uint8_t *key_ptr = &DATA[offset]; + if (key_len == 0) { // clear the code +clear_code: + is_validated = 1; + return write_attr(OATH_FILE, ATTR_KEY, NULL, 0); + } + if (key_len != KEY_LEN + 1) EXCEPT(SW_WRONG_DATA); + offset += key_len; + if (LC <= offset + 2) EXCEPT(SW_WRONG_LENGTH); + if (DATA[offset++] != OATH_TAG_CHALLENGE) EXCEPT(SW_WRONG_DATA); + const uint8_t chal_len = DATA[offset++]; + const uint8_t *chal_ptr = &DATA[offset]; + offset += chal_len; + if (LC <= offset + 2) EXCEPT(SW_WRONG_LENGTH); + if (DATA[offset++] != OATH_TAG_FULL_RESPONSE) EXCEPT(SW_WRONG_DATA); + const uint8_t resp_len = DATA[offset++]; + const uint8_t *resp_ptr = &DATA[offset]; + if (resp_len != SHA1_DIGEST_LENGTH) EXCEPT(SW_WRONG_DATA); + offset += resp_len; + if (LC != offset) EXCEPT(SW_WRONG_LENGTH); + + // verify the response + uint8_t hmac[SHA1_DIGEST_LENGTH]; + hmac_sha1(key_ptr + 1, KEY_LEN, chal_ptr, chal_len, hmac); + if (memcmp(hmac, resp_ptr, SHA1_DIGEST_LENGTH) != 0) EXCEPT(SW_DATA_INVALID); + + is_validated = 0; + // save the key + return write_attr(OATH_FILE, ATTR_KEY, key_ptr + 1, key_len - 1); +} + +static int oath_validate(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + + // check input data first + uint16_t offset = 0; + if (LC <= 2) EXCEPT(SW_WRONG_LENGTH); + if (DATA[offset++] != OATH_TAG_FULL_RESPONSE) EXCEPT(SW_WRONG_DATA); + const uint8_t resp_len = DATA[offset++]; + const uint8_t *resp_ptr = &DATA[offset]; + if (resp_len != SHA1_DIGEST_LENGTH) EXCEPT(SW_WRONG_DATA); + offset += resp_len; + if (LC <= offset + 2) EXCEPT(SW_WRONG_LENGTH); + if (DATA[offset++] != OATH_TAG_CHALLENGE) EXCEPT(SW_WRONG_DATA); + const uint8_t chal_len = DATA[offset++]; + const uint8_t *chal_ptr = &DATA[offset]; + offset += chal_len; + if (LC != offset) EXCEPT(SW_WRONG_LENGTH); + + // verify the response + uint8_t key[KEY_LEN]; + const int32_t ret = read_attr(OATH_FILE, ATTR_KEY, key, KEY_LEN); + if (ret < 0) return -1; + if (ret == 0) EXCEPT(SW_DATA_INVALID); + uint8_t hmac[SHA1_DIGEST_LENGTH]; + hmac_sha1(key, KEY_LEN, auth_challenge, sizeof(auth_challenge), hmac); + is_validated = memcmp(hmac, resp_ptr, SHA1_DIGEST_LENGTH) == 0; + if (!is_validated) EXCEPT(SW_WRONG_DATA); + + // build the response + hmac_sha1(key, KEY_LEN, chal_ptr, chal_len, hmac); + memzero(key, KEY_LEN); + RDATA[0] = OATH_TAG_FULL_RESPONSE; + RDATA[1] = SHA1_DIGEST_LENGTH; + memcpy(RDATA + 2, hmac, SHA1_DIGEST_LENGTH); + LL = SHA1_DIGEST_LENGTH + 2; + + return 0; +} + +static int oath_list(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + + oath_remaining_type = REMAINING_LIST; + const int size = get_file_size(OATH_FILE); + if (size < 0) return -1; + OATH_RECORD record; + const size_t n_records = size / sizeof(OATH_RECORD); + size_t off = 0; + + while (off < LE) { + if (record_idx >= n_records) { + oath_remaining_type = REMAINING_NONE; + break; + } + if (read_file(OATH_FILE, &record, record_idx * sizeof(OATH_RECORD), sizeof(OATH_RECORD)) < 0) return -1; + if (off + 3 + record.name_len > LE) { // tag (1) + name_len (1) + algo (1) + name + // shouldn't increase the record_idx in this case + SW = 0x61FF; + break; + } + record_idx++; + if (record.name_len == 0) continue; + + RDATA[off++] = OATH_TAG_NAME_LIST; + RDATA[off++] = record.name_len + 1; + RDATA[off++] = record.key[0]; + memcpy(RDATA + off, record.name, record.name_len); + off += record.name_len; + } + LL = off; + + return 0; +} + +static int oath_update_challenge_field(const OATH_RECORD *record, const size_t file_offset) { + return write_file(OATH_FILE, record->challenge, file_offset + (size_t) & ((OATH_RECORD *)0)->challenge, + sizeof(record->challenge), 0); +} + +static int oath_enforce_increasing(OATH_RECORD *record, const size_t file_offset, const uint8_t challenge_len, uint8_t challenge[MAX_CHALLENGE_LEN]) { + if (record->prop & OATH_PROP_INC) { + if (challenge_len != sizeof(record->challenge)) return -1; + DBG_MSG("challenge_len=%u %hhu %hhu\n", challenge_len, record->challenge[7], challenge[7]); + if (memcmp(record->challenge, challenge, sizeof(record->challenge)) > 0) return -2; + memcpy(record->challenge, challenge, sizeof(record->challenge)); + oath_update_challenge_field(record, file_offset); + return 0; + } + return 0; +} + +static int oath_increase_counter(OATH_RECORD *record) { + int i; + for (i = sizeof(record->challenge) - 1; i >= 0; i--) { + record->challenge[i]++; + if (record->challenge[i] != 0) break; + } + return i >= 0 ? 0 : -1; +} + +static uint8_t *oath_digest(const OATH_RECORD *record, uint8_t buffer[SHA512_DIGEST_LENGTH], + const uint8_t challenge_len, uint8_t challenge[MAX_CHALLENGE_LEN], const bool truncated) { + uint8_t digest_length; + if ((record->key[0] & OATH_ALG_MASK) == OATH_ALG_SHA1) { + hmac_sha1(record->key + 2, record->key_len - 2, challenge, challenge_len, buffer); + digest_length = SHA1_DIGEST_LENGTH; + } else if ((record->key[0] & OATH_ALG_MASK) == OATH_ALG_SHA256) { + hmac_sha256(record->key + 2, record->key_len - 2, challenge, challenge_len, buffer); + digest_length = SHA256_DIGEST_LENGTH; + } else { + hmac_sha512(record->key + 2, record->key_len - 2, challenge, challenge_len, buffer); + digest_length = SHA512_DIGEST_LENGTH; + } + if (!truncated) { + return (uint8_t *)(uintptr_t)digest_length; + } + + const uint8_t offset = buffer[digest_length - 1] & 0xF; + buffer[offset] &= 0x7F; + return buffer + offset; +} + +static int oath_calculate_by_offset(const size_t file_offset, uint8_t result[4]) { + if (file_offset % sizeof(OATH_RECORD) != 0) return -2; + const int size = get_file_size(OATH_FILE); + if (size < 0 || file_offset >= (size_t)size) return -2; + uint8_t challenge_len; + uint8_t challenge[MAX_CHALLENGE_LEN]; + OATH_RECORD record; + if (read_file(OATH_FILE, &record, file_offset, sizeof(OATH_RECORD)) < 0) return -1; + + if (record.name_len == 0) { + ERR_MSG("Record deleted\n"); + return -2; + } + if ((record.key[0] & OATH_TYPE_MASK) == OATH_TYPE_TOTP) { + ERR_MSG("TOTP is not supported\n"); + return -1; + } + if ((record.key[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) { + if (oath_increase_counter(&record) < 0) return -1; + oath_update_challenge_field(&record, file_offset); + + challenge_len = sizeof(record.challenge); + memcpy(challenge, record.challenge, challenge_len); + } else { + return -1; + } + + uint8_t hash[SHA512_DIGEST_LENGTH]; + memcpy(result, oath_digest(&record, hash, challenge_len, challenge, true), 4); + return record.key[1]; // the number of digits +} + +static int oath_set_default(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + + uint16_t offset = 0; + if (offset + 1 >= LC) EXCEPT(SW_WRONG_LENGTH); + if (DATA[offset++] != OATH_TAG_NAME) EXCEPT(SW_WRONG_DATA); + const uint8_t name_len = DATA[offset++]; + const uint8_t *name_ptr = &DATA[offset]; + if (name_len > MAX_NAME_LEN || name_len == 0) EXCEPT(SW_WRONG_DATA); + offset += name_len; + if (offset > LC) EXCEPT(SW_WRONG_LENGTH); + + // find the record + const int size = get_file_size(OATH_FILE); + if (size < 0) return -1; + const uint32_t n_records = size / sizeof(OATH_RECORD); + uint32_t i; + uint32_t file_offset; + OATH_RECORD record; + for (i = 0; i != n_records; ++i) { + file_offset = i * sizeof(OATH_RECORD); + if (read_file(OATH_FILE, &record, file_offset, sizeof(OATH_RECORD)) < 0) return -1; + if (record.name_len == name_len && memcmp(record.name, name_ptr, name_len) == 0) break; + } + if (i == n_records) EXCEPT(SW_DATA_INVALID); + if ((record.key[0] & OATH_TYPE_MASK) == OATH_TYPE_TOTP) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + + if (write_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &file_offset, sizeof(file_offset)) < 0) return -1; + return 0; +} + +static int oath_calculate(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || (P2 != 0x00 && P2 != 0x01)) EXCEPT(SW_WRONG_P1P2); + + uint16_t offset = 0; + if (LC <= 2) EXCEPT(SW_WRONG_LENGTH); + if (DATA[offset++] != OATH_TAG_NAME) EXCEPT(SW_WRONG_DATA); + const uint8_t name_len = DATA[offset++]; + if (name_len > MAX_NAME_LEN || name_len == 0) EXCEPT(SW_WRONG_DATA); + offset += name_len; + if (LC < offset) EXCEPT(SW_WRONG_LENGTH); + + // find the record + const int size = get_file_size(OATH_FILE); + if (size < 0) return -1; + const size_t n_records = size / sizeof(OATH_RECORD); + size_t i; + size_t file_offset = 0; + OATH_RECORD record; + for (i = 0; i != n_records; ++i) { + file_offset = i * sizeof(OATH_RECORD); + if (read_file(OATH_FILE, &record, file_offset, sizeof(OATH_RECORD)) < 0) return -1; + if (record.name_len == name_len && memcmp(record.name, DATA + 2, name_len) == 0) break; + } + if (i == n_records) EXCEPT(SW_DATA_INVALID); + + if (record.prop & OATH_PROP_TOUCH) { + if (!is_nfc()) { + switch (wait_for_user_presence(WAIT_ENTRY_CCID)) { + case USER_PRESENCE_CANCEL: + case USER_PRESENCE_TIMEOUT: + EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + default: // USER_PRESENCE_OK + break; + } + } + } + + uint8_t challenge_len; + uint8_t challenge[MAX_CHALLENGE_LEN]; + if ((record.key[0] & OATH_TYPE_MASK) == OATH_TYPE_TOTP) { + if (offset + 1 >= LC) EXCEPT(SW_WRONG_LENGTH); + if (DATA[offset++] != OATH_TAG_CHALLENGE) EXCEPT(SW_WRONG_DATA); + challenge_len = DATA[offset++]; + if (challenge_len > MAX_CHALLENGE_LEN || challenge_len == 0) { + EXCEPT(SW_WRONG_DATA); + } + if (offset + challenge_len > LC) EXCEPT(SW_WRONG_LENGTH); + memcpy(challenge, DATA + offset, challenge_len); + offset += challenge_len; + if (offset > LC) EXCEPT(SW_WRONG_LENGTH); + + if (oath_enforce_increasing(&record, file_offset, challenge_len, challenge) < 0) + EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + } else if ((record.key[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) { + if (oath_increase_counter(&record) < 0) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + oath_update_challenge_field(&record, file_offset); + + challenge_len = sizeof(record.challenge); + memcpy(challenge, record.challenge, challenge_len); + } else { + return -1; + } + + if (P2) { + RDATA[0] = OATH_TAG_RESPONSE; + RDATA[1] = 5; + + uint8_t hash[SHA512_DIGEST_LENGTH]; + memcpy(RDATA + 3, oath_digest(&record, hash, challenge_len, challenge, true), 4); + } else { + RDATA[0] = OATH_TAG_FULL_RESPONSE; + RDATA[1] = 1 + (uint8_t)(uintptr_t)oath_digest(&record, &RDATA[3], challenge_len, challenge, false); + } + RDATA[2] = record.key[1]; + LL = RDATA[1] + 2; + + return 0; +} + +static int oath_calculate_all(const CAPDU *capdu, RAPDU *rapdu) { + static uint8_t challenge_len; + static uint8_t challenge[MAX_CHALLENGE_LEN]; + + if (P2 != 0x00 && P2 != 0x01) EXCEPT(SW_WRONG_P1P2); + + const int size = get_file_size(OATH_FILE); + if (size < 0) return -1; + + // store challenge in the first call + if (record_idx == 0) { + uint16_t off_in = 0; + if (off_in + 1 >= LC) EXCEPT(SW_WRONG_LENGTH); + if (DATA[off_in++] != OATH_TAG_CHALLENGE) EXCEPT(SW_WRONG_DATA); + challenge_len = DATA[off_in++]; + if (challenge_len > MAX_CHALLENGE_LEN || challenge_len == 0) { + challenge_len = 0; + EXCEPT(SW_WRONG_DATA); + } + if (off_in + challenge_len > LC) EXCEPT(SW_WRONG_LENGTH); + memcpy(challenge, DATA + off_in, challenge_len); + off_in += challenge_len; + if (off_in > LC) EXCEPT(SW_WRONG_LENGTH); + oath_remaining_type = P2 ? REMAINING_CALC_TRUNC : REMAINING_CALC_FULL; + } + + OATH_RECORD record; + const size_t n_records = size / sizeof(OATH_RECORD); + size_t off_out = 0; + while (off_out < LE) { + if (record_idx >= n_records) { + oath_remaining_type = REMAINING_NONE; + break; + } + const size_t file_offset = record_idx * sizeof(OATH_RECORD); + if (read_file(OATH_FILE, &record, file_offset, sizeof(OATH_RECORD)) < 0) return -1; + const size_t estimated_len = 2 + record.name_len + 2 + 1 + (oath_remaining_type == REMAINING_CALC_TRUNC ? 4 : SHA512_DIGEST_LENGTH); + if (estimated_len + off_out > LE) { + // shouldn't increase the record_idx in this case + SW = 0x61FF; // more data available + break; + } + record_idx++; + if (record.name_len == 0) continue; + + RDATA[off_out++] = OATH_TAG_NAME; + RDATA[off_out++] = record.name_len; + memcpy(RDATA + off_out, record.name, record.name_len); + off_out += record.name_len; + + if ((record.key[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) { + RDATA[off_out++] = OATH_TAG_NO_RESP; + RDATA[off_out++] = 1; + RDATA[off_out++] = record.key[1]; + continue; + } + if (record.prop & OATH_PROP_TOUCH) { + RDATA[off_out++] = OATH_TAG_REQ_TOUCH; + RDATA[off_out++] = 1; + RDATA[off_out++] = record.key[1]; + continue; + } + + if (oath_enforce_increasing(&record, file_offset, challenge_len, challenge) < 0) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + + if (oath_remaining_type == REMAINING_CALC_TRUNC) { + RDATA[off_out++] = OATH_TAG_RESPONSE; + RDATA[off_out++] = 5; + RDATA[off_out++] = record.key[1]; + + uint8_t hash[SHA512_DIGEST_LENGTH]; + memcpy(RDATA + off_out, oath_digest(&record, hash, challenge_len, challenge, true), 4); + off_out += 4; + } else { + uint8_t *hash = &RDATA[off_out + 3]; + RDATA[off_out++] = OATH_TAG_FULL_RESPONSE; + RDATA[off_out++] = 1 + (uint8_t)(uintptr_t)oath_digest(&record, hash, challenge_len, challenge, false); + RDATA[off_out] = record.key[1]; + off_out += RDATA[off_out - 1]; + } + } + LL = off_out; + + return 0; +} + +static int oath_send_remaining(const CAPDU *capdu, RAPDU *rapdu) { + if (oath_remaining_type == REMAINING_LIST) return oath_list(capdu, rapdu); + if (oath_remaining_type == REMAINING_CALC_FULL || oath_remaining_type == REMAINING_CALC_TRUNC) return oath_calculate_all(capdu, rapdu); + EXCEPT(SW_CONDITIONS_NOT_SATISFIED); +} + +int oath_process_one_touch(char *output, const size_t maxlen) { + uint32_t offset = 0xffffffff, otp_code; + if (read_attr(OATH_FILE, ATTR_DEFAULT_RECORD, &offset, sizeof(offset)) < 0) return -2; + int ret = oath_calculate_by_offset(offset, (uint8_t *)&otp_code); + if (ret < 0) return ret; + if ((size_t)(ret + 1) > maxlen) return -1; + output[ret] = '\0'; + otp_code = htobe32(otp_code); + while (ret--) { + output[ret] = otp_code % 10 + '0'; + otp_code /= 10; + } + return 0; +} + +// ReSharper disable once CppDFAConstantFunctionResult +int oath_process_apdu(const CAPDU *capdu, RAPDU *rapdu) { + LL = 0; + SW = SW_NO_ERROR; + + if (!is_validated && INS != OATH_INS_SELECT && INS != OATH_INS_VALIDATE) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + + int ret; + switch (INS) { + case OATH_INS_PUT: + ret = oath_put(capdu, rapdu); + break; + case OATH_INS_DELETE: + ret = oath_delete(capdu, rapdu); + break; + case OATH_INS_SET_CODE: + ret = oath_set_code(capdu, rapdu); + break; + case OATH_INS_RENAME: + ret = oath_rename(capdu, rapdu); + break; + case OATH_INS_LIST: + record_idx = 0; + ret = oath_list(capdu, rapdu); + break; + case OATH_INS_CALCULATE: + ret = oath_calculate(capdu, rapdu); + break; + case OATH_INS_VALIDATE: + ret = oath_validate(capdu, rapdu); + break; + case OATH_INS_SELECT: + if (P1 == 0x04) { + ret = oath_select(capdu, rapdu); + } else if (P1 == 0x00) { + if (!is_validated) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + record_idx = 0; + ret = oath_calculate_all(capdu, rapdu); + } else { + EXCEPT(SW_WRONG_P1P2); + } + break; + case OATH_INS_SEND_REMAINING: + ret = oath_send_remaining(capdu, rapdu); + break; + case OATH_INS_SET_DEFAULT: + ret = oath_set_default(capdu, rapdu); + break; + default: + EXCEPT(SW_INS_NOT_SUPPORTED); + } + if (ret < 0) EXCEPT(SW_UNABLE_TO_PROCESS); + return 0; +} diff --git a/main/applets/openpgp/key.c b/main/applets/openpgp/key.c new file mode 100644 index 0000000..23ad394 --- /dev/null +++ b/main/applets/openpgp/key.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "key.h" +#include + +#define ATTR_FINGERPRINT 0x00 +#define ATTR_DATETIME 0x01 + +int openpgp_key_get_fingerprint(const char *path, void *buf) { + return read_attr(path, ATTR_FINGERPRINT, buf, KEY_FINGERPRINT_LENGTH); +} + +int openpgp_key_set_fingerprint(const char *path, const void *buf) { + return write_attr(path, ATTR_FINGERPRINT, buf, KEY_FINGERPRINT_LENGTH); +} + +int openpgp_key_get_datetime(const char *path, void *buf) { + return read_attr(path, ATTR_DATETIME, buf, KEY_DATETIME_LENGTH); +} + +int openpgp_key_set_datetime(const char *path, const void *buf) { + return write_attr(path, ATTR_DATETIME, buf, KEY_DATETIME_LENGTH); +} diff --git a/main/applets/openpgp/key.h b/main/applets/openpgp/key.h new file mode 100644 index 0000000..3f4c606 --- /dev/null +++ b/main/applets/openpgp/key.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef CANOKEY_CORE_OPENPGP_KEY_H +#define CANOKEY_CORE_OPENPGP_KEY_H + +#include + +#define KEY_FINGERPRINT_LENGTH 20 +#define KEY_DATETIME_LENGTH 4 +#define MAX_ATTR_LENGTH 13 + +int openpgp_key_get_fingerprint(const char *path, void *buf); +int openpgp_key_set_fingerprint(const char *path, const void *buf); +int openpgp_key_get_datetime(const char *path, void *buf); +int openpgp_key_set_datetime(const char *path, const void *buf); + +#endif // CANOKEY_CORE_OPENPGP_KEY_H diff --git a/main/applets/openpgp/openpgp.c b/main/applets/openpgp/openpgp.c new file mode 100644 index 0000000..acbd9ff --- /dev/null +++ b/main/applets/openpgp/openpgp.c @@ -0,0 +1,1299 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "key.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DATA_PATH "pgp-data" // Content: URL +#define SIG_KEY_PATH "pgp-sigk" +#define DEC_KEY_PATH "pgp-deck" +#define AUT_KEY_PATH "pgp-autk" +#define SIG_CERT_PATH "pgp-sigc" +#define DEC_CERT_PATH "pgp-decc" +#define AUT_CERT_PATH "pgp-autc" + +#define MAX_LOGIN_LENGTH 63 +#define MAX_URL_LENGTH 255 +#define MAX_NAME_LENGTH 39 +#define MAX_LANG_LENGTH 8 +#define MAX_SEX_LENGTH 1 +#define MAX_PIN_LENGTH 64 +#define MAX_CERT_LENGTH 0x480 +#define MAX_DO_LENGTH 0xFF +#define MAX_KEY_TEMPLATE_LENGTH 0x16 +#define DIGITAL_SIG_COUNTER_LENGTH 3 +#define PW_STATUS_LENGTH 7 + +#define ATTR_CA1_FP 0xFF +#define ATTR_CA2_FP 0xFE +#define ATTR_CA3_FP 0xFD +#define ATTR_TERMINATED 0xFC +#define ATTR_TOUCH_CACHE_TIME 0xFB + +#define STATE_NORMAL 0x00 +#define STATE_SELECT_DATA 0x01 +#define STATE_GET_CERT_DATA 0x02 + +// Algorithm ID +#define ALGO_ID_RSA 0x01 +#define ALGO_ID_ECDH 0x12 +#define ALGO_ID_ECDSA 0x13 +#define ALGO_ID_ED25519 0x16 // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-rfc4880bis-08#section-9.1 + +static const uint8_t algo_attr[][12] = { + [SECP256R1] = {9, 0x00, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}, + [SECP256K1] = {6, 0x00, 0x2B, 0x81, 0x04, 0x00, 0x0A}, + [SECP384R1] = {6, 0x00, 0x2B, 0x81, 0x04, 0x00, 0x22}, + [SM2] = {11, 0x00, 0x06, 0x08, 0x2A, 0x81, 0x1C, 0xCF, 0x55, 0x01, 0x82, 0x2D}, + [ED25519] = {10, ALGO_ID_ED25519, 0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01}, + [X25519] = {11, ALGO_ID_ECDH, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01}, + [RSA2048] = {6, ALGO_ID_RSA, 0x08, 0x00, 0x00, 0x20, 0x02}, + [RSA3072] = {6, ALGO_ID_RSA, 0x0C, 0x00, 0x00, 0x20, 0x02}, + [RSA4096] = {6, ALGO_ID_RSA, 0x10, 0x00, 0x00, 0x20, 0x02}, +}; + +// clang-format off +static const uint8_t aid[] = {0xD2, 0x76, 0x00, 0x01, 0x24, 0x01, // aid + 0x03, 0x04, // version + 0xf1, 0xd0, // manufacturer + 0x00, 0x00, 0x00, 0x00, // serial number + 0x00, 0x00}; + +static const uint8_t historical_bytes[] = {0x00, + 0x31, // card services + 0xC5, // Section 6.2 + 0x73, // card capabilities + 0xC0, // full/partial + 0x01, // data coding byte + 0x40, // extended apdu (Section 6.1) + 0x05, 0x90, 0x00}; + +static const uint8_t extended_length_info[] = {0x02, 0x02, HI(APDU_BUFFER_SIZE), LO(APDU_BUFFER_SIZE), + 0x02, 0x02, HI(APDU_BUFFER_SIZE), LO(APDU_BUFFER_SIZE)}; + +static const uint8_t extended_capabilities[] = { + 0x34, // Support key import, pw1 status change, and algorithm attributes changes + 0x00, // No SM algorithm + 0x00, + 0x00, // No challenge support + HI(MAX_CERT_LENGTH), + LO(MAX_CERT_LENGTH), // Cert length + HI(MAX_DO_LENGTH), + LO(MAX_DO_LENGTH), // Other DO length + 0x00, // No PIN block 2 format + 0x00, // No MSE +}; + +// clang-format on +static uint8_t pw1_mode, current_occurrence, state; +static pin_t pw1 = {.min_length = 6, .max_length = MAX_PIN_LENGTH, .is_validated = 0, .path = "pgp-pw1"}; +static pin_t pw3 = {.min_length = 8, .max_length = MAX_PIN_LENGTH, .is_validated = 0, .path = "pgp-pw3"}; +static pin_t rc = {.min_length = 8, .max_length = MAX_PIN_LENGTH, .is_validated = 0, .path = "pgp-rc"}; +static uint8_t touch_cache_time; +static uint32_t last_touch = UINT32_MAX; + +#define PW1_MODE81_ON() pw1_mode |= 1u +#define PW1_MODE81_OFF() pw1_mode &= 0XFEu +#define PW1_MODE81() (pw1_mode & 1u) +#define PW1_MODE82_ON() pw1_mode |= 2u +#define PW1_MODE82_OFF() pw1_mode &= 0XFDu +#define PW1_MODE82() (pw1_mode & 2u) +#define PW_RETRY_COUNTER_DEFAULT 3 + +#define ASSERT_ADMIN() \ + do { \ + if (pw3.is_validated == 0) { \ + EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); \ + } \ + } while (0) + +#define UIF_DISABLED 0 +#define UIF_ENABLED 1 +#define UIF_PERMANENTLY 2 + +#define OPENPGP_TOUCH() \ + do { \ + if (is_nfc()) break; \ + uint32_t current_tick = device_get_tick(); \ + if (current_tick > last_touch && current_tick - last_touch < touch_cache_time * 1000) break; \ + switch (wait_for_user_presence(WAIT_ENTRY_CCID)) { \ + case USER_PRESENCE_CANCEL: \ + case USER_PRESENCE_TIMEOUT: \ + EXCEPT(SW_ERROR_WHILE_RECEIVING); \ + } \ + last_touch = device_get_tick(); \ + } while (0) + +static const char *get_key_path(uint8_t tag) { + switch (tag) { + case 0xB6: + return SIG_KEY_PATH; + case 0xB8: + return DEC_KEY_PATH; + case 0xA4: + return AUT_KEY_PATH; + default: + return NULL; + } +} + +static int reset_sig_counter(void) { + uint8_t buf[3] = {0}; + if (write_attr(DATA_PATH, TAG_DIGITAL_SIG_COUNTER, buf, DIGITAL_SIG_COUNTER_LENGTH) < 0) return -1; + return 0; +} + +static inline int fill_attr(const key_meta_t *meta, uint8_t *buf) { + const uint8_t *attr = algo_attr[meta->type]; + memcpy(buf, attr, attr[0] + 1); + if (IS_SHORT_WEIERSTRASS(meta->type)) { + if (meta->usage == SIGN) + buf[1] = ALGO_ID_ECDSA; + else if (meta->usage == ENCRYPT) + buf[1] = ALGO_ID_ECDH; + else + return -1; + } + return attr[0] + 1; +} + +static inline int get_touch_policy(uint8_t touch_policy) { + switch (touch_policy) { + case TOUCH_POLICY_DEFAULT: + return UIF_DISABLED; + case TOUCH_POLICY_CACHED: + return UIF_ENABLED; + case TOUCH_POLICY_PERMANENT: + return UIF_PERMANENTLY; + default: + return -1; + } +} + +static int UIF_TO_TOUCH_POLICY[3] = {[UIF_DISABLED] = TOUCH_POLICY_DEFAULT, + [UIF_ENABLED] = TOUCH_POLICY_CACHED, + [UIF_PERMANENTLY] = TOUCH_POLICY_PERMANENT}; + +void openpgp_poweroff(void) { + pw1_mode = 0; + pw1.is_validated = 0; + pw3.is_validated = 0; + state = STATE_NORMAL; +} + +int openpgp_install(uint8_t reset) { + openpgp_poweroff(); + if (!reset && get_file_size(DATA_PATH) >= 0) return 0; + + // Cardholder Data + if (write_file(DATA_PATH, NULL, 0, 0, 1) < 0) return -1; + uint8_t terminated = 0x01; // Terminated: yes + if (write_attr(DATA_PATH, ATTR_TERMINATED, &terminated, 1) < 0) return -1; + if (write_attr(DATA_PATH, TAG_LOGIN, NULL, 0) < 0) return -1; + if (write_attr(DATA_PATH, TAG_NAME, NULL, 0)) return -1; + // default lang = NULL + if (write_attr(DATA_PATH, LO(TAG_LANG), NULL, 0) < 0) return -1; + uint8_t default_sex = 0x39; // default sex + if (write_attr(DATA_PATH, LO(TAG_SEX), &default_sex, 1) < 0) return -1; + uint8_t default_pin_strategy = 0x00; // verify PIN every time + if (write_attr(DATA_PATH, TAG_PW_STATUS, &default_pin_strategy, 1) < 0) return -1; + + // Key data, default to RSA2048 + uint8_t buf[20]; + memzero(buf, sizeof(buf)); + ck_key_t key = {.meta.origin = KEY_ORIGIN_NOT_PRESENT, .meta.type = RSA2048}; + + key.meta.usage = SIGN; + if (ck_write_key(SIG_KEY_PATH, &key) < 0) return -1; + if (openpgp_key_set_fingerprint(SIG_KEY_PATH, buf) < 0) return -1; + if (openpgp_key_set_datetime(SIG_KEY_PATH, buf) < 0) return -1; + + key.meta.usage = ENCRYPT; + if (ck_write_key(DEC_KEY_PATH, &key) < 0) return -1; + if (openpgp_key_set_fingerprint(DEC_KEY_PATH, buf) < 0) return -1; + if (openpgp_key_set_datetime(DEC_KEY_PATH, buf) < 0) return -1; + + key.meta.usage = SIGN; + if (ck_write_key(AUT_KEY_PATH, &key) < 0) return -1; + if (openpgp_key_set_fingerprint(AUT_KEY_PATH, buf) < 0) return -1; + if (openpgp_key_set_datetime(AUT_KEY_PATH, buf) < 0) return -1; + + if (write_attr(DATA_PATH, ATTR_CA1_FP, buf, KEY_FINGERPRINT_LENGTH) < 0) return -1; + if (write_attr(DATA_PATH, ATTR_CA2_FP, buf, KEY_FINGERPRINT_LENGTH) < 0) return -1; + if (write_attr(DATA_PATH, ATTR_CA3_FP, buf, KEY_FINGERPRINT_LENGTH) < 0) return -1; + + // Touch policy + touch_cache_time = 0; + if (write_attr(DATA_PATH, ATTR_TOUCH_CACHE_TIME, &touch_cache_time, sizeof(touch_cache_time)) < 0) return -1; + + // Digital Sig Counter + if (reset_sig_counter() < 0) return -1; + + // Certs + if (write_file(SIG_CERT_PATH, NULL, 0, 0, 1) < 0) return -1; + if (write_file(DEC_CERT_PATH, NULL, 0, 0, 1) < 0) return -1; + if (write_file(AUT_CERT_PATH, NULL, 0, 0, 1) < 0) return -1; + + // PIN data + if (pin_create(&pw1, "123456", 6, PW_RETRY_COUNTER_DEFAULT) < 0) return -1; + if (pin_create(&pw3, "12345678", 8, PW_RETRY_COUNTER_DEFAULT) < 0) return -1; + if (pin_create(&rc, NULL, 0, PW_RETRY_COUNTER_DEFAULT) < 0) return -1; + + terminated = 0x00; // Terminated: no + if (write_attr(DATA_PATH, ATTR_TERMINATED, &terminated, 1) < 0) return -1; + + return 0; +} + +static int openpgp_select(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x04 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (LC != 6 || memcmp(DATA, aid, LC) != 0) EXCEPT(SW_FILE_NOT_FOUND); + if (read_attr(DATA_PATH, ATTR_TOUCH_CACHE_TIME, &touch_cache_time, sizeof(touch_cache_time)) < 0) return -1; + return 0; +} + +static int openpgp_get_data(const CAPDU *capdu, RAPDU *rapdu) { + if (LC != 0) EXCEPT(SW_WRONG_LENGTH); + + uint16_t tag = (uint16_t)(P1 << 8u) | P2; + uint16_t off = 0; + int len, retries; + key_meta_t sig_meta, dec_meta, aut_meta; + if (ck_read_key_metadata(SIG_KEY_PATH, &sig_meta) < 0) return -1; + if (ck_read_key_metadata(DEC_KEY_PATH, &dec_meta) < 0) return -1; + if (ck_read_key_metadata(AUT_KEY_PATH, &aut_meta) < 0) return -1; + + switch (tag) { + case TAG_AID: + memcpy(RDATA, aid, sizeof(aid)); + fill_sn(RDATA + 10); + LL = sizeof(aid); + break; + + case TAG_LOGIN: + len = read_attr(DATA_PATH, TAG_LOGIN, RDATA, MAX_LOGIN_LENGTH); + if (len < 0) return -1; + LL = len; + break; + + case TAG_URL: + len = read_file(DATA_PATH, RDATA, 0, MAX_URL_LENGTH); + if (len < 0) return -1; + LL = len; + break; + + case TAG_HISTORICAL_BYTES: + memcpy(RDATA, historical_bytes, sizeof(historical_bytes)); + LL = sizeof(historical_bytes); + break; + + case TAG_CARDHOLDER_RELATED_DATA: + RDATA[off++] = TAG_CARDHOLDER_RELATED_DATA; + RDATA[off++] = 0; // to be filled later + RDATA[off++] = TAG_NAME; + len = read_attr(DATA_PATH, TAG_NAME, RDATA + off + 1, MAX_NAME_LENGTH); + if (len < 0) return -1; + RDATA[off++] = len; + off += len; + + RDATA[off++] = HI(TAG_LANG); + RDATA[off++] = LO(TAG_LANG); + len = read_attr(DATA_PATH, LO(TAG_LANG), RDATA + off + 1, MAX_LANG_LENGTH); + if (len < 0) return -1; + RDATA[off++] = len; + off += len; + + RDATA[off++] = HI(TAG_SEX); + RDATA[off++] = LO(TAG_SEX); + len = read_attr(DATA_PATH, LO(TAG_SEX), RDATA + off + 1, MAX_SEX_LENGTH); + if (len < 0) return -1; + RDATA[off++] = len; + off += len; + RDATA[1] = off - 2; + LL = off; + break; + + case TAG_APPLICATION_RELATED_DATA: + RDATA[off++] = TAG_APPLICATION_RELATED_DATA; + RDATA[off++] = 0x82; // extended length + RDATA[off++] = 0; // to be filled later + RDATA[off++] = 0; // to be filled later + RDATA[off++] = TAG_AID; + RDATA[off++] = sizeof(aid); + memcpy(RDATA + off, aid, sizeof(aid)); + fill_sn(RDATA + off + 10); + off += sizeof(aid); + + RDATA[off++] = HI(TAG_HISTORICAL_BYTES); + RDATA[off++] = LO(TAG_HISTORICAL_BYTES); + RDATA[off++] = sizeof(historical_bytes); + memcpy(RDATA + off, historical_bytes, sizeof(historical_bytes)); + off += sizeof(historical_bytes); + + RDATA[off++] = HI(TAG_EXTENDED_LENGTH_INFO); + RDATA[off++] = LO(TAG_EXTENDED_LENGTH_INFO); + RDATA[off++] = sizeof(extended_length_info); + memcpy(RDATA + off, extended_length_info, sizeof(extended_length_info)); + off += sizeof(extended_length_info); + + RDATA[off++] = HI(TAG_GENERAL_FEATURE_MANAGEMENT); + RDATA[off++] = LO(TAG_GENERAL_FEATURE_MANAGEMENT); + RDATA[off++] = 0x03; + RDATA[off++] = 0x81; + RDATA[off++] = 0x01; + RDATA[off++] = 0x20; // announces a button + + RDATA[off++] = TAG_DISCRETIONARY_DATA_OBJECTS; + RDATA[off++] = 0x82; + uint16_t length_pos = off; + RDATA[off++] = 0; // these two bytes are for length + RDATA[off++] = 0; + + RDATA[off++] = TAG_EXTENDED_CAPABILITIES; + RDATA[off++] = sizeof(extended_capabilities); + memcpy(RDATA + off, extended_capabilities, sizeof(extended_capabilities)); + off += sizeof(extended_capabilities); + + RDATA[off++] = TAG_ALGORITHM_ATTRIBUTES_SIG; + len = fill_attr(&sig_meta, RDATA + off); + if (len < 0) return -1; + off += len; + + RDATA[off++] = TAG_ALGORITHM_ATTRIBUTES_DEC; + len = fill_attr(&dec_meta, RDATA + off); + if (len < 0) return -1; + off += len; + + RDATA[off++] = TAG_ALGORITHM_ATTRIBUTES_AUT; + len = fill_attr(&aut_meta, RDATA + off); + if (len < 0) return -1; + off += len; + + RDATA[off++] = TAG_PW_STATUS; + RDATA[off++] = PW_STATUS_LENGTH; + if (read_attr(DATA_PATH, TAG_PW_STATUS, RDATA + off++, 1) < 0) return -1; + RDATA[off++] = MAX_PIN_LENGTH; + RDATA[off++] = MAX_PIN_LENGTH; + RDATA[off++] = MAX_PIN_LENGTH; + retries = pin_get_retries(&pw1); + if (retries < 0) return -1; + RDATA[off++] = retries; + retries = pin_get_retries(&rc); + if (retries < 0) return -1; + RDATA[off++] = retries; + retries = pin_get_retries(&pw3); + if (retries < 0) return -1; + RDATA[off++] = retries; + + RDATA[off++] = TAG_KEY_FINGERPRINTS; + RDATA[off++] = KEY_FINGERPRINT_LENGTH * 3; + len = openpgp_key_get_fingerprint(SIG_KEY_PATH, RDATA + off); + if (len < 0) return -1; + off += len; + len = openpgp_key_get_fingerprint(DEC_KEY_PATH, RDATA + off); + if (len < 0) return -1; + off += len; + len = openpgp_key_get_fingerprint(AUT_KEY_PATH, RDATA + off); + if (len < 0) return -1; + off += len; + + RDATA[off++] = TAG_CA_FINGERPRINTS; + RDATA[off++] = KEY_FINGERPRINT_LENGTH * 3; + len = read_attr(DATA_PATH, ATTR_CA1_FP, RDATA + off, KEY_FINGERPRINT_LENGTH); + if (len < 0) return -1; + off += len; + len = read_attr(DATA_PATH, ATTR_CA2_FP, RDATA + off, KEY_FINGERPRINT_LENGTH); + if (len < 0) return -1; + off += len; + len = read_attr(DATA_PATH, ATTR_CA3_FP, RDATA + off, KEY_FINGERPRINT_LENGTH); + if (len < 0) return -1; + off += len; + + RDATA[off++] = TAG_KEY_GENERATION_DATES; + RDATA[off++] = KEY_DATETIME_LENGTH * 3; + len = openpgp_key_get_datetime(SIG_KEY_PATH, RDATA + off); + if (len < 0) return -1; + off += len; + len = openpgp_key_get_datetime(DEC_KEY_PATH, RDATA + off); + if (len < 0) return -1; + off += len; + len = openpgp_key_get_datetime(AUT_KEY_PATH, RDATA + off); + if (len < 0) return -1; + off += len; + + RDATA[off++] = TAG_KEY_INFO; + RDATA[off++] = 6; + RDATA[off++] = 0x01; // Key-ref: sig + RDATA[off++] = sig_meta.origin; + RDATA[off++] = 0x02; // Key-ref: dec + RDATA[off++] = dec_meta.origin; + RDATA[off++] = 0x03; // Key-ref: aut + RDATA[off++] = aut_meta.origin; + + RDATA[off++] = TAG_UIF_SIG; + RDATA[off++] = 2; + RDATA[off++] = get_touch_policy(sig_meta.touch_policy); + RDATA[off++] = 0x20; // button + + RDATA[off++] = TAG_UIF_DEC; + RDATA[off++] = 2; + RDATA[off++] = get_touch_policy(dec_meta.touch_policy); + RDATA[off++] = 0x20; // button + + RDATA[off++] = TAG_UIF_AUT; + RDATA[off++] = 2; + RDATA[off++] = get_touch_policy(aut_meta.touch_policy); + RDATA[off++] = 0x20; // button + + uint16_t ddo_length = off - length_pos - 2; + RDATA[length_pos] = HI(ddo_length); + RDATA[length_pos + 1] = LO(ddo_length); + + ddo_length = off - 4; + RDATA[2] = HI(ddo_length); + RDATA[3] = LO(ddo_length); + LL = off; + break; + + case TAG_SECURITY_SUPPORT_TEMPLATE: + RDATA[0] = TAG_SECURITY_SUPPORT_TEMPLATE; + RDATA[1] = DIGITAL_SIG_COUNTER_LENGTH + 2; + RDATA[2] = TAG_DIGITAL_SIG_COUNTER; + RDATA[3] = DIGITAL_SIG_COUNTER_LENGTH; + len = read_attr(DATA_PATH, TAG_DIGITAL_SIG_COUNTER, RDATA + 4, DIGITAL_SIG_COUNTER_LENGTH); + if (len < 0) return -1; + LL = 4 + DIGITAL_SIG_COUNTER_LENGTH; + break; + + case TAG_CARDHOLDER_CERTIFICATE: + if (current_occurrence == 0) + len = read_file(SIG_CERT_PATH, RDATA, 0, MAX_CERT_LENGTH); + else if (current_occurrence == 1) + len = read_file(DEC_CERT_PATH, RDATA, 0, MAX_CERT_LENGTH); + else if (current_occurrence == 2) + len = read_file(AUT_CERT_PATH, RDATA, 0, MAX_CERT_LENGTH); + else + EXCEPT(SW_REFERENCE_DATA_NOT_FOUND); + if (len < 0) return -1; + LL = len; + break; + + case TAG_EXTENDED_LENGTH_INFO: + memcpy(RDATA, extended_length_info, sizeof(extended_length_info)); + LL = sizeof(extended_length_info); + break; + + case TAG_GENERAL_FEATURE_MANAGEMENT: + RDATA[0] = 0x81; + RDATA[1] = 0x01; + RDATA[2] = 0x20; + LL = 3; + break; + + case TAG_PW_STATUS: + if (read_attr(DATA_PATH, TAG_PW_STATUS, RDATA, 1) < 0) return -1; + RDATA[1] = MAX_PIN_LENGTH; + RDATA[2] = MAX_PIN_LENGTH; + RDATA[3] = MAX_PIN_LENGTH; + retries = pin_get_retries(&pw1); + if (retries < 0) return -1; + RDATA[4] = retries; + retries = pin_get_retries(&rc); + if (retries < 0) return -1; + RDATA[5] = retries; + retries = pin_get_retries(&pw3); + if (retries < 0) return -1; + RDATA[6] = retries; + LL = PW_STATUS_LENGTH; + break; + + case TAG_KEY_INFO: + RDATA[0] = 0x01; // Key-ref: sig + RDATA[1] = sig_meta.origin; + RDATA[2] = 0x02; // Key-ref: dec + RDATA[3] = dec_meta.origin; + RDATA[4] = 0x03; // Key-ref: aut + RDATA[5] = aut_meta.origin; + LL = 6; + break; + + case TAG_ALGORITHM_INFORMATION: +#define ALGO_INFO(tag, algo, id) \ + do { \ + RDATA[off++] = tag; \ + const uint8_t *attr = algo_attr[algo]; \ + memcpy(RDATA + off, attr, attr[0] + 1); \ + RDATA[off + 1] = id; \ + off += attr[0] + 1; \ + } while (0) + + // SIG + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_SIG, RSA2048, ALGO_ID_RSA); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_SIG, RSA3072, ALGO_ID_RSA); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_SIG, RSA4096, ALGO_ID_RSA); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_SIG, SECP256R1, ALGO_ID_ECDSA); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_SIG, SECP256K1, ALGO_ID_ECDSA); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_SIG, SECP384R1, ALGO_ID_ECDSA); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_SIG, ED25519, ALGO_ID_ED25519); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_SIG, SM2, ALGO_ID_ECDSA); + // DEC + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_DEC, RSA2048, ALGO_ID_RSA); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_DEC, RSA3072, ALGO_ID_RSA); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_DEC, RSA4096, ALGO_ID_RSA); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_DEC, SECP256R1, ALGO_ID_ECDH); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_DEC, SECP256K1, ALGO_ID_ECDH); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_DEC, SECP384R1, ALGO_ID_ECDH); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_DEC, X25519, ALGO_ID_ECDH); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_DEC, SM2, ALGO_ID_ECDH); + // AUT + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_AUT, RSA2048, ALGO_ID_RSA); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_AUT, RSA3072, ALGO_ID_RSA); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_AUT, RSA4096, ALGO_ID_RSA); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_AUT, SECP256R1, ALGO_ID_ECDSA); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_AUT, SECP256K1, ALGO_ID_ECDSA); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_AUT, SECP384R1, ALGO_ID_ECDSA); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_AUT, ED25519, ALGO_ID_ED25519); + ALGO_INFO(TAG_ALGORITHM_ATTRIBUTES_AUT, SM2, ALGO_ID_ECDSA); + + LL = off; + break; + + case TAG_UIF_CACHE_TIME: + RDATA[0] = touch_cache_time; + LL = 1; + break; + + default: + EXCEPT(SW_REFERENCE_DATA_NOT_FOUND); + } + + return 0; +} + +static int openpgp_verify(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 && P1 != 0xFF) EXCEPT(SW_WRONG_P1P2); + + pin_t *pw; + if (P2 == 0x81) { + pw = &pw1; + PW1_MODE81_OFF(); + } else if (P2 == 0x82) { + pw = &pw1; + PW1_MODE82_OFF(); + } else if (P2 == 0x83) { + pw = &pw3; + } else { + EXCEPT(SW_WRONG_P1P2); + } + + if (P1 == 0xFF) { + pw->is_validated = 0; + return 0; + } + + if (LC == 0) { + if (pw->is_validated) return 0; + int retries = pin_get_retries(pw); + if (retries < 0) return -1; + EXCEPT(SW_PIN_RETRIES + retries); + } + + uint8_t ctr; + int err = pin_verify(pw, DATA, LC, &ctr); + if (err == PIN_IO_FAIL) return -1; + if (err == PIN_LENGTH_INVALID) EXCEPT(SW_WRONG_LENGTH); + if (ctr == 0) EXCEPT(SW_AUTHENTICATION_BLOCKED); + if (err == PIN_AUTH_FAIL) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + if (P2 == 0x81) { + PW1_MODE81_ON(); + } else if (P2 == 0x82) { + PW1_MODE82_ON(); + } + + return 0; +} + +static int openpgp_change_reference_data(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00) EXCEPT(SW_WRONG_P1P2); + + pin_t *pw; + if (P2 == 0x81) { + pw = &pw1; + pw1_mode = 0; + } else if (P2 == 0x83) { + pw = &pw3; + } else { + EXCEPT(SW_WRONG_P1P2); + } + + uint8_t ctr; + int pw_length = pin_get_size(pw); + int err = pin_verify(pw, DATA, (LC < pw_length ? LC : pw_length), &ctr); + if (err == PIN_IO_FAIL) return -1; + if (ctr == 0) EXCEPT(SW_AUTHENTICATION_BLOCKED); + if (err == PIN_AUTH_FAIL) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + if (LC < pw_length) EXCEPT(SW_WRONG_LENGTH); + err = pin_update(pw, DATA + pw_length, LC - pw_length); + if (err == PIN_IO_FAIL) return -1; + if (err == PIN_LENGTH_INVALID) EXCEPT(SW_WRONG_LENGTH); + + return 0; +} + +static int openpgp_reset_retry_counter(const CAPDU *capdu, RAPDU *rapdu) { + if ((P1 != 0x00 && P1 != 0x02) || P2 != 0x81) EXCEPT(SW_WRONG_P1P2); + + int offset, err; + if (P1 == 0x00) { + offset = pin_get_size(&rc); + if (offset == 0) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + uint8_t ctr; + err = pin_verify(&rc, DATA, (LC < offset ? LC : offset), &ctr); + if (err == PIN_IO_FAIL) return -1; + if (ctr == 0) EXCEPT(SW_AUTHENTICATION_BLOCKED); + if (err == PIN_AUTH_FAIL) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + } else { +#ifndef FUZZ + ASSERT_ADMIN(); +#endif + offset = 0; + } + if (LC < offset) EXCEPT(SW_WRONG_LENGTH); + + err = pin_update(&pw1, DATA + offset, LC - offset); + if (err == PIN_IO_FAIL) return -1; + if (err == PIN_LENGTH_INVALID) EXCEPT(SW_WRONG_LENGTH); + + return 0; +} + +static int openpgp_generate_asymmetric_key_pair(const CAPDU *capdu, RAPDU *rapdu) { + if (P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (LC != 0x02 && LC != 0x05) EXCEPT(SW_WRONG_LENGTH); + + const char *key_path = get_key_path(DATA[0]); + if (key_path == NULL) EXCEPT(SW_WRONG_DATA); + + ck_key_t key; + if (ck_read_key(key_path, &key) < 0) return -1; + + if (P1 == 0x80) { + start_quick_blinking(0); + if (ck_generate_key(&key) < 0) { + ERR_MSG("Generate key %s failed\n", key_path); + return -1; + } + if (ck_write_key(key_path, &key) < 0) { + ERR_MSG("Write key %s failed\n", key_path); + return -1; + } + DBG_MSG("Generate key %s successful\n", key_path); + DBG_KEY_META(&key.meta); + } else if (P1 == 0x81) { + if (key.meta.origin == KEY_ORIGIN_NOT_PRESENT) { + DBG_MSG("Generate key %s not set\n", key_path); + memzero(&key, sizeof(key)); + EXCEPT(SW_REFERENCE_DATA_NOT_FOUND); + } + } else { + memzero(&key, sizeof(key)); + EXCEPT(SW_WRONG_P1P2); + } + + RDATA[0] = 0x7F; + RDATA[1] = 0x49; + int len = ck_encode_public_key(&key, &RDATA[2], true); + memzero(&key, sizeof(key)); + if (len < 0) return -1; + LL = len + 2; + if (P1 == 0x80 && strcmp(key_path, SIG_KEY_PATH) == 0) return reset_sig_counter(); + + return 0; +} + +static int openpgp_sign_or_auth(const CAPDU *capdu, RAPDU *rapdu, bool is_sign) { +#ifndef FUZZ + if (is_sign) { + if (PW1_MODE81() == 0) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + } else { + if (PW1_MODE82() == 0) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + } +#endif + + if (is_sign) { + uint8_t pw1_status; + if (read_attr(DATA_PATH, TAG_PW_STATUS, &pw1_status, 1) < 0) return -1; + if (pw1_status == 0x00) PW1_MODE81_OFF(); + } + + const char *key_path = is_sign ? SIG_KEY_PATH : AUT_KEY_PATH; + + ck_key_t key; + if (ck_read_key_metadata(key_path, &key.meta) < 0) { + ERR_MSG("Read metadata failed\n"); + return -1; + } + + if (key.meta.touch_policy == TOUCH_POLICY_CACHED || key.meta.touch_policy == TOUCH_POLICY_PERMANENT) OPENPGP_TOUCH(); + if (key.meta.origin == KEY_ORIGIN_NOT_PRESENT) EXCEPT(SW_REFERENCE_DATA_NOT_FOUND); + if ((key.meta.usage & SIGN) == 0) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + start_quick_blinking(0); + + if (IS_RSA(key.meta.type)) { + if (LC > PUBLIC_KEY_LENGTH[key.meta.type] * 2 / 5) { + DBG_MSG("DigestInfo should be not longer than 40%% of the length of the modulus\n"); + EXCEPT(SW_WRONG_LENGTH); + } + } else if (IS_SHORT_WEIERSTRASS(key.meta.type)) { + if (LC != PRIVATE_KEY_LENGTH[key.meta.type]) { + DBG_MSG("digest should has the same length as the private key\n"); + EXCEPT(SW_WRONG_LENGTH); + } + } + + if (ck_read_key(key_path, &key) < 0) { + ERR_MSG("Read key failed\n"); + return -1; + } + + DBG_KEY_META(&key.meta); + + int len = ck_sign(&key, DATA, LC, RDATA); + if (len < 0) { + ERR_MSG("Sign failed\n"); + return -1; + } + + memzero(&key, sizeof(key)); + LL = len; + + if (is_sign) { + uint8_t ctr[3]; + if (read_attr(DATA_PATH, TAG_DIGITAL_SIG_COUNTER, ctr, DIGITAL_SIG_COUNTER_LENGTH) < 0) { + ERR_MSG("Read sig counter failed\n"); + return -1; + } + for (int i = 3; i > 0; --i) + if (++ctr[i - 1] != 0) break; + if (write_attr(DATA_PATH, TAG_DIGITAL_SIG_COUNTER, ctr, DIGITAL_SIG_COUNTER_LENGTH) < 0) { + ERR_MSG("Write sig counter failed\n"); + return -1; + } + + } + + return 0; +} + +static int openpgp_decipher(const CAPDU *capdu, RAPDU *rapdu) { +#ifndef FUZZ + if (PW1_MODE82() == 0) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); +#endif + + ck_key_t key; + if (ck_read_key_metadata(DEC_KEY_PATH, &key.meta) < 0) return -1; + + if (key.meta.touch_policy == TOUCH_POLICY_CACHED || key.meta.touch_policy == TOUCH_POLICY_PERMANENT) OPENPGP_TOUCH(); + if (key.meta.origin == KEY_ORIGIN_NOT_PRESENT) EXCEPT(SW_REFERENCE_DATA_NOT_FOUND); + if ((key.meta.usage & ENCRYPT) == 0) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + start_quick_blinking(0); + + if (ck_read_key(DEC_KEY_PATH, &key) < 0) { + ERR_MSG("Read DEC key failed\n"); + return -1; + } + + DBG_KEY_META(&key.meta); + + if (IS_RSA(key.meta.type)) { + DBG_MSG("Using RSA key: %d\n", key.meta.type); + + size_t olen; + uint8_t invalid_padding; + + if (LC < PUBLIC_KEY_LENGTH[key.meta.type] + 1) { + DBG_MSG("Incorrect LC\n"); + memzero(&key, sizeof(key)); + EXCEPT(SW_WRONG_LENGTH); + } + if (DATA[0] != 0x00) { // Padding indicator byte (00) for RSA + DBG_MSG("Incorrect padding indicator\n"); + memzero(&key, sizeof(key)); + EXCEPT(SW_WRONG_DATA); + } + + if (rsa_decrypt_pkcs_v15(&key.rsa, DATA + 1, &olen, RDATA, &invalid_padding) < 0) { + ERR_MSG("Decrypt failed\n"); + memzero(&key, sizeof(key)); + if (invalid_padding) EXCEPT(SW_WRONG_DATA); + return -1; + } + + memzero(&key, sizeof(key)); + LL = olen; + } else if (IS_ECC(key.meta.type)) { + DBG_MSG("Using ECC key: %d\n", key.meta.type); + + // check data and length first + // A6 xx Cipher DO + // 7F49 xx Public Key DO + // 86 xx // External Public Key (04 || x || y, for short Weierstrass; x for X25519) + if (LC < 8) { + DBG_MSG("Incorrect LC\n"); + memzero(&key, sizeof(key)); + EXCEPT(SW_WRONG_LENGTH); + } + if (DATA[0] != 0xA6 || DATA[2] != 0x7F || DATA[3] != 0x49 || DATA[5] != 0x86) { + DBG_MSG("Incorrect data\n"); + memzero(&key, sizeof(key)); + EXCEPT(SW_WRONG_DATA); + } + + int public_key_offset; + if (IS_SHORT_WEIERSTRASS(key.meta.type)) { + if (DATA[1] != PUBLIC_KEY_LENGTH[key.meta.type] + 6 || DATA[4] != PUBLIC_KEY_LENGTH[key.meta.type] + 3 || + DATA[6] != PUBLIC_KEY_LENGTH[key.meta.type] + 1 || DATA[7] != 0x04) { + DBG_MSG("Incorrect length data\n"); + memzero(&key, sizeof(key)); + EXCEPT(SW_WRONG_DATA); + } + public_key_offset = 8; + } else { + if (DATA[1] != PUBLIC_KEY_LENGTH[key.meta.type] + 5 || DATA[4] != PUBLIC_KEY_LENGTH[key.meta.type] + 2 || + DATA[6] != PUBLIC_KEY_LENGTH[key.meta.type]) { + DBG_MSG("Incorrect length data\n"); + memzero(&key, sizeof(key)); + EXCEPT(SW_WRONG_DATA); + } + public_key_offset = 7; + } + + if (ecdh(key.meta.type, key.ecc.pri, DATA + public_key_offset, RDATA) < 0) { + ERR_MSG("ECDH failed\n"); + memzero(&key, sizeof(key)); + return -1; + } + + LL = PRIVATE_KEY_LENGTH[key.meta.type]; + memzero(&key, sizeof(key)); + } else { + return -1; + } + + return 0; +} + +static int openpgp_put_data(const CAPDU *capdu, RAPDU *rapdu) { +#ifndef FUZZ + ASSERT_ADMIN(); +#endif + int err; + uint16_t tag = (uint16_t)(P1 << 8u) | P2; + key_meta_t meta; + + switch (tag) { + case TAG_NAME: + if (LC > MAX_NAME_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (write_attr(DATA_PATH, TAG_NAME, DATA, LC) < 0) return -1; + break; + + case TAG_LOGIN: + if (LC > MAX_LOGIN_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (write_attr(DATA_PATH, TAG_LOGIN, DATA, LC) < 0) return -1; + break; + + case TAG_LANG: + if (LC > MAX_LANG_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (write_attr(DATA_PATH, LO(TAG_LANG), DATA, LC) < 0) return -1; + break; + + case TAG_SEX: + if (LC > MAX_SEX_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (write_attr(DATA_PATH, LO(TAG_SEX), DATA, LC) < 0) return -1; + break; + + case TAG_URL: + if (LC > MAX_URL_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (write_file(DATA_PATH, DATA, 0, LC, 1) < 0) return -1; + break; + + case TAG_CARDHOLDER_CERTIFICATE: + if (LC > MAX_CERT_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (current_occurrence == 0) + err = write_file(SIG_CERT_PATH, DATA, 0, LC, 1); + else if (current_occurrence == 1) + err = write_file(DEC_CERT_PATH, DATA, 0, LC, 1); + else if (current_occurrence == 2) + err = write_file(AUT_CERT_PATH, DATA, 0, LC, 1); + else + EXCEPT(SW_REFERENCE_DATA_NOT_FOUND); + if (err < 0) return -1; + current_occurrence = 0; + break; + + case TAG_ALGORITHM_ATTRIBUTES_SIG: + case TAG_ALGORITHM_ATTRIBUTES_DEC: + case TAG_ALGORITHM_ATTRIBUTES_AUT: + if (LC < 1 || LC > MAX_ATTR_LENGTH) EXCEPT(SW_WRONG_LENGTH); + + key_type_t type; + for (type = SECP256R1 /* i.e., 0 */; type < KEY_TYPE_PKC_END; ++type) { + const uint8_t *attr = algo_attr[type]; + if (LC == attr[0]) { + if (DATA[0] == ALGO_ID_RSA) { // For RSA, we only care the nbits + if (DATA[2] != 0) { + DBG_MSG("Invalid attr type\n"); + EXCEPT(SW_WRONG_DATA); + } + if (DATA[1] == 0x08) { + type = RSA2048; + break; + } else if (DATA[1] == 0x0C) { + type = RSA3072; + break; + } else if (DATA[1] == 0x10) { + type = RSA4096; + break; + } else { + DBG_MSG("Invalid attr type\n"); + EXCEPT(SW_WRONG_DATA); + } + } else if (memcmp(&attr[2], &DATA[1], LC - 1) == 0) { // OID + break; + } + } + + } + if (type == KEY_TYPE_PKC_END) { + DBG_MSG("Invalid attr type\n"); + EXCEPT(SW_WRONG_DATA); + } + DBG_MSG("New attr type: %d\n", type); + + const char *key_path = NULL; + if (tag == TAG_ALGORITHM_ATTRIBUTES_SIG) { + key_path = SIG_KEY_PATH; + } else if (tag == TAG_ALGORITHM_ATTRIBUTES_DEC) { + key_path = DEC_KEY_PATH; + } else { + key_path = AUT_KEY_PATH; + } + + if (ck_read_key_metadata(key_path, &meta) < 0) return -1; + if (type == meta.type) { // Key algorithm attribute unchanged + DBG_MSG("Attr unchanged\n"); + break; + } + if (tag == TAG_ALGORITHM_ATTRIBUTES_DEC) { + if (type == ED25519) { + DBG_MSG("DEC key disallows ed25519\n"); + EXCEPT(SW_WRONG_DATA); + } + } else { // TAG_ALGORITHM_ATTRIBUTES_SIG or TAG_ALGORITHM_ATTRIBUTES_AUT + if (type == X25519) { + DBG_MSG("SIG/AUT key disallows x25519\n"); + EXCEPT(SW_WRONG_DATA); + } + } + meta.type = type; + meta.origin = KEY_ORIGIN_NOT_PRESENT; + if (ck_write_key_metadata(key_path, &meta) < 0) return -1; + break; + + case TAG_PW_STATUS: + if (LC != 1) EXCEPT(SW_WRONG_LENGTH); + if (DATA[0] != 0x00 && DATA[0] != 0x01) EXCEPT(SW_WRONG_DATA); + if (write_attr(DATA_PATH, TAG_PW_STATUS, DATA, LC) < 0) return -1; + break; + + case TAG_KEY_SIG_FINGERPRINT: + if (LC != KEY_FINGERPRINT_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (openpgp_key_set_fingerprint(SIG_KEY_PATH, DATA) < 0) return -1; + break; + + case TAG_KEY_DEC_FINGERPRINT: + if (LC != KEY_FINGERPRINT_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (openpgp_key_set_fingerprint(DEC_KEY_PATH, DATA) < 0) return -1; + break; + + case TAG_KEY_AUT_FINGERPRINT: + if (LC != KEY_FINGERPRINT_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (openpgp_key_set_fingerprint(AUT_KEY_PATH, DATA) < 0) return -1; + break; + + case TAG_KEY_CA1_FINGERPRINT: + if (LC != KEY_FINGERPRINT_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (write_attr(DATA_PATH, ATTR_CA1_FP, DATA, KEY_FINGERPRINT_LENGTH) < 0) return -1; + break; + + case TAG_KEY_CA2_FINGERPRINT: + if (LC != KEY_FINGERPRINT_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (write_attr(DATA_PATH, ATTR_CA2_FP, DATA, KEY_FINGERPRINT_LENGTH) < 0) return -1; + break; + + case TAG_KEY_CA3_FINGERPRINT: + if (LC != KEY_FINGERPRINT_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (write_attr(DATA_PATH, ATTR_CA3_FP, DATA, KEY_FINGERPRINT_LENGTH) < 0) return -1; + break; + + case TAG_KEY_SIG_GENERATION_DATES: + if (LC != KEY_DATETIME_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (openpgp_key_set_datetime(SIG_KEY_PATH, DATA) < 0) return -1; + break; + + case TAG_KEY_DEC_GENERATION_DATES: + if (LC != KEY_DATETIME_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (openpgp_key_set_datetime(DEC_KEY_PATH, DATA) < 0) return -1; + break; + + case TAG_KEY_AUT_GENERATION_DATES: + if (LC != KEY_DATETIME_LENGTH) EXCEPT(SW_WRONG_LENGTH); + if (openpgp_key_set_datetime(AUT_KEY_PATH, DATA) < 0) return -1; + break; + + case TAG_RESETTING_CODE: + if ((LC > 0 && LC < rc.min_length) || LC > rc.max_length) EXCEPT(SW_WRONG_LENGTH); + if (LC == 0) { + if (pin_clear(&rc) < 0) return -1; + return 0; + } else { + err = pin_update(&rc, DATA, LC); + if (err == PIN_IO_FAIL) return -1; + if (err == PIN_LENGTH_INVALID) EXCEPT(SW_WRONG_LENGTH); + return 0; + } + + case TAG_UIF_SIG: + if (LC != 2) EXCEPT(SW_WRONG_LENGTH); + if (ck_read_key_metadata(SIG_KEY_PATH, &meta) < 0) return -1; + if (get_touch_policy(meta.touch_policy) == UIF_PERMANENTLY) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + if (DATA[0] > UIF_PERMANENTLY) EXCEPT(SW_WRONG_DATA); + meta.touch_policy = UIF_TO_TOUCH_POLICY[DATA[0]]; + if (ck_write_key_metadata(SIG_KEY_PATH, &meta) < 0) return -1; + break; + + case TAG_UIF_DEC: + if (LC != 2) EXCEPT(SW_WRONG_LENGTH); + if (ck_read_key_metadata(DEC_KEY_PATH, &meta) < 0) return -1; + if (get_touch_policy(meta.touch_policy) == UIF_PERMANENTLY) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + if (DATA[0] > UIF_PERMANENTLY) EXCEPT(SW_WRONG_DATA); + meta.touch_policy = UIF_TO_TOUCH_POLICY[DATA[0]]; + if (ck_write_key_metadata(DEC_KEY_PATH, &meta) < 0) return -1; + break; + + case TAG_UIF_AUT: + if (ck_read_key_metadata(AUT_KEY_PATH, &meta) < 0) return -1; + if (get_touch_policy(meta.touch_policy) == UIF_PERMANENTLY) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + if (DATA[0] > UIF_PERMANENTLY) EXCEPT(SW_WRONG_DATA); + meta.touch_policy = UIF_TO_TOUCH_POLICY[DATA[0]]; + if (ck_write_key_metadata(AUT_KEY_PATH, &meta) < 0) return -1; + break; + + case TAG_UIF_CACHE_TIME: + if (LC != 1) EXCEPT(SW_WRONG_LENGTH); + touch_cache_time = DATA[0]; + if (write_attr(DATA_PATH, ATTR_TOUCH_CACHE_TIME, &touch_cache_time, sizeof(touch_cache_time)) < 0) return -1; + break; + + default: + EXCEPT(SW_WRONG_P1P2); + } + return 0; +} + +static int openpgp_import_key(const CAPDU *capdu, RAPDU *rapdu) { +#ifndef FUZZ + ASSERT_ADMIN(); +#endif + if (P1 != 0x3F || P2 != 0xFF) EXCEPT(SW_WRONG_P1P2); + + // 4D xx Extended Header list + // B6/B8/A4 00/03 Control Reference Template + // Below are processed by ck_parse_openpgp + // 7F48 ... + // 5F48 ... + + const uint8_t *p = DATA; + int fail, len; + size_t length_size; + + // Extended Header list + if (LC < 2) EXCEPT(SW_WRONG_LENGTH); + if (*p++ != 0x4D) EXCEPT(SW_WRONG_DATA); + + len = tlv_get_length_safe(p, LC - 1, &fail, &length_size); + if (fail || len < 2) EXCEPT(SW_WRONG_DATA); + + if (len + length_size + 1 != LC) EXCEPT(SW_WRONG_LENGTH); + p += length_size; + + // Control Reference Template to indicate the private key: B6, B8 or A4 + uint8_t key_ref = *p; + const char *key_path = get_key_path(key_ref); + if (key_path == NULL) EXCEPT(SW_WRONG_DATA); + + // XX 00 or XX 03 84 01 01, XX = B6 / B8 / A4 + ++p; + if (*p != 0x00 && *p != 0x03) EXCEPT(SW_WRONG_DATA); + p += *p + 1; + + ck_key_t key; + if (ck_read_key_metadata(key_path, &key.meta) < 0) return -1; + int err = ck_parse_openpgp(&key, p, LC - (p - DATA)); + if (err == KEY_ERR_LENGTH) EXCEPT(SW_WRONG_LENGTH); + else if (err == KEY_ERR_DATA) EXCEPT(SW_WRONG_DATA); + else if (err < 0) EXCEPT(SW_UNABLE_TO_PROCESS); + if (ck_write_key(key_path, &key) < 0) { + memzero(&key, sizeof(key)); + return -1; + } + memzero(&key, sizeof(key)); + + if (key_ref == 0xB6) return reset_sig_counter(); + return 0; +} + +static int openpgp_select_data(const CAPDU *capdu, RAPDU *rapdu) { + current_occurrence = 0; + if (P1 > 2 || P2 != 0x04) EXCEPT(SW_WRONG_P1P2); + if (LC != 0x06) EXCEPT(SW_WRONG_LENGTH); + if (DATA[0] != 0x60 || DATA[1] != 0x04 || DATA[2] != 0x5C || DATA[3] != 0x02 || DATA[4] != 0x7F || DATA[5] != 0x21) + EXCEPT(SW_WRONG_DATA); + current_occurrence = P1; + return 0; +} + +static int openpgp_get_next_data(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x7F || P2 != 0x21) EXCEPT(SW_WRONG_P1P2); + if (LC > 0) EXCEPT(SW_WRONG_LENGTH); + int len; + ++current_occurrence; + if (current_occurrence == 1) { + len = read_file(DEC_CERT_PATH, RDATA, 0, MAX_CERT_LENGTH); + } else if (current_occurrence == 2) { + len = read_file(AUT_CERT_PATH, RDATA, 0, MAX_CERT_LENGTH); + } else { + EXCEPT(SW_REFERENCE_DATA_NOT_FOUND); + } + if (len < 0) return -1; + LL = len; + return 0; +} + +static int openpgp_terminate(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + int retries = pin_get_retries(&pw3); + if (retries < 0) return -1; + if (retries > 0) ASSERT_ADMIN(); + uint8_t terminated = 1; + if (write_attr(DATA_PATH, ATTR_TERMINATED, &terminated, 1) < 0) return -1; + return 0; +} + +static int openpgp_activate(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + return openpgp_install(1); +} + +int openpgp_process_apdu(const CAPDU *capdu, RAPDU *rapdu) { + LL = 0; + SW = SW_NO_ERROR; + if (CLA != 0x00) EXCEPT(SW_CLA_NOT_SUPPORTED); + + if (INS == OPENPGP_INS_SELECT_DATA) { + state = STATE_SELECT_DATA; + } else if (state == STATE_NORMAL) { + if (INS == OPENPGP_INS_GET_NEXT_DATA) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + if (INS == OPENPGP_INS_GET_DATA && P1 == 0x7F && P2 == 0x21) { + state = STATE_GET_CERT_DATA; + } + } else if (state == STATE_SELECT_DATA) { + if (INS == OPENPGP_INS_GET_NEXT_DATA) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + if (INS == OPENPGP_INS_GET_DATA && P1 == 0x7F && P2 == 0x21) { + state = STATE_GET_CERT_DATA; + } else { + if (INS != OPENPGP_INS_PUT_DATA || P1 != 0x7F || P2 != 0x21) current_occurrence = 0; + state = STATE_NORMAL; + } + } else { + if (INS != OPENPGP_INS_GET_NEXT_DATA) { + current_occurrence = 0; + state = STATE_NORMAL; + } + } + + uint8_t terminated; + if (read_attr(DATA_PATH, ATTR_TERMINATED, &terminated, 1) < 0) EXCEPT(SW_UNABLE_TO_PROCESS); +#ifndef FUZZ + if (terminated == 1 && INS != OPENPGP_INS_ACTIVATE && INS != OPENPGP_INS_SELECT) EXCEPT(SW_TERMINATED); +#endif + + int ret; + switch (INS) { + case OPENPGP_INS_SELECT: + ret = openpgp_select(capdu, rapdu); + break; + case OPENPGP_INS_ACTIVATE: + if (terminated == 0) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + ret = openpgp_activate(capdu, rapdu); + break; + case OPENPGP_INS_GET_DATA: + ret = openpgp_get_data(capdu, rapdu); + break; + case OPENPGP_INS_SELECT_DATA: + ret = openpgp_select_data(capdu, rapdu); + break; + case OPENPGP_INS_GET_NEXT_DATA: + ret = openpgp_get_next_data(capdu, rapdu); + break; + case OPENPGP_INS_VERIFY: + ret = openpgp_verify(capdu, rapdu); + break; + case OPENPGP_INS_CHANGE_REFERENCE_DATA: + ret = openpgp_change_reference_data(capdu, rapdu); + break; + case OPENPGP_INS_RESET_RETRY_COUNTER: + ret = openpgp_reset_retry_counter(capdu, rapdu); + break; + case OPENPGP_INS_PUT_DATA: + ret = openpgp_put_data(capdu, rapdu); + break; + case OPENPGP_INS_IMPORT_KEY: + ret = openpgp_import_key(capdu, rapdu); + break; + case OPENPGP_INS_GENERATE_ASYMMETRIC_KEY_PAIR: + ret = openpgp_generate_asymmetric_key_pair(capdu, rapdu); + stop_blinking(); + break; + case OPENPGP_INS_PSO: + if (P1 == 0x9E && P2 == 0x9A) { + ret = openpgp_sign_or_auth(capdu, rapdu, true); + stop_blinking(); + break; + } + if (P1 == 0x80 && P2 == 0x86) { + ret = openpgp_decipher(capdu, rapdu); + stop_blinking(); + break; + } + EXCEPT(SW_WRONG_P1P2); + case OPENPGP_INS_INTERNAL_AUTHENTICATE: + ret = openpgp_sign_or_auth(capdu, rapdu, false); + stop_blinking(); + break; + case OPENPGP_INS_TERMINATE: + ret = openpgp_terminate(capdu, rapdu); + break; + default: + EXCEPT(SW_INS_NOT_SUPPORTED); + } + + if (ret < 0) EXCEPT(SW_UNABLE_TO_PROCESS); + return 0; +} diff --git a/main/applets/piv/piv.c b/main/applets/piv/piv.c new file mode 100644 index 0000000..e520cde --- /dev/null +++ b/main/applets/piv/piv.c @@ -0,0 +1,1209 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// data object path +#define MAX_DO_PATH_LEN 9 +#define PIV_AUTH_CERT_PATH "piv-pauc" // 9A +#define SIG_CERT_PATH "piv-sigc" // 9C +#define CARD_AUTH_CERT_PATH "piv-cauc" // 9E +#define KEY_MANAGEMENT_CERT_PATH "piv-mntc" // 9D +#define KEY_MANAGEMENT_82_CERT_PATH "piv-82c" // 82 +#define KEY_MANAGEMENT_83_CERT_PATH "piv-83c" // 83 +#define CHUID_PATH "piv-chu" // card holder uid +#define CCC_PATH "piv-ccc" // card capability container +#define PI_PATH "piv-pi" // printed information + +// key tags and path +#define TAG_PIN_KEY_DEFAULT 0x81 // DO if pin or admin key is default +#define AUTH_KEY_PATH "piv-pauk" // 9A +#define SIG_KEY_PATH "piv-sigk" // 9C +#define CARD_AUTH_KEY_PATH "piv-cauk" // 9E +#define KEY_MANAGEMENT_KEY_PATH "piv-mntk" // 9D +#define KEY_MANAGEMENT_82_KEY_PATH "piv-82" // 82 +#define KEY_MANAGEMENT_83_KEY_PATH "piv-83" // 83 +#define CARD_ADMIN_KEY_PATH "piv-admk" // 9B + +// alg +#define ALGORITHM_EXT_CONFIG_PATH "piv-alg" +#define ALG_DEFAULT 0x00 +#define ALG_TDEA_3KEY 0x03 +#define ALG_RSA_2048 0x07 +#define ALG_ECC_256 0x11 +#define ALG_ECC_384 0x14 +#define ALG_ED25519_DEFAULT 0x22 // defined in https://github.com/go-piv/piv-go/pull/69 +#define ALG_RSA_3072_DEFAULT 0x05 // defined in NIST SP 800-78-5 (Initial Public Draft) +#define ALG_RSA_4096_DEFAULT 0x51 +#define ALG_X25519_DEFAULT 0x52 +#define ALG_SECP256K1_DEFAULT 0x53 +#define ALG_SM2_DEFAULT 0x54 + +#define TDEA_BLOCK_SIZE 8 + +// tags for general auth +#define TAG_WITNESS 0x80 +#define TAG_CHALLENGE 0x81 +#define TAG_RESPONSE 0x82 +#define TAG_EXP 0x85 +#define IDX_WITNESS (TAG_WITNESS - 0x80) +#define IDX_CHALLENGE (TAG_CHALLENGE - 0x80) +#define IDX_RESPONSE (TAG_RESPONSE - 0x80) +#define IDX_EXP (TAG_EXP - 0x80) + +// offsets for auth +#define OFFSET_AUTH_STATE 0 +#define OFFSET_AUTH_CHALLENGE 1 +#define LENGTH_CHALLENGE 16 +#define LENGTH_AUTH_STATE (1 + LENGTH_CHALLENGE) + +// states for auth +#define AUTH_STATE_NONE 0 +#define AUTH_STATE_EXTERNAL 1 +#define AUTH_STATE_MUTUAL 2 + +#define PIV_TOUCH(cached) \ + do { \ + if (is_nfc()) break; \ + uint32_t current_tick = device_get_tick(); \ + if ((cached) && current_tick > last_touch && current_tick - last_touch < 15000) break; \ + switch (wait_for_user_presence(WAIT_ENTRY_CCID)) { \ + case USER_PRESENCE_CANCEL: \ + case USER_PRESENCE_TIMEOUT: \ + EXCEPT(SW_ERROR_WHILE_RECEIVING); \ + } \ + last_touch = device_get_tick(); \ + } while (0) + +static const uint8_t DEFAULT_MGMT_KEY[] = {1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}; +static const char *DEFAULT_PIN = "123456\xFF\xFF"; +static const char *DEFAULT_PUK = "12345678"; + +static const uint8_t rid[] = {0xA0, 0x00, 0x00, 0x03, 0x08}; +static const uint8_t pix[] = {0x00, 0x00, 0x10, 0x00, 0x01, 0x00}; +static const uint8_t pin_policy[] = {0x40, 0x10}; +static uint8_t auth_ctx[LENGTH_AUTH_STATE]; +static uint8_t in_admin_status; +static uint8_t pin_is_consumed; +static char piv_do_path[MAX_DO_PATH_LEN]; // data object file path during chaining read/write +static int piv_do_write; // -1: not in chaining write, otherwise: count of remaining bytes +static int piv_do_read; // -1: not in chaining read mode, otherwise: data object offset +static uint32_t last_touch = UINT32_MAX; +static piv_algorithm_extension_config_t alg_ext_cfg; + +static pin_t pin = {.min_length = 8, .max_length = 8, .is_validated = 0, .path = "piv-pin"}; +static pin_t puk = {.min_length = 8, .max_length = 8, .is_validated = 0, .path = "piv-puk"}; + +static void authenticate_reset(void) { + auth_ctx[OFFSET_AUTH_STATE] = AUTH_STATE_NONE; + memset(auth_ctx + OFFSET_AUTH_CHALLENGE, 0, LENGTH_CHALLENGE); +} + +static int create_key(const char *path, const key_usage_t usage, const pin_policy_t pin_policy) { + const ck_key_t key = {.meta = {.type = KEY_TYPE_PKC_END, + .origin = KEY_ORIGIN_NOT_PRESENT, + .usage = usage, + .pin_policy = pin_policy, + .touch_policy = TOUCH_POLICY_NEVER}}; + if (ck_write_key(path, &key) < 0) { + return -1; + } + return 0; +} + +static key_type_t algo_id_to_key_type(const uint8_t id) { + switch (id) { + case ALG_ECC_256: + return SECP256R1; + case ALG_ECC_384: + return SECP384R1; + case ALG_RSA_2048: + return RSA2048; + case ALG_DEFAULT: + case ALG_TDEA_3KEY: + return TDEA; + default: + break; + } + + if (alg_ext_cfg.enabled == 0) return KEY_TYPE_PKC_END; + if (id == alg_ext_cfg.ed25519) return ED25519; + if (id == alg_ext_cfg.rsa3072) return RSA3072; + if (id == alg_ext_cfg.rsa4096) return RSA4096; + if (id == alg_ext_cfg.x25519) return X25519; + if (id == alg_ext_cfg.secp256k1) return SECP256K1; + if (id == alg_ext_cfg.sm2) return SM2; + return KEY_TYPE_PKC_END; +} + +static uint8_t key_type_to_algo_id(const key_type_t type) { + switch (type) { + case SECP256R1: + return ALG_ECC_256; + case SECP384R1: + return ALG_ECC_384; + case RSA2048: + return ALG_RSA_2048; + case ED25519: + return alg_ext_cfg.ed25519; + case X25519: + return alg_ext_cfg.x25519; + case SECP256K1: + return alg_ext_cfg.secp256k1; + case SM2: + return alg_ext_cfg.sm2; + case RSA3072: + return alg_ext_cfg.rsa3072; + case RSA4096: + return alg_ext_cfg.rsa4096; + case TDEA: + return ALG_TDEA_3KEY; + case KEY_TYPE_PKC_END: + default: + return ALG_DEFAULT; + } +} + +int piv_security_status_check(uint8_t id __attribute__((unused)), const key_meta_t *meta) { + switch (meta->pin_policy) { + case PIN_POLICY_NEVER: + break; + default: + case PIN_POLICY_ONCE: + if (pin.is_validated == 0) return 1; + break; + case PIN_POLICY_ALWAYS: + if (pin.is_validated == 0 || pin_is_consumed) return 1; + break; + } + pin_is_consumed = 1; + return 0; +} + +void piv_poweroff(void) { + in_admin_status = 0; + pin_is_consumed = 0; + pin.is_validated = 0; + puk.is_validated = 0; + piv_do_write = -1; + piv_do_read = -1; + piv_do_path[0] = '\0'; +} + +int piv_install(const uint8_t reset) { + piv_poweroff(); + if (!reset && get_file_size(ALGORITHM_EXT_CONFIG_PATH) >= 0) { + if (read_file(ALGORITHM_EXT_CONFIG_PATH, &alg_ext_cfg, 0, sizeof(alg_ext_cfg)) < 0) return -1; + return 0; + } + + // objects + if (write_file(PIV_AUTH_CERT_PATH, NULL, 0, 0, 1) < 0) return -1; + if (write_file(SIG_CERT_PATH, NULL, 0, 0, 1) < 0) return -1; + if (write_file(KEY_MANAGEMENT_CERT_PATH, NULL, 0, 0, 1) < 0) return -1; + if (write_file(CARD_AUTH_CERT_PATH, NULL, 0, 0, 1) < 0) return -1; + if (write_file(KEY_MANAGEMENT_82_CERT_PATH, NULL, 0, 0, 1) < 0) return -1; + if (write_file(KEY_MANAGEMENT_83_CERT_PATH, NULL, 0, 0, 1) < 0) return -1; + if (write_file(PI_PATH, NULL, 0, 0, 1) < 0) return -1; + uint8_t ccc_tpl[] = {0x53, 0x33, 0xf0, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x01, 0x21, + 0xf2, 0x01, 0x21, 0xf3, 0x00, 0xf4, 0x01, 0x00, 0xf5, 0x01, 0x10, 0xf6, 0x00, 0xf7, + 0x00, 0xfa, 0x00, 0xfb, 0x00, 0xfc, 0x00, 0xfd, 0x00, 0xfe, 0x00}; + random_buffer(ccc_tpl + 4, 21); + if (write_file(CCC_PATH, ccc_tpl, 0, sizeof(ccc_tpl), 1) < 0) return -1; + uint8_t chuid_tpl[] = {0x53, 0x3b, 0x30, 0x19, 0xd4, 0xe7, 0x39, 0xda, 0x73, 0x9c, 0xed, 0x39, 0xce, 0x73, 0x9d, 0x83, + 0x68, 0x58, 0x21, 0x08, 0x42, 0x10, 0x84, 0x21, 0xc8, 0x42, 0x10, 0xc3, 0xeb, 0x34, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, + 0x08, 0x32, 0x30, 0x35, 0x30, 0x30, 0x31, 0x30, 0x31, 0x3e, 0x00, 0xfe, 0x00}; + random_buffer(chuid_tpl + 31, 16); + if (write_file(CHUID_PATH, chuid_tpl, 0, sizeof(chuid_tpl), 1) < 0) return -1; + + // keys + if (create_key(AUTH_KEY_PATH, SIGN, PIN_POLICY_ONCE) < 0) return -1; + if (create_key(SIG_KEY_PATH, SIGN, PIN_POLICY_ONCE) < 0) return -1; + if (create_key(KEY_MANAGEMENT_KEY_PATH, KEY_AGREEMENT, PIN_POLICY_ONCE) < 0) return -1; + if (create_key(CARD_AUTH_KEY_PATH, SIGN, PIN_POLICY_NEVER) < 0) return -1; + if (create_key(KEY_MANAGEMENT_82_KEY_PATH, KEY_AGREEMENT, PIN_POLICY_ONCE) < 0) return -1; + if (create_key(KEY_MANAGEMENT_83_KEY_PATH, KEY_AGREEMENT, PIN_POLICY_ONCE) < 0) return -1; + + // TDEA admin key + ck_key_t admin_key = {.meta = {.type = TDEA, + .origin = KEY_ORIGIN_GENERATED, + .usage = ENCRYPT, + .pin_policy = PIN_POLICY_NEVER, + .touch_policy = TOUCH_POLICY_NEVER}}; + memcpy(admin_key.data, DEFAULT_MGMT_KEY, 24); + if (ck_write_key(CARD_ADMIN_KEY_PATH, &admin_key) < 0) { + return -1; + } + const uint8_t tmp = 0x01; + if (write_attr(CARD_ADMIN_KEY_PATH, TAG_PIN_KEY_DEFAULT, &tmp, sizeof(tmp)) < 0) return -1; + + // PIN data + if (pin_create(&pin, DEFAULT_PIN, 8, 3) < 0) return -1; + if (write_attr(pin.path, TAG_PIN_KEY_DEFAULT, &tmp, sizeof(tmp)) < 0) return -1; + if (pin_create(&puk, DEFAULT_PUK, 8, 3) < 0) return -1; + if (write_attr(puk.path, TAG_PIN_KEY_DEFAULT, &tmp, sizeof(tmp)) < 0) return -1; + + // Algorithm extensions + alg_ext_cfg.enabled = 1; + alg_ext_cfg.ed25519 = ALG_ED25519_DEFAULT; + alg_ext_cfg.rsa3072 = ALG_RSA_3072_DEFAULT; + alg_ext_cfg.rsa4096 = ALG_RSA_4096_DEFAULT; + alg_ext_cfg.x25519 = ALG_X25519_DEFAULT; + alg_ext_cfg.secp256k1 = ALG_SECP256K1_DEFAULT; + alg_ext_cfg.sm2 = ALG_SM2_DEFAULT; + if (write_file(ALGORITHM_EXT_CONFIG_PATH, &alg_ext_cfg, 0, sizeof(alg_ext_cfg), 1) < 0) return -1; + + return 0; +} + +static const char *get_object_path_by_tag(const uint8_t tag) { + // Part 1 Table 3 0x5FC1XX + switch (tag) { + case 0x05: // X.509 Certificate for PIV Authentication + return PIV_AUTH_CERT_PATH; + case 0x0A: // X.509 Certificate for Digital Signature + return SIG_CERT_PATH; + case 0x0B: // X.509 Certificate for Key Management + return KEY_MANAGEMENT_CERT_PATH; + case 0x01: // X.509 Certificate for Card Authentication + return CARD_AUTH_CERT_PATH; + case 0x0D: // Retired X.509 Certificate for Key Management 1 + return KEY_MANAGEMENT_82_CERT_PATH; + case 0x0E: // Retired X.509 Certificate for Key Management 2 + return KEY_MANAGEMENT_83_CERT_PATH; + case 0x02: // Card Holder Unique Identifier + return CHUID_PATH; + case 0x07: // Card Capability Container + return CCC_PATH; + case 0x09: // Printed Information + return PI_PATH; + default: + return NULL; + } +} + +static uint16_t get_capacity_by_tag(const uint8_t tag) { + // Part 1 Table 7 Container Minimum Capacity, 5FC1XX + switch (tag) { + case 0x05: // X.509 Certificate for PIV Authentication (9A) + case 0x0A: // X.509 Certificate for Digital Signature (9C) + case 0x0B: // X.509 Certificate for Key Management (9D) + case 0x01: // X.509 Certificate for Card Authentication (9E) + case 0x0D: // Retired X.509 Certificate for Key Management 1 (82) + case 0x0E: // Retired X.509 Certificate for Key Management 2 (83) + return 3000; + case 0x02: // Card Holder Unique Identifier + return 2916; + case 0x07: // Card Capability Container + return 287; + case 0x09: // Printed Information + return 245; + default: + return 0; + } +} + +static int piv_select(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x04 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + + // reset internal states + in_admin_status = 0; + pin_is_consumed = 0; + pin.is_validated = 0; + puk.is_validated = 0; + piv_do_write = -1; + piv_do_read = -1; + authenticate_reset(); + + RDATA[0] = 0x61; + RDATA[1] = 6 + sizeof(pix) + sizeof(rid); + RDATA[2] = 0x4F; + RDATA[3] = sizeof(pix); + memcpy(RDATA + 4, pix, sizeof(pix)); + RDATA[4 + sizeof(pix)] = 0x79; + RDATA[5 + sizeof(pix)] = 2 + sizeof(rid); + RDATA[6 + sizeof(pix)] = 0x4F; + RDATA[7 + sizeof(pix)] = sizeof(rid); + memcpy(RDATA + 8 + sizeof(pix), rid, sizeof(rid)); + LL = 8 + sizeof(pix) + sizeof(rid); + + return 0; +} + +static int piv_get_large_data(const CAPDU *capdu, RAPDU *rapdu, const char *path, const int size) { + // piv_do_read should equal to -1 before calling this function + + const int read = read_file(path, RDATA, 0, LE); // return first chunk + if (read < 0) { + return -1; + } + LL = read; + DBG_MSG("read file %s, expected: %d, read: %d\n", path, LE, read); + const int remains = size - read; + if (remains == 0) { // sent all + SW = SW_NO_ERROR; + } else { + // save state for GET REPONSE command + strcpy(piv_do_path, path); + piv_do_read = read; + if (remains > 0xFF) + SW = 0x61FF; + else + SW = 0x6100 + remains; + } + return 0; +} + +/* + * Command Data: + * --------------------------------------------- + * Name Tag Value + * --------------------------------------------- + * Tag List 5C Tag to read + * 0x7E for Discovery Object + * 0x7F61 for BIT, ignore + * 0x5FC1xx for others + * --------------------------------------------- + */ +static int piv_get_data(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x3F || P2 != 0xFF) EXCEPT(SW_WRONG_P1P2); + if (LC < 2) EXCEPT(SW_WRONG_LENGTH); + if (DATA[0] != 0x5C) EXCEPT(SW_WRONG_DATA); + if (DATA[1] + 2 != LC) EXCEPT(SW_WRONG_LENGTH); + if (DATA[1] == 1) { + if (DATA[2] != 0x7E) EXCEPT(SW_FILE_NOT_FOUND); + // For the Discovery Object, the 0x7E template nests two data elements: + // 1) tag 0x4F contains the AID of the PIV Card Application and + // 2) tag 0x5F2F lists the PIN Usage Policy. + RDATA[0] = 0x7E; + RDATA[1] = 5 + sizeof(rid) + sizeof(pix) + sizeof(pin_policy); + RDATA[2] = 0x4F; + RDATA[3] = sizeof(rid) + sizeof(pix); + memcpy(RDATA + 4, rid, sizeof(rid)); + memcpy(RDATA + 4 + sizeof(rid), pix, sizeof(pix)); + RDATA[4 + sizeof(rid) + sizeof(pix)] = 0x5F; + RDATA[5 + sizeof(rid) + sizeof(pix)] = 0x2F; + RDATA[6 + sizeof(rid) + sizeof(pix)] = sizeof(pin_policy); + memcpy(RDATA + 7 + sizeof(rid) + sizeof(pix), pin_policy, sizeof(pin_policy)); + LL = 7 + sizeof(rid) + sizeof(pix) + sizeof(pin_policy); + } else if (DATA[1] == 3) { + if (LC != 5 || DATA[2] != 0x5F || DATA[3] != 0xC1) EXCEPT(SW_FILE_NOT_FOUND); + // Printed Information requires PIN verification + if (DATA[4] == 0x09 && !pin.is_validated) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + const char *path = get_object_path_by_tag(DATA[4]); + if (path == NULL) EXCEPT(SW_FILE_NOT_FOUND); + const int size = get_file_size(path); + if (size < 0) { + return -1; + } + if (size == 0) EXCEPT(SW_FILE_NOT_FOUND); + return piv_get_large_data(capdu, rapdu, path, size); + } else + EXCEPT(SW_FILE_NOT_FOUND); + return 0; +} + +static int piv_get_data_response(const CAPDU *capdu, RAPDU *rapdu) { + if (piv_do_read == -1) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + if (piv_do_path[0] == '\0') return -1; + + const int size = get_file_size(piv_do_path); + if (size < 0) { + return -1; + } + const int read = read_file(piv_do_path, RDATA, piv_do_read, LE); + if (read < 0) { + return -1; + } + DBG_MSG("continue to read file %s, expected: %d, read: %d\n", piv_do_path, LE, read); + LL = read; + piv_do_read += read; + + const int remains = size - piv_do_read; + if (remains == 0) { // sent all + piv_do_read = -1; + piv_do_path[0] = '\0'; + SW = SW_NO_ERROR; + } else if (remains > 0xFF) + SW = 0x61FF; + else + SW = 0x6100 + remains; + + return 0; +} + +static int piv_verify(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 && P1 != 0xFF) EXCEPT(SW_WRONG_P1P2); + if (P2 != 0x80) EXCEPT(SW_REFERENCE_DATA_NOT_FOUND); + if (P1 == 0xFF) { + if (LC != 0) EXCEPT(SW_WRONG_LENGTH); + pin.is_validated = 0; + pin_is_consumed = 0; + return 0; + } + if (LC == 0) { + if (pin.is_validated) return 0; + const int retries = pin_get_retries(&pin); + if (retries < 0) return -1; + EXCEPT(SW_PIN_RETRIES + retries); + } + if (LC != 8) EXCEPT(SW_WRONG_LENGTH); + uint8_t ctr; + const int err = pin_verify(&pin, DATA, 8, &ctr); + if (err == PIN_IO_FAIL) return -1; + if (ctr == 0) EXCEPT(SW_AUTHENTICATION_BLOCKED); + if (err == PIN_AUTH_FAIL) EXCEPT(SW_PIN_RETRIES + ctr); + pin_is_consumed = 0; + return 0; +} + +static int piv_change_reference_data(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00) EXCEPT(SW_WRONG_P1P2); + pin_t *p; + const char *default_val; + if (P2 == 0x80) + p = &pin, default_val = DEFAULT_PIN; + else if (P2 == 0x81) + p = &puk, default_val = DEFAULT_PUK; + else + EXCEPT(SW_REFERENCE_DATA_NOT_FOUND); + if (LC != 16) EXCEPT(SW_WRONG_LENGTH); + uint8_t ctr; + int err = pin_verify(p, DATA, 8, &ctr); + if (err == PIN_IO_FAIL) return -1; + if (ctr == 0) EXCEPT(SW_AUTHENTICATION_BLOCKED); + if (err == PIN_AUTH_FAIL) EXCEPT(SW_PIN_RETRIES + ctr); + err = pin_update(p, DATA + 8, 8); + if (err == PIN_IO_FAIL) return -1; + if (err == PIN_LENGTH_INVALID) EXCEPT(SW_WRONG_LENGTH); + const uint8_t is_default = !memcmp(DATA + 8, default_val, 8); + if (write_attr(p->path, TAG_PIN_KEY_DEFAULT, &is_default, sizeof(is_default)) < 0) return -1; + return 0; +} + +static int piv_reset_retry_counter(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (P2 != 0x80) EXCEPT(SW_REFERENCE_DATA_NOT_FOUND); + if (LC != 16) EXCEPT(SW_WRONG_LENGTH); + uint8_t ctr; + int err = pin_verify(&puk, DATA, 8, &ctr); + if (err == PIN_IO_FAIL) return -1; + if (ctr == 0) EXCEPT(SW_AUTHENTICATION_BLOCKED); + if (err == PIN_AUTH_FAIL) EXCEPT(0x63C0 + ctr); + err = pin_update(&pin, DATA + 8, 8); + if (err == PIN_IO_FAIL) return -1; + if (err == PIN_LENGTH_INVALID) EXCEPT(SW_WRONG_LENGTH); + return 0; +} + +static const char *get_key_path(const uint8_t id) { + switch (id) { + case 0x9A: + return AUTH_KEY_PATH; + case 0x9B: + return CARD_ADMIN_KEY_PATH; + case 0x9C: + return SIG_KEY_PATH; + case 0x9D: + return KEY_MANAGEMENT_KEY_PATH; + case 0x9E: + return CARD_AUTH_KEY_PATH; + case 0x82: + return KEY_MANAGEMENT_82_KEY_PATH; + case 0x83: + return KEY_MANAGEMENT_83_KEY_PATH; + default: + return NULL; + } +} + +static int piv_general_authenticate(const CAPDU *capdu, RAPDU *rapdu) { + if (LC == 0) EXCEPT(SW_WRONG_LENGTH); + if (*DATA != 0x7C) EXCEPT(SW_WRONG_DATA); + + const char *key_path = get_key_path(P2); + if (key_path == NULL) EXCEPT(SW_WRONG_P1P2); + + ck_key_t key; + if (P2 == 0x9B) { // Card admin + if (P1 != ALG_DEFAULT && P1 != ALG_TDEA_3KEY) { + DBG_MSG("Invalid P1/P2 for card admin key\n"); + EXCEPT(SW_WRONG_P1P2); + } + } else if (P2 != 0x9A && P2 != 0x9C && P2 != 0x9D && P2 != 0x9E && P2 != 0x82 && P2 != 0x83) { + DBG_MSG("Invalid key ref\n"); + EXCEPT(SW_REFERENCE_DATA_NOT_FOUND); + } + if (ck_read_key_metadata(key_path, &key.meta) < 0) { + return -1; + } + DBG_KEY_META(&key.meta); + + // empty slot after reset + if (key.meta.type == KEY_TYPE_PKC_END) EXCEPT(SW_CONDITIONS_NOT_SATISFIED); + if (algo_id_to_key_type(P1) != key.meta.type) { + DBG_MSG("The value of P1 mismatches the key specified by P2\n"); + EXCEPT(SW_WRONG_P1P2); + } + + uint16_t pos[6] = {0}, len[6] = {0}; + int fail = 0; + size_t length_size = 0; + tlv_get_length_safe(DATA + 1, LC - 1, &fail, &length_size); + if (fail) EXCEPT(SW_WRONG_LENGTH); + uint16_t dat_pos = 1 + length_size; + while (dat_pos < LC) { + const uint8_t tag = DATA[dat_pos++]; + if (tag != 0x80 && tag != 0x81 && tag != 0x82 && tag != 0x85) EXCEPT(SW_WRONG_DATA); + len[tag - 0x80] = tlv_get_length_safe(DATA + dat_pos, LC - dat_pos, &fail, &length_size); + if (fail) EXCEPT(SW_WRONG_LENGTH); + dat_pos += length_size; + pos[tag - 0x80] = dat_pos; + dat_pos += len[tag - 0x80]; + DBG_MSG("Tag %02X, pos: %d, len: %d\n", tag, pos[tag - 0x80], len[tag - 0x80]); + } + + // User presence test + if (key.meta.touch_policy == TOUCH_POLICY_CACHED || key.meta.touch_policy == TOUCH_POLICY_ALWAYS) PIV_TOUCH(key.meta.touch_policy == TOUCH_POLICY_CACHED); + + // + // CASE 1 - INTERNAL AUTHENTICATE (Key ID = 9A / 9E) + // Authenticates the CARD to the CLIENT and is also used for KEY ESTABLISHMENT + // and DIGITAL SIGNATURES. Documented in SP800-73-4 Part 2 Appendix A.3 + // + // OR - Signature Generation (Key ID = 9C) + // Documented in SP800-73-4 Part 2 Appendix A.4 + // + // OR - KEY ESTABLISHMENT (Key ID = 9D, RSA only) + // Documented in SP800-73-4 Part 2 Appendix A.5 + // + + // > Client application sends a challenge to the PIV Card Application + if (pos[IDX_WITNESS] == 0 && pos[IDX_CHALLENGE] > 0 && len[IDX_CHALLENGE] > 0 && pos[IDX_RESPONSE] > 0 && + len[IDX_RESPONSE] == 0) { + DBG_MSG("Case 1\n"); + authenticate_reset(); +#ifndef FUZZ + if (piv_security_status_check(P2, &key.meta) != 0) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); +#endif + + if ((IS_SHORT_WEIERSTRASS(key.meta.type) && len[IDX_CHALLENGE] > PRIVATE_KEY_LENGTH[key.meta.type]) || + (IS_RSA(key.meta.type) && len[IDX_CHALLENGE] != PUBLIC_KEY_LENGTH[key.meta.type])) { + DBG_MSG("Incorrect challenge data length\n"); + EXCEPT(SW_WRONG_LENGTH); + } + if (ck_read_key(key_path, &key) < 0) { + return -1; + } + DBG_KEY_META(&key.meta); + + start_quick_blinking(0); + + if (IS_RSA(key.meta.type)) { + // The input has been padded + DBG_MSG("e: "); + PRINT_HEX(key.rsa.e, E_LENGTH); + DBG_MSG("p: "); + PRINT_HEX(key.rsa.p, PRIVATE_KEY_LENGTH[key.meta.type]); + DBG_MSG("q: "); + PRINT_HEX(key.rsa.q, PRIVATE_KEY_LENGTH[key.meta.type]); + if (rsa_private(&key.rsa, DATA + pos[IDX_CHALLENGE], RDATA + 8) < 0) { + ERR_MSG("Sign failed\n"); + memzero(&key, sizeof(key)); + return -1; + } + RDATA[0] = 0x7C; + RDATA[1] = 0x82; + RDATA[2] = HI(SIGNATURE_LENGTH[key.meta.type] + 4); + RDATA[3] = LO(SIGNATURE_LENGTH[key.meta.type] + 4); + RDATA[4] = TAG_RESPONSE; + RDATA[5] = 0x82; + RDATA[6] = HI(SIGNATURE_LENGTH[key.meta.type]); + RDATA[7] = LO(SIGNATURE_LENGTH[key.meta.type]); + LL = SIGNATURE_LENGTH[key.meta.type] + 8; + + memzero(&key, sizeof(key)); + } else if (IS_ECC(key.meta.type)) { + if (IS_SHORT_WEIERSTRASS(key.meta.type)) { + // prepend zeros + memmove(DATA + pos[IDX_CHALLENGE] + (PRIVATE_KEY_LENGTH[key.meta.type] - len[IDX_CHALLENGE]), + DATA + pos[IDX_CHALLENGE], + len[IDX_CHALLENGE]); + memzero(DATA + pos[IDX_CHALLENGE], PRIVATE_KEY_LENGTH[key.meta.type] - len[IDX_CHALLENGE]); + } + int sig_len = ck_sign(&key, DATA + pos[IDX_CHALLENGE], PRIVATE_KEY_LENGTH[key.meta.type], RDATA + 4); + if (sig_len < 0) { + ERR_MSG("Sign failed\n"); + return -1; + } + + if (IS_SHORT_WEIERSTRASS(key.meta.type)) { + sig_len = (int) ecdsa_sig2ansi(PRIVATE_KEY_LENGTH[key.meta.type], RDATA + 4, RDATA + 4); + } + + RDATA[0] = 0x7C; + RDATA[1] = sig_len + 2; + RDATA[2] = TAG_RESPONSE; + RDATA[3] = sig_len; + LL = sig_len + 4; + + memzero(&key, sizeof(key)); + } else { + return -1; + } + } + + // + // CASE 2 - EXTERNAL AUTHENTICATE REQUEST + // Authenticates the HOST to the CARD + // + + // > Client application requests a challenge from the PIV Card Application. + else if (pos[IDX_CHALLENGE] > 0 && len[IDX_CHALLENGE] == 0) { + DBG_MSG("Case 2\n"); + authenticate_reset(); + in_admin_status = 0; + + if (P2 != 0x9B) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + + RDATA[0] = 0x7C; + RDATA[1] = TDEA_BLOCK_SIZE + 2; + RDATA[2] = TAG_CHALLENGE; + RDATA[3] = TDEA_BLOCK_SIZE; + random_buffer(RDATA + 4, TDEA_BLOCK_SIZE); + LL = TDEA_BLOCK_SIZE + 4; + + auth_ctx[OFFSET_AUTH_STATE] = AUTH_STATE_EXTERNAL; + + if (ck_read_key(key_path, &key) < 0) { + return -1; + } + DBG_KEY_META(&key.meta); + + if (tdes_enc(RDATA + 4, auth_ctx + OFFSET_AUTH_CHALLENGE, key.data) < 0) { + memzero(&key, sizeof(key)); + return -1; + } + memzero(&key, sizeof(key)); + } + + // + // CASE 3 - EXTERNAL AUTHENTICATE RESPONSE + // + + // > Client application requests a challenge from the PIV Card Application. + else if (pos[IDX_RESPONSE] > 0 && len[IDX_RESPONSE] > 0) { + DBG_MSG("Case 3\n"); + if (auth_ctx[OFFSET_AUTH_STATE] != AUTH_STATE_EXTERNAL || + P2 != 0x9B || + TDEA_BLOCK_SIZE != len[IDX_RESPONSE] || + memcmp(auth_ctx + OFFSET_AUTH_CHALLENGE, DATA + pos[IDX_RESPONSE], TDEA_BLOCK_SIZE) != 0) { + authenticate_reset(); + EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + } + + authenticate_reset(); + in_admin_status = 1; + } + + // + // CASE 4 - MUTUAL AUTHENTICATE REQUEST + // + + // > Client application requests a WITNESS from the PIV Card Application. + else if (pos[IDX_WITNESS] > 0 && len[IDX_WITNESS] == 0) { + DBG_MSG("Case 4\n"); + authenticate_reset(); + in_admin_status = 0; + + if (P2 != 0x9B) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + + auth_ctx[OFFSET_AUTH_STATE] = AUTH_STATE_MUTUAL; + random_buffer(auth_ctx + OFFSET_AUTH_CHALLENGE, TDEA_BLOCK_SIZE); + + RDATA[0] = 0x7C; + RDATA[1] = TDEA_BLOCK_SIZE + 2; + RDATA[2] = TAG_WITNESS; + RDATA[3] = TDEA_BLOCK_SIZE; + LL = TDEA_BLOCK_SIZE + 4; + + if (ck_read_key(key_path, &key) < 0) { + return -1; + } + DBG_KEY_META(&key.meta); + + if (tdes_enc(auth_ctx + OFFSET_AUTH_CHALLENGE, RDATA + 4, key.data) < 0) { + memzero(&key, sizeof(key)); + return -1; + } + memzero(&key, sizeof(key)); + } + + // + // CASE 5 - MUTUAL AUTHENTICATE RESPONSE + // + + // > Client application returns the decrypted witness referencing the original + // algorithm key reference + else if (pos[IDX_WITNESS] > 0 && len[IDX_WITNESS] > 0 && pos[IDX_CHALLENGE] > 0 && len[IDX_CHALLENGE] > 0) { + DBG_MSG("Case 5\n"); + if (auth_ctx[OFFSET_AUTH_STATE] != AUTH_STATE_MUTUAL || + P2 != 0x9B || + TDEA_BLOCK_SIZE != len[IDX_WITNESS] || + memcmp(auth_ctx + OFFSET_AUTH_CHALLENGE, DATA + pos[IDX_WITNESS], TDEA_BLOCK_SIZE) != 0) { + authenticate_reset(); + EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + } + + if (TDEA_BLOCK_SIZE != len[IDX_CHALLENGE]) { + authenticate_reset(); + EXCEPT(SW_WRONG_LENGTH); + } + + RDATA[0] = 0x7C; + RDATA[1] = TDEA_BLOCK_SIZE + 2; + RDATA[2] = TAG_RESPONSE; + RDATA[3] = TDEA_BLOCK_SIZE; + LL = TDEA_BLOCK_SIZE + 4; + + if (ck_read_key(key_path, &key) < 0) { + return -1; + } + DBG_KEY_META(&key.meta); + + if (tdes_enc(DATA + pos[IDX_CHALLENGE], RDATA + 4, key.data) < 0) { + memzero(&key, sizeof(key)); + return -1; + } + memzero(&key, sizeof(key)); + + authenticate_reset(); + in_admin_status = 1; + } + + // + // CASE 6 - ECDH with the PIV KMK + // Documented in SP800-73-4 Part 2 Appendix A.5 + // + + else if (pos[IDX_RESPONSE] > 0 && len[IDX_RESPONSE] == 0 && pos[IDX_EXP] > 0 && len[IDX_EXP] > 0) { + DBG_MSG("Case 6\n"); + authenticate_reset(); +#ifndef FUZZ + if (piv_security_status_check(P2, &key.meta) != 0) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); +#endif + + if (len[IDX_EXP] != PUBLIC_KEY_LENGTH[key.meta.type] + (IS_SHORT_WEIERSTRASS(key.meta.type) ? 1 : 0)) { + DBG_MSG("Incorrect data length\n"); + EXCEPT(SW_WRONG_DATA); + } + if (ck_read_key(key_path, &key) < 0) { + return -1; + } + DBG_KEY_META(&key.meta); + + start_quick_blinking(0); + + if (ecdh(key.meta.type, key.ecc.pri, DATA + pos[IDX_EXP] + (IS_SHORT_WEIERSTRASS(key.meta.type) ? 1 : 0), RDATA + 4) < 0) { + ERR_MSG("ECDH failed\n"); + memzero(&key, sizeof(key)); + return -1; + } + + RDATA[0] = 0x7C; + RDATA[1] = PRIVATE_KEY_LENGTH[key.meta.type] + 2; + RDATA[2] = TAG_RESPONSE; + RDATA[3] = PRIVATE_KEY_LENGTH[key.meta.type]; + LL = PRIVATE_KEY_LENGTH[key.meta.type] + 4; + + memzero(&key, sizeof(key)); + } + + // + // INVALID CASE + // + else { + authenticate_reset(); + EXCEPT(SW_WRONG_DATA); + } + + return 0; +} + +static int piv_put_data(const CAPDU *capdu, RAPDU *rapdu) { +#ifndef FUZZ + if (!in_admin_status) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); +#endif + + if (P1 != 0x3F || P2 != 0xFF) EXCEPT(SW_WRONG_P1P2); + + if (piv_do_write == -1) { // not in chaining write + if (LC < 5) EXCEPT(SW_WRONG_LENGTH); + const int size = LC - 5; + if (DATA[0] != 0x5C) EXCEPT(SW_WRONG_DATA); + // Part 1 Table 3 0x5FC1XX + if (DATA[1] != 3 || DATA[2] != 0x5F || DATA[3] != 0xC1) EXCEPT(SW_FILE_NOT_FOUND); + const char *path = get_object_path_by_tag(DATA[4]); + const int max_len = get_capacity_by_tag(DATA[4]); + if (path == NULL) EXCEPT(SW_FILE_NOT_FOUND); + if (size > max_len) EXCEPT(SW_WRONG_LENGTH); + DBG_MSG("write file %s, first chunk length %d\n", path, size); + const int rc = write_file(path, DATA + 5, 0, size, 1); + if (rc < 0) { + return -1; + } + if ((CLA & 0x10) != 0 && size < max_len) { + // enter chaining write mode + piv_do_write = max_len - size; + strcpy(piv_do_path, path); + } + } else { + // piv_do_path should be valid + if (piv_do_path[0] == '\0') return -1; + // data length exceeded, terminate chaining write + if (LC > piv_do_write) { + piv_do_write = -1; + piv_do_path[0] = '\0'; + EXCEPT(SW_WRONG_LENGTH); + } + piv_do_write -= LC; + + DBG_MSG("write file %s, continuous chunk length %d\n", piv_do_path, LC); + const int rc = append_file(piv_do_path, DATA, LC); + if (rc < 0) { + return -1; + } + if ((CLA & 0x10) == 0) { // last chunk + piv_do_write = -1; + piv_do_path[0] = '\0'; + } + } + + return 0; +} + +static int piv_generate_asymmetric_key_pair(const CAPDU *capdu, RAPDU *rapdu) { +#ifndef FUZZ + if (!in_admin_status) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); +#endif + if (LC < 5) { + DBG_MSG("Wrong length\n"); + EXCEPT(SW_WRONG_LENGTH); + } + if (P1 != 0x00 || (P2 != 0x9A && P2 != 0x9C && P2 != 0x9D && P2 != 0x9E && P2 != 0x82 && P2 != 0x83) || DATA[0] != 0xAC || DATA[2] != 0x80 || + DATA[3] != 0x01) { + DBG_MSG("Wrong P1/P2 or tags\n"); + EXCEPT(SW_WRONG_DATA); + } + + const char *key_path = get_key_path(P2); + ck_key_t key; + if (ck_read_key(key_path, &key) < 0) { + return -1; + } + + key.meta.type = algo_id_to_key_type(DATA[4]); + if (key.meta.type == KEY_TYPE_PKC_END) EXCEPT(SW_WRONG_DATA); + start_quick_blinking(0); + if (ck_generate_key(&key) < 0) { + return -1; + } + const int err = ck_parse_piv_policies(&key, &DATA[5], LC - 5); + if (err != 0) { + DBG_MSG("Wrong metadata\n"); + memzero(&key, sizeof(key)); + EXCEPT(SW_WRONG_DATA); + } + if (ck_write_key(key_path, &key) < 0) { + return -1; + } + DBG_MSG("Generate key %s successful\n", key_path); + DBG_KEY_META(&key.meta); + + RDATA[0] = 0x7F; + RDATA[1] = 0x49; + const int len = ck_encode_public_key(&key, &RDATA[2], true); + memzero(&key, sizeof(key)); + if (len < 0) return -1; + LL = len + 2; + + return 0; +} + +static int piv_set_management_key(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0xFF || P2 != 0xFF) EXCEPT(SW_WRONG_P1P2); + if (LC != 27) EXCEPT(SW_WRONG_LENGTH); + if (DATA[0] != 0x03 || DATA[1] != 0x9B || DATA[2] != 24) EXCEPT(SW_WRONG_DATA); +#ifndef FUZZ + if (!in_admin_status) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); +#endif + if (write_file(CARD_ADMIN_KEY_PATH, DATA + 3, 0, 24, 1) < 0) return -1; + const uint8_t is_default = !memcmp(DATA + 3, DEFAULT_MGMT_KEY, 24); + if (write_attr(CARD_ADMIN_KEY_PATH, TAG_PIN_KEY_DEFAULT, &is_default, sizeof(is_default)) < 0) return -1; + return 0; +} + +static int piv_reset(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (LC != 0) EXCEPT(SW_WRONG_LENGTH); + if (pin_get_retries(&pin) > 0 || pin_get_retries(&puk) > 0) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); + piv_install(1); + return 0; +} + +static int piv_import_asymmetric_key(const CAPDU *capdu, RAPDU *rapdu) { +#ifndef FUZZ + if (!in_admin_status) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); +#endif + const char *key_path = get_key_path(P2); + if (key_path == NULL || P2 == 0x9B) { + DBG_MSG("Unknown key file\n"); + EXCEPT(SW_WRONG_P1P2); + } + ck_key_t key; + if (ck_read_key(key_path, &key) < 0) { + return -1; + } + + key.meta.type = algo_id_to_key_type(P1); + if (key.meta.type == KEY_TYPE_PKC_END) EXCEPT(SW_WRONG_P1P2); + + const int err = ck_parse_piv(&key, DATA, LC); + if (err == KEY_ERR_LENGTH) { + DBG_MSG("Wrong length when importing\n"); + EXCEPT(SW_WRONG_LENGTH); + } + if (err == KEY_ERR_DATA) { + DBG_MSG("Wrong data when importing\n"); + EXCEPT(SW_WRONG_DATA); + } + if (err < 0) { + DBG_MSG("Error when importing\n"); + EXCEPT(SW_UNABLE_TO_PROCESS); + } + if (ck_write_key(key_path, &key) < 0) { + memzero(&key, sizeof(key)); + return -1; + } + memzero(&key, sizeof(key)); + + return 0; +} + +static int piv_get_metadata(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (LC != 0) EXCEPT(SW_WRONG_LENGTH); + + int pos = 0; + switch (P2) { + case 0x80: // PIN + case 0x81: // PUK + { + const pin_t *p = P2 == 0x80 ? &pin : &puk; + uint8_t default_value; + if (read_attr(p->path, TAG_PIN_KEY_DEFAULT, &default_value, 1) < 0) return -1; + const int default_retries = pin_get_default_retries(p); + const int retries = pin_get_retries(p); + if (retries < 0) return -1; + + RDATA[pos++] = 0x01; // Algorithm + RDATA[pos++] = 0x01; + RDATA[pos++] = 0xFF; + RDATA[pos++] = 0x05; + RDATA[pos++] = 0x01; + RDATA[pos++] = default_value; + RDATA[pos++] = 0x06; + RDATA[pos++] = 0x02; + RDATA[pos++] = default_retries; + RDATA[pos++] = retries; + break; + } + case 0x9B: // Management + { + uint8_t default_value; + if (read_attr(CARD_ADMIN_KEY_PATH, TAG_PIN_KEY_DEFAULT, &default_value, 1) < 0) return -1; + RDATA[pos++] = 0x01; // Algorithm + RDATA[pos++] = 0x01; + RDATA[pos++] = 0x03; + RDATA[pos++] = 0x02; // Policy + RDATA[pos++] = 0x02; + RDATA[pos++] = 0x00; + RDATA[pos++] = 0x01; + RDATA[pos++] = 0x05; + RDATA[pos++] = 0x01; + RDATA[pos++] = default_value; + break; + } + case 0x9A: // Authentication + case 0x9C: // Signing + case 0x9D: // Key Management + case 0x9E: // Card Authentication + case 0x82: // Retired Key Management 1 + case 0x83: // Retired Key Management 2 + { + const char *key_path = get_key_path(P2); + if (key_path == NULL) { + DBG_MSG("Key file not found\n"); + EXCEPT(SW_WRONG_P1P2); + } + + ck_key_t key; + if (ck_read_key(key_path, &key) < 0) { + return -1; + } + DBG_KEY_META(&key.meta); + if (key.meta.type == KEY_TYPE_PKC_END) EXCEPT(SW_REFERENCE_DATA_NOT_FOUND); + + RDATA[pos++] = 0x01; // Algorithm + RDATA[pos++] = 0x01; + RDATA[pos++] = key_type_to_algo_id(key.meta.type); + RDATA[pos++] = 0x02; // Policy + RDATA[pos++] = 0x02; + RDATA[pos++] = key.meta.pin_policy; + RDATA[pos++] = key.meta.touch_policy; + RDATA[pos++] = 0x03; // Origin + RDATA[pos++] = 0x01; + RDATA[pos++] = key.meta.origin; + RDATA[pos++] = 0x04; // Public + const int len = ck_encode_public_key(&key, &RDATA[pos], true); + if (len < 0) { + memzero(&key, sizeof(key)); + return -1; + } + pos += len; + memzero(&key, sizeof(key)); + break; + } + default: + EXCEPT(SW_REFERENCE_DATA_NOT_FOUND); + } + + LL = pos; + + return 0; +} + +static int piv_get_version(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (LC != 0) EXCEPT(SW_WRONG_LENGTH); + RDATA[0] = 0x05; + RDATA[1] = 0x04; + RDATA[2] = 0x00; + LL = 3; + return 0; +} + +static int piv_get_serial(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + if (LC != 0) EXCEPT(SW_WRONG_LENGTH); + fill_sn(RDATA); + LL = 4; + return 0; +} + +static int piv_algorithm_extension(const CAPDU *capdu, RAPDU *rapdu) { +#ifndef FUZZ + if (!in_admin_status) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); +#endif + + if (P1 != 0x01 && P1 != 0x02) EXCEPT(SW_WRONG_P1P2); + if (P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + + if (P1 == 0x01) { + if (read_file(ALGORITHM_EXT_CONFIG_PATH, RDATA, 0, sizeof(alg_ext_cfg)) < 0) return -1; + LL = sizeof(alg_ext_cfg); + } else { + if (LC != sizeof(alg_ext_cfg)) EXCEPT(SW_WRONG_LENGTH); + if (DATA[0] != 0 && DATA[0] != 1) EXCEPT(SW_WRONG_DATA); + // We trust the rest data because no dangerous result will be caused even if the IDs are not unique. + if (write_file(ALGORITHM_EXT_CONFIG_PATH, DATA, 0, sizeof(alg_ext_cfg), 1) < 0) return -1; + // Effective immediately + memcpy(&alg_ext_cfg, DATA, sizeof(alg_ext_cfg)); + } + + return 0; +} + +int piv_process_apdu(const CAPDU *capdu, RAPDU *rapdu) { + LL = 0; + SW = SW_NO_ERROR; + if (!(CLA == 0x00 || (CLA == 0x10 && INS == PIV_INS_PUT_DATA))) EXCEPT(SW_CLA_NOT_SUPPORTED); + + if (INS != PIV_INS_PUT_DATA) piv_do_write = -1; + if (INS != PIV_INS_GET_DATA_RESPONSE) piv_do_read = -1; + + int ret; + switch (INS) { + case PIV_INS_SELECT: + ret = piv_select(capdu, rapdu); + break; + case PIV_INS_GET_DATA: + ret = piv_get_data(capdu, rapdu); + break; + case PIV_INS_GET_DATA_RESPONSE: + ret = piv_get_data_response(capdu, rapdu); + break; + case PIV_INS_VERIFY: + ret = piv_verify(capdu, rapdu); + break; + case PIV_INS_CHANGE_REFERENCE_DATA: + ret = piv_change_reference_data(capdu, rapdu); + break; + case PIV_INS_RESET_RETRY_COUNTER: + ret = piv_reset_retry_counter(capdu, rapdu); + break; + case PIV_INS_GENERAL_AUTHENTICATE: + ret = piv_general_authenticate(capdu, rapdu); + stop_blinking(); + break; + case PIV_INS_PUT_DATA: + ret = piv_put_data(capdu, rapdu); + break; + case PIV_INS_GENERATE_ASYMMETRIC_KEY_PAIR: + ret = piv_generate_asymmetric_key_pair(capdu, rapdu); + stop_blinking(); + break; + case PIV_INS_SET_MANAGEMENT_KEY: + ret = piv_set_management_key(capdu, rapdu); + break; + case PIV_INS_RESET: + ret = piv_reset(capdu, rapdu); + break; + case PIV_INS_IMPORT_ASYMMETRIC_KEY: + ret = piv_import_asymmetric_key(capdu, rapdu); + break; + case PIV_INS_GET_VERSION: + ret = piv_get_version(capdu, rapdu); + break; + case PIV_INS_GET_SERIAL: + ret = piv_get_serial(capdu, rapdu); + break; + case PIV_INS_GET_METADATA: + ret = piv_get_metadata(capdu, rapdu); + break; + case PIV_INS_ALGORITHM_EXTENSION: + ret = piv_algorithm_extension(capdu, rapdu); + break; + default: + EXCEPT(SW_INS_NOT_SUPPORTED); + } + + if (ret < 0) EXCEPT(SW_UNABLE_TO_PROCESS); + return 0; +} + +// for testing without authentication +#ifdef TEST +void set_admin_status(const int status) { in_admin_status = status; } +#endif diff --git a/main/crypto/crypto-util.c b/main/crypto/crypto-util.c index 80ba39a..97db602 100644 --- a/main/crypto/crypto-util.c +++ b/main/crypto/crypto-util.c @@ -10,7 +10,7 @@ void print_hex(const uint8_t *buf, size_t len) { printf("\n"); } -int memcmp_s(const void *p, const void *q, size_t len) { +int memcmp(const void *p, const void *q, size_t len) { volatile size_t equal = 0, notequal = 0; for (size_t i = 0; i != len; ++i) if (((uint8_t*)p)[i] == ((uint8_t*)q)[i]) diff --git a/main/crypto/des.c b/main/crypto/des.c index b978374..8bbff53 100644 --- a/main/crypto/des.c +++ b/main/crypto/des.c @@ -3,6 +3,7 @@ #ifdef USE_MBEDCRYPTO #include #endif +#include __attribute__((weak)) int des_enc(const uint8_t *in, uint8_t *out, const uint8_t *key) { #ifdef USE_MBEDCRYPTO @@ -36,6 +37,423 @@ __attribute__((weak)) int des_dec(const uint8_t *in, uint8_t *out, const uint8_t #endif } + +__attribute__((weak)) void mbedtls_des3_init( mbedtls_des3_context *ctx ) +{ + memset( ctx, 0, sizeof( mbedtls_des3_context ) ); +} + +__attribute__((weak)) void mbedtls_des3_free(mbedtls_des3_context *ctx) +{ + if (ctx == NULL) { + return; + } + + mbedtls_platform_zeroize(ctx, sizeof(mbedtls_des3_context)); +} + +#ifndef MBEDTLS_GET_UINT32_BE +#define MBEDTLS_GET_UINT32_BE( data , offset ) \ + ( \ + ( (uint32_t) ( data )[( offset ) ] << 24 ) \ + | ( (uint32_t) ( data )[( offset ) + 1] << 16 ) \ + | ( (uint32_t) ( data )[( offset ) + 2] << 8 ) \ + | ( (uint32_t) ( data )[( offset ) + 3] ) \ + ) +#endif + +static const uint32_t LHs[16] = +{ + 0x00000000, 0x00000001, 0x00000100, 0x00000101, + 0x00010000, 0x00010001, 0x00010100, 0x00010101, + 0x01000000, 0x01000001, 0x01000100, 0x01000101, + 0x01010000, 0x01010001, 0x01010100, 0x01010101 +}; + +static const uint32_t RHs[16] = +{ + 0x00000000, 0x01000000, 0x00010000, 0x01010000, + 0x00000100, 0x01000100, 0x00010100, 0x01010100, + 0x00000001, 0x01000001, 0x00010001, 0x01010001, + 0x00000101, 0x01000101, 0x00010101, 0x01010101, +}; + + +__attribute__((weak)) void mbedtls_des_setkey( uint32_t SK[32], const unsigned char key[MBEDTLS_DES_KEY_SIZE] ) +{ + int i; + uint32_t X, Y, T; + + X = MBEDTLS_GET_UINT32_BE( key, 0 ); + Y = MBEDTLS_GET_UINT32_BE( key, 4 ); + + /* + * Permuted Choice 1 + */ + T = ((Y >> 4) ^ X) & 0x0F0F0F0F; X ^= T; Y ^= (T << 4); + T = ((Y ) ^ X) & 0x10101010; X ^= T; Y ^= (T ); + + X = (LHs[ (X ) & 0xF] << 3) | (LHs[ (X >> 8) & 0xF ] << 2) + | (LHs[ (X >> 16) & 0xF] << 1) | (LHs[ (X >> 24) & 0xF ] ) + | (LHs[ (X >> 5) & 0xF] << 7) | (LHs[ (X >> 13) & 0xF ] << 6) + | (LHs[ (X >> 21) & 0xF] << 5) | (LHs[ (X >> 29) & 0xF ] << 4); + + Y = (RHs[ (Y >> 1) & 0xF] << 3) | (RHs[ (Y >> 9) & 0xF ] << 2) + | (RHs[ (Y >> 17) & 0xF] << 1) | (RHs[ (Y >> 25) & 0xF ] ) + | (RHs[ (Y >> 4) & 0xF] << 7) | (RHs[ (Y >> 12) & 0xF ] << 6) + | (RHs[ (Y >> 20) & 0xF] << 5) | (RHs[ (Y >> 28) & 0xF ] << 4); + + X &= 0x0FFFFFFF; + Y &= 0x0FFFFFFF; + + /* + * calculate subkeys + */ + for( i = 0; i < 16; i++ ) + { + if( i < 2 || i == 8 || i == 15 ) + { + X = ((X << 1) | (X >> 27)) & 0x0FFFFFFF; + Y = ((Y << 1) | (Y >> 27)) & 0x0FFFFFFF; + } + else + { + X = ((X << 2) | (X >> 26)) & 0x0FFFFFFF; + Y = ((Y << 2) | (Y >> 26)) & 0x0FFFFFFF; + } + + *SK++ = ((X << 4) & 0x24000000) | ((X << 28) & 0x10000000) + | ((X << 14) & 0x08000000) | ((X << 18) & 0x02080000) + | ((X << 6) & 0x01000000) | ((X << 9) & 0x00200000) + | ((X >> 1) & 0x00100000) | ((X << 10) & 0x00040000) + | ((X << 2) & 0x00020000) | ((X >> 10) & 0x00010000) + | ((Y >> 13) & 0x00002000) | ((Y >> 4) & 0x00001000) + | ((Y << 6) & 0x00000800) | ((Y >> 1) & 0x00000400) + | ((Y >> 14) & 0x00000200) | ((Y ) & 0x00000100) + | ((Y >> 5) & 0x00000020) | ((Y >> 10) & 0x00000010) + | ((Y >> 3) & 0x00000008) | ((Y >> 18) & 0x00000004) + | ((Y >> 26) & 0x00000002) | ((Y >> 24) & 0x00000001); + + *SK++ = ((X << 15) & 0x20000000) | ((X << 17) & 0x10000000) + | ((X << 10) & 0x08000000) | ((X << 22) & 0x04000000) + | ((X >> 2) & 0x02000000) | ((X << 1) & 0x01000000) + | ((X << 16) & 0x00200000) | ((X << 11) & 0x00100000) + | ((X << 3) & 0x00080000) | ((X >> 6) & 0x00040000) + | ((X << 15) & 0x00020000) | ((X >> 4) & 0x00010000) + | ((Y >> 2) & 0x00002000) | ((Y << 8) & 0x00001000) + | ((Y >> 14) & 0x00000808) | ((Y >> 9) & 0x00000400) + | ((Y ) & 0x00000200) | ((Y << 7) & 0x00000100) + | ((Y >> 7) & 0x00000020) | ((Y >> 3) & 0x00000011) + | ((Y << 2) & 0x00000004) | ((Y >> 21) & 0x00000002); + } +} + +__attribute__((weak)) void des3_set3key( uint32_t esk[96], + uint32_t dsk[96], + const unsigned char key[24] ) +{ + int i; + + mbedtls_des_setkey( esk, key ); + mbedtls_des_setkey( dsk + 32, key + 8 ); + mbedtls_des_setkey( esk + 64, key + 16 ); + + for( i = 0; i < 32; i += 2 ) + { + dsk[i ] = esk[94 - i]; + dsk[i + 1] = esk[95 - i]; + + esk[i + 32] = dsk[62 - i]; + esk[i + 33] = dsk[63 - i]; + + dsk[i + 64] = esk[30 - i]; + dsk[i + 65] = esk[31 - i]; + } +} + +__attribute__((weak)) int mbedtls_des3_set3key_enc( mbedtls_des3_context *ctx, + const unsigned char key[MBEDTLS_DES_KEY_SIZE * 3] ) +{ + uint32_t sk[96]; + + des3_set3key( ctx->MBEDTLS_PRIVATE(sk), sk, key ); + mbedtls_platform_zeroize( sk, sizeof( sk ) ); + + return( 0 ); +} + +__attribute__((weak)) int mbedtls_des3_set3key_dec( mbedtls_des3_context *ctx, + const unsigned char key[MBEDTLS_DES_KEY_SIZE * 3] ) +{ + uint32_t sk[96]; + + des3_set3key( sk, ctx->MBEDTLS_PRIVATE(sk), key ); + mbedtls_platform_zeroize( sk, sizeof( sk ) ); + + return( 0 ); +} + +static const uint32_t SB1[64] = +{ + 0x01010400, 0x00000000, 0x00010000, 0x01010404, + 0x01010004, 0x00010404, 0x00000004, 0x00010000, + 0x00000400, 0x01010400, 0x01010404, 0x00000400, + 0x01000404, 0x01010004, 0x01000000, 0x00000004, + 0x00000404, 0x01000400, 0x01000400, 0x00010400, + 0x00010400, 0x01010000, 0x01010000, 0x01000404, + 0x00010004, 0x01000004, 0x01000004, 0x00010004, + 0x00000000, 0x00000404, 0x00010404, 0x01000000, + 0x00010000, 0x01010404, 0x00000004, 0x01010000, + 0x01010400, 0x01000000, 0x01000000, 0x00000400, + 0x01010004, 0x00010000, 0x00010400, 0x01000004, + 0x00000400, 0x00000004, 0x01000404, 0x00010404, + 0x01010404, 0x00010004, 0x01010000, 0x01000404, + 0x01000004, 0x00000404, 0x00010404, 0x01010400, + 0x00000404, 0x01000400, 0x01000400, 0x00000000, + 0x00010004, 0x00010400, 0x00000000, 0x01010004 +}; + +static const uint32_t SB2[64] = +{ + 0x80108020, 0x80008000, 0x00008000, 0x00108020, + 0x00100000, 0x00000020, 0x80100020, 0x80008020, + 0x80000020, 0x80108020, 0x80108000, 0x80000000, + 0x80008000, 0x00100000, 0x00000020, 0x80100020, + 0x00108000, 0x00100020, 0x80008020, 0x00000000, + 0x80000000, 0x00008000, 0x00108020, 0x80100000, + 0x00100020, 0x80000020, 0x00000000, 0x00108000, + 0x00008020, 0x80108000, 0x80100000, 0x00008020, + 0x00000000, 0x00108020, 0x80100020, 0x00100000, + 0x80008020, 0x80100000, 0x80108000, 0x00008000, + 0x80100000, 0x80008000, 0x00000020, 0x80108020, + 0x00108020, 0x00000020, 0x00008000, 0x80000000, + 0x00008020, 0x80108000, 0x00100000, 0x80000020, + 0x00100020, 0x80008020, 0x80000020, 0x00100020, + 0x00108000, 0x00000000, 0x80008000, 0x00008020, + 0x80000000, 0x80100020, 0x80108020, 0x00108000 +}; + +static const uint32_t SB3[64] = +{ + 0x00000208, 0x08020200, 0x00000000, 0x08020008, + 0x08000200, 0x00000000, 0x00020208, 0x08000200, + 0x00020008, 0x08000008, 0x08000008, 0x00020000, + 0x08020208, 0x00020008, 0x08020000, 0x00000208, + 0x08000000, 0x00000008, 0x08020200, 0x00000200, + 0x00020200, 0x08020000, 0x08020008, 0x00020208, + 0x08000208, 0x00020200, 0x00020000, 0x08000208, + 0x00000008, 0x08020208, 0x00000200, 0x08000000, + 0x08020200, 0x08000000, 0x00020008, 0x00000208, + 0x00020000, 0x08020200, 0x08000200, 0x00000000, + 0x00000200, 0x00020008, 0x08020208, 0x08000200, + 0x08000008, 0x00000200, 0x00000000, 0x08020008, + 0x08000208, 0x00020000, 0x08000000, 0x08020208, + 0x00000008, 0x00020208, 0x00020200, 0x08000008, + 0x08020000, 0x08000208, 0x00000208, 0x08020000, + 0x00020208, 0x00000008, 0x08020008, 0x00020200 +}; + +static const uint32_t SB4[64] = +{ + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802080, 0x00800081, 0x00800001, 0x00002001, + 0x00000000, 0x00802000, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00800080, 0x00800001, + 0x00000001, 0x00002000, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002001, 0x00002080, + 0x00800081, 0x00000001, 0x00002080, 0x00800080, + 0x00002000, 0x00802080, 0x00802081, 0x00000081, + 0x00800080, 0x00800001, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00000000, 0x00802000, + 0x00002080, 0x00800080, 0x00800081, 0x00000001, + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802081, 0x00000081, 0x00000001, 0x00002000, + 0x00800001, 0x00002001, 0x00802080, 0x00800081, + 0x00002001, 0x00002080, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002000, 0x00802080 +}; + +static const uint32_t SB5[64] = +{ + 0x00000100, 0x02080100, 0x02080000, 0x42000100, + 0x00080000, 0x00000100, 0x40000000, 0x02080000, + 0x40080100, 0x00080000, 0x02000100, 0x40080100, + 0x42000100, 0x42080000, 0x00080100, 0x40000000, + 0x02000000, 0x40080000, 0x40080000, 0x00000000, + 0x40000100, 0x42080100, 0x42080100, 0x02000100, + 0x42080000, 0x40000100, 0x00000000, 0x42000000, + 0x02080100, 0x02000000, 0x42000000, 0x00080100, + 0x00080000, 0x42000100, 0x00000100, 0x02000000, + 0x40000000, 0x02080000, 0x42000100, 0x40080100, + 0x02000100, 0x40000000, 0x42080000, 0x02080100, + 0x40080100, 0x00000100, 0x02000000, 0x42080000, + 0x42080100, 0x00080100, 0x42000000, 0x42080100, + 0x02080000, 0x00000000, 0x40080000, 0x42000000, + 0x00080100, 0x02000100, 0x40000100, 0x00080000, + 0x00000000, 0x40080000, 0x02080100, 0x40000100 +}; + +static const uint32_t SB6[64] = +{ + 0x20000010, 0x20400000, 0x00004000, 0x20404010, + 0x20400000, 0x00000010, 0x20404010, 0x00400000, + 0x20004000, 0x00404010, 0x00400000, 0x20000010, + 0x00400010, 0x20004000, 0x20000000, 0x00004010, + 0x00000000, 0x00400010, 0x20004010, 0x00004000, + 0x00404000, 0x20004010, 0x00000010, 0x20400010, + 0x20400010, 0x00000000, 0x00404010, 0x20404000, + 0x00004010, 0x00404000, 0x20404000, 0x20000000, + 0x20004000, 0x00000010, 0x20400010, 0x00404000, + 0x20404010, 0x00400000, 0x00004010, 0x20000010, + 0x00400000, 0x20004000, 0x20000000, 0x00004010, + 0x20000010, 0x20404010, 0x00404000, 0x20400000, + 0x00404010, 0x20404000, 0x00000000, 0x20400010, + 0x00000010, 0x00004000, 0x20400000, 0x00404010, + 0x00004000, 0x00400010, 0x20004010, 0x00000000, + 0x20404000, 0x20000000, 0x00400010, 0x20004010 +}; + +static const uint32_t SB7[64] = +{ + 0x00200000, 0x04200002, 0x04000802, 0x00000000, + 0x00000800, 0x04000802, 0x00200802, 0x04200800, + 0x04200802, 0x00200000, 0x00000000, 0x04000002, + 0x00000002, 0x04000000, 0x04200002, 0x00000802, + 0x04000800, 0x00200802, 0x00200002, 0x04000800, + 0x04000002, 0x04200000, 0x04200800, 0x00200002, + 0x04200000, 0x00000800, 0x00000802, 0x04200802, + 0x00200800, 0x00000002, 0x04000000, 0x00200800, + 0x04000000, 0x00200800, 0x00200000, 0x04000802, + 0x04000802, 0x04200002, 0x04200002, 0x00000002, + 0x00200002, 0x04000000, 0x04000800, 0x00200000, + 0x04200800, 0x00000802, 0x00200802, 0x04200800, + 0x00000802, 0x04000002, 0x04200802, 0x04200000, + 0x00200800, 0x00000000, 0x00000002, 0x04200802, + 0x00000000, 0x00200802, 0x04200000, 0x00000800, + 0x04000002, 0x04000800, 0x00000800, 0x00200002 +}; + +static const uint32_t SB8[64] = +{ + 0x10001040, 0x00001000, 0x00040000, 0x10041040, + 0x10000000, 0x10001040, 0x00000040, 0x10000000, + 0x00040040, 0x10040000, 0x10041040, 0x00041000, + 0x10041000, 0x00041040, 0x00001000, 0x00000040, + 0x10040000, 0x10000040, 0x10001000, 0x00001040, + 0x00041000, 0x00040040, 0x10040040, 0x10041000, + 0x00001040, 0x00000000, 0x00000000, 0x10040040, + 0x10000040, 0x10001000, 0x00041040, 0x00040000, + 0x00041040, 0x00040000, 0x10041000, 0x00001000, + 0x00000040, 0x10040040, 0x00001000, 0x00041040, + 0x10001000, 0x00000040, 0x10000040, 0x10040000, + 0x10040040, 0x10000000, 0x00040000, 0x10001040, + 0x00000000, 0x10041040, 0x00040040, 0x10000040, + 0x10040000, 0x10001000, 0x10001040, 0x00000000, + 0x10041040, 0x00041000, 0x00041000, 0x00001040, + 0x00001040, 0x00040040, 0x10000000, 0x10041000 +}; + +#define MBEDTLS_BYTE_0( x ) ( (uint8_t) ( ( x ) & 0xff ) ) +#define MBEDTLS_BYTE_1( x ) ( (uint8_t) ( ( ( x ) >> 8 ) & 0xff ) ) +#define MBEDTLS_BYTE_2( x ) ( (uint8_t) ( ( ( x ) >> 16 ) & 0xff ) ) +#define MBEDTLS_BYTE_3( x ) ( (uint8_t) ( ( ( x ) >> 24 ) & 0xff ) ) +#define MBEDTLS_BYTE_4( x ) ( (uint8_t) ( ( ( x ) >> 32 ) & 0xff ) ) +#define MBEDTLS_BYTE_5( x ) ( (uint8_t) ( ( ( x ) >> 40 ) & 0xff ) ) +#define MBEDTLS_BYTE_6( x ) ( (uint8_t) ( ( ( x ) >> 48 ) & 0xff ) ) +#define MBEDTLS_BYTE_7( x ) ( (uint8_t) ( ( ( x ) >> 56 ) & 0xff ) ) + +#define DES_FP(X,Y) \ + do \ + { \ + (X) = (((X) << 31) | ((X) >> 1)) & 0xFFFFFFFF; \ + T = ((X) ^ (Y)) & 0xAAAAAAAA; (X) ^= T; (Y) ^= T; \ + (Y) = (((Y) << 31) | ((Y) >> 1)) & 0xFFFFFFFF; \ + T = (((Y) >> 8) ^ (X)) & 0x00FF00FF; (X) ^= T; (Y) ^= (T << 8); \ + T = (((Y) >> 2) ^ (X)) & 0x33333333; (X) ^= T; (Y) ^= (T << 2); \ + T = (((X) >> 16) ^ (Y)) & 0x0000FFFF; (Y) ^= T; (X) ^= (T << 16); \ + T = (((X) >> 4) ^ (Y)) & 0x0F0F0F0F; (Y) ^= T; (X) ^= (T << 4); \ + } while( 0 ) + +#ifndef MBEDTLS_PUT_UINT32_BE +#define MBEDTLS_PUT_UINT32_BE( n, data, offset ) \ +{ \ + ( data )[( offset ) ] = MBEDTLS_BYTE_3( n ); \ + ( data )[( offset ) + 1] = MBEDTLS_BYTE_2( n ); \ + ( data )[( offset ) + 2] = MBEDTLS_BYTE_1( n ); \ + ( data )[( offset ) + 3] = MBEDTLS_BYTE_0( n ); \ +} +#endif + +#define DES_ROUND(X,Y) \ + do \ + { \ + T = *SK++ ^ (X); \ + (Y) ^= SB8[ (T ) & 0x3F ] ^ \ + SB6[ (T >> 8) & 0x3F ] ^ \ + SB4[ (T >> 16) & 0x3F ] ^ \ + SB2[ (T >> 24) & 0x3F ]; \ + \ + T = *SK++ ^ (((X) << 28) | ((X) >> 4)); \ + (Y) ^= SB7[ (T ) & 0x3F ] ^ \ + SB5[ (T >> 8) & 0x3F ] ^ \ + SB3[ (T >> 16) & 0x3F ] ^ \ + SB1[ (T >> 24) & 0x3F ]; \ + } while( 0 ) + +#define DES_IP(X,Y) \ + do \ + { \ + T = (((X) >> 4) ^ (Y)) & 0x0F0F0F0F; (Y) ^= T; (X) ^= (T << 4); \ + T = (((X) >> 16) ^ (Y)) & 0x0000FFFF; (Y) ^= T; (X) ^= (T << 16); \ + T = (((Y) >> 2) ^ (X)) & 0x33333333; (X) ^= T; (Y) ^= (T << 2); \ + T = (((Y) >> 8) ^ (X)) & 0x00FF00FF; (X) ^= T; (Y) ^= (T << 8); \ + (Y) = (((Y) << 1) | ((Y) >> 31)) & 0xFFFFFFFF; \ + T = ((X) ^ (Y)) & 0xAAAAAAAA; (Y) ^= T; (X) ^= T; \ + (X) = (((X) << 1) | ((X) >> 31)) & 0xFFFFFFFF; \ + } while( 0 ) + + + +__attribute__((weak)) int mbedtls_des3_crypt_ecb( mbedtls_des3_context *ctx, + const unsigned char input[8], + unsigned char output[8] ) +{ + int i; + uint32_t X, Y, T, *SK; + + SK = ctx->MBEDTLS_PRIVATE(sk); + + X = MBEDTLS_GET_UINT32_BE( input, 0 ); + Y = MBEDTLS_GET_UINT32_BE( input, 4 ); + + DES_IP( X, Y ); + + for( i = 0; i < 8; i++ ) + { + DES_ROUND( Y, X ); + DES_ROUND( X, Y ); + } + + for( i = 0; i < 8; i++ ) + { + DES_ROUND( X, Y ); + DES_ROUND( Y, X ); + } + + for( i = 0; i < 8; i++ ) + { + DES_ROUND( Y, X ); + DES_ROUND( X, Y ); + } + + DES_FP( Y, X ); + + MBEDTLS_PUT_UINT32_BE( Y, output, 0 ); + MBEDTLS_PUT_UINT32_BE( X, output, 4 ); + + return( 0 ); +} + __attribute__((weak)) int tdes_enc(const uint8_t *in, uint8_t *out, const uint8_t *key) { #ifdef USE_MBEDCRYPTO mbedtls_des3_context ctx; diff --git a/main/crypto/include/crypto-util.h b/main/crypto/include/crypto-util.h index edab828..ca4d363 100644 --- a/main/crypto/include/crypto-util.h +++ b/main/crypto/include/crypto-util.h @@ -7,7 +7,7 @@ void raise_exception(void); void print_hex(const uint8_t *buf, size_t len); -int memcmp_s(const void *p, const void *q, size_t len); +int memcmp(const void *p, const void *q, size_t len); void random_delay(void); #endif //_UTILS_H diff --git a/main/crypto/memzero.c b/main/crypto/memzero.c index a0b6f4a..2b2c0fe 100644 --- a/main/crypto/memzero.c +++ b/main/crypto/memzero.c @@ -1,13 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 -#define __STDC_WANT_LIB_EXT1__ 1 // C11's bounds-checking interface. + #include void memzero(void *pnt, size_t len) { -#ifdef __STDC_LIB_EXT1__ - memset_s(pnt, len, 0, len); -#else - volatile unsigned char *p = pnt; - while (len--) - *p++ = 0; -#endif + + memset(pnt, 0, len); + } diff --git a/main/device.c b/main/device.c index ba4e53f..6608d82 100644 --- a/main/device.c +++ b/main/device.c @@ -12,10 +12,14 @@ #include "driver/gpio.h" #include "driver/gptimer.h" #include "esp_partition.h" +#include "esp_timer.h" #include #include "ctap.h" #include "sdkconfig.h" -#include "secret.h" +#include "ccid_device.h" +#include "ccid.h" +#include "applets.h" +#include "esp_mac.h" #define NVS_PARTITION_LABEL "lfs" @@ -259,23 +263,36 @@ void device_init(void) ESP_ERROR_CHECK(gpio_config(&boot_button_config)); #endif - hid_rx_rb = xRingbufferCreate(HID_RPT_SIZE*32, RINGBUF_TYPE_BYTEBUF); + + hid_rx_rb = xRingbufferCreate(HID_RPT_SIZE * 32, RINGBUF_TYPE_BYTEBUF); hid_tx_requested = xSemaphoreCreateBinary(); hid_tx_done = xSemaphoreCreateBinary(); CTAPHID_Init(); + CCID_Init(); if (get_file_size("ctap_cert") <= 0) { ESP_LOGI(TAG, "cert file initialization"); - ctap_install(1); + CAPDU apdu_cert; + + apdu_cert.lc = u2f_cert_end - u2f_cert_start; + apdu_cert.data = (uint8_t *)u2f_cert_start; + + ctap_install_cert(&apdu_cert, NULL); - ctap_install_cert(u2f_cert_start, u2f_cert_end - u2f_cert_start); - ctap_install_private_key(u2f_cert_key_start, u2f_cert_key_end - u2f_cert_key_start); + apdu_cert.lc = u2f_cert_key_end - u2f_cert_key_start; + apdu_cert.data = (uint8_t *)u2f_cert_key_start; + ctap_install_private_key(&apdu_cert, NULL); + + uint8_t sta_mac[6]; + esp_efuse_mac_get_default(sta_mac); + + write_file("sn", sta_mac + 2, 0, 4, 1); } - ctap_install(0); + applets_install(); ESP_LOGI(TAG, "u2f device init done"); } @@ -283,7 +300,6 @@ void device_init(void) void device_recv_data(uint8_t const *data, uint16_t len) { - if (len == 0) { return; @@ -297,18 +313,16 @@ void device_loop(uint8_t has_touch) size_t item_size = 0; uint8_t *data = xRingbufferReceiveUpTo(hid_rx_rb, &item_size, 0, HID_RPT_SIZE); - - if (item_size==HID_RPT_SIZE) + if (item_size == HID_RPT_SIZE) { - memcpy(packet_buf,data,item_size); + memcpy(packet_buf, data, item_size); vRingbufferReturnItem(hid_rx_rb, data); CTAPHID_OutEvent(packet_buf); } - CTAPHID_Loop(0); - // ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size()); + CCID_Loop(); } void tud_hid_report_complete_cb(uint8_t instance, uint8_t const *report, /*uint16_t*/ uint8_t len) @@ -371,10 +385,7 @@ uint8_t get_touch_result(void) { set_touch_result(TOUCH_SHORT); } - - cp_begin_using_uv_auth_token(1); #else - cp_begin_using_uv_auth_token(1); set_touch_result(TOUCH_SHORT); #endif @@ -457,7 +468,99 @@ uint8_t wait_for_user_presence(uint8_t entry) return USER_PRESENCE_OK; } -void device_get_aaguid(uint8_t *data, uint8_t len){ +void device_get_aaguid(uint8_t *data, uint8_t len) +{ + + memcpy(data, u2f_aaguid_start, len); +} + +int device_spinlock_lock(spinlock_t *lock, uint32_t blocking) +{ + + if (blocking) + { + spinlock_acquire(lock, SPINLOCK_WAIT_FOREVER); + } + else + { + + spinlock_acquire(lock, SPINLOCK_NO_WAIT); + } + + return 0; +} + +void device_spinlock_unlock(spinlock_t *lock) +{ + spinlock_release(lock); +} + +esp_timer_handle_t m_timer_timeout = NULL; + +void device_set_timeout(void (*callback)(void *), uint16_t timeout) +{ + if (m_timer_timeout) + { + esp_timer_stop(m_timer_timeout); + esp_timer_delete(m_timer_timeout); + m_timer_timeout = NULL; + } + + if (timeout) + { + const esp_timer_create_args_t timer_args = { + .callback = callback, + /* name is optional, but may help identify the timer when debugging */ + .name = "one-shot"}; + + esp_timer_create(&timer_args, &m_timer_timeout); + esp_timer_start_once(m_timer_timeout, timeout * 1000); + } +} + +int device_atomic_compare_and_swap(volatile uint32_t *var, uint32_t expect, uint32_t update) +{ + if (*var == expect) + { + *var = update; + return 0; + } + else + { + return -1; + } +} + +__attribute__((weak)) int strong_user_presence_test(void) +{ + for (int i = 0; i < 5; i++) + { + const uint8_t wait_sec = 2; + start_blinking_interval(wait_sec, (i & 1) ? 200 : 50); + uint32_t now, begin = device_get_tick(); + bool user_presence = false; + do + { + if (get_touch_result() == TOUCH_SHORT) + { + user_presence = true; + set_touch_result(TOUCH_NO); + stop_blinking(); + // wait for some time before next user-precense test + begin = device_get_tick(); + } + now = device_get_tick(); + } while (now - begin < 1000 * wait_sec); + if (!user_presence) + { + return -1; + } + } + return 0; +} + +void device_delay(int ms) +{ - memcpy(data,u2f_aaguid_start,len); + vTaskDelay(pdMS_TO_TICKS(ms)); } \ No newline at end of file diff --git a/main/include/admin.h b/main/include/admin.h new file mode 100644 index 0000000..3a872a2 --- /dev/null +++ b/main/include/admin.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef CANOKEY_CORE_ADMIN_ADMIN_H_ +#define CANOKEY_CORE_ADMIN_ADMIN_H_ + +#include + +#define ADMIN_INS_WRITE_FIDO_PRIVATE_KEY 0x01 +#define ADMIN_INS_WRITE_FIDO_CERT 0x02 +#define ADMIN_INS_RESET_OPENPGP 0x03 +#define ADMIN_INS_RESET_PIV 0x04 +#define ADMIN_INS_RESET_OATH 0x05 +#define ADMIN_INS_RESET_NDEF 0x07 +#define ADMIN_INS_TOGGLE_NDEF_READ_ONLY 0x08 +#define ADMIN_INS_RESET_CTAP 0x09 +#define ADMIN_INS_READ_CTAP_SM2_CONFIG 0x11 +#define ADMIN_INS_WRITE_CTAP_SM2_CONFIG 0x12 +#define ADMIN_INS_VERIFY 0x20 +#define ADMIN_INS_CHANGE_PIN 0x21 +#define ADMIN_INS_WRITE_SN 0x30 +#define ADMIN_INS_READ_VERSION 0x31 +#define ADMIN_INS_READ_SN 0x32 +#define ADMIN_INS_CONFIG 0x40 +#define ADMIN_INS_FLASH_USAGE 0x41 +#define ADMIN_INS_READ_CONFIG 0x42 +#define ADMIN_INS_FACTORY_RESET 0x50 +#define ADMIN_INS_SELECT 0xA4 +#define ADMIN_INS_VENDOR_SPECIFIC 0xFF + +#define ADMIN_P1_CFG_LED_ON 0x01 +#define ADMIN_P1_CFG_KBDIFACE 0x03 +#define ADMIN_P1_CFG_NDEF 0x04 +#define ADMIN_P1_CFG_WEBUSB_LANDING 0x05 +#define ADMIN_P1_CFG_KBD_WITH_RETURN 0x06 + +typedef struct { + uint32_t reserved; + uint32_t led_normally_on : 1; + uint32_t unused : 1; + uint32_t kbd_interface_en : 1; + uint32_t ndef_en : 1; + uint32_t webusb_landing_en : 1; + uint32_t kbd_with_return_en : 1; +} __packed admin_device_config_t; + +void admin_poweroff(void); +int admin_install(uint8_t reset); +int admin_process_apdu(const CAPDU *capdu, RAPDU *rapdu); +int admin_vendor_specific(const CAPDU *capdu, RAPDU *rapdu); +int admin_vendor_version(const CAPDU *capdu, RAPDU *rapdu); +int admin_vendor_hw_variant(const CAPDU *capdu, RAPDU *rapdu); +int admin_vendor_hw_sn(const CAPDU *capdu, RAPDU *rapdu); + +uint8_t cfg_is_led_normally_on(void); +uint8_t cfg_is_kbd_interface_enable(void); +uint8_t cfg_is_ndef_enable(void); +uint8_t cfg_is_webusb_landing_enable(void); +uint8_t cfg_is_kbd_with_return_enable(void); + +#endif // CANOKEY_CORE_ADMIN_ADMIN_H_ diff --git a/main/apdu.h b/main/include/apdu.h similarity index 85% rename from main/apdu.h rename to main/include/apdu.h index b082766..6ce0453 100644 --- a/main/apdu.h +++ b/main/include/apdu.h @@ -86,5 +86,13 @@ enum { BUFFER_OWNER_USBD, // store the configuration descriptor during a control transfer }; +void init_apdu_buffer(void); // implement in ccid.c for reusing the ccid buffer +int acquire_apdu_buffer(uint8_t owner); +int release_apdu_buffer(uint8_t owner); -#endif // CANOKEY_CORE__APDU_H +int build_capdu(CAPDU *capdu, const uint8_t *cmd, uint16_t len); +int apdu_input(CAPDU_CHAINING *ex, const CAPDU *sh); +int apdu_output(RAPDU_CHAINING *ex, RAPDU *sh); +void process_apdu(CAPDU *capdu, RAPDU *rapdu); + +#endif // CANOKEY_CORE__APDU_H \ No newline at end of file diff --git a/main/include/applets.h b/main/include/applets.h new file mode 100644 index 0000000..7fde6d1 --- /dev/null +++ b/main/include/applets.h @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +#ifndef APPLETS_H_ +#define APPLETS_H_ + +void applets_install(void); +void applets_poweroff(void); + +#endif // APPLETS_H_ diff --git a/main/include/ccid.h b/main/include/ccid.h new file mode 100644 index 0000000..1a64bce --- /dev/null +++ b/main/include/ccid.h @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef _CCID_H_ +#define _CCID_H_ + +#include + +#define ABDATA_SIZE (APDU_BUFFER_SIZE + 2) +#define CCID_CMD_HEADER_SIZE 10 +#define CCID_NUMBER_OF_SLOTS 1 +#define TIME_EXTENSION_PERIOD 1500 + +typedef struct { + uint8_t bMessageType; /* Offset = 0*/ + uint32_t dwLength; /* Offset = 1, The length field (dwLength) is the length + of the message not including the 10-byte header.*/ + uint8_t bSlot; /* Offset = 5*/ + uint8_t bSeq; /* Offset = 6*/ + uint8_t bSpecific_0; /* Offset = 7*/ + uint8_t bSpecific_1; /* Offset = 8*/ + uint8_t bSpecific_2; /* Offset = 9*/ + uint8_t *abData; /* Offset = 10*/ +} __packed ccid_bulkout_data_t; + +typedef struct { + uint8_t bMessageType; /* Offset = 0*/ + uint32_t dwLength; /* Offset = 1*/ + uint8_t bSlot; /* Offset = 5, Same as Bulk-OUT message */ + uint8_t bSeq; /* Offset = 6, Same as Bulk-OUT message */ + uint8_t bStatus; /* Offset = 7, Slot status as defined in § 6.2.6*/ + uint8_t bError; /* Offset = 8, Slot error as defined in § 6.2.6*/ + uint8_t bSpecific; /* Offset = 9*/ + uint8_t abData[ABDATA_SIZE]; /* Offset = 10*/ +} __packed ccid_bulkin_data_t; + +typedef struct { + uint8_t bMessageType; /* Offset = 0*/ + uint32_t dwLength; /* Offset = 1*/ + uint8_t bSlot; /* Offset = 5, Same as Bulk-OUT message */ + uint8_t bSeq; /* Offset = 6, Same as Bulk-OUT message */ + uint8_t bStatus; /* Offset = 7, Slot status as defined in § 6.2.6*/ + uint8_t bError; /* Offset = 8, Slot error as defined in § 6.2.6*/ + uint8_t bSpecific; /* Offset = 9*/ +} __packed empty_ccid_bulkin_data_t; + +/******************************************************************************/ +/* ERROR CODES for USB Bulk In Messages : bError */ +/******************************************************************************/ + +#define SLOT_NO_ERROR 0x81 +#define SLOTERROR_UNKNOWN 0x82 + +#define SLOTERROR_BAD_LENTGH 0x01 +#define SLOTERROR_BAD_SLOT 0x05 +#define SLOTERROR_BAD_POWERSELECT 0x07 +#define SLOTERROR_BAD_PROTOCOLNUM 0x07 +#define SLOTERROR_BAD_CLOCKCOMMAND 0x07 +#define SLOTERROR_BAD_ABRFU_3B 0x07 +#define SLOTERROR_BAD_BMCHANGES 0x07 +#define SLOTERROR_BAD_BFUNCTION_MECHANICAL 0x07 +#define SLOTERROR_BAD_ABRFU_2B 0x08 +#define SLOTERROR_BAD_LEVELPARAMETER 0x08 +#define SLOTERROR_BAD_FIDI 0x0A +#define SLOTERROR_BAD_T01CONVCHECKSUM 0x0B +#define SLOTERROR_BAD_GUARDTIME 0x0C +#define SLOTERROR_BAD_WAITINGINTEGER 0x0D +#define SLOTERROR_BAD_CLOCKSTOP 0x0E +#define SLOTERROR_BAD_IFSC 0x0F +#define SLOTERROR_BAD_NAD 0x10 +#define SLOTERROR_BAD_DWLENGTH 0x08 /* Used in PC_to_RDR_XfrBlock*/ + +#define SLOTERROR_CMD_ABORTED 0xFF +#define SLOTERROR_ICC_MUTE 0xFE +#define SLOTERROR_XFR_PARITY_ERROR 0xFD +#define SLOTERROR_XFR_OVERRUN 0xFC +#define SLOTERROR_HW_ERROR 0xFB +#define SLOTERROR_BAD_ATR_TS 0xF8 +#define SLOTERROR_BAD_ATR_TCK 0xF7 +#define SLOTERROR_ICC_PROTOCOL_NOT_SUPPORTED 0xF6 +#define SLOTERROR_ICC_CLASS_NOT_SUPPORTED 0xF5 +#define SLOTERROR_PROCEDURE_BYTE_CONFLICT 0xF4 +#define SLOTERROR_DEACTIVATED_PROTOCOL 0xF3 +#define SLOTERROR_BUSY_WITH_AUTO_SEQUENCE 0xF2 +#define SLOTERROR_PIN_TIMEOUT 0xF0 +#define SLOTERROR_PIN_CANCELLED 0xEF +#define SLOTERROR_CMD_SLOT_BUSY 0xE0 +#define SLOTERROR_CMD_NOT_SUPPORTED 0x00 + +#define BM_ICC_PRESENT_ACTIVE 0x00 +#define BM_ICC_PRESENT_INACTIVE 0x01 +#define BM_ICC_NO_ICC_PRESENT 0x02 + +#define BM_COMMAND_STATUS_OFFSET 0x06 +#define BM_COMMAND_STATUS_NO_ERROR 0x00 +#define BM_COMMAND_STATUS_FAILED (0x01 << BM_COMMAND_STATUS_OFFSET) +#define BM_COMMAND_STATUS_TIME_EXTN (0x02 << BM_COMMAND_STATUS_OFFSET) + +#define LEN_RDR_TO_PC_SLOTSTATUS 10 + +typedef enum { + CHK_PARAM_SLOT = 1, + CHK_PARAM_DWLENGTH = (1 << 1), + CHK_PARAM_abRFU2 = (1 << 2), + CHK_PARAM_abRFU3 = (1 << 3), + CHK_PARAM_CARD_PRESENT = (1 << 4), + CHK_PARAM_ABORT = (1 << 5), + CHK_ACTIVE_STATE = (1 << 6) +} ChkParam_t; + +#define PC_TO_RDR_ICCPOWERON 0x62 +#define PC_TO_RDR_ICCPOWEROFF 0x63 +#define PC_TO_RDR_GETSLOTSTATUS 0x65 +#define PC_TO_RDR_XFRBLOCK 0x6F +#define PC_TO_RDR_GETPARAMETERS 0x6C +#define PC_TO_RDR_RESETPARAMETERS 0x6D +#define PC_TO_RDR_SETPARAMETERS 0x61 +#define PC_TO_RDR_ESCAPE 0x6B +#define PC_TO_RDR_ICCCLOCK 0x6E +#define PC_TO_RDR_T0APDU 0x6A +#define PC_TO_RDR_SECURE 0x69 +#define PC_TO_RDR_MECHANICAL 0x71 +#define PC_TO_RDR_ABORT 0x72 +#define PC_TO_RDR_SETDATARATEANDCLOCKFREQUENCY 0x73 + +#define RDR_TO_PC_DATABLOCK 0x80 +#define RDR_TO_PC_SLOTSTATUS 0x81 +#define RDR_TO_PC_PARAMETERS 0x82 +#define RDR_TO_PC_ESCAPE 0x83 +#define RDR_TO_PC_DATARATEANDCLOCKFREQUENCY 0x84 + +uint8_t CCID_Init(void); +uint8_t CCID_OutEvent(uint8_t *data, uint8_t len); +void CCID_Loop(void); +void CCID_TimeExtensionLoop(void* argc); +uint8_t PC_to_RDR_XfrBlock(void); // Exported for test purposes + +#endif //_CCID_H_ diff --git a/main/include/ccid_device.h b/main/include/ccid_device.h new file mode 100644 index 0000000..477d6c0 --- /dev/null +++ b/main/include/ccid_device.h @@ -0,0 +1,93 @@ +#ifndef _DEVICE_CCID_H_ +#define _DEVICE_CCID_H_ + +#include "tusb.h" +#include "ccid.h" + +#define CFG_TUD_CCID (1) + +#define CFG_TUD_CCID_EP_BUFSIZE 64 + +// Starting endpoints; adjusted elsewhere as needed +#define CCID_EPOUT (0x02) +#define CCID_EPIN (0x82) + +#define CCID_HDR_SZ (10) // CCID message header size +#define CCID_DESC_SZ (54) // CCID function descriptor size +#define CCID_DESC_TYPE_CCID (0x21) // CCID Descriptor + +#define CCID_VERSION (0x0110) +#define CCID_IFSD (ABDATA_SIZE) +#define CCID_FEATURES (0x40000 | 0x40 | 0x20 | 0x10 | 0x08 | 0x04 | 0x02) +#define CCID_MSGLEN (CCID_IFSD + CCID_HDR_SZ) +#define CCID_CLAGET (0xFF) +#define CCID_CLAENV (0xFF) + +#define CFG_TUD_CCID_TX_BUFSIZE CCID_MSGLEN*8 +#define CFG_TUD_CCID_RX_BUFSIZE CCID_MSGLEN*8 + +#define TUD_CCID_DESC_LEN (9 + CCID_DESC_SZ + 7 + 7) + +// CCID Descriptor Template +// Interface number, string index, EP notification address and size, EP data address (out, in) and size. +#define TUD_CCID_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize) \ + /* CCID Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 2, TUSB_CLASS_SMART_CARD, 0, 0, _stridx,\ + /* CCID Function, version, max slot index, supported voltages and protocols */\ + CCID_DESC_SZ, CCID_DESC_TYPE_CCID, U16_TO_U8S_LE(CCID_VERSION), 0, 0x7, U32_TO_U8S_LE(3),\ + /* default clock, maximum clock, num clocks, current datarate, max datarate */\ + U32_TO_U8S_LE(4000), U32_TO_U8S_LE(5000), 0, U32_TO_U8S_LE(9600), U32_TO_U8S_LE(625000),\ + /* num datarates, max IFSD, sync. protocols, mechanical, features */\ + 0, U32_TO_U8S_LE(CCID_IFSD), U32_TO_U8S_LE(0), U32_TO_U8S_LE(0), U32_TO_U8S_LE(CCID_FEATURES),\ + /* max msg len, get response CLA, envelope CLA, LCD layout, PIN support, max busy slots */\ + U32_TO_U8S_LE(CCID_MSGLEN), CCID_CLAGET, CCID_CLAENV, U16_TO_U8S_LE(0), 0, 1,\ + \ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0\ + + +typedef struct { + uint8_t itf_num; + uint8_t ep_in; + uint8_t ep_out; + + tu_fifo_t rx_ff; // nothing is cleared on reset from here on + tu_fifo_t tx_ff; + uint8_t rx_ff_buf[CFG_TUD_CCID_RX_BUFSIZE]; + uint8_t tx_ff_buf[CFG_TUD_CCID_TX_BUFSIZE]; + + CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUD_CCID_EP_BUFSIZE]; + CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUD_CCID_EP_BUFSIZE]; +} ccidd_interface_t; + +TU_ATTR_WEAK void tud_ccid_rx_cb(uint8_t itf); +TU_ATTR_WEAK void tud_ccid_tx_cb(uint8_t itf, uint16_t xferred_bytes); + +bool tud_ccid_n_mounted(uint8_t itf); +uint32_t tud_ccid_n_available(uint8_t itf); +uint32_t tud_ccid_n_read(uint8_t itf, void *buffer, uint32_t bufsize); +uint32_t tud_ccid_n_write(uint8_t itf, void const*buffer, uint32_t bufsize); +uint32_t tud_ccid_write_n_flush(ccidd_interface_t *p_itf); + +static inline uint32_t tud_ccid_read (void* buffer, uint32_t bufsize) +{ + return tud_ccid_n_read(0, buffer, bufsize); +} + +static inline uint32_t tud_ccid_write (void const* buffer, uint32_t bufsize) +{ + return tud_ccid_n_write(0, buffer, bufsize); +} + +static inline uint32_t tud_ccid_write_flush (void) +{ + return tud_ccid_write_n_flush(0); +} + +static inline uint32_t tud_ccid_available(void) { return tud_ccid_n_available(0); } + +static inline bool tud_ccid_mounted(void) { return tud_ccid_n_mounted(0); } + +#endif //_CCID_H_ \ No newline at end of file diff --git a/main/common.h b/main/include/common.h similarity index 100% rename from main/common.h rename to main/include/common.h diff --git a/main/ctap.h b/main/include/ctap.h similarity index 79% rename from main/ctap.h rename to main/include/ctap.h index 17dd638..375a8aa 100644 --- a/main/ctap.h +++ b/main/include/ctap.h @@ -6,8 +6,8 @@ #include uint8_t ctap_install(uint8_t reset); -int ctap_install_private_key(const uint8_t* cert_key,lfs_size_t len); -int ctap_install_cert(const uint8_t* cert,lfs_size_t len); +int ctap_install_private_key(const CAPDU *capdu, RAPDU *rapdu); +int ctap_install_cert(const CAPDU *capdu, RAPDU *rapdu); int ctap_read_sm2_config(const CAPDU *capdu, RAPDU *rapdu); int ctap_write_sm2_config(const CAPDU *capdu, RAPDU *rapdu); int ctap_process_cbor(uint8_t *req, size_t req_len, uint8_t *resp, size_t *resp_len); diff --git a/main/ctaphid.h b/main/include/ctaphid.h similarity index 100% rename from main/ctaphid.h rename to main/include/ctaphid.h diff --git a/main/include/device-config-default.h b/main/include/device-config-default.h new file mode 100644 index 0000000..bcd6908 --- /dev/null +++ b/main/include/device-config-default.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef __DEVICE_CONFIG_DEFAULT__H__ +#define __DEVICE_CONFIG_DEFAULT__H__ + +#define USB_MAX_EP0_SIZE 16 + +#endif /* __DEVICE_CONFIG_DEFAULT__H__ */ diff --git a/main/device.h b/main/include/device.h similarity index 89% rename from main/device.h rename to main/include/device.h index e876e45..db4a1e5 100644 --- a/main/device.h +++ b/main/include/device.h @@ -5,6 +5,7 @@ #include "common.h" #include +#include "spinlock.h" #define TOUCH_NO 0 #define TOUCH_SHORT 1 @@ -17,6 +18,13 @@ #define WAIT_ENTRY_CCID 0 #define WAIT_ENTRY_CTAPHID 1 +// CCID Bulk State machine +#define CCID_STATE_IDLE 0 +#define CCID_STATE_RECEIVE_DATA 1 +#define CCID_STATE_DATA_IN 2 +#define CCID_STATE_DATA_IN_WITH_ZLP 3 +#define CCID_STATE_PROCESS_DATA 4 + typedef enum { CTAPHID_IDLE = 0, CTAPHID_BUSY } CTAPHID_StateTypeDef; // functions should be implemented by device @@ -36,14 +44,14 @@ uint32_t device_get_tick(void); * * @return 0 for locking successfully, -1 for failure. */ -int device_spinlock_lock(volatile uint32_t *lock, uint32_t blocking); +int device_spinlock_lock(spinlock_t *lock, uint32_t blocking); /** * Unlock the specific handler. * * @param lock The lock handler. */ -void device_spinlock_unlock(volatile uint32_t *lock); +void device_spinlock_unlock(spinlock_t *lock); /** * Update the value of a variable atomically. @@ -56,7 +64,7 @@ int device_atomic_compare_and_swap(volatile uint32_t *var, uint32_t expect, uint void led_on(void); void led_off(void); -void device_set_timeout(void (*callback)(void), uint16_t timeout); +void device_set_timeout(void (*callback)(void*), uint16_t timeout); // NFC related /** diff --git a/main/fs.h b/main/include/fs.h similarity index 100% rename from main/fs.h rename to main/include/fs.h diff --git a/main/include/key.h b/main/include/key.h new file mode 100644 index 0000000..1ba4499 --- /dev/null +++ b/main/include/key.h @@ -0,0 +1,93 @@ +#ifndef CANOKEY_CORE_KEY_H +#define CANOKEY_CORE_KEY_H + +#include +#include +#include +#include + +#define KEY_ERR_LENGTH (-1) +#define KEY_ERR_DATA (-2) +#define KEY_ERR_PROC (-3) + +typedef enum { + SIGN = 0x01, + ENCRYPT = 0x02, + KEY_AGREEMENT = 0x04, +} key_usage_t; + +typedef enum { + KEY_ORIGIN_NOT_PRESENT = 0x00, + KEY_ORIGIN_GENERATED = 0x01, + KEY_ORIGIN_IMPORTED = 0x02, +} key_origin_t; + +typedef enum { + PIN_POLICY_NEVER = 0x01, + PIN_POLICY_ONCE = 0x02, + PIN_POLICY_ALWAYS = 0x03, +} pin_policy_t; + +typedef enum { + TOUCH_POLICY_DEFAULT = 0x00, // disabled in both OpenPGP and PIV + TOUCH_POLICY_NEVER = 0x01, // not used in OpenPGP; the same as default in PIV + TOUCH_POLICY_ALWAYS = 0x02, // not used in OpenPGP; enabled in PIV without cache + TOUCH_POLICY_CACHED = 0x03, // enabled in OpenPGP; enabled in PIV with cache + TOUCH_POLICY_PERMANENT = 0x04, // permanently enabled in OpenPGP; not used in PIV +} touch_policy_t; + +typedef struct { + key_type_t type; + key_origin_t origin; + key_usage_t usage; + pin_policy_t pin_policy; + touch_policy_t touch_policy; +} key_meta_t; + +typedef struct { + key_meta_t meta; + union { + rsa_key_t rsa; + ecc_key_t ecc; + uint8_t data[0]; + }; +} ck_key_t; + + +/** + * Encode public key + * + * @param key key type + * @param buf buffer + * @param include_length encode the length or not + * @return encoded length + */ +int ck_encode_public_key(ck_key_t *key, uint8_t *buf, bool include_length); + +/** + * Parse the key imported to PIV + * + * @param key parsed key. origin will be set to KEY_ORIGIN_IMPORTED. + * @param buf data buffer that contains the key + * @param buf_len data buffer length + * @return 0 for success. Negative values for errors. + */ +int ck_parse_piv(ck_key_t *key, const uint8_t *buf, size_t buf_len); + +int ck_parse_piv_policies(ck_key_t *key, const uint8_t *buf, size_t buf_len); + +int ck_parse_openpgp(ck_key_t *key, const uint8_t *buf, size_t buf_len); + +int ck_read_key_metadata(const char *path, key_meta_t *meta); + +int ck_write_key_metadata(const char *path, const key_meta_t *meta); + +int ck_read_key(const char *path, ck_key_t *key); + +int ck_write_key(const char *path, const ck_key_t *key); + +int ck_generate_key(ck_key_t *key); + +int ck_sign(const ck_key_t *key, const uint8_t *input, size_t input_len, uint8_t *sig); + +#endif // CANOKEY_CORE_KEY_H diff --git a/main/include/meta.h b/main/include/meta.h new file mode 100644 index 0000000..5c9442f --- /dev/null +++ b/main/include/meta.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef CANOKEY_CORE_INCLUDE_META_H +#define CANOKEY_CORE_INCLUDE_META_H + +#include + +#define META_INS_SELECT 0xA4 +#define META_INS_READ_META 0x1D + +int meta_process_apdu(const CAPDU *capdu, RAPDU *rapdu); + +#endif // CANOKEY_CORE_INCLUDE_META_H diff --git a/main/include/ndef.h b/main/include/ndef.h new file mode 100644 index 0000000..a97f53c --- /dev/null +++ b/main/include/ndef.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef CANOKEY_CORE_INCLUDE_NDEF_H +#define CANOKEY_CORE_INCLUDE_NDEF_H + +#include + +#define NDEF_INS_SELECT 0xA4 +#define NDEF_INS_READ_BINARY 0xB0 +#define NDEF_INS_UPDATE 0xD6 + +void ndef_poweroff(void); +int ndef_install(uint8_t reset); +int ndef_process_apdu(const CAPDU *capdu, RAPDU *rapdu); +int ndef_get_read_only(void); +int ndef_toggle_read_only(const CAPDU *capdu, RAPDU *rapdu); + +#endif // CANOKEY_CORE_INCLUDE_NDEF_H diff --git a/main/include/nfc.h b/main/include/nfc.h new file mode 100644 index 0000000..61db157 --- /dev/null +++ b/main/include/nfc.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef _NFC_H_ +#define _NFC_H_ + +#define NFC_CHIP_FM11NC 0 +#define NFC_CHIP_FM11NT 1 +#define NFC_CHIP_NA -1 + +#ifndef NFC_CHIP +#define NFC_CHIP NFC_CHIP_NA +#endif + +#if NFC_CHIP == NFC_CHIP_FM11NC + +#define FM_REG_FIFO_FLUSH 0x1 +#define FM_REG_FIFO_WORDCNT 0x2 +#define FM_REG_RF_STATUS 0x3 +#define FM_REG_RF_TXEN 0x4 +#define FM_REG_RF_BAUD 0x5 +#define FM_REG_RF_RATS 0x6 +#define FM_REG_MAIN_IRQ 0x7 +#define FM_REG_FIFO_IRQ 0x8 +#define FM_REG_AUX_IRQ 0x9 +#define FM_REG_MAIN_IRQ_MASK 0xA +#define FM_REG_FIFO_IRQ_MASK 0xB +#define FM_REG_AUX_IRQ_MASK 0xC +#define FM_REG_NFC_CFG 0xD +#define FM_REG_REGU_CFG 0xE + +#define FM_EEPROM_ATQA 0x03A0 +#define FM_EEPROM_ATS 0x03B0 + +#define RF_STATE_MASK 0xE0 + +#elif NFC_CHIP == NFC_CHIP_FM11NT + +#define FM_REG_USER_CFG0 0xFFE0 +#define FM_REG_USER_CFG1 0xFFE1 +#define FM_REG_USER_CFG2 0xFFE2 +#define FM_REG_RESET_SILENCE 0xFFE6 +#define FM_REG_STATUS 0xFFE7 +#define FM_REG_VOUT_EN_CFG 0xFFE9 +#define FM_REG_VOUT_RES_CFG 0xFFEA +#define FM_REG_FIFO_ACCESS 0xFFF0 +#define FM_REG_FIFO_FLUSH 0xFFF1 +#define FM_REG_FIFO_WORDCNT 0xFFF2 +#define FM_REG_RF_STATUS 0xFFF3 +#define FM_REG_RF_TXEN 0xFFF4 +#define FM_REG_RF_CFG 0xFFF5 +#define FM_REG_RF_RATS 0xFFF6 +#define FM_REG_MAIN_IRQ 0xFFF7 +#define FM_REG_FIFO_IRQ 0xFFF8 +#define FM_REG_AUX_IRQ 0xFFF9 +#define FM_REG_MAIN_IRQ_MASK 0xFFFA +#define FM_REG_FIFO_IRQ_MASK 0xFFFB +#define FM_REG_AUX_IRQ_MASK 0xFFFC + +#define FM_EEPROM_SN 0x0000 +#define FM_EEPROM_USER_CFG0 0x0390 +#define FM_EEPROM_USER_CFG1 0x0391 +#define FM_EEPROM_USER_CFG2 0x0392 +#define FM_EEPROM_ATS 0x03B0 +#define FM_EEPROM_ATQA 0x03BC +#define FM_EEPROM_CRC8 0x03BB + +#endif + +#define MAIN_IRQ_AUX (1 << 0) +#define MAIN_IRQ_FIFO (1 << 1) +#define MAIN_IRQ_ARBIT (1 << 2) +#define MAIN_IRQ_TX_DONE (1 << 3) +#define MAIN_IRQ_RX_DONE (1 << 4) +#define MAIN_IRQ_RX_START (1 << 5) +#define MAIN_IRQ_ACTIVE (1 << 6) +#define MAIN_IRQ_RF_ON (1 << 7) + +#define FIFO_IRQ_OVERFLOW (1 << 2) +#define FIFO_IRQ_WATER_LEVEL (1 << 3) + +#define AUX_IRQ_ERROR_MASK 0x78 + +#define PCB_MASK 0xC0 +#define PCB_I_BLOCK 0x00 +#define PCB_R_BLOCK 0x80 +#define PCB_S_BLOCK 0xC0 +#define PCB_I_CHAINING 0x10 + +#define R_BLOCK_MASK 0xB2 +#define R_ACK 0xA2 +#define R_NAK 0xB2 + +#define S_WTX 0xF2 + +#define NFC_STATE_IDLE 0x00 +#define NFC_STATE_BUSY 0x01 + +void nfc_init(void); +void nfc_handler(void); +void nfc_loop(void); + +#endif // _NFC_H_ diff --git a/main/include/oath.h b/main/include/oath.h new file mode 100644 index 0000000..8fd0195 --- /dev/null +++ b/main/include/oath.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef CANOKEY_CORE_OATH_OATH_H_ +#define CANOKEY_CORE_OATH_OATH_H_ + +#include + +#define ATTR_DEFAULT_RECORD 0x01 +#define ATTR_KEY 0x02 +#define ATTR_HANDLE 0x03 + +#define OATH_TAG_NAME 0x71 +#define OATH_TAG_NAME_LIST 0x72 +#define OATH_TAG_KEY 0x73 +#define OATH_TAG_CHALLENGE 0x74 +#define OATH_TAG_FULL_RESPONSE 0x75 +#define OATH_TAG_RESPONSE 0x76 +#define OATH_TAG_NO_RESP 0x77 +#define OATH_TAG_PROPERTY 0x78 +#define OATH_TAG_VERSION 0x79 +#define OATH_TAG_COUNTER 0x7A +#define OATH_TAG_ALGORITHM 0x7B +#define OATH_TAG_REQ_TOUCH 0x7C + +#define OATH_INS_PUT 0x01 +#define OATH_INS_DELETE 0x02 +#define OATH_INS_SET_CODE 0x03 +#define OATH_INS_RENAME 0x05 +#define OATH_INS_LIST 0xA1 +#define OATH_INS_CALCULATE 0xA2 +#define OATH_INS_VALIDATE 0xA3 +#define OATH_INS_SELECT 0xA4 +#define OATH_INS_SEND_REMAINING 0xA5 +#define OATH_INS_SET_DEFAULT 0x55 + +#define OATH_ALG_MASK 0x0F +#define OATH_ALG_SHA1 0x01 +#define OATH_ALG_SHA256 0x02 +#define OATH_ALG_SHA512 0x03 + +#define OATH_TYPE_MASK 0xF0 +#define OATH_TYPE_HOTP 0x10 +#define OATH_TYPE_TOTP 0x20 + +#define OATH_PROP_INC 0x01 +#define OATH_PROP_TOUCH 0x02 +#define OATH_PROP_ALL_FLAGS 0x03 // OR of flags above + +#define MAX_NAME_LEN 64 +#define MAX_KEY_LEN 66 // 64 + 2 for algo & digits +#define MAX_CHALLENGE_LEN 8 +#define HANDLE_LEN 8 +#define KEY_LEN 16 + +typedef struct { + uint8_t name_len; + uint8_t name[MAX_NAME_LEN]; + uint8_t key_len; + // Byte 0 is type(higher half)/algorithm(lower half). + // Byte 1 is number of digits. + // Remaining is the secret. + uint8_t key[MAX_KEY_LEN]; + uint8_t prop; + uint8_t challenge[MAX_CHALLENGE_LEN]; +} __packed OATH_RECORD; + +void oath_poweroff(void); +int oath_install(uint8_t reset); +int oath_process_apdu(const CAPDU *capdu, RAPDU *rapdu); +int oath_process_one_touch(char *output, size_t maxlen); + +#endif // CANOKEY_CORE_OATH_OATH_H_ diff --git a/main/include/openpgp.h b/main/include/openpgp.h new file mode 100644 index 0000000..ef3fcfd --- /dev/null +++ b/main/include/openpgp.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef CANOKEY_CORE_OPENPGP_OPENPGP_H +#define CANOKEY_CORE_OPENPGP_OPENPGP_H + +#include + +#define OPENPGP_INS_SELECT 0xA4 +#define OPENPGP_INS_SELECT_DATA 0xA5 +#define OPENPGP_INS_GET_DATA 0xCA +#define OPENPGP_INS_GET_NEXT_DATA 0xCC +#define OPENPGP_INS_VERIFY 0x20 +#define OPENPGP_INS_CHANGE_REFERENCE_DATA 0x24 +#define OPENPGP_INS_RESET_RETRY_COUNTER 0x2C +#define OPENPGP_INS_INTERNAL_AUTHENTICATE 0x88 +#define OPENPGP_INS_PSO 0x2A +#define OPENPGP_INS_PUT_DATA 0xDA +#define OPENPGP_INS_IMPORT_KEY 0xDB +#define OPENPGP_INS_GENERATE_ASYMMETRIC_KEY_PAIR 0x47 +#define OPENPGP_INS_TERMINATE 0xE6 +#define OPENPGP_INS_ACTIVATE 0x44 + +#define TAG_AID 0x4F +#define TAG_LOGIN 0x5E +#define TAG_URL 0x5F50 +#define TAG_HISTORICAL_BYTES 0x5F52 +#define TAG_CARDHOLDER_RELATED_DATA 0x65 +#define TAG_NAME 0x5B +#define TAG_LANG 0x5F2D +#define TAG_SEX 0x5F35 +#define TAG_APPLICATION_RELATED_DATA 0x6E +#define TAG_DISCRETIONARY_DATA_OBJECTS 0x73 +#define TAG_EXTENDED_CAPABILITIES 0xC0 +#define TAG_ALGORITHM_ATTRIBUTES_SIG 0xC1 +#define TAG_ALGORITHM_ATTRIBUTES_DEC 0xC2 +#define TAG_ALGORITHM_ATTRIBUTES_AUT 0xC3 +#define TAG_PW_STATUS 0xC4 +#define TAG_KEY_FINGERPRINTS 0xC5 +#define TAG_CA_FINGERPRINTS 0xC6 +#define TAG_KEY_SIG_FINGERPRINT 0xC7 +#define TAG_KEY_DEC_FINGERPRINT 0xC8 +#define TAG_KEY_AUT_FINGERPRINT 0xC9 +#define TAG_KEY_CA1_FINGERPRINT 0xCA +#define TAG_KEY_CA2_FINGERPRINT 0xCB +#define TAG_KEY_CA3_FINGERPRINT 0xCC +#define TAG_KEY_GENERATION_DATES 0xCD +#define TAG_KEY_SIG_GENERATION_DATES 0xCE +#define TAG_KEY_DEC_GENERATION_DATES 0xCF +#define TAG_KEY_AUT_GENERATION_DATES 0xD0 +#define TAG_RESETTING_CODE 0xD3 +#define TAG_SECURITY_SUPPORT_TEMPLATE 0x7A +#define TAG_DIGITAL_SIG_COUNTER 0x93 +#define TAG_CARDHOLDER_CERTIFICATE 0x7F21 +#define TAG_EXTENDED_LENGTH_INFO 0x7F66 +#define TAG_GENERAL_FEATURE_MANAGEMENT 0x7F74 +#define TAG_KEY_INFO 0xDE +#define TAG_ALGORITHM_INFORMATION 0xFA +#define TAG_UIF_SIG 0xD6 +#define TAG_UIF_DEC 0xD7 +#define TAG_UIF_AUT 0xD8 +#define TAG_UIF_CACHE_TIME 0x0102 + +void openpgp_poweroff(void); +int openpgp_install(uint8_t reset); +int openpgp_process_apdu(const CAPDU *capdu, RAPDU *rapdu); + +#endif // CANOKEY_CORE_OPENPGP_OPENPGP_H diff --git a/main/include/pin.h b/main/include/pin.h new file mode 100644 index 0000000..d55f65f --- /dev/null +++ b/main/include/pin.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef CANOKEY_CORE_SRC_PIN_H +#define CANOKEY_CORE_SRC_PIN_H + +#include + +typedef struct { + uint8_t min_length; + uint8_t max_length; + uint8_t is_validated; + char path[]; +} pin_t; + +#define PIN_IO_FAIL -1 +#define PIN_AUTH_FAIL -2 +#define PIN_LENGTH_INVALID -3 +#define PIN_MAX_LENGTH 64 + +int pin_create(const pin_t *pin, const void *buf, uint8_t len, + uint8_t max_retries); +int pin_verify(pin_t *pin, const void *buf, uint8_t len, uint8_t *retries); +int pin_update(pin_t *pin, const void *buf, uint8_t len); +int pin_get_size(const pin_t *pin); +int pin_get_retries(const pin_t *pin); +int pin_get_default_retries(const pin_t *pin); +int pin_clear(const pin_t *pin); + +#endif // CANOKEY_CORE_SRC_PIN_H diff --git a/main/include/piv.h b/main/include/piv.h new file mode 100644 index 0000000..cf1672f --- /dev/null +++ b/main/include/piv.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef CANOKEY_CORE_INCLUDE_PIV_H_ +#define CANOKEY_CORE_INCLUDE_PIV_H_ + +#include + +#define PIV_INS_VERIFY 0x20 +#define PIV_INS_CHANGE_REFERENCE_DATA 0x24 +#define PIV_INS_RESET_RETRY_COUNTER 0x2C +#define PIV_INS_GENERATE_ASYMMETRIC_KEY_PAIR 0x47 +#define PIV_INS_GENERAL_AUTHENTICATE 0x87 +#define PIV_INS_SELECT 0xA4 +#define PIV_INS_GET_DATA_RESPONSE 0xC0 +#define PIV_INS_GET_DATA 0xCB +#define PIV_INS_PUT_DATA 0xDB +#define PIV_INS_GET_METADATA 0xF7 +#define PIV_INS_GET_SERIAL 0xF8 +#define PIV_INS_RESET 0xFB +#define PIV_INS_GET_VERSION 0xFD +#define PIV_INS_IMPORT_ASYMMETRIC_KEY 0xFE +#define PIV_INS_SET_MANAGEMENT_KEY 0xFF + +#define PIV_INS_ALGORITHM_EXTENSION 0xEE + +typedef struct { + uint8_t enabled; + uint8_t ed25519; + uint8_t rsa3072; + uint8_t rsa4096; + uint8_t x25519; + uint8_t secp256k1; + uint8_t sm2; +} __packed piv_algorithm_extension_config_t; + +int piv_install(uint8_t reset); +void piv_poweroff(void); +int piv_process_apdu(const CAPDU *capdu, RAPDU *rapdu); + +#endif // CANOKEY_CORE_INCLUDE_PIV_H_ diff --git a/main/key.c b/main/key.c new file mode 100644 index 0000000..69648c0 --- /dev/null +++ b/main/key.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "ecc.h" +#include "memzero.h" +#include +#include + +#define KEY_META_ATTR 0xFF +#define CEIL_DIV_SQRT2 0xB504F334 +#define MAX_KEY_TEMPLATE_LENGTH 0x16 + +int ck_encode_public_key(ck_key_t *key, uint8_t *buf, bool include_length) { + int off = 0; + + switch (key->meta.type) { + case SECP256R1: + case SECP256K1: + case SECP384R1: + case SM2: + if (include_length) { + buf[off++] = PUBLIC_KEY_LENGTH[key->meta.type] + 3; // tag, length, and 0x04 + } + buf[off++] = 0x86; + buf[off++] = PUBLIC_KEY_LENGTH[key->meta.type] + 1; // 0x04 + buf[off++] = 0x04; + memcpy(&buf[off], key->ecc.pub, PUBLIC_KEY_LENGTH[key->meta.type]); + off += PUBLIC_KEY_LENGTH[key->meta.type]; + break; + + case ED25519: + case X25519: + if (include_length) { + buf[off++] = PUBLIC_KEY_LENGTH[key->meta.type] + 2; // tag, length + } + buf[off++] = 0x86; + buf[off++] = PUBLIC_KEY_LENGTH[key->meta.type]; + memcpy(&buf[off], key->ecc.pub, PUBLIC_KEY_LENGTH[key->meta.type]); + if (key->meta.type == X25519) { + swap_big_number_endian(&buf[off]); // Public key of x25519 is encoded in little endian + } + off += PUBLIC_KEY_LENGTH[key->meta.type]; + break; + + case RSA2048: + case RSA3072: + case RSA4096: + if (include_length) { // 3-byte length + buf[off++] = 0x82; + // 6 = modulus: tag (1), length (3); exponent: tag (1), length (1) + buf[off++] = HI(6 + PUBLIC_KEY_LENGTH[key->meta.type] + E_LENGTH); + buf[off++] = LO(6 + PUBLIC_KEY_LENGTH[key->meta.type] + E_LENGTH); + } + buf[off++] = 0x81; // modulus + buf[off++] = 0x82; + buf[off++] = HI(PUBLIC_KEY_LENGTH[key->meta.type]); + buf[off++] = LO(PUBLIC_KEY_LENGTH[key->meta.type]); + rsa_get_public_key(&key->rsa, &buf[off]); + off += PUBLIC_KEY_LENGTH[key->meta.type]; + buf[off++] = 0x82; // exponent + buf[off++] = E_LENGTH; + memcpy(&buf[off], key->rsa.e, E_LENGTH); + off += E_LENGTH; + break; + + default: + return -1; + } + + return off; +} + +int ck_parse_piv_policies(ck_key_t *key, const uint8_t *buf, size_t buf_len) { + const uint8_t *end = buf + buf_len; + + while (buf < end) { + switch (*buf++) { + case 0xAA: + DBG_MSG("May have pin policy\n"); + if (buf < end && *buf++ != 0x01) { + DBG_MSG("Wrong length for pin policy\n"); + return KEY_ERR_LENGTH; + } + if (buf < end && (*buf > PIN_POLICY_ALWAYS || *buf < PIN_POLICY_NEVER)) { + DBG_MSG("Wrong data for pin policy\n"); + return KEY_ERR_DATA; + } + key->meta.pin_policy = *buf++; + break; + + case 0xAB: + DBG_MSG("May have touch policy\n"); + if (buf < end && *buf++ != 0x01) { + DBG_MSG("Wrong length for touch policy\n"); + return KEY_ERR_LENGTH; + } + if (buf < end && (*buf > TOUCH_POLICY_CACHED || *buf < TOUCH_POLICY_NEVER)) { + DBG_MSG("Wrong data for touch policy\n"); + return KEY_ERR_DATA; + } + key->meta.touch_policy = *buf++; + break; + + default: + buf = end; + break; + } + } + + return 0; +} + +int ck_parse_piv(ck_key_t *key, const uint8_t *buf, size_t buf_len) { + memzero(key->data, sizeof(rsa_key_t)); + key->meta.origin = KEY_ORIGIN_IMPORTED; + + const uint8_t *p = buf; + + switch (key->meta.type) { + case SECP256R1: + case SECP256K1: + case SECP384R1: + case SM2: + case ED25519: + case X25519: { + + if (buf_len < PRIVATE_KEY_LENGTH[key->meta.type] + 2) { + DBG_MSG("too short\n"); + return KEY_ERR_LENGTH; + } + if (*p++ != 0x06) { + DBG_MSG("invalid tag\n"); + return KEY_ERR_DATA; + } + if (*p++ != PRIVATE_KEY_LENGTH[key->meta.type]) { + DBG_MSG("invalid private key length\n"); + return KEY_ERR_LENGTH; + } + memcpy(key->ecc.pri, p, PRIVATE_KEY_LENGTH[key->meta.type]); + if (!ecc_verify_private_key(key->meta.type, &key->ecc)) { + memzero(key, sizeof(ck_key_t)); + return KEY_ERR_DATA; + } + if (ecc_complete_key(key->meta.type, &key->ecc) < 0) { + memzero(key, sizeof(ck_key_t)); + return KEY_ERR_PROC; + } + p += PRIVATE_KEY_LENGTH[key->meta.type]; + break; + } + + case RSA2048: + case RSA3072: + case RSA4096: { + int fail; + size_t length_size; + + key->rsa.nbits = PRIVATE_KEY_LENGTH[key->meta.type] * 16; + *(uint32_t *)key->rsa.e = htobe32(65537); + + uint8_t *data_ptr[] = {key->rsa.p, key->rsa.q, key->rsa.dp, key->rsa.dq, key->rsa.qinv}; + + for (int i = 1; i <= 5; ++i) { + if ((size_t)(p - buf) >= buf_len) return KEY_ERR_LENGTH; + if (*p++ != i) return KEY_ERR_DATA; + const size_t len = tlv_get_length_safe(p, buf_len - (p - buf), &fail, &length_size); + if (fail) return KEY_ERR_LENGTH; + if (len > PRIVATE_KEY_LENGTH[key->meta.type]) return KEY_ERR_DATA; + p += length_size; + memcpy(data_ptr[i - 1] + (PRIVATE_KEY_LENGTH[key->meta.type] - len), p, len); + p += len; + } + + if (be32toh(*(uint32_t *)key->rsa.p) < CEIL_DIV_SQRT2 || be32toh(*(uint32_t *)key->rsa.q) < CEIL_DIV_SQRT2) { + memzero(key, sizeof(ck_key_t)); + return KEY_ERR_DATA; + } + + break; + } + + default: + return -1; + } + + return ck_parse_piv_policies(key, p, buf + buf_len - p); +} + +/* + * RSA: + * 7F48 xx Cardholder private key template + * 91 xx e + * 92 xx p + * 93 xx q + * 94 xx qinv + * 95 xx dp + * 96 xx dq + * 5F48 xx Concatenation of key data as defined in DO 7F48 + * + * ECC: + * 7F48 xx Cardholder private key template + * 92 xx private key + * 99 xx public key (optional) + * 5F48 xx Concatenation of key data as defined in DO 7F48 + */ +int ck_parse_openpgp(ck_key_t *key, const uint8_t *buf, size_t buf_len) { + memzero(key->data, sizeof(rsa_key_t)); + key->meta.origin = KEY_ORIGIN_IMPORTED; + + const uint8_t *p = buf; + int fail; + size_t length_size; + + // Cardholder private key template + if ((size_t)(p + 2 - buf) >= buf_len) return KEY_ERR_LENGTH; + if (*p++ != 0x7F || *p++ != 0x48) return KEY_ERR_DATA; + size_t len = tlv_get_length_safe(p, buf_len - (p - buf), &fail, &length_size); + if (fail) return KEY_ERR_LENGTH; + if (len > MAX_KEY_TEMPLATE_LENGTH) return KEY_ERR_DATA; + p += length_size; + const uint8_t *data_tag = p + len; // saved for tag 5F48 + + switch (key->meta.type) { + case SECP256R1: + case SECP256K1: + case SECP384R1: + case SM2: + case ED25519: + case X25519: { + if ((size_t)(p + 1 - buf) >= buf_len) return KEY_ERR_LENGTH; + if (*p++ != 0x92) return KEY_ERR_DATA; + const size_t data_pri_key_len = tlv_get_length_safe(p, buf_len - (p - buf), &fail, &length_size); + if (fail) return KEY_ERR_LENGTH; + if (data_pri_key_len > PRIVATE_KEY_LENGTH[key->meta.type]) return KEY_ERR_DATA; + p += length_size; + + size_t data_pub_key_len = 0; // this is optional + if (p < data_tag) { + if ((size_t)(p + 1 - buf) >= buf_len) return KEY_ERR_LENGTH; + if (*p++ == 0x99) { + data_pub_key_len = tlv_get_length_safe(p, buf_len - (p - buf), &fail, &length_size); + if (fail) return KEY_ERR_LENGTH; + if (data_pub_key_len > PUBLIC_KEY_LENGTH[key->meta.type] + 1) return KEY_ERR_DATA; + } + } + + // Concatenation of key data + p = data_tag; + if ((size_t)(p + 2 - buf) >= buf_len) return KEY_ERR_LENGTH; + if (*p++ != 0x5F || *p++ != 0x48) return KEY_ERR_DATA; + len = tlv_get_length_safe(p, buf_len - (p - buf), &fail, &length_size); + if (fail || len != data_pri_key_len + data_pub_key_len) return KEY_ERR_DATA; + p += length_size; + const int n_leading_zeros = PRIVATE_KEY_LENGTH[key->meta.type] - data_pri_key_len; + if ((size_t)(p + data_pri_key_len - buf) > buf_len) return KEY_ERR_LENGTH; + memcpy(key->ecc.pri + n_leading_zeros, p, data_pri_key_len); + + if (!ecc_verify_private_key(key->meta.type, &key->ecc)) { + memzero(key, sizeof(ck_key_t)); + return KEY_ERR_DATA; + } + if (ecc_complete_key(key->meta.type, &key->ecc) < 0) { + memzero(key, sizeof(ck_key_t)); + return KEY_ERR_PROC; + } + + return 0; + } + + case RSA2048: + case RSA3072: + case RSA4096: { + // 0x91: e + if ((size_t)(p + 1 - buf) >= buf_len) return KEY_ERR_LENGTH; + if (*p++ != 0x91) return KEY_ERR_DATA; + len = tlv_get_length_safe(p, buf_len - (p - buf), &fail, &length_size); + if (fail) return KEY_ERR_LENGTH; + if (len != E_LENGTH) return KEY_ERR_DATA; + p += length_size; + + // 0x92: p + if ((size_t)(p + 1 - buf) >= buf_len) return KEY_ERR_LENGTH; + if (*p++ != 0x92) return KEY_ERR_DATA; + len = tlv_get_length_safe(p, buf_len - (p - buf), &fail, &length_size); + if (fail) return KEY_ERR_LENGTH; + if (len != PRIVATE_KEY_LENGTH[key->meta.type]) return KEY_ERR_DATA; + p += length_size; + + // 0x93: q + if ((size_t)(p + 1 - buf) >= buf_len) return KEY_ERR_LENGTH; + if (*p++ != 0x93) return KEY_ERR_DATA; + len = tlv_get_length_safe(p, buf_len - (p - buf), &fail, &length_size); + if (fail) return KEY_ERR_LENGTH; + if (len != PRIVATE_KEY_LENGTH[key->meta.type]) return KEY_ERR_DATA; + p += length_size; + + // 0x94: qinv, may be less than p/q's length + if ((size_t)(p + 1 - buf) >= buf_len) return KEY_ERR_LENGTH; + if (*p++ != 0x94) return KEY_ERR_DATA; + const size_t qinv_len = tlv_get_length_safe(p, buf_len - (p - buf), &fail, &length_size); + if (fail) return KEY_ERR_LENGTH; + if (qinv_len > PRIVATE_KEY_LENGTH[key->meta.type]) return KEY_ERR_DATA; + p += length_size; + + // 0x94: dp, may be less than p/q's length + if ((size_t)(p + 1 - buf) >= buf_len) return KEY_ERR_LENGTH; + if (*p++ != 0x95) return KEY_ERR_DATA; + const size_t dp_len = tlv_get_length_safe(p, buf_len - (p - buf), &fail, &length_size); + if (fail) return KEY_ERR_LENGTH; + if (dp_len > PRIVATE_KEY_LENGTH[key->meta.type]) return KEY_ERR_DATA; + p += length_size; + + // 0x94: dq, may be less than p/q's length + if ((size_t)(p + 1 - buf) >= buf_len) return KEY_ERR_LENGTH; + if (*p++ != 0x96) return KEY_ERR_DATA; + const size_t dq_len = tlv_get_length_safe(p, buf_len - (p - buf), &fail, &length_size); + if (fail) return KEY_ERR_LENGTH; + if (dq_len > PRIVATE_KEY_LENGTH[key->meta.type]) return KEY_ERR_DATA; + + // Concatenation of key data + p = data_tag; + if ((size_t)(p + 2 - buf) >= buf_len) return KEY_ERR_LENGTH; + if (*p++ != 0x5F || *p++ != 0x48) return KEY_ERR_DATA; + len = tlv_get_length_safe(p, buf_len - (p - buf), &fail, &length_size); + if (fail) return KEY_ERR_LENGTH; + if (len != PRIVATE_KEY_LENGTH[key->meta.type] * 2 + qinv_len + dp_len + dq_len + E_LENGTH) return KEY_ERR_DATA; + p += length_size; + + if ((size_t)(p + len - buf) > buf_len) return KEY_ERR_LENGTH; + key->rsa.nbits = PRIVATE_KEY_LENGTH[key->meta.type] * 16; + memcpy(key->rsa.e, p, E_LENGTH); + p += E_LENGTH; + memcpy(key->rsa.p, p, PRIVATE_KEY_LENGTH[key->meta.type]); + p += PRIVATE_KEY_LENGTH[key->meta.type]; + memcpy(key->rsa.q, p, PRIVATE_KEY_LENGTH[key->meta.type]); + p += PRIVATE_KEY_LENGTH[key->meta.type]; + memcpy(key->rsa.qinv + PRIVATE_KEY_LENGTH[key->meta.type] - qinv_len, p, qinv_len); + p += qinv_len; + memcpy(key->rsa.dp + PRIVATE_KEY_LENGTH[key->meta.type] - dp_len, p, dp_len); + p += dp_len; + memcpy(key->rsa.dq + PRIVATE_KEY_LENGTH[key->meta.type] - dq_len, p, dq_len); + if (be32toh(*(uint32_t *)key->rsa.p) < CEIL_DIV_SQRT2 || be32toh(*(uint32_t *)key->rsa.q) < CEIL_DIV_SQRT2) { + memzero(key, sizeof(ck_key_t)); + return KEY_ERR_DATA; + } + + return 0; + } + + default: + return -1; + } +} + +int ck_read_key_metadata(const char *path, key_meta_t *meta) { + return read_attr(path, KEY_META_ATTR, meta, sizeof(key_meta_t)); +} + +int ck_write_key_metadata(const char *path, const key_meta_t *meta) { + return write_attr(path, KEY_META_ATTR, meta, sizeof(key_meta_t)); +} + +int ck_read_key(const char *path, ck_key_t *key) { + const int err = ck_read_key_metadata(path, &key->meta); + if (err < 0) return err; + return read_file(path, key->data, 0, sizeof(rsa_key_t)); +} + +int ck_write_key(const char *path, const ck_key_t *key) { + const int err = write_file(path, key->data, 0, sizeof(rsa_key_t), 1); + if (err < 0) return err; + return ck_write_key_metadata(path, &key->meta); +} + +int ck_generate_key(ck_key_t *key) { + key->meta.origin = KEY_ORIGIN_GENERATED; + + if (IS_ECC(key->meta.type)) { + if (ecc_generate(key->meta.type, &key->ecc) < 0) { + memzero(key, sizeof(ck_key_t)); + return -1; + } + return 0; + } else if (IS_RSA(key->meta.type)) { + if (rsa_generate_key(&key->rsa, PUBLIC_KEY_LENGTH[key->meta.type] * 8) < 0) { + memzero(key, sizeof(ck_key_t)); + return -1; + } + return 0; + } else { + return -1; + } +} + +int ck_sign(const ck_key_t *key, const uint8_t *input, size_t input_len, uint8_t *sig) { + DBG_MSG("Data: "); + PRINT_HEX(input, input_len); + if (IS_ECC(key->meta.type)) { + DBG_MSG("Private Key: "); + PRINT_HEX(key->ecc.pri, PRIVATE_KEY_LENGTH[key->meta.type]); + DBG_MSG("Public Key: "); + PRINT_HEX(key->ecc.pub, PUBLIC_KEY_LENGTH[key->meta.type]); + if (ecc_sign(key->meta.type, &key->ecc, input, input_len, sig) < 0) { + ERR_MSG("ECC signing failed\n"); + DBG_KEY_META(&key->meta); + return -1; + } + } else if (IS_RSA(key->meta.type)) { + DBG_MSG("Key: "); + PRINT_HEX(key->rsa.p, PRIVATE_KEY_LENGTH[key->meta.type]); + PRINT_HEX(key->rsa.q, PRIVATE_KEY_LENGTH[key->meta.type]); + if (rsa_sign_pkcs_v15(&key->rsa, input, input_len, sig) < 0) { + ERR_MSG("RSA signing failed\n"); + DBG_KEY_META(&key->meta); + return -1; + } + } else { + return -1; + } + DBG_MSG("Sig: "); + PRINT_HEX(sig, SIGNATURE_LENGTH[key->meta.type]); + return SIGNATURE_LENGTH[key->meta.type]; +} diff --git a/main/main.c b/main/main.c index e6d7c36..e73569b 100644 --- a/main/main.c +++ b/main/main.c @@ -13,13 +13,12 @@ #include "driver/gpio.h" #include "sdkconfig.h" #include "device.h" +#include "ccid_device.h" - -#define APP_BUTTON (GPIO_NUM_0) // Use BOOT signal by default static const char *TAG = "main"; /************* TinyUSB descriptors ****************/ -#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_INOUT_DESC_LEN) +#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_INOUT_DESC_LEN + TUD_CCID_DESC_LEN ) static const tusb_desc_device_t hid_u2f_device_desc = { .bLength = sizeof(hid_u2f_device_desc), @@ -47,7 +46,7 @@ static const tusb_desc_device_t hid_u2f_device_desc = { .iSerialNumber = 0x03, .bNumConfigurations = 0x01}; -static char const *hid_u2f_string_descriptor[] = { +static char const *u2f_string_descriptor[] = { (const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409) CONFIG_TINYUSB_DESC_MANUFACTURER_STRING, // 1: Manufacturer CONFIG_TINYUSB_DESC_PRODUCT_STRING, // 2: Product @@ -70,11 +69,13 @@ const uint8_t u2f_report_descriptor[] = { static const uint8_t u2f_configuration_descriptor[] = { // Configuration number, interface count, string index, total length, attribute, power in mA - TUD_CONFIG_DESCRIPTOR(1, 1, 0, TUSB_DESC_TOTAL_LEN, 0, 100), + TUD_CONFIG_DESCRIPTOR(1, 2, 0, TUSB_DESC_TOTAL_LEN, 0, 100), // Interface number, string index, protocol, report descriptor len, EP OUT & IN address, size & polling interval TUD_HID_INOUT_DESCRIPTOR(0, 2, HID_ITF_PROTOCOL_NONE, sizeof(u2f_report_descriptor), 0x01, 0x80 | 0x01, CFG_TUD_HID_EP_BUFSIZE, 5), + TUD_CCID_DESCRIPTOR(1, 2, CCID_EPOUT, CCID_EPIN, CFG_TUD_CCID_EP_BUFSIZE), + }; /********* TinyUSB HID callbacks ***************/ @@ -119,8 +120,8 @@ void app_main(void) ESP_LOGI(TAG, "USB initialization"); const tinyusb_config_t tusb_cfg = { .device_descriptor = &hid_u2f_device_desc, - .string_descriptor = hid_u2f_string_descriptor, - .string_descriptor_count = sizeof(hid_u2f_string_descriptor) / sizeof(hid_u2f_string_descriptor[0]), + .string_descriptor = u2f_string_descriptor, + .string_descriptor_count = sizeof(u2f_string_descriptor) / sizeof(u2f_string_descriptor[0]), .external_phy = false, .configuration_descriptor = u2f_configuration_descriptor, }; @@ -132,6 +133,6 @@ void app_main(void) { device_loop(0); - vTaskDelay(pdMS_TO_TICKS(100)); + vTaskDelay(pdMS_TO_TICKS(50)); } } diff --git a/main/pin.c b/main/pin.c new file mode 100644 index 0000000..141bad7 --- /dev/null +++ b/main/pin.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include + +#define RETRY_ATTR 0 +#define DEFAULT_RETRY_ATTR 1 + +int pin_create(const pin_t *pin, const void *buf, uint8_t len, uint8_t max_retries) { + int err = write_file(pin->path, buf, 0, len, 1); + if (err < 0) return PIN_IO_FAIL; + err = write_attr(pin->path, RETRY_ATTR, &max_retries, sizeof(max_retries)); + if (err < 0) return PIN_IO_FAIL; + err = write_attr(pin->path, DEFAULT_RETRY_ATTR, &max_retries, sizeof(max_retries)); + if (err < 0) return PIN_IO_FAIL; + return 0; +} + +int pin_verify(pin_t *pin, const void *buf, uint8_t len, uint8_t *retries) { + pin->is_validated = 0; + if (len < pin->min_length || len > pin->max_length) return PIN_LENGTH_INVALID; + uint8_t ctr; + int err = read_attr(pin->path, RETRY_ATTR, &ctr, sizeof(ctr)); + if (err < 0) return PIN_IO_FAIL; + if (retries) *retries = ctr; + if (ctr == 0) return PIN_AUTH_FAIL; + uint8_t pin_buf[PIN_MAX_LENGTH]; + int real_len = read_file(pin->path, pin_buf, 0, PIN_MAX_LENGTH); + if (real_len < 0) return PIN_IO_FAIL; + if (((real_len != (int)len) - memcmp(buf, pin_buf, len)) != 0) { // the two conditions should be both evaluated + --ctr; + if (retries) *retries = ctr; + err = write_attr(pin->path, RETRY_ATTR, &ctr, sizeof(ctr)); + if (err < 0) { + memzero(pin_buf, sizeof(pin_buf)); + return PIN_IO_FAIL; + } + memzero(pin_buf, sizeof(pin_buf)); +#ifndef FUZZ // skip verification while fuzzing + return PIN_AUTH_FAIL; +#endif + } + pin->is_validated = 1; + err = read_attr(pin->path, DEFAULT_RETRY_ATTR, &ctr, sizeof(ctr)); + if (err < 0) { + memzero(pin_buf, sizeof(pin_buf)); + return PIN_IO_FAIL; + } + err = write_attr(pin->path, RETRY_ATTR, &ctr, sizeof(ctr)); + if (err < 0) { + memzero(pin_buf, sizeof(pin_buf)); + return PIN_IO_FAIL; + } + memzero(pin_buf, sizeof(pin_buf)); + return 0; +} + +int pin_update(pin_t *pin, const void *buf, uint8_t len) { + if (len < pin->min_length || len > pin->max_length) return PIN_LENGTH_INVALID; + pin->is_validated = 0; + int err = write_file(pin->path, buf, 0, len, 1); + if (err < 0) return PIN_IO_FAIL; + uint8_t ctr; + err = read_attr(pin->path, DEFAULT_RETRY_ATTR, &ctr, sizeof(ctr)); + if (err < 0) return PIN_IO_FAIL; + err = write_attr(pin->path, RETRY_ATTR, &ctr, sizeof(ctr)); + if (err < 0) return PIN_IO_FAIL; + return 0; +} + +int pin_get_size(const pin_t *pin) { return get_file_size(pin->path); } + +int pin_get_retries(const pin_t *pin) { + if (pin_get_size(pin) == 0) return 0; + uint8_t ctr; + int err = read_attr(pin->path, RETRY_ATTR, &ctr, sizeof(ctr)); + if (err < 0) return PIN_IO_FAIL; + return ctr; +} + +int pin_get_default_retries(const pin_t *pin) { + if (pin_get_size(pin) == 0) return 0; + uint8_t ctr; + int err = read_attr(pin->path, DEFAULT_RETRY_ATTR, &ctr, sizeof(ctr)); + if (err < 0) return PIN_IO_FAIL; + return ctr; +} + +int pin_clear(const pin_t *pin) { + int err = write_file(pin->path, NULL, 0, 0, 1); + if (err < 0) return PIN_IO_FAIL; + uint8_t ctr; + err = read_attr(pin->path, DEFAULT_RETRY_ATTR, &ctr, sizeof(ctr)); + if (err < 0) return PIN_IO_FAIL; + err = write_attr(pin->path, RETRY_ATTR, &ctr, sizeof(ctr)); + if (err < 0) return PIN_IO_FAIL; + return 0; +} diff --git a/main/usb/ccid/ccid.c b/main/usb/ccid/ccid.c new file mode 100644 index 0000000..cdf49c5 --- /dev/null +++ b/main/usb/ccid/ccid.c @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include "spinlock.h" +#include "ccid_device.h" + +#define CCID_UpdateCommandStatus(cmd_status, icc_status) bulkin_data.bStatus = (cmd_status | icc_status) + +static uint8_t CCID_CheckCommandParams(uint32_t param_type); + +// Fi=372, Di=1, 372 cycles/ETU 10752 bits/s at 4.00 MHz +// BWT = 5.7s +static const uint8_t atr_ccid[] = {0x3B, 0xF7, 0x11, 0x00, 0x00, 0x81, 0x31, 0xFE, 0x65, + 0x43, 0x61, 0x6E, 0x6F, 0x6B, 0x65, 0x79, 0x99}; + +static empty_ccid_bulkin_data_t bulkin_time_extension; +ccid_bulkin_data_t bulkin_data; +ccid_bulkout_data_t bulkout_data; +static uint16_t ab_data_length; +static volatile uint8_t bulkout_state; +static volatile uint8_t has_cmd; +static volatile uint8_t bulkin_state; +//static volatile uint32_t send_data_spinlock; +static spinlock_t send_data_spinlock; +static CAPDU apdu_cmd; +static RAPDU apdu_resp; +uint8_t *global_buffer; + +void init_apdu_buffer(void) { + global_buffer = bulkin_data.abData; +} + + +uint8_t CCID_Response_SendData(const uint8_t *buf, uint16_t len, uint8_t is_time_extension_request) { + + if (!tud_ccid_mounted()) return 0; + uint8_t ret; + int retry = 0; + while (bulkin_state != CCID_STATE_IDLE) { + if (is_time_extension_request) + return 0; + else if (++retry > 50) + return 0; + else + device_delay(1); + } + + bulkin_state = len % CFG_TUD_CCID_EP_BUFSIZE == 0 ? CCID_STATE_DATA_IN_WITH_ZLP : CCID_STATE_DATA_IN; + ret = tud_ccid_write(buf, len); + + return ret; +} + +uint8_t CCID_Init(void) { + + spinlock_initialize(&send_data_spinlock); + bulkout_state = CCID_STATE_IDLE; + has_cmd = 0; + bulkout_data.abData = bulkin_data.abData; + apdu_cmd.data = bulkin_data.abData; + apdu_resp.data = bulkin_data.abData; + return 0; +} + +uint8_t CCID_OutEvent(uint8_t *data, uint8_t len) { + switch (bulkout_state) { + case CCID_STATE_IDLE: + if (len == 0) + bulkout_state = CCID_STATE_IDLE; + else if (len >= CCID_CMD_HEADER_SIZE) { + ab_data_length = len - CCID_CMD_HEADER_SIZE; + memcpy(&bulkout_data, data, CCID_CMD_HEADER_SIZE); + memcpy(bulkout_data.abData, data + CCID_CMD_HEADER_SIZE, ab_data_length); + bulkout_data.dwLength = letoh32(bulkout_data.dwLength); + bulkin_data.bSlot = bulkout_data.bSlot; + bulkin_data.bSeq = bulkout_data.bSeq; + if (ab_data_length == bulkout_data.dwLength) + has_cmd = 1; + else if (ab_data_length < bulkout_data.dwLength) { + if (bulkout_data.dwLength > ABDATA_SIZE) + bulkout_state = CCID_STATE_IDLE; + else + bulkout_state = CCID_STATE_RECEIVE_DATA; + } else + bulkout_state = CCID_STATE_IDLE; + } + break; + + case CCID_STATE_RECEIVE_DATA: + if (ab_data_length + len < bulkout_data.dwLength) { + memcpy(bulkout_data.abData + ab_data_length, data, len); + ab_data_length += len; + } else if (ab_data_length + len == bulkout_data.dwLength) { + memcpy(bulkout_data.abData + ab_data_length, data, len); + bulkout_state = CCID_STATE_IDLE; + has_cmd = 1; + } else + bulkout_state = CCID_STATE_IDLE; + } + return 0; +} + +/** + * @brief PC_to_RDR_IccPowerOn + * PC_TO_RDR_ICCPOWERON message execution, apply voltage and get ATR + * @param None + * @retval uint8_t status of the command execution + */ +static uint8_t PC_to_RDR_IccPowerOn(void) { + bulkin_data.dwLength = 0; + uint8_t error = CCID_CheckCommandParams(CHK_PARAM_SLOT | CHK_PARAM_DWLENGTH | CHK_PARAM_abRFU2); + if (error != 0) return error; + + uint8_t voltage = bulkout_data.bSpecific_0; + if (voltage != 0x00) { + CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); + return SLOTERROR_BAD_POWERSELECT; + } + + if (acquire_apdu_buffer(BUFFER_OWNER_CCID) != 0) { + CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); + return SLOTERROR_BAD_GUARDTIME; + } + + memcpy(bulkin_data.abData, atr_ccid, sizeof(atr_ccid)); + bulkin_data.dwLength = sizeof(atr_ccid); + CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_ACTIVE); + return SLOT_NO_ERROR; +} + +/** + * @brief PC_to_RDR_IccPowerOff + * Icc VCC is switched Off + * @param None + * @retval uint8_t error: status of the command execution + */ +static uint8_t PC_to_RDR_IccPowerOff(void) { + uint8_t error = CCID_CheckCommandParams(CHK_PARAM_SLOT | CHK_PARAM_abRFU3 | CHK_PARAM_DWLENGTH); + if (error != 0) return error; + + release_apdu_buffer(BUFFER_OWNER_CCID); + CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_INACTIVE); + return SLOT_NO_ERROR; +} + +/** + * @brief PC_to_RDR_GetSlotStatus + * Provides the Slot status to the host + * @param None + * @retval uint8_t status of the command execution + */ +static uint8_t PC_to_RDR_GetSlotStatus(void) { + uint8_t error = CCID_CheckCommandParams(CHK_PARAM_SLOT | CHK_PARAM_DWLENGTH | CHK_PARAM_abRFU3); + if (error != 0) return error; + CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_ACTIVE); + return SLOT_NO_ERROR; +} + +/** + * @brief PC_to_RDR_XfrBlock + * Handles the Block transfer from Host. + * Response to this command message is the RDR_to_PC_DataBlock + * @param None + * @retval uint8_t status of the command execution + */ +uint8_t PC_to_RDR_XfrBlock(void) { + uint8_t error = CCID_CheckCommandParams(CHK_PARAM_SLOT); + if (error != 0) return error; + + DBG_MSG("O: "); + PRINT_HEX(bulkout_data.abData, bulkout_data.dwLength); + + CAPDU *capdu = &apdu_cmd; + RAPDU *rapdu = &apdu_resp; + + if (build_capdu(&apdu_cmd, bulkout_data.abData, bulkout_data.dwLength) < 0) { + // abandon malformed apdu + LL = 0; + SW = SW_WRONG_LENGTH; + } else { + device_set_timeout(CCID_TimeExtensionLoop, TIME_EXTENSION_PERIOD); + process_apdu(capdu, rapdu); + device_set_timeout(NULL, 0); + } + + bulkin_data.dwLength = LL + 2; + bulkin_data.abData[LL] = HI(SW); + bulkin_data.abData[LL + 1] = LO(SW); + DBG_MSG("I: "); + PRINT_HEX(bulkin_data.abData, bulkin_data.dwLength); + CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_ACTIVE); + return SLOT_NO_ERROR; +} + +/** + * @brief PC_to_RDR_GetParameters + * Provides the ICC parameters to the host + * Response to this command message is the RDR_to_PC_Parameters + * @param None + * @retval uint8_t status of the command execution + */ +static uint8_t PC_to_RDR_GetParameters(void) { + uint8_t error = CCID_CheckCommandParams(CHK_PARAM_SLOT | CHK_PARAM_DWLENGTH | CHK_PARAM_abRFU3); + if (error != 0) return error; + CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_ACTIVE); + return SLOT_NO_ERROR; +} + +/** + * @brief RDR_to_PC_DataBlock + * Provide the data block response to the host + * Response for PC_to_RDR_IccPowerOn, PC_to_RDR_XfrBlock + * @param uint8_t errorCode: code to be returned to the host + * @retval None + */ +static void RDR_to_PC_DataBlock(uint8_t errorCode) { + bulkin_data.bMessageType = RDR_TO_PC_DATABLOCK; + bulkin_data.bError = errorCode; + bulkin_data.bSpecific = 0; +} + +/** + * @brief RDR_to_PC_SlotStatus + * Provide the Slot status response to the host + * Response for PC_to_RDR_IccPowerOff + * PC_to_RDR_GetSlotStatus + * @param uint8_t errorCode: code to be returned to the host + * @retval None + */ +static void RDR_to_PC_SlotStatus(uint8_t errorCode) { + bulkin_data.bMessageType = RDR_TO_PC_SLOTSTATUS; + bulkin_data.dwLength = 0; + bulkin_data.bError = errorCode; + bulkin_data.bSpecific = 0; +} + +/** + * @brief RDR_to_PC_Parameters + * Provide the data block response to the host + * Response for PC_to_RDR_GetParameters + * @param uint8_t errorCode: code to be returned to the host + * @retval None + */ +static void RDR_to_PC_Parameters(uint8_t errorCode) { + bulkin_data.bMessageType = RDR_TO_PC_PARAMETERS; + bulkin_data.bError = errorCode; + + if (errorCode == SLOT_NO_ERROR) + bulkin_data.dwLength = 7; + else + bulkin_data.dwLength = 0; + + bulkin_data.abData[0] = 0x11; // Fi=372, Di=1 + bulkin_data.abData[1] = 0x10; // Checksum: LRC, Convention: direct, ignored by CCID + bulkin_data.abData[2] = 0x00; // No extra guard time + bulkin_data.abData[3] = 0x15; // BWI = 1, CWI = 5 + bulkin_data.abData[4] = 0x00; // Stopping the Clock is not allowed + bulkin_data.abData[5] = 0xFE; // IFSC = 0xFE + bulkin_data.abData[6] = 0x00; // NAD + + bulkin_data.bSpecific = 0x01; +} + +/** + * @brief RDR_to_PC_Escape + * Provide the Escape response to the host + * Response for PC_to_RDR_Escape + * @param uint8_t errorCode: code to be returned to the host + * @retval None + */ +static void RDR_to_PC_Escape(uint8_t errorCode) { + bulkin_data.bMessageType = RDR_TO_PC_ESCAPE; + bulkin_data.dwLength = 0; + bulkin_data.bError = errorCode; + bulkin_data.bSpecific = 0; +} + +/** + * @brief CCID_CheckCommandParams + * Checks the specific parameters requested by the function and update + * status accordingly. This function is called from all + * PC_to_RDR functions + * @param uint32_t param_type : Parameter enum to be checked by calling + * function + * @retval uint8_t status + */ +static uint8_t CCID_CheckCommandParams(uint32_t param_type) { + bulkin_data.bStatus = BM_ICC_PRESENT_ACTIVE | BM_COMMAND_STATUS_NO_ERROR; + uint32_t parameter = param_type; + + if (parameter & CHK_PARAM_SLOT) { + if (bulkout_data.bSlot >= CCID_NUMBER_OF_SLOTS) { + CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_NO_ICC_PRESENT); + return SLOTERROR_BAD_SLOT; + } + } + + if (parameter & CHK_PARAM_DWLENGTH) { + if (bulkout_data.dwLength != 0) { + CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); + return SLOTERROR_BAD_LENTGH; + } + } + + if (parameter & CHK_PARAM_abRFU2) { + if ((bulkout_data.bSpecific_1 != 0) || (bulkout_data.bSpecific_2 != 0)) { + CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); + return SLOTERROR_BAD_ABRFU_2B; + } + } + + if (parameter & CHK_PARAM_abRFU3) { + if ((bulkout_data.bSpecific_0 != 0) || (bulkout_data.bSpecific_1 != 0) || (bulkout_data.bSpecific_2 != 0)) { + CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); + return SLOTERROR_BAD_ABRFU_3B; + } + } + + return 0; +} + +void CCID_Loop(void) { + if (!has_cmd) return; + has_cmd = 0; + + uint8_t errorCode; + switch (bulkout_data.bMessageType) { + case PC_TO_RDR_ICCPOWERON: + DBG_MSG("Slot power on\n"); + errorCode = PC_to_RDR_IccPowerOn(); + RDR_to_PC_DataBlock(errorCode); + break; + case PC_TO_RDR_ICCPOWEROFF: + DBG_MSG("Slot power off\n"); + errorCode = PC_to_RDR_IccPowerOff(); + RDR_to_PC_SlotStatus(errorCode); + break; + case PC_TO_RDR_GETSLOTSTATUS: + DBG_MSG("Slot get status\n"); + errorCode = PC_to_RDR_GetSlotStatus(); + RDR_to_PC_SlotStatus(errorCode); + break; + case PC_TO_RDR_XFRBLOCK: + errorCode = PC_to_RDR_XfrBlock(); + RDR_to_PC_DataBlock(errorCode); + break; + case PC_TO_RDR_GETPARAMETERS: + DBG_MSG("Slot get param\n"); + errorCode = PC_to_RDR_GetParameters(); + RDR_to_PC_Parameters(errorCode); + break; + case PC_TO_RDR_RESETPARAMETERS: + case PC_TO_RDR_SETPARAMETERS: + RDR_to_PC_Parameters(SLOTERROR_CMD_NOT_SUPPORTED); + break; + case PC_TO_RDR_ESCAPE: + RDR_to_PC_Escape(SLOTERROR_CMD_NOT_SUPPORTED); + break; + case PC_TO_RDR_SECURE: + bulkin_data.dwLength = 0; + RDR_to_PC_DataBlock(SLOTERROR_CMD_NOT_SUPPORTED); + break; + case PC_TO_RDR_ICCCLOCK: + case PC_TO_RDR_T0APDU: + case PC_TO_RDR_MECHANICAL: + case PC_TO_RDR_ABORT: + default: + RDR_to_PC_SlotStatus(SLOTERROR_CMD_NOT_SUPPORTED); + break; + } + + uint16_t len = bulkin_data.dwLength; + bulkin_data.dwLength = htole32(bulkin_data.dwLength); + device_spinlock_lock(&send_data_spinlock, true); + CCID_Response_SendData((uint8_t *)&bulkin_data, len + CCID_CMD_HEADER_SIZE, 0); + device_spinlock_unlock(&send_data_spinlock); +} + +void CCID_TimeExtensionLoop(void* arg) { + if (device_spinlock_lock(&send_data_spinlock, false) == 0) { // try lock + DBG_MSG("send t-ext\r\n"); + bulkin_time_extension.bMessageType = RDR_TO_PC_DATABLOCK; + bulkin_time_extension.dwLength = 0; + bulkin_time_extension.bSlot = bulkout_data.bSlot; + bulkin_time_extension.bSeq = bulkout_data.bSeq; + bulkin_time_extension.bStatus = BM_COMMAND_STATUS_TIME_EXTN; + bulkin_time_extension.bError = 1; // Request another 1 BTWs (5.7s) + bulkin_time_extension.bSpecific = 0; + CCID_Response_SendData((uint8_t *)&bulkin_time_extension, CCID_CMD_HEADER_SIZE, 1); + device_spinlock_unlock(&send_data_spinlock); + } + + device_set_timeout(CCID_TimeExtensionLoop, TIME_EXTENSION_PERIOD); +} + +//--------------------------------------------------------------------+ +// TinyUSB stack callbacks +//--------------------------------------------------------------------+ +// Invoked when received new data +void tud_ccid_rx_cb(uint8_t itf) { + DBG_MSG("tud_ccid_rx_cb, itf: %d\r\n", itf); + + if (itf != 0) return; + uint32_t len = tud_ccid_available(); + + switch (bulkout_state) { + case CCID_STATE_IDLE: + if (len == 0) + bulkout_state = CCID_STATE_IDLE; + else if (len >= CCID_CMD_HEADER_SIZE) { + ab_data_length = len - CCID_CMD_HEADER_SIZE; + tud_ccid_read(&bulkout_data, CCID_CMD_HEADER_SIZE); + tud_ccid_read(bulkout_data.abData, ab_data_length); + + bulkout_data.dwLength = tu_le32toh(bulkout_data.dwLength); + bulkin_data.bSlot = bulkout_data.bSlot; + bulkin_data.bSeq = bulkout_data.bSeq; + + if (ab_data_length == bulkout_data.dwLength) + has_cmd = 1; + else if (ab_data_length < bulkout_data.dwLength) { + if (bulkout_data.dwLength > ABDATA_SIZE) + bulkout_state = CCID_STATE_IDLE; + else + bulkout_state = CCID_STATE_RECEIVE_DATA; + } else + bulkout_state = CCID_STATE_IDLE; + } + break; + + case CCID_STATE_RECEIVE_DATA: + if (ab_data_length + len < bulkout_data.dwLength) { + tud_ccid_read(bulkout_data.abData + ab_data_length, len); + ab_data_length += len; + + } else if (ab_data_length + len == bulkout_data.dwLength) { + tud_ccid_read(bulkout_data.abData + ab_data_length, len); + + + bulkout_state = CCID_STATE_IDLE; + has_cmd = 1; + } else + bulkout_state = CCID_STATE_IDLE; + } +} + +// Invoked when last tx transfer finished +void tud_ccid_tx_cb(uint8_t itf, uint16_t sent_bytes) { + DBG_MSG("tud_ccid_tx_cb, itf=%d, sent_bytes=%d\r\n", itf, sent_bytes); + if (bulkin_state == CCID_STATE_DATA_IN_WITH_ZLP) { + bulkin_state = CCID_STATE_DATA_IN; + tud_ccid_write(NULL, 0); + } else { + bulkin_state = CCID_STATE_IDLE; + } +} \ No newline at end of file diff --git a/main/usb/ccid/ccid_device.c b/main/usb/ccid/ccid_device.c new file mode 100644 index 0000000..23f610c --- /dev/null +++ b/main/usb/ccid/ccid_device.c @@ -0,0 +1,223 @@ +#include +#include "esp_log.h" +#include "tusb.h" +#include "device/usbd.h" +#include "device/usbd_pvt.h" +#include "device/dcd.h" +#include "ccid_device.h" + +#define TAG "CCID" + +#define ITF_MEM_RESET_SIZE offsetof(ccidd_interface_t, rx_ff) + +// TODO: multiple instances to be tested & completed +CFG_TUSB_MEM_SECTION static ccidd_interface_t _ccidd_itf[CFG_TUD_CCID]; + +//--------------------------------------------------------------------+ +// Read API +//--------------------------------------------------------------------+ +static void _prep_out_transaction(ccidd_interface_t *p_itf) +{ + uint8_t const rhport = 0; + uint16_t available = tu_fifo_remaining(&p_itf->rx_ff); + + TU_VERIFY(available >= sizeof(p_itf->epout_buf), ); // This pre-check reduces endpoint claiming + TU_VERIFY(usbd_edpt_claim(rhport, p_itf->ep_out), ); // claim endpoint + + available = tu_fifo_remaining(&p_itf->rx_ff); // fifo can be changed before endpoint is claimed + if (available >= sizeof(p_itf->epout_buf)) + { + usbd_edpt_xfer(rhport, p_itf->ep_out, p_itf->epout_buf, sizeof(p_itf->epout_buf)); + } + else + { + usbd_edpt_release(rhport, p_itf->ep_out); // Release endpoint since we don't make any transfer + } +} + +uint32_t tud_ccid_n_read(uint8_t itf, void *buffer, uint32_t bufsize) +{ + ccidd_interface_t *p_itf = &_ccidd_itf[itf]; + TU_VERIFY(p_itf->ep_out); + + uint32_t const num_read = tu_fifo_read_n(&p_itf->rx_ff, buffer, bufsize); + _prep_out_transaction(p_itf); + return num_read; +} + +//--------------------------------------------------------------------+ +// Write API +//--------------------------------------------------------------------+ +uint32_t tud_ccid_write_n_flush(ccidd_interface_t *p_itf) +{ + if (!tu_fifo_count(&p_itf->tx_ff)) // No data to send + return 0; + + uint8_t const rhport = 0; + TU_VERIFY(usbd_edpt_claim(rhport, p_itf->ep_in), 0); // skip if previous transfer not complete + + uint16_t count = tu_fifo_read_n(&p_itf->tx_ff, p_itf->epin_buf, sizeof(p_itf->epin_buf)); + if (count) + { + TU_ASSERT(usbd_edpt_xfer(rhport, p_itf->ep_in, p_itf->epin_buf, count), 0); + return count; + } + else + { + usbd_edpt_release(rhport, p_itf->ep_in); // Release endpoint since we don't make any transfer + return 0; + } +} + +uint32_t tud_ccid_n_write(uint8_t itf, void const *buffer, uint32_t bufsize) +{ + ccidd_interface_t *p_itf = &_ccidd_itf[itf]; + TU_VERIFY(p_itf->ep_in); + + uint16_t ret = tu_fifo_write_n(&p_itf->tx_ff, buffer, bufsize); + return tud_ccid_write_n_flush(p_itf) > 0 ? ret : 0; +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +static void ccid_init(void) +{ + tu_memclr(&_ccidd_itf, sizeof(_ccidd_itf)); + + for (uint8_t i = 0; i < CFG_TUD_CCID; i++) + { + ccidd_interface_t *p_itf = &_ccidd_itf[i]; + tu_fifo_config(&p_itf->rx_ff, p_itf->rx_ff_buf, CFG_TUD_CCID_RX_BUFSIZE, 1, false); + tu_fifo_config(&p_itf->tx_ff, p_itf->tx_ff_buf, CFG_TUD_CCID_TX_BUFSIZE, 1, false); + } +} + +static void ccid_reset(uint8_t rhport) +{ + (void)rhport; + + for (uint8_t i = 0; i < CFG_TUD_CCID; i++) + { + ccidd_interface_t *p_itf = &_ccidd_itf[i]; + tu_memclr(p_itf, ITF_MEM_RESET_SIZE); + tu_fifo_clear(&p_itf->rx_ff); + tu_fifo_clear(&p_itf->tx_ff); + } +} + +static uint16_t ccid_open(uint8_t rhport, tusb_desc_interface_t const *desc_itf, uint16_t max_len) +{ + if (desc_itf->bInterfaceClass != TUSB_CLASS_SMART_CARD) + return 0; // not our interface class + + // desc_intf->bInterfaceSubClass == 0 && desc_intf->bInterfaceProtocol == 0 + uint16_t drv_len = sizeof(tusb_desc_interface_t); + TU_VERIFY(max_len >= drv_len, 0); + + uint8_t const *p_desc = (uint8_t const *)desc_itf; + + //------------- CCID descriptor -------------// + p_desc = tu_desc_next(p_desc); + TU_ASSERT(CCID_DESC_TYPE_CCID == tu_desc_type(p_desc), 0); + drv_len += tu_desc_len(p_desc); + + ccidd_interface_t *p_itf = NULL; + for (uint8_t i = 0; i < CFG_TUD_CCID; i++) + { // Find available interface + if (_ccidd_itf[i].ep_in == 0 && _ccidd_itf[i].ep_out == 0) + { + p_itf = &_ccidd_itf[i]; + break; + } + } + TU_ASSERT(p_itf); + + p_itf->itf_num = desc_itf->bInterfaceNumber; + (void)p_itf->itf_num; + + //------------- Endpoint Descriptor -------------// + p_desc = tu_desc_next(p_desc); + uint8_t numEp = desc_itf->bNumEndpoints; + TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, numEp, TUSB_XFER_BULK, &p_itf->ep_out, &p_itf->ep_in), 0); + drv_len += numEp * sizeof(tusb_desc_endpoint_t); + + if (p_itf->ep_out) + { + _prep_out_transaction(p_itf); + } + + if (p_itf->ep_in) + { + tud_ccid_write_n_flush(p_itf); + } + + return drv_len; +} + +static bool ccid_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) +{ + return false; // no control transfers supported +} + +static bool ccid_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + (void)rhport; + (void)result; + + uint8_t itf; + ccidd_interface_t *p_itf; + + for (itf = 0; itf < CFG_TUD_CCID; itf++) + { // Identify which interface to use + p_itf = &_ccidd_itf[itf]; + if ((ep_addr == p_itf->ep_out) || (ep_addr == p_itf->ep_in)) + break; + } + TU_ASSERT(itf < CFG_TUD_CCID); + + if (ep_addr == p_itf->ep_out) + { // receive new data + tu_fifo_write_n(&p_itf->rx_ff, p_itf->epout_buf, (uint16_t)xferred_bytes); + + if (tud_ccid_rx_cb) // invoke receive callback if available + tud_ccid_rx_cb(itf); + + _prep_out_transaction(p_itf); // prepare for next + } + else if (ep_addr == p_itf->ep_in) + { + if (tud_ccid_tx_cb) + tud_ccid_tx_cb(itf, (uint16_t)xferred_bytes); + + tud_ccid_write_n_flush(p_itf); + } + + return true; +} + +uint32_t tud_ccid_n_available(uint8_t itf) { return tu_fifo_count(&_ccidd_itf[itf].rx_ff); } + +bool tud_ccid_n_mounted(uint8_t itf) { + // Return true if the interface is mounted + return _ccidd_itf[itf].ep_in && _ccidd_itf[itf].ep_out; +} + +// static void ccid_sof(uint8_t rhport, uint32_t frame_count) { }// optional + +static usbd_class_driver_t const _ccid_driver = { +#if CFG_TUSB_DEBUG >= 2 + .name = "CCID", // +#endif + .init = ccid_init, // + .reset = ccid_reset, // + .open = ccid_open, // + .control_xfer_cb = ccid_control_xfer_cb, // + .xfer_cb = ccid_xfer_cb, // + .sof = NULL}; + +usbd_class_driver_t const *usbd_app_driver_get_cb(uint8_t *driver_count) +{ // callback to add application driver + *driver_count = 1; + return &_ccid_driver; +} \ No newline at end of file diff --git a/main/ctaphid.c b/main/usb/ctaphid/ctaphid.c similarity index 99% rename from main/ctaphid.c rename to main/usb/ctaphid/ctaphid.c index b4b1c0b..58d96d6 100644 --- a/main/ctaphid.c +++ b/main/usb/ctaphid/ctaphid.c @@ -272,4 +272,4 @@ void CTAPHID_SendKeepAlive(uint8_t status) frame.init.bcntl = 1; frame.init.data[0] = status; CTAPHID_SendFrame(); -} +} \ No newline at end of file