* add tinycbor
* add client/fido
* add test file with options for fido2
* hf fido commands
* add changelog
This commit is contained in:
Oleg Moiseenko 2018-12-07 17:42:37 +02:00 committed by pwpiwi
commit 0bb514502a
29 changed files with 7799 additions and 104 deletions

491
client/fido/cbortools.c Normal file
View file

@ -0,0 +1,491 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// Tools for work with CBOR format http://cbor.io/spec.html
// via Intel tinycbor (https://github.com/intel/tinycbor) library
//-----------------------------------------------------------------------------
//
#include "cbortools.h"
#include <stdlib.h>
#include "emv/emvjson.h"
#include "util.h"
#include "fidocore.h"
static void indent(int nestingLevel) {
while (nestingLevel--)
printf(" ");
}
static CborError dumpelm(CborValue *it, bool *got_next, int nestingLevel) {
CborError err;
*got_next = false;
CborType type = cbor_value_get_type(it);
indent(nestingLevel);
switch (type) {
case CborMapType:
case CborArrayType: {
printf(type == CborArrayType ? "Array[" : "Map[");
break;
}
case CborIntegerType: {
int64_t val;
cbor_value_get_int64(it, &val); // can't fail
printf("%lld", (long long)val);
break;
}
case CborByteStringType: {
uint8_t *buf;
size_t n;
err = cbor_value_dup_byte_string(it, &buf, &n, it);
*got_next = true;
if (err)
return err; // parse error
printf("%s", sprint_hex(buf, n));
free(buf);
break;
}
case CborTextStringType: {
char *buf;
size_t n;
err = cbor_value_dup_text_string(it, &buf, &n, it);
*got_next = true;
if (err)
return err; // parse error
printf("%s", buf);
free(buf);
break;
}
case CborTagType: {
CborTag tag;
cbor_value_get_tag(it, &tag);
printf("Tag(%lld)", (long long)tag);
break;
}
case CborSimpleType: {
uint8_t type;
cbor_value_get_simple_type(it, &type);
printf("simple(%u)", type);
break;
}
case CborNullType:
printf("null");
break;
case CborUndefinedType:
printf("undefined");
break;
case CborBooleanType: {
bool val;
cbor_value_get_boolean(it, &val); // can't fail
printf("%s", val ? "true" : "false");
break;
}
case CborDoubleType: {
double val;
if (false) {
float f;
case CborFloatType:
cbor_value_get_float(it, &f);
val = f;
} else {
cbor_value_get_double(it, &val);
}
printf("%g", val);
break;
}
case CborHalfFloatType: {
uint16_t val;
cbor_value_get_half_float(it, &val);
printf("__f16(%04x)", val);
break;
}
case CborInvalidType:
printf("CborInvalidType!!!");
break;
}
return CborNoError;
}
static CborError dumprecursive(uint8_t cmdCode, bool isResponse, CborValue *it, bool isMapType, int nestingLevel) {
int elmCount = 0;
while (!cbor_value_at_end(it)) {
CborError err;
CborType type = cbor_value_get_type(it);
//printf("^%x^", type);
bool got_next;
switch (type) {
case CborMapType:
case CborArrayType: {
// recursive type
CborValue recursed;
assert(cbor_value_is_container(it));
if (!(isMapType && (elmCount % 2)))
indent(nestingLevel);
printf(type == CborArrayType ? "Array[\n" : "Map[\n");
err = cbor_value_enter_container(it, &recursed);
if (err)
return err; // parse error
err = dumprecursive(cmdCode, isResponse, &recursed, (type == CborMapType), nestingLevel + 1);
if (err)
return err; // parse error
err = cbor_value_leave_container(it, &recursed);
if (err)
return err; // parse error
indent(nestingLevel);
printf("]");
got_next = true;
break;
}
default: {
err = dumpelm(it, &got_next, (isMapType && (elmCount % 2)) ? 0 : nestingLevel);
if (err)
return err;
if (cmdCode > 0 && nestingLevel == 1 && isMapType && !(elmCount % 2)) {
int64_t val;
cbor_value_get_int64(it, &val);
char *desc = fido2GetCmdMemberDescription(cmdCode, isResponse, val);
if (desc)
printf(" (%s)", desc);
}
break;
}
}
if (!got_next) {
err = cbor_value_advance_fixed(it);
if (err)
return err;
}
if (isMapType && !(elmCount % 2)) {
printf(": ");
} else {
printf("\n");
}
elmCount++;
}
return CborNoError;
}
int TinyCborInit(uint8_t *data, size_t length, CborValue *cb) {
CborParser parser;
CborError err = cbor_parser_init(data, length, 0, &parser, cb);
if (err)
return err;
return 0;
}
int TinyCborPrintFIDOPackage(uint8_t cmdCode, bool isResponse, uint8_t *data, size_t length) {
CborValue cb;
int res;
res = TinyCborInit(data, length, &cb);
if (res)
return res;
CborError err = dumprecursive(cmdCode, isResponse, &cb, false, 0);
if (err) {
fprintf(stderr,
#if __WORDSIZE == 64
"CBOR parsing failure at offset %" PRId64 " : %s\n",
#else
"CBOR parsing failure at offset %" PRId32 " : %s\n",
#endif
cb.ptr - data, cbor_error_string(err));
return 1;
}
return 0;
}
int JsonObjElmCount(json_t *elm) {
int res = 0;
const char *key;
json_t *value;
if (!json_is_object(elm))
return 0;
json_object_foreach(elm, key, value) {
if (strlen(key) > 0 && key[0] != '.')
res++;
}
return res;
}
int JsonToCbor(json_t *elm, CborEncoder *encoder) {
if (!elm || !encoder)
return 1;
int res;
// CBOR map == JSON object
if (json_is_object(elm)) {
CborEncoder map;
const char *key;
json_t *value;
res = cbor_encoder_create_map(encoder, &map, JsonObjElmCount(elm));
cbor_check(res);
json_object_foreach(elm, key, value) {
if (strlen(key) > 0 && key[0] != '.') {
res = cbor_encode_text_stringz(&map, key);
cbor_check(res);
// RECURSION!
JsonToCbor(value, &map);
}
}
res = cbor_encoder_close_container(encoder, &map);
cbor_check(res);
}
// CBOR array == JSON array
if (json_is_array(elm)) {
size_t index;
json_t *value;
CborEncoder array;
res = cbor_encoder_create_array(encoder, &array, json_array_size(elm));
cbor_check(res);
json_array_foreach(elm, index, value) {
// RECURSION!
JsonToCbor(value, &array);
}
res = cbor_encoder_close_container(encoder, &array);
cbor_check(res);
}
if (json_is_boolean(elm)) {
res = cbor_encode_boolean(encoder, json_is_true(elm));
cbor_check(res);
}
if (json_is_integer(elm)) {
res = cbor_encode_int(encoder, json_integer_value(elm));
cbor_check(res);
}
if (json_is_real(elm)) {
res = cbor_encode_float(encoder, json_real_value(elm));
cbor_check(res);
}
if (json_is_string(elm)) {
const char * val = json_string_value(elm);
if (CheckStringIsHEXValue(val)) {
size_t datalen = 0;
uint8_t data[4096] = {0};
res = JsonLoadBufAsHex(elm, "$", data, sizeof(data), &datalen);
if (res)
return 100;
res = cbor_encode_byte_string(encoder, data, datalen);
cbor_check(res);
} else {
res = cbor_encode_text_stringz(encoder, val);
cbor_check(res);
}
}
return 0;
}
int CborMapGetKeyById(CborParser *parser, CborValue *map, uint8_t *data, size_t dataLen, int key) {
CborValue cb;
CborError err = cbor_parser_init(data, dataLen, 0, parser, &cb);
cbor_check(err);
if (cbor_value_get_type(&cb) != CborMapType)
return 1;
err = cbor_value_enter_container(&cb, map);
cbor_check(err);
int64_t indx;
while (!cbor_value_at_end(map)) {
// check number
if (cbor_value_get_type(map) != CborIntegerType)
return 1;
cbor_value_get_int64(map, &indx);
err = cbor_value_advance(map);
cbor_check(err);
if (indx == key)
return 0;
// pass value
err = cbor_value_advance(map);
cbor_check(err);
}
err = cbor_value_leave_container(&cb, map);
cbor_check(err);
return 2;
}
CborError CborGetArrayBinStringValue(CborValue *elm, uint8_t *data, size_t maxdatalen, size_t *datalen) {
return CborGetArrayBinStringValueEx(elm, data, maxdatalen, datalen, NULL, 0);
}
CborError CborGetArrayBinStringValueEx(CborValue *elm, uint8_t *data, size_t maxdatalen, size_t *datalen, uint8_t *delimeter, size_t delimeterlen) {
CborValue array;
if (datalen)
*datalen = 0;
size_t slen = maxdatalen;
size_t totallen = 0;
CborError res = cbor_value_enter_container(elm, &array);
cbor_check(res);
while (!cbor_value_at_end(&array)) {
res = cbor_value_copy_byte_string(&array, &data[totallen], &slen, &array);
cbor_check(res);
totallen += slen;
if (delimeter) {
memcpy(&data[totallen], delimeter, delimeterlen);
totallen += delimeterlen;
}
slen = maxdatalen - totallen;
}
res = cbor_value_leave_container(elm, &array);
cbor_check(res);
if (datalen)
*datalen = totallen;
return CborNoError;
};
CborError CborGetBinStringValue(CborValue *elm, uint8_t *data, size_t maxdatalen, size_t *datalen) {
if (datalen)
*datalen = 0;
size_t slen = maxdatalen;
CborError res = cbor_value_copy_byte_string(elm, data, &slen, elm);
cbor_check(res);
if (datalen)
*datalen = slen;
return CborNoError;
};
CborError CborGetArrayStringValue(CborValue *elm, char *data, size_t maxdatalen, size_t *datalen, char *delimeter) {
CborValue array;
if (datalen)
*datalen = 0;
size_t slen = maxdatalen;
size_t totallen = 0;
CborError res = cbor_value_enter_container(elm, &array);
cbor_check(res);
while (!cbor_value_at_end(&array)) {
res = cbor_value_copy_text_string(&array, &data[totallen], &slen, &array);
cbor_check(res);
totallen += slen;
if (delimeter) {
strcat(data, delimeter);
totallen += strlen(delimeter);
}
slen = maxdatalen - totallen;
data[totallen] = 0x00;
}
res = cbor_value_leave_container(elm, &array);
cbor_check(res);
if (datalen)
*datalen = totallen;
return CborNoError;
};
CborError CborGetStringValue(CborValue *elm, char *data, size_t maxdatalen, size_t *datalen) {
if (datalen)
*datalen = 0;
size_t slen = maxdatalen;
CborError res = cbor_value_copy_text_string(elm, data, &slen, elm);
cbor_check(res);
if (datalen)
*datalen = slen;
return CborNoError;
};
CborError CborGetStringValueBuf(CborValue *elm) {
static char stringBuf[2048];
memset(stringBuf, 0x00, sizeof(stringBuf));
return CborGetStringValue(elm, stringBuf, sizeof(stringBuf), NULL);
};
int CBOREncodeElm(json_t *root, char *rootElmId, CborEncoder *encoder) {
json_t *elm = NULL;
if (rootElmId && strlen(rootElmId) && rootElmId[0] == '$')
elm = json_path_get(root, rootElmId);
else
elm = json_object_get(root, rootElmId);
if (!elm)
return 1;
int res = JsonToCbor(elm, encoder);
return res;
}
CborError CBOREncodeClientDataHash(json_t *root, CborEncoder *encoder) {
uint8_t buf[100] = {0};
size_t jlen;
JsonLoadBufAsHex(root, "$.ClientDataHash", buf, sizeof(buf), &jlen);
// fill with 0x00 if not found
if (!jlen)
jlen = 32;
int res = cbor_encode_byte_string(encoder, buf, jlen);
cbor_check(res);
return 0;
}

