From 39cc1c879e3d75b3cafd79e4e139a7f6673dd349 Mon Sep 17 00:00:00 2001 From: Oleg Moiseenko <807634+merlokk@users.noreply.github.com> Date: Sat, 17 Nov 2018 20:22:21 +0200 Subject: [PATCH] FIDO U2F NFC authenticators (#697) * `hf fido` command * detects FIDO tag * add new commands for fido u2f * added changelog * added fido2 info --- CHANGELOG.md | 2 + armsrc/epa.c | 2 +- armsrc/iso14443a.c | 29 ++- armsrc/iso14443a.h | 2 +- client/Makefile | 1 + client/cmdhf.c | 2 + client/cmdhf14a.c | 64 +++-- client/cmdhffido.c | 550 +++++++++++++++++++++++++++++++++++++++++++ client/cmdhffido.h | 27 +++ client/emv/emvcore.c | 11 +- client/emv/emvcore.h | 4 + client/emv/emvjson.c | 36 ++- client/emv/emvjson.h | 5 + client/util.c | 4 +- 14 files changed, 704 insertions(+), 35 deletions(-) create mode 100644 client/cmdhffido.c create mode 100644 client/cmdhffido.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 7781935e..6b134562 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,9 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac ### Fixed ### Added +- Added `hf emv scan` - commands for scan EMV card and dump data to json file (Merlok) - `hf mfp` group of commands (Merlok) +- Added `hf fido` - FIDO U2F authenticator commands https://fidoalliance.org/ (Merlok) ## [v3.1.0][2018-10-10] diff --git a/armsrc/epa.c b/armsrc/epa.c index fd71430b..01aff302 100644 --- a/armsrc/epa.c +++ b/armsrc/epa.c @@ -116,7 +116,7 @@ int EPA_APDU(uint8_t *apdu, size_t length, uint8_t *response) switch(iso_type) { case 'a': - return iso14_apdu(apdu, (uint16_t) length, response); + return iso14_apdu(apdu, (uint16_t) length, response, NULL); break; case 'b': return iso14443b_apdu(apdu, length, response); diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 059db71e..7bf8f5af 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -1935,15 +1935,21 @@ b8 b7 b6 b5 b4 b3 b2 b1 b5,b6 = 00 - DESELECT 11 - WTX */ -int iso14_apdu(uint8_t *cmd, uint16_t cmd_len, void *data) { +int iso14_apdu(uint8_t *cmd, uint16_t cmd_len, void *data, uint8_t *res) { uint8_t parity[MAX_PARITY_SIZE]; uint8_t real_cmd[cmd_len + 4]; - // ISO 14443 APDU frame: PCB [CID] [NAD] APDU CRC PCB=0x02 - real_cmd[0] = 0x02; // bnr,nad,cid,chn=0; i-block(0x00) - // put block number into the PCB - real_cmd[0] |= iso14_pcb_blocknum; - memcpy(real_cmd + 1, cmd, cmd_len); + if (cmd_len) { + // ISO 14443 APDU frame: PCB [CID] [NAD] APDU CRC PCB=0x02 + real_cmd[0] = 0x02; // bnr,nad,cid,chn=0; i-block(0x00) + // put block number into the PCB + real_cmd[0] |= iso14_pcb_blocknum; + memcpy(real_cmd + 1, cmd, cmd_len); + } else { + // R-block. ACK + real_cmd[0] = 0xA2; // r-block + ACK + real_cmd[0] |= iso14_pcb_blocknum; + } AppendCrc14443a(real_cmd, cmd_len + 1); ReaderTransmit(real_cmd, cmd_len + 3, NULL); @@ -1982,9 +1988,13 @@ int iso14_apdu(uint8_t *cmd, uint16_t cmd_len, void *data) { { iso14_pcb_blocknum ^= 1; } + + // if we received I-block with chaining we need to send ACK and receive another block of data + if (res) + *res = data_bytes[0]; // crc check - if (len >=3 && !CheckCrc14443(CRC_14443_A, data_bytes, len)) { + if (len >= 3 && !CheckCrc14443(CRC_14443_A, data_bytes, len)) { return -1; } @@ -2050,9 +2060,10 @@ void ReaderIso14443a(UsbCommand *c) } if(param & ISO14A_APDU && !cantSELECT) { - arg0 = iso14_apdu(cmd, len, buf); + uint8_t res; + arg0 = iso14_apdu(cmd, len, buf, &res); LED_B_ON(); - cmd_send(CMD_ACK, arg0, 0, 0, buf, sizeof(buf)); + cmd_send(CMD_ACK, arg0, res, 0, buf, sizeof(buf)); LED_B_OFF(); } diff --git a/armsrc/iso14443a.h b/armsrc/iso14443a.h index 8796edf5..396b2100 100644 --- a/armsrc/iso14443a.h +++ b/armsrc/iso14443a.h @@ -49,7 +49,7 @@ extern int EmSendPrecompiledCmd(tag_response_info_t *response_info); extern bool prepare_allocated_tag_modulation(tag_response_info_t *response_info, uint8_t **buffer, size_t *buffer_size); extern void iso14443a_setup(uint8_t fpga_minor_mode); -extern int iso14_apdu(uint8_t *cmd, uint16_t cmd_len, void *data); +extern int iso14_apdu(uint8_t *cmd, uint16_t cmd_len, void *data, uint8_t *res); extern int iso14443a_select_card(uint8_t *uid_ptr, iso14a_card_select_t *resp_data, uint32_t *cuid_ptr, bool anticollision, uint8_t num_cascades, bool no_rats); extern void iso14a_set_trigger(bool enable); extern void iso14a_set_timeout(uint32_t timeout); diff --git a/client/Makefile b/client/Makefile index 1b211d4f..969eb241 100644 --- a/client/Makefile +++ b/client/Makefile @@ -164,6 +164,7 @@ CMDSRCS = $(SRC_SMARTCARD) \ cmdhfmfhard.c \ hardnested/hardnested_bruteforce.c \ cmdhftopaz.c \ + cmdhffido.c \ cmdhw.c \ cmdlf.c \ cmdlfawid.c \ diff --git a/client/cmdhf.c b/client/cmdhf.c index 762cc791..d2201800 100644 --- a/client/cmdhf.c +++ b/client/cmdhf.c @@ -34,6 +34,7 @@ #include "protocols.h" #include "emv/cmdemv.h" #include "cmdhflist.h" +#include "cmdhffido.h" static int CmdHelp(const char *Cmd); @@ -598,6 +599,7 @@ static command_t CommandTable[] = {"mfu", CmdHFMFUltra, 1, "{ MIFARE Ultralight RFIDs... }"}, {"mfp", CmdHFMFP, 1, "{ MIFARE Plus RFIDs... }"}, {"topaz", CmdHFTopaz, 1, "{ TOPAZ (NFC Type 1) RFIDs... }"}, + {"fido", CmdHFFido, 1, "{ FIDO and FIDO2 authenticators... }"}, {"tune", CmdHFTune, 0, "Continuously measure HF antenna tuning"}, {"list", CmdHFList, 1, "List protocol data in trace buffer"}, {"search", CmdHFSearch, 1, "Search for known HF tags [preliminary]"}, diff --git a/client/cmdhf14a.c b/client/cmdhf14a.c index 326eaf50..922a9449 100644 --- a/client/cmdhf14a.c +++ b/client/cmdhf14a.c @@ -786,20 +786,20 @@ int ExchangeRAW14a(uint8_t *datain, int datainlen, bool activateField, bool leav return 0; } -int ExchangeAPDU14a(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { +int CmdExchangeAPDU(uint8_t *datain, int datainlen, bool activateField, uint8_t *dataout, int maxdataoutlen, int *dataoutlen, bool *chaining) { uint16_t cmdc = 0; + + *chaining = false; if (activateField) { cmdc |= ISO14A_CONNECT | ISO14A_CLEAR_TRACE; } - if (leaveSignalON) - cmdc |= ISO14A_NO_DISCONNECT; // "Command APDU" length should be 5+255+1, but javacard's APDU buffer might be smaller - 133 bytes // https://stackoverflow.com/questions/32994936/safe-max-java-card-apdu-data-command-and-respond-size // here length USB_CMD_DATA_SIZE=512 // timeout must be authomatically set by "get ATS" - UsbCommand c = {CMD_READER_ISO_14443a, {ISO14A_APDU | cmdc, (datainlen & 0xFFFF), 0}}; + UsbCommand c = {CMD_READER_ISO_14443a, {ISO14A_APDU | ISO14A_NO_DISCONNECT | cmdc, (datainlen & 0xFFFF), 0}}; memcpy(c.d.asBytes, datain, datainlen); SendCommand(&c); @@ -813,6 +813,7 @@ int ExchangeAPDU14a(uint8_t *datain, int datainlen, bool activateField, bool lea } if (resp.arg[0] != 1) { PrintAndLog("APDU ERROR: Proxmark error %d.", resp.arg[0]); + DropField(); return 1; } } @@ -820,45 +821,76 @@ int ExchangeAPDU14a(uint8_t *datain, int datainlen, bool activateField, bool lea if (WaitForResponseTimeout(CMD_ACK, &resp, 1500)) { recv = resp.d.asBytes; int iLen = resp.arg[0]; + uint8_t res = resp.arg[1]; - *dataoutlen = iLen - 2; - if (*dataoutlen < 0) - *dataoutlen = 0; + int dlen = iLen - 2; + if (dlen < 0) + dlen = 0; + *dataoutlen += dlen; if (maxdataoutlen && *dataoutlen > maxdataoutlen) { PrintAndLog("APDU ERROR: Buffer too small(%d). Needs %d bytes", *dataoutlen, maxdataoutlen); return 2; } - memcpy(dataout, recv, *dataoutlen); - if(!iLen) { PrintAndLog("APDU ERROR: No APDU response."); return 1; } + // check apdu length + if (iLen < 4 && iLen >= 0) { + PrintAndLog("APDU ERROR: Small APDU response. Len=%d", iLen); + return 2; + } + // check block TODO if (iLen == -2) { PrintAndLog("APDU ERROR: Block type mismatch."); return 2; } + + memcpy(dataout, recv, dlen); + + // chaining + if ((res & 0x10) != 0) { + *chaining = true; + } // CRC Check if (iLen == -1) { PrintAndLog("APDU ERROR: ISO 14443A CRC error."); return 3; } - - // check apdu length - if (iLen < 4) { - PrintAndLog("APDU ERROR: Small APDU response. Len=%d", iLen); - return 2; - } - } else { PrintAndLog("APDU ERROR: Reply timeout."); return 4; } + + return 0; +} + + +int ExchangeAPDU14a(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { + *dataoutlen = 0; + bool chaining = false; + + int res = CmdExchangeAPDU(datain, datainlen, activateField, dataout, maxdataoutlen, dataoutlen, &chaining); + + while (chaining) { + // I-block with chaining + res = CmdExchangeAPDU(NULL, 0, false, &dataout[*dataoutlen], maxdataoutlen, dataoutlen, &chaining); + + if (res) { + if (!leaveSignalON) + DropField(); + + return 100; + } + } + + if (!leaveSignalON) + DropField(); return 0; } diff --git a/client/cmdhffido.c b/client/cmdhffido.c new file mode 100644 index 00000000..fd97ace5 --- /dev/null +++ b/client/cmdhffido.c @@ -0,0 +1,550 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2018 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// High frequency MIFARE Plus commands +//----------------------------------------------------------------------------- +// +// Documentation here: +// +// FIDO Alliance specifications +// https://fidoalliance.org/download/ +// FIDO NFC Protocol Specification v1.0 +// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-nfc-protocol-v1.2-ps-20170411.html +// FIDO U2F Raw Message Formats +// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html +//----------------------------------------------------------------------------- + + +#include "cmdhffido.h" + +#include +#include +#include +#include +#include +#include +#include +#include "comms.h" +#include "cmdmain.h" +#include "util.h" +#include "ui.h" +#include "proxmark3.h" +#include "cmdhf14a.h" +#include "mifare.h" +#include "emv/emvcore.h" +#include "emv/emvjson.h" +#include "emv/dump.h" +#include "cliparser/cliparser.h" + +static int CmdHelp(const char *Cmd); + +int FIDOSelect(bool ActivateField, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + uint8_t data[] = {0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01}; + + return EMVSelect(ActivateField, LeaveFieldON, data, sizeof(data), Result, MaxResultLen, ResultLen, sw, NULL); +} + +int FIDOExchange(sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + int res = EMVExchange(true, apdu, Result, MaxResultLen, ResultLen, sw, NULL); + if (res == 5) // apdu result (sw) not a 0x9000 + res = 0; + // software chaining + while (!res && (*sw >> 8) == 0x61) { + size_t oldlen = *ResultLen; + res = EMVExchange(true, (sAPDU){0x00, 0xC0, 0x00, 0x00, 0x00, NULL}, &Result[oldlen], MaxResultLen - oldlen, ResultLen, sw, NULL); + if (res == 5) // apdu result (sw) not a 0x9000 + res = 0; + + *ResultLen += oldlen; + if (*ResultLen > MaxResultLen) + return 100; + } + return res; +} + +int FIDORegister(uint8_t *params, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + return FIDOExchange((sAPDU){0x00, 0x01, 0x03, 0x00, 64, params}, Result, MaxResultLen, ResultLen, sw); +} + +int FIDOAuthentication(uint8_t *params, uint8_t paramslen, uint8_t controlb, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + return FIDOExchange((sAPDU){0x00, 0x02, controlb, 0x00, paramslen, params}, Result, MaxResultLen, ResultLen, sw); +} + +int FIDO2GetInfo(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + uint8_t data[] = {0x04}; + return FIDOExchange((sAPDU){0x80, 0x10, 0x00, 0x00, sizeof(data), data}, Result, MaxResultLen, ResultLen, sw); +} + +int CmdHFFidoInfo(const char *cmd) { + + if (cmd && strlen(cmd) > 0) + PrintAndLog("WARNING: command don't have any parameters.\n"); + + // info about 14a part + CmdHF14AInfo(""); + + // FIDO info + PrintAndLog("--------------------------------------------"); + SetAPDULogging(false); + + uint8_t buf[APDU_RES_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + int res = FIDOSelect(true, true, buf, sizeof(buf), &len, &sw); + + if (res) { + DropField(); + return res; + } + + if (sw != 0x9000) { + if (sw) + PrintAndLog("Not a FIDO card! APDU response: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + else + PrintAndLog("APDU exchange error. Card returns 0x0000."); + + DropField(); + return 0; + } + + if (!strncmp((char *)buf, "U2F_V2", 7)) { + if (!strncmp((char *)buf, "FIDO_2_0", 8)) { + PrintAndLog("FIDO2 authenricator detected. Version: %.*s", len, buf); + } else { + PrintAndLog("FIDO authenricator detected (not standard U2F)."); + PrintAndLog("Non U2F authenticator version:"); + dump_buffer((const unsigned char *)buf, len, NULL, 0); + } + } else { + PrintAndLog("FIDO U2F authenricator detected. Version: %.*s", len, buf); + } + + res = FIDO2GetInfo(buf, sizeof(buf), &len, &sw); + DropField(); + if (res) { + return res; + } + if (sw != 0x9000) { + PrintAndLog("FIDO2 version not exists (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + + return 0; + } + + PrintAndLog("FIDO2 version: (%d)", len); + dump_buffer((const unsigned char *)buf, len, NULL, 0); + + return 0; +} + +json_t *OpenJson(int paramnum, char *fname, void* argtable[], bool *err) { + json_t *root = NULL; + json_error_t error; + *err = false; + + uint8_t jsonname[250] ={0}; + char *cjsonname = (char *)jsonname; + int jsonnamelen = 0; + + // CLIGetStrWithReturn(paramnum, jsonname, &jsonnamelen); + if (CLIParamStrToBuf(arg_get_str(paramnum), jsonname, sizeof(jsonname), &jsonnamelen)) { + CLIParserFree(); + return NULL; + } + + // current path + file name + if (!strstr(cjsonname, ".json")) + strcat(cjsonname, ".json"); + + if (jsonnamelen) { + strcpy(fname, get_my_executable_directory()); + strcat(fname, cjsonname); + if (access(fname, F_OK) != -1) { + root = json_load_file(fname, 0, &error); + if (!root) { + PrintAndLog("ERROR: json error on line %d: %s", error.line, error.text); + *err = true; + return NULL; + } + + if (!json_is_object(root)) { + PrintAndLog("ERROR: Invalid json format. root must be an object."); + json_decref(root); + *err = true; + return NULL; + } + + } else { + root = json_object(); + } + } + return root; +} + +int CmdHFFidoRegister(const char *cmd) { + uint8_t data[64] = {0}; + int chlen = 0; + uint8_t cdata[250] = {0}; + int applen = 0; + uint8_t adata[250] = {0}; + json_t *root = NULL; + + CLIParserInit("hf fido reg", + "Initiate a U2F token registration. Needs two 32-byte hash number. \nchallenge parameter (32b) and application parameter (32b).", + "Usage:\n\thf fido reg -> execute command with 2 parameters, filled 0x00\n" + "\thf fido reg 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f -> execute command with parameters" + "\thf fido reg -p s0 s1 -> execute command with plain parameters"); + + void* argtable[] = { + arg_param_begin, + arg_lit0("aA", "apdu", "show APDU reqests and responses"), + arg_lit0("vV", "verbose", "show technical data"), + arg_lit0("pP", "plain", "send plain ASCII to challenge and application parameters instead of HEX"), + arg_str0("jJ", "json", "fido.json", "JSON input / output file name for parameters."), + arg_str0(NULL, NULL, "", NULL), + arg_str0(NULL, NULL, "", NULL), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + bool APDULogging = arg_get_lit(1); + bool verbose = arg_get_lit(2); + bool paramsPlain = arg_get_lit(3); + + char fname[250] = {0}; + bool err; + root = OpenJson(4, fname, argtable, &err); + if(err) + return 1; + if (root) { + size_t jlen; + JsonLoadBufAsHex(root, "$.ChallengeParam", data, 32, &jlen); + JsonLoadBufAsHex(root, "$.ApplicationParam", &data[32], 32, &jlen); + } + + if (paramsPlain) { + memset(cdata, 0x00, 32); + CLIGetStrWithReturn(5, cdata, &chlen); + if (chlen && chlen > 16) { + PrintAndLog("ERROR: challenge parameter length in ASCII mode must be less than 16 chars instead of: %d", chlen); + return 1; + } + } else { + CLIGetHexWithReturn(5, cdata, &chlen); + if (chlen && chlen != 32) { + PrintAndLog("ERROR: challenge parameter length must be 32 bytes only."); + return 1; + } + } + if (chlen) + memmove(data, cdata, 32); + + + if (paramsPlain) { + memset(adata, 0x00, 32); + CLIGetStrWithReturn(6, adata, &applen); + if (applen && applen > 16) { + PrintAndLog("ERROR: application parameter length in ASCII mode must be less than 16 chars instead of: %d", applen); + return 1; + } + } else { + CLIGetHexWithReturn(6, adata, &applen); + if (applen && applen != 32) { + PrintAndLog("ERROR: application parameter length must be 32 bytes only."); + return 1; + } + } + if (applen) + memmove(&data[32], adata, 32); + + CLIParserFree(); + + SetAPDULogging(APDULogging); + + // challenge parameter [32 bytes] - The challenge parameter is the SHA-256 hash of the Client Data, a stringified JSON data structure that the FIDO Client prepares + // application parameter [32 bytes] - The application parameter is the SHA-256 hash of the UTF-8 encoding of the application identity + + uint8_t buf[2048] = {0}; + size_t len = 0; + uint16_t sw = 0; + + DropField(); + int res = FIDOSelect(true, true, buf, sizeof(buf), &len, &sw); + + if (res) { + PrintAndLog("Can't select authenticator. res=%x. Exit...", res); + DropField(); + return res; + } + + if (sw != 0x9000) { + PrintAndLog("Can't select FIDO application. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + DropField(); + return 2; + } + + res = FIDORegister(data, buf, sizeof(buf), &len, &sw); + DropField(); + if (res) { + PrintAndLog("Can't execute register command. res=%x. Exit...", res); + return res; + } + + if (sw != 0x9000) { + PrintAndLog("ERROR execute register command. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + return 3; + } + + PrintAndLog(""); + if (APDULogging) + PrintAndLog("---------------------------------------------------------------"); + PrintAndLog("data len: %d", len); + if (verbose) { + PrintAndLog("--------------data----------------------"); + dump_buffer((const unsigned char *)buf, len, NULL, 0); + PrintAndLog("--------------data----------------------"); + } + + if (buf[0] != 0x05) { + PrintAndLog("ERROR: First byte must be 0x05, but it %2x", buf[0]); + return 5; + } + PrintAndLog("User public key: %s", sprint_hex(&buf[1], 65)); + + uint8_t keyHandleLen = buf[66]; + PrintAndLog("Key handle[%d]: %s", keyHandleLen, sprint_hex(&buf[67], keyHandleLen)); + + int derp = 67 + keyHandleLen; + int derLen = (buf[derp + 2] << 8) + buf[derp + 3] + 4; + // needs to decode DER certificate + if (verbose) { + PrintAndLog("DER certificate[%d]:------------------DER-------------------", derLen); + dump_buffer_simple((const unsigned char *)&buf[67 + keyHandleLen], derLen, NULL); + PrintAndLog("\n----------------DER---------------------"); + } else { + PrintAndLog("DER certificate[%d]: %s...", derLen, sprint_hex(&buf[derp], 20)); + } + + + int hashp = 1 + 65 + 1 + keyHandleLen + derLen; + PrintAndLog("Hash[%d]: %s", len - hashp, sprint_hex(&buf[hashp], len - hashp)); + + // check ANSI X9.62 format ECDSA signature (on P-256) + + PrintAndLog("\nauth command: "); + printf("hf fido auth %s%s", paramsPlain?"-p ":"", sprint_hex_inrow(&buf[67], keyHandleLen)); + if(chlen || applen) + printf(" %s", paramsPlain?(char *)cdata:sprint_hex_inrow(cdata, 32)); + if(applen) + printf(" %s", paramsPlain?(char *)adata:sprint_hex_inrow(adata, 32)); + printf("\n"); + + if (root) { + JsonSaveBufAsHex(root, "ChallengeParam", data, 32); + JsonSaveBufAsHex(root, "ApplicationParam", &data[32], 32); + JsonSaveInt(root, "KeyHandleLen", keyHandleLen); + JsonSaveBufAsHexCompact(root, "KeyHandle", &buf[67], keyHandleLen); + JsonSaveBufAsHexCompact(root, "DER", &buf[67 + keyHandleLen], derLen); + + res = json_dump_file(root, fname, JSON_INDENT(2)); + if (res) { + PrintAndLog("ERROR: can't save the file: %s", fname); + return 200; + } + PrintAndLog("File `%s` saved.", fname); + + // free json object + json_decref(root); + } + + return 0; +}; + +int CmdHFFidoAuthenticate(const char *cmd) { + uint8_t data[512] = {0}; + uint8_t hdata[250] = {0}; + int hdatalen = 0; + uint8_t keyHandleLen = 0; + json_t *root = NULL; + + CLIParserInit("hf fido auth", + "Initiate a U2F token authentication. Needs key handle and two 32-byte hash number. \nkey handle(var 0..255), challenge parameter (32b) and application parameter (32b).", + "Usage:\n\thf fido auth 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f -> execute command with 2 parameters, filled 0x00 and key handle\n" + "\thf fido auth 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f " + "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f -> execute command with parameters"); + + void* argtable[] = { + arg_param_begin, + arg_lit0("aA", "apdu", "show APDU reqests and responses"), + arg_lit0("vV", "verbose", "show technical data"), + arg_lit0("pP", "plain", "send plain ASCII to challenge and application parameters instead of HEX"), + arg_rem("default mode:", "dont-enforce-user-presence-and-sign"), + arg_lit0("uU", "user", "mode: enforce-user-presence-and-sign"), + arg_lit0("cC", "check", "mode: check-only"), + arg_str0("jJ", "json", "fido.json", "JSON input / output file name for parameters."), + arg_str0(NULL, NULL, "", NULL), + arg_str0(NULL, NULL, "", NULL), + arg_str0(NULL, NULL, "", NULL), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + bool APDULogging = arg_get_lit(1); + //bool verbose = arg_get_lit(2); + bool paramsPlain = arg_get_lit(3); + uint8_t controlByte = 0x08; + if (arg_get_lit(5)) + controlByte = 0x03; + if (arg_get_lit(6)) + controlByte = 0x07; + + char fname[250] = {0}; + bool err; + root = OpenJson(7, fname, argtable, &err); + if(err) + return 1; + if (root) { + size_t jlen; + JsonLoadBufAsHex(root, "$.ChallengeParam", data, 32, &jlen); + JsonLoadBufAsHex(root, "$.ApplicationParam", &data[32], 32, &jlen); + JsonLoadBufAsHex(root, "$.KeyHandle", &data[65], 512 - 67, &jlen); + keyHandleLen = jlen & 0xff; + data[64] = keyHandleLen; + } + + CLIGetHexWithReturn(8, hdata, &hdatalen); + if (hdatalen > 255) { + PrintAndLog("ERROR: application parameter length must be less than 255."); + return 1; + } + if (hdatalen) { + keyHandleLen = hdatalen; + data[64] = keyHandleLen; + memmove(&data[65], hdata, keyHandleLen); + } + + if (paramsPlain) { + memset(hdata, 0x00, 32); + CLIGetStrWithReturn(9, hdata, &hdatalen); + if (hdatalen && hdatalen > 16) { + PrintAndLog("ERROR: challenge parameter length in ASCII mode must be less than 16 chars instead of: %d", hdatalen); + return 1; + } + } else { + CLIGetHexWithReturn(9, hdata, &hdatalen); + if (hdatalen && hdatalen != 32) { + PrintAndLog("ERROR: challenge parameter length must be 32 bytes only."); + return 1; + } + } + if (hdatalen) + memmove(data, hdata, 32); + + if (paramsPlain) { + memset(hdata, 0x00, 32); + CLIGetStrWithReturn(10, hdata, &hdatalen); + if (hdatalen && hdatalen > 16) { + PrintAndLog("ERROR: application parameter length in ASCII mode must be less than 16 chars instead of: %d", hdatalen); + return 1; + } + } else { + CLIGetHexWithReturn(10, hdata, &hdatalen); + if (hdatalen && hdatalen != 32) { + PrintAndLog("ERROR: application parameter length must be 32 bytes only."); + return 1; + } + } + if (hdatalen) + memmove(&data[32], hdata, 32); + + CLIParserFree(); + + SetAPDULogging(APDULogging); + + // (in parameter) conrtol byte 0x07 - check only, 0x03 - user presense + cign. 0x08 - sign only + // challenge parameter [32 bytes] + // application parameter [32 bytes] + // key handle length [1b] = N + // key handle [N] + + uint8_t datalen = 32 + 32 + 1 + keyHandleLen; + + uint8_t buf[2048] = {0}; + size_t len = 0; + uint16_t sw = 0; + + DropField(); + int res = FIDOSelect(true, true, buf, sizeof(buf), &len, &sw); + + if (res) { + PrintAndLog("Can't select authenticator. res=%x. Exit...", res); + DropField(); + return res; + } + + if (sw != 0x9000) { + PrintAndLog("Can't select FIDO application. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + DropField(); + return 2; + } + + res = FIDOAuthentication(data, datalen, controlByte, buf, sizeof(buf), &len, &sw); + DropField(); + if (res) { + PrintAndLog("Can't execute authentication command. res=%x. Exit...", res); + return res; + } + + if (sw != 0x9000) { + PrintAndLog("ERROR execute authentication command. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + return 3; + } + + PrintAndLog("---------------------------------------------------------------"); + PrintAndLog("User presence: %s", (buf[0]?"verified":"not verified")); + uint32_t cntr = (uint32_t)bytes_to_num(&buf[1], 4); + PrintAndLog("Counter: %d", cntr); + PrintAndLog("Hash[%d]: %s", len - 5, sprint_hex(&buf[5], len - 5)); + + if (root) { + JsonSaveBufAsHex(root, "ChallengeParam", data, 32); + JsonSaveBufAsHex(root, "ApplicationParam", &data[32], 32); + JsonSaveInt(root, "KeyHandleLen", keyHandleLen); + JsonSaveBufAsHexCompact(root, "KeyHandle", &data[65], keyHandleLen); + JsonSaveInt(root, "Counter", cntr); + + res = json_dump_file(root, fname, JSON_INDENT(2)); + if (res) { + PrintAndLog("ERROR: can't save the file: %s", fname); + return 200; + } + PrintAndLog("File `%s` saved.", fname); + + // free json object + json_decref(root); + } + return 0; +}; + +static command_t CommandTable[] = +{ + {"help", CmdHelp, 1, "This help."}, + {"info", CmdHFFidoInfo, 0, "Info about FIDO tag."}, + {"reg", CmdHFFidoRegister, 0, "FIDO U2F Registration Message."}, + {"auth", CmdHFFidoAuthenticate, 0, "FIDO U2F Authentication Message."}, + {NULL, NULL, 0, NULL} +}; + +int CmdHFFido(const char *Cmd) { + (void)WaitForResponseTimeout(CMD_ACK,NULL,100); + CmdsParse(CommandTable, Cmd); + return 0; +} + +int CmdHelp(const char *Cmd) { + CmdsHelp(CommandTable); + return 0; +} diff --git a/client/cmdhffido.h b/client/cmdhffido.h new file mode 100644 index 00000000..2460a170 --- /dev/null +++ b/client/cmdhffido.h @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2018 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// High frequency FIDO U2F and FIDO2 contactless authenticators +//----------------------------------------------------------------------------- +// +// Documentation here: +// +// FIDO Alliance specifications +// https://fidoalliance.org/download/ +// FIDO NFC Protocol Specification v1.0 +// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-nfc-protocol-v1.2-ps-20170411.html +// FIDO U2F Raw Message Formats +// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html +//----------------------------------------------------------------------------- + +#ifndef CMDHFFIDO_H__ +#define CMDHFFIDO_H__ + +extern int CmdHFFido(const char *Cmd); + + +#endif \ No newline at end of file diff --git a/client/emv/emvcore.c b/client/emv/emvcore.c index 98ecc5b0..c1259114 100644 --- a/client/emv/emvcore.c +++ b/client/emv/emvcore.c @@ -266,9 +266,14 @@ int EMVExchangeEx(bool ActivateField, bool LeaveFieldON, sAPDU apdu, bool Includ *sw = isw; if (isw != 0x9000) { - if (APDULogging) - PrintAndLog("APDU(%02x%02x) ERROR: [%4X] %s", apdu.CLA, apdu.INS, isw, GetAPDUCodeDescription(*sw >> 8, *sw & 0xff)); - return 5; + if (APDULogging) { + if (*sw >> 8 == 0x61) { + PrintAndLog("APDU chaining len:%02x -->", *sw & 0xff); + } else { + PrintAndLog("APDU(%02x%02x) ERROR: [%4X] %s", apdu.CLA, apdu.INS, isw, GetAPDUCodeDescription(*sw >> 8, *sw & 0xff)); + return 5; + } + } } // add to tlv tree diff --git a/client/emv/emvcore.h b/client/emv/emvcore.h index ece7324a..fa7a4db8 100644 --- a/client/emv/emvcore.h +++ b/client/emv/emvcore.h @@ -70,6 +70,10 @@ extern struct tlvdb *GetdCVVRawFromTrack2(const struct tlv *track2); extern void SetAPDULogging(bool logging); +// exchange +extern int EMVExchange(bool LeaveFieldON, sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw, struct tlvdb *tlv); + + // search application extern int EMVSearchPSE(bool ActivateField, bool LeaveFieldON, bool decodeTLV, struct tlvdb *tlv); extern int EMVSearch(bool ActivateField, bool LeaveFieldON, bool decodeTLV, struct tlvdb *tlv); diff --git a/client/emv/emvjson.c b/client/emv/emvjson.c index 02297435..e56ecbb7 100644 --- a/client/emv/emvjson.c +++ b/client/emv/emvjson.c @@ -68,24 +68,40 @@ char* GetApplicationDataName(tlv_tag_t tag) { return NULL; } -int JsonSaveStr(json_t *root, char *path, char *value) { +int JsonSaveJsonObject(json_t *root, char *path, json_t *value) { json_error_t error; if (strlen(path) < 1) return 1; if (path[0] == '$') { - if (json_path_set(root, path, json_string(value), 0, &error)) { + if (json_path_set(root, path, value, 0, &error)) { PrintAndLog("ERROR: can't set json path: ", error.text); return 2; } else { return 0; } } else { - return json_object_set_new(root, path, json_string(value)); + return json_object_set_new(root, path, value); } +} + +int JsonSaveInt(json_t *root, char *path, int value) { + return JsonSaveJsonObject(root, path, json_integer(value)); +} + +int JsonSaveStr(json_t *root, char *path, char *value) { + return JsonSaveJsonObject(root, path, json_string(value)); }; +int JsonSaveBufAsHexCompact(json_t *elm, char *path, uint8_t *data, size_t datalen) { + char * msg = sprint_hex_inrow(data, datalen); + if (msg && strlen(msg) && msg[strlen(msg) - 1] == ' ') + msg[strlen(msg) - 1] = '\0'; + + return JsonSaveStr(elm, path, msg); +} + int JsonSaveBufAsHex(json_t *elm, char *path, uint8_t *data, size_t datalen) { char * msg = sprint_hex(data, datalen); if (msg && strlen(msg) && msg[strlen(msg) - 1] == ' ') @@ -248,6 +264,20 @@ bool HexToBuffer(const char *errormsg, const char *hexvalue, uint8_t * buffer, s return true; } +int JsonLoadBufAsHex(json_t *elm, char *path, uint8_t *data, size_t maxbufferlen, size_t *datalen) { + if (datalen) + *datalen = 0; + + json_t *jelm = json_path_get((const json_t *)elm, path); + if (!jelm || !json_is_string(jelm)) + return 1; + + if (!HexToBuffer("ERROR load", json_string_value(jelm), data, maxbufferlen, datalen)) + return 2; + + return 0; +}; + bool ParamLoadFromJson(struct tlvdb *tlv) { json_t *root; json_error_t error; diff --git a/client/emv/emvjson.h b/client/emv/emvjson.h index a518d7b9..9c6eda5e 100644 --- a/client/emv/emvjson.h +++ b/client/emv/emvjson.h @@ -20,7 +20,10 @@ typedef struct { extern char* GetApplicationDataName(tlv_tag_t tag); +extern int JsonSaveJsonObject(json_t *root, char *path, json_t *value); extern int JsonSaveStr(json_t *root, char *path, char *value); +extern int JsonSaveInt(json_t *root, char *path, int value); +extern int JsonSaveBufAsHexCompact(json_t *elm, char *path, uint8_t *data, size_t datalen); extern int JsonSaveBufAsHex(json_t *elm, char *path, uint8_t *data, size_t datalen); extern int JsonSaveHex(json_t *elm, char *path, uint64_t data, int datalen); @@ -30,6 +33,8 @@ extern int JsonSaveTLVTreeElm(json_t *elm, char *path, struct tlvdb *tlvdbelm, b extern int JsonSaveTLVTree(json_t *root, json_t *elm, char *path, struct tlvdb *tlvdbelm); +extern int JsonLoadBufAsHex(json_t *elm, char *path, uint8_t *data, size_t maxbufferlen, size_t *datalen); + extern bool ParamLoadFromJson(struct tlvdb *tlv); #endif \ No newline at end of file diff --git a/client/util.c b/client/util.c index d7c824d9..c6d5f0d6 100644 --- a/client/util.c +++ b/client/util.c @@ -139,7 +139,7 @@ void hex_to_buffer(const uint8_t *buf, const uint8_t *hex_data, const size_t hex // printing and converting functions char *sprint_hex(const uint8_t *data, const size_t len) { - static char buf[1025] = {0}; + static char buf[4097] = {0}; hex_to_buffer((uint8_t *)buf, data, len, sizeof(buf) - 1, 0, 1, false); @@ -147,7 +147,7 @@ char *sprint_hex(const uint8_t *data, const size_t len) { } char *sprint_hex_inrow_ex(const uint8_t *data, const size_t len, const size_t min_str_len) { - static char buf[1025] = {0}; + static char buf[4097] = {0}; hex_to_buffer((uint8_t *)buf, data, len, sizeof(buf) - 1, min_str_len, 0, false); -- 2.39.5