905 lines
33 KiB
C
905 lines
33 KiB
C
//-----------------------------------------------------------------------------
|
|
// 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 <unistd.h>
|
|
#include "cmdparser.h" // command_t
|
|
#include "commonutil.h"
|
|
#include "comms.h"
|
|
#include "proxmark3.h"
|
|
#include "emv/emvcore.h"
|
|
#include "emv/emvjson.h"
|
|
#include "cliparser.h"
|
|
#include "crypto/asn1utils.h"
|
|
#include "crypto/libpcrypto.h"
|
|
#include "fido/cbortools.h"
|
|
#include "fido/fidocore.h"
|
|
#include "ui.h"
|
|
#include "cmdhf14a.h"
|
|
#include "cmdtrace.h"
|
|
#include "util.h"
|
|
#include "fileutils.h" // laodFileJSONroot
|
|
|
|
#define DEF_FIDO_SIZE 2048
|
|
#define DEF_FIDO_PARAM_FILE "fido2_defparams.json"
|
|
|
|
static int CmdHelp(const char *Cmd);
|
|
|
|
static int CmdHFFidoList(const char *Cmd) {
|
|
return CmdTraceListAlias(Cmd, "hf fido", "14a");
|
|
}
|
|
|
|
static int CmdHFFidoInfo(const char *Cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf fido info",
|
|
"Get info from Fido tags",
|
|
"hf fido info");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
|
CLIParserFree(ctx);
|
|
|
|
// info about 14a part
|
|
infoHF14A(false, false, false);
|
|
|
|
// FIDO info
|
|
PrintAndLogEx(INFO, "-----------" _CYAN_("FIDO Info") "---------------------------------");
|
|
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)
|
|
PrintAndLogEx(INFO, "Not a FIDO card! APDU response: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
else
|
|
PrintAndLogEx(ERR, "APDU exchange error. Card returns 0x0000.");
|
|
|
|
DropField();
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
if (!strncmp((char *)buf, "U2F_V2", 7)) {
|
|
if (!strncmp((char *)buf, "FIDO_2_0", 8)) {
|
|
PrintAndLogEx(INFO, "FIDO2 authenticator detected. Version: %.*s", (int)len, buf);
|
|
} else {
|
|
PrintAndLogEx(INFO, "FIDO authenticator detected (not standard U2F).");
|
|
PrintAndLogEx(INFO, "Non U2F authenticator version:");
|
|
print_buffer((const unsigned char *)buf, len, 1);
|
|
}
|
|
} else {
|
|
PrintAndLogEx(INFO, "FIDO U2F authenticator detected. Version: %.*s", (int)len, buf);
|
|
}
|
|
|
|
res = FIDO2GetInfo(buf, sizeof(buf), &len, &sw);
|
|
DropField();
|
|
if (res) {
|
|
return res;
|
|
}
|
|
if (sw != 0x9000) {
|
|
PrintAndLogEx(ERR, "FIDO2 version doesn't exist (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
if (buf[0]) {
|
|
PrintAndLogEx(ERR, "FIDO2 get version error: %d - %s", buf[0], fido2GetCmdErrorDescription(buf[0]));
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
if (len > 1) {
|
|
PrintAndLogEx(SUCCESS, "FIDO2 version CBOR decoded:");
|
|
TinyCborPrintFIDOPackage(fido2CmdGetInfo, true, &buf[1], len - 1);
|
|
} else {
|
|
PrintAndLogEx(ERR, "FIDO2 version length error");
|
|
}
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHFFidoRegister(const char *cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf fido reg",
|
|
"Initiate a U2F token registration. Needs two 32-byte hash numbers.\n"
|
|
"challenge parameter (32b) and application parameter (32b).\n"
|
|
"The output template filename is `hf-fido2-params.json`\n"
|
|
"\n",
|
|
"hf fido reg -> execute command with 2 parameters, filled 0x00\n"
|
|
"hf fido reg --cp s0 --ap s1 -> execute command with plain parameters\n"
|
|
"hf fido reg --cpx 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f --apx 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f\n"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("a", "apdu", "show APDU requests and responses"),
|
|
arg_litn("v", "verbose", 0, 2, "show technical data. vv - show full certificates data"),
|
|
arg_lit0("t", "tlv", "Show DER certificate contents in TLV representation"),
|
|
arg_str0("f", "file", "<fn>", "JSON input file name for parameters"),
|
|
arg_str0(NULL, "cp", "<ascii>", "challenge parameter (1..16 chars)"),
|
|
arg_str0(NULL, "ap", "<ascii>", "application parameter (1..16 chars)"),
|
|
arg_str0(NULL, "cpx", "<hex>", "challenge parameter (32 bytes hex)"),
|
|
arg_str0(NULL, "apx", "<hex>", "application parameter (32 bytes hex)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, cmd, argtable, true);
|
|
|
|
bool APDULogging = arg_get_lit(ctx, 1);
|
|
bool verbose = arg_get_lit(ctx, 2);
|
|
bool verbose2 = arg_get_lit(ctx, 2) > 1;
|
|
bool showDERTLV = arg_get_lit(ctx, 3);
|
|
bool cpplain = arg_get_str_len(ctx, 5);
|
|
bool applain = arg_get_str_len(ctx, 6);
|
|
bool cphex = arg_get_str_len(ctx, 7);
|
|
bool aphex = arg_get_str_len(ctx, 8);
|
|
|
|
uint8_t data[64] = {0};
|
|
int chlen = 0;
|
|
uint8_t cdata[250] = {0};
|
|
int applen = 0;
|
|
uint8_t adata[250] = {0};
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
// deafault name
|
|
if (fnlen == 0) {
|
|
strcat(filename, DEF_FIDO_PARAM_FILE);
|
|
fnlen = strlen(filename);
|
|
}
|
|
|
|
json_t *root = NULL;
|
|
int res = loadFileJSONroot(filename, (void **)&root, verbose);
|
|
if (res != PM3_SUCCESS) {
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
size_t jlen = 0;
|
|
JsonLoadBufAsHex(root, "$.ChallengeParam", data, 32, &jlen);
|
|
JsonLoadBufAsHex(root, "$.ApplicationParam", &data[32], 32, &jlen);
|
|
|
|
if (cpplain) {
|
|
memset(cdata, 0x00, 32);
|
|
chlen = sizeof(cdata);
|
|
CLIGetStrWithReturn(ctx, 5, cdata, &chlen);
|
|
if (chlen > 16) {
|
|
PrintAndLogEx(ERR, "ERROR: challenge parameter length in ASCII mode must be less than 16 chars instead of: %d", chlen);
|
|
CLIParserFree(ctx);
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
if (cphex && cpplain == false) {
|
|
chlen = sizeof(cdata);
|
|
CLIGetHexWithReturn(ctx, 7, cdata, &chlen);
|
|
if (chlen && chlen != 32) {
|
|
PrintAndLogEx(ERR, "ERROR: challenge parameter length must be 32 bytes only.");
|
|
CLIParserFree(ctx);
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
if (chlen)
|
|
memmove(data, cdata, 32);
|
|
|
|
if (applain) {
|
|
memset(adata, 0x00, 32);
|
|
applen = sizeof(adata);
|
|
CLIGetStrWithReturn(ctx, 6, adata, &applen);
|
|
if (applen > 16) {
|
|
PrintAndLogEx(ERR, "ERROR: application parameter length in ASCII mode must be less than 16 chars instead of: %d", applen);
|
|
CLIParserFree(ctx);
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
if (aphex && applain == false) {
|
|
applen = sizeof(adata);
|
|
CLIGetHexWithReturn(ctx, 8, adata, &applen);
|
|
if (applen && applen != 32) {
|
|
PrintAndLogEx(ERR, "ERROR: application parameter length must be 32 bytes only.");
|
|
CLIParserFree(ctx);
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
if (applen)
|
|
memmove(&data[32], adata, 32);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
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();
|
|
res = FIDOSelect(true, true, buf, sizeof(buf), &len, &sw);
|
|
|
|
if (res) {
|
|
PrintAndLogEx(ERR, "Can't select authenticator. res=%x. Exit...", res);
|
|
DropField();
|
|
json_decref(root);
|
|
return res;
|
|
}
|
|
|
|
if (sw != 0x9000) {
|
|
PrintAndLogEx(ERR, "Can't select FIDO application. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
DropField();
|
|
json_decref(root);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
res = FIDORegister(data, buf, sizeof(buf), &len, &sw);
|
|
DropField();
|
|
if (res) {
|
|
PrintAndLogEx(ERR, "Can't execute register command. res=%x. Exit...", res);
|
|
json_decref(root);
|
|
return res;
|
|
}
|
|
|
|
if (sw != 0x9000) {
|
|
PrintAndLogEx(ERR, "ERROR execute register command. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
PrintAndLogEx(NORMAL, "");
|
|
if (APDULogging)
|
|
PrintAndLogEx(INFO, "---------------------------------------------------------------");
|
|
|
|
PrintAndLogEx(INFO, "data len: %zu", len);
|
|
|
|
if (verbose2) {
|
|
PrintAndLogEx(INFO, "------------ " _CYAN_("data") " ----------------------");
|
|
print_buffer((const unsigned char *)buf, len, 1);
|
|
PrintAndLogEx(INFO, "-------------" _CYAN_("data") " ----------------------");
|
|
}
|
|
|
|
if (buf[0] != 0x05) {
|
|
PrintAndLogEx(ERR, "ERROR: First byte must be 0x05, but it %2x", buf[0]);
|
|
json_decref(root);
|
|
return PM3_ESOFT;
|
|
}
|
|
PrintAndLogEx(SUCCESS, "User public key: %s", sprint_hex(&buf[1], 65));
|
|
|
|
uint8_t keyHandleLen = buf[66];
|
|
PrintAndLogEx(SUCCESS, "Key handle[%d]: %s", keyHandleLen, sprint_hex(&buf[67], keyHandleLen));
|
|
|
|
int derp = 67 + keyHandleLen;
|
|
int derLen = (buf[derp + 2] << 8) + buf[derp + 3] + 4;
|
|
if (verbose2) {
|
|
PrintAndLogEx(INFO, "DER certificate[%d]:", derLen);
|
|
PrintAndLogEx(INFO, "------------------DER-------------------");
|
|
PrintAndLogEx(INFO, "%s", sprint_hex(buf + derp, derLen));
|
|
PrintAndLogEx(INFO, "----------------DER---------------------");
|
|
} else {
|
|
if (verbose)
|
|
PrintAndLogEx(INFO, "------------------DER-------------------");
|
|
PrintAndLogEx(INFO, "DER certificate[%d]: %s...", derLen, sprint_hex(&buf[derp], 20));
|
|
}
|
|
|
|
// check and print DER certificate
|
|
uint8_t public_key[65] = {0};
|
|
|
|
// print DER certificate in TLV view
|
|
if (showDERTLV) {
|
|
PrintAndLogEx(INFO, "----------------DER TLV-----------------");
|
|
asn1_print(&buf[derp], derLen, " ");
|
|
PrintAndLogEx(INFO, "----------------DER TLV-----------------");
|
|
}
|
|
|
|
FIDOCheckDERAndGetKey(&buf[derp], derLen, verbose, public_key, sizeof(public_key));
|
|
|
|
// get hash
|
|
int hashp = 1 + 65 + 1 + keyHandleLen + derLen;
|
|
PrintAndLogEx(SUCCESS, "Hash[%zu]: %s", len - hashp, sprint_hex(&buf[hashp], len - hashp));
|
|
|
|
// check ANSI X9.62 format ECDSA signature (on P-256)
|
|
uint8_t rval[300] = {0};
|
|
uint8_t sval[300] = {0};
|
|
res = ecdsa_asn1_get_signature(&buf[hashp], len - hashp, rval, sval);
|
|
if (res == PM3_SUCCESS) {
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, " r: %s", sprint_hex(rval, 32));
|
|
PrintAndLogEx(INFO, " s: %s", sprint_hex(sval, 32));
|
|
}
|
|
|
|
uint8_t xbuf[4096] = {0};
|
|
size_t xbuflen = 0;
|
|
res = FillBuffer(xbuf, sizeof(xbuf), &xbuflen,
|
|
"\x00", 1,
|
|
&data[32], 32, // application parameter
|
|
&data[0], 32, // challenge parameter
|
|
&buf[67], keyHandleLen, // keyHandle
|
|
&buf[1], 65, // user public key
|
|
NULL, 0);
|
|
(void)res;
|
|
//PrintAndLogEx(INFO, "--xbuf(%d)[%d]: %s", res, xbuflen, sprint_hex(xbuf, xbuflen));
|
|
res = ecdsa_signature_verify(MBEDTLS_ECP_DP_SECP256R1, public_key, xbuf, xbuflen, &buf[hashp], len - hashp, true);
|
|
if (res) {
|
|
if (res == MBEDTLS_ERR_ECP_VERIFY_FAILED) {
|
|
PrintAndLogEx(WARNING, "Signature is ( " _RED_("not valid") " )");
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Other signature check error: %x %s", (res < 0) ? -res : res, ecdsa_get_error(res));
|
|
}
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, "Signature is ( " _GREEN_("ok") " )");
|
|
}
|
|
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Invalid signature. res = %d. ( " _RED_("fail") " )", res);
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "");
|
|
PrintAndLogEx(INFO, "auth command: ");
|
|
char command[500] = {0};
|
|
sprintf(command, "hf fido auth --kh %s", sprint_hex_inrow(&buf[67], keyHandleLen));
|
|
if (chlen)
|
|
sprintf(command + strlen(command), " --%s %s", cpplain ? "cp" : "cpx", cpplain ? (char *)cdata : sprint_hex_inrow(cdata, 32));
|
|
if (applen)
|
|
sprintf(command + strlen(command), " --%s %s", applain ? "cp" : "cpx", applain ? (char *)adata : sprint_hex_inrow(adata, 32));
|
|
PrintAndLogEx(INFO, "%s", command);
|
|
|
|
if (root) {
|
|
JsonSaveBufAsHex(root, "ChallengeParam", data, 32);
|
|
JsonSaveBufAsHex(root, "ApplicationParam", &data[32], 32);
|
|
JsonSaveBufAsHexCompact(root, "PublicKey", &buf[1], 65);
|
|
JsonSaveInt(root, "KeyHandleLen", keyHandleLen);
|
|
JsonSaveBufAsHexCompact(root, "KeyHandle", &buf[67], keyHandleLen);
|
|
JsonSaveBufAsHexCompact(root, "DER", &buf[67 + keyHandleLen], derLen);
|
|
|
|
//sprintf(filename, "hf-fido2-params");
|
|
res = saveFileJSONroot(filename, root, JSON_INDENT(2), verbose);
|
|
(void)res;
|
|
}
|
|
json_decref(root);
|
|
return PM3_SUCCESS;
|
|
}
|
|
|
|
static int CmdHFFidoAuthenticate(const char *cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf fido auth",
|
|
"Initiate a U2F token authentication. Needs key handle and two 32-byte hash numbers.\n"
|
|
"key handle(var 0..255), challenge parameter (32b) and application parameter (32b)\n"
|
|
"The output template filename is `hf-fido2-params.json`\n"
|
|
"\n",
|
|
"hf fido auth --kh 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f -> execute command with 2 parameters, filled 0x00 and key handle\n"
|
|
"hf fido auth \n"
|
|
"--kh 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f\n"
|
|
"--cp 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f \n"
|
|
"--ap 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f -> execute command with parameters");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("a", "apdu", "show APDU reqests and responses"),
|
|
arg_lit0("v", "verbose", "show technical data"),
|
|
arg_lit0("p", "plain", "send plain ASCII to challenge and application parameters instead of HEX"),
|
|
arg_rem("default mode:", "dont-enforce-user-presence-and-sign"),
|
|
arg_lit0("u", "user", "mode: enforce-user-presence-and-sign"),
|
|
arg_lit0("c", "check", "mode: check-only"),
|
|
arg_str0("f", "file", "<fn>", "JSON input file name for parameters"),
|
|
arg_str0("k", "key", "<hex>", "public key to verify signature"),
|
|
arg_str0(NULL, "kh", "<hex>", "key handle (var 0..255b)"),
|
|
arg_str0(NULL, "cp", "<ascii>", "challenge parameter (1..16 chars)"),
|
|
arg_str0(NULL, "ap", "<ascii>", "application parameter (1..16 chars)"),
|
|
arg_str0(NULL, "cpx", "<hex>", "challenge parameter (32 bytes hex)"),
|
|
arg_str0(NULL, "apx", "<hex>", "application parameter (32 bytes hex)"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, cmd, argtable, true);
|
|
|
|
bool APDULogging = arg_get_lit(ctx, 1);
|
|
bool verbose = arg_get_lit(ctx, 2);
|
|
bool paramsPlain = arg_get_lit(ctx, 3);
|
|
|
|
uint8_t controlByte = 0x08;
|
|
if (arg_get_lit(ctx, 5))
|
|
controlByte = 0x03;
|
|
|
|
if (arg_get_lit(ctx, 6))
|
|
controlByte = 0x07;
|
|
|
|
uint8_t data[512] = {0};
|
|
uint8_t hdata[250] = {0};
|
|
bool public_key_loaded = false;
|
|
uint8_t public_key[65] = {0};
|
|
int hdatalen = 0;
|
|
uint8_t keyHandleLen = 0;
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 7), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
|
|
// deafault name
|
|
if (fnlen == 0) {
|
|
strcat(filename, DEF_FIDO_PARAM_FILE);
|
|
fnlen = strlen(filename);
|
|
}
|
|
|
|
json_t *root = NULL;
|
|
int res = loadFileJSONroot(filename, (void **)&root, verbose);
|
|
if (res != PM3_SUCCESS) {
|
|
CLIParserFree(ctx);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
size_t jlen = 0;
|
|
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;
|
|
JsonLoadBufAsHex(root, "$.PublicKey", public_key, 65, &jlen);
|
|
public_key_loaded = (jlen > 0);
|
|
|
|
// public key
|
|
CLIGetHexWithReturn(ctx, 8, hdata, &hdatalen);
|
|
if (hdatalen && hdatalen != 65) {
|
|
PrintAndLogEx(ERR, "ERROR: public key length must be 65 bytes only.");
|
|
CLIParserFree(ctx);
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (hdatalen) {
|
|
memmove(public_key, hdata, hdatalen);
|
|
public_key_loaded = true;
|
|
}
|
|
|
|
CLIGetHexWithReturn(ctx, 9, hdata, &hdatalen);
|
|
if (hdatalen > 255) {
|
|
PrintAndLogEx(ERR, "ERROR: application parameter length must be less than 255.");
|
|
CLIParserFree(ctx);
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
if (hdatalen) {
|
|
keyHandleLen = hdatalen;
|
|
data[64] = keyHandleLen;
|
|
memmove(&data[65], hdata, keyHandleLen);
|
|
}
|
|
|
|
if (paramsPlain) {
|
|
memset(hdata, 0x00, 32);
|
|
hdatalen = sizeof(hdata);
|
|
CLIGetStrWithReturn(ctx, 10, hdata, &hdatalen);
|
|
if (hdatalen > 16) {
|
|
PrintAndLogEx(ERR, "ERROR: challenge parameter length in ASCII mode must be less than 16 chars instead of: %d", hdatalen);
|
|
CLIParserFree(ctx);
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
} else {
|
|
hdatalen = sizeof(hdata);
|
|
CLIGetHexWithReturn(ctx, 12, hdata, &hdatalen);
|
|
if (hdatalen && hdatalen != 32) {
|
|
PrintAndLogEx(ERR, "ERROR: challenge parameter length must be 32 bytes only.");
|
|
CLIParserFree(ctx);
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
if (hdatalen)
|
|
memmove(data, hdata, 32);
|
|
|
|
if (paramsPlain) {
|
|
memset(hdata, 0x00, 32);
|
|
hdatalen = sizeof(hdata);
|
|
CLIGetStrWithReturn(ctx, 11, hdata, &hdatalen);
|
|
if (hdatalen > 16) {
|
|
PrintAndLogEx(ERR, "ERROR: application parameter length in ASCII mode must be less than 16 chars instead of: %d", hdatalen);
|
|
CLIParserFree(ctx);
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
} else {
|
|
hdatalen = sizeof(hdata);
|
|
CLIGetHexWithReturn(ctx, 13, hdata, &hdatalen);
|
|
if (hdatalen && hdatalen != 32) {
|
|
PrintAndLogEx(ERR, "ERROR: application parameter length must be 32 bytes only.");
|
|
CLIParserFree(ctx);
|
|
json_decref(root);
|
|
return PM3_EINVARG;
|
|
}
|
|
}
|
|
|
|
if (hdatalen)
|
|
memmove(&data[32], hdata, 32);
|
|
|
|
CLIParserFree(ctx);
|
|
|
|
SetAPDULogging(APDULogging);
|
|
|
|
// (in parameter) control byte 0x07 - check only, 0x03 - user presence + 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();
|
|
res = FIDOSelect(true, true, buf, sizeof(buf), &len, &sw);
|
|
if (res) {
|
|
PrintAndLogEx(ERR, "Can't select authenticator. res=%x. Exit...", res);
|
|
DropField();
|
|
json_decref(root);
|
|
return res;
|
|
}
|
|
|
|
if (sw != 0x9000) {
|
|
PrintAndLogEx(ERR, "Can't select FIDO application. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
DropField();
|
|
json_decref(root);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
res = FIDOAuthentication(data, datalen, controlByte, buf, sizeof(buf), &len, &sw);
|
|
DropField();
|
|
if (res) {
|
|
PrintAndLogEx(ERR, "Can't execute authentication command. res=%x. Exit...", res);
|
|
json_decref(root);
|
|
return res;
|
|
}
|
|
|
|
if (sw != 0x9000) {
|
|
PrintAndLogEx(ERR, "ERROR execute authentication command. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
json_decref(root);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
PrintAndLogEx(INFO, "---------------------------------------------------------------");
|
|
PrintAndLogEx(SUCCESS, "User presence: %s", (buf[0] ? "verified" : "not verified"));
|
|
uint32_t cntr = (uint32_t)bytes_to_num(&buf[1], 4);
|
|
PrintAndLogEx(SUCCESS, "Counter: %d", cntr);
|
|
PrintAndLogEx(SUCCESS, "Hash[%zu]: %s", len - 5, sprint_hex(&buf[5], len - 5));
|
|
|
|
// check ANSI X9.62 format ECDSA signature (on P-256)
|
|
uint8_t rval[300] = {0};
|
|
uint8_t sval[300] = {0};
|
|
res = ecdsa_asn1_get_signature(&buf[5], len - 5, rval, sval);
|
|
if (res == PM3_SUCCESS) {
|
|
if (verbose) {
|
|
PrintAndLogEx(INFO, " r: %s", sprint_hex(rval, 32));
|
|
PrintAndLogEx(INFO, " s: %s", sprint_hex(sval, 32));
|
|
}
|
|
if (public_key_loaded) {
|
|
uint8_t xbuf[4096] = {0};
|
|
size_t xbuflen = 0;
|
|
res = FillBuffer(xbuf, sizeof(xbuf), &xbuflen,
|
|
&data[32], 32, // application parameter
|
|
&buf[0], 1, // user presence
|
|
&buf[1], 4, // counter
|
|
data, 32, // challenge parameter
|
|
NULL, 0);
|
|
(void)res;
|
|
//PrintAndLogEx(INFO, "--xbuf(%d)[%d]: %s", res, xbuflen, sprint_hex(xbuf, xbuflen));
|
|
res = ecdsa_signature_verify(MBEDTLS_ECP_DP_SECP256R1, public_key, xbuf, xbuflen, &buf[5], len - 5, true);
|
|
if (res) {
|
|
if (res == MBEDTLS_ERR_ECP_VERIFY_FAILED) {
|
|
PrintAndLogEx(WARNING, "Signature is" _RED_("NOT VALID."));
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Other signature check error: %x %s", (res < 0) ? -res : res, ecdsa_get_error(res));
|
|
}
|
|
} else {
|
|
PrintAndLogEx(SUCCESS, "Signature is (" _GREEN_("ok") " )");
|
|
}
|
|
} else {
|
|
PrintAndLogEx(WARNING, "No public key provided. can't check signature.");
|
|
}
|
|
} else {
|
|
PrintAndLogEx(WARNING, "Invalid signature. res = %d. ( " _RED_("fail") " )", res);
|
|
}
|
|
|
|
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);
|
|
|
|
sprintf(filename, "hf-fido2-params");
|
|
res = saveFileJSONroot(filename, root, JSON_INDENT(2), verbose);
|
|
(void)res;
|
|
}
|
|
json_decref(root);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
static int CmdHFFido2MakeCredential(const char *cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf fido make",
|
|
"Execute a FIDO2 Make Credential command. Needs json file with parameters.\n"
|
|
"Sample file `fido2_defparams.json` in `client/resources/`.",
|
|
"hf fido make --> use default parameters file `fido2_defparams.json`\n"
|
|
"hf fido make -f test.json --> use parameters file `text.json`"
|
|
);
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("a", "apdu", "show APDU reqests and responses"),
|
|
arg_litn("v", "verbose", 0, 2, "show technical data. vv - show full certificates data"),
|
|
arg_lit0("t", "tlv", "Show DER certificate contents in TLV representation"),
|
|
arg_lit0("c", "cbor", "show CBOR decoded data"),
|
|
arg_str0("f", "file", "<fn>", "parameter JSON file name"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, cmd, argtable, true);
|
|
|
|
bool APDULogging = arg_get_lit(ctx, 1);
|
|
bool verbose = arg_get_lit(ctx, 2);
|
|
bool verbose2 = arg_get_lit(ctx, 2) > 1;
|
|
bool showDERTLV = arg_get_lit(ctx, 3);
|
|
bool showCBOR = arg_get_lit(ctx, 4);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
CLIParserFree(ctx);
|
|
|
|
// deafault name
|
|
if (fnlen == 0) {
|
|
strcat(filename, DEF_FIDO_PARAM_FILE);
|
|
fnlen = strlen(filename);
|
|
}
|
|
|
|
json_t *root = NULL;
|
|
loadFileJSONroot(filename, (void **)&root, verbose);
|
|
if (root == NULL) {
|
|
return PM3_EFILE;
|
|
}
|
|
|
|
SetAPDULogging(APDULogging);
|
|
|
|
uint8_t data[DEF_FIDO_SIZE] = {0};
|
|
size_t datalen = 0;
|
|
uint8_t buf[DEF_FIDO_SIZE] = {0};
|
|
size_t len = 0;
|
|
uint16_t sw = 0;
|
|
|
|
DropField();
|
|
int res = FIDOSelect(true, true, buf, sizeof(buf), &len, &sw);
|
|
if (res) {
|
|
PrintAndLogEx(ERR, "Can't select authenticator. res=%x. Exit...", res);
|
|
DropField();
|
|
json_decref(root);
|
|
return res;
|
|
}
|
|
|
|
if (sw != 0x9000) {
|
|
PrintAndLogEx(ERR, "Can't select FIDO application. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
DropField();
|
|
json_decref(root);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
res = FIDO2CreateMakeCredentionalReq(root, data, sizeof(data), &datalen);
|
|
if (res) {
|
|
json_decref(root);
|
|
return res;
|
|
}
|
|
|
|
if (showCBOR) {
|
|
PrintAndLogEx(INFO, "CBOR make credential request:");
|
|
PrintAndLogEx(INFO, "---------------- " _CYAN_("CBOR") " ------------------");
|
|
TinyCborPrintFIDOPackage(fido2CmdMakeCredential, false, data, datalen);
|
|
PrintAndLogEx(INFO, "---------------- " _CYAN_("CBOR") " ------------------");
|
|
}
|
|
|
|
res = FIDO2MakeCredential(data, datalen, buf, sizeof(buf), &len, &sw);
|
|
DropField();
|
|
if (res) {
|
|
PrintAndLogEx(ERR, "Can't execute make credential command. res=%x. exit...", res);
|
|
json_decref(root);
|
|
return res;
|
|
}
|
|
|
|
if (sw != 0x9000) {
|
|
PrintAndLogEx(ERR, "ERROR execute make credential command. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
json_decref(root);
|
|
return PM3_EFILE;
|
|
}
|
|
|
|
if (buf[0]) {
|
|
PrintAndLogEx(ERR, "FIDO2 make credential error: %d - %s", buf[0], fido2GetCmdErrorDescription(buf[0]));
|
|
json_decref(root);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "MakeCredential result %zu b ( ok )", len);
|
|
if (showCBOR) {
|
|
PrintAndLogEx(SUCCESS, "CBOR make credential response:");
|
|
PrintAndLogEx(INFO, "---------------- " _CYAN_("CBOR") " ------------------");
|
|
TinyCborPrintFIDOPackage(fido2CmdMakeCredential, true, &buf[1], len - 1);
|
|
PrintAndLogEx(INFO, "---------------- " _CYAN_("CBOR") " ------------------");
|
|
}
|
|
|
|
// parse returned cbor
|
|
FIDO2MakeCredentionalParseRes(root, &buf[1], len - 1, verbose, verbose2, showCBOR, showDERTLV);
|
|
|
|
// new file name..
|
|
sprintf(filename, "hf-fido2");
|
|
res = saveFileJSONroot(filename, root, JSON_INDENT(2), verbose);
|
|
(void)res;
|
|
json_decref(root);
|
|
return res;
|
|
}
|
|
|
|
static int CmdHFFido2GetAssertion(const char *cmd) {
|
|
CLIParserContext *ctx;
|
|
CLIParserInit(&ctx, "hf fido assert",
|
|
"Execute a FIDO2 Get Assertion command. Needs json file with parameters.\n"
|
|
"Sample file `fido2_defparams.json` in `client/resources/`.\n"
|
|
"- Needs if `rk` option is `false` (authenticator doesn't store credential to its memory)"
|
|
,
|
|
"hf fido assert --> default parameters file `fido2_defparams.json`\n"
|
|
"hf fido assert -f test.json -l --> use parameters file `text.json` and add to request CredentialId");
|
|
|
|
void *argtable[] = {
|
|
arg_param_begin,
|
|
arg_lit0("a", "apdu", "show APDU reqests and responses"),
|
|
arg_litn("v", "verbose", 0, 2, "show technical data. vv - show full certificates data"),
|
|
arg_lit0("c", "cbor", "show CBOR decoded data"),
|
|
arg_lit0("l", "list", "add CredentialId from json to allowList"),
|
|
arg_str0("f", "file", "<fn>", "parameter JSON file name"),
|
|
arg_param_end
|
|
};
|
|
CLIExecWithReturn(ctx, cmd, argtable, true);
|
|
|
|
bool APDULogging = arg_get_lit(ctx, 1);
|
|
bool verbose = arg_get_lit(ctx, 2);
|
|
bool verbose2 = arg_get_lit(ctx, 2) > 1;
|
|
bool showCBOR = arg_get_lit(ctx, 3);
|
|
bool createAllowList = arg_get_lit(ctx, 4);
|
|
|
|
int fnlen = 0;
|
|
char filename[FILE_PATH_SIZE] = {0};
|
|
CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
|
|
CLIParserFree(ctx);
|
|
|
|
// deafault name
|
|
if (fnlen == 0) {
|
|
strcat(filename, DEF_FIDO_PARAM_FILE);
|
|
fnlen = strlen(filename);
|
|
}
|
|
|
|
json_t *root = NULL;
|
|
loadFileJSONroot(filename, (void **)&root, verbose);
|
|
if (root == NULL) {
|
|
return PM3_EFILE;
|
|
}
|
|
|
|
SetAPDULogging(APDULogging);
|
|
|
|
uint8_t data[DEF_FIDO_SIZE] = {0};
|
|
size_t datalen = 0;
|
|
uint8_t buf[DEF_FIDO_SIZE] = {0};
|
|
size_t len = 0;
|
|
uint16_t sw = 0;
|
|
|
|
DropField();
|
|
int res = FIDOSelect(true, true, buf, sizeof(buf), &len, &sw);
|
|
if (res) {
|
|
PrintAndLogEx(ERR, "Can't select authenticator. res=%x. exiting...", res);
|
|
DropField();
|
|
json_decref(root);
|
|
return res;
|
|
}
|
|
|
|
if (sw != 0x9000) {
|
|
PrintAndLogEx(ERR, "Can't select FIDO application. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
DropField();
|
|
json_decref(root);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
res = FIDO2CreateGetAssertionReq(root, data, sizeof(data), &datalen, createAllowList);
|
|
if (res) {
|
|
json_decref(root);
|
|
return res;
|
|
}
|
|
|
|
if (showCBOR) {
|
|
PrintAndLogEx(SUCCESS, "CBOR get assertion request:");
|
|
PrintAndLogEx(INFO, "---------------- " _CYAN_("CBOR") " ------------------");
|
|
TinyCborPrintFIDOPackage(fido2CmdGetAssertion, false, data, datalen);
|
|
PrintAndLogEx(INFO, "---------------- " _CYAN_("CBOR") " ------------------");
|
|
}
|
|
|
|
res = FIDO2GetAssertion(data, datalen, buf, sizeof(buf), &len, &sw);
|
|
DropField();
|
|
if (res) {
|
|
PrintAndLogEx(ERR, "Can't execute get assertion command. res=%x. Exit...", res);
|
|
json_decref(root);
|
|
return res;
|
|
}
|
|
|
|
if (sw != 0x9000) {
|
|
PrintAndLogEx(ERR, "ERROR execute get assertion command. APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
|
|
json_decref(root);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
if (buf[0]) {
|
|
PrintAndLogEx(ERR, "FIDO2 get assertion error: %d - %s", buf[0], fido2GetCmdErrorDescription(buf[0]));
|
|
json_decref(root);
|
|
return PM3_ESOFT;
|
|
}
|
|
|
|
PrintAndLogEx(SUCCESS, "GetAssertion result (%zu b) OK.", len);
|
|
if (showCBOR) {
|
|
PrintAndLogEx(SUCCESS, "CBOR get assertion response:");
|
|
PrintAndLogEx(INFO, "---------------- " _CYAN_("CBOR") " ------------------");
|
|
TinyCborPrintFIDOPackage(fido2CmdGetAssertion, true, &buf[1], len - 1);
|
|
PrintAndLogEx(INFO, "---------------- " _CYAN_("CBOR") " ------------------");
|
|
}
|
|
|
|
// parse returned cbor
|
|
FIDO2GetAssertionParseRes(root, &buf[1], len - 1, verbose, verbose2, showCBOR);
|
|
|
|
// new file name..
|
|
sprintf(filename, "hf-fido2");
|
|
res = saveFileJSONroot(filename, root, JSON_INDENT(2), verbose);
|
|
(void)res;
|
|
json_decref(root);
|
|
return res;
|
|
}
|
|
|
|
static command_t CommandTable[] = {
|
|
{"help", CmdHelp, AlwaysAvailable, "This help."},
|
|
{"list", CmdHFFidoList, AlwaysAvailable, "List ISO 14443A history"},
|
|
{"info", CmdHFFidoInfo, IfPm3Iso14443a, "Info about FIDO tag."},
|
|
{"reg", CmdHFFidoRegister, IfPm3Iso14443a, "FIDO U2F Registration Message."},
|
|
{"auth", CmdHFFidoAuthenticate, IfPm3Iso14443a, "FIDO U2F Authentication Message."},
|
|
{"make", CmdHFFido2MakeCredential, IfPm3Iso14443a, "FIDO2 MakeCredential command."},
|
|
{"assert", CmdHFFido2GetAssertion, IfPm3Iso14443a, "FIDO2 GetAssertion command."},
|
|
{NULL, NULL, 0, NULL}
|
|
};
|
|
|
|
int CmdHFFido(const char *Cmd) {
|
|
clearCommandBuffer();
|
|
return CmdsParse(CommandTable, Cmd);
|
|
}
|
|
|
|
int CmdHelp(const char *Cmd) {
|
|
(void)Cmd; // Cmd is not used so far
|
|
CmdsHelp(CommandTable);
|
|
return PM3_SUCCESS;
|
|
}
|