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"
25 #include "protocols.h"
30 char *ShortDescription
;
34 fido2Error_t fido2Errors
[] = {
36 {0x00, "CTAP1_ERR_SUCCESS", "Indicates successful response."},
37 {0x01, "CTAP1_ERR_INVALID_COMMAND", "The command is not a valid CTAP command."},
38 {0x02, "CTAP1_ERR_INVALID_PARAMETER", "The command included an invalid parameter."},
39 {0x03, "CTAP1_ERR_INVALID_LENGTH", "Invalid message or item length."},
40 {0x04, "CTAP1_ERR_INVALID_SEQ", "Invalid message sequencing."},
41 {0x05, "CTAP1_ERR_TIMEOUT", "Message timed out."},
42 {0x06, "CTAP1_ERR_CHANNEL_BUSY", "Channel busy."},
43 {0x0A, "CTAP1_ERR_LOCK_REQUIRED", "Command requires channel lock."},
44 {0x0B, "CTAP1_ERR_INVALID_CHANNEL", "Command not allowed on this cid."},
45 {0x10, "CTAP2_ERR_CBOR_PARSING", "Error while parsing CBOR."},
46 {0x11, "CTAP2_ERR_CBOR_UNEXPECTED_TYPE", "Invalid/unexpected CBOR error."},
47 {0x12, "CTAP2_ERR_INVALID_CBOR", "Error when parsing CBOR."},
48 {0x13, "CTAP2_ERR_INVALID_CBOR_TYPE", "Invalid or unexpected CBOR type."},
49 {0x14, "CTAP2_ERR_MISSING_PARAMETER", "Missing non-optional parameter."},
50 {0x15, "CTAP2_ERR_LIMIT_EXCEEDED", "Limit for number of items exceeded."},
51 {0x16, "CTAP2_ERR_UNSUPPORTED_EXTENSION", "Unsupported extension."},
52 {0x17, "CTAP2_ERR_TOO_MANY_ELEMENTS", "Limit for number of items exceeded."},
53 {0x18, "CTAP2_ERR_EXTENSION_NOT_SUPPORTED", "Unsupported extension."},
54 {0x19, "CTAP2_ERR_CREDENTIAL_EXCLUDED", "Valid credential found in the exludeList."},
55 {0x20, "CTAP2_ERR_CREDENTIAL_NOT_VALID", "Credential not valid for authenticator."},
56 {0x21, "CTAP2_ERR_PROCESSING", "Processing (Lengthy operation is in progress)."},
57 {0x22, "CTAP2_ERR_INVALID_CREDENTIAL", "Credential not valid for the authenticator."},
58 {0x23, "CTAP2_ERR_USER_ACTION_PENDING", "Authentication is waiting for user interaction."},
59 {0x24, "CTAP2_ERR_OPERATION_PENDING", "Processing, lengthy operation is in progress."},
60 {0x25, "CTAP2_ERR_NO_OPERATIONS", "No request is pending."},
61 {0x26, "CTAP2_ERR_UNSUPPORTED_ALGORITHM", "Authenticator does not support requested algorithm."},
62 {0x27, "CTAP2_ERR_OPERATION_DENIED", "Not authorized for requested operation."},
63 {0x28, "CTAP2_ERR_KEY_STORE_FULL", "Internal key storage is full."},
64 {0x29, "CTAP2_ERR_NOT_BUSY", "Authenticator cannot cancel as it is not busy."},
65 {0x2A, "CTAP2_ERR_NO_OPERATION_PENDING", "No outstanding operations."},
66 {0x2B, "CTAP2_ERR_UNSUPPORTED_OPTION", "Unsupported option."},
67 {0x2C, "CTAP2_ERR_INVALID_OPTION", "Unsupported option."},
68 {0x2D, "CTAP2_ERR_KEEPALIVE_CANCEL", "Pending keep alive was cancelled."},
69 {0x2E, "CTAP2_ERR_NO_CREDENTIALS", "No valid credentials provided."},
70 {0x2F, "CTAP2_ERR_USER_ACTION_TIMEOUT", "Timeout waiting for user interaction."},
71 {0x30, "CTAP2_ERR_NOT_ALLOWED", "Continuation command, such as, authenticatorGetNextAssertion not allowed."},
72 {0x31, "CTAP2_ERR_PIN_INVALID", "PIN Blocked."},
73 {0x32, "CTAP2_ERR_PIN_BLOCKED", "PIN Blocked."},
74 {0x33, "CTAP2_ERR_PIN_AUTH_INVALID", "PIN authentication,pinAuth, verification failed."},
75 {0x34, "CTAP2_ERR_PIN_AUTH_BLOCKED", "PIN authentication,pinAuth, blocked. Requires power recycle to reset."},
76 {0x35, "CTAP2_ERR_PIN_NOT_SET", "No PIN has been set."},
77 {0x36, "CTAP2_ERR_PIN_REQUIRED", "PIN is required for the selected operation."},
78 {0x37, "CTAP2_ERR_PIN_POLICY_VIOLATION", "PIN policy violation. Currently only enforces minimum length."},
79 {0x38, "CTAP2_ERR_PIN_TOKEN_EXPIRED", "pinToken expired on authenticator."},
80 {0x39, "CTAP2_ERR_REQUEST_TOO_LARGE", "Authenticator cannot handle this request due to memory constraints."},
81 {0x7F, "CTAP1_ERR_OTHER", "Other unspecified error."},
82 {0xDF, "CTAP2_ERR_SPEC_LAST", "CTAP 2 spec last error."},
86 fido2Commands Command
;
87 fido2PacketType PckType
;
92 fido2Desc_t fido2CmdGetInfoRespDesc
[] = {
93 {fido2CmdMakeCredential
, ptResponse
, 0x01, "fmt"},
94 {fido2CmdMakeCredential
, ptResponse
, 0x02, "authData"},
95 {fido2CmdMakeCredential
, ptResponse
, 0x03, "attStmt"},
97 {fido2CmdMakeCredential
, ptQuery
, 0x01, "clientDataHash"},
98 {fido2CmdMakeCredential
, ptQuery
, 0x02, "rp"},
99 {fido2CmdMakeCredential
, ptQuery
, 0x03, "user"},
100 {fido2CmdMakeCredential
, ptQuery
, 0x04, "pubKeyCredParams"},
101 {fido2CmdMakeCredential
, ptQuery
, 0x05, "excludeList"},
102 {fido2CmdMakeCredential
, ptQuery
, 0x06, "extensions"},
103 {fido2CmdMakeCredential
, ptQuery
, 0x07, "options"},
104 {fido2CmdMakeCredential
, ptQuery
, 0x08, "pinAuth"},
105 {fido2CmdMakeCredential
, ptQuery
, 0x09, "pinProtocol"},
107 {fido2CmdGetAssertion
, ptResponse
, 0x01, "credential"},
108 {fido2CmdGetAssertion
, ptResponse
, 0x02, "authData"},
109 {fido2CmdGetAssertion
, ptResponse
, 0x03, "signature"},
110 {fido2CmdGetAssertion
, ptResponse
, 0x04, "publicKeyCredentialUserEntity"},
111 {fido2CmdGetAssertion
, ptResponse
, 0x05, "numberOfCredentials"},
113 {fido2CmdGetAssertion
, ptQuery
, 0x01, "rpId"},
114 {fido2CmdGetAssertion
, ptQuery
, 0x02, "clientDataHash"},
115 {fido2CmdGetAssertion
, ptQuery
, 0x03, "allowList"},
116 {fido2CmdGetAssertion
, ptQuery
, 0x04, "extensions"},
117 {fido2CmdGetAssertion
, ptQuery
, 0x05, "options"},
118 {fido2CmdGetAssertion
, ptQuery
, 0x06, "pinAuth"},
119 {fido2CmdGetAssertion
, ptQuery
, 0x07, "pinProtocol"},
121 {fido2CmdGetNextAssertion
, ptResponse
, 0x01, "credential"},
122 {fido2CmdGetNextAssertion
, ptResponse
, 0x02, "authData"},
123 {fido2CmdGetNextAssertion
, ptResponse
, 0x03, "signature"},
124 {fido2CmdGetNextAssertion
, ptResponse
, 0x04, "publicKeyCredentialUserEntity"},
126 {fido2CmdGetInfo
, ptResponse
, 0x01, "versions"},
127 {fido2CmdGetInfo
, ptResponse
, 0x02, "extensions"},
128 {fido2CmdGetInfo
, ptResponse
, 0x03, "aaguid"},
129 {fido2CmdGetInfo
, ptResponse
, 0x04, "options"},
130 {fido2CmdGetInfo
, ptResponse
, 0x05, "maxMsgSize"},
131 {fido2CmdGetInfo
, ptResponse
, 0x06, "pinProtocols"},
133 {fido2CmdClientPIN
, ptResponse
, 0x01, "keyAgreement"},
134 {fido2CmdClientPIN
, ptResponse
, 0x02, "pinToken"},
135 {fido2CmdClientPIN
, ptResponse
, 0x03, "retries"},
137 {fido2CmdClientPIN
, ptQuery
, 0x01, "pinProtocol"},
138 {fido2CmdClientPIN
, ptQuery
, 0x02, "subCommand"},
139 {fido2CmdClientPIN
, ptQuery
, 0x03, "keyAgreement"},
140 {fido2CmdClientPIN
, ptQuery
, 0x04, "pinAuth"},
141 {fido2CmdClientPIN
, ptQuery
, 0x05, "newPinEnc"},
142 {fido2CmdClientPIN
, ptQuery
, 0x06, "pinHashEnc"},
143 {fido2CmdClientPIN
, ptQuery
, 0x07, "getKeyAgreement"},
144 {fido2CmdClientPIN
, ptQuery
, 0x08, "getRetries"},
146 {fido2COSEKey
, ptResponse
, 0x01, "kty"},
147 {fido2COSEKey
, ptResponse
, 0x03, "alg"},
148 {fido2COSEKey
, ptResponse
, -1, "crv"},
149 {fido2COSEKey
, ptResponse
, -2, "x - coordinate"},
150 {fido2COSEKey
, ptResponse
, -3, "y - coordinate"},
151 {fido2COSEKey
, ptResponse
, -4, "d - private key"},
154 char *fido2GetCmdErrorDescription(uint8_t errorCode
) {
155 for (int i
= 0; i
< sizeof(fido2Errors
) / sizeof(fido2Error_t
); i
++)
156 if (fido2Errors
[i
].ErrorCode
== errorCode
)
157 return fido2Errors
[i
].Description
;
159 return fido2Errors
[0].Description
;
162 char *fido2GetCmdMemberDescription(uint8_t cmdCode
, bool isResponse
, int memberNum
) {
163 for (int i
= 0; i
< sizeof(fido2CmdGetInfoRespDesc
) / sizeof(fido2Desc_t
); i
++)
164 if (fido2CmdGetInfoRespDesc
[i
].Command
== cmdCode
&&
165 fido2CmdGetInfoRespDesc
[i
].PckType
== (isResponse
? ptResponse
: ptQuery
) &&
166 fido2CmdGetInfoRespDesc
[i
].MemberNumber
== memberNum
)
167 return fido2CmdGetInfoRespDesc
[i
].Description
;
172 int FIDOSelect(bool ActivateField
, bool LeaveFieldON
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
173 uint8_t data
[] = {0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01};
175 return EMVSelect(ECC_CONTACTLESS
, ActivateField
, LeaveFieldON
, data
, sizeof(data
), Result
, MaxResultLen
, ResultLen
, sw
, NULL
);
178 int FIDOExchange(uint8_t* apdu
, int apdulen
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
179 int res
= EMVExchange(ECC_CONTACTLESS
, true, apdu
, apdulen
, Result
, MaxResultLen
, ResultLen
, sw
, NULL
);
180 if (res
== 5) // apdu result (sw) not a 0x9000
183 while (!res
&& (*sw
>> 8) == 0x61) {
184 uint8_t La
= *sw
& 0xff;
185 uint8_t get_response_APDU
[5] = {apdu
[0], ISO7816_GET_RESPONSE
, 0x00, 0x00, La
};
186 size_t oldlen
= *ResultLen
;
187 res
= EMVExchange(ECC_CONTACTLESS
, true, get_response_APDU
, sizeof(get_response_APDU
), &Result
[oldlen
], MaxResultLen
- oldlen
, ResultLen
, sw
, NULL
);
188 if (res
== 5) // apdu result (sw) not a 0x9000
191 *ResultLen
+= oldlen
;
192 if (*ResultLen
> MaxResultLen
)
198 int FIDORegister(uint8_t *params
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
)
200 uint8_t APDU
[4 + 64] = {0x00, 0x01, 0x03, 0x00, 64, 0x00};
201 memcpy(APDU
, params
, 64);
202 return FIDOExchange(APDU
, 4 + 64, Result
, MaxResultLen
, ResultLen
, sw
);
205 int FIDOAuthentication(uint8_t *params
, uint8_t paramslen
, uint8_t controlb
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
)
207 uint8_t APDU
[APDU_COMMAND_LEN
] = {0x00, 0x02, controlb
, 0x00, paramslen
, 0x00};
208 memcpy(APDU
+5, params
, paramslen
);
209 int apdu_len
= 5 + paramslen
;
210 return FIDOExchange(APDU
, apdu_len
, Result
, MaxResultLen
, ResultLen
, sw
);
213 int FIDO2GetInfo(uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
)
215 uint8_t APDU
[5] = {0x80, 0x10, 0x00, 0x00, fido2CmdGetInfo
};
216 return FIDOExchange(APDU
, sizeof(APDU
), Result
, MaxResultLen
, ResultLen
, sw
);
219 int FIDO2MakeCredential(uint8_t *params
, uint8_t paramslen
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
)
221 uint8_t APDU
[APDU_COMMAND_LEN
] = {0x80, 0x10, 0x00, 0x00, paramslen
+ 1, fido2CmdMakeCredential
, 0x00};
222 memcpy(APDU
+6, params
, paramslen
);
223 int apdu_len
= 5 + paramslen
+ 1;
224 return FIDOExchange(APDU
, apdu_len
, Result
, MaxResultLen
, ResultLen
, sw
);
227 int FIDO2GetAssertion(uint8_t *params
, uint8_t paramslen
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
)
229 uint8_t APDU
[APDU_COMMAND_LEN
] = {0x80, 0x10, 0x00, 0x00, paramslen
+ 1, fido2CmdGetAssertion
, 0x00};
230 memcpy(APDU
+6, params
, paramslen
);
231 int apdu_len
= 5 + paramslen
+ 1;
232 return FIDOExchange(APDU
, apdu_len
, Result
, MaxResultLen
, ResultLen
, sw
);
235 int FIDOCheckDERAndGetKey(uint8_t *der
, size_t derLen
, bool verbose
, uint8_t *publicKey
, size_t publicKeyMaxLen
) {
239 mbedtls_x509_crt cacert
;
240 mbedtls_x509_crt_init(&cacert
);
241 res
= mbedtls_x509_crt_parse(&cacert
, (const unsigned char *) additional_ca_pem
, additional_ca_pem_len
);
243 PrintAndLog("ERROR: CA parse certificate returned -0x%x - %s", -res
, ecdsa_get_error(res
));
246 PrintAndLog("CA load OK. %d skipped", res
);
248 // load DER certificate from authenticator's data
249 mbedtls_x509_crt cert
;
250 mbedtls_x509_crt_init(&cert
);
251 res
= mbedtls_x509_crt_parse_der(&cert
, der
, derLen
);
253 PrintAndLog("ERROR: DER parse returned 0x%x - %s", (res
<0)?-res
:res
, ecdsa_get_error(res
));
256 // get certificate info
257 char linfo
[300] = {0};
259 mbedtls_x509_crt_info(linfo
, sizeof(linfo
), " ", &cert
);
260 PrintAndLog("DER certificate info:\n%s", linfo
);
263 // verify certificate
264 uint32_t verifyflags
= 0;
265 res
= mbedtls_x509_crt_verify(&cert
, &cacert
, NULL
, NULL
, &verifyflags
, NULL
, NULL
);
267 PrintAndLog("ERROR: DER verify returned 0x%x - %s", (res
<0)?-res
:res
, ecdsa_get_error(res
));
269 PrintAndLog("Certificate OK.");
273 memset(linfo
, 0x00, sizeof(linfo
));
274 mbedtls_x509_crt_verify_info(linfo
, sizeof(linfo
), " ", verifyflags
);
275 PrintAndLog("Verification info:\n%s", linfo
);
279 res
= ecdsa_public_key_from_pk(&cert
.pk
, publicKey
, publicKeyMaxLen
);
281 PrintAndLog("ERROR: getting public key from certificate 0x%x - %s", (res
<0)?-res
:res
, ecdsa_get_error(res
));
284 PrintAndLog("Got a public key from certificate:\n%s", sprint_hex_inrow(publicKey
, 65));
288 PrintAndLog("------------------DER-------------------");
290 mbedtls_x509_crt_free(&cert
);
291 mbedtls_x509_crt_free(&cacert
);
296 #define fido_check_if(r) if ((r) != CborNoError) {return r;} else
297 #define fido_check(r) if ((r) != CborNoError) return r;
299 int FIDO2CreateMakeCredentionalReq(json_t
*root
, uint8_t *data
, size_t maxdatalen
, size_t *datalen
) {
302 if (!root
|| !data
|| !maxdatalen
)
309 cbor_encoder_init(&encoder
, data
, maxdatalen
, 0);
312 res
= cbor_encoder_create_map(&encoder
, &map
, 5);
315 res
= cbor_encode_uint(&map
, 1);
317 res
= CBOREncodeClientDataHash(root
, &map
);
322 res
= cbor_encode_uint(&map
, 2);
324 res
= CBOREncodeElm(root
, "RelyingPartyEntity", &map
);
329 res
= cbor_encode_uint(&map
, 3);
331 res
= CBOREncodeElm(root
, "UserEntity", &map
);
336 res
= cbor_encode_uint(&map
, 4);
338 res
= CBOREncodeElm(root
, "pubKeyCredParams", &map
);
343 res
= cbor_encode_uint(&map
, 7);
345 res
= CBOREncodeElm(root
, "MakeCredentialOptions", &map
);
349 res
= cbor_encoder_close_container(&encoder
, &map
);
352 size_t len
= cbor_encoder_get_buffer_size(&encoder
, data
);
359 bool CheckrpIdHash(json_t
*json
, uint8_t *hash
) {
360 char hashval
[300] = {0};
361 uint8_t hash2
[32] = {0};
363 JsonLoadStr(json
, "$.RelyingPartyEntity.id", hashval
);
364 sha256hash((uint8_t *)hashval
, strlen(hashval
), hash2
);
366 return !memcmp(hash
, hash2
, 32);
369 // check ANSI X9.62 format ECDSA signature (on P-256)
370 int FIDO2CheckSignature(json_t
*root
, uint8_t *publickey
, uint8_t *sign
, size_t signLen
, uint8_t *authData
, size_t authDataLen
, bool verbose
) {
372 uint8_t rval
[300] = {0};
373 uint8_t sval
[300] = {0};
374 res
= ecdsa_asn1_get_signature(sign
, signLen
, rval
, sval
);
377 PrintAndLog(" r: %s", sprint_hex(rval
, 32));
378 PrintAndLog(" s: %s", sprint_hex(sval
, 32));
381 uint8_t clientDataHash
[32] = {0};
382 size_t clientDataHashLen
= 0;
383 res
= JsonLoadBufAsHex(root
, "$.ClientDataHash", clientDataHash
, sizeof(clientDataHash
), &clientDataHashLen
);
384 if (res
|| clientDataHashLen
!= 32) {
385 PrintAndLog("ERROR: Can't get clientDataHash from json!");
389 uint8_t xbuf
[4096] = {0};
391 res
= FillBuffer(xbuf
, sizeof(xbuf
), &xbuflen
,
392 authData
, authDataLen
, // rpIdHash[32] + flags[1] + signCount[4]
393 clientDataHash
, 32, // Hash of the serialized client data. "$.ClientDataHash" from json
395 //PrintAndLog("--xbuf(%d)[%d]: %s", res, xbuflen, sprint_hex(xbuf, xbuflen));
396 res
= ecdsa_signature_verify(publickey
, xbuf
, xbuflen
, sign
, signLen
);
398 if (res
== -0x4e00) {
399 PrintAndLog("Signature is NOT VALID.");
401 PrintAndLog("Other signature check error: %x %s", (res
<0)?-res
:res
, ecdsa_get_error(res
));
405 PrintAndLog("Signature is OK.");
408 PrintAndLog("Invalid signature. res=%d.", res
);
415 int FIDO2MakeCredentionalParseRes(json_t
*root
, uint8_t *data
, size_t dataLen
, bool verbose
, bool verbose2
, bool showCBOR
, bool showDERTLV
) {
417 CborValue map
, mapsmt
;
424 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 1);
428 res
= cbor_value_dup_text_string(&map
, &buf
, &n
, &map
);
430 PrintAndLog("format: %s", buf
);
434 uint8_t authData
[400] = {0};
435 size_t authDataLen
= 0;
436 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 2);
439 res
= cbor_value_dup_byte_string(&map
, &ubuf
, &n
, &map
);
443 memcpy(authData
, ubuf
, authDataLen
);
446 PrintAndLog("authData[%d]: %s", n
, sprint_hex_inrow(authData
, authDataLen
));
448 PrintAndLog("authData[%d]: %s...", n
, sprint_hex(authData
, MIN(authDataLen
, 16)));
451 PrintAndLog("RP ID Hash: %s", sprint_hex(ubuf
, 32));
454 if (CheckrpIdHash(root
, ubuf
)) {
455 PrintAndLog("rpIdHash OK.");
457 PrintAndLog("rpIdHash ERROR!");
460 PrintAndLog("Flags 0x%02x:", ubuf
[32]);
464 PrintAndLog("up - user presence result");
466 PrintAndLog("uv - user verification (fingerprint scan or a PIN or ...) result");
468 PrintAndLog("at - attested credential data included");
470 PrintAndLog("ed - extension data included");
472 uint32_t cntr
= (uint32_t)bytes_to_num(&ubuf
[33], 4);
473 PrintAndLog("Counter: %d", cntr
);
474 JsonSaveInt(root
, "$.AppData.Counter", cntr
);
477 PrintAndLog("AAGUID: %s", sprint_hex(&ubuf
[37], 16));
478 JsonSaveBufAsHexCompact(root
, "$.AppData.AAGUID", &ubuf
[37], 16);
481 uint8_t cridlen
= (uint16_t)bytes_to_num(&ubuf
[53], 2);
482 PrintAndLog("Credential id[%d]: %s", cridlen
, sprint_hex_inrow(&ubuf
[55], cridlen
));
483 JsonSaveInt(root
, "$.AppData.CredentialIdLen", cridlen
);
484 JsonSaveBufAsHexCompact(root
, "$.AppData.CredentialId", &ubuf
[55], cridlen
);
486 //Credentional public key (COSE_KEY)
487 uint8_t coseKey
[65] = {0};
488 uint16_t cplen
= n
- 55 - cridlen
;
490 PrintAndLog("Credentional public key (COSE_KEY)[%d]: %s", cplen
, sprint_hex_inrow(&ubuf
[55 + cridlen
], cplen
));
492 PrintAndLog("Credentional public key (COSE_KEY)[%d]: %s...", cplen
, sprint_hex(&ubuf
[55 + cridlen
], MIN(cplen
, 16)));
494 JsonSaveBufAsHexCompact(root
, "$.AppData.COSE_KEY", &ubuf
[55 + cridlen
], cplen
);
497 PrintAndLog("COSE structure:");
498 PrintAndLog("---------------- CBOR ------------------");
499 TinyCborPrintFIDOPackage(fido2COSEKey
, true, &ubuf
[55 + cridlen
], cplen
);
500 PrintAndLog("---------------- CBOR ------------------");
503 res
= COSEGetECDSAKey(&ubuf
[55 + cridlen
], cplen
, verbose
, coseKey
);
505 PrintAndLog("ERROR: Can't get COSE_KEY.");
507 PrintAndLog("COSE public key: %s", sprint_hex_inrow(coseKey
, sizeof(coseKey
)));
508 JsonSaveBufAsHexCompact(root
, "$.AppData.COSEPublicKey", coseKey
, sizeof(coseKey
));
513 // attStmt - we are check only as DER certificate
515 uint8_t sign
[128] = {0};
517 uint8_t der
[4097] = {0};
520 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 3);
524 res
= cbor_value_enter_container(&map
, &mapsmt
);
527 while (!cbor_value_at_end(&mapsmt
)) {
529 res
= CborGetStringValue(&mapsmt
, key
, sizeof(key
), &n
);
531 if (!strcmp(key
, "alg")) {
532 cbor_value_get_int64(&mapsmt
, &alg
);
533 PrintAndLog("Alg [%lld] %s", (long long)alg
, GetCOSEAlgDescription(alg
));
534 res
= cbor_value_advance_fixed(&mapsmt
);
538 if (!strcmp(key
, "sig")) {
539 res
= CborGetBinStringValue(&mapsmt
, sign
, sizeof(sign
), &signLen
);
542 PrintAndLog("signature [%d]: %s", signLen
, sprint_hex_inrow(sign
, signLen
));
544 PrintAndLog("signature [%d]: %s...", signLen
, sprint_hex(sign
, MIN(signLen
, 16)));
548 if (!strcmp(key
, "x5c")) {
549 res
= CborGetArrayBinStringValue(&mapsmt
, der
, sizeof(der
), &derLen
);
552 PrintAndLog("DER certificate[%d]:\n------------------DER-------------------", derLen
);
553 dump_buffer_simple((const unsigned char *)der
, derLen
, NULL
);
554 PrintAndLog("\n----------------DER---------------------");
556 PrintAndLog("DER [%d]: %s...", derLen
, sprint_hex(der
, MIN(derLen
, 16)));
558 JsonSaveBufAsHexCompact(root
, "$.AppData.DER", der
, derLen
);
561 res
= cbor_value_leave_container(&map
, &mapsmt
);
564 uint8_t public_key
[65] = {0};
566 // print DER certificate in TLV view
568 PrintAndLog("----------------DER TLV-----------------");
569 asn1_print(der
, derLen
, " ");
570 PrintAndLog("----------------DER TLV-----------------");
572 FIDOCheckDERAndGetKey(der
, derLen
, verbose
, public_key
, sizeof(public_key
));
573 JsonSaveBufAsHexCompact(root
, "$.AppData.DERPublicKey", public_key
, sizeof(public_key
));
575 // check ANSI X9.62 format ECDSA signature (on P-256)
576 FIDO2CheckSignature(root
, public_key
, sign
, signLen
, authData
, authDataLen
, verbose
);
581 int FIDO2CreateGetAssertionReq(json_t
*root
, uint8_t *data
, size_t maxdatalen
, size_t *datalen
, bool createAllowList
) {
584 if (!root
|| !data
|| !maxdatalen
)
589 CborEncoder map
, array
, mapint
;
591 cbor_encoder_init(&encoder
, data
, maxdatalen
, 0);
594 res
= cbor_encoder_create_map(&encoder
, &map
, createAllowList
? 4 : 3);
597 res
= cbor_encode_uint(&map
, 1);
599 res
= CBOREncodeElm(root
, "$.RelyingPartyEntity.id", &map
);
604 res
= cbor_encode_uint(&map
, 2);
606 res
= CBOREncodeClientDataHash(root
, &map
);
611 if (createAllowList
) {
612 res
= cbor_encode_uint(&map
, 3);
614 res
= cbor_encoder_create_array(&map
, &array
, 1);
616 res
= cbor_encoder_create_map(&array
, &mapint
, 2);
618 res
= cbor_encode_text_stringz(&mapint
, "type");
621 res
= cbor_encode_text_stringz(&mapint
, "public-key");
624 res
= cbor_encode_text_stringz(&mapint
, "id");
627 res
= CBOREncodeElm(root
, "$.AppData.CredentialId", &mapint
);
630 res
= cbor_encoder_close_container(&array
, &mapint
);
633 res
= cbor_encoder_close_container(&map
, &array
);
639 res
= cbor_encode_uint(&map
, 5);
641 res
= CBOREncodeElm(root
, "GetAssertionOptions", &map
);
645 res
= cbor_encoder_close_container(&encoder
, &map
);
648 size_t len
= cbor_encoder_get_buffer_size(&encoder
, data
);
655 int FIDO2GetAssertionParseRes(json_t
*root
, uint8_t *data
, size_t dataLen
, bool verbose
, bool verbose2
, bool showCBOR
) {
657 CborValue map
, mapint
;
663 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 1);
667 res
= cbor_value_enter_container(&map
, &mapint
);
670 while (!cbor_value_at_end(&mapint
)) {
672 res
= CborGetStringValue(&mapint
, key
, sizeof(key
), &n
);
675 if (!strcmp(key
, "type")) {
676 char ctype
[200] = {0};
677 res
= CborGetStringValue(&mapint
, ctype
, sizeof(ctype
), &n
);
679 PrintAndLog("credential type: %s", ctype
);
682 if (!strcmp(key
, "id")) {
683 uint8_t cid
[200] = {0};
684 res
= CborGetBinStringValue(&mapint
, cid
, sizeof(cid
), &n
);
686 PrintAndLog("credential id [%d]: %s", n
, sprint_hex(cid
, n
));
689 res
= cbor_value_leave_container(&map
, &mapint
);
693 uint8_t authData
[400] = {0};
694 size_t authDataLen
= 0;
695 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 2);
698 res
= cbor_value_dup_byte_string(&map
, &ubuf
, &n
, &map
);
702 memcpy(authData
, ubuf
, authDataLen
);
705 PrintAndLog("authData[%d]: %s", n
, sprint_hex_inrow(authData
, authDataLen
));
707 PrintAndLog("authData[%d]: %s...", n
, sprint_hex(authData
, MIN(authDataLen
, 16)));
710 PrintAndLog("RP ID Hash: %s", sprint_hex(ubuf
, 32));
713 if (CheckrpIdHash(root
, ubuf
)) {
714 PrintAndLog("rpIdHash OK.");
716 PrintAndLog("rpIdHash ERROR!");
719 PrintAndLog("Flags 0x%02x:", ubuf
[32]);
723 PrintAndLog("up - user presence result");
725 PrintAndLog("uv - user verification (fingerprint scan or a PIN or ...) result");
727 PrintAndLog("at - attested credential data included");
729 PrintAndLog("ed - extension data included");
731 uint32_t cntr
= (uint32_t)bytes_to_num(&ubuf
[33], 4);
732 PrintAndLog("Counter: %d", cntr
);
733 JsonSaveInt(root
, "$.AppData.Counter", cntr
);
737 // publicKeyCredentialUserEntity
738 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 4);
740 PrintAndLog("UserEntity n/a");
742 res
= cbor_value_enter_container(&map
, &mapint
);
745 while (!cbor_value_at_end(&mapint
)) {
747 res
= CborGetStringValue(&mapint
, key
, sizeof(key
), &n
);
750 if (!strcmp(key
, "name") || !strcmp(key
, "displayName")) {
751 char cname
[200] = {0};
752 res
= CborGetStringValue(&mapint
, cname
, sizeof(cname
), &n
);
754 PrintAndLog("UserEntity %s: %s", key
, cname
);
757 if (!strcmp(key
, "id")) {
758 uint8_t cid
[200] = {0};
759 res
= CborGetBinStringValue(&mapint
, cid
, sizeof(cid
), &n
);
761 PrintAndLog("UserEntity id [%d]: %s", n
, sprint_hex(cid
, n
));
764 uint8_t idbuf
[100] = {0};
767 JsonLoadBufAsHex(root
, "$.UserEntity.id", idbuf
, sizeof(idbuf
), &idbuflen
);
769 if (idbuflen
== n
&& !memcmp(idbuf
, cid
, idbuflen
)) {
770 PrintAndLog("UserEntity id OK.");
772 PrintAndLog("ERROR: Wrong UserEntity id (from json: %s)", sprint_hex(idbuf
, idbuflen
));
776 res
= cbor_value_leave_container(&map
, &mapint
);
782 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 3);
785 res
= cbor_value_dup_byte_string(&map
, &ubuf
, &n
, &map
);
788 uint8_t *sign
= ubuf
;
793 PrintAndLog("signature [%d]: %s", signLen
, sprint_hex_inrow(sign
, signLen
));
795 PrintAndLog("signature [%d]: %s...", signLen
, sprint_hex(sign
, MIN(signLen
, 16)));
798 // get public key from json
799 uint8_t PublicKey
[65] = {0};
800 size_t PublicKeyLen
= 0;
801 JsonLoadBufAsHex(root
, "$.AppData.COSEPublicKey", PublicKey
, 65, &PublicKeyLen
);
803 // check ANSI X9.62 format ECDSA signature (on P-256)
804 FIDO2CheckSignature(root
, PublicKey
, sign
, signLen
, authData
, authDataLen
, verbose
);
808 // numberOfCredentials
809 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 5);
811 PrintAndLog("numberOfCredentials: 1 by default");
813 int64_t numberOfCredentials
= 0;
814 cbor_value_get_int64(&map
, &numberOfCredentials
);
815 PrintAndLog("numberOfCredentials: %lld", (long long)numberOfCredentials
);