textual
[RRG-proxmark3.git] / client / src / fido / fidocore.c
blob1529d9172846bc11d29c71b86285f2bf5131106b
1 //-----------------------------------------------------------------------------
2 // Copyright (C) 2018 Merlok
3 //
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
6 // the license.
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 //-----------------------------------------------------------------------------
13 #include "fidocore.h"
15 #include "commonutil.h" // ARRAYLEN
17 #include "iso7816/iso7816core.h"
18 #include "emv/emvjson.h"
19 #include "cbortools.h"
20 #include "x509_crt.h"
21 #include "crypto/asn1utils.h"
22 #include "crypto/libpcrypto.h"
23 #include "additional_ca.h"
24 #include "cose.h"
25 #include "ui.h"
26 #include "util.h"
28 typedef struct {
29 uint8_t ErrorCode;
30 const char *ShortDescription;
31 const char *Description;
32 } fido2Error_t;
34 fido2Error_t fido2Errors[] = {
35 {0xFF, "n/a", "n/a"},
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."},
85 typedef struct {
86 fido2Commands Command;
87 fido2PacketType PckType;
88 int MemberNumber;
89 const char *Description;
90 } fido2Desc_t;
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;
169 return NULL;
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
181 res = 0;
182 // software chaining
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
187 res = 0;
189 *ResultLen += oldlen;
190 if (*ResultLen > MaxResultLen)
191 return 100;
193 return res;
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) {
224 int res;
226 // load CA's
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);
230 if (res < 0) {
231 PrintAndLogEx(ERR, "ERROR: CA parse certificate returned -0x%x - %s", -res, ecdsa_get_error(res));
233 if (verbose)
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);
240 if (res) {
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};
246 if (verbose) {
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);
254 if (res) {
255 PrintAndLogEx(ERR, "ERROR: DER verify returned 0x%x - %s\n", (res < 0) ? -res : res, ecdsa_get_error(res));
256 } else {
257 PrintAndLogEx(SUCCESS, "Certificate ( " _GREEN_("ok") " )\n");
260 if (verbose) {
261 memset(linfo, 0x00, sizeof(linfo));
262 mbedtls_x509_crt_verify_info(linfo, sizeof(linfo), " ", verifyflags);
263 PrintAndLogEx(SUCCESS, "Verification info:\n%s", linfo);
266 // get public key
267 res = ecdsa_public_key_from_pk(&cert.pk, MBEDTLS_ECP_DP_SECP256R1, publicKey, publicKeyMaxLen);
268 if (res) {
269 PrintAndLogEx(ERR, "ERROR: getting public key from certificate 0x%x - %s", (res < 0) ? -res : res, ecdsa_get_error(res));
270 } else {
271 if (verbose)
272 PrintAndLogEx(SUCCESS, "Got a public key from certificate:\n%s", sprint_hex_inrow(publicKey, 65));
275 if (verbose)
276 PrintAndLogEx(INFO, "------------------DER-------------------");
278 mbedtls_x509_crt_free(&cert);
279 mbedtls_x509_crt_free(&cacert);
281 return 0;
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) {
288 if (datalen)
289 *datalen = 0;
290 if (!root || !data || !maxdatalen)
291 return 1;
293 int res;
294 CborEncoder encoder;
295 CborEncoder map;
297 cbor_encoder_init(&encoder, data, maxdatalen, 0);
299 // create main map
300 res = cbor_encoder_create_map(&encoder, &map, 5);
301 fido_check_if(res) {
302 // clientDataHash
303 res = cbor_encode_uint(&map, 1);
304 fido_check_if(res) {
305 res = CBOREncodeClientDataHash(root, &map);
306 fido_check(res);
309 // rp
310 res = cbor_encode_uint(&map, 2);
311 fido_check_if(res) {
312 res = CBOREncodeElm(root, "RelyingPartyEntity", &map);
313 fido_check(res);
316 // user
317 res = cbor_encode_uint(&map, 3);
318 fido_check_if(res) {
319 res = CBOREncodeElm(root, "UserEntity", &map);
320 fido_check(res);
323 // pubKeyCredParams
324 res = cbor_encode_uint(&map, 4);
325 fido_check_if(res) {
326 res = CBOREncodeElm(root, "pubKeyCredParams", &map);
327 fido_check(res);
330 // options
331 res = cbor_encode_uint(&map, 7);
332 fido_check_if(res) {
333 res = CBOREncodeElm(root, "MakeCredentialOptions", &map);
334 fido_check(res);
337 res = cbor_encoder_close_container(&encoder, &map);
338 fido_check(res);
340 size_t len = cbor_encoder_get_buffer_size(&encoder, data);
341 if (datalen)
342 *datalen = len;
344 return 0;
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);
353 if (res)
354 return false;
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) {
367 if (verbose) {
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!");
377 return 2;
380 uint8_t xbuf[4096] = {0};
381 size_t xbuflen = 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
385 NULL, 0);
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);
389 if (res) {
390 if (res == MBEDTLS_ERR_ECP_VERIFY_FAILED) {
391 PrintAndLogEx(WARNING, "Signature is ( " _RED_("not valid") " )");
392 } else {
393 PrintAndLogEx(WARNING, "Other signature check error: %x %s", (res < 0) ? -res : res, ecdsa_get_error(res));
395 return res;
396 } else {
397 PrintAndLogEx(SUCCESS, "Signature is ( " _GREEN_("ok") " )");
399 } else {
400 PrintAndLogEx(ERR, "Invalid signature. res = %d.", res);
401 return res;
404 return 0;
407 int FIDO2MakeCredentionalParseRes(json_t *root, uint8_t *data, size_t dataLen, bool verbose, bool verbose2, bool showCBOR, bool showDERTLV) {
408 CborParser parser;
409 CborValue map, mapsmt;
410 int res;
411 char *buf;
412 uint8_t *ubuf;
413 size_t n;
415 // fmt
416 res = CborMapGetKeyById(&parser, &map, data, dataLen, 1);
417 if (res)
418 return res;
420 res = cbor_value_dup_text_string(&map, &buf, &n, &map);
421 cbor_check(res);
422 PrintAndLogEx(INFO, "format: %s", buf);
423 free(buf);
425 // authData
426 uint8_t authData[400] = {0};
427 size_t authDataLen = 0;
428 res = CborMapGetKeyById(&parser, &map, data, dataLen, 2);
429 if (res)
430 return res;
431 res = cbor_value_dup_byte_string(&map, &ubuf, &n, &map);
432 cbor_check(res);
434 authDataLen = n;
435 memcpy(authData, ubuf, authDataLen);
437 if (verbose2) {
438 PrintAndLogEx(INFO, "authData[%zu]: %s", n, sprint_hex_inrow(authData, authDataLen));
439 } else {
440 PrintAndLogEx(INFO, "authData[%zu]: %s...", n, sprint_hex(authData, MIN(authDataLen, 16)));
443 PrintAndLogEx(INFO, "RP ID Hash: %s", sprint_hex(ubuf, 32));
445 // check RP ID Hash
446 if (CheckrpIdHash(root, ubuf)) {
447 PrintAndLogEx(SUCCESS, "rpIdHash ( " _GREEN_("ok")" )");
448 } else {
449 PrintAndLogEx(ERR, "rpIdHash " _RED_("ERROR!!"));
452 PrintAndLogEx(INFO, "Flags 0x%02x:", ubuf[32]);
453 if (!ubuf[32])
454 PrintAndLogEx(SUCCESS, "none");
455 if (ubuf[32] & 0x01)
456 PrintAndLogEx(SUCCESS, "up - user presence result");
457 if (ubuf[32] & 0x04)
458 PrintAndLogEx(SUCCESS, "uv - user verification (fingerprint scan or a PIN or ...) result");
459 if (ubuf[32] & 0x40)
460 PrintAndLogEx(SUCCESS, "at - attested credential data included");
461 if (ubuf[32] & 0x80)
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);
468 // attestation data
469 PrintAndLogEx(SUCCESS, "AAGUID: %s", sprint_hex(&ubuf[37], 16));
470 JsonSaveBufAsHexCompact(root, "$.AppData.AAGUID", &ubuf[37], 16);
472 // Credential ID
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;
481 if (verbose2) {
482 PrintAndLogEx(SUCCESS, "Credentional public key (COSE_KEY)[%d]: %s", cplen, sprint_hex_inrow(&ubuf[55 + cridlen], cplen));
483 } else {
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);
488 if (showCBOR) {
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);
496 if (res) {
497 PrintAndLogEx(ERR, "ERROR: Can't get COSE_KEY.");
498 } else {
499 PrintAndLogEx(SUCCESS, "COSE public key: %s", sprint_hex_inrow(coseKey, sizeof(coseKey)));
500 JsonSaveBufAsHexCompact(root, "$.AppData.COSEPublicKey", coseKey, sizeof(coseKey));
503 free(ubuf);
505 // attStmt - we are check only as DER certificate
506 int64_t alg = 0;
507 uint8_t sign[128] = {0};
508 size_t signLen = 0;
509 uint8_t der[4097] = {0};
510 size_t derLen = 0;
512 res = CborMapGetKeyById(&parser, &map, data, dataLen, 3);
513 if (res)
514 return res;
516 res = cbor_value_enter_container(&map, &mapsmt);
517 cbor_check(res);
519 while (!cbor_value_at_end(&mapsmt)) {
520 char key[100] = {0};
521 res = CborGetStringValue(&mapsmt, key, sizeof(key), &n);
522 cbor_check(res);
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);
527 cbor_check(res);
530 if (!strcmp(key, "sig")) {
531 res = CborGetBinStringValue(&mapsmt, sign, sizeof(sign), &signLen);
532 cbor_check(res);
533 if (verbose2) {
534 PrintAndLogEx(INFO, "signature [%zu]: %s", signLen, sprint_hex_inrow(sign, signLen));
535 } else {
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);
542 cbor_check(res);
543 if (verbose2) {
544 PrintAndLogEx(INFO, "DER certificate[%zu]:", derLen);
545 PrintAndLogEx(INFO, "------------------DER-------------------");
546 PrintAndLogEx(INFO, "%s", sprint_hex(der, derLen));
547 PrintAndLogEx(INFO, "----------------DER---------------------");
548 } else {
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);
555 cbor_check(res);
557 uint8_t public_key[65] = {0};
559 // print DER certificate in TLV view
560 if (showDERTLV) {
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);
571 return 0;
574 int FIDO2CreateGetAssertionReq(json_t *root, uint8_t *data, size_t maxdatalen, size_t *datalen, bool createAllowList) {
575 if (datalen)
576 *datalen = 0;
577 if (!root || !data || !maxdatalen)
578 return 1;
580 int res;
581 CborEncoder encoder;
582 CborEncoder map, array, mapint;
584 cbor_encoder_init(&encoder, data, maxdatalen, 0);
586 // create main map
587 res = cbor_encoder_create_map(&encoder, &map, createAllowList ? 4 : 3);
588 fido_check_if(res) {
589 // rpId
590 res = cbor_encode_uint(&map, 1);
591 fido_check_if(res) {
592 res = CBOREncodeElm(root, "$.RelyingPartyEntity.id", &map);
593 fido_check(res);
596 // clientDataHash
597 res = cbor_encode_uint(&map, 2);
598 fido_check_if(res) {
599 res = CBOREncodeClientDataHash(root, &map);
600 fido_check(res);
603 // allowList
604 if (createAllowList) {
605 res = cbor_encode_uint(&map, 3);
606 fido_check_if(res) {
607 res = cbor_encoder_create_array(&map, &array, 1);
608 fido_check_if(res) {
609 res = cbor_encoder_create_map(&array, &mapint, 2);
610 fido_check_if(res) {
611 res = cbor_encode_text_stringz(&mapint, "type");
612 fido_check(res);
614 res = cbor_encode_text_stringz(&mapint, "public-key");
615 fido_check(res);
617 res = cbor_encode_text_stringz(&mapint, "id");
618 fido_check(res);
620 res = CBOREncodeElm(root, "$.AppData.CredentialId", &mapint);
621 fido_check(res);
623 res = cbor_encoder_close_container(&array, &mapint);
624 fido_check(res);
626 res = cbor_encoder_close_container(&map, &array);
627 fido_check(res);
631 // options
632 res = cbor_encode_uint(&map, 5);
633 fido_check_if(res) {
634 res = CBOREncodeElm(root, "GetAssertionOptions", &map);
635 fido_check(res);
638 res = cbor_encoder_close_container(&encoder, &map);
639 fido_check(res);
641 size_t len = cbor_encoder_get_buffer_size(&encoder, data);
642 if (datalen)
643 *datalen = len;
645 return 0;
648 int FIDO2GetAssertionParseRes(json_t *root, uint8_t *data, size_t dataLen, bool verbose, bool verbose2, bool showCBOR) {
649 CborParser parser;
650 CborValue map, mapint;
651 int res;
652 uint8_t *ubuf;
653 size_t n;
655 // credential
656 res = CborMapGetKeyById(&parser, &map, data, dataLen, 1);
657 if (res)
658 return res;
660 res = cbor_value_enter_container(&map, &mapint);
661 cbor_check(res);
663 while (!cbor_value_at_end(&mapint)) {
664 char key[100] = {0};
665 res = CborGetStringValue(&mapint, key, sizeof(key), &n);
666 cbor_check(res);
668 if (!strcmp(key, "type")) {
669 char ctype[200] = {0};
670 res = CborGetStringValue(&mapint, ctype, sizeof(ctype), &n);
671 cbor_check(res);
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);
678 cbor_check(res);
679 PrintAndLogEx(SUCCESS, "credential id [%zu]: %s", n, sprint_hex(cid, n));
682 res = cbor_value_leave_container(&map, &mapint);
683 cbor_check(res);
685 // authData
686 uint8_t authData[400] = {0};
687 size_t authDataLen = 0;
688 res = CborMapGetKeyById(&parser, &map, data, dataLen, 2);
689 if (res)
690 return res;
691 res = cbor_value_dup_byte_string(&map, &ubuf, &n, &map);
692 cbor_check(res);
694 authDataLen = n;
695 memcpy(authData, ubuf, authDataLen);
697 if (verbose2) {
698 PrintAndLogEx(INFO, "authData[%zu]: %s", n, sprint_hex_inrow(authData, authDataLen));
699 } else {
700 PrintAndLogEx(INFO, "authData[%zu]: %s...", n, sprint_hex(authData, MIN(authDataLen, 16)));
703 PrintAndLogEx(INFO, "RP ID Hash: %s", sprint_hex(ubuf, 32));
705 // check RP ID Hash
706 if (CheckrpIdHash(root, ubuf)) {
707 PrintAndLogEx(SUCCESS, "rpIdHash ( " _GREEN_("ok")" )");
708 } else {
709 PrintAndLogEx(ERR, "rpIdHash " _RED_("ERROR!!"));
712 PrintAndLogEx(INFO, "Flags 0x%02x:", ubuf[32]);
713 if (!ubuf[32])
714 PrintAndLogEx(SUCCESS, "none");
715 if (ubuf[32] & 0x01)
716 PrintAndLogEx(SUCCESS, "up - user presence result");
717 if (ubuf[32] & 0x04)
718 PrintAndLogEx(SUCCESS, "uv - user verification (fingerprint scan or a PIN or ...) result");
719 if (ubuf[32] & 0x40)
720 PrintAndLogEx(SUCCESS, "at - attested credential data included");
721 if (ubuf[32] & 0x80)
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);
728 free(ubuf);
730 // publicKeyCredentialUserEntity
731 res = CborMapGetKeyById(&parser, &map, data, dataLen, 4);
732 if (res) {
733 PrintAndLogEx(SUCCESS, "UserEntity n/a");
734 } else {
735 res = cbor_value_enter_container(&map, &mapint);
736 cbor_check(res);
738 while (!cbor_value_at_end(&mapint)) {
739 char key[100] = {0};
740 res = CborGetStringValue(&mapint, key, sizeof(key), &n);
741 cbor_check(res);
743 if (!strcmp(key, "name") || !strcmp(key, "displayName")) {
744 char cname[200] = {0};
745 res = CborGetStringValue(&mapint, cname, sizeof(cname), &n);
746 cbor_check(res);
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);
753 cbor_check(res);
754 PrintAndLogEx(SUCCESS, "UserEntity id [%zu]: %s", n, sprint_hex(cid, n));
756 // check
757 uint8_t idbuf[100] = {0};
758 size_t idbuflen;
760 JsonLoadBufAsHex(root, "$.UserEntity.id", idbuf, sizeof(idbuf), &idbuflen);
762 if (idbuflen == n && !memcmp(idbuf, cid, idbuflen)) {
763 PrintAndLogEx(SUCCESS, "UserEntity id ( " _GREEN_("ok") " )");
764 } else {
765 PrintAndLogEx(ERR, "ERROR: Wrong UserEntity id (from json: %s)", sprint_hex(idbuf, idbuflen));
769 res = cbor_value_leave_container(&map, &mapint);
770 cbor_check(res);
774 // signature
775 res = CborMapGetKeyById(&parser, &map, data, dataLen, 3);
776 if (res)
777 return res;
778 res = cbor_value_dup_byte_string(&map, &ubuf, &n, &map);
779 cbor_check(res);
781 uint8_t *sign = ubuf;
782 size_t signLen = n;
784 cbor_check(res);
785 if (verbose2) {
786 PrintAndLogEx(SUCCESS, "signature [%zu]: %s", signLen, sprint_hex_inrow(sign, signLen));
787 } else {
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);
799 free(ubuf);
801 // numberOfCredentials
802 res = CborMapGetKeyById(&parser, &map, data, dataLen, 5);
803 if (res) {
804 PrintAndLogEx(SUCCESS, "numberOfCredentials: 1 by default");
805 } else {
806 int64_t numberOfCredentials = 0;
807 cbor_value_get_int64(&map, &numberOfCredentials);
808 PrintAndLogEx(SUCCESS, "numberOfCredentials: %lld", (long long)numberOfCredentials);
811 return 0;