mirror of
https://github.com/Proxmark/proxmark3.git
synced 2025-02-23 18:27:28 -08:00
FIDO U2F NFC authenticators (#697)
* `hf fido` command * detects FIDO tag * add new commands for fido u2f * added changelog * added fido2 info
This commit is contained in:
parent
8fa6838476
commit
39cc1c879e
@ -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]
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -164,6 +164,7 @@ CMDSRCS = $(SRC_SMARTCARD) \
|
||||
cmdhfmfhard.c \
|
||||
hardnested/hardnested_bruteforce.c \
|
||||
cmdhftopaz.c \
|
||||
cmdhffido.c \
|
||||
cmdhw.c \
|
||||
cmdlf.c \
|
||||
cmdlfawid.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]"},
|
||||
|
@ -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;
|
||||
}
|
||||
|
550
client/cmdhffido.c
Normal file
550
client/cmdhffido.c
Normal file
@ -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 <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include <jansson.h>
|
||||
#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, "<HEX/ASCII challenge parameter (32b HEX/1..16 chars)>", NULL),
|
||||
arg_str0(NULL, NULL, "<HEX/ASCII application parameter (32b HEX/1..16 chars)>", 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, "<HEX key handle (var 0..255b)>", NULL),
|
||||
arg_str0(NULL, NULL, "<HEX/ASCII challenge parameter (32b HEX/1..16 chars)>", NULL),
|
||||
arg_str0(NULL, NULL, "<HEX/ASCII application parameter (32b HEX/1..16 chars)>", 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;
|
||||
}
|
27
client/cmdhffido.h
Normal file
27
client/cmdhffido.h
Normal file
@ -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
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user