Skip to content

Commit

Permalink
fix hmac pin protocol 2
Browse files Browse the repository at this point in the history
  • Loading branch information
dangfan committed Oct 15, 2023
1 parent 427e45f commit 3cf127b
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 79 deletions.
4 changes: 4 additions & 0 deletions FIDO2 Conformance Testing.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
{
"major": 1,
"minor": 1
},
{
"major": 1,
"minor": 0
}
],
"authenticationAlgorithms": ["secp256r1_ecdsa_sha256_raw", "ed25519_eddsa_sha512_raw"],
Expand Down
77 changes: 41 additions & 36 deletions applets/ctap/ctap-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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;
Expand Down
26 changes: 20 additions & 6 deletions applets/ctap/ctap-parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
69 changes: 36 additions & 33 deletions applets/ctap/ctap.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 6 additions & 4 deletions applets/ctap/secret.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
Expand Down

0 comments on commit 3cf127b

Please sign in to comment.