Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07729f807b | ||
|
|
e0c793dd0a | ||
|
|
9d6003d1e5 | ||
|
|
147a93d7fb | ||
|
|
f12c55805c | ||
|
|
7e10e25f96 | ||
|
|
9052c66a7f | ||
|
|
443ca69547 | ||
|
|
415c1b2e9c | ||
|
|
d87c1530c7 | ||
|
|
f90baaf095 | ||
|
|
1d7bdb0861 | ||
|
|
fa811e2a0f | ||
|
|
ff498ebfdf | ||
|
|
cceb735cc0 | ||
|
|
5a9de32e02 | ||
|
|
c9eacc4a3d | ||
|
|
c23d92ea28 | ||
|
|
da04fbb824 | ||
|
|
0bfa760903 | ||
|
|
bd9d4286d5 | ||
|
|
3d1c68fa40 | ||
|
|
26ac66e813 | ||
|
|
05afcd706e | ||
|
|
8c90dd55bd | ||
|
|
c6c1d0c6eb |
@@ -95,6 +95,7 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_config.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_vendor.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_large_blobs.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/management.c
|
||||
)
|
||||
if (${ENABLE_OATH_APP})
|
||||
set(SOURCES ${SOURCES}
|
||||
|
||||
16
README.md
16
README.md
@@ -27,6 +27,13 @@ Pico FIDO has implemented the following features:
|
||||
- credBlobs extension
|
||||
- largeBlobKey extension
|
||||
- largeBlobs support (2048 bytes máx.)
|
||||
- OATH (based on YKOATH protocol specification)
|
||||
- TOTP / HOTP
|
||||
- Yubikey OTP
|
||||
- Challenge-response generation
|
||||
- Emulated keyboard interface
|
||||
- Button press generates an OTP that is written directly is it was typed
|
||||
- Yubico YKMAN compatible
|
||||
|
||||
All these features are compliant with the specification. Therefore, if you detect some behaviour that is not expected or it does not follow the rules of specs, please open an issue.
|
||||
|
||||
@@ -38,6 +45,15 @@ If the Pico is stolen the contents of private and secret keys can be read.
|
||||
## Download
|
||||
Please, go to the [Release page](https://github.com/polhenarejos/pico-fido/releases "Release page") and download the UF2 file for your board.
|
||||
|
||||
Note that UF2 files are shiped with a dummy VID/PID to avoid license issues (FEFF:FCFD). If you are planning to use it with OpenSC or similar, you should modify Info.plist of CCID driver to add these VID/PID or use the [Pico Patcher tool](https://www.picokeys.com/pico-patcher/).
|
||||
|
||||
Alternatively you can use the legacy VID/PID patcher as follows:
|
||||
`./patch_vidpid.sh VID:PID input_hsm_file.uf2 output_hsm_file.uf2`
|
||||
|
||||
You can use whatever VID/PID (i.e., 234b:0000 from FISJ), but remember that you are not authorized to distribute the binary with a VID/PID that you do not own.
|
||||
|
||||
Note that the pure-browser option [Pico Patcher tool](https://www.picokeys.com/pico-patcher/) is the most recommended.
|
||||
|
||||
## Build
|
||||
Before building, ensure you have installed the toolchain for the Pico and the Pico SDK is properly located in your drive.
|
||||
|
||||
|
||||
Submodule pico-hsm-sdk updated: b12e66a057...46e7d3a181
@@ -23,6 +23,32 @@
|
||||
#include "files.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
|
||||
const uint8_t u2f_aid[] = {
|
||||
7,
|
||||
0xA0, 0x00, 0x00, 0x05, 0x27, 0x10, 0x02
|
||||
};
|
||||
|
||||
int u2f_unload();
|
||||
int u2f_process_apdu();
|
||||
|
||||
app_t *u2f_select(app_t *a, const uint8_t *aid, uint8_t aid_len) {
|
||||
if (!memcmp(aid, u2f_aid + 1, MIN(aid_len, u2f_aid[0]))) {
|
||||
a->aid = u2f_aid;
|
||||
a->process_apdu = u2f_process_apdu;
|
||||
a->unload = u2f_unload;
|
||||
return a;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void __attribute__((constructor)) u2f_ctor() {
|
||||
register_app(u2f_select);
|
||||
}
|
||||
|
||||
int u2f_unload() {
|
||||
return CCID_OK;
|
||||
}
|
||||
|
||||
const uint8_t *bogus_firefox =
|
||||
(const uint8_t *)
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||
@@ -110,3 +136,27 @@ int cmd_register() {
|
||||
ef_certdev_size + olen;
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
extern int cmd_register();
|
||||
extern int cmd_authenticate();
|
||||
extern int cmd_version();
|
||||
|
||||
static const cmd_t cmds[] = {
|
||||
{ CTAP_REGISTER, cmd_register },
|
||||
{ CTAP_AUTHENTICATE, cmd_authenticate },
|
||||
{ CTAP_VERSION, cmd_version },
|
||||
{ 0x00, 0x0 }
|
||||
};
|
||||
|
||||
int u2f_process_apdu() {
|
||||
if (CLA(apdu) != 0x00) {
|
||||
return SW_CLA_NOT_SUPPORTED();
|
||||
}
|
||||
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
|
||||
if (cmd->ins == INS(apdu)) {
|
||||
int r = cmd->cmd_handler();
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return SW_INS_NOT_SUPPORTED();
|
||||
}
|
||||
|
||||
116
src/fido/management.c
Normal file
116
src/fido/management.c
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "hsm.h"
|
||||
#include "apdu.h"
|
||||
#include "version.h"
|
||||
|
||||
int man_process_apdu();
|
||||
int man_unload();
|
||||
|
||||
const uint8_t man_aid[] = {
|
||||
8,
|
||||
0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17
|
||||
};
|
||||
|
||||
app_t *man_select(app_t *a, const uint8_t *aid, uint8_t aid_len) {
|
||||
if (!memcmp(aid, man_aid + 1, MIN(aid_len, man_aid[0]))) {
|
||||
a->aid = man_aid;
|
||||
a->process_apdu = man_process_apdu;
|
||||
a->unload = man_unload;
|
||||
sprintf((char *)res_APDU, "%d.%d.0", PICO_FIDO_VERSION_MAJOR, PICO_FIDO_VERSION_MINOR);
|
||||
res_APDU_size = strlen((char *)res_APDU);
|
||||
apdu.ne = res_APDU_size;
|
||||
return a;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void __attribute__((constructor)) man_ctor() {
|
||||
register_app(man_select);
|
||||
}
|
||||
|
||||
int man_unload() {
|
||||
return CCID_OK;
|
||||
}
|
||||
|
||||
int man_get_config() {
|
||||
res_APDU_size = 0;
|
||||
res_APDU[res_APDU_size++] = 0; // Overall length. Filled later
|
||||
res_APDU[res_APDU_size++] = 0x01;
|
||||
res_APDU[res_APDU_size++] = 2;
|
||||
res_APDU[res_APDU_size++] = 0x02;
|
||||
res_APDU[res_APDU_size++] = 0x01 | 0x02 | 0x20;
|
||||
res_APDU[res_APDU_size++] = 0x02;
|
||||
res_APDU[res_APDU_size++] = 4;
|
||||
#ifndef ENABLE_EMULATION
|
||||
pico_get_unique_board_id_string((char *) res_APDU + res_APDU_size, 4);
|
||||
#endif
|
||||
res_APDU_size += 4;
|
||||
res_APDU[res_APDU_size++] = 0x03;
|
||||
res_APDU[res_APDU_size++] = 2;
|
||||
res_APDU[res_APDU_size++] = 0x02;
|
||||
res_APDU[res_APDU_size++] = 0x01 | 0x02 | 0x20;
|
||||
res_APDU[res_APDU_size++] = 0x04;
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
res_APDU[res_APDU_size++] = 0x01;
|
||||
res_APDU[res_APDU_size++] = 0x05;
|
||||
res_APDU[res_APDU_size++] = 3;
|
||||
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MAJOR;
|
||||
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MINOR;
|
||||
res_APDU[res_APDU_size++] = 0;
|
||||
res_APDU[res_APDU_size++] = 0x08;
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
res_APDU[res_APDU_size++] = 0x80;
|
||||
res_APDU[res_APDU_size++] = 0x0A;
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
res_APDU[res_APDU_size++] = 0x00;
|
||||
res_APDU[res_APDU_size++] = 0x0D;
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
res_APDU[res_APDU_size++] = 0x00;
|
||||
res_APDU[res_APDU_size++] = 0x0E;
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
res_APDU[res_APDU_size++] = 0x00;
|
||||
res_APDU[0] = res_APDU_size - 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_read_config() {
|
||||
man_get_config();
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
#define INS_READ_CONFIG 0x1D
|
||||
|
||||
static const cmd_t cmds[] = {
|
||||
{ INS_READ_CONFIG, cmd_read_config },
|
||||
{ 0x00, 0x0 }
|
||||
};
|
||||
|
||||
int man_process_apdu() {
|
||||
if (CLA(apdu) != 0x00) {
|
||||
return SW_CLA_NOT_SUPPORTED();
|
||||
}
|
||||
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
|
||||
if (cmd->ins == INS(apdu)) {
|
||||
int r = cmd->cmd_handler();
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return SW_INS_NOT_SUPPORTED();
|
||||
}
|
||||
@@ -82,6 +82,7 @@ app_t *oath_select(app_t *a, const uint8_t *aid, uint8_t aid_len) {
|
||||
memset(res_APDU + res_APDU_size, 0, 8); res_APDU_size += 8;
|
||||
#endif
|
||||
if (file_has_data(search_dynamic_file(EF_OATH_CODE)) == true) {
|
||||
random_gen(NULL, challenge, sizeof(challenge));
|
||||
res_APDU[res_APDU_size++] = TAG_CHALLENGE;
|
||||
res_APDU[res_APDU_size++] = sizeof(challenge);
|
||||
memcpy(res_APDU + res_APDU_size, challenge, sizeof(challenge));
|
||||
|
||||
333
src/fido/otp.c
333
src/fido/otp.c
@@ -22,6 +22,11 @@
|
||||
#include "random.h"
|
||||
#include "version.h"
|
||||
#include "asn1.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#ifndef ENABLE_EMULATION
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#include "mbedtls/aes.h"
|
||||
|
||||
#define FIXED_SIZE 16
|
||||
#define KEY_SIZE 16
|
||||
@@ -36,6 +41,52 @@
|
||||
#define CONFIG_LED_INV 0x10
|
||||
#define CONFIG_STATUS_MASK 0x1f
|
||||
|
||||
/* EXT Flags */
|
||||
#define SERIAL_BTN_VISIBLE 0x01 // Serial number visible at startup (button press)
|
||||
#define SERIAL_USB_VISIBLE 0x02 // Serial number visible in USB iSerial field
|
||||
#define SERIAL_API_VISIBLE 0x04 // Serial number visible via API call
|
||||
#define USE_NUMERIC_KEYPAD 0x08 // Use numeric keypad for digits
|
||||
#define FAST_TRIG 0x10 // Use fast trig if only cfg1 set
|
||||
#define ALLOW_UPDATE 0x20 // Allow update of existing configuration (selected flags + access code)
|
||||
#define DORMANT 0x40 // Dormant config (woken up, flag removed, requires update flag)
|
||||
#define LED_INV 0x80 // LED idle state is off rather than on
|
||||
#define EXTFLAG_UPDATE_MASK (SERIAL_BTN_VISIBLE | SERIAL_USB_VISIBLE | SERIAL_API_VISIBLE | USE_NUMERIC_KEYPAD | FAST_TRIG | ALLOW_UPDATE | DORMANT | LED_INV)
|
||||
|
||||
/* TKT Flags */
|
||||
#define TAB_FIRST 0x01 // Send TAB before first part
|
||||
#define APPEND_TAB1 0x02 // Send TAB after first part
|
||||
#define APPEND_TAB2 0x04 // Send TAB after second part
|
||||
#define APPEND_DELAY1 0x08 // Add 0.5s delay after first part
|
||||
#define APPEND_DELAY2 0x10 // Add 0.5s delay after second part
|
||||
#define APPEND_CR 0x20 // Append CR as final character
|
||||
#define OATH_HOTP 0x40 // OATH HOTP mode
|
||||
#define CHAL_RESP 0x40 // Challenge-response enabled (both must be set)
|
||||
#define PROTECT_CFG2 0x80 // Block update of config 2 unless config 2 is configured and has this bit set
|
||||
#define TKTFLAG_UPDATE_MASK (TAB_FIRST | APPEND_TAB1 | APPEND_TAB2 | APPEND_DELAY1 | APPEND_DELAY2 | APPEND_CR)
|
||||
|
||||
/* CFG Flags */
|
||||
#define SEND_REF 0x01 // Send reference string (0..F) before data
|
||||
#define PACING_10MS 0x04 // Add 10ms intra-key pacing
|
||||
#define PACING_20MS 0x08 // Add 20ms intra-key pacing
|
||||
#define STATIC_TICKET 0x20 // Static ticket generation
|
||||
// Static
|
||||
#define SHORT_TICKET 0x02 // Send truncated ticket (half length)
|
||||
#define STRONG_PW1 0x10 // Strong password policy flag #1 (mixed case)
|
||||
#define STRONG_PW2 0x40 // Strong password policy flag #2 (subtitute 0..7 to digits)
|
||||
#define MAN_UPDATE 0x80 // Allow manual (local) update of static OTP
|
||||
// Challenge (no keyboard)
|
||||
#define HMAC_LT64 0x04 // Set when HMAC message is less than 64 bytes
|
||||
#define CHAL_BTN_TRIG 0x08 // Challenge-response operation requires button press
|
||||
#define CHAL_YUBICO 0x20 // Challenge-response enabled - Yubico OTP mode
|
||||
#define CHAL_HMAC 0x22 // Challenge-response enabled - HMAC-SHA1
|
||||
// OATH
|
||||
#define OATH_HOTP8 0x02 // Generate 8 digits HOTP rather than 6 digits
|
||||
#define OATH_FIXED_MODHEX1 0x10 // First byte in fixed part sent as modhex
|
||||
#define OATH_FIXED_MODHEX2 0x40 // First two bytes in fixed part sent as modhex
|
||||
#define OATH_FIXED_MODHEX 0x50 // Fixed part sent as modhex
|
||||
#define OATH_FIXED_MASK 0x50 // Mask to get out fixed flags
|
||||
#define CFGFLAG_UPDATE_MASK (PACING_10MS | PACING_20MS)
|
||||
|
||||
static uint8_t config_seq = { 1 };
|
||||
|
||||
typedef struct otp_config {
|
||||
@@ -75,14 +126,178 @@ app_t *otp_select(app_t *a, const uint8_t *aid, uint8_t aid_len) {
|
||||
config_seq = 0;
|
||||
}
|
||||
otp_status();
|
||||
memmove(res_APDU, res_APDU + 1, 6);
|
||||
res_APDU_size = 6;
|
||||
apdu.ne = res_APDU_size;
|
||||
return a;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t modhex_tab[] = {'c', 'b', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'r', 't', 'u', 'v'};
|
||||
int encode_modhex(const uint8_t *in, size_t len, uint8_t *out) {
|
||||
for (int l = 0; l < len; l++) {
|
||||
*out++ = modhex_tab[in[l] >> 4];
|
||||
*out++ = modhex_tab[in[l] & 0xf];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static bool scanned = false;
|
||||
extern void scan_all();
|
||||
void init_otp() {
|
||||
if (scanned == false) {
|
||||
scan_all();
|
||||
for (int i = 0; i < 2; i++) {
|
||||
file_t *ef = search_dynamic_file(EF_OTP_SLOT1 + i);
|
||||
uint8_t *data = file_get_data(ef);
|
||||
otp_config_t *otp_config = (otp_config_t *)data;
|
||||
if (file_has_data(ef) && !(otp_config->tkt_flags & OATH_HOTP) && !(otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET)) {
|
||||
uint16_t counter = (data[otp_config_size] << 8) | data[otp_config_size + 1];
|
||||
if (++counter <= 0x7fff) {
|
||||
uint8_t new_data[otp_config_size + 8];
|
||||
memcpy(new_data, data, sizeof(new_data));
|
||||
new_data[otp_config_size] = counter >> 8;
|
||||
new_data[otp_config_size + 1] = counter & 0xff;
|
||||
flash_write_data_to_file(ef, new_data, sizeof(new_data));
|
||||
}
|
||||
}
|
||||
}
|
||||
scanned = true;
|
||||
low_flash_available();
|
||||
}
|
||||
}
|
||||
extern int calculate_oath(uint8_t truncate,
|
||||
const uint8_t *key,
|
||||
size_t key_len,
|
||||
const uint8_t *chal,
|
||||
size_t chal_len);
|
||||
#ifndef ENABLE_EMULATION
|
||||
static uint8_t session_counter[2] = {0};
|
||||
#endif
|
||||
int otp_button_pressed(uint8_t slot) {
|
||||
init_otp();
|
||||
#ifndef ENABLE_EMULATION
|
||||
file_t *ef = search_dynamic_file(slot == 1 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
|
||||
const uint8_t *data = file_get_data(ef);
|
||||
otp_config_t *otp_config = (otp_config_t *)data;
|
||||
if (file_has_data(ef) == false) {
|
||||
return 1;
|
||||
}
|
||||
if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) {
|
||||
return 2;
|
||||
}
|
||||
if (otp_config->tkt_flags & OATH_HOTP) {
|
||||
uint8_t tmp_key[KEY_SIZE + 2];
|
||||
tmp_key[0] = 0x01;
|
||||
memcpy(tmp_key + 2, otp_config->aes_key, KEY_SIZE);
|
||||
uint64_t imf = 0;
|
||||
const uint8_t *p = data + otp_config_size;
|
||||
imf |= (uint64_t)*p++ << 56;
|
||||
imf |= (uint64_t)*p++ << 48;
|
||||
imf |= (uint64_t)*p++ << 40;
|
||||
imf |= (uint64_t)*p++ << 32;
|
||||
imf |= *p++ << 24;
|
||||
imf |= *p++ << 16;
|
||||
imf |= *p++ << 8;
|
||||
imf |= *p++;
|
||||
if (imf == 0) {
|
||||
imf = ((otp_config->uid[4] << 8) | otp_config->uid[5]) << 4;
|
||||
}
|
||||
uint8_t chal[8] = {imf >> 56, imf >> 48, imf >> 40, imf >> 32, imf >> 24, imf >> 16, imf >> 8, imf & 0xff};
|
||||
res_APDU_size = 0;
|
||||
int ret = calculate_oath(1, tmp_key, sizeof(tmp_key), chal, sizeof(chal));
|
||||
if (ret == CCID_OK) {
|
||||
uint32_t base = otp_config->cfg_flags & OATH_HOTP8 ? 1e8 : 1e6;
|
||||
uint32_t number = (res_APDU[2] << 24) | (res_APDU[3] << 16) | (res_APDU[4] << 8) | res_APDU[5];
|
||||
number %= base;
|
||||
char number_str[9];
|
||||
if (otp_config->cfg_flags & OATH_HOTP8) {
|
||||
sprintf(number_str, "%08lu", (long unsigned int)number);
|
||||
add_keyboard_buffer((const uint8_t *)number_str, 8, true);
|
||||
}
|
||||
else {
|
||||
sprintf(number_str, "%06lu", (long unsigned int)number);
|
||||
add_keyboard_buffer((const uint8_t *)number_str, 6, true);
|
||||
}
|
||||
imf++;
|
||||
uint8_t new_chal[8] = {imf >> 56, imf >> 48, imf >> 40, imf >> 32, imf >> 24, imf >> 16, imf >> 8, imf & 0xff};
|
||||
uint8_t new_otp_config[otp_config_size + sizeof(new_chal)];
|
||||
memcpy(new_otp_config, otp_config, otp_config_size);
|
||||
memcpy(new_otp_config + otp_config_size, new_chal, sizeof(new_chal));
|
||||
flash_write_data_to_file(ef, new_otp_config, sizeof(new_otp_config));
|
||||
low_flash_available();
|
||||
}
|
||||
if (otp_config->tkt_flags & APPEND_CR) {
|
||||
append_keyboard_buffer((const uint8_t *)"\r", 1);
|
||||
}
|
||||
}
|
||||
else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) {
|
||||
if (otp_config->cfg_flags & SHORT_TICKET) {
|
||||
otp_config->fixed_size /= 2;
|
||||
}
|
||||
add_keyboard_buffer(otp_config->fixed_data, otp_config->fixed_size, false);
|
||||
if (otp_config->tkt_flags & APPEND_CR) {
|
||||
append_keyboard_buffer((const uint8_t *)"\x28", 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
uint8_t otpk[22], *po = otpk;
|
||||
bool update_counter = false;
|
||||
uint16_t counter = (data[otp_config_size] << 8) | data[otp_config_size + 1], crc = 0;
|
||||
uint32_t ts = board_millis() / 1000;
|
||||
if (counter == 0) {
|
||||
update_counter = true;
|
||||
counter = 1;
|
||||
}
|
||||
memcpy(po, otp_config->fixed_data, 6);
|
||||
po += 6;
|
||||
memcpy(po, otp_config->uid, UID_SIZE);
|
||||
po += UID_SIZE;
|
||||
*po++ = counter & 0xff;
|
||||
*po++ = counter >> 8;
|
||||
ts >>= 3;
|
||||
*po++ = ts & 0xff;
|
||||
*po++ = ts >> 8;
|
||||
*po++ = ts >> 16;
|
||||
*po++ = session_counter[slot - 1];
|
||||
random_gen(NULL, po, 2);
|
||||
po += 2;
|
||||
crc = calculate_crc(otpk + 6, 14);
|
||||
*po++ = ~crc & 0xff;
|
||||
*po++ = ~crc >> 8;
|
||||
mbedtls_aes_context ctx;
|
||||
mbedtls_aes_init(&ctx);
|
||||
mbedtls_aes_setkey_enc(&ctx, otp_config->aes_key, 128);
|
||||
mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, otpk + 6, otpk + 6);
|
||||
mbedtls_aes_free(&ctx);
|
||||
uint8_t otp_out[44];
|
||||
encode_modhex(otpk, sizeof(otpk), otp_out);
|
||||
add_keyboard_buffer((const uint8_t *)otp_out, sizeof(otp_out), true);
|
||||
if (otp_config->tkt_flags & APPEND_CR) {
|
||||
append_keyboard_buffer((const uint8_t *)"\r", 1);
|
||||
}
|
||||
|
||||
if (++session_counter[slot - 1] == 0) {
|
||||
if (++counter <= 0x7fff) {
|
||||
update_counter = true;
|
||||
}
|
||||
}
|
||||
if (update_counter == true) {
|
||||
uint8_t new_data[otp_config_size + 8];
|
||||
memcpy(new_data, data, sizeof(new_data));
|
||||
new_data[otp_config_size] = counter >> 8;
|
||||
new_data[otp_config_size + 1] = counter & 0xff;
|
||||
flash_write_data_to_file(ef, new_data, sizeof(new_data));
|
||||
low_flash_available();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __attribute__((constructor)) otp_ctor() {
|
||||
register_app(otp_select);
|
||||
button_pressed_cb = otp_button_pressed;
|
||||
}
|
||||
|
||||
int otp_unload() {
|
||||
@@ -90,29 +305,39 @@ int otp_unload() {
|
||||
}
|
||||
|
||||
uint16_t otp_status() {
|
||||
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MAJOR;
|
||||
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MINOR;
|
||||
res_APDU[res_APDU_size++] = 0;
|
||||
res_APDU[res_APDU_size++] = config_seq;
|
||||
res_APDU[res_APDU_size++] = 0;
|
||||
res_APDU[res_APDU_size++] = (CONFIG2_TOUCH | CONFIG1_TOUCH) |
|
||||
if (scanned == false) {
|
||||
scan_all();
|
||||
scanned = true;
|
||||
}
|
||||
res_APDU_size = 0;
|
||||
res_APDU[1] = PICO_FIDO_VERSION_MAJOR;
|
||||
res_APDU[2] = PICO_FIDO_VERSION_MINOR;
|
||||
res_APDU[3] = 0;
|
||||
res_APDU[4] = config_seq;
|
||||
res_APDU[5] = (CONFIG2_TOUCH | CONFIG1_TOUCH) |
|
||||
(file_has_data(search_dynamic_file(EF_OTP_SLOT1)) ? CONFIG1_VALID :
|
||||
0x00) |
|
||||
(file_has_data(search_dynamic_file(EF_OTP_SLOT2)) ? CONFIG2_VALID :
|
||||
0x00);
|
||||
res_APDU[6] = 0;
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
bool check_crc(const otp_config_t *data) {
|
||||
uint16_t crc = calculate_crc((const uint8_t *)data, otp_config_size);
|
||||
return crc == 0xF0B8;
|
||||
}
|
||||
|
||||
extern int man_get_config();
|
||||
|
||||
int cmd_otp() {
|
||||
uint8_t p1 = P1(apdu), p2 = P2(apdu);
|
||||
if (p2 != 0x00) {
|
||||
return SW_INCORRECT_P1P2();
|
||||
}
|
||||
if (p1 == 0x01 || p1 == 0x03) { // Configure slot
|
||||
if (apdu.nc != otp_config_size + ACC_CODE_SIZE) {
|
||||
return SW_WRONG_LENGTH();
|
||||
}
|
||||
if (apdu.data[48] != 0 || apdu.data[49] != 0) {
|
||||
otp_config_t *odata = (otp_config_t *)apdu.data;
|
||||
if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
file_t *ef = file_new(p1 == 0x01 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
|
||||
@@ -124,7 +349,8 @@ int cmd_otp() {
|
||||
}
|
||||
for (int c = 0; c < otp_config_size; c++) {
|
||||
if (apdu.data[c] != 0) {
|
||||
flash_write_data_to_file(ef, apdu.data, otp_config_size);
|
||||
memset(apdu.data + otp_config_size, 0, 8); // Add 8 bytes extra
|
||||
flash_write_data_to_file(ef, apdu.data, otp_config_size + 8);
|
||||
low_flash_available();
|
||||
config_seq++;
|
||||
return otp_status();
|
||||
@@ -138,24 +364,93 @@ int cmd_otp() {
|
||||
}
|
||||
return otp_status();
|
||||
}
|
||||
else if (p1 == 0x04 || p1 == 0x05) {
|
||||
otp_config_t *odata = (otp_config_t *)apdu.data;
|
||||
if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
file_t *ef = search_dynamic_file(p1 == 0x04 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
|
||||
if (file_has_data(ef)) {
|
||||
otp_config_t *otpc = (otp_config_t *) file_get_data(ef);
|
||||
if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) {
|
||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||
}
|
||||
memcpy(apdu.data, file_get_data(ef), FIXED_SIZE + UID_SIZE + KEY_SIZE);
|
||||
odata->fixed_size = otpc->fixed_size;
|
||||
odata->ext_flags = (otpc->ext_flags & ~EXTFLAG_UPDATE_MASK) | (odata->ext_flags & EXTFLAG_UPDATE_MASK);
|
||||
odata->tkt_flags = (otpc->tkt_flags & ~TKTFLAG_UPDATE_MASK) | (odata->tkt_flags & TKTFLAG_UPDATE_MASK);
|
||||
odata->cfg_flags = (otpc->cfg_flags & ~CFGFLAG_UPDATE_MASK) | (odata->cfg_flags & CFGFLAG_UPDATE_MASK);
|
||||
flash_write_data_to_file(ef, apdu.data, otp_config_size);
|
||||
low_flash_available();
|
||||
}
|
||||
}
|
||||
else if (p1 == 0x06) {
|
||||
uint8_t tmp[otp_config_size + 8];
|
||||
bool ef1_data = false;
|
||||
file_t *ef1 = file_new(EF_OTP_SLOT1);
|
||||
file_t *ef2 = file_new(EF_OTP_SLOT2);
|
||||
if (file_has_data(ef1)) {
|
||||
memcpy(tmp, file_get_data(ef1), file_get_size(ef1));
|
||||
ef1_data = true;
|
||||
}
|
||||
if (file_has_data(ef2)) {
|
||||
flash_write_data_to_file(ef1, file_get_data(ef2), file_get_size(ef2));
|
||||
}
|
||||
else {
|
||||
delete_file(ef1);
|
||||
}
|
||||
if (ef1_data) {
|
||||
flash_write_data_to_file(ef2, tmp, sizeof(tmp));
|
||||
}
|
||||
else {
|
||||
delete_file(ef2);
|
||||
}
|
||||
low_flash_available();
|
||||
}
|
||||
else if (p1 == 0x10) {
|
||||
#ifndef ENABLE_EMULATION
|
||||
pico_get_unique_board_id_string((char *) res_APDU, 4);
|
||||
#endif
|
||||
res_APDU_size = 4;
|
||||
}
|
||||
else if (p1 == 0x13) {
|
||||
man_get_config();
|
||||
}
|
||||
else if (p1 == 0x30 || p1 == 0x38 || p1 == 0x20 || p1 == 0x28) {
|
||||
file_t *ef = search_dynamic_file(p1 == 0x30 || p1 == 0x20 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
|
||||
if (file_has_data(ef)) {
|
||||
otp_config_t *otp_config = (otp_config_t *)file_get_data(ef);
|
||||
if (!(otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP)) {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
int ret = 0;
|
||||
if (p1 == 0x30 || p1 == 0x38) {
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), otp_config->aes_key, KEY_SIZE, apdu.data, 8, res_APDU);
|
||||
if (ret == 0) {
|
||||
res_APDU_size = 20;
|
||||
}
|
||||
}
|
||||
else if (p1 == 0x20 || p1 == 0x28) {
|
||||
uint8_t challenge[16];
|
||||
memcpy(challenge, apdu.data, 6);
|
||||
#ifndef ENABLE_EMULATION
|
||||
pico_get_unique_board_id_string((char *) challenge + 6, 10);
|
||||
#endif
|
||||
mbedtls_aes_context ctx;
|
||||
mbedtls_aes_init(&ctx);
|
||||
mbedtls_aes_setkey_enc(&ctx, otp_config->aes_key, 128);
|
||||
ret = mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, challenge, res_APDU);
|
||||
mbedtls_aes_free(&ctx);
|
||||
if (ret == 0) {
|
||||
res_APDU_size = 16;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
#define INS_OTP 0x01
|
||||
#define INS_DELETE 0x02
|
||||
#define INS_SET_CODE 0x03
|
||||
#define INS_RESET 0x04
|
||||
#define INS_LIST 0xa1
|
||||
#define INS_CALCULATE 0xa2
|
||||
#define INS_VALIDATE 0xa3
|
||||
#define INS_CALC_ALL 0xa4
|
||||
#define INS_SEND_REMAINING 0xa5
|
||||
|
||||
static const cmd_t cmds[] = {
|
||||
{ INS_OTP, cmd_otp },
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#ifndef __VERSION_H_
|
||||
#define __VERSION_H_
|
||||
|
||||
#define PICO_FIDO_VERSION 0x0300
|
||||
#define PICO_FIDO_VERSION 0x0504
|
||||
|
||||
#define PICO_FIDO_VERSION_MAJOR ((PICO_FIDO_VERSION >> 8) & 0xff)
|
||||
#define PICO_FIDO_VERSION_MINOR (PICO_FIDO_VERSION & 0xff)
|
||||
|
||||
Reference in New Issue
Block a user