]> git.zerfleddert.de Git - proxmark3-svn/commitdiff
FIDO U2F NFC authenticators (#697)
authorOleg Moiseenko <807634+merlokk@users.noreply.github.com>
Sat, 17 Nov 2018 18:22:21 +0000 (20:22 +0200)
committerpwpiwi <pwpiwi@users.noreply.github.com>
Sat, 17 Nov 2018 18:22:21 +0000 (20:22 +0200)
* `hf fido` command
* detects FIDO tag
* add new commands for fido u2f
* added changelog
* added fido2 info

14 files changed:
CHANGELOG.md
armsrc/epa.c
armsrc/iso14443a.c
armsrc/iso14443a.h
client/Makefile
client/cmdhf.c
client/cmdhf14a.c
client/cmdhffido.c [new file with mode: 0644]
client/cmdhffido.h [new file with mode: 0644]
client/emv/emvcore.c
client/emv/emvcore.h
client/emv/emvjson.c
client/emv/emvjson.h
client/util.c

index 7781935e9973c435a806103a9f148d4f22e0e274..6b134562ce11041780565764b2495963996a3383 100644 (file)
@@ -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]
 
index fd71430bf04ba1906a75c3cae42988079d1844c2..01aff30235bf3d2d02e5d231244e47378d5abba0 100644 (file)
@@ -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);
index 059db71e5f0708f39f3c5128f3051a4d5197f2e6..7bf8f5af7d110e4f440b933ebed4f5c1f8138052 100644 (file)
@@ -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();
        }
 
index 8796edf5b8395a4cbe9811fd40d8012beefc39ca..396b21008ecd474515e628b66fda5e1b1b72ba8a 100644 (file)
@@ -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);
index 1b211d4f159090ed76024113c7f5493f2402e6f8..969eb2411b87b981626fc7b5640f1a30a9a673e8 100644 (file)
@@ -164,6 +164,7 @@ CMDSRCS =   $(SRC_SMARTCARD) \
                        cmdhfmfhard.c \
                        hardnested/hardnested_bruteforce.c \
                        cmdhftopaz.c \
+                       cmdhffido.c \
                        cmdhw.c \
                        cmdlf.c \
                        cmdlfawid.c \
index 762cc791443bd01b426c71e23cca91cda7aa7118..d220180089aae888b68b5731414a5f4f32a1739f 100644 (file)
@@ -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]"},
index 326eaf50f91316a7540d61e1702e10e06c494e4e..922a94492d9f2e1628fcdd7b9d885dac94bc4027 100644 (file)
@@ -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 (file)
index 0000000..fd97ace
--- /dev/null
@@ -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;
+}
diff --git a/client/cmdhffido.h b/client/cmdhffido.h
new file mode 100644 (file)
index 0000000..2460a17
--- /dev/null
@@ -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
index 98ecc5b04ba8d43c4ca4c4965d0782682eb2b8fb..c12591148766bd4c3ffd48ac66e185339b6879ae 100644 (file)
@@ -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
index ece7324a8f5f47525c22933e1922a154481d72f5..fa7a4db8abf3237e6e262bdf64ff7183a529317a 100644 (file)
@@ -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);
index 022974358c4d04d0256a97faeef4334a2864aa86..e56ecbb71dbe95c0d7f00e38e267fa4182048a24 100644 (file)
@@ -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;
index a518d7b9113292f422adaf3ee6291dbc57eb067a..9c6eda5ea19aacca87b81d11bc8d42eb162f8ff0 100644 (file)
@@ -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
index d7c824d9c85505599b08067265a6723605996187..c6d5f0d6207e35803f8d79f51aeae0c9c1836bc5 100644 (file)
@@ -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);
 
Impressum, Datenschutz