38
client/fido/cbortools.h Normal file
View file

@ -0,0 +1,38 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// Tools for work with CBOR format http://cbor.io/spec.html
// via Intel tinycbor (https://github.com/intel/tinycbor) library
//-----------------------------------------------------------------------------
//
#ifndef __CBORTOOLS_H__
#define __CBORTOOLS_H__
#include <stddef.h>
#include <stdint.h>
#include <jansson.h>
#include <cbor.h>
#define cbor_check_if(r) if ((r) != CborNoError) {return r;} else
#define cbor_check(r) if ((r) != CborNoError) return r;
extern int TinyCborPrintFIDOPackage(uint8_t cmdCode, bool isResponse, uint8_t *data, size_t length);
extern int JsonToCbor(json_t *elm, CborEncoder *encoder);
extern int CborMapGetKeyById(CborParser *parser, CborValue *map, uint8_t *data, size_t dataLen, int key);
extern CborError CborGetArrayBinStringValue(CborValue *elm, uint8_t *data, size_t maxdatalen, size_t *datalen);
extern CborError CborGetArrayBinStringValueEx(CborValue *elm, uint8_t *data, size_t maxdatalen, size_t *datalen, uint8_t *delimeter, size_t delimeterlen);
extern CborError CborGetBinStringValue(CborValue *elm, uint8_t *data, size_t maxdatalen, size_t *datalen);
extern CborError CborGetArrayStringValue(CborValue *elm, char *data, size_t maxdatalen, size_t *datalen, char *delimeter);
extern CborError CborGetStringValue(CborValue *elm, char *data, size_t maxdatalen, size_t *datalen);
extern CborError CborGetStringValueBuf(CborValue *elm);
extern int CBOREncodeElm(json_t *root, char *rootElmId, CborEncoder *encoder);
extern CborError CBOREncodeClientDataHash(json_t *root, CborEncoder *encoder);
#endif /* __CBORTOOLS_H__ */

240
client/fido/cose.c Normal file
View file

