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