1 //-----------------------------------------------------------------------------
2 // Copyright (C) 2018 Merlok
4 // This code is licensed to you under the terms of the GNU GPL, version 2 or,
5 // at your option, any later version. See the LICENSE.txt file for the text of
7 //-----------------------------------------------------------------------------
8 // FIDO2 authenticators core data and commands
9 // https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html
10 //-----------------------------------------------------------------------------
14 #include "emv/emvcore.h"
15 #include "emv/emvjson.h"
17 #include "cbortools.h"
18 #include <mbedtls/x509_crt.h>
19 #include <mbedtls/x509.h>
20 #include <mbedtls/pk.h>
21 #include "crypto/asn1utils.h"
22 #include "crypto/libpcrypto.h"
23 #include "fido/additional_ca.h"
24 #include "fido/cose.h"
26 #include "protocols.h"
33 char *ShortDescription
;
37 fido2Error_t fido2Errors
[] = {
39 {0x00, "CTAP1_ERR_SUCCESS", "Indicates successful response."},
40 {0x01, "CTAP1_ERR_INVALID_COMMAND", "The command is not a valid CTAP command."},
41 {0x02, "CTAP1_ERR_INVALID_PARAMETER", "The command included an invalid parameter."},
42 {0x03, "CTAP1_ERR_INVALID_LENGTH", "Invalid message or item length."},
43 {0x04, "CTAP1_ERR_INVALID_SEQ", "Invalid message sequencing."},
44 {0x05, "CTAP1_ERR_TIMEOUT", "Message timed out."},
45 {0x06, "CTAP1_ERR_CHANNEL_BUSY", "Channel busy."},
46 {0x0A, "CTAP1_ERR_LOCK_REQUIRED", "Command requires channel lock."},
47 {0x0B, "CTAP1_ERR_INVALID_CHANNEL", "Command not allowed on this cid."},
48 {0x10, "CTAP2_ERR_CBOR_PARSING", "Error while parsing CBOR."},
49 {0x11, "CTAP2_ERR_CBOR_UNEXPECTED_TYPE", "Invalid/unexpected CBOR error."},
50 {0x12, "CTAP2_ERR_INVALID_CBOR", "Error when parsing CBOR."},
51 {0x13, "CTAP2_ERR_INVALID_CBOR_TYPE", "Invalid or unexpected CBOR type."},
52 {0x14, "CTAP2_ERR_MISSING_PARAMETER", "Missing non-optional parameter."},
53 {0x15, "CTAP2_ERR_LIMIT_EXCEEDED", "Limit for number of items exceeded."},
54 {0x16, "CTAP2_ERR_UNSUPPORTED_EXTENSION", "Unsupported extension."},
55 {0x17, "CTAP2_ERR_TOO_MANY_ELEMENTS", "Limit for number of items exceeded."},
56 {0x18, "CTAP2_ERR_EXTENSION_NOT_SUPPORTED", "Unsupported extension."},
57 {0x19, "CTAP2_ERR_CREDENTIAL_EXCLUDED", "Valid credential found in the exludeList."},
58 {0x20, "CTAP2_ERR_CREDENTIAL_NOT_VALID", "Credential not valid for authenticator."},
59 {0x21, "CTAP2_ERR_PROCESSING", "Processing (Lengthy operation is in progress)."},
60 {0x22, "CTAP2_ERR_INVALID_CREDENTIAL", "Credential not valid for the authenticator."},
61 {0x23, "CTAP2_ERR_USER_ACTION_PENDING", "Authentication is waiting for user interaction."},
62 {0x24, "CTAP2_ERR_OPERATION_PENDING", "Processing, lengthy operation is in progress."},
63 {0x25, "CTAP2_ERR_NO_OPERATIONS", "No request is pending."},
64 {0x26, "CTAP2_ERR_UNSUPPORTED_ALGORITHM", "Authenticator does not support requested algorithm."},
65 {0x27, "CTAP2_ERR_OPERATION_DENIED", "Not authorized for requested operation."},
66 {0x28, "CTAP2_ERR_KEY_STORE_FULL", "Internal key storage is full."},
67 {0x29, "CTAP2_ERR_NOT_BUSY", "Authenticator cannot cancel as it is not busy."},
68 {0x2A, "CTAP2_ERR_NO_OPERATION_PENDING", "No outstanding operations."},
69 {0x2B, "CTAP2_ERR_UNSUPPORTED_OPTION", "Unsupported option."},
70 {0x2C, "CTAP2_ERR_INVALID_OPTION", "Unsupported option."},
71 {0x2D, "CTAP2_ERR_KEEPALIVE_CANCEL", "Pending keep alive was cancelled."},
72 {0x2E, "CTAP2_ERR_NO_CREDENTIALS", "No valid credentials provided."},
73 {0x2F, "CTAP2_ERR_USER_ACTION_TIMEOUT", "Timeout waiting for user interaction."},
74 {0x30, "CTAP2_ERR_NOT_ALLOWED", "Continuation command, such as, authenticatorGetNextAssertion not allowed."},
75 {0x31, "CTAP2_ERR_PIN_INVALID", "PIN Blocked."},
76 {0x32, "CTAP2_ERR_PIN_BLOCKED", "PIN Blocked."},
77 {0x33, "CTAP2_ERR_PIN_AUTH_INVALID", "PIN authentication,pinAuth, verification failed."},
78 {0x34, "CTAP2_ERR_PIN_AUTH_BLOCKED", "PIN authentication,pinAuth, blocked. Requires power recycle to reset."},
79 {0x35, "CTAP2_ERR_PIN_NOT_SET", "No PIN has been set."},
80 {0x36, "CTAP2_ERR_PIN_REQUIRED", "PIN is required for the selected operation."},
81 {0x37, "CTAP2_ERR_PIN_POLICY_VIOLATION", "PIN policy violation. Currently only enforces minimum length."},
82 {0x38, "CTAP2_ERR_PIN_TOKEN_EXPIRED", "pinToken expired on authenticator."},
83 {0x39, "CTAP2_ERR_REQUEST_TOO_LARGE", "Authenticator cannot handle this request due to memory constraints."},
84 {0x7F, "CTAP1_ERR_OTHER", "Other unspecified error."},
85 {0xDF, "CTAP2_ERR_SPEC_LAST", "CTAP 2 spec last error."},
89 fido2Commands Command
;
90 fido2PacketType PckType
;
95 fido2Desc_t fido2CmdGetInfoRespDesc
[] = {
96 {fido2CmdMakeCredential
, ptResponse
, 0x01, "fmt"},
97 {fido2CmdMakeCredential
, ptResponse
, 0x02, "authData"},
98 {fido2CmdMakeCredential
, ptResponse
, 0x03, "attStmt"},
100 {fido2CmdMakeCredential
, ptQuery
, 0x01, "clientDataHash"},
101 {fido2CmdMakeCredential
, ptQuery
, 0x02, "rp"},
102 {fido2CmdMakeCredential
, ptQuery
, 0x03, "user"},
103 {fido2CmdMakeCredential
, ptQuery
, 0x04, "pubKeyCredParams"},
104 {fido2CmdMakeCredential
, ptQuery
, 0x05, "excludeList"},
105 {fido2CmdMakeCredential
, ptQuery
, 0x06, "extensions"},
106 {fido2CmdMakeCredential
, ptQuery
, 0x07, "options"},
107 {fido2CmdMakeCredential
, ptQuery
, 0x08, "pinAuth"},
108 {fido2CmdMakeCredential
, ptQuery
, 0x09, "pinProtocol"},
110 {fido2CmdGetAssertion
, ptResponse
, 0x01, "credential"},
111 {fido2CmdGetAssertion
, ptResponse
, 0x02, "authData"},
112 {fido2CmdGetAssertion
, ptResponse
, 0x03, "signature"},
113 {fido2CmdGetAssertion
, ptResponse
, 0x04, "publicKeyCredentialUserEntity"},
114 {fido2CmdGetAssertion
, ptResponse
, 0x05, "numberOfCredentials"},
116 {fido2CmdGetAssertion
, ptQuery
, 0x01, "rpId"},
117 {fido2CmdGetAssertion
, ptQuery
, 0x02, "clientDataHash"},
118 {fido2CmdGetAssertion
, ptQuery
, 0x03, "allowList"},
119 {fido2CmdGetAssertion
, ptQuery
, 0x04, "extensions"},
120 {fido2CmdGetAssertion
, ptQuery
, 0x05, "options"},
121 {fido2CmdGetAssertion
, ptQuery
, 0x06, "pinAuth"},
122 {fido2CmdGetAssertion
, ptQuery
, 0x07, "pinProtocol"},
124 {fido2CmdGetNextAssertion
, ptResponse
, 0x01, "credential"},
125 {fido2CmdGetNextAssertion
, ptResponse
, 0x02, "authData"},
126 {fido2CmdGetNextAssertion
, ptResponse
, 0x03, "signature"},
127 {fido2CmdGetNextAssertion
, ptResponse
, 0x04, "publicKeyCredentialUserEntity"},
129 {fido2CmdGetInfo
, ptResponse
, 0x01, "versions"},
130 {fido2CmdGetInfo
, ptResponse
, 0x02, "extensions"},
131 {fido2CmdGetInfo
, ptResponse
, 0x03, "aaguid"},
132 {fido2CmdGetInfo
, ptResponse
, 0x04, "options"},
133 {fido2CmdGetInfo
, ptResponse
, 0x05, "maxMsgSize"},
134 {fido2CmdGetInfo
, ptResponse
, 0x06, "pinProtocols"},
136 {fido2CmdClientPIN
, ptResponse
, 0x01, "keyAgreement"},
137 {fido2CmdClientPIN
, ptResponse
, 0x02, "pinToken"},
138 {fido2CmdClientPIN
, ptResponse
, 0x03, "retries"},
140 {fido2CmdClientPIN
, ptQuery
, 0x01, "pinProtocol"},
141 {fido2CmdClientPIN
, ptQuery
, 0x02, "subCommand"},
142 {fido2CmdClientPIN
, ptQuery
, 0x03, "keyAgreement"},
143 {fido2CmdClientPIN
, ptQuery
, 0x04, "pinAuth"},
144 {fido2CmdClientPIN
, ptQuery
, 0x05, "newPinEnc"},
145 {fido2CmdClientPIN
, ptQuery
, 0x06, "pinHashEnc"},
146 {fido2CmdClientPIN
, ptQuery
, 0x07, "getKeyAgreement"},
147 {fido2CmdClientPIN
, ptQuery
, 0x08, "getRetries"},
149 {fido2COSEKey
, ptResponse
, 0x01, "kty"},
150 {fido2COSEKey
, ptResponse
, 0x03, "alg"},
151 {fido2COSEKey
, ptResponse
, -1, "crv"},
152 {fido2COSEKey
, ptResponse
, -2, "x - coordinate"},
153 {fido2COSEKey
, ptResponse
, -3, "y - coordinate"},
154 {fido2COSEKey
, ptResponse
, -4, "d - private key"},
157 char *fido2GetCmdErrorDescription(uint8_t errorCode
) {
158 for (int i
= 0; i
< sizeof(fido2Errors
) / sizeof(fido2Error_t
); i
++)
159 if (fido2Errors
[i
].ErrorCode
== errorCode
)
160 return fido2Errors
[i
].Description
;
162 return fido2Errors
[0].Description
;
165 char *fido2GetCmdMemberDescription(uint8_t cmdCode
, bool isResponse
, int memberNum
) {
166 for (int i
= 0; i
< sizeof(fido2CmdGetInfoRespDesc
) / sizeof(fido2Desc_t
); i
++)
167 if (fido2CmdGetInfoRespDesc
[i
].Command
== cmdCode
&&
168 fido2CmdGetInfoRespDesc
[i
].PckType
== (isResponse
? ptResponse
: ptQuery
) &&
169 fido2CmdGetInfoRespDesc
[i
].MemberNumber
== memberNum
)
170 return fido2CmdGetInfoRespDesc
[i
].Description
;
175 int FIDOSelect(bool ActivateField
, bool LeaveFieldON
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
176 uint8_t data
[] = {0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01};
178 return EMVSelect(ECC_CONTACTLESS
, ActivateField
, LeaveFieldON
, data
, sizeof(data
), Result
, MaxResultLen
, ResultLen
, sw
, NULL
);
181 int FIDOExchange(uint8_t* apdu
, int apdulen
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
182 int res
= EMVExchangeEx(ECC_CONTACTLESS
, false, true, apdu
, apdulen
, Result
, MaxResultLen
, ResultLen
, sw
, NULL
);
183 // if (res == 5) // apdu result (sw) not a 0x9000
185 // // software chaining
186 // while (!res && (*sw >> 8) == 0x61) {
187 // uint8_t La = *sw & 0xff;
188 // uint8_t get_response_APDU[5] = {apdu[0], ISO7816_GET_RESPONSE, 0x00, 0x00, La};
189 // size_t oldlen = *ResultLen;
190 // res = EMVExchange(ECC_CONTACTLESS, true, get_response_APDU, sizeof(get_response_APDU), &Result[oldlen], MaxResultLen - oldlen, ResultLen, sw, NULL);
191 // if (res == 5) // apdu result (sw) not a 0x9000
194 // *ResultLen += oldlen;
195 // if (*ResultLen > MaxResultLen)
201 int FIDORegister(uint8_t *params
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
)
203 uint8_t APDU
[5 + 64] = {0x00, 0x01, 0x03, 0x00, 64, 0x00};
204 memcpy(APDU
+ 5, params
, 64);
205 return FIDOExchange(APDU
, 5 + 64, Result
, MaxResultLen
, ResultLen
, sw
);
208 int FIDOAuthentication(uint8_t *params
, uint8_t paramslen
, uint8_t controlb
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
)
210 uint8_t APDU
[APDU_COMMAND_LEN
] = {0x00, 0x02, controlb
, 0x00, paramslen
, 0x00};
211 memcpy(APDU
+ 5, params
, paramslen
);
212 int apdu_len
= 5 + paramslen
;
213 return FIDOExchange(APDU
, apdu_len
, Result
, MaxResultLen
, ResultLen
, sw
);
216 int FIDO2GetInfo(uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
)
218 uint8_t APDU
[6] = {0x80, 0x10, 0x00, 0x00, 0x01, fido2CmdGetInfo
};
219 return FIDOExchange(APDU
, sizeof(APDU
), Result
, MaxResultLen
, ResultLen
, sw
);
222 int FIDO2MakeCredential(uint8_t *params
, uint8_t paramslen
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
)
224 uint8_t APDU
[APDU_COMMAND_LEN
] = {0x80, 0x10, 0x00, 0x00, paramslen
+ 1, fido2CmdMakeCredential
, 0x00};
225 memcpy(APDU
+6, params
, paramslen
);
226 int apdu_len
= 5 + paramslen
+ 1;
227 return FIDOExchange(APDU
, apdu_len
, Result
, MaxResultLen
, ResultLen
, sw
);
230 int FIDO2GetAssertion(uint8_t *params
, uint8_t paramslen
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
)
232 uint8_t APDU
[APDU_COMMAND_LEN
] = {0x80, 0x10, 0x00, 0x00, paramslen
+ 1, fido2CmdGetAssertion
, 0x00};
233 memcpy(APDU
+6, params
, paramslen
);
234 int apdu_len
= 5 + paramslen
+ 1;
235 return FIDOExchange(APDU
, apdu_len
, Result
, MaxResultLen
, ResultLen
, sw
);
238 int FIDOCheckDERAndGetKey(uint8_t *der
, size_t derLen
, bool verbose
, uint8_t *publicKey
, size_t publicKeyMaxLen
) {
242 mbedtls_x509_crt cacert
;
243 mbedtls_x509_crt_init(&cacert
);
244 res
= mbedtls_x509_crt_parse(&cacert
, (const unsigned char *) additional_ca_pem
, additional_ca_pem_len
);
246 PrintAndLog("ERROR: CA parse certificate returned -0x%x - %s", -res
, ecdsa_get_error(res
));
249 PrintAndLog("CA load OK. %d skipped", res
);
251 // load DER certificate from authenticator's data
252 mbedtls_x509_crt cert
;
253 mbedtls_x509_crt_init(&cert
);
254 res
= mbedtls_x509_crt_parse_der(&cert
, der
, derLen
);
256 PrintAndLog("ERROR: DER parse returned 0x%x - %s", (res
<0)?-res
:res
, ecdsa_get_error(res
));
259 // get certificate info
260 char linfo
[300] = {0};
262 mbedtls_x509_crt_info(linfo
, sizeof(linfo
), " ", &cert
);
263 PrintAndLog("DER certificate info:\n%s", linfo
);
266 // verify certificate
267 uint32_t verifyflags
= 0;
268 res
= mbedtls_x509_crt_verify(&cert
, &cacert
, NULL
, NULL
, &verifyflags
, NULL
, NULL
);
270 PrintAndLog("ERROR: DER verify returned 0x%x - %s", (res
<0)?-res
:res
, ecdsa_get_error(res
));
272 PrintAndLog("Certificate OK.");
276 memset(linfo
, 0x00, sizeof(linfo
));
277 mbedtls_x509_crt_verify_info(linfo
, sizeof(linfo
), " ", verifyflags
);
278 PrintAndLog("Verification info:\n%s", linfo
);
282 res
= ecdsa_public_key_from_pk(&cert
.pk
, publicKey
, publicKeyMaxLen
);
284 PrintAndLog("ERROR: getting public key from certificate 0x%x - %s", (res
<0)?-res
:res
, ecdsa_get_error(res
));
287 PrintAndLog("Got a public key from certificate:\n%s", sprint_hex_inrow(publicKey
, 65));
291 PrintAndLog("------------------DER-------------------");
293 mbedtls_x509_crt_free(&cert
);
294 mbedtls_x509_crt_free(&cacert
);
299 #define fido_check_if(r) if ((r) != CborNoError) {return r;} else
300 #define fido_check(r) if ((r) != CborNoError) return r;
302 int FIDO2CreateMakeCredentionalReq(json_t
*root
, uint8_t *data
, size_t maxdatalen
, size_t *datalen
) {
305 if (!root
|| !data
|| !maxdatalen
)
312 cbor_encoder_init(&encoder
, data
, maxdatalen
, 0);
315 res
= cbor_encoder_create_map(&encoder
, &map
, 5);
318 res
= cbor_encode_uint(&map
, 1);
320 res
= CBOREncodeClientDataHash(root
, &map
);
325 res
= cbor_encode_uint(&map
, 2);
327 res
= CBOREncodeElm(root
, "RelyingPartyEntity", &map
);
332 res
= cbor_encode_uint(&map
, 3);
334 res
= CBOREncodeElm(root
, "UserEntity", &map
);
339 res
= cbor_encode_uint(&map
, 4);
341 res
= CBOREncodeElm(root
, "pubKeyCredParams", &map
);
346 res
= cbor_encode_uint(&map
, 7);
348 res
= CBOREncodeElm(root
, "MakeCredentialOptions", &map
);
352 res
= cbor_encoder_close_container(&encoder
, &map
);
355 size_t len
= cbor_encoder_get_buffer_size(&encoder
, data
);
362 bool CheckrpIdHash(json_t
*json
, uint8_t *hash
) {
363 char hashval
[300] = {0};
364 uint8_t hash2
[32] = {0};
366 JsonLoadStr(json
, "$.RelyingPartyEntity.id", hashval
);
367 sha256hash((uint8_t *)hashval
, strlen(hashval
), hash2
);
369 return !memcmp(hash
, hash2
, 32);
372 // check ANSI X9.62 format ECDSA signature (on P-256)
373 int FIDO2CheckSignature(json_t
*root
, uint8_t *publickey
, uint8_t *sign
, size_t signLen
, uint8_t *authData
, size_t authDataLen
, bool verbose
) {
375 uint8_t rval
[300] = {0};
376 uint8_t sval
[300] = {0};
377 res
= ecdsa_asn1_get_signature(sign
, signLen
, rval
, sval
);
380 PrintAndLog(" r: %s", sprint_hex(rval
, 32));
381 PrintAndLog(" s: %s", sprint_hex(sval
, 32));
384 uint8_t clientDataHash
[32] = {0};
385 size_t clientDataHashLen
= 0;
386 res
= JsonLoadBufAsHex(root
, "$.ClientDataHash", clientDataHash
, sizeof(clientDataHash
), &clientDataHashLen
);
387 if (res
|| clientDataHashLen
!= 32) {
388 PrintAndLog("ERROR: Can't get clientDataHash from json!");
392 uint8_t xbuf
[4096] = {0};
394 res
= FillBuffer(xbuf
, sizeof(xbuf
), &xbuflen
,
395 authData
, authDataLen
, // rpIdHash[32] + flags[1] + signCount[4]
396 clientDataHash
, 32, // Hash of the serialized client data. "$.ClientDataHash" from json
398 //PrintAndLog("--xbuf(%d)[%d]: %s", res, xbuflen, sprint_hex(xbuf, xbuflen));
399 res
= ecdsa_signature_verify(publickey
, xbuf
, xbuflen
, sign
, signLen
);
401 if (res
== -0x4e00) {
402 PrintAndLog("Signature is NOT VALID.");
404 PrintAndLog("Other signature check error: %x %s", (res
<0)?-res
:res
, ecdsa_get_error(res
));
408 PrintAndLog("Signature is OK.");
411 PrintAndLog("Invalid signature. res=%d.", res
);
418 int FIDO2MakeCredentionalParseRes(json_t
*root
, uint8_t *data
, size_t dataLen
, bool verbose
, bool verbose2
, bool showCBOR
, bool showDERTLV
) {
420 CborValue map
, mapsmt
;
427 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 1);
431 res
= cbor_value_dup_text_string(&map
, &buf
, &n
, &map
);
433 PrintAndLog("format: %s", buf
);
437 uint8_t authData
[400] = {0};
438 size_t authDataLen
= 0;
439 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 2);
442 res
= cbor_value_dup_byte_string(&map
, &ubuf
, &n
, &map
);
446 memcpy(authData
, ubuf
, authDataLen
);
449 PrintAndLog("authData[%d]: %s", n
, sprint_hex_inrow(authData
, authDataLen
));
451 PrintAndLog("authData[%d]: %s...", n
, sprint_hex(authData
, MIN(authDataLen
, 16)));
454 PrintAndLog("RP ID Hash: %s", sprint_hex(ubuf
, 32));
457 if (CheckrpIdHash(root
, ubuf
)) {
458 PrintAndLog("rpIdHash OK.");
460 PrintAndLog("rpIdHash ERROR!");
463 PrintAndLog("Flags 0x%02x:", ubuf
[32]);
467 PrintAndLog("up - user presence result");
469 PrintAndLog("uv - user verification (fingerprint scan or a PIN or ...) result");
471 PrintAndLog("at - attested credential data included");
473 PrintAndLog("ed - extension data included");
475 uint32_t cntr
= (uint32_t)bytes_to_num(&ubuf
[33], 4);
476 PrintAndLog("Counter: %d", cntr
);
477 JsonSaveInt(root
, "$.AppData.Counter", cntr
);
480 PrintAndLog("AAGUID: %s", sprint_hex(&ubuf
[37], 16));
481 JsonSaveBufAsHexCompact(root
, "$.AppData.AAGUID", &ubuf
[37], 16);
484 uint8_t cridlen
= (uint16_t)bytes_to_num(&ubuf
[53], 2);
485 PrintAndLog("Credential id[%d]: %s", cridlen
, sprint_hex_inrow(&ubuf
[55], cridlen
));
486 JsonSaveInt(root
, "$.AppData.CredentialIdLen", cridlen
);
487 JsonSaveBufAsHexCompact(root
, "$.AppData.CredentialId", &ubuf
[55], cridlen
);
489 //Credentional public key (COSE_KEY)
490 uint8_t coseKey
[65] = {0};
491 uint16_t cplen
= n
- 55 - cridlen
;
493 PrintAndLog("Credentional public key (COSE_KEY)[%d]: %s", cplen
, sprint_hex_inrow(&ubuf
[55 + cridlen
], cplen
));
495 PrintAndLog("Credentional public key (COSE_KEY)[%d]: %s...", cplen
, sprint_hex(&ubuf
[55 + cridlen
], MIN(cplen
, 16)));
497 JsonSaveBufAsHexCompact(root
, "$.AppData.COSE_KEY", &ubuf
[55 + cridlen
], cplen
);
500 PrintAndLog("COSE structure:");
501 PrintAndLog("---------------- CBOR ------------------");
502 TinyCborPrintFIDOPackage(fido2COSEKey
, true, &ubuf
[55 + cridlen
], cplen
);
503 PrintAndLog("---------------- CBOR ------------------");
506 res
= COSEGetECDSAKey(&ubuf
[55 + cridlen
], cplen
, verbose
, coseKey
);
508 PrintAndLog("ERROR: Can't get COSE_KEY.");
510 PrintAndLog("COSE public key: %s", sprint_hex_inrow(coseKey
, sizeof(coseKey
)));
511 JsonSaveBufAsHexCompact(root
, "$.AppData.COSEPublicKey", coseKey
, sizeof(coseKey
));
516 // attStmt - we are check only as DER certificate
518 uint8_t sign
[128] = {0};
520 uint8_t der
[4097] = {0};
523 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 3);
527 res
= cbor_value_enter_container(&map
, &mapsmt
);
530 while (!cbor_value_at_end(&mapsmt
)) {
532 res
= CborGetStringValue(&mapsmt
, key
, sizeof(key
), &n
);
534 if (!strcmp(key
, "alg")) {
535 cbor_value_get_int64(&mapsmt
, &alg
);
536 PrintAndLog("Alg [%lld] %s", (long long)alg
, GetCOSEAlgDescription(alg
));
537 res
= cbor_value_advance_fixed(&mapsmt
);
541 if (!strcmp(key
, "sig")) {
542 res
= CborGetBinStringValue(&mapsmt
, sign
, sizeof(sign
), &signLen
);
545 PrintAndLog("signature [%d]: %s", signLen
, sprint_hex_inrow(sign
, signLen
));
547 PrintAndLog("signature [%d]: %s...", signLen
, sprint_hex(sign
, MIN(signLen
, 16)));
551 if (!strcmp(key
, "x5c")) {
552 res
= CborGetArrayBinStringValue(&mapsmt
, der
, sizeof(der
), &derLen
);
555 PrintAndLog("DER certificate[%d]:\n------------------DER-------------------", derLen
);
556 dump_buffer_simple((const unsigned char *)der
, derLen
, NULL
);
557 PrintAndLog("\n----------------DER---------------------");
559 PrintAndLog("DER [%d]: %s...", derLen
, sprint_hex(der
, MIN(derLen
, 16)));
561 JsonSaveBufAsHexCompact(root
, "$.AppData.DER", der
, derLen
);
564 res
= cbor_value_leave_container(&map
, &mapsmt
);
567 uint8_t public_key
[65] = {0};
569 // print DER certificate in TLV view
571 PrintAndLog("----------------DER TLV-----------------");
572 asn1_print(der
, derLen
, " ");
573 PrintAndLog("----------------DER TLV-----------------");
575 FIDOCheckDERAndGetKey(der
, derLen
, verbose
, public_key
, sizeof(public_key
));
576 JsonSaveBufAsHexCompact(root
, "$.AppData.DERPublicKey", public_key
, sizeof(public_key
));
578 // check ANSI X9.62 format ECDSA signature (on P-256)
579 FIDO2CheckSignature(root
, public_key
, sign
, signLen
, authData
, authDataLen
, verbose
);
584 int FIDO2CreateGetAssertionReq(json_t
*root
, uint8_t *data
, size_t maxdatalen
, size_t *datalen
, bool createAllowList
) {
587 if (!root
|| !data
|| !maxdatalen
)
592 CborEncoder map
, array
, mapint
;
594 cbor_encoder_init(&encoder
, data
, maxdatalen
, 0);
597 res
= cbor_encoder_create_map(&encoder
, &map
, createAllowList
? 4 : 3);
600 res
= cbor_encode_uint(&map
, 1);
602 res
= CBOREncodeElm(root
, "$.RelyingPartyEntity.id", &map
);
607 res
= cbor_encode_uint(&map
, 2);
609 res
= CBOREncodeClientDataHash(root
, &map
);
614 if (createAllowList
) {
615 res
= cbor_encode_uint(&map
, 3);
617 res
= cbor_encoder_create_array(&map
, &array
, 1);
619 res
= cbor_encoder_create_map(&array
, &mapint
, 2);
621 res
= cbor_encode_text_stringz(&mapint
, "type");
624 res
= cbor_encode_text_stringz(&mapint
, "public-key");
627 res
= cbor_encode_text_stringz(&mapint
, "id");
630 res
= CBOREncodeElm(root
, "$.AppData.CredentialId", &mapint
);
633 res
= cbor_encoder_close_container(&array
, &mapint
);
636 res
= cbor_encoder_close_container(&map
, &array
);
642 res
= cbor_encode_uint(&map
, 5);
644 res
= CBOREncodeElm(root
, "GetAssertionOptions", &map
);
648 res
= cbor_encoder_close_container(&encoder
, &map
);
651 size_t len
= cbor_encoder_get_buffer_size(&encoder
, data
);
658 int FIDO2GetAssertionParseRes(json_t
*root
, uint8_t *data
, size_t dataLen
, bool verbose
, bool verbose2
, bool showCBOR
) {
660 CborValue map
, mapint
;
666 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 1);
670 res
= cbor_value_enter_container(&map
, &mapint
);
673 while (!cbor_value_at_end(&mapint
)) {
675 res
= CborGetStringValue(&mapint
, key
, sizeof(key
), &n
);
678 if (!strcmp(key
, "type")) {
679 char ctype
[200] = {0};
680 res
= CborGetStringValue(&mapint
, ctype
, sizeof(ctype
), &n
);
682 PrintAndLog("credential type: %s", ctype
);
685 if (!strcmp(key
, "id")) {
686 uint8_t cid
[200] = {0};
687 res
= CborGetBinStringValue(&mapint
, cid
, sizeof(cid
), &n
);
689 PrintAndLog("credential id [%d]: %s", n
, sprint_hex(cid
, n
));
692 res
= cbor_value_leave_container(&map
, &mapint
);
696 uint8_t authData
[400] = {0};
697 size_t authDataLen
= 0;
698 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 2);
701 res
= cbor_value_dup_byte_string(&map
, &ubuf
, &n
, &map
);
705 memcpy(authData
, ubuf
, authDataLen
);
708 PrintAndLog("authData[%d]: %s", n
, sprint_hex_inrow(authData
, authDataLen
));
710 PrintAndLog("authData[%d]: %s...", n
, sprint_hex(authData
, MIN(authDataLen
, 16)));
713 PrintAndLog("RP ID Hash: %s", sprint_hex(ubuf
, 32));
716 if (CheckrpIdHash(root
, ubuf
)) {
717 PrintAndLog("rpIdHash OK.");
719 PrintAndLog("rpIdHash ERROR!");
722 PrintAndLog("Flags 0x%02x:", ubuf
[32]);
726 PrintAndLog("up - user presence result");
728 PrintAndLog("uv - user verification (fingerprint scan or a PIN or ...) result");
730 PrintAndLog("at - attested credential data included");
732 PrintAndLog("ed - extension data included");
734 uint32_t cntr
= (uint32_t)bytes_to_num(&ubuf
[33], 4);
735 PrintAndLog("Counter: %d", cntr
);
736 JsonSaveInt(root
, "$.AppData.Counter", cntr
);
740 // publicKeyCredentialUserEntity
741 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 4);
743 PrintAndLog("UserEntity n/a");
745 res
= cbor_value_enter_container(&map
, &mapint
);
748 while (!cbor_value_at_end(&mapint
)) {
750 res
= CborGetStringValue(&mapint
, key
, sizeof(key
), &n
);
753 if (!strcmp(key
, "name") || !strcmp(key
, "displayName")) {
754 char cname
[200] = {0};
755 res
= CborGetStringValue(&mapint
, cname
, sizeof(cname
), &n
);
757 PrintAndLog("UserEntity %s: %s", key
, cname
);
760 if (!strcmp(key
, "id")) {
761 uint8_t cid
[200] = {0};
762 res
= CborGetBinStringValue(&mapint
, cid
, sizeof(cid
), &n
);
764 PrintAndLog("UserEntity id [%d]: %s", n
, sprint_hex(cid
, n
));
767 uint8_t idbuf
[100] = {0};
770 JsonLoadBufAsHex(root
, "$.UserEntity.id", idbuf
, sizeof(idbuf
), &idbuflen
);
772 if (idbuflen
== n
&& !memcmp(idbuf
, cid
, idbuflen
)) {
773 PrintAndLog("UserEntity id OK.");
775 PrintAndLog("ERROR: Wrong UserEntity id (from json: %s)", sprint_hex(idbuf
, idbuflen
));
779 res
= cbor_value_leave_container(&map
, &mapint
);
785 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 3);
788 res
= cbor_value_dup_byte_string(&map
, &ubuf
, &n
, &map
);
791 uint8_t *sign
= ubuf
;
796 PrintAndLog("signature [%d]: %s", signLen
, sprint_hex_inrow(sign
, signLen
));
798 PrintAndLog("signature [%d]: %s...", signLen
, sprint_hex(sign
, MIN(signLen
, 16)));
801 // get public key from json
802 uint8_t PublicKey
[65] = {0};
803 size_t PublicKeyLen
= 0;
804 JsonLoadBufAsHex(root
, "$.AppData.COSEPublicKey", PublicKey
, 65, &PublicKeyLen
);
806 // check ANSI X9.62 format ECDSA signature (on P-256)
807 FIDO2CheckSignature(root
, PublicKey
, sign
, signLen
, authData
, authDataLen
, verbose
);
811 // numberOfCredentials
812 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 5);
814 PrintAndLog("numberOfCredentials: 1 by default");
816 int64_t numberOfCredentials
= 0;
817 cbor_value_get_int64(&map
, &numberOfCredentials
);
818 PrintAndLog("numberOfCredentials: %lld", (long long)numberOfCredentials
);