@ -0,0 +1,240 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// Tools for work with COSE (CBOR Object Signing and Encryption) rfc8152
// https://tools.ietf.org/html/rfc8152
//-----------------------------------------------------------------------------
//
#include "cose.h"
#include <cbor.h>
#include "cbortools.h"
#include "util.h"
#include "ui.h"
static const char COSEEmptyStr[] = "";
typedef struct {
int Value;
char *Name;
char *Description;
} COSEValueNameDesc_t;
typedef struct {
int Value;
char *Type;
char *Name;
char *Description;
} COSEValueTypeNameDesc_t;
// kty - Key Type Values
COSEValueNameDesc_t COSEKeyTypeValueDesc[] = {
{0, "Reserved", "Reserved"},
{1, "OKP", "Octet Key Pair"},
{2, "EC2", "Elliptic Curve Key w/ x- and y-coordinate pair"},
{4, "Symmetric", "Symmetric Key"},
};
COSEValueNameDesc_t *GetCOSEktyElm(int id) {
for (int i = 0; i < ARRAYLEN(COSEKeyTypeValueDesc); i++)
if (COSEKeyTypeValueDesc[i].Value == id)
return &COSEKeyTypeValueDesc[i];
return NULL;
}
const char *GetCOSEktyDescription(int id) {
COSEValueNameDesc_t *elm = GetCOSEktyElm(id);
if (elm)
return elm->Description;
return COSEEmptyStr;
}
// keys
COSEValueTypeNameDesc_t COSECurvesDesc[] = {
{1, "EC2", "P-256", "NIST P-256 also known as secp256r1"},
{2, "EC2", "P-384", "NIST P-384 also known as secp384r1"},
{3, "EC2", "P-521", "NIST P-521 also known as secp521r1"},
{4, "OKP", "X25519", "X25519 for use w/ ECDH only"},
{5, "OKP", "X448", "X448 for use w/ ECDH only"},
{6, "OKP", "Ed25519", "Ed25519 for use w/ EdDSA only"},
{7, "OKP", "Ed448", "Ed448 for use w/ EdDSA only"},
};
COSEValueTypeNameDesc_t *GetCOSECurveElm(int id) {
for (int i = 0; i < ARRAYLEN(COSECurvesDesc); i++)
if (COSECurvesDesc[i].Value == id)
return &COSECurvesDesc[i];
return NULL;
}
const char *GetCOSECurveDescription(int id) {
COSEValueTypeNameDesc_t *elm = GetCOSECurveElm(id);
if (elm)
return elm->Description;
return COSEEmptyStr;
}
// RFC8152 https://www.iana.org/assignments/cose/cose.xhtml#algorithms
COSEValueNameDesc_t COSEAlg[] = {
{-65536, "Unassigned", "Unassigned"},
{-65535, "RS1", "RSASSA-PKCS1-v1_5 w/ SHA-1"},
{-259, "RS512", "RSASSA-PKCS1-v1_5 w/ SHA-512"},
{-258, "RS384", "RSASSA-PKCS1-v1_5 w/ SHA-384"},
{-257, "RS256", "RSASSA-PKCS1-v1_5 w/ SHA-256"},
{-42, "RSAES-OAEP w/ SHA-512", "RSAES-OAEP w/ SHA-512"},
{-41, "RSAES-OAEP w/ SHA-256", "RSAES-OAEP w/ SHA-256"},
{-40, "RSAES-OAEP w/ RFC 8017 def param", "RSAES-OAEP w/ SHA-1"},
{-39, "PS512", "RSASSA-PSS w/ SHA-512"},
{-38, "PS384", "RSASSA-PSS w/ SHA-384"},
{-37, "PS256", "RSASSA-PSS w/ SHA-256"},
{-36, "ES512", "ECDSA w/ SHA-512"},
{-35, "ES384", "ECDSA w/ SHA-384"},
{-34, "ECDH-SS + A256KW", "ECDH SS w/ Concat KDF and AES Key Wrap w/ 256-bit key"},
{-33, "ECDH-SS + A192KW", "ECDH SS w/ Concat KDF and AES Key Wrap w/ 192-bit key"},
{-32, "ECDH-SS + A128KW", "ECDH SS w/ Concat KDF and AES Key Wrap w/ 128-bit key"},
{-31, "ECDH-ES + A256KW", "ECDH ES w/ Concat KDF and AES Key Wrap w/ 256-bit key"},
{-30, "ECDH-ES + A192KW", "ECDH ES w/ Concat KDF and AES Key Wrap w/ 192-bit key"},
{-29, "ECDH-ES + A128KW", "ECDH ES w/ Concat KDF and AES Key Wrap w/ 128-bit key"},
{-28, "ECDH-SS + HKDF-512", "ECDH SS w/ HKDF - generate key directly"},
{-27, "ECDH-SS + HKDF-256", "ECDH SS w/ HKDF - generate key directly"},
{-26, "ECDH-ES + HKDF-512", "ECDH ES w/ HKDF - generate key directly"},
{-25, "ECDH-ES + HKDF-256", "ECDH ES w/ HKDF - generate key directly"},
{-13, "direct+HKDF-AES-256", "Shared secret w/ AES-MAC 256-bit key"},
{-12, "direct+HKDF-AES-128", "Shared secret w/ AES-MAC 128-bit key"},
{-11, "direct+HKDF-SHA-512", "Shared secret w/ HKDF and SHA-512"},
{-10, "direct+HKDF-SHA-256", "Shared secret w/ HKDF and SHA-256"},
{-8, "EdDSA", "EdDSA"},
{-7, "ES256", "ECDSA w/ SHA-256"},
{-6, "direct", "Direct use of CEK"},
{-5, "A256KW", "AES Key Wrap w/ 256-bit key"},
{-4, "A192KW", "AES Key Wrap w/ 192-bit key"},
{-3, "A128KW", "AES Key Wrap w/ 128-bit key"},
{0, "Reserved", "Reserved"},
{1, "A128GCM", "AES-GCM mode w/ 128-bit key, 128-bit tag"},
{2, "A192GCM", "AES-GCM mode w/ 192-bit key, 128-bit tag"},
{3, "A256GCM", "AES-GCM mode w/ 256-bit key, 128-bit tag"},
{4, "HMAC 256/64", "HMAC w/ SHA-256 truncated to 64 bits"},
{5, "HMAC 256/256", "HMAC w/ SHA-256"},
{6, "HMAC 384/384", "HMAC w/ SHA-384"},
{7, "HMAC 512/512", "HMAC w/ SHA-512"},
{10, "AES-CCM-16-64-128", "AES-CCM mode 128-bit key, 64-bit tag, 13-byte nonce"},
{11, "AES-CCM-16-64-256", "AES-CCM mode 256-bit key, 64-bit tag, 13-byte nonce"},
{12, "AES-CCM-64-64-128", "AES-CCM mode 128-bit key, 64-bit tag, 7-byte nonce"},
{13, "AES-CCM-64-64-256", "AES-CCM mode 256-bit key, 64-bit tag, 7-byte nonce"},
{14, "AES-MAC 128/64", "AES-MAC 128-bit key, 64-bit tag"},
{15, "AES-MAC 256/64", "AES-MAC 256-bit key, 64-bit tag"},
{24, "ChaCha20/Poly1305", "ChaCha20/Poly1305 w/ 256-bit key, 128-bit tag"},
{25, "AES-MAC 128/128", "AES-MAC 128-bit key, 128-bit tag"},
{26, "AES-MAC 256/128", "AES-MAC 256-bit key, 128-bit tag"},
{30, "AES-CCM-16-128-128", "AES-CCM mode 128-bit key, 128-bit tag, 13-byte nonce"},
{31, "AES-CCM-16-128-256", "AES-CCM mode 256-bit key, 128-bit tag, 13-byte nonce"},
{32, "AES-CCM-64-128-128", "AES-CCM mode 128-bit key, 128-bit tag, 7-byte nonce"},
{33, "AES-CCM-64-128-256", "AES-CCM mode 256-bit key, 128-bit tag, 7-byte nonce"}
};
COSEValueNameDesc_t *GetCOSEAlgElm(int id) {
for (int i = 0; i < ARRAYLEN(COSEAlg); i++)
if (COSEAlg[i].Value == id)
return &COSEAlg[i];
return NULL;
}
const char *GetCOSEAlgName(int id) {
COSEValueNameDesc_t *elm = GetCOSEAlgElm(id);
if (elm)
return elm->Name;
return COSEEmptyStr;
}
const char *GetCOSEAlgDescription(int id) {
COSEValueNameDesc_t *elm = GetCOSEAlgElm(id);
if (elm)
return elm->Description;
return COSEEmptyStr;
}
int COSEGetECDSAKey(uint8_t *data, size_t datalen, bool verbose, uint8_t *public_key) {
CborParser parser;
CborValue map;
int64_t i64;
size_t len;
if(verbose)
PrintAndLog("----------- CBOR decode ----------------");
// kty
int res = CborMapGetKeyById(&parser, &map, data, datalen, 1);
if(!res) {
cbor_value_get_int64(&map, &i64);
if(verbose)
PrintAndLog("kty [%lld] %s", (long long)i64, GetCOSEktyDescription(i64));
if (i64 != 2)
PrintAndLog("ERROR: kty must be 2.");
}
// algorithm
res = CborMapGetKeyById(&parser, &map, data, datalen, 3);
if(!res) {
cbor_value_get_int64(&map, &i64);
if(verbose)
PrintAndLog("algorithm [%lld] %s", (long long)i64, GetCOSEAlgDescription(i64));
if (i64 != -7)
PrintAndLog("ERROR: algorithm must be -7.");
}
// curve
res = CborMapGetKeyById(&parser, &map, data, datalen, -1);
if(!res) {
cbor_value_get_int64(&map, &i64);
if(verbose)
PrintAndLog("curve [%lld] %s", (long long)i64, GetCOSECurveDescription(i64));
if (i64 != 1)
PrintAndLog("ERROR: curve must be 1.");
}
// plain key
public_key[0] = 0x04;
// x - coordinate
res = CborMapGetKeyById(&parser, &map, data, datalen, -2);
if(!res) {
res = CborGetBinStringValue(&map, &public_key[1], 32, &len);
cbor_check(res);
if(verbose)
PrintAndLog("x - coordinate [%d]: %s", len, sprint_hex(&public_key[1], 32));
if (len != 32)
PrintAndLog("ERROR: x - coordinate length must be 32.");
}
// y - coordinate
res = CborMapGetKeyById(&parser, &map, data, datalen, -3);
if(!res) {
res = CborGetBinStringValue(&map, &public_key[33], 32, &len);
cbor_check(res);
if(verbose)
PrintAndLog("y - coordinate [%d]: %s", len, sprint_hex(&public_key[33], 32));
if (len != 32)
PrintAndLog("ERROR: y - coordinate length must be 32.");
}
// d - private key
uint8_t private_key[128] = {0};
res = CborMapGetKeyById(&parser, &map, data, datalen, -4);
if(!res) {
res = CborGetBinStringValue(&map, private_key, sizeof(private_key), &len);
cbor_check(res);
if(verbose)
PrintAndLog("d - private key [%d]: %s", len, sprint_hex(private_key, len));
}
if(verbose)
PrintAndLog("----------- CBOR decode ----------------");
return 0;
}

