X-Git-Url: http://git.zerfleddert.de/cgi-bin/gitweb.cgi/proxmark3-svn/blobdiff_plain/8fa68384769d5d241bac693ac86ff03dbdc6a81d..39cc1c879e3d75b3cafd79e4e139a7f6673dd349:/client/cmdhffido.c?ds=sidebyside 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; +}