| 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 | } |