diff --git a/FIDO2 Conformance Testing.json b/FIDO2 Conformance Testing.json index 33f3cd9d..e63fa088 100644 --- a/FIDO2 Conformance Testing.json +++ b/FIDO2 Conformance Testing.json @@ -8,6 +8,10 @@ { "major": 1, "minor": 1 + }, + { + "major": 1, + "minor": 0 } ], "authenticationAlgorithms": ["secp256r1_ecdsa_sha256_raw", "ed25519_eddsa_sha512_raw"], diff --git a/applets/ctap/ctap-internal.h b/applets/ctap/ctap-internal.h index 85df4c9e..443103d5 100644 --- a/applets/ctap/ctap-internal.h +++ b/applets/ctap/ctap-internal.h @@ -189,39 +189,43 @@ #define LB_RESP_CONFIG 0x01 // Size limits -#define KH_KEY_SIZE 32 -#define HE_KEY_SIZE 32 -#define PRI_KEY_SIZE 32 -#define PUB_KEY_SIZE 64 -#define SHARED_SECRET_SIZE 32 -#define MAX_COSE_KEY_SIZE 78 -#define PIN_ENC_SIZE_P1 64 -#define PIN_ENC_SIZE_P2 80 -#define PIN_HASH_SIZE_P1 16 -#define PIN_HASH_SIZE_P2 32 -#define MAX_CERT_SIZE 1152 -#define AAGUID_SIZE 16 -#define PIN_AUTH_SIZE_P1 16 -#define PIN_TOKEN_SIZE 32 -#define HMAC_SECRET_SALT_SIZE 64 -#define HMAC_SECRET_SALT_AUTH_SIZE 16 -#define CREDENTIAL_TAG_SIZE 16 -#define CLIENT_DATA_HASH_SIZE 32 -#define CREDENTIAL_NONCE_SIZE 16 -#define CREDENTIAL_NONCE_DC_POS 16 -#define CREDENTIAL_NONCE_CP_POS 17 -#define DOMAIN_NAME_MAX_SIZE 254 -#define USER_ID_MAX_SIZE 64 -#define DISPLAY_NAME_LIMIT 65 -#define USER_NAME_LIMIT 65 -#define MAX_DC_NUM 64 -#define MAX_STORED_RPID_LENGTH 32 -#define MAX_EXTENSION_SIZE_IN_AUTH 51 -#define MAX_CREDENTIAL_COUNT_IN_LIST 8 -#define MAX_CRED_BLOB_LENGTH 32 -#define LARGE_BLOB_KEY_SIZE 32 -#define LARGE_BLOB_SIZE_LIMIT 4096 -#define MAX_FRAGMENT_LENGTH (MAX_CTAP_BUFSIZE - 64) +#define KH_KEY_SIZE 32 +#define HE_KEY_SIZE 32 +#define PRI_KEY_SIZE 32 +#define PUB_KEY_SIZE 64 +#define SHARED_SECRET_SIZE_P1 32 +#define SHARED_SECRET_SIZE_P2 64 +#define SHARED_SECRET_SIZE_HMAC 32 +#define MAX_COSE_KEY_SIZE 78 +#define PIN_ENC_SIZE_P1 64 +#define PIN_ENC_SIZE_P2 80 +#define PIN_HASH_SIZE_P1 16 +#define PIN_HASH_SIZE_P2 32 +#define MAX_CERT_SIZE 1152 +#define AAGUID_SIZE 16 +#define PIN_AUTH_SIZE_P1 16 +#define PIN_TOKEN_SIZE 32 +#define HMAC_SECRET_SALT_SIZE 64 +#define HMAC_SECRET_SALT_IV_SIZE 16 +#define HMAC_SECRET_SALT_AUTH_SIZE_P1 16 +#define HMAC_SECRET_SALT_AUTH_SIZE_P2 32 +#define CREDENTIAL_TAG_SIZE 16 +#define CLIENT_DATA_HASH_SIZE 32 +#define CREDENTIAL_NONCE_SIZE 16 +#define CREDENTIAL_NONCE_DC_POS 16 +#define CREDENTIAL_NONCE_CP_POS 17 +#define DOMAIN_NAME_MAX_SIZE 254 +#define USER_ID_MAX_SIZE 64 +#define DISPLAY_NAME_LIMIT 65 +#define USER_NAME_LIMIT 65 +#define MAX_DC_NUM 64 +#define MAX_STORED_RPID_LENGTH 32 +#define MAX_EXTENSION_SIZE_IN_AUTH 51 +#define MAX_CREDENTIAL_COUNT_IN_LIST 8 +#define MAX_CRED_BLOB_LENGTH 32 +#define LARGE_BLOB_KEY_SIZE 32 +#define LARGE_BLOB_SIZE_LIMIT 4096 +#define MAX_FRAGMENT_LENGTH (MAX_CTAP_BUFSIZE - 64) typedef struct { uint8_t id[USER_ID_MAX_SIZE]; @@ -316,9 +320,10 @@ typedef struct { size_t pin_uv_auth_param_len; uint8_t pin_uv_auth_protocol; uint8_t ext_hmac_secret_key_agreement[PUB_KEY_SIZE]; - uint8_t ext_hmac_secret_salt_enc[HMAC_SECRET_SALT_SIZE]; - uint8_t ext_hmac_secret_salt_auth[HMAC_SECRET_SALT_AUTH_SIZE]; - uint8_t ext_hmac_secret_salt_len; + uint8_t ext_hmac_secret_salt_enc[HMAC_SECRET_SALT_IV_SIZE + HMAC_SECRET_SALT_SIZE]; + uint8_t ext_hmac_secret_salt_enc_len; + uint8_t ext_hmac_secret_salt_auth[HMAC_SECRET_SALT_AUTH_SIZE_P2]; + uint8_t ext_hmac_secret_salt_auth_len; uint8_t ext_hmac_secret_pin_protocol; bool ext_large_blob_key; bool ext_cred_blob; diff --git a/applets/ctap/ctap-parser.c b/applets/ctap/ctap-parser.c index c6d8fb77..c871132d 100644 --- a/applets/ctap/ctap-parser.c +++ b/applets/ctap/ctap-parser.c @@ -488,23 +488,25 @@ uint8_t parse_ga_extensions(CTAP_get_assertion *ga, CborValue *val) { if (cbor_value_get_type(&hmac_map) != CborByteStringType) return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; len = sizeof(ga->ext_hmac_secret_salt_enc); ret = cbor_value_copy_byte_string(&hmac_map, ga->ext_hmac_secret_salt_enc, &len, NULL); - if (ret == CborErrorOutOfMemory) return CTAP1_ERR_INVALID_LENGTH; + if (ret == CborErrorOutOfMemory) { + ERR_MSG("ext_hmac_secret_salt_enc is too long\n"); + return CTAP1_ERR_INVALID_LENGTH; + } CHECK_CBOR_RET(ret); - if (len != HMAC_SECRET_SALT_SIZE && len != HMAC_SECRET_SALT_SIZE / 2) return CTAP1_ERR_INVALID_LENGTH; - ga->ext_hmac_secret_salt_len = len; + ga->ext_hmac_secret_salt_enc_len = len; map_has_entry |= GA_HS_MAP_ENTRY_SALT_ENC; DBG_MSG("salt_enc: "); - PRINT_HEX(ga->ext_hmac_secret_salt_enc, ga->ext_hmac_secret_salt_len); + PRINT_HEX(ga->ext_hmac_secret_salt_enc, ga->ext_hmac_secret_salt_enc_len); break; case GA_REQ_HMAC_SECRET_SALT_AUTH: if (cbor_value_get_type(&hmac_map) != CborByteStringType) return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; len = sizeof(ga->ext_hmac_secret_salt_auth); ret = cbor_value_copy_byte_string(&hmac_map, ga->ext_hmac_secret_salt_auth, &len, NULL); CHECK_CBOR_RET(ret); - if (len != HMAC_SECRET_SALT_AUTH_SIZE) return CTAP1_ERR_INVALID_LENGTH; + ga->ext_hmac_secret_salt_auth_len = len; map_has_entry |= GA_HS_MAP_ENTRY_SALT_AUTH; DBG_MSG("salt_auth: "); - PRINT_HEX(ga->ext_hmac_secret_salt_auth, 16); + PRINT_HEX(ga->ext_hmac_secret_salt_auth, ga->ext_hmac_secret_salt_auth_len); break; case GA_REQ_HMAC_SECRET_PIN_PROTOCOL: if (cbor_value_get_type(&hmac_map) != CborIntegerType) return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; @@ -522,6 +524,18 @@ uint8_t parse_ga_extensions(CTAP_get_assertion *ga, CborValue *val) { } if ((map_has_entry & GA_HS_MAP_ENTRY_ALL_REQUIRED) != GA_HS_MAP_ENTRY_ALL_REQUIRED) return CTAP2_ERR_MISSING_PARAMETER; + if ((ga->ext_hmac_secret_pin_protocol == 1 && ga->ext_hmac_secret_salt_enc_len != HMAC_SECRET_SALT_SIZE && + ga->ext_hmac_secret_salt_enc_len != HMAC_SECRET_SALT_SIZE / 2) || + (ga->ext_hmac_secret_pin_protocol == 2 && ga->ext_hmac_secret_salt_enc_len != HMAC_SECRET_SALT_SIZE + HMAC_SECRET_SALT_IV_SIZE && + ga->ext_hmac_secret_salt_enc_len != HMAC_SECRET_SALT_SIZE / 2 + HMAC_SECRET_SALT_IV_SIZE)) { + ERR_MSG("Invalid hmac_secret_salt_len\n"); + return CTAP1_ERR_INVALID_LENGTH; + } + if ((ga->ext_hmac_secret_pin_protocol == 1 && len != HMAC_SECRET_SALT_AUTH_SIZE_P1) || + (ga->ext_hmac_secret_pin_protocol == 2 && len != HMAC_SECRET_SALT_AUTH_SIZE_P2)) { + ERR_MSG("Invalid hmac_secret_auth_size\n"); + return CTAP1_ERR_INVALID_LENGTH; + } ga->parsed_params |= PARAM_HMAC_SECRET; } else if (strcmp(key, "credBlob") == 0) { if (cbor_value_get_type(&map) != CborBooleanType) return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; diff --git a/applets/ctap/ctap.c b/applets/ctap/ctap.c index c1a0d42c..3e4e9c53 100644 --- a/applets/ctap/ctap.c +++ b/applets/ctap/ctap.c @@ -448,7 +448,7 @@ static uint8_t ctap_make_credential(CborEncoder *encoder, uint8_t *params, size_ // v. Else, (implying userPresentFlagValue is true) terminate this procedure and return CTAP2_ERR_CREDENTIAL_EXCLUDED. return CTAP2_ERR_CREDENTIAL_EXCLUDED; - // c) Else (implying user verification was not collected in Step 11), + // c) Else (implying user verification was not collected in Step 11), // remove the credential from the excludeList and continue parsing the rest of the list. } else { DBG_MSG("Ignore this Exclude ID\n"); @@ -1010,48 +1010,51 @@ static uint8_t ctap_get_assertion(CborEncoder *encoder, uint8_t *params, size_t // Process hmac-secret extension if (ga.parsed_params & PARAM_HMAC_SECRET) { - uint8_t iv[16] = {0}; - block_cipher_config cfg = {.block_size = 16, .mode = CBC, .iv = iv, .encrypt = aes256_enc, .decrypt = aes256_dec}; - uint8_t *hmac_enc_key = ga.ext_hmac_secret_pin_protocol == 2 ? - ga.ext_hmac_secret_key_agreement + SHARED_SECRET_SIZE : - ga.ext_hmac_secret_key_agreement; if (credential_counter == 0) { ret = cp_decapsulate(ga.ext_hmac_secret_key_agreement, ga.ext_hmac_secret_pin_protocol); CHECK_PARSER_RET(ret); - DBG_MSG("ext_hmac_secret_key_agreement: "); - PRINT_HEX(ga.ext_hmac_secret_key_agreement, ga.ext_hmac_secret_pin_protocol == 2 ? 64 : 32); - uint8_t hmac_buf[SHA256_DIGEST_LENGTH]; - hmac_sha256(ga.ext_hmac_secret_key_agreement, SHARED_SECRET_SIZE, ga.ext_hmac_secret_salt_enc, - ga.ext_hmac_secret_salt_len, - hmac_buf); - if (memcmp_s(hmac_buf, ga.ext_hmac_secret_salt_auth, HMAC_SECRET_SALT_AUTH_SIZE) != 0) - return CTAP2_ERR_EXTENSION_FIRST; - cfg.key = hmac_enc_key; - cfg.in_size = ga.ext_hmac_secret_salt_len; - cfg.in = ga.ext_hmac_secret_salt_enc; - cfg.out = ga.ext_hmac_secret_salt_enc; - block_cipher_dec(&cfg); + DBG_MSG("Shared secret: "); + PRINT_HEX(ga.ext_hmac_secret_key_agreement, ga.ext_hmac_secret_pin_protocol == 2 ? SHARED_SECRET_SIZE_P2 : SHARED_SECRET_SIZE_P1); + if (!cp_verify(ga.ext_hmac_secret_key_agreement, SHARED_SECRET_SIZE_HMAC, ga.ext_hmac_secret_salt_enc, + ga.ext_hmac_secret_salt_enc_len, ga.ext_hmac_secret_salt_auth, ga.ext_hmac_secret_pin_protocol)) { + ERR_MSG("Hmac verification failed\n"); + return CTAP2_ERR_PIN_AUTH_INVALID; + } + if (cp_decrypt(ga.ext_hmac_secret_key_agreement, ga.ext_hmac_secret_salt_enc, ga.ext_hmac_secret_salt_enc_len, + ga.ext_hmac_secret_salt_enc, ga.ext_hmac_secret_pin_protocol) != 0) { + ERR_MSG("Hmac decryption failed\n"); + return CTAP2_ERR_UNHANDLED_REQUEST; + } } uint8_t hmac_secret_output[HMAC_SECRET_SALT_SIZE]; DBG_MSG("hmac-secret-salt: "); - PRINT_HEX(ga.ext_hmac_secret_salt_enc, ga.ext_hmac_secret_salt_len); - ret = make_hmac_secret_output(dc.credential_id.nonce, ga.ext_hmac_secret_salt_enc, ga.ext_hmac_secret_salt_len, + PRINT_HEX(ga.ext_hmac_secret_salt_enc, ga.ext_hmac_secret_pin_protocol == 1 + ? ga.ext_hmac_secret_salt_enc_len + : ga.ext_hmac_secret_salt_enc_len - HMAC_SECRET_SALT_IV_SIZE); + ret = make_hmac_secret_output(dc.credential_id.nonce, ga.ext_hmac_secret_salt_enc, + ga.ext_hmac_secret_pin_protocol == 1 + ? ga.ext_hmac_secret_salt_enc_len + : ga.ext_hmac_secret_salt_enc_len - HMAC_SECRET_SALT_IV_SIZE, hmac_secret_output, uv); CHECK_PARSER_RET(ret); DBG_MSG("hmac-secret %s UV (plain): ", uv ? "with" : "without"); - PRINT_HEX(hmac_secret_output, ga.ext_hmac_secret_salt_len); - cfg.key = hmac_enc_key; - cfg.in_size = ga.ext_hmac_secret_salt_len; - cfg.in = hmac_secret_output; - cfg.out = hmac_secret_output; - block_cipher_enc(&cfg); + PRINT_HEX(hmac_secret_output, ga.ext_hmac_secret_pin_protocol == 1 + ? ga.ext_hmac_secret_salt_enc_len + : ga.ext_hmac_secret_salt_enc_len - HMAC_SECRET_SALT_IV_SIZE); + if (cp_encrypt(ga.ext_hmac_secret_key_agreement, hmac_secret_output, + ga.ext_hmac_secret_pin_protocol == 1 ? ga.ext_hmac_secret_salt_enc_len + : ga.ext_hmac_secret_salt_enc_len - HMAC_SECRET_SALT_IV_SIZE, + ga.ext_hmac_secret_salt_enc, ga.ext_hmac_secret_pin_protocol) < 0) + return CTAP2_ERR_UNHANDLED_REQUEST; + DBG_MSG("hmac-secret output: "); + PRINT_HEX(ga.ext_hmac_secret_salt_enc, ga.ext_hmac_secret_salt_enc_len); if (credential_counter + 1 == number_of_credentials) { // encryption key will not be used any more memzero(ga.ext_hmac_secret_key_agreement, sizeof(ga.ext_hmac_secret_key_agreement)); } ret = cbor_encode_text_stringz(&map, "hmac-secret"); CHECK_CBOR_RET(ret); - ret = cbor_encode_byte_string(&map, hmac_secret_output, ga.ext_hmac_secret_salt_len); + ret = cbor_encode_byte_string(&map, ga.ext_hmac_secret_salt_enc, ga.ext_hmac_secret_salt_enc_len); CHECK_CBOR_RET(ret); } ret = cbor_encoder_close_container(&extension_encoder, &map); @@ -1402,8 +1405,8 @@ static uint8_t ctap_client_pin(CborEncoder *encoder, const uint8_t *params, size ret = cp_decapsulate(cp.key_agreement, cp.pin_uv_auth_protocol); CHECK_PARSER_RET(ret); DBG_MSG("Shared Secret: "); - PRINT_HEX(cp.key_agreement, SHARED_SECRET_SIZE); - if (!cp_verify(cp.key_agreement, SHARED_SECRET_SIZE, cp.new_pin_enc, + PRINT_HEX(cp.key_agreement, cp.pin_uv_auth_protocol == 2 ? SHARED_SECRET_SIZE_P2 : SHARED_SECRET_SIZE_P1); + if (!cp_verify(cp.key_agreement, SHARED_SECRET_SIZE_HMAC, cp.new_pin_enc, cp.pin_uv_auth_protocol == 1 ? PIN_ENC_SIZE_P1 : PIN_ENC_SIZE_P2, cp.pin_uv_auth_param, cp.pin_uv_auth_protocol)) { ERR_MSG("CP verification failed\n"); @@ -1442,12 +1445,12 @@ static uint8_t ctap_client_pin(CborEncoder *encoder, const uint8_t *params, size if (cp.pin_uv_auth_protocol == 1) { memcpy(buf, cp.new_pin_enc, PIN_ENC_SIZE_P1); memcpy(buf + PIN_ENC_SIZE_P1, cp.pin_hash_enc, PIN_HASH_SIZE_P1); - ret = cp_verify(cp.key_agreement, SHARED_SECRET_SIZE, buf, PIN_ENC_SIZE_P1 + PIN_HASH_SIZE_P1, + ret = cp_verify(cp.key_agreement, SHARED_SECRET_SIZE_HMAC, buf, PIN_ENC_SIZE_P1 + PIN_HASH_SIZE_P1, cp.pin_uv_auth_param, cp.pin_uv_auth_protocol); } else { memcpy(buf, cp.new_pin_enc, PIN_ENC_SIZE_P2); memcpy(buf + PIN_ENC_SIZE_P2, cp.pin_hash_enc, PIN_HASH_SIZE_P2); - ret = cp_verify(cp.key_agreement, SHARED_SECRET_SIZE, buf, PIN_ENC_SIZE_P2 + PIN_HASH_SIZE_P2, + ret = cp_verify(cp.key_agreement, SHARED_SECRET_SIZE_HMAC, buf, PIN_ENC_SIZE_P2 + PIN_HASH_SIZE_P2, cp.pin_uv_auth_param, cp.pin_uv_auth_protocol); } if (ret == false) { @@ -1665,7 +1668,7 @@ static uint8_t ctap_credential_management(CborEncoder *encoder, const uint8_t *p break; case CM_CMD_ENUMERATE_RPS_GET_NEXT_RP: - if (last_cmd != CTAP_CREDENTIAL_MANAGEMENT || + if (last_cmd != CTAP_CREDENTIAL_MANAGEMENT || (last_cm_cmd != CM_CMD_ENUMERATE_RPS_BEGIN && last_cm_cmd != CM_CMD_ENUMERATE_RPS_GET_NEXT_RP)) { last_cm_cmd = 0; return CTAP2_ERR_NOT_ALLOWED; diff --git a/applets/ctap/secret.c b/applets/ctap/secret.c index ec52e09a..45007aeb 100644 --- a/applets/ctap/secret.c +++ b/applets/ctap/secret.c @@ -131,11 +131,13 @@ int cp_encrypt(const uint8_t *key, const uint8_t *in, size_t in_size, uint8_t *o cfg.out = out; } else { random_buffer(iv, sizeof(iv)); - cfg.key = key + 32; + cfg.key = key + SHARED_SECRET_SIZE_HMAC; cfg.out = out + sizeof(iv); - memcpy(out, iv, sizeof(iv)); } - return block_cipher_enc(&cfg); + int ret = block_cipher_enc(&cfg); + if (pin_protocol == 2) + memcpy(out, iv, sizeof(iv)); + return ret; } int cp_encrypt_pin_token(const uint8_t *key, uint8_t *out, int pin_protocol) { @@ -154,7 +156,7 @@ int cp_decrypt(const uint8_t *key, const uint8_t *in, size_t in_size, uint8_t *o } else { if (in_size < sizeof(iv)) return -1; memcpy(iv, in, sizeof(iv)); - cfg.key = key + 32; + cfg.key = key + SHARED_SECRET_SIZE_HMAC; cfg.in_size = in_size - sizeof(iv); cfg.in = in + sizeof(iv); cfg.out = out;