2018-11-08 23:29:58 +08:00
//-----------------------------------------------------------------------------
// Copyright (C) 2018 Merlok
//
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
// at your option, any later version. See the LICENSE.txt file for the text of
// the license.
//-----------------------------------------------------------------------------
// High frequency MIFARE Plus commands
//-----------------------------------------------------------------------------
//
// Documentation here:
//
// FIDO Alliance specifications
// https://fidoalliance.org/download/
// FIDO NFC Protocol Specification v1.0
// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-nfc-protocol-v1.2-ps-20170411.html
// FIDO U2F Raw Message Formats
// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html
//-----------------------------------------------------------------------------
# include "cmdhffido.h"
# include <inttypes.h>
# include <string.h>
# include <stdio.h>
# include <stdlib.h>
# include <ctype.h>
# include <unistd.h>
# include <jansson.h>
2018-11-17 00:33:10 +08:00
# include <mbedtls/x509_crt.h>
# include <mbedtls/x509.h>
# include <mbedtls/pk.h>
2018-11-08 23:29:58 +08:00
# 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"
2018-11-13 01:05:05 +08:00
# include "crypto/asn1utils.h"
# include "crypto/libpcrypto.h"
2018-11-14 01:02:02 +08:00
# include "fido/additional_ca.h"
2018-11-17 00:33:10 +08:00
# include "fido/cbortools.h"
2018-11-17 22:19:09 +08:00
# include "fido/fidocore.h"
2018-11-08 23:29:58 +08:00
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 ) {
2018-11-17 22:19:09 +08:00
uint8_t data [ ] = { fido2CmdGetInfo } ;
2018-11-08 23:29:58 +08:00
return FIDOExchange ( ( sAPDU ) { 0x80 , 0x10 , 0x00 , 0x00 , sizeof ( data ) , data } , Result , MaxResultLen , ResultLen , sw ) ;
}
2018-11-18 05:52:36 +08:00
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 ) ;
}
2018-11-08 23:29:58 +08:00
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 ;
}
2018-11-17 07:40:27 +08:00
if ( buf [ 0 ] ) {
2018-11-18 00:11:46 +08:00
PrintAndLog ( " FIDO2 ger version error: %d - %s " , buf [ 0 ] , fido2GetCmdErrorDescription ( buf [ 0 ] ) ) ;
2018-11-17 07:40:27 +08:00
return 0 ;
}
2018-11-08 23:29:58 +08:00
2018-11-18 00:39:21 +08:00
if ( len > 1 ) {
// if (false) {
// PrintAndLog("FIDO2 version: (len=%d)", len);
// dump_buffer((const unsigned char *)buf, len, NULL, 0);
// }
PrintAndLog ( " FIDO2 version CBOR decoded: " ) ;
TinyCborPrintFIDOPackage ( fido2CmdGetInfo , & buf [ 1 ] , len - 1 ) ;
} else {
PrintAndLog ( " FIDO2 version length error " ) ;
}
2018-11-08 23:29:58 +08:00
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. \n challenge parameter (32b) and application parameter (32b). " ,
" Usage: \n \t hf fido reg -> execute command with 2 parameters, filled 0x00 \n "
" \t hf fido reg 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f -> execute command with parameters "
" \t hf fido reg -p s0 s1 -> execute command with plain parameters " ) ;
void * argtable [ ] = {
arg_param_begin ,
arg_lit0 ( " aA " , " apdu " , " show APDU reqests and responses " ) ,
2018-11-14 01:06:30 +08:00
arg_litn ( " vV " , " verbose " , 0 , 2 , " show technical data. vv - show full certificates data " ) ,
2018-11-08 23:29:58 +08:00
arg_lit0 ( " pP " , " plain " , " send plain ASCII to challenge and application parameters instead of HEX " ) ,
2018-11-16 00:20:37 +08:00
arg_lit0 ( " tT " , " tlv " , " Show DER certificate contents in TLV representation " ) ,
2018-11-08 23:29:58 +08:00
arg_str0 ( " jJ " , " json " , " fido.json " , " JSON input / output file name for parameters. " ) ,
arg_str0 ( NULL , NULL , " <HEX/ASCII challenge parameter (32b HEX/1..16 chars)> " , NULL ) ,
arg_str0 ( NULL , NULL , " <HEX/ASCII application parameter (32b HEX/1..16 chars)> " , NULL ) ,
arg_param_end
} ;
CLIExecWithReturn ( cmd , argtable , true ) ;
bool APDULogging = arg_get_lit ( 1 ) ;
bool verbose = arg_get_lit ( 2 ) ;
2018-11-14 01:02:02 +08:00
bool verbose2 = arg_get_lit ( 2 ) > 1 ;
2018-11-08 23:29:58 +08:00
bool paramsPlain = arg_get_lit ( 3 ) ;
2018-11-16 00:20:37 +08:00
bool showDERTLV = arg_get_lit ( 4 ) ;
2018-11-08 23:29:58 +08:00
char fname [ 250 ] = { 0 } ;
bool err ;
2018-11-16 00:20:37 +08:00
root = OpenJson ( 5 , fname , argtable , & err ) ;
2018-11-08 23:29:58 +08:00
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 ) ;
2018-11-16 00:20:37 +08:00
CLIGetStrWithReturn ( 6 , cdata , & chlen ) ;
2018-11-08 23:29:58 +08:00
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 {
2018-11-16 00:20:37 +08:00
CLIGetHexWithReturn ( 6 , cdata , & chlen ) ;
2018-11-08 23:29:58 +08:00
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 ) ;
2018-11-16 00:20:37 +08:00
CLIGetStrWithReturn ( 7 , adata , & applen ) ;
2018-11-08 23:29:58 +08:00
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 {
2018-11-16 00:20:37 +08:00
CLIGetHexWithReturn ( 7 , adata , & applen ) ;
2018-11-08 23:29:58 +08:00
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 ) ;
2018-11-14 01:02:02 +08:00
if ( verbose2 ) {
2018-11-08 23:29:58 +08:00
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 ;
2018-11-14 01:02:02 +08:00
if ( verbose2 ) {
2018-11-14 01:22:10 +08:00
PrintAndLog ( " DER certificate[%d]: \n ------------------DER------------------- " , derLen ) ;
2018-11-15 02:44:32 +08:00
dump_buffer_simple ( ( const unsigned char * ) & buf [ derp ] , derLen , NULL ) ;
2018-11-08 23:29:58 +08:00
PrintAndLog ( " \n ----------------DER--------------------- " ) ;
} else {
2018-11-14 01:22:10 +08:00
if ( verbose )
PrintAndLog ( " ------------------DER------------------- " ) ;
2018-11-08 23:29:58 +08:00
PrintAndLog ( " DER certificate[%d]: %s... " , derLen , sprint_hex ( & buf [ derp ] , 20 ) ) ;
}
2018-11-13 01:05:05 +08:00
// check and print DER certificate
2018-11-14 01:02:02 +08:00
uint8_t public_key [ 65 ] = { 0 } ;
2018-11-16 00:42:22 +08:00
// print DER certificate in TLV view
2018-11-16 00:20:37 +08:00
if ( showDERTLV ) {
PrintAndLog ( " ----------------DER TLV----------------- " ) ;
asn1_print ( & buf [ derp ] , derLen , " " ) ;
PrintAndLog ( " ----------------DER TLV----------------- " ) ;
}
2018-11-14 01:02:02 +08:00
// 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 ) ;
2018-11-15 02:44:32 +08:00
res = mbedtls_x509_crt_parse_der ( & cert , & buf [ derp ] , derLen ) ;
2018-11-14 01:02:02 +08:00
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 } ;
2018-11-14 01:22:10 +08:00
if ( verbose ) {
mbedtls_x509_crt_info ( linfo , sizeof ( linfo ) , " " , & cert ) ;
PrintAndLog ( " DER certificate info: \n %s " , linfo ) ;
}
2018-11-14 01:02:02 +08:00
// 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 ) ) ;
2018-11-14 01:22:10 +08:00
} else {
PrintAndLog ( " Certificate OK. " ) ;
2018-11-14 01:02:02 +08:00
}
2018-11-14 01:22:10 +08:00
if ( verbose ) {
memset ( linfo , 0x00 , sizeof ( linfo ) ) ;
mbedtls_x509_crt_verify_info ( linfo , sizeof ( linfo ) , " " , verifyflags ) ;
PrintAndLog ( " Verification info: \n %s " , linfo ) ;
}
2018-11-14 01:02:02 +08:00
// get public key
res = ecdsa_public_key_from_pk ( & cert . pk , public_key , sizeof ( public_key ) ) ;
if ( res ) {
PrintAndLog ( " ERROR: getting public key from certificate 0x%x - %s " , ( res < 0 ) ? - res : res , ecdsa_get_error ( res ) ) ;
} else {
if ( verbose )
2018-11-14 01:22:10 +08:00
PrintAndLog ( " Got a public key from certificate: \n %s " , sprint_hex_inrow ( public_key , 65 ) ) ;
2018-11-14 01:02:02 +08:00
}
2018-11-14 01:22:10 +08:00
if ( verbose )
PrintAndLog ( " ------------------DER------------------- " ) ;
2018-11-14 01:02:02 +08:00
mbedtls_x509_crt_free ( & cert ) ;
mbedtls_x509_crt_free ( & cacert ) ;
2018-11-08 23:29:58 +08:00
2018-11-13 01:05:05 +08:00
// get hash
2018-11-08 23:29:58 +08:00
int hashp = 1 + 65 + 1 + keyHandleLen + derLen ;
PrintAndLog ( " Hash[%d]: %s " , len - hashp , sprint_hex ( & buf [ hashp ] , len - hashp ) ) ;
2018-11-13 01:05:05 +08:00
2018-11-08 23:29:58 +08:00
// check ANSI X9.62 format ECDSA signature (on P-256)
2018-11-13 01:05:05 +08:00
uint8_t rval [ 300 ] = { 0 } ;
uint8_t sval [ 300 ] = { 0 } ;
res = ecdsa_asn1_get_signature ( & buf [ hashp ] , len - hashp , rval , sval ) ;
if ( ! res ) {
if ( verbose ) {
PrintAndLog ( " r: %s " , sprint_hex ( rval , 32 ) ) ;
PrintAndLog ( " s: %s " , sprint_hex ( sval , 32 ) ) ;
}
uint8_t xbuf [ 4096 ] = { 0 } ;
size_t xbuflen = 0 ;
res = FillBuffer ( xbuf , sizeof ( xbuf ) , & xbuflen ,
" \x00 " , 1 ,
2018-11-14 01:02:02 +08:00
& data [ 32 ] , 32 , // application parameter
& data [ 0 ] , 32 , // challenge parameter
2018-11-13 01:05:05 +08:00
& buf [ 67 ] , keyHandleLen , // keyHandle
& buf [ 1 ] , 65 , // user public key
NULL , 0 ) ;
//PrintAndLog("--xbuf(%d)[%d]: %s", res, xbuflen, sprint_hex(xbuf, xbuflen));
res = ecdsa_signature_verify ( public_key , xbuf , xbuflen , & buf [ hashp ] , len - hashp ) ;
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 ) ) ;
}
} else {
PrintAndLog ( " Signature is OK. " ) ;
}
} else {
PrintAndLog ( " Invalid signature. res=%d. " , res ) ;
}
2018-11-08 23:29:58 +08:00
PrintAndLog ( " \n auth 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 ) ;
2018-11-13 20:55:02 +08:00
JsonSaveBufAsHexCompact ( root , " PublicKey " , & buf [ 1 ] , 65 ) ;
2018-11-08 23:29:58 +08:00
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 } ;
2018-11-13 20:05:14 +08:00
bool public_key_loaded = false ;
uint8_t public_key [ 65 ] = { 0 } ;
2018-11-08 23:29:58 +08:00
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. \n key handle(var 0..255), challenge parameter (32b) and application parameter (32b). " ,
" Usage: \n \t hf fido auth 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f -> execute command with 2 parameters, filled 0x00 and key handle \n "
" \t hf 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. " ) ,
2018-11-13 20:05:14 +08:00
arg_str0 ( " kK " , " key " , " public key to verify signature " , NULL ) ,
2018-11-08 23:29:58 +08:00
arg_str0 ( NULL , NULL , " <HEX key handle (var 0..255b)> " , NULL ) ,
arg_str0 ( NULL , NULL , " <HEX/ASCII challenge parameter (32b HEX/1..16 chars)> " , NULL ) ,
arg_str0 ( NULL , NULL , " <HEX/ASCII application parameter (32b HEX/1..16 chars)> " , NULL ) ,
arg_param_end
} ;
CLIExecWithReturn ( cmd , argtable , true ) ;
bool APDULogging = arg_get_lit ( 1 ) ;
2018-11-13 20:05:14 +08:00
bool verbose = arg_get_lit ( 2 ) ;
2018-11-08 23:29:58 +08:00
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 ;
2018-11-13 20:55:02 +08:00
JsonLoadBufAsHex ( root , " $.PublicKey " , public_key , 65 , & jlen ) ;
public_key_loaded = ( jlen > 0 ) ;
2018-11-08 23:29:58 +08:00
}
2018-11-13 20:05:14 +08:00
// public key
2018-11-08 23:29:58 +08:00
CLIGetHexWithReturn ( 8 , hdata , & hdatalen ) ;
2018-11-13 20:05:14 +08:00
if ( hdatalen & & hdatalen ! = 130 ) {
PrintAndLog ( " ERROR: public key length must be 65 bytes only. " ) ;
return 1 ;
}
if ( hdatalen ) {
memmove ( public_key , hdata , hdatalen ) ;
public_key_loaded = true ;
}
CLIGetHexWithReturn ( 9 , hdata , & hdatalen ) ;
2018-11-08 23:29:58 +08:00
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 {
2018-11-13 20:05:14 +08:00
CLIGetHexWithReturn ( 10 , hdata , & hdatalen ) ;
2018-11-08 23:29:58 +08:00
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 ) ;
2018-11-13 20:05:14 +08:00
CLIGetStrWithReturn ( 11 , hdata , & hdatalen ) ;
2018-11-08 23:29:58 +08:00
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 ) ) ;
2018-11-13 20:05:14 +08:00
// 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 ) {
if ( verbose ) {
PrintAndLog ( " r: %s " , sprint_hex ( rval , 32 ) ) ;
PrintAndLog ( " 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 ) ;
2018-11-13 20:55:02 +08:00
//PrintAndLog("--xbuf(%d)[%d]: %s", res, xbuflen, sprint_hex(xbuf, xbuflen));
2018-11-13 20:05:14 +08:00
res = ecdsa_signature_verify ( public_key , xbuf , xbuflen , & buf [ 5 ] , len - 5 ) ;
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 ) ) ;
}
} else {
PrintAndLog ( " Signature is OK. " ) ;
}
} else {
PrintAndLog ( " No public key provided. can't check signature. " ) ;
}
} else {
PrintAndLog ( " Invalid signature. res=%d. " , res ) ;
}
2018-11-08 23:29:58 +08:00
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 ;
}