]>
Commit | Line | Data |
---|---|---|
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 | //----------------------------------------------------------------------------- | |
11 | // | |
12 | ||
13 | #include "fidocore.h" | |
14 | #include "emv/emvcore.h" | |
15 | #include "emv/emvjson.h" | |
16 | #include <cbor.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 "emv/dump.h" | |
26 | #include "protocols.h" | |
27 | #include "ui.h" | |
28 | #include "util.h" | |
29 | ||
30 | ||
31 | typedef struct { | |
32 | uint8_t ErrorCode; | |
33 | char *ShortDescription; | |
34 | char *Description; | |
35 | } fido2Error_t; | |
36 | ||
37 | fido2Error_t fido2Errors[] = { | |
38 | {0xFF, "n/a", "n/a"}, | |
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."}, | |
86 | }; | |
87 | ||
88 | typedef struct { | |
89 | fido2Commands Command; | |
90 | fido2PacketType PckType; | |
91 | int MemberNumber; | |
92 | char *Description; | |
93 | } fido2Desc_t; | |
94 | ||
95 | fido2Desc_t fido2CmdGetInfoRespDesc[] = { | |
96 | {fido2CmdMakeCredential, ptResponse, 0x01, "fmt"}, | |
97 | {fido2CmdMakeCredential, ptResponse, 0x02, "authData"}, | |
98 | {fido2CmdMakeCredential, ptResponse, 0x03, "attStmt"}, | |
99 | ||
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"}, | |
109 | ||
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"}, | |
115 | ||
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"}, | |
123 | ||
124 | {fido2CmdGetNextAssertion, ptResponse, 0x01, "credential"}, | |
125 | {fido2CmdGetNextAssertion, ptResponse, 0x02, "authData"}, | |
126 | {fido2CmdGetNextAssertion, ptResponse, 0x03, "signature"}, | |
127 | {fido2CmdGetNextAssertion, ptResponse, 0x04, "publicKeyCredentialUserEntity"}, | |
128 | ||
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"}, | |
135 | ||
136 | {fido2CmdClientPIN, ptResponse, 0x01, "keyAgreement"}, | |
137 | {fido2CmdClientPIN, ptResponse, 0x02, "pinToken"}, | |
138 | {fido2CmdClientPIN, ptResponse, 0x03, "retries"}, | |
139 | ||
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"}, | |
148 | ||
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"}, | |
155 | }; | |
156 | ||
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; | |
161 | ||
162 | return fido2Errors[0].Description; | |
163 | } | |
164 | ||
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; | |
171 | ||
172 | return NULL; | |
173 | } | |
174 | ||
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}; | |
177 | ||
178 | return EMVSelect(ECC_CONTACTLESS, ActivateField, LeaveFieldON, data, sizeof(data), Result, MaxResultLen, ResultLen, sw, NULL); | |
179 | } | |
180 | ||
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 | |
184 | // res = 0; | |
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 | |
192 | // res = 0; | |
193 | ||
194 | // *ResultLen += oldlen; | |
195 | // if (*ResultLen > MaxResultLen) | |
196 | // return 100; | |
197 | // } | |
198 | return res; | |
199 | } | |
200 | ||
201 | int FIDORegister(uint8_t *params, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) | |
202 | { | |
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); | |
206 | } | |
207 | ||
208 | int FIDOAuthentication(uint8_t *params, uint8_t paramslen, uint8_t controlb, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) | |
209 | { | |
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); | |
214 | } | |
215 | ||
216 | int FIDO2GetInfo(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) | |
217 | { | |
218 | uint8_t APDU[6] = {0x80, 0x10, 0x00, 0x00, 0x01, fido2CmdGetInfo}; | |
219 | return FIDOExchange(APDU, sizeof(APDU), Result, MaxResultLen, ResultLen, sw); | |
220 | } | |
221 | ||
222 | int FIDO2MakeCredential(uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) | |
223 | { | |
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); | |
228 | } | |
229 | ||
230 | int FIDO2GetAssertion(uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) | |
231 | { | |
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); | |
236 | } | |
237 | ||
238 | int FIDOCheckDERAndGetKey(uint8_t *der, size_t derLen, bool verbose, uint8_t *publicKey, size_t publicKeyMaxLen) { | |
239 | int res; | |
240 | ||
241 | // load CA's | |
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); | |
245 | if (res < 0) { | |
246 | PrintAndLog("ERROR: CA parse certificate returned -0x%x - %s", -res, ecdsa_get_error(res)); | |
247 | } | |
248 | if (verbose) | |
249 | PrintAndLog("CA load OK. %d skipped", res); | |
250 | ||
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); | |
255 | if (res) { | |
256 | PrintAndLog("ERROR: DER parse returned 0x%x - %s", (res<0)?-res:res, ecdsa_get_error(res)); | |
257 | } | |
258 | ||
259 | // get certificate info | |
260 | char linfo[300] = {0}; | |
261 | if (verbose) { | |
262 | mbedtls_x509_crt_info(linfo, sizeof(linfo), " ", &cert); | |
263 | PrintAndLog("DER certificate info:\n%s", linfo); | |
264 | } | |
265 | ||
266 | // verify certificate | |
267 | uint32_t verifyflags = 0; | |
268 | res = mbedtls_x509_crt_verify(&cert, &cacert, NULL, NULL, &verifyflags, NULL, NULL); | |
269 | if (res) { | |
270 | PrintAndLog("ERROR: DER verify returned 0x%x - %s\n", (res<0)?-res:res, ecdsa_get_error(res)); | |
271 | } else { | |
272 | PrintAndLog("Certificate OK.\n"); | |
273 | } | |
274 | ||
275 | if (verbose) { | |
276 | memset(linfo, 0x00, sizeof(linfo)); | |
277 | mbedtls_x509_crt_verify_info(linfo, sizeof(linfo), " ", verifyflags); | |
278 | PrintAndLog("Verification info:\n%s", linfo); | |
279 | } | |
280 | ||
281 | // get public key | |
282 | res = ecdsa_public_key_from_pk(&cert.pk, publicKey, publicKeyMaxLen); | |
283 | if (res) { | |
284 | PrintAndLog("ERROR: getting public key from certificate 0x%x - %s", (res<0)?-res:res, ecdsa_get_error(res)); | |
285 | } else { | |
286 | if (verbose) | |
287 | PrintAndLog("Got a public key from certificate:\n%s", sprint_hex_inrow(publicKey, 65)); | |
288 | } | |
289 | ||
290 | if (verbose) | |
291 | PrintAndLog("------------------DER-------------------"); | |
292 | ||
293 | mbedtls_x509_crt_free(&cert); | |
294 | mbedtls_x509_crt_free(&cacert); | |
295 | ||
296 | return 0; | |
297 | } | |
298 | ||
299 | #define fido_check_if(r) if ((r) != CborNoError) {return r;} else | |
300 | #define fido_check(r) if ((r) != CborNoError) return r; | |
301 | ||
302 | int FIDO2CreateMakeCredentionalReq(json_t *root, uint8_t *data, size_t maxdatalen, size_t *datalen) { | |
303 | if (datalen) | |
304 | *datalen = 0; | |
305 | if (!root || !data || !maxdatalen) | |
306 | return 1; | |
307 | ||
308 | int res; | |
309 | CborEncoder encoder; | |
310 | CborEncoder map; | |
311 | ||
312 | cbor_encoder_init(&encoder, data, maxdatalen, 0); | |
313 | ||
314 | // create main map | |
315 | res = cbor_encoder_create_map(&encoder, &map, 5); | |
316 | fido_check_if(res) { | |
317 | // clientDataHash | |
318 | res = cbor_encode_uint(&map, 1); | |
319 | fido_check_if(res) { | |
320 | res = CBOREncodeClientDataHash(root, &map); | |
321 | fido_check(res); | |
322 | } | |
323 | ||
324 | // rp | |
325 | res = cbor_encode_uint(&map, 2); | |
326 | fido_check_if(res) { | |
327 | res = CBOREncodeElm(root, "RelyingPartyEntity", &map); | |
328 | fido_check(res); | |
329 | } | |
330 | ||
331 | // user | |
332 | res = cbor_encode_uint(&map, 3); | |
333 | fido_check_if(res) { | |
334 | res = CBOREncodeElm(root, "UserEntity", &map); | |
335 | fido_check(res); | |
336 | } | |
337 | ||
338 | // pubKeyCredParams | |
339 | res = cbor_encode_uint(&map, 4); | |
340 | fido_check_if(res) { | |
341 | res = CBOREncodeElm(root, "pubKeyCredParams", &map); | |
342 | fido_check(res); | |
343 | } | |
344 | ||
345 | // options | |
346 | res = cbor_encode_uint(&map, 7); | |
347 | fido_check_if(res) { | |
348 | res = CBOREncodeElm(root, "MakeCredentialOptions", &map); | |
349 | fido_check(res); | |
350 | } | |
351 | } | |
352 | res = cbor_encoder_close_container(&encoder, &map); | |
353 | fido_check(res); | |
354 | ||
355 | size_t len = cbor_encoder_get_buffer_size(&encoder, data); | |
356 | if (datalen) | |
357 | *datalen = len; | |
358 | ||
359 | return 0; | |
360 | } | |
361 | ||
362 | bool CheckrpIdHash(json_t *json, uint8_t *hash) { | |
363 | char hashval[300] = {0}; | |
364 | uint8_t hash2[32] = {0}; | |
365 | ||
366 | JsonLoadStr(json, "$.RelyingPartyEntity.id", hashval); | |
367 | sha256hash((uint8_t *)hashval, strlen(hashval), hash2); | |
368 | ||
369 | return !memcmp(hash, hash2, 32); | |
370 | } | |
371 | ||
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) { | |
374 | int res; | |
375 | uint8_t rval[300] = {0}; | |
376 | uint8_t sval[300] = {0}; | |
377 | res = ecdsa_asn1_get_signature(sign, signLen, rval, sval); | |
378 | if (!res) { | |
379 | if (verbose) { | |
380 | PrintAndLog(" r: %s", sprint_hex(rval, 32)); | |
381 | PrintAndLog(" s: %s", sprint_hex(sval, 32)); | |
382 | } | |
383 | ||
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!"); | |
389 | return 2; | |
390 | } | |
391 | ||
392 | uint8_t xbuf[4096] = {0}; | |
393 | size_t xbuflen = 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 | |
397 | NULL, 0); | |
398 | //PrintAndLog("--xbuf(%d)[%d]: %s", res, xbuflen, sprint_hex(xbuf, xbuflen)); | |
399 | res = ecdsa_signature_verify(publickey, xbuf, xbuflen, sign, signLen); | |
400 | if (res) { | |
401 | if (res == -0x4e00) { | |
402 | PrintAndLog("Signature is NOT VALID."); | |
403 | } else { | |
404 | PrintAndLog("Other signature check error: %x %s", (res<0)?-res:res, ecdsa_get_error(res)); | |
405 | } | |
406 | return res; | |
407 | } else { | |
408 | PrintAndLog("Signature is OK."); | |
409 | } | |
410 | } else { | |
411 | PrintAndLog("Invalid signature. res=%d.", res); | |
412 | return res; | |
413 | } | |
414 | ||
415 | return 0; | |
416 | } | |
417 | ||
418 | int FIDO2MakeCredentionalParseRes(json_t *root, uint8_t *data, size_t dataLen, bool verbose, bool verbose2, bool showCBOR, bool showDERTLV) { | |
419 | CborParser parser; | |
420 | CborValue map, mapsmt; | |
421 | int res; | |
422 | char *buf; | |
423 | uint8_t *ubuf; | |
424 | size_t n; | |
425 | ||
426 | // fmt | |
427 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 1); | |
428 | if (res) | |
429 | return res; | |
430 | ||
431 | res = cbor_value_dup_text_string(&map, &buf, &n, &map); | |
432 | cbor_check(res); | |
433 | PrintAndLog("format: %s", buf); | |
434 | free(buf); | |
435 | ||
436 | // authData | |
437 | uint8_t authData[400] = {0}; | |
438 | size_t authDataLen = 0; | |
439 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 2); | |
440 | if (res) | |
441 | return res; | |
442 | res = cbor_value_dup_byte_string(&map, &ubuf, &n, &map); | |
443 | cbor_check(res); | |
444 | ||
445 | authDataLen = n; | |
446 | memcpy(authData, ubuf, authDataLen); | |
447 | ||
448 | if (verbose2) { | |
449 | PrintAndLog("authData[%d]: %s", n, sprint_hex_inrow(authData, authDataLen)); | |
450 | } else { | |
451 | PrintAndLog("authData[%d]: %s...", n, sprint_hex(authData, MIN(authDataLen, 16))); | |
452 | } | |
453 | ||
454 | PrintAndLog("RP ID Hash: %s", sprint_hex(ubuf, 32)); | |
455 | ||
456 | // check RP ID Hash | |
457 | if (CheckrpIdHash(root, ubuf)) { | |
458 | PrintAndLog("rpIdHash OK."); | |
459 | } else { | |
460 | PrintAndLog("rpIdHash ERROR!"); | |
461 | } | |
462 | ||
463 | PrintAndLog("Flags 0x%02x:", ubuf[32]); | |
464 | if (!ubuf[32]) | |
465 | PrintAndLog("none"); | |
466 | if (ubuf[32] & 0x01) | |
467 | PrintAndLog("up - user presence result"); | |
468 | if (ubuf[32] & 0x04) | |
469 | PrintAndLog("uv - user verification (fingerprint scan or a PIN or ...) result"); | |
470 | if (ubuf[32] & 0x40) | |
471 | PrintAndLog("at - attested credential data included"); | |
472 | if (ubuf[32] & 0x80) | |
473 | PrintAndLog("ed - extension data included"); | |
474 | ||
475 | uint32_t cntr = (uint32_t)bytes_to_num(&ubuf[33], 4); | |
476 | PrintAndLog("Counter: %d", cntr); | |
477 | JsonSaveInt(root, "$.AppData.Counter", cntr); | |
478 | ||
479 | // attestation data | |
480 | PrintAndLog("AAGUID: %s", sprint_hex(&ubuf[37], 16)); | |
481 | JsonSaveBufAsHexCompact(root, "$.AppData.AAGUID", &ubuf[37], 16); | |
482 | ||
483 | // Credential ID | |
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); | |
488 | ||
489 | //Credentional public key (COSE_KEY) | |
490 | uint8_t coseKey[65] = {0}; | |
491 | uint16_t cplen = n - 55 - cridlen; | |
492 | if (verbose2) { | |
493 | PrintAndLog("Credentional public key (COSE_KEY)[%d]: %s", cplen, sprint_hex_inrow(&ubuf[55 + cridlen], cplen)); | |
494 | } else { | |
495 | PrintAndLog("Credentional public key (COSE_KEY)[%d]: %s...", cplen, sprint_hex(&ubuf[55 + cridlen], MIN(cplen, 16))); | |
496 | } | |
497 | JsonSaveBufAsHexCompact(root, "$.AppData.COSE_KEY", &ubuf[55 + cridlen], cplen); | |
498 | ||
499 | if (showCBOR) { | |
500 | PrintAndLog("COSE structure:"); | |
501 | PrintAndLog("---------------- CBOR ------------------"); | |
502 | TinyCborPrintFIDOPackage(fido2COSEKey, true, &ubuf[55 + cridlen], cplen); | |
503 | PrintAndLog("---------------- CBOR ------------------"); | |
504 | } | |
505 | ||
506 | res = COSEGetECDSAKey(&ubuf[55 + cridlen], cplen, verbose, coseKey); | |
507 | if (res) { | |
508 | PrintAndLog("ERROR: Can't get COSE_KEY."); | |
509 | } else { | |
510 | PrintAndLog("COSE public key: %s", sprint_hex_inrow(coseKey, sizeof(coseKey))); | |
511 | JsonSaveBufAsHexCompact(root, "$.AppData.COSEPublicKey", coseKey, sizeof(coseKey)); | |
512 | } | |
513 | ||
514 | free(ubuf); | |
515 | ||
516 | // attStmt - we are check only as DER certificate | |
517 | int64_t alg = 0; | |
518 | uint8_t sign[128] = {0}; | |
519 | size_t signLen = 0; | |
520 | uint8_t der[4097] = {0}; | |
521 | size_t derLen = 0; | |
522 | ||
523 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 3); | |
524 | if (res) | |
525 | return res; | |
526 | ||
527 | res = cbor_value_enter_container(&map, &mapsmt); | |
528 | cbor_check(res); | |
529 | ||
530 | while (!cbor_value_at_end(&mapsmt)) { | |
531 | char key[100] = {0}; | |
532 | res = CborGetStringValue(&mapsmt, key, sizeof(key), &n); | |
533 | cbor_check(res); | |
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); | |
538 | cbor_check(res); | |
539 | } | |
540 | ||
541 | if (!strcmp(key, "sig")) { | |
542 | res = CborGetBinStringValue(&mapsmt, sign, sizeof(sign), &signLen); | |
543 | cbor_check(res); | |
544 | if (verbose2) { | |
545 | PrintAndLog("signature [%d]: %s", signLen, sprint_hex_inrow(sign, signLen)); | |
546 | } else { | |
547 | PrintAndLog("signature [%d]: %s...", signLen, sprint_hex(sign, MIN(signLen, 16))); | |
548 | } | |
549 | } | |
550 | ||
551 | if (!strcmp(key, "x5c")) { | |
552 | res = CborGetArrayBinStringValue(&mapsmt, der, sizeof(der), &derLen); | |
553 | cbor_check(res); | |
554 | if (verbose2) { | |
555 | PrintAndLog("DER certificate[%d]:\n------------------DER-------------------", derLen); | |
556 | dump_buffer_simple((const unsigned char *)der, derLen, NULL); | |
557 | PrintAndLog("\n----------------DER---------------------"); | |
558 | } else { | |
559 | PrintAndLog("DER [%d]: %s...", derLen, sprint_hex(der, MIN(derLen, 16))); | |
560 | } | |
561 | JsonSaveBufAsHexCompact(root, "$.AppData.DER", der, derLen); | |
562 | } | |
563 | } | |
564 | res = cbor_value_leave_container(&map, &mapsmt); | |
565 | cbor_check(res); | |
566 | ||
567 | uint8_t public_key[65] = {0}; | |
568 | ||
569 | // print DER certificate in TLV view | |
570 | if (showDERTLV) { | |
571 | PrintAndLog("----------------DER TLV-----------------"); | |
572 | asn1_print(der, derLen, " "); | |
573 | PrintAndLog("----------------DER TLV-----------------"); | |
574 | } | |
575 | FIDOCheckDERAndGetKey(der, derLen, verbose, public_key, sizeof(public_key)); | |
576 | JsonSaveBufAsHexCompact(root, "$.AppData.DERPublicKey", public_key, sizeof(public_key)); | |
577 | ||
578 | // check ANSI X9.62 format ECDSA signature (on P-256) | |
579 | FIDO2CheckSignature(root, public_key, sign, signLen, authData, authDataLen, verbose); | |
580 | ||
581 | return 0; | |
582 | } | |
583 | ||
584 | int FIDO2CreateGetAssertionReq(json_t *root, uint8_t *data, size_t maxdatalen, size_t *datalen, bool createAllowList) { | |
585 | if (datalen) | |
586 | *datalen = 0; | |
587 | if (!root || !data || !maxdatalen) | |
588 | return 1; | |
589 | ||
590 | int res; | |
591 | CborEncoder encoder; | |
592 | CborEncoder map, array, mapint; | |
593 | ||
594 | cbor_encoder_init(&encoder, data, maxdatalen, 0); | |
595 | ||
596 | // create main map | |
597 | res = cbor_encoder_create_map(&encoder, &map, createAllowList ? 4 : 3); | |
598 | fido_check_if(res) { | |
599 | // rpId | |
600 | res = cbor_encode_uint(&map, 1); | |
601 | fido_check_if(res) { | |
602 | res = CBOREncodeElm(root, "$.RelyingPartyEntity.id", &map); | |
603 | fido_check(res); | |
604 | } | |
605 | ||
606 | // clientDataHash | |
607 | res = cbor_encode_uint(&map, 2); | |
608 | fido_check_if(res) { | |
609 | res = CBOREncodeClientDataHash(root, &map); | |
610 | fido_check(res); | |
611 | } | |
612 | ||
613 | // allowList | |
614 | if (createAllowList) { | |
615 | res = cbor_encode_uint(&map, 3); | |
616 | fido_check_if(res) { | |
617 | res = cbor_encoder_create_array(&map, &array, 1); | |
618 | fido_check_if(res) { | |
619 | res = cbor_encoder_create_map(&array, &mapint, 2); | |
620 | fido_check_if(res) { | |
621 | res = cbor_encode_text_stringz(&mapint, "type"); | |
622 | fido_check(res); | |
623 | ||
624 | res = cbor_encode_text_stringz(&mapint, "public-key"); | |
625 | fido_check(res); | |
626 | ||
627 | res = cbor_encode_text_stringz(&mapint, "id"); | |
628 | fido_check(res); | |
629 | ||
630 | res = CBOREncodeElm(root, "$.AppData.CredentialId", &mapint); | |
631 | fido_check(res); | |
632 | } | |
633 | res = cbor_encoder_close_container(&array, &mapint); | |
634 | fido_check(res); | |
635 | } | |
636 | res = cbor_encoder_close_container(&map, &array); | |
637 | fido_check(res); | |
638 | } | |
639 | } | |
640 | ||
641 | // options | |
642 | res = cbor_encode_uint(&map, 5); | |
643 | fido_check_if(res) { | |
644 | res = CBOREncodeElm(root, "GetAssertionOptions", &map); | |
645 | fido_check(res); | |
646 | } | |
647 | } | |
648 | res = cbor_encoder_close_container(&encoder, &map); | |
649 | fido_check(res); | |
650 | ||
651 | size_t len = cbor_encoder_get_buffer_size(&encoder, data); | |
652 | if (datalen) | |
653 | *datalen = len; | |
654 | ||
655 | return 0; | |
656 | } | |
657 | ||
658 | int FIDO2GetAssertionParseRes(json_t *root, uint8_t *data, size_t dataLen, bool verbose, bool verbose2, bool showCBOR) { | |
659 | CborParser parser; | |
660 | CborValue map, mapint; | |
661 | int res; | |
662 | uint8_t *ubuf; | |
663 | size_t n; | |
664 | ||
665 | // credential | |
666 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 1); | |
667 | if (res) | |
668 | return res; | |
669 | ||
670 | res = cbor_value_enter_container(&map, &mapint); | |
671 | cbor_check(res); | |
672 | ||
673 | while (!cbor_value_at_end(&mapint)) { | |
674 | char key[100] = {0}; | |
675 | res = CborGetStringValue(&mapint, key, sizeof(key), &n); | |
676 | cbor_check(res); | |
677 | ||
678 | if (!strcmp(key, "type")) { | |
679 | char ctype[200] = {0}; | |
680 | res = CborGetStringValue(&mapint, ctype, sizeof(ctype), &n); | |
681 | cbor_check(res); | |
682 | PrintAndLog("credential type: %s", ctype); | |
683 | } | |
684 | ||
685 | if (!strcmp(key, "id")) { | |
686 | uint8_t cid[200] = {0}; | |
687 | res = CborGetBinStringValue(&mapint, cid, sizeof(cid), &n); | |
688 | cbor_check(res); | |
689 | PrintAndLog("credential id [%d]: %s", n, sprint_hex(cid, n)); | |
690 | } | |
691 | } | |
692 | res = cbor_value_leave_container(&map, &mapint); | |
693 | cbor_check(res); | |
694 | ||
695 | // authData | |
696 | uint8_t authData[400] = {0}; | |
697 | size_t authDataLen = 0; | |
698 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 2); | |
699 | if (res) | |
700 | return res; | |
701 | res = cbor_value_dup_byte_string(&map, &ubuf, &n, &map); | |
702 | cbor_check(res); | |
703 | ||
704 | authDataLen = n; | |
705 | memcpy(authData, ubuf, authDataLen); | |
706 | ||
707 | if (verbose2) { | |
708 | PrintAndLog("authData[%d]: %s", n, sprint_hex_inrow(authData, authDataLen)); | |
709 | } else { | |
710 | PrintAndLog("authData[%d]: %s...", n, sprint_hex(authData, MIN(authDataLen, 16))); | |
711 | } | |
712 | ||
713 | PrintAndLog("RP ID Hash: %s", sprint_hex(ubuf, 32)); | |
714 | ||
715 | // check RP ID Hash | |
716 | if (CheckrpIdHash(root, ubuf)) { | |
717 | PrintAndLog("rpIdHash OK."); | |
718 | } else { | |
719 | PrintAndLog("rpIdHash ERROR!"); | |
720 | } | |
721 | ||
722 | PrintAndLog("Flags 0x%02x:", ubuf[32]); | |
723 | if (!ubuf[32]) | |
724 | PrintAndLog("none"); | |
725 | if (ubuf[32] & 0x01) | |
726 | PrintAndLog("up - user presence result"); | |
727 | if (ubuf[32] & 0x04) | |
728 | PrintAndLog("uv - user verification (fingerprint scan or a PIN or ...) result"); | |
729 | if (ubuf[32] & 0x40) | |
730 | PrintAndLog("at - attested credential data included"); | |
731 | if (ubuf[32] & 0x80) | |
732 | PrintAndLog("ed - extension data included"); | |
733 | ||
734 | uint32_t cntr = (uint32_t)bytes_to_num(&ubuf[33], 4); | |
735 | PrintAndLog("Counter: %d", cntr); | |
736 | JsonSaveInt(root, "$.AppData.Counter", cntr); | |
737 | ||
738 | free(ubuf); | |
739 | ||
740 | // publicKeyCredentialUserEntity | |
741 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 4); | |
742 | if (res) { | |
743 | PrintAndLog("UserEntity n/a"); | |
744 | } else { | |
745 | res = cbor_value_enter_container(&map, &mapint); | |
746 | cbor_check(res); | |
747 | ||
748 | while (!cbor_value_at_end(&mapint)) { | |
749 | char key[100] = {0}; | |
750 | res = CborGetStringValue(&mapint, key, sizeof(key), &n); | |
751 | cbor_check(res); | |
752 | ||
753 | if (!strcmp(key, "name") || !strcmp(key, "displayName")) { | |
754 | char cname[200] = {0}; | |
755 | res = CborGetStringValue(&mapint, cname, sizeof(cname), &n); | |
756 | cbor_check(res); | |
757 | PrintAndLog("UserEntity %s: %s", key, cname); | |
758 | } | |
759 | ||
760 | if (!strcmp(key, "id")) { | |
761 | uint8_t cid[200] = {0}; | |
762 | res = CborGetBinStringValue(&mapint, cid, sizeof(cid), &n); | |
763 | cbor_check(res); | |
764 | PrintAndLog("UserEntity id [%d]: %s", n, sprint_hex(cid, n)); | |
765 | ||
766 | // check | |
767 | uint8_t idbuf[100] = {0}; | |
768 | size_t idbuflen; | |
769 | ||
770 | JsonLoadBufAsHex(root, "$.UserEntity.id", idbuf, sizeof(idbuf), &idbuflen); | |
771 | ||
772 | if (idbuflen == n && !memcmp(idbuf, cid, idbuflen)) { | |
773 | PrintAndLog("UserEntity id OK."); | |
774 | } else { | |
775 | PrintAndLog("ERROR: Wrong UserEntity id (from json: %s)", sprint_hex(idbuf, idbuflen)); | |
776 | } | |
777 | } | |
778 | } | |
779 | res = cbor_value_leave_container(&map, &mapint); | |
780 | cbor_check(res); | |
781 | } | |
782 | ||
783 | ||
784 | // signature | |
785 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 3); | |
786 | if (res) | |
787 | return res; | |
788 | res = cbor_value_dup_byte_string(&map, &ubuf, &n, &map); | |
789 | cbor_check(res); | |
790 | ||
791 | uint8_t *sign = ubuf; | |
792 | size_t signLen = n; | |
793 | ||
794 | cbor_check(res); | |
795 | if (verbose2) { | |
796 | PrintAndLog("signature [%d]: %s", signLen, sprint_hex_inrow(sign, signLen)); | |
797 | } else { | |
798 | PrintAndLog("signature [%d]: %s...", signLen, sprint_hex(sign, MIN(signLen, 16))); | |
799 | } | |
800 | ||
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); | |
805 | ||
806 | // check ANSI X9.62 format ECDSA signature (on P-256) | |
807 | FIDO2CheckSignature(root, PublicKey, sign, signLen, authData, authDataLen, verbose); | |
808 | ||
809 | free(ubuf); | |
810 | ||
811 | // numberOfCredentials | |
812 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 5); | |
813 | if (res) { | |
814 | PrintAndLog("numberOfCredentials: 1 by default"); | |
815 | } else { | |
816 | int64_t numberOfCredentials = 0; | |
817 | cbor_value_get_int64(&map, &numberOfCredentials); | |
818 | PrintAndLog("numberOfCredentials: %lld", (long long)numberOfCredentials); | |
819 | } | |
820 | ||
821 | return 0; | |
822 | } |