27
client/fido/cose.h Normal file
View 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.
//-----------------------------------------------------------------------------
// Tools for work with COSE (CBOR Object Signing and Encryption) rfc8152
// https://tools.ietf.org/html/rfc8152
//-----------------------------------------------------------------------------
//
#ifndef __COSE_H__
#define __COSE_H__
#include <stddef.h>
#include <stdint.h>
#include <cbor.h>
extern const char *GetCOSEAlgName(int id);
extern const char *GetCOSEAlgDescription(int id);
extern const char *GetCOSEktyDescription(int id);
extern const char *GetCOSECurveDescription(int id);
extern int COSEGetECDSAKey(uint8_t *data, size_t datalen, bool verbose, uint8_t *public_key);
#endif /* __COSE_H__ */

33
client/fido/fido2.json Normal file
View file

@ -0,0 +1,33 @@
{
"ClientDataHash": "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f",
"RelyingPartyEntity": {
"id": "acme.com",
"name": "Acme"
},
"UserEntity": {
"id": "00000000000000000000000000000001",
"icon": "https://pics.acme.com/00/p/aBjjjpqPb.png",
"name": "johnpsmith@acme.com",
"displayName": "John P. Smith"
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7,
".name": "ES256"
},
{
"type": "public-key",
"alg": -257,
".name": "RS256"
}
],
"MakeCredentialOptions": {
"uv": false,
"rk": true
},
"GetAssertionOptions": {
"up": true,
"uv": false
}
}

805
client/fido/fidocore.c Normal file
View file

@ -0,0 +1,805 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// FIDO2 authenticators core data and commands
// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html
//-----------------------------------------------------------------------------
//
#include "fidocore.h"
#include "emv/emvcore.h"
#include "emv/emvjson.h"
#include <cbor.h>
#include "cbortools.h"
#include <mbedtls/x509_crt.h>
#include <mbedtls/x509.h>
#include <mbedtls/pk.h>
#include "crypto/asn1utils.h"
#include "crypto/libpcrypto.h"
#include "fido/additional_ca.h"
#include "fido/cose.h"
typedef struct {
uint8_t ErrorCode;
char *ShortDescription;
char *Description;
} fido2Error_t;
fido2Error_t fido2Errors[] = {
{0xFF, "n/a", "n/a"},
{0x00, "CTAP1_ERR_SUCCESS", "Indicates successful response."},
{0x01, "CTAP1_ERR_INVALID_COMMAND", "The command is not a valid CTAP command."},
{0x02, "CTAP1_ERR_INVALID_PARAMETER", "The command included an invalid parameter."},
{0x03, "CTAP1_ERR_INVALID_LENGTH", "Invalid message or item length."},
{0x04, "CTAP1_ERR_INVALID_SEQ", "Invalid message sequencing."},
{0x05, "CTAP1_ERR_TIMEOUT", "Message timed out."},
{0x06, "CTAP1_ERR_CHANNEL_BUSY", "Channel busy."},
{0x0A, "CTAP1_ERR_LOCK_REQUIRED", "Command requires channel lock."},
{0x0B, "CTAP1_ERR_INVALID_CHANNEL", "Command not allowed on this cid."},
{0x10, "CTAP2_ERR_CBOR_PARSING", "Error while parsing CBOR."},
{0x11, "CTAP2_ERR_CBOR_UNEXPECTED_TYPE", "Invalid/unexpected CBOR error."},
{0x12, "CTAP2_ERR_INVALID_CBOR", "Error when parsing CBOR."},
{0x13, "CTAP2_ERR_INVALID_CBOR_TYPE", "Invalid or unexpected CBOR type."},
{0x14, "CTAP2_ERR_MISSING_PARAMETER", "Missing non-optional parameter."},
{0x15, "CTAP2_ERR_LIMIT_EXCEEDED", "Limit for number of items exceeded."},
{0x16, "CTAP2_ERR_UNSUPPORTED_EXTENSION", "Unsupported extension."},
{0x17, "CTAP2_ERR_TOO_MANY_ELEMENTS", "Limit for number of items exceeded."},
{0x18, "CTAP2_ERR_EXTENSION_NOT_SUPPORTED", "Unsupported extension."},
{0x19, "CTAP2_ERR_CREDENTIAL_EXCLUDED", "Valid credential found in the exludeList."},
{0x20, "CTAP2_ERR_CREDENTIAL_NOT_VALID", "Credential not valid for authenticator."},
{0x21, "CTAP2_ERR_PROCESSING", "Processing (Lengthy operation is in progress)."},
{0x22, "CTAP2_ERR_INVALID_CREDENTIAL", "Credential not valid for the authenticator."},
{0x23, "CTAP2_ERR_USER_ACTION_PENDING", "Authentication is waiting for user interaction."},
{0x24, "CTAP2_ERR_OPERATION_PENDING", "Processing, lengthy operation is in progress."},
{0x25, "CTAP2_ERR_NO_OPERATIONS", "No request is pending."},
{0x26, "CTAP2_ERR_UNSUPPORTED_ALGORITHM", "Authenticator does not support requested algorithm."},
{0x27, "CTAP2_ERR_OPERATION_DENIED", "Not authorized for requested operation."},
{0x28, "CTAP2_ERR_KEY_STORE_FULL", "Internal key storage is full."},
{0x29, "CTAP2_ERR_NOT_BUSY", "Authenticator cannot cancel as it is not busy."},
{0x2A, "CTAP2_ERR_NO_OPERATION_PENDING", "No outstanding operations."},
{0x2B, "CTAP2_ERR_UNSUPPORTED_OPTION", "Unsupported option."},
{0x2C, "CTAP2_ERR_INVALID_OPTION", "Unsupported option."},
{0x2D, "CTAP2_ERR_KEEPALIVE_CANCEL", "Pending keep alive was cancelled."},
{0x2E, "CTAP2_ERR_NO_CREDENTIALS", "No valid credentials provided."},
{0x2F, "CTAP2_ERR_USER_ACTION_TIMEOUT", "Timeout waiting for user interaction."},
{0x30, "CTAP2_ERR_NOT_ALLOWED", "Continuation command, such as, authenticatorGetNextAssertion not allowed."},
{0x31, "CTAP2_ERR_PIN_INVALID", "PIN Blocked."},
{0x32, "CTAP2_ERR_PIN_BLOCKED", "PIN Blocked."},
{0x33, "CTAP2_ERR_PIN_AUTH_INVALID", "PIN authentication,pinAuth, verification failed."},
{0x34, "CTAP2_ERR_PIN_AUTH_BLOCKED", "PIN authentication,pinAuth, blocked. Requires power recycle to reset."},
{0x35, "CTAP2_ERR_PIN_NOT_SET", "No PIN has been set."},
{0x36, "CTAP2_ERR_PIN_REQUIRED", "PIN is required for the selected operation."},
{0x37, "CTAP2_ERR_PIN_POLICY_VIOLATION", "PIN policy violation. Currently only enforces minimum length."},
{0x38, "CTAP2_ERR_PIN_TOKEN_EXPIRED", "pinToken expired on authenticator."},
{0x39, "CTAP2_ERR_REQUEST_TOO_LARGE", "Authenticator cannot handle this request due to memory constraints."},
{0x7F, "CTAP1_ERR_OTHER", "Other unspecified error."},
{0xDF, "CTAP2_ERR_SPEC_LAST", "CTAP 2 spec last error."},
};
typedef struct {
fido2Commands Command;
fido2PacketType PckType;
int MemberNumber;
char *Description;
} fido2Desc_t;
fido2Desc_t fido2CmdGetInfoRespDesc[] = {
{fido2CmdMakeCredential, ptResponse, 0x01, "fmt"},
{fido2CmdMakeCredential, ptResponse, 0x02, "authData"},
{fido2CmdMakeCredential, ptResponse, 0x03, "attStmt"},
{fido2CmdMakeCredential, ptQuery, 0x01, "clientDataHash"},
{fido2CmdMakeCredential, ptQuery, 0x02, "rp"},
{fido2CmdMakeCredential, ptQuery, 0x03, "user"},
{fido2CmdMakeCredential, ptQuery, 0x04, "pubKeyCredParams"},
{fido2CmdMakeCredential, ptQuery, 0x05, "excludeList"},
{fido2CmdMakeCredential, ptQuery, 0x06, "extensions"},
{fido2CmdMakeCredential, ptQuery, 0x07, "options"},
{fido2CmdMakeCredential, ptQuery, 0x08, "pinAuth"},
{fido2CmdMakeCredential, ptQuery, 0x09, "pinProtocol"},
{fido2CmdGetAssertion, ptResponse, 0x01, "credential"},
{fido2CmdGetAssertion, ptResponse, 0x02, "authData"},
{fido2CmdGetAssertion, ptResponse, 0x03, "signature"},
{fido2CmdGetAssertion, ptResponse, 0x04, "publicKeyCredentialUserEntity"},
{fido2CmdGetAssertion, ptResponse, 0x05, "numberOfCredentials"},
{fido2CmdGetAssertion, ptQuery, 0x01, "rpId"},
{fido2CmdGetAssertion, ptQuery, 0x02, "clientDataHash"},
{fido2CmdGetAssertion, ptQuery, 0x03, "allowList"},
{fido2CmdGetAssertion, ptQuery, 0x04, "extensions"},
{fido2CmdGetAssertion, ptQuery, 0x05, "options"},
{fido2CmdGetAssertion, ptQuery, 0x06, "pinAuth"},
{fido2CmdGetAssertion, ptQuery, 0x07, "pinProtocol"},
{fido2CmdGetNextAssertion, ptResponse, 0x01, "credential"},
{fido2CmdGetNextAssertion, ptResponse, 0x02, "authData"},
{fido2CmdGetNextAssertion, ptResponse, 0x03, "signature"},
{fido2CmdGetNextAssertion, ptResponse, 0x04, "publicKeyCredentialUserEntity"},
{fido2CmdGetInfo, ptResponse, 0x01, "versions"},
{fido2CmdGetInfo, ptResponse, 0x02, "extensions"},
{fido2CmdGetInfo, ptResponse, 0x03, "aaguid"},
{fido2CmdGetInfo, ptResponse, 0x04, "options"},
{fido2CmdGetInfo, ptResponse, 0x05, "maxMsgSize"},
{fido2CmdGetInfo, ptResponse, 0x06, "pinProtocols"},
{fido2CmdClientPIN, ptResponse, 0x01, "keyAgreement"},
{fido2CmdClientPIN, ptResponse, 0x02, "pinToken"},
{fido2CmdClientPIN, ptResponse, 0x03, "retries"},
{fido2CmdClientPIN, ptQuery, 0x01, "pinProtocol"},
{fido2CmdClientPIN, ptQuery, 0x02, "subCommand"},
{fido2CmdClientPIN, ptQuery, 0x03, "keyAgreement"},
{fido2CmdClientPIN, ptQuery, 0x04, "pinAuth"},
{fido2CmdClientPIN, ptQuery, 0x05, "newPinEnc"},
{fido2CmdClientPIN, ptQuery, 0x06, "pinHashEnc"},
{fido2CmdClientPIN, ptQuery, 0x07, "getKeyAgreement"},
{fido2CmdClientPIN, ptQuery, 0x08, "getRetries"},
{fido2COSEKey, ptResponse, 0x01, "kty"},
{fido2COSEKey, ptResponse, 0x03, "alg"},
{fido2COSEKey, ptResponse, -1, "crv"},
{fido2COSEKey, ptResponse, -2, "x - coordinate"},
{fido2COSEKey, ptResponse, -3, "y - coordinate"},
{fido2COSEKey, ptResponse, -4, "d - private key"},
};
char *fido2GetCmdErrorDescription(uint8_t errorCode) {
for (int i = 0; i < sizeof(fido2Errors) / sizeof(fido2Error_t); i++)
if (fido2Errors[i].ErrorCode == errorCode)
return fido2Errors[i].Description;
return fido2Errors[0].Description;
}
char *fido2GetCmdMemberDescription(uint8_t cmdCode, bool isResponse, int memberNum) {
for (int i = 0; i < sizeof(fido2CmdGetInfoRespDesc) / sizeof(fido2Desc_t); i++)
if (fido2CmdGetInfoRespDesc[i].Command == cmdCode &&
fido2CmdGetInfoRespDesc[i].PckType == (isResponse ? ptResponse : ptQuery) &&
fido2CmdGetInfoRespDesc[i].MemberNumber == memberNum )
return fido2CmdGetInfoRespDesc[i].Description;
return NULL;
}
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[] = {fido2CmdGetInfo};
return FIDOExchange((sAPDU){0x80, 0x10, 0x00, 0x00, sizeof(data), data}, Result, MaxResultLen, ResultLen, sw);
}
int FIDO2MakeCredential(uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
uint8_t data[paramslen + 1];
data[0] = fido2CmdMakeCredential;
memcpy(&data[1], params, paramslen);
return FIDOExchange((sAPDU){0x80, 0x10, 0x00, 0x00, sizeof(data), data}, Result, MaxResultLen, ResultLen, sw);
}
int FIDO2GetAssertion(uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) {
uint8_t data[paramslen + 1];
data[0] = fido2CmdGetAssertion;
memcpy(&data[1], params, paramslen);
return FIDOExchange((sAPDU){0x80, 0x10, 0x00, 0x00, sizeof(data), data}, Result, MaxResultLen, ResultLen, sw);
}
int FIDOCheckDERAndGetKey(uint8_t *der, size_t derLen, bool verbose, uint8_t *publicKey, size_t publicKeyMaxLen) {
int res;
// load CA's
mbedtls_x509_crt cacert;
mbedtls_x509_crt_init(&cacert);
res = mbedtls_x509_crt_parse(&cacert, (const unsigned char *) additional_ca_pem, additional_ca_pem_len);
if (res < 0) {
PrintAndLog("ERROR: CA parse certificate returned -0x%x - %s", -res, ecdsa_get_error(res));
}
if (verbose)
PrintAndLog("CA load OK. %d skipped", res);
// load DER certificate from authenticator's data
mbedtls_x509_crt cert;
mbedtls_x509_crt_init(&cert);
res = mbedtls_x509_crt_parse_der(&cert, der, derLen);
if (res) {
PrintAndLog("ERROR: DER parse returned 0x%x - %s", (res<0)?-res:res, ecdsa_get_error(res));
}
// get certificate info
char linfo[300] = {0};
if (verbose) {
mbedtls_x509_crt_info(linfo, sizeof(linfo), " ", &cert);
PrintAndLog("DER certificate info:\n%s", linfo);
}
// verify certificate
uint32_t verifyflags = 0;
res = mbedtls_x509_crt_verify(&cert, &cacert, NULL, NULL, &verifyflags, NULL, NULL);
if (res) {
PrintAndLog("ERROR: DER verify returned 0x%x - %s", (res<0)?-res:res, ecdsa_get_error(res));
} else {
PrintAndLog("Certificate OK.");
}
if (verbose) {
memset(linfo, 0x00, sizeof(linfo));
mbedtls_x509_crt_verify_info(linfo, sizeof(linfo), " ", verifyflags);
PrintAndLog("Verification info:\n%s", linfo);
}
// get public key
res = ecdsa_public_key_from_pk(&cert.pk, publicKey, publicKeyMaxLen);
if (res) {
PrintAndLog("ERROR: getting public key from certificate 0x%x - %s", (res<0)?-res:res, ecdsa_get_error(res));
} else {
if (verbose)
PrintAndLog("Got a public key from certificate:\n%s", sprint_hex_inrow(publicKey, 65));
}
if (verbose)
PrintAndLog("------------------DER-------------------");
mbedtls_x509_crt_free(&cert);
mbedtls_x509_crt_free(&cacert);
return 0;
}
#define fido_check_if(r) if ((r) != CborNoError) {return r;} else
#define fido_check(r) if ((r) != CborNoError) return r;
int FIDO2CreateMakeCredentionalReq(json_t *root, uint8_t *data, size_t maxdatalen, size_t *datalen) {
if (datalen)
*datalen = 0;
if (!root || !data || !maxdatalen)
return 1;
int res;
CborEncoder encoder;
CborEncoder map;
cbor_encoder_init(&encoder, data, maxdatalen, 0);
// create main map
res = cbor_encoder_create_map(&encoder, &map, 5);
fido_check_if(res) {
// clientDataHash
res = cbor_encode_uint(&map, 1);
fido_check_if(res) {
res = CBOREncodeClientDataHash(root, &map);
fido_check(res);
}
// rp
res = cbor_encode_uint(&map, 2);
fido_check_if(res) {
res = CBOREncodeElm(root, "RelyingPartyEntity", &map);
fido_check(res);
}
// user
res = cbor_encode_uint(&map, 3);
fido_check_if(res) {
res = CBOREncodeElm(root, "UserEntity", &map);
fido_check(res);
}
// pubKeyCredParams
res = cbor_encode_uint(&map, 4);
fido_check_if(res) {
res = CBOREncodeElm(root, "pubKeyCredParams", &map);
fido_check(res);
}
// options
res = cbor_encode_uint(&map, 7);
fido_check_if(res) {
res = CBOREncodeElm(root, "MakeCredentialOptions", &map);
fido_check(res);
}
}
res = cbor_encoder_close_container(&encoder, &map);
fido_check(res);
size_t len = cbor_encoder_get_buffer_size(&encoder, data);
if (datalen)
*datalen = len;
return 0;
}
bool CheckrpIdHash(json_t *json, uint8_t *hash) {
char hashval[300] = {0};
uint8_t hash2[32] = {0};
JsonLoadStr(json, "$.RelyingPartyEntity.id", hashval);
sha256hash((uint8_t *)hashval, strlen(hashval), hash2);
return !memcmp(hash, hash2, 32);
}
// check ANSI X9.62 format ECDSA signature (on P-256)
int FIDO2CheckSignature(json_t *root, uint8_t *publickey, uint8_t *sign, size_t signLen, uint8_t *authData, size_t authDataLen, bool verbose) {
int res;
uint8_t rval[300] = {0};
uint8_t sval[300] = {0};
res = ecdsa_asn1_get_signature(sign, signLen, rval, sval);
if (!res) {
if (verbose) {
PrintAndLog(" r: %s", sprint_hex(rval, 32));
PrintAndLog(" s: %s", sprint_hex(sval, 32));
}
uint8_t clientDataHash[32] = {0};
size_t clientDataHashLen = 0;
res = JsonLoadBufAsHex(root, "$.ClientDataHash", clientDataHash, sizeof(clientDataHash), &clientDataHashLen);
if (res || clientDataHashLen != 32) {
PrintAndLog("ERROR: Can't get clientDataHash from json!");
return 2;
}
uint8_t xbuf[4096] = {0};
size_t xbuflen = 0;
res = FillBuffer(xbuf, sizeof(xbuf), &xbuflen,
authData, authDataLen, // rpIdHash[32] + flags[1] + signCount[4]
clientDataHash, 32, // Hash of the serialized client data. "$.ClientDataHash" from json
NULL, 0);
//PrintAndLog("--xbuf(%d)[%d]: %s", res, xbuflen, sprint_hex(xbuf, xbuflen));
res = ecdsa_signature_verify(publickey, xbuf, xbuflen, sign, signLen);
if (res) {
if (res == -0x4e00) {
PrintAndLog("Signature is NOT VALID.");
} else {
PrintAndLog("Other signature check error: %x %s", (res<0)?-res:res, ecdsa_get_error(res));
}
return res;
} else {
PrintAndLog("Signature is OK.");
}
} else {
PrintAndLog("Invalid signature. res=%d.", res);
return res;
}
return 0;
}
int FIDO2MakeCredentionalParseRes(json_t *root, uint8_t *data, size_t dataLen, bool verbose, bool verbose2, bool showCBOR, bool showDERTLV) {
CborParser parser;
CborValue map, mapsmt;
int res;
char *buf;
uint8_t *ubuf;
size_t n;
// fmt
res = CborMapGetKeyById(&parser, &map, data, dataLen, 1);
if (res)
return res;
res = cbor_value_dup_text_string(&map, &buf, &n, &map);
cbor_check(res);
PrintAndLog("format: %s", buf);
free(buf);
// authData
uint8_t authData[400] = {0};
size_t authDataLen = 0;
res = CborMapGetKeyById(&parser, &map, data, dataLen, 2);
if (res)
return res;
res = cbor_value_dup_byte_string(&map, &ubuf, &n, &map);
cbor_check(res);
authDataLen = n;
memcpy(authData, ubuf, authDataLen);
if (verbose2) {
PrintAndLog("authData[%d]: %s", n, sprint_hex_inrow(authData, authDataLen));
} else {
PrintAndLog("authData[%d]: %s...", n, sprint_hex(authData, MIN(authDataLen, 16)));
}
PrintAndLog("RP ID Hash: %s", sprint_hex(ubuf, 32));
// check RP ID Hash
if (CheckrpIdHash(root, ubuf)) {
PrintAndLog("rpIdHash OK.");
} else {
PrintAndLog("rpIdHash ERROR!");
}
PrintAndLog("Flags 0x%02x:", ubuf[32]);
if (!ubuf[32])
PrintAndLog("none");
if (ubuf[32] & 0x01)
PrintAndLog("up - user presence result");
if (ubuf[32] & 0x04)
PrintAndLog("uv - user verification (fingerprint scan or a PIN or ...) result");
if (ubuf[32] & 0x40)
PrintAndLog("at - attested credential data included");
if (ubuf[32] & 0x80)
PrintAndLog("ed - extension data included");
uint32_t cntr = (uint32_t)bytes_to_num(&ubuf[33], 4);
PrintAndLog("Counter: %d", cntr);
JsonSaveInt(root, "$.AppData.Counter", cntr);
// attestation data
PrintAndLog("AAGUID: %s", sprint_hex(&ubuf[37], 16));
JsonSaveBufAsHexCompact(root, "$.AppData.AAGUID", &ubuf[37], 16);
// Credential ID
uint8_t cridlen = (uint16_t)bytes_to_num(&ubuf[53], 2);
PrintAndLog("Credential id[%d]: %s", cridlen, sprint_hex_inrow(&ubuf[55], cridlen));
JsonSaveInt(root, "$.AppData.CredentialIdLen", cridlen);
JsonSaveBufAsHexCompact(root, "$.AppData.CredentialId", &ubuf[55], cridlen);
//Credentional public key (COSE_KEY)
uint8_t coseKey[65] = {0};
uint16_t cplen = n - 55 - cridlen;
if (verbose2) {
PrintAndLog("Credentional public key (COSE_KEY)[%d]: %s", cplen, sprint_hex_inrow(&ubuf[55 + cridlen], cplen));
} else {
PrintAndLog("Credentional public key (COSE_KEY)[%d]: %s...", cplen, sprint_hex(&ubuf[55 + cridlen], MIN(cplen, 16)));
}
JsonSaveBufAsHexCompact(root, "$.AppData.COSE_KEY", &ubuf[55 + cridlen], cplen);
if (showCBOR) {
PrintAndLog("COSE structure:");
PrintAndLog("---------------- CBOR ------------------");
TinyCborPrintFIDOPackage(fido2COSEKey, true, &ubuf[55 + cridlen], cplen);
PrintAndLog("---------------- CBOR ------------------");
}
res = COSEGetECDSAKey(&ubuf[55 + cridlen], cplen, verbose, coseKey);
if (res) {
PrintAndLog("ERROR: Can't get COSE_KEY.");
} else {
PrintAndLog("COSE public key: %s", sprint_hex_inrow(coseKey, sizeof(coseKey)));
JsonSaveBufAsHexCompact(root, "$.AppData.COSEPublicKey", coseKey, sizeof(coseKey));
}
free(ubuf);
// attStmt - we are check only as DER certificate
int64_t alg = 0;
uint8_t sign[128] = {0};
size_t signLen = 0;
uint8_t der[4097] = {0};
size_t derLen = 0;
res = CborMapGetKeyById(&parser, &map, data, dataLen, 3);
if (res)
return res;
res = cbor_value_enter_container(&map, &mapsmt);
cbor_check(res);
while (!cbor_value_at_end(&mapsmt)) {
char key[100] = {0};
res = CborGetStringValue(&mapsmt, key, sizeof(key), &n);
cbor_check(res);
if (!strcmp(key, "alg")) {
cbor_value_get_int64(&mapsmt, &alg);
PrintAndLog("Alg [%lld] %s", (long long)alg, GetCOSEAlgDescription(alg));
res = cbor_value_advance_fixed(&mapsmt);
cbor_check(res);
}
if (!strcmp(key, "sig")) {
res = CborGetBinStringValue(&mapsmt, sign, sizeof(sign), &signLen);
cbor_check(res);
if (verbose2) {
PrintAndLog("signature [%d]: %s", signLen, sprint_hex_inrow(sign, signLen));
} else {
PrintAndLog("signature [%d]: %s...", signLen, sprint_hex(sign, MIN(signLen, 16)));
}
}
if (!strcmp(key, "x5c")) {
res = CborGetArrayBinStringValue(&mapsmt, der, sizeof(der), &derLen);
cbor_check(res);
if (verbose2) {
PrintAndLog("DER certificate[%d]:\n------------------DER-------------------", derLen);
dump_buffer_simple((const unsigned char *)der, derLen, NULL);
PrintAndLog("\n----------------DER---------------------");
} else {
PrintAndLog("DER [%d]: %s...", derLen, sprint_hex(der, MIN(derLen, 16)));
}
JsonSaveBufAsHexCompact(root, "$.AppData.DER", der, derLen);
}
}
res = cbor_value_leave_container(&map, &mapsmt);
cbor_check(res);
uint8_t public_key[65] = {0};
// print DER certificate in TLV view
if (showDERTLV) {
PrintAndLog("----------------DER TLV-----------------");
asn1_print(der, derLen, " ");
PrintAndLog("----------------DER TLV-----------------");
}
FIDOCheckDERAndGetKey(der, derLen, verbose, public_key, sizeof(public_key));
JsonSaveBufAsHexCompact(root, "$.AppData.DERPublicKey", public_key, sizeof(public_key));
// check ANSI X9.62 format ECDSA signature (on P-256)
FIDO2CheckSignature(root, public_key, sign, signLen, authData, authDataLen, verbose);
return 0;
}
int FIDO2CreateGetAssertionReq(json_t *root, uint8_t *data, size_t maxdatalen, size_t *datalen, bool createAllowList) {
if (datalen)
*datalen = 0;
if (!root || !data || !maxdatalen)
return 1;
int res;
CborEncoder encoder;
CborEncoder map, array, mapint;
cbor_encoder_init(&encoder, data, maxdatalen, 0);
// create main map
res = cbor_encoder_create_map(&encoder, &map, createAllowList ? 4 : 3);
fido_check_if(res) {
// rpId
res = cbor_encode_uint(&map, 1);
fido_check_if(res) {
res = CBOREncodeElm(root, "$.RelyingPartyEntity.id", &map);
fido_check(res);
}
// clientDataHash
res = cbor_encode_uint(&map, 2);
fido_check_if(res) {
res = CBOREncodeClientDataHash(root, &map);
fido_check(res);
}
// allowList
if (createAllowList) {
res = cbor_encode_uint(&map, 3);
fido_check_if(res) {
res = cbor_encoder_create_array(&map, &array, 1);
fido_check_if(res) {
res = cbor_encoder_create_map(&array, &mapint, 2);
fido_check_if(res) {
res = cbor_encode_text_stringz(&mapint, "type");
fido_check(res);
res = cbor_encode_text_stringz(&mapint, "public-key");
fido_check(res);
res = cbor_encode_text_stringz(&mapint, "id");
fido_check(res);
res = CBOREncodeElm(root, "$.AppData.CredentialId", &mapint);
fido_check(res);
}
res = cbor_encoder_close_container(&array, &mapint);
fido_check(res);
}
res = cbor_encoder_close_container(&map, &array);
fido_check(res);
}
}
// options
res = cbor_encode_uint(&map, 5);
fido_check_if(res) {
res = CBOREncodeElm(root, "GetAssertionOptions", &map);
fido_check(res);
}
}
res = cbor_encoder_close_container(&encoder, &map);
fido_check(res);
size_t len = cbor_encoder_get_buffer_size(&encoder, data);
if (datalen)
*datalen = len;
return 0;
}
int FIDO2GetAssertionParseRes(json_t *root, uint8_t *data, size_t dataLen, bool verbose, bool verbose2, bool showCBOR) {
CborParser parser;
CborValue map, mapint;
int res;
uint8_t *ubuf;
size_t n;
// credential
res = CborMapGetKeyById(&parser, &map, data, dataLen, 1);
if (res)
return res;
res = cbor_value_enter_container(&map, &mapint);
cbor_check(res);
while (!cbor_value_at_end(&mapint)) {
char key[100] = {0};
res = CborGetStringValue(&mapint, key, sizeof(key), &n);
cbor_check(res);
if (!strcmp(key, "type")) {
char ctype[200] = {0};
res = CborGetStringValue(&mapint, ctype, sizeof(ctype), &n);
cbor_check(res);
PrintAndLog("credential type: %s", ctype);
}
if (!strcmp(key, "id")) {
uint8_t cid[200] = {0};
res = CborGetBinStringValue(&mapint, cid, sizeof(cid), &n);
cbor_check(res);
PrintAndLog("credential id [%d]: %s", n, sprint_hex(cid, n));
}
}
res = cbor_value_leave_container(&map, &mapint);
cbor_check(res);
// authData
uint8_t authData[400] = {0};
size_t authDataLen = 0;
res = CborMapGetKeyById(&parser, &map, data, dataLen, 2);
if (res)
return res;
res = cbor_value_dup_byte_string(&map, &ubuf, &n, &map);
cbor_check(res);
authDataLen = n;
memcpy(authData, ubuf, authDataLen);
if (verbose2) {
PrintAndLog("authData[%d]: %s", n, sprint_hex_inrow(authData, authDataLen));
} else {
PrintAndLog("authData[%d]: %s...", n, sprint_hex(authData, MIN(authDataLen, 16)));
}
PrintAndLog("RP ID Hash: %s", sprint_hex(ubuf, 32));
// check RP ID Hash
if (CheckrpIdHash(root, ubuf)) {
PrintAndLog("rpIdHash OK.");
} else {
PrintAndLog("rpIdHash ERROR!");
}
PrintAndLog("Flags 0x%02x:", ubuf[32]);
if (!ubuf[32])
PrintAndLog("none");
if (ubuf[32] & 0x01)
PrintAndLog("up - user presence result");
if (ubuf[32] & 0x04)
PrintAndLog("uv - user verification (fingerprint scan or a PIN or ...) result");
if (ubuf[32] & 0x40)
PrintAndLog("at - attested credential data included");
if (ubuf[32] & 0x80)
PrintAndLog("ed - extension data included");
uint32_t cntr = (uint32_t)bytes_to_num(&ubuf[33], 4);
PrintAndLog("Counter: %d", cntr);
JsonSaveInt(root, "$.AppData.Counter", cntr);
free(ubuf);
// publicKeyCredentialUserEntity
res = CborMapGetKeyById(&parser, &map, data, dataLen, 4);
if (res) {
PrintAndLog("UserEntity n/a");
} else {
res = cbor_value_enter_container(&map, &mapint);
cbor_check(res);
while (!cbor_value_at_end(&mapint)) {
char key[100] = {0};
res = CborGetStringValue(&mapint, key, sizeof(key), &n);
cbor_check(res);
if (!strcmp(key, "name") || !strcmp(key, "displayName")) {
char cname[200] = {0};
res = CborGetStringValue(&mapint, cname, sizeof(cname), &n);
cbor_check(res);
PrintAndLog("UserEntity %s: %s", key, cname);
}
if (!strcmp(key, "id")) {
uint8_t cid[200] = {0};
res = CborGetBinStringValue(&mapint, cid, sizeof(cid), &n);
cbor_check(res);
PrintAndLog("UserEntity id [%d]: %s", n, sprint_hex(cid, n));
// check
uint8_t idbuf[100] = {0};
size_t idbuflen;
JsonLoadBufAsHex(root, "$.UserEntity.id", idbuf, sizeof(idbuf), &idbuflen);
if (idbuflen == n && !memcmp(idbuf, cid, idbuflen)) {
PrintAndLog("UserEntity id OK.");
} else {
PrintAndLog("ERROR: Wrong UserEntity id (from json: %s)", sprint_hex(idbuf, idbuflen));
}
}
}
res = cbor_value_leave_container(&map, &mapint);
cbor_check(res);
}
// signature
res = CborMapGetKeyById(&parser, &map, data, dataLen, 3);
if (res)
return res;
res = cbor_value_dup_byte_string(&map, &ubuf, &n, &map);
cbor_check(res);
uint8_t *sign = ubuf;
size_t signLen = n;
cbor_check(res);
if (verbose2) {
PrintAndLog("signature [%d]: %s", signLen, sprint_hex_inrow(sign, signLen));
} else {
PrintAndLog("signature [%d]: %s...", signLen, sprint_hex(sign, MIN(signLen, 16)));
}
// get public key from json
uint8_t PublicKey[65] = {0};
size_t PublicKeyLen = 0;
JsonLoadBufAsHex(root, "$.AppData.COSEPublicKey", PublicKey, 65, &PublicKeyLen);
// check ANSI X9.62 format ECDSA signature (on P-256)
FIDO2CheckSignature(root, PublicKey, sign, signLen, authData, authDataLen, verbose);
free(ubuf);
// numberOfCredentials
res = CborMapGetKeyById(&parser, &map, data, dataLen, 5);
if (res) {
PrintAndLog("numberOfCredentials: 1 by default");
} else {
int64_t numberOfCredentials = 0;
cbor_value_get_int64(&map, &numberOfCredentials);
PrintAndLog("numberOfCredentials: %lld", (long long)numberOfCredentials);
}
return 0;
}

58
client/fido/fidocore.h Normal file
View file

@ -0,0 +1,58 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// FIDO2 authenticators core data and commands
// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html
//-----------------------------------------------------------------------------
//
#ifndef __FIDOCORE_H__
#define __FIDOCORE_H__
#include <stddef.h>
#include <stdint.h>
#include <jansson.h>
#include "cmdhf14a.h"
#include "emv/emvcore.h"
typedef enum {
fido2CmdMakeCredential = 0x01,
fido2CmdGetAssertion = 0x02,
fido2CmdCancel = 0x03,
fido2CmdGetInfo = 0x04,
fido2CmdClientPIN = 0x06,
fido2CmdReset = 0x07,
fido2CmdGetNextAssertion = 0x08,
// another data
fido2COSEKey = 0xF0
} fido2Commands;
typedef enum {
ptQuery,
ptResponse,
} fido2PacketType;
extern int FIDOSelect(bool ActivateField, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
extern int FIDOExchange(sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
extern int FIDORegister(uint8_t *params, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
extern int FIDOAuthentication(uint8_t *params, uint8_t paramslen, uint8_t controlb, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
extern int FIDO2GetInfo(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
extern int FIDO2MakeCredential(uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
extern int FIDO2GetAssertion(uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw);
extern int FIDOCheckDERAndGetKey(uint8_t *der, size_t derLen, bool verbose, uint8_t *publicKey, size_t publicKeyMaxLen);
extern char *fido2GetCmdMemberDescription(uint8_t cmdCode, bool isResponse, int memberNum);
extern char *fido2GetCmdErrorDescription(uint8_t errorCode);
extern bool CheckrpIdHash(json_t *json, uint8_t *hash);
extern int FIDO2CreateMakeCredentionalReq(json_t *root, uint8_t *data, size_t maxdatalen, size_t *datalen);
extern int FIDO2MakeCredentionalParseRes(json_t *root, uint8_t *data, size_t dataLen, bool verbose, bool verbose2, bool showCBOR, bool showDERTLV);
extern int FIDO2CreateGetAssertionReq(json_t *root, uint8_t *data, size_t maxdatalen, size_t *datalen, bool createAllowList);
extern int FIDO2GetAssertionParseRes(json_t *root, uint8_t *data, size_t dataLen, bool verbose, bool verbose2, bool showCBOR);
#endif /* __FIDOCORE_H__ */