]>
Commit | Line | Data |
---|---|---|
0bb51450 OM |
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 | ||
26 | typedef struct { | |
27 | uint8_t ErrorCode; | |
28 | char *ShortDescription; | |
29 | char *Description; | |
30 | } fido2Error_t; | |
31 | ||
32 | fido2Error_t fido2Errors[] = { | |
33 | {0xFF, "n/a", "n/a"}, | |
34 | {0x00, "CTAP1_ERR_SUCCESS", "Indicates successful response."}, | |
35 | {0x01, "CTAP1_ERR_INVALID_COMMAND", "The command is not a valid CTAP command."}, | |
36 | {0x02, "CTAP1_ERR_INVALID_PARAMETER", "The command included an invalid parameter."}, | |
37 | {0x03, "CTAP1_ERR_INVALID_LENGTH", "Invalid message or item length."}, | |
38 | {0x04, "CTAP1_ERR_INVALID_SEQ", "Invalid message sequencing."}, | |
39 | {0x05, "CTAP1_ERR_TIMEOUT", "Message timed out."}, | |
40 | {0x06, "CTAP1_ERR_CHANNEL_BUSY", "Channel busy."}, | |
41 | {0x0A, "CTAP1_ERR_LOCK_REQUIRED", "Command requires channel lock."}, | |
42 | {0x0B, "CTAP1_ERR_INVALID_CHANNEL", "Command not allowed on this cid."}, | |
43 | {0x10, "CTAP2_ERR_CBOR_PARSING", "Error while parsing CBOR."}, | |
44 | {0x11, "CTAP2_ERR_CBOR_UNEXPECTED_TYPE", "Invalid/unexpected CBOR error."}, | |
45 | {0x12, "CTAP2_ERR_INVALID_CBOR", "Error when parsing CBOR."}, | |
46 | {0x13, "CTAP2_ERR_INVALID_CBOR_TYPE", "Invalid or unexpected CBOR type."}, | |
47 | {0x14, "CTAP2_ERR_MISSING_PARAMETER", "Missing non-optional parameter."}, | |
48 | {0x15, "CTAP2_ERR_LIMIT_EXCEEDED", "Limit for number of items exceeded."}, | |
49 | {0x16, "CTAP2_ERR_UNSUPPORTED_EXTENSION", "Unsupported extension."}, | |
50 | {0x17, "CTAP2_ERR_TOO_MANY_ELEMENTS", "Limit for number of items exceeded."}, | |
51 | {0x18, "CTAP2_ERR_EXTENSION_NOT_SUPPORTED", "Unsupported extension."}, | |
52 | {0x19, "CTAP2_ERR_CREDENTIAL_EXCLUDED", "Valid credential found in the exludeList."}, | |
53 | {0x20, "CTAP2_ERR_CREDENTIAL_NOT_VALID", "Credential not valid for authenticator."}, | |
54 | {0x21, "CTAP2_ERR_PROCESSING", "Processing (Lengthy operation is in progress)."}, | |
55 | {0x22, "CTAP2_ERR_INVALID_CREDENTIAL", "Credential not valid for the authenticator."}, | |
56 | {0x23, "CTAP2_ERR_USER_ACTION_PENDING", "Authentication is waiting for user interaction."}, | |
57 | {0x24, "CTAP2_ERR_OPERATION_PENDING", "Processing, lengthy operation is in progress."}, | |
58 | {0x25, "CTAP2_ERR_NO_OPERATIONS", "No request is pending."}, | |
59 | {0x26, "CTAP2_ERR_UNSUPPORTED_ALGORITHM", "Authenticator does not support requested algorithm."}, | |
60 | {0x27, "CTAP2_ERR_OPERATION_DENIED", "Not authorized for requested operation."}, | |
61 | {0x28, "CTAP2_ERR_KEY_STORE_FULL", "Internal key storage is full."}, | |
62 | {0x29, "CTAP2_ERR_NOT_BUSY", "Authenticator cannot cancel as it is not busy."}, | |
63 | {0x2A, "CTAP2_ERR_NO_OPERATION_PENDING", "No outstanding operations."}, | |
64 | {0x2B, "CTAP2_ERR_UNSUPPORTED_OPTION", "Unsupported option."}, | |
65 | {0x2C, "CTAP2_ERR_INVALID_OPTION", "Unsupported option."}, | |
66 | {0x2D, "CTAP2_ERR_KEEPALIVE_CANCEL", "Pending keep alive was cancelled."}, | |
67 | {0x2E, "CTAP2_ERR_NO_CREDENTIALS", "No valid credentials provided."}, | |
68 | {0x2F, "CTAP2_ERR_USER_ACTION_TIMEOUT", "Timeout waiting for user interaction."}, | |
69 | {0x30, "CTAP2_ERR_NOT_ALLOWED", "Continuation command, such as, authenticatorGetNextAssertion not allowed."}, | |
70 | {0x31, "CTAP2_ERR_PIN_INVALID", "PIN Blocked."}, | |
71 | {0x32, "CTAP2_ERR_PIN_BLOCKED", "PIN Blocked."}, | |
72 | {0x33, "CTAP2_ERR_PIN_AUTH_INVALID", "PIN authentication,pinAuth, verification failed."}, | |
73 | {0x34, "CTAP2_ERR_PIN_AUTH_BLOCKED", "PIN authentication,pinAuth, blocked. Requires power recycle to reset."}, | |
74 | {0x35, "CTAP2_ERR_PIN_NOT_SET", "No PIN has been set."}, | |
75 | {0x36, "CTAP2_ERR_PIN_REQUIRED", "PIN is required for the selected operation."}, | |
76 | {0x37, "CTAP2_ERR_PIN_POLICY_VIOLATION", "PIN policy violation. Currently only enforces minimum length."}, | |
77 | {0x38, "CTAP2_ERR_PIN_TOKEN_EXPIRED", "pinToken expired on authenticator."}, | |
78 | {0x39, "CTAP2_ERR_REQUEST_TOO_LARGE", "Authenticator cannot handle this request due to memory constraints."}, | |
79 | {0x7F, "CTAP1_ERR_OTHER", "Other unspecified error."}, | |
80 | {0xDF, "CTAP2_ERR_SPEC_LAST", "CTAP 2 spec last error."}, | |
81 | }; | |
82 | ||
83 | typedef struct { | |
84 | fido2Commands Command; | |
85 | fido2PacketType PckType; | |
86 | int MemberNumber; | |
87 | char *Description; | |
88 | } fido2Desc_t; | |
89 | ||
90 | fido2Desc_t fido2CmdGetInfoRespDesc[] = { | |
91 | {fido2CmdMakeCredential, ptResponse, 0x01, "fmt"}, | |
92 | {fido2CmdMakeCredential, ptResponse, 0x02, "authData"}, | |
93 | {fido2CmdMakeCredential, ptResponse, 0x03, "attStmt"}, | |
94 | ||
95 | {fido2CmdMakeCredential, ptQuery, 0x01, "clientDataHash"}, | |
96 | {fido2CmdMakeCredential, ptQuery, 0x02, "rp"}, | |
97 | {fido2CmdMakeCredential, ptQuery, 0x03, "user"}, | |
98 | {fido2CmdMakeCredential, ptQuery, 0x04, "pubKeyCredParams"}, | |
99 | {fido2CmdMakeCredential, ptQuery, 0x05, "excludeList"}, | |
100 | {fido2CmdMakeCredential, ptQuery, 0x06, "extensions"}, | |
101 | {fido2CmdMakeCredential, ptQuery, 0x07, "options"}, | |
102 | {fido2CmdMakeCredential, ptQuery, 0x08, "pinAuth"}, | |
103 | {fido2CmdMakeCredential, ptQuery, 0x09, "pinProtocol"}, | |
104 | ||
105 | {fido2CmdGetAssertion, ptResponse, 0x01, "credential"}, | |
106 | {fido2CmdGetAssertion, ptResponse, 0x02, "authData"}, | |
107 | {fido2CmdGetAssertion, ptResponse, 0x03, "signature"}, | |
108 | {fido2CmdGetAssertion, ptResponse, 0x04, "publicKeyCredentialUserEntity"}, | |
109 | {fido2CmdGetAssertion, ptResponse, 0x05, "numberOfCredentials"}, | |
110 | ||
111 | {fido2CmdGetAssertion, ptQuery, 0x01, "rpId"}, | |
112 | {fido2CmdGetAssertion, ptQuery, 0x02, "clientDataHash"}, | |
113 | {fido2CmdGetAssertion, ptQuery, 0x03, "allowList"}, | |
114 | {fido2CmdGetAssertion, ptQuery, 0x04, "extensions"}, | |
115 | {fido2CmdGetAssertion, ptQuery, 0x05, "options"}, | |
116 | {fido2CmdGetAssertion, ptQuery, 0x06, "pinAuth"}, | |
117 | {fido2CmdGetAssertion, ptQuery, 0x07, "pinProtocol"}, | |
118 | ||
119 | {fido2CmdGetNextAssertion, ptResponse, 0x01, "credential"}, | |
120 | {fido2CmdGetNextAssertion, ptResponse, 0x02, "authData"}, | |
121 | {fido2CmdGetNextAssertion, ptResponse, 0x03, "signature"}, | |
122 | {fido2CmdGetNextAssertion, ptResponse, 0x04, "publicKeyCredentialUserEntity"}, | |
123 | ||
124 | {fido2CmdGetInfo, ptResponse, 0x01, "versions"}, | |
125 | {fido2CmdGetInfo, ptResponse, 0x02, "extensions"}, | |
126 | {fido2CmdGetInfo, ptResponse, 0x03, "aaguid"}, | |
127 | {fido2CmdGetInfo, ptResponse, 0x04, "options"}, | |
128 | {fido2CmdGetInfo, ptResponse, 0x05, "maxMsgSize"}, | |
129 | {fido2CmdGetInfo, ptResponse, 0x06, "pinProtocols"}, | |
130 | ||
131 | {fido2CmdClientPIN, ptResponse, 0x01, "keyAgreement"}, | |
132 | {fido2CmdClientPIN, ptResponse, 0x02, "pinToken"}, | |
133 | {fido2CmdClientPIN, ptResponse, 0x03, "retries"}, | |
134 | ||
135 | {fido2CmdClientPIN, ptQuery, 0x01, "pinProtocol"}, | |
136 | {fido2CmdClientPIN, ptQuery, 0x02, "subCommand"}, | |
137 | {fido2CmdClientPIN, ptQuery, 0x03, "keyAgreement"}, | |
138 | {fido2CmdClientPIN, ptQuery, 0x04, "pinAuth"}, | |
139 | {fido2CmdClientPIN, ptQuery, 0x05, "newPinEnc"}, | |
140 | {fido2CmdClientPIN, ptQuery, 0x06, "pinHashEnc"}, | |
141 | {fido2CmdClientPIN, ptQuery, 0x07, "getKeyAgreement"}, | |
142 | {fido2CmdClientPIN, ptQuery, 0x08, "getRetries"}, | |
143 | ||
144 | {fido2COSEKey, ptResponse, 0x01, "kty"}, | |
145 | {fido2COSEKey, ptResponse, 0x03, "alg"}, | |
146 | {fido2COSEKey, ptResponse, -1, "crv"}, | |
147 | {fido2COSEKey, ptResponse, -2, "x - coordinate"}, | |
148 | {fido2COSEKey, ptResponse, -3, "y - coordinate"}, | |
149 | {fido2COSEKey, ptResponse, -4, "d - private key"}, | |
150 | }; | |
151 | ||
152 | char *fido2GetCmdErrorDescription(uint8_t errorCode) { | |
153 | for (int i = 0; i < sizeof(fido2Errors) / sizeof(fido2Error_t); i++) | |
154 | if (fido2Errors[i].ErrorCode == errorCode) | |
155 | return fido2Errors[i].Description; | |
156 | ||
157 | return fido2Errors[0].Description; | |
158 | } | |
159 | ||
160 | char *fido2GetCmdMemberDescription(uint8_t cmdCode, bool isResponse, int memberNum) { | |
161 | for (int i = 0; i < sizeof(fido2CmdGetInfoRespDesc) / sizeof(fido2Desc_t); i++) | |
162 | if (fido2CmdGetInfoRespDesc[i].Command == cmdCode && | |
163 | fido2CmdGetInfoRespDesc[i].PckType == (isResponse ? ptResponse : ptQuery) && | |
164 | fido2CmdGetInfoRespDesc[i].MemberNumber == memberNum ) | |
165 | return fido2CmdGetInfoRespDesc[i].Description; | |
166 | ||
167 | return NULL; | |
168 | } | |
169 | ||
170 | int FIDOSelect(bool ActivateField, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { | |
171 | uint8_t data[] = {0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01}; | |
172 | ||
8d7d7b61 | 173 | return EMVSelect(ECC_CONTACTLESS, ActivateField, LeaveFieldON, data, sizeof(data), Result, MaxResultLen, ResultLen, sw, NULL); |
0bb51450 OM |
174 | } |
175 | ||
176 | int FIDOExchange(sAPDU apdu, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { | |
8d7d7b61 | 177 | int res = EMVExchange(ECC_CONTACTLESS, true, apdu, Result, MaxResultLen, ResultLen, sw, NULL); |
0bb51450 OM |
178 | if (res == 5) // apdu result (sw) not a 0x9000 |
179 | res = 0; | |
180 | // software chaining | |
181 | while (!res && (*sw >> 8) == 0x61) { | |
182 | size_t oldlen = *ResultLen; | |
8d7d7b61 | 183 | res = EMVExchange(ECC_CONTACTLESS, true, (sAPDU){0x00, 0xC0, 0x00, 0x00, 0x00, NULL}, &Result[oldlen], MaxResultLen - oldlen, ResultLen, sw, NULL); |
0bb51450 OM |
184 | if (res == 5) // apdu result (sw) not a 0x9000 |
185 | res = 0; | |
186 | ||
187 | *ResultLen += oldlen; | |
188 | if (*ResultLen > MaxResultLen) | |
189 | return 100; | |
190 | } | |
191 | return res; | |
192 | } | |
193 | ||
194 | int FIDORegister(uint8_t *params, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { | |
195 | return FIDOExchange((sAPDU){0x00, 0x01, 0x03, 0x00, 64, params}, Result, MaxResultLen, ResultLen, sw); | |
196 | } | |
197 | ||
198 | int FIDOAuthentication(uint8_t *params, uint8_t paramslen, uint8_t controlb, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { | |
199 | return FIDOExchange((sAPDU){0x00, 0x02, controlb, 0x00, paramslen, params}, Result, MaxResultLen, ResultLen, sw); | |
200 | } | |
201 | ||
202 | int FIDO2GetInfo(uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { | |
203 | uint8_t data[] = {fido2CmdGetInfo}; | |
204 | return FIDOExchange((sAPDU){0x80, 0x10, 0x00, 0x00, sizeof(data), data}, Result, MaxResultLen, ResultLen, sw); | |
205 | } | |
206 | ||
207 | int FIDO2MakeCredential(uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { | |
208 | uint8_t data[paramslen + 1]; | |
209 | data[0] = fido2CmdMakeCredential; | |
210 | memcpy(&data[1], params, paramslen); | |
211 | return FIDOExchange((sAPDU){0x80, 0x10, 0x00, 0x00, sizeof(data), data}, Result, MaxResultLen, ResultLen, sw); | |
212 | } | |
213 | ||
214 | int FIDO2GetAssertion(uint8_t *params, uint8_t paramslen, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { | |
215 | uint8_t data[paramslen + 1]; | |
216 | data[0] = fido2CmdGetAssertion; | |
217 | memcpy(&data[1], params, paramslen); | |
218 | return FIDOExchange((sAPDU){0x80, 0x10, 0x00, 0x00, sizeof(data), data}, Result, MaxResultLen, ResultLen, sw); | |
219 | } | |
220 | ||
221 | int FIDOCheckDERAndGetKey(uint8_t *der, size_t derLen, bool verbose, uint8_t *publicKey, size_t publicKeyMaxLen) { | |
222 | int res; | |
223 | ||
224 | // load CA's | |
225 | mbedtls_x509_crt cacert; | |
226 | mbedtls_x509_crt_init(&cacert); | |
227 | res = mbedtls_x509_crt_parse(&cacert, (const unsigned char *) additional_ca_pem, additional_ca_pem_len); | |
228 | if (res < 0) { | |
229 | PrintAndLog("ERROR: CA parse certificate returned -0x%x - %s", -res, ecdsa_get_error(res)); | |
230 | } | |
231 | if (verbose) | |
232 | PrintAndLog("CA load OK. %d skipped", res); | |
233 | ||
234 | // load DER certificate from authenticator's data | |
235 | mbedtls_x509_crt cert; | |
236 | mbedtls_x509_crt_init(&cert); | |
237 | res = mbedtls_x509_crt_parse_der(&cert, der, derLen); | |
238 | if (res) { | |
239 | PrintAndLog("ERROR: DER parse returned 0x%x - %s", (res<0)?-res:res, ecdsa_get_error(res)); | |
240 | } | |
241 | ||
242 | // get certificate info | |
243 | char linfo[300] = {0}; | |
244 | if (verbose) { | |
245 | mbedtls_x509_crt_info(linfo, sizeof(linfo), " ", &cert); | |
246 | PrintAndLog("DER certificate info:\n%s", linfo); | |
247 | } | |
248 | ||
249 | // verify certificate | |
250 | uint32_t verifyflags = 0; | |
251 | res = mbedtls_x509_crt_verify(&cert, &cacert, NULL, NULL, &verifyflags, NULL, NULL); | |
252 | if (res) { | |
253 | PrintAndLog("ERROR: DER verify returned 0x%x - %s", (res<0)?-res:res, ecdsa_get_error(res)); | |
254 | } else { | |
255 | PrintAndLog("Certificate OK."); | |
256 | } | |
257 | ||
258 | if (verbose) { | |
259 | memset(linfo, 0x00, sizeof(linfo)); | |
260 | mbedtls_x509_crt_verify_info(linfo, sizeof(linfo), " ", verifyflags); | |
261 | PrintAndLog("Verification info:\n%s", linfo); | |
262 | } | |
263 | ||
264 | // get public key | |
265 | res = ecdsa_public_key_from_pk(&cert.pk, publicKey, publicKeyMaxLen); | |
266 | if (res) { | |
267 | PrintAndLog("ERROR: getting public key from certificate 0x%x - %s", (res<0)?-res:res, ecdsa_get_error(res)); | |
268 | } else { | |
269 | if (verbose) | |
270 | PrintAndLog("Got a public key from certificate:\n%s", sprint_hex_inrow(publicKey, 65)); | |
271 | } | |
272 | ||
273 | if (verbose) | |
274 | PrintAndLog("------------------DER-------------------"); | |
275 | ||
276 | mbedtls_x509_crt_free(&cert); | |
277 | mbedtls_x509_crt_free(&cacert); | |
278 | ||
279 | return 0; | |
280 | } | |
281 | ||
282 | #define fido_check_if(r) if ((r) != CborNoError) {return r;} else | |
283 | #define fido_check(r) if ((r) != CborNoError) return r; | |
284 | ||
285 | int FIDO2CreateMakeCredentionalReq(json_t *root, uint8_t *data, size_t maxdatalen, size_t *datalen) { | |
286 | if (datalen) | |
287 | *datalen = 0; | |
288 | if (!root || !data || !maxdatalen) | |
289 | return 1; | |
290 | ||
291 | int res; | |
292 | CborEncoder encoder; | |
293 | CborEncoder map; | |
294 | ||
295 | cbor_encoder_init(&encoder, data, maxdatalen, 0); | |
296 | ||
297 | // create main map | |
298 | res = cbor_encoder_create_map(&encoder, &map, 5); | |
299 | fido_check_if(res) { | |
300 | // clientDataHash | |
301 | res = cbor_encode_uint(&map, 1); | |
302 | fido_check_if(res) { | |
303 | res = CBOREncodeClientDataHash(root, &map); | |
304 | fido_check(res); | |
305 | } | |
306 | ||
307 | // rp | |
308 | res = cbor_encode_uint(&map, 2); | |
309 | fido_check_if(res) { | |
310 | res = CBOREncodeElm(root, "RelyingPartyEntity", &map); | |
311 | fido_check(res); | |
312 | } | |
313 | ||
314 | // user | |
315 | res = cbor_encode_uint(&map, 3); | |
316 | fido_check_if(res) { | |
317 | res = CBOREncodeElm(root, "UserEntity", &map); | |
318 | fido_check(res); | |
319 | } | |
320 | ||
321 | // pubKeyCredParams | |
322 | res = cbor_encode_uint(&map, 4); | |
323 | fido_check_if(res) { | |
324 | res = CBOREncodeElm(root, "pubKeyCredParams", &map); | |
325 | fido_check(res); | |
326 | } | |
327 | ||
328 | // options | |
329 | res = cbor_encode_uint(&map, 7); | |
330 | fido_check_if(res) { | |
331 | res = CBOREncodeElm(root, "MakeCredentialOptions", &map); | |
332 | fido_check(res); | |
333 | } | |
334 | } | |
335 | res = cbor_encoder_close_container(&encoder, &map); | |
336 | fido_check(res); | |
337 | ||
338 | size_t len = cbor_encoder_get_buffer_size(&encoder, data); | |
339 | if (datalen) | |
340 | *datalen = len; | |
341 | ||
342 | return 0; | |
343 | } | |
344 | ||
345 | bool CheckrpIdHash(json_t *json, uint8_t *hash) { | |
346 | char hashval[300] = {0}; | |
347 | uint8_t hash2[32] = {0}; | |
348 | ||
349 | JsonLoadStr(json, "$.RelyingPartyEntity.id", hashval); | |
350 | sha256hash((uint8_t *)hashval, strlen(hashval), hash2); | |
351 | ||
352 | return !memcmp(hash, hash2, 32); | |
353 | } | |
354 | ||
355 | // check ANSI X9.62 format ECDSA signature (on P-256) | |
356 | int FIDO2CheckSignature(json_t *root, uint8_t *publickey, uint8_t *sign, size_t signLen, uint8_t *authData, size_t authDataLen, bool verbose) { | |
357 | int res; | |
358 | uint8_t rval[300] = {0}; | |
359 | uint8_t sval[300] = {0}; | |
360 | res = ecdsa_asn1_get_signature(sign, signLen, rval, sval); | |
361 | if (!res) { | |
362 | if (verbose) { | |
363 | PrintAndLog(" r: %s", sprint_hex(rval, 32)); | |
364 | PrintAndLog(" s: %s", sprint_hex(sval, 32)); | |
365 | } | |
366 | ||
367 | uint8_t clientDataHash[32] = {0}; | |
368 | size_t clientDataHashLen = 0; | |
369 | res = JsonLoadBufAsHex(root, "$.ClientDataHash", clientDataHash, sizeof(clientDataHash), &clientDataHashLen); | |
370 | if (res || clientDataHashLen != 32) { | |
371 | PrintAndLog("ERROR: Can't get clientDataHash from json!"); | |
372 | return 2; | |
373 | } | |
374 | ||
375 | uint8_t xbuf[4096] = {0}; | |
376 | size_t xbuflen = 0; | |
377 | res = FillBuffer(xbuf, sizeof(xbuf), &xbuflen, | |
378 | authData, authDataLen, // rpIdHash[32] + flags[1] + signCount[4] | |
379 | clientDataHash, 32, // Hash of the serialized client data. "$.ClientDataHash" from json | |
380 | NULL, 0); | |
381 | //PrintAndLog("--xbuf(%d)[%d]: %s", res, xbuflen, sprint_hex(xbuf, xbuflen)); | |
382 | res = ecdsa_signature_verify(publickey, xbuf, xbuflen, sign, signLen); | |
383 | if (res) { | |
384 | if (res == -0x4e00) { | |
385 | PrintAndLog("Signature is NOT VALID."); | |
386 | } else { | |
387 | PrintAndLog("Other signature check error: %x %s", (res<0)?-res:res, ecdsa_get_error(res)); | |
388 | } | |
389 | return res; | |
390 | } else { | |
391 | PrintAndLog("Signature is OK."); | |
392 | } | |
393 | } else { | |
394 | PrintAndLog("Invalid signature. res=%d.", res); | |
395 | return res; | |
396 | } | |
397 | ||
398 | return 0; | |
399 | } | |
400 | ||
401 | int FIDO2MakeCredentionalParseRes(json_t *root, uint8_t *data, size_t dataLen, bool verbose, bool verbose2, bool showCBOR, bool showDERTLV) { | |
402 | CborParser parser; | |
403 | CborValue map, mapsmt; | |
404 | int res; | |
405 | char *buf; | |
406 | uint8_t *ubuf; | |
407 | size_t n; | |
408 | ||
409 | // fmt | |
410 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 1); | |
411 | if (res) | |
412 | return res; | |
413 | ||
414 | res = cbor_value_dup_text_string(&map, &buf, &n, &map); | |
415 | cbor_check(res); | |
416 | PrintAndLog("format: %s", buf); | |
417 | free(buf); | |
418 | ||
419 | // authData | |
420 | uint8_t authData[400] = {0}; | |
421 | size_t authDataLen = 0; | |
422 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 2); | |
423 | if (res) | |
424 | return res; | |
425 | res = cbor_value_dup_byte_string(&map, &ubuf, &n, &map); | |
426 | cbor_check(res); | |
427 | ||
428 | authDataLen = n; | |
429 | memcpy(authData, ubuf, authDataLen); | |
430 | ||
431 | if (verbose2) { | |
432 | PrintAndLog("authData[%d]: %s", n, sprint_hex_inrow(authData, authDataLen)); | |
433 | } else { | |
434 | PrintAndLog("authData[%d]: %s...", n, sprint_hex(authData, MIN(authDataLen, 16))); | |
435 | } | |
436 | ||
437 | PrintAndLog("RP ID Hash: %s", sprint_hex(ubuf, 32)); | |
438 | ||
439 | // check RP ID Hash | |
440 | if (CheckrpIdHash(root, ubuf)) { | |
441 | PrintAndLog("rpIdHash OK."); | |
442 | } else { | |
443 | PrintAndLog("rpIdHash ERROR!"); | |
444 | } | |
445 | ||
446 | PrintAndLog("Flags 0x%02x:", ubuf[32]); | |
447 | if (!ubuf[32]) | |
448 | PrintAndLog("none"); | |
449 | if (ubuf[32] & 0x01) | |
450 | PrintAndLog("up - user presence result"); | |
451 | if (ubuf[32] & 0x04) | |
452 | PrintAndLog("uv - user verification (fingerprint scan or a PIN or ...) result"); | |
453 | if (ubuf[32] & 0x40) | |
454 | PrintAndLog("at - attested credential data included"); | |
455 | if (ubuf[32] & 0x80) | |
456 | PrintAndLog("ed - extension data included"); | |
457 | ||
458 | uint32_t cntr = (uint32_t)bytes_to_num(&ubuf[33], 4); | |
459 | PrintAndLog("Counter: %d", cntr); | |
460 | JsonSaveInt(root, "$.AppData.Counter", cntr); | |
461 | ||
462 | // attestation data | |
463 | PrintAndLog("AAGUID: %s", sprint_hex(&ubuf[37], 16)); | |
464 | JsonSaveBufAsHexCompact(root, "$.AppData.AAGUID", &ubuf[37], 16); | |
465 | ||
466 | // Credential ID | |
467 | uint8_t cridlen = (uint16_t)bytes_to_num(&ubuf[53], 2); | |
468 | PrintAndLog("Credential id[%d]: %s", cridlen, sprint_hex_inrow(&ubuf[55], cridlen)); | |
469 | JsonSaveInt(root, "$.AppData.CredentialIdLen", cridlen); | |
470 | JsonSaveBufAsHexCompact(root, "$.AppData.CredentialId", &ubuf[55], cridlen); | |
471 | ||
472 | //Credentional public key (COSE_KEY) | |
473 | uint8_t coseKey[65] = {0}; | |
474 | uint16_t cplen = n - 55 - cridlen; | |
475 | if (verbose2) { | |
476 | PrintAndLog("Credentional public key (COSE_KEY)[%d]: %s", cplen, sprint_hex_inrow(&ubuf[55 + cridlen], cplen)); | |
477 | } else { | |
478 | PrintAndLog("Credentional public key (COSE_KEY)[%d]: %s...", cplen, sprint_hex(&ubuf[55 + cridlen], MIN(cplen, 16))); | |
479 | } | |
480 | JsonSaveBufAsHexCompact(root, "$.AppData.COSE_KEY", &ubuf[55 + cridlen], cplen); | |
481 | ||
482 | if (showCBOR) { | |
483 | PrintAndLog("COSE structure:"); | |
484 | PrintAndLog("---------------- CBOR ------------------"); | |
485 | TinyCborPrintFIDOPackage(fido2COSEKey, true, &ubuf[55 + cridlen], cplen); | |
486 | PrintAndLog("---------------- CBOR ------------------"); | |
487 | } | |
488 | ||
489 | res = COSEGetECDSAKey(&ubuf[55 + cridlen], cplen, verbose, coseKey); | |
490 | if (res) { | |
491 | PrintAndLog("ERROR: Can't get COSE_KEY."); | |
492 | } else { | |
493 | PrintAndLog("COSE public key: %s", sprint_hex_inrow(coseKey, sizeof(coseKey))); | |
494 | JsonSaveBufAsHexCompact(root, "$.AppData.COSEPublicKey", coseKey, sizeof(coseKey)); | |
495 | } | |
496 | ||
497 | free(ubuf); | |
498 | ||
499 | // attStmt - we are check only as DER certificate | |
500 | int64_t alg = 0; | |
501 | uint8_t sign[128] = {0}; | |
502 | size_t signLen = 0; | |
503 | uint8_t der[4097] = {0}; | |
504 | size_t derLen = 0; | |
505 | ||
506 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 3); | |
507 | if (res) | |
508 | return res; | |
509 | ||
510 | res = cbor_value_enter_container(&map, &mapsmt); | |
511 | cbor_check(res); | |
512 | ||
513 | while (!cbor_value_at_end(&mapsmt)) { | |
514 | char key[100] = {0}; | |
515 | res = CborGetStringValue(&mapsmt, key, sizeof(key), &n); | |
516 | cbor_check(res); | |
517 | if (!strcmp(key, "alg")) { | |
518 | cbor_value_get_int64(&mapsmt, &alg); | |
519 | PrintAndLog("Alg [%lld] %s", (long long)alg, GetCOSEAlgDescription(alg)); | |
520 | res = cbor_value_advance_fixed(&mapsmt); | |
521 | cbor_check(res); | |
522 | } | |
523 | ||
524 | if (!strcmp(key, "sig")) { | |
525 | res = CborGetBinStringValue(&mapsmt, sign, sizeof(sign), &signLen); | |
526 | cbor_check(res); | |
527 | if (verbose2) { | |
528 | PrintAndLog("signature [%d]: %s", signLen, sprint_hex_inrow(sign, signLen)); | |
529 | } else { | |
530 | PrintAndLog("signature [%d]: %s...", signLen, sprint_hex(sign, MIN(signLen, 16))); | |
531 | } | |
532 | } | |
533 | ||
534 | if (!strcmp(key, "x5c")) { | |
535 | res = CborGetArrayBinStringValue(&mapsmt, der, sizeof(der), &derLen); | |
536 | cbor_check(res); | |
537 | if (verbose2) { | |
538 | PrintAndLog("DER certificate[%d]:\n------------------DER-------------------", derLen); | |
539 | dump_buffer_simple((const unsigned char *)der, derLen, NULL); | |
540 | PrintAndLog("\n----------------DER---------------------"); | |
541 | } else { | |
542 | PrintAndLog("DER [%d]: %s...", derLen, sprint_hex(der, MIN(derLen, 16))); | |
543 | } | |
544 | JsonSaveBufAsHexCompact(root, "$.AppData.DER", der, derLen); | |
545 | } | |
546 | } | |
547 | res = cbor_value_leave_container(&map, &mapsmt); | |
548 | cbor_check(res); | |
549 | ||
550 | uint8_t public_key[65] = {0}; | |
551 | ||
552 | // print DER certificate in TLV view | |
553 | if (showDERTLV) { | |
554 | PrintAndLog("----------------DER TLV-----------------"); | |
555 | asn1_print(der, derLen, " "); | |
556 | PrintAndLog("----------------DER TLV-----------------"); | |
557 | } | |
558 | FIDOCheckDERAndGetKey(der, derLen, verbose, public_key, sizeof(public_key)); | |
559 | JsonSaveBufAsHexCompact(root, "$.AppData.DERPublicKey", public_key, sizeof(public_key)); | |
560 | ||
561 | // check ANSI X9.62 format ECDSA signature (on P-256) | |
562 | FIDO2CheckSignature(root, public_key, sign, signLen, authData, authDataLen, verbose); | |
563 | ||
564 | return 0; | |
565 | } | |
566 | ||
567 | int FIDO2CreateGetAssertionReq(json_t *root, uint8_t *data, size_t maxdatalen, size_t *datalen, bool createAllowList) { | |
568 | if (datalen) | |
569 | *datalen = 0; | |
570 | if (!root || !data || !maxdatalen) | |
571 | return 1; | |
572 | ||
573 | int res; | |
574 | CborEncoder encoder; | |
575 | CborEncoder map, array, mapint; | |
576 | ||
577 | cbor_encoder_init(&encoder, data, maxdatalen, 0); | |
578 | ||
579 | // create main map | |
580 | res = cbor_encoder_create_map(&encoder, &map, createAllowList ? 4 : 3); | |
581 | fido_check_if(res) { | |
582 | // rpId | |
583 | res = cbor_encode_uint(&map, 1); | |
584 | fido_check_if(res) { | |
585 | res = CBOREncodeElm(root, "$.RelyingPartyEntity.id", &map); | |
586 | fido_check(res); | |
587 | } | |
588 | ||
589 | // clientDataHash | |
590 | res = cbor_encode_uint(&map, 2); | |
591 | fido_check_if(res) { | |
592 | res = CBOREncodeClientDataHash(root, &map); | |
593 | fido_check(res); | |
594 | } | |
595 | ||
596 | // allowList | |
597 | if (createAllowList) { | |
598 | res = cbor_encode_uint(&map, 3); | |
599 | fido_check_if(res) { | |
600 | res = cbor_encoder_create_array(&map, &array, 1); | |
601 | fido_check_if(res) { | |
602 | res = cbor_encoder_create_map(&array, &mapint, 2); | |
603 | fido_check_if(res) { | |
604 | res = cbor_encode_text_stringz(&mapint, "type"); | |
605 | fido_check(res); | |
606 | ||
607 | res = cbor_encode_text_stringz(&mapint, "public-key"); | |
608 | fido_check(res); | |
609 | ||
610 | res = cbor_encode_text_stringz(&mapint, "id"); | |
611 | fido_check(res); | |
612 | ||
613 | res = CBOREncodeElm(root, "$.AppData.CredentialId", &mapint); | |
614 | fido_check(res); | |
615 | } | |
616 | res = cbor_encoder_close_container(&array, &mapint); | |
617 | fido_check(res); | |
618 | } | |
619 | res = cbor_encoder_close_container(&map, &array); | |
620 | fido_check(res); | |
621 | } | |
622 | } | |
623 | ||
624 | // options | |
625 | res = cbor_encode_uint(&map, 5); | |
626 | fido_check_if(res) { | |
627 | res = CBOREncodeElm(root, "GetAssertionOptions", &map); | |
628 | fido_check(res); | |
629 | } | |
630 | } | |
631 | res = cbor_encoder_close_container(&encoder, &map); | |
632 | fido_check(res); | |
633 | ||
634 | size_t len = cbor_encoder_get_buffer_size(&encoder, data); | |
635 | if (datalen) | |
636 | *datalen = len; | |
637 | ||
638 | return 0; | |
639 | } | |
640 | ||
641 | int FIDO2GetAssertionParseRes(json_t *root, uint8_t *data, size_t dataLen, bool verbose, bool verbose2, bool showCBOR) { | |
642 | CborParser parser; | |
643 | CborValue map, mapint; | |
644 | int res; | |
645 | uint8_t *ubuf; | |
646 | size_t n; | |
647 | ||
648 | // credential | |
649 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 1); | |
650 | if (res) | |
651 | return res; | |
652 | ||
653 | res = cbor_value_enter_container(&map, &mapint); | |
654 | cbor_check(res); | |
655 | ||
656 | while (!cbor_value_at_end(&mapint)) { | |
657 | char key[100] = {0}; | |
658 | res = CborGetStringValue(&mapint, key, sizeof(key), &n); | |
659 | cbor_check(res); | |
660 | ||
661 | if (!strcmp(key, "type")) { | |
662 | char ctype[200] = {0}; | |
663 | res = CborGetStringValue(&mapint, ctype, sizeof(ctype), &n); | |
664 | cbor_check(res); | |
665 | PrintAndLog("credential type: %s", ctype); | |
666 | } | |
667 | ||
668 | if (!strcmp(key, "id")) { | |
669 | uint8_t cid[200] = {0}; | |
670 | res = CborGetBinStringValue(&mapint, cid, sizeof(cid), &n); | |
671 | cbor_check(res); | |
672 | PrintAndLog("credential id [%d]: %s", n, sprint_hex(cid, n)); | |
673 | } | |
674 | } | |
675 | res = cbor_value_leave_container(&map, &mapint); | |
676 | cbor_check(res); | |
677 | ||
678 | // authData | |
679 | uint8_t authData[400] = {0}; | |
680 | size_t authDataLen = 0; | |
681 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 2); | |
682 | if (res) | |
683 | return res; | |
684 | res = cbor_value_dup_byte_string(&map, &ubuf, &n, &map); | |
685 | cbor_check(res); | |
686 | ||
687 | authDataLen = n; | |
688 | memcpy(authData, ubuf, authDataLen); | |
689 | ||
690 | if (verbose2) { | |
691 | PrintAndLog("authData[%d]: %s", n, sprint_hex_inrow(authData, authDataLen)); | |
692 | } else { | |
693 | PrintAndLog("authData[%d]: %s...", n, sprint_hex(authData, MIN(authDataLen, 16))); | |
694 | } | |
695 | ||
696 | PrintAndLog("RP ID Hash: %s", sprint_hex(ubuf, 32)); | |
697 | ||
698 | // check RP ID Hash | |
699 | if (CheckrpIdHash(root, ubuf)) { | |
700 | PrintAndLog("rpIdHash OK."); | |
701 | } else { | |
702 | PrintAndLog("rpIdHash ERROR!"); | |
703 | } | |
704 | ||
705 | PrintAndLog("Flags 0x%02x:", ubuf[32]); | |
706 | if (!ubuf[32]) | |
707 | PrintAndLog("none"); | |
708 | if (ubuf[32] & 0x01) | |
709 | PrintAndLog("up - user presence result"); | |
710 | if (ubuf[32] & 0x04) | |
711 | PrintAndLog("uv - user verification (fingerprint scan or a PIN or ...) result"); | |
712 | if (ubuf[32] & 0x40) | |
713 | PrintAndLog("at - attested credential data included"); | |
714 | if (ubuf[32] & 0x80) | |
715 | PrintAndLog("ed - extension data included"); | |
716 | ||
717 | uint32_t cntr = (uint32_t)bytes_to_num(&ubuf[33], 4); | |
718 | PrintAndLog("Counter: %d", cntr); | |
719 | JsonSaveInt(root, "$.AppData.Counter", cntr); | |
720 | ||
721 | free(ubuf); | |
722 | ||
723 | // publicKeyCredentialUserEntity | |
724 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 4); | |
725 | if (res) { | |
726 | PrintAndLog("UserEntity n/a"); | |
727 | } else { | |
728 | res = cbor_value_enter_container(&map, &mapint); | |
729 | cbor_check(res); | |
730 | ||
731 | while (!cbor_value_at_end(&mapint)) { | |
732 | char key[100] = {0}; | |
733 | res = CborGetStringValue(&mapint, key, sizeof(key), &n); | |
734 | cbor_check(res); | |
735 | ||
736 | if (!strcmp(key, "name") || !strcmp(key, "displayName")) { | |
737 | char cname[200] = {0}; | |
738 | res = CborGetStringValue(&mapint, cname, sizeof(cname), &n); | |
739 | cbor_check(res); | |
740 | PrintAndLog("UserEntity %s: %s", key, cname); | |
741 | } | |
742 | ||
743 | if (!strcmp(key, "id")) { | |
744 | uint8_t cid[200] = {0}; | |
745 | res = CborGetBinStringValue(&mapint, cid, sizeof(cid), &n); | |
746 | cbor_check(res); | |
747 | PrintAndLog("UserEntity id [%d]: %s", n, sprint_hex(cid, n)); | |
748 | ||
749 | // check | |
750 | uint8_t idbuf[100] = {0}; | |
751 | size_t idbuflen; | |
752 | ||
753 | JsonLoadBufAsHex(root, "$.UserEntity.id", idbuf, sizeof(idbuf), &idbuflen); | |
754 | ||
755 | if (idbuflen == n && !memcmp(idbuf, cid, idbuflen)) { | |
756 | PrintAndLog("UserEntity id OK."); | |
757 | } else { | |
758 | PrintAndLog("ERROR: Wrong UserEntity id (from json: %s)", sprint_hex(idbuf, idbuflen)); | |
759 | } | |
760 | } | |
761 | } | |
762 | res = cbor_value_leave_container(&map, &mapint); | |
763 | cbor_check(res); | |
764 | } | |
765 | ||
766 | ||
767 | // signature | |
768 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 3); | |
769 | if (res) | |
770 | return res; | |
771 | res = cbor_value_dup_byte_string(&map, &ubuf, &n, &map); | |
772 | cbor_check(res); | |
773 | ||
774 | uint8_t *sign = ubuf; | |
775 | size_t signLen = n; | |
776 | ||
777 | cbor_check(res); | |
778 | if (verbose2) { | |
779 | PrintAndLog("signature [%d]: %s", signLen, sprint_hex_inrow(sign, signLen)); | |
780 | } else { | |
781 | PrintAndLog("signature [%d]: %s...", signLen, sprint_hex(sign, MIN(signLen, 16))); | |
782 | } | |
783 | ||
784 | // get public key from json | |
785 | uint8_t PublicKey[65] = {0}; | |
786 | size_t PublicKeyLen = 0; | |
787 | JsonLoadBufAsHex(root, "$.AppData.COSEPublicKey", PublicKey, 65, &PublicKeyLen); | |
788 | ||
789 | // check ANSI X9.62 format ECDSA signature (on P-256) | |
790 | FIDO2CheckSignature(root, PublicKey, sign, signLen, authData, authDataLen, verbose); | |
791 | ||
792 | free(ubuf); | |
793 | ||
794 | // numberOfCredentials | |
795 | res = CborMapGetKeyById(&parser, &map, data, dataLen, 5); | |
796 | if (res) { | |
797 | PrintAndLog("numberOfCredentials: 1 by default"); | |
798 | } else { | |
799 | int64_t numberOfCredentials = 0; | |
800 | cbor_value_get_int64(&map, &numberOfCredentials); | |
801 | PrintAndLog("numberOfCredentials: %lld", (long long)numberOfCredentials); | |
802 | } | |
803 | ||
804 | return 0; | |
805 | } |