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 //-----------------------------------------------------------------------------
15 #include "commonutil.h" // ARRAYLEN
17 #include "iso7816/iso7816core.h"
18 #include "emv/emvjson.h"
19 #include "cbortools.h"
21 #include "crypto/asn1utils.h"
22 #include "crypto/libpcrypto.h"
23 #include "additional_ca.h"
30 const char *ShortDescription
;
31 const char *Description
;
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
;
89 const char *Description
;
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 const char *fido2GetCmdErrorDescription(uint8_t errorCode
) {
155 for (size_t i
= 0; i
< ARRAYLEN(fido2Errors
); i
++)
156 if (fido2Errors
[i
].ErrorCode
== errorCode
)
157 return fido2Errors
[i
].Description
;
159 return fido2Errors
[0].Description
;
162 const char *fido2GetCmdMemberDescription(uint8_t cmdCode
, bool isResponse
, int memberNum
) {
163 for (size_t i
= 0; i
< ARRAYLEN(fido2CmdGetInfoRespDesc
); 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 Iso7816Select(CC_CONTACTLESS
, ActivateField
, LeaveFieldON
, data
, sizeof(data
), Result
, MaxResultLen
, ResultLen
, sw
);
178 int FIDOExchange(sAPDU apdu
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
179 int res
= Iso7816Exchange(CC_CONTACTLESS
, true, apdu
, Result
, MaxResultLen
, ResultLen
, sw
);
180 if (res
== 5) // apdu result (sw) not a 0x9000
183 while (!res
&& (*sw
>> 8) == 0x61) {
184 size_t oldlen
= *ResultLen
;
185 res
= Iso7816Exchange(CC_CONTACTLESS
, true, (sAPDU
) {0x00, 0xC0, 0x00, 0x00, 0x00, NULL
}, &Result
[oldlen
], MaxResultLen
- oldlen
, ResultLen
, sw
);
186 if (res
== 5) // apdu result (sw) not a 0x9000
189 *ResultLen
+= oldlen
;
190 if (*ResultLen
> MaxResultLen
)
196 int FIDORegister(uint8_t *params
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
197 return FIDOExchange((sAPDU
) {0x00, 0x01, 0x03, 0x00, 64, params
}, Result
, MaxResultLen
, ResultLen
, sw
);
200 int FIDOAuthentication(uint8_t *params
, uint8_t paramslen
, uint8_t controlb
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
201 return FIDOExchange((sAPDU
) {0x00, 0x02, controlb
, 0x00, paramslen
, params
}, Result
, MaxResultLen
, ResultLen
, sw
);
204 int FIDO2GetInfo(uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
205 uint8_t data
[] = {fido2CmdGetInfo
};
206 return FIDOExchange((sAPDU
) {0x80, 0x10, 0x00, 0x00, sizeof(data
), data
}, Result
, MaxResultLen
, ResultLen
, sw
);
209 int FIDO2MakeCredential(uint8_t *params
, uint8_t paramslen
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
210 uint8_t data
[paramslen
+ 1];
211 data
[0] = fido2CmdMakeCredential
;
212 memcpy(&data
[1], params
, paramslen
);
213 return FIDOExchange((sAPDU
) {0x80, 0x10, 0x00, 0x00, sizeof(data
), data
}, Result
, MaxResultLen
, ResultLen
, sw
);
216 int FIDO2GetAssertion(uint8_t *params
, uint8_t paramslen
, uint8_t *Result
, size_t MaxResultLen
, size_t *ResultLen
, uint16_t *sw
) {
217 uint8_t data
[paramslen
+ 1];
218 data
[0] = fido2CmdGetAssertion
;
219 memcpy(&data
[1], params
, paramslen
);
220 return FIDOExchange((sAPDU
) {0x80, 0x10, 0x00, 0x00, sizeof(data
), data
}, Result
, MaxResultLen
, ResultLen
, sw
);
223 int FIDOCheckDERAndGetKey(uint8_t *der
, size_t derLen
, bool verbose
, uint8_t *publicKey
, size_t publicKeyMaxLen
) {
227 mbedtls_x509_crt cacert
;
228 mbedtls_x509_crt_init(&cacert
);
229 res
= mbedtls_x509_crt_parse(&cacert
, (const unsigned char *) additional_ca_pem
, additional_ca_pem_len
);
231 PrintAndLogEx(ERR
, "ERROR: CA parse certificate returned -0x%x - %s", -res
, ecdsa_get_error(res
));
234 PrintAndLogEx(SUCCESS
, "CA load OK. %d skipped", res
);
236 // load DER certificate from authenticator's data
237 mbedtls_x509_crt cert
;
238 mbedtls_x509_crt_init(&cert
);
239 res
= mbedtls_x509_crt_parse_der(&cert
, der
, derLen
);
241 PrintAndLogEx(ERR
, "ERROR: DER parse returned 0x%x - %s", (res
< 0) ? -res
: res
, ecdsa_get_error(res
));
244 // get certificate info
245 char linfo
[300] = {0};
247 mbedtls_x509_crt_info(linfo
, sizeof(linfo
), " ", &cert
);
248 PrintAndLogEx(SUCCESS
, "DER certificate info:\n%s", linfo
);
251 // verify certificate
252 uint32_t verifyflags
= 0;
253 res
= mbedtls_x509_crt_verify(&cert
, &cacert
, NULL
, NULL
, &verifyflags
, NULL
, NULL
);
255 PrintAndLogEx(ERR
, "ERROR: DER verify returned 0x%x - %s\n", (res
< 0) ? -res
: res
, ecdsa_get_error(res
));
257 PrintAndLogEx(SUCCESS
, "Certificate ( " _GREEN_("ok") " )\n");
261 memset(linfo
, 0x00, sizeof(linfo
));
262 mbedtls_x509_crt_verify_info(linfo
, sizeof(linfo
), " ", verifyflags
);
263 PrintAndLogEx(SUCCESS
, "Verification info:\n%s", linfo
);
267 res
= ecdsa_public_key_from_pk(&cert
.pk
, MBEDTLS_ECP_DP_SECP256R1
, publicKey
, publicKeyMaxLen
);
269 PrintAndLogEx(ERR
, "ERROR: getting public key from certificate 0x%x - %s", (res
< 0) ? -res
: res
, ecdsa_get_error(res
));
272 PrintAndLogEx(SUCCESS
, "Got a public key from certificate:\n%s", sprint_hex_inrow(publicKey
, 65));
276 PrintAndLogEx(INFO
, "------------------DER-------------------");
278 mbedtls_x509_crt_free(&cert
);
279 mbedtls_x509_crt_free(&cacert
);
284 #define fido_check_if(r) if ((r) != CborNoError) {return r;} else
285 #define fido_check(r) if ((r) != CborNoError) return r;
287 int FIDO2CreateMakeCredentionalReq(json_t
*root
, uint8_t *data
, size_t maxdatalen
, size_t *datalen
) {
290 if (!root
|| !data
|| !maxdatalen
)
297 cbor_encoder_init(&encoder
, data
, maxdatalen
, 0);
300 res
= cbor_encoder_create_map(&encoder
, &map
, 5);
303 res
= cbor_encode_uint(&map
, 1);
305 res
= CBOREncodeClientDataHash(root
, &map
);
310 res
= cbor_encode_uint(&map
, 2);
312 res
= CBOREncodeElm(root
, "RelyingPartyEntity", &map
);
317 res
= cbor_encode_uint(&map
, 3);
319 res
= CBOREncodeElm(root
, "UserEntity", &map
);
324 res
= cbor_encode_uint(&map
, 4);
326 res
= CBOREncodeElm(root
, "pubKeyCredParams", &map
);
331 res
= cbor_encode_uint(&map
, 7);
333 res
= CBOREncodeElm(root
, "MakeCredentialOptions", &map
);
337 res
= cbor_encoder_close_container(&encoder
, &map
);
340 size_t len
= cbor_encoder_get_buffer_size(&encoder
, data
);
347 bool CheckrpIdHash(json_t
*json
, uint8_t *hash
) {
348 char hashval
[300] = {0};
349 uint8_t hash2
[32] = {0};
351 JsonLoadStr(json
, "$.RelyingPartyEntity.id", hashval
);
352 int res
= sha256hash((uint8_t *)hashval
, strlen(hashval
), hash2
);
356 return !memcmp(hash
, hash2
, 32);
359 // check ANSI X9.62 format ECDSA signature (on P-256)
360 static int FIDO2CheckSignature(json_t
*root
, uint8_t *publickey
, uint8_t *sign
, size_t signLen
, uint8_t *authData
, size_t authDataLen
, bool verbose
) {
362 uint8_t rval
[300] = {0};
363 uint8_t sval
[300] = {0};
365 int res
= ecdsa_asn1_get_signature(sign
, signLen
, rval
, sval
);
366 if (res
== PM3_SUCCESS
) {
368 PrintAndLogEx(INFO
, " r: %s", sprint_hex(rval
, 32));
369 PrintAndLogEx(INFO
, " s: %s", sprint_hex(sval
, 32));
372 uint8_t clientDataHash
[32] = {0};
373 size_t clientDataHashLen
= 0;
374 res
= JsonLoadBufAsHex(root
, "$.ClientDataHash", clientDataHash
, sizeof(clientDataHash
), &clientDataHashLen
);
375 if (res
|| clientDataHashLen
!= 32) {
376 PrintAndLogEx(ERR
, "ERROR: Can't get clientDataHash from json!");
380 uint8_t xbuf
[4096] = {0};
382 res
= FillBuffer(xbuf
, sizeof(xbuf
), &xbuflen
,
383 authData
, authDataLen
, // rpIdHash[32] + flags[1] + signCount[4]
384 clientDataHash
, 32, // Hash of the serialized client data. "$.ClientDataHash" from json
386 PrintAndLogEx(DEBUG
, "--xbuf(%d)[%zu]: %s", res
, xbuflen
, sprint_hex(xbuf
, xbuflen
));
388 res
= ecdsa_signature_verify(MBEDTLS_ECP_DP_SECP256R1
, publickey
, xbuf
, xbuflen
, sign
, signLen
, true);
390 if (res
== MBEDTLS_ERR_ECP_VERIFY_FAILED
) {
391 PrintAndLogEx(WARNING
, "Signature is ( " _RED_("not valid") " )");
393 PrintAndLogEx(WARNING
, "Other signature check error: %x %s", (res
< 0) ? -res
: res
, ecdsa_get_error(res
));
397 PrintAndLogEx(SUCCESS
, "Signature is ( " _GREEN_("ok") " )");
400 PrintAndLogEx(ERR
, "Invalid signature. res = %d.", res
);
407 int FIDO2MakeCredentionalParseRes(json_t
*root
, uint8_t *data
, size_t dataLen
, bool verbose
, bool verbose2
, bool showCBOR
, bool showDERTLV
) {
409 CborValue map
, mapsmt
;
416 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 1);
420 res
= cbor_value_dup_text_string(&map
, &buf
, &n
, &map
);
422 PrintAndLogEx(INFO
, "format: %s", buf
);
426 uint8_t authData
[400] = {0};
427 size_t authDataLen
= 0;
428 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 2);
431 res
= cbor_value_dup_byte_string(&map
, &ubuf
, &n
, &map
);
435 memcpy(authData
, ubuf
, authDataLen
);
438 PrintAndLogEx(INFO
, "authData[%zu]: %s", n
, sprint_hex_inrow(authData
, authDataLen
));
440 PrintAndLogEx(INFO
, "authData[%zu]: %s...", n
, sprint_hex(authData
, MIN(authDataLen
, 16)));
443 PrintAndLogEx(INFO
, "RP ID Hash: %s", sprint_hex(ubuf
, 32));
446 if (CheckrpIdHash(root
, ubuf
)) {
447 PrintAndLogEx(SUCCESS
, "rpIdHash ( " _GREEN_("ok")" )");
449 PrintAndLogEx(ERR
, "rpIdHash " _RED_("ERROR!!"));
452 PrintAndLogEx(INFO
, "Flags 0x%02x:", ubuf
[32]);
454 PrintAndLogEx(SUCCESS
, "none");
456 PrintAndLogEx(SUCCESS
, "up - user presence result");
458 PrintAndLogEx(SUCCESS
, "uv - user verification (fingerprint scan or a PIN or ...) result");
460 PrintAndLogEx(SUCCESS
, "at - attested credential data included");
462 PrintAndLogEx(SUCCESS
, "ed - extension data included");
464 uint32_t cntr
= (uint32_t)bytes_to_num(&ubuf
[33], 4);
465 PrintAndLogEx(SUCCESS
, "Counter: %d", cntr
);
466 JsonSaveInt(root
, "$.AppData.Counter", cntr
);
469 PrintAndLogEx(SUCCESS
, "AAGUID: %s", sprint_hex(&ubuf
[37], 16));
470 JsonSaveBufAsHexCompact(root
, "$.AppData.AAGUID", &ubuf
[37], 16);
473 uint8_t cridlen
= (uint16_t)bytes_to_num(&ubuf
[53], 2);
474 PrintAndLogEx(SUCCESS
, "Credential id[%d]: %s", cridlen
, sprint_hex_inrow(&ubuf
[55], cridlen
));
475 JsonSaveInt(root
, "$.AppData.CredentialIdLen", cridlen
);
476 JsonSaveBufAsHexCompact(root
, "$.AppData.CredentialId", &ubuf
[55], cridlen
);
478 //Credentional public key (COSE_KEY)
479 uint8_t coseKey
[65] = {0};
480 uint16_t cplen
= n
- 55 - cridlen
;
482 PrintAndLogEx(SUCCESS
, "Credentional public key (COSE_KEY)[%d]: %s", cplen
, sprint_hex_inrow(&ubuf
[55 + cridlen
], cplen
));
484 PrintAndLogEx(SUCCESS
, "Credentional public key (COSE_KEY)[%d]: %s...", cplen
, sprint_hex(&ubuf
[55 + cridlen
], MIN(cplen
, 16)));
486 JsonSaveBufAsHexCompact(root
, "$.AppData.COSE_KEY", &ubuf
[55 + cridlen
], cplen
);
489 PrintAndLogEx(INFO
, "COSE structure:");
490 PrintAndLogEx(INFO
, "---------------- CBOR ------------------");
491 TinyCborPrintFIDOPackage(fido2COSEKey
, true, &ubuf
[55 + cridlen
], cplen
);
492 PrintAndLogEx(INFO
, "---------------- CBOR ------------------");
495 res
= COSEGetECDSAKey(&ubuf
[55 + cridlen
], cplen
, verbose
, coseKey
);
497 PrintAndLogEx(ERR
, "ERROR: Can't get COSE_KEY.");
499 PrintAndLogEx(SUCCESS
, "COSE public key: %s", sprint_hex_inrow(coseKey
, sizeof(coseKey
)));
500 JsonSaveBufAsHexCompact(root
, "$.AppData.COSEPublicKey", coseKey
, sizeof(coseKey
));
505 // attStmt - we are check only as DER certificate
507 uint8_t sign
[128] = {0};
509 uint8_t der
[4097] = {0};
512 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 3);
516 res
= cbor_value_enter_container(&map
, &mapsmt
);
519 while (!cbor_value_at_end(&mapsmt
)) {
521 res
= CborGetStringValue(&mapsmt
, key
, sizeof(key
), &n
);
523 if (!strcmp(key
, "alg")) {
524 cbor_value_get_int64(&mapsmt
, &alg
);
525 PrintAndLogEx(INFO
, "Alg [%lld] %s", (long long)alg
, GetCOSEAlgDescription(alg
));
526 res
= cbor_value_advance_fixed(&mapsmt
);
530 if (!strcmp(key
, "sig")) {
531 res
= CborGetBinStringValue(&mapsmt
, sign
, sizeof(sign
), &signLen
);
534 PrintAndLogEx(INFO
, "signature [%zu]: %s", signLen
, sprint_hex_inrow(sign
, signLen
));
536 PrintAndLogEx(INFO
, "signature [%zu]: %s...", signLen
, sprint_hex(sign
, MIN(signLen
, 16)));
540 if (!strcmp(key
, "x5c")) {
541 res
= CborGetArrayBinStringValue(&mapsmt
, der
, sizeof(der
), &derLen
);
544 PrintAndLogEx(INFO
, "DER certificate[%zu]:", derLen
);
545 PrintAndLogEx(INFO
, "------------------DER-------------------");
546 PrintAndLogEx(INFO
, "%s", sprint_hex(der
, derLen
));
547 PrintAndLogEx(INFO
, "----------------DER---------------------");
549 PrintAndLogEx(INFO
, "DER [%zu]: %s...", derLen
, sprint_hex(der
, MIN(derLen
, 16)));
551 JsonSaveBufAsHexCompact(root
, "$.AppData.DER", der
, derLen
);
554 res
= cbor_value_leave_container(&map
, &mapsmt
);
557 uint8_t public_key
[65] = {0};
559 // print DER certificate in TLV view
561 PrintAndLogEx(INFO
, "----------------DER TLV-----------------");
562 asn1_print(der
, derLen
, " ");
563 PrintAndLogEx(INFO
, "----------------DER TLV-----------------");
565 FIDOCheckDERAndGetKey(der
, derLen
, verbose
, public_key
, sizeof(public_key
));
566 JsonSaveBufAsHexCompact(root
, "$.AppData.DERPublicKey", public_key
, sizeof(public_key
));
568 // check ANSI X9.62 format ECDSA signature (on P-256)
569 FIDO2CheckSignature(root
, public_key
, sign
, signLen
, authData
, authDataLen
, verbose
);
574 int FIDO2CreateGetAssertionReq(json_t
*root
, uint8_t *data
, size_t maxdatalen
, size_t *datalen
, bool createAllowList
) {
577 if (!root
|| !data
|| !maxdatalen
)
582 CborEncoder map
, array
, mapint
;
584 cbor_encoder_init(&encoder
, data
, maxdatalen
, 0);
587 res
= cbor_encoder_create_map(&encoder
, &map
, createAllowList
? 4 : 3);
590 res
= cbor_encode_uint(&map
, 1);
592 res
= CBOREncodeElm(root
, "$.RelyingPartyEntity.id", &map
);
597 res
= cbor_encode_uint(&map
, 2);
599 res
= CBOREncodeClientDataHash(root
, &map
);
604 if (createAllowList
) {
605 res
= cbor_encode_uint(&map
, 3);
607 res
= cbor_encoder_create_array(&map
, &array
, 1);
609 res
= cbor_encoder_create_map(&array
, &mapint
, 2);
611 res
= cbor_encode_text_stringz(&mapint
, "type");
614 res
= cbor_encode_text_stringz(&mapint
, "public-key");
617 res
= cbor_encode_text_stringz(&mapint
, "id");
620 res
= CBOREncodeElm(root
, "$.AppData.CredentialId", &mapint
);
623 res
= cbor_encoder_close_container(&array
, &mapint
);
626 res
= cbor_encoder_close_container(&map
, &array
);
632 res
= cbor_encode_uint(&map
, 5);
634 res
= CBOREncodeElm(root
, "GetAssertionOptions", &map
);
638 res
= cbor_encoder_close_container(&encoder
, &map
);
641 size_t len
= cbor_encoder_get_buffer_size(&encoder
, data
);
648 int FIDO2GetAssertionParseRes(json_t
*root
, uint8_t *data
, size_t dataLen
, bool verbose
, bool verbose2
, bool showCBOR
) {
650 CborValue map
, mapint
;
656 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 1);
660 res
= cbor_value_enter_container(&map
, &mapint
);
663 while (!cbor_value_at_end(&mapint
)) {
665 res
= CborGetStringValue(&mapint
, key
, sizeof(key
), &n
);
668 if (!strcmp(key
, "type")) {
669 char ctype
[200] = {0};
670 res
= CborGetStringValue(&mapint
, ctype
, sizeof(ctype
), &n
);
672 PrintAndLogEx(SUCCESS
, "credential type: %s", ctype
);
675 if (!strcmp(key
, "id")) {
676 uint8_t cid
[200] = {0};
677 res
= CborGetBinStringValue(&mapint
, cid
, sizeof(cid
), &n
);
679 PrintAndLogEx(SUCCESS
, "credential id [%zu]: %s", n
, sprint_hex(cid
, n
));
682 res
= cbor_value_leave_container(&map
, &mapint
);
686 uint8_t authData
[400] = {0};
687 size_t authDataLen
= 0;
688 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 2);
691 res
= cbor_value_dup_byte_string(&map
, &ubuf
, &n
, &map
);
695 memcpy(authData
, ubuf
, authDataLen
);
698 PrintAndLogEx(INFO
, "authData[%zu]: %s", n
, sprint_hex_inrow(authData
, authDataLen
));
700 PrintAndLogEx(INFO
, "authData[%zu]: %s...", n
, sprint_hex(authData
, MIN(authDataLen
, 16)));
703 PrintAndLogEx(INFO
, "RP ID Hash: %s", sprint_hex(ubuf
, 32));
706 if (CheckrpIdHash(root
, ubuf
)) {
707 PrintAndLogEx(SUCCESS
, "rpIdHash ( " _GREEN_("ok")" )");
709 PrintAndLogEx(ERR
, "rpIdHash " _RED_("ERROR!!"));
712 PrintAndLogEx(INFO
, "Flags 0x%02x:", ubuf
[32]);
714 PrintAndLogEx(SUCCESS
, "none");
716 PrintAndLogEx(SUCCESS
, "up - user presence result");
718 PrintAndLogEx(SUCCESS
, "uv - user verification (fingerprint scan or a PIN or ...) result");
720 PrintAndLogEx(SUCCESS
, "at - attested credential data included");
722 PrintAndLogEx(SUCCESS
, "ed - extension data included");
724 uint32_t cntr
= (uint32_t)bytes_to_num(&ubuf
[33], 4);
725 PrintAndLogEx(SUCCESS
, "Counter: %d", cntr
);
726 JsonSaveInt(root
, "$.AppData.Counter", cntr
);
730 // publicKeyCredentialUserEntity
731 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 4);
733 PrintAndLogEx(SUCCESS
, "UserEntity n/a");
735 res
= cbor_value_enter_container(&map
, &mapint
);
738 while (!cbor_value_at_end(&mapint
)) {
740 res
= CborGetStringValue(&mapint
, key
, sizeof(key
), &n
);
743 if (!strcmp(key
, "name") || !strcmp(key
, "displayName")) {
744 char cname
[200] = {0};
745 res
= CborGetStringValue(&mapint
, cname
, sizeof(cname
), &n
);
747 PrintAndLogEx(SUCCESS
, "UserEntity %s: %s", key
, cname
);
750 if (!strcmp(key
, "id")) {
751 uint8_t cid
[200] = {0};
752 res
= CborGetBinStringValue(&mapint
, cid
, sizeof(cid
), &n
);
754 PrintAndLogEx(SUCCESS
, "UserEntity id [%zu]: %s", n
, sprint_hex(cid
, n
));
757 uint8_t idbuf
[100] = {0};
760 JsonLoadBufAsHex(root
, "$.UserEntity.id", idbuf
, sizeof(idbuf
), &idbuflen
);
762 if (idbuflen
== n
&& !memcmp(idbuf
, cid
, idbuflen
)) {
763 PrintAndLogEx(SUCCESS
, "UserEntity id ( " _GREEN_("ok") " )");
765 PrintAndLogEx(ERR
, "ERROR: Wrong UserEntity id (from json: %s)", sprint_hex(idbuf
, idbuflen
));
769 res
= cbor_value_leave_container(&map
, &mapint
);
775 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 3);
778 res
= cbor_value_dup_byte_string(&map
, &ubuf
, &n
, &map
);
781 uint8_t *sign
= ubuf
;
786 PrintAndLogEx(SUCCESS
, "signature [%zu]: %s", signLen
, sprint_hex_inrow(sign
, signLen
));
788 PrintAndLogEx(SUCCESS
, "signature [%zu]: %s...", signLen
, sprint_hex(sign
, MIN(signLen
, 16)));
791 // get public key from json
792 uint8_t PublicKey
[65] = {0};
793 size_t PublicKeyLen
= 0;
794 JsonLoadBufAsHex(root
, "$.AppData.COSEPublicKey", PublicKey
, 65, &PublicKeyLen
);
796 // check ANSI X9.62 format ECDSA signature (on P-256)
797 FIDO2CheckSignature(root
, PublicKey
, sign
, signLen
, authData
, authDataLen
, verbose
);
801 // numberOfCredentials
802 res
= CborMapGetKeyById(&parser
, &map
, data
, dataLen
, 5);
804 PrintAndLogEx(SUCCESS
, "numberOfCredentials: 1 by default");
806 int64_t numberOfCredentials
= 0;
807 cbor_value_get_int64(&map
, &numberOfCredentials
);
808 PrintAndLogEx(SUCCESS
, "numberOfCredentials: %lld", (long long)numberOfCredentials
);