Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Based on the latest master #68

Merged
merged 5 commits into from
Nov 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions applets/ctap/ctap-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@
#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_EXTENSION_SIZE_IN_AUTH 140
#define MAX_CREDENTIAL_COUNT_IN_LIST 8
#define MAX_CRED_BLOB_LENGTH 32
#define LARGE_BLOB_KEY_SIZE 32
Expand Down Expand Up @@ -369,6 +369,6 @@ int u2f_authenticate(const CAPDU *capdu, RAPDU *rapdu);
int u2f_version(const CAPDU *capdu, RAPDU *rapdu);
int u2f_select(const CAPDU *capdu, RAPDU *rapdu);
uint8_t ctap_make_auth_data(uint8_t *rp_id_hash, uint8_t *buf, uint8_t flags, const uint8_t *extension,
uint8_t extension_size, size_t *len, int32_t alg_type, bool dc, uint8_t cred_protect);
size_t extension_size, size_t *len, int32_t alg_type, bool dc, uint8_t cred_protect);

#endif
212 changes: 111 additions & 101 deletions applets/ctap/ctap.c
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ int ctap_consistency_check(void) {
}

uint8_t ctap_make_auth_data(uint8_t *rp_id_hash, uint8_t *buf, uint8_t flags, const uint8_t *extension,
uint8_t extension_size, size_t *len, int32_t alg_type, bool dc, uint8_t cred_protect) {
size_t extension_size, size_t *len, int32_t alg_type, bool dc, uint8_t cred_protect) {
// See https://www.w3.org/TR/webauthn/#sec-authenticator-data
// auth data is a byte string
// --------------------------------------------------------------------------------
Expand Down Expand Up @@ -486,48 +486,54 @@ static uint8_t ctap_make_credential(CborEncoder *encoder, uint8_t *params, size_
cp_clear_user_verified_flag();
cp_clear_pin_uv_auth_token_permissions_except_lbw();

// 15. If the extensions parameter is present:
CborEncoder map;
uint8_t extension_buffer[MAX_EXTENSION_SIZE_IN_AUTH];
CborEncoder extension_encoder, map;
cbor_encoder_init(&extension_encoder, extension_buffer, sizeof(extension_buffer), 0);
ret = cbor_encoder_create_map(&extension_encoder, &map,
(mc.ext_hmac_secret ? 1 : 0) +
size_t extension_size = 0;
// 15. If the extensions parameter is present:
uint8_t extension_map_items = (mc.ext_hmac_secret ? 1 : 0) +
// largeBlobKey has no outputs here
(mc.ext_cred_protect != CRED_PROTECT_ABSENT ? 1 : 0) +
(mc.ext_has_cred_blob ? 1 : 0));
CHECK_CBOR_RET(ret);
(mc.ext_has_cred_blob ? 1 : 0);
if (extension_map_items > 0) {
CborEncoder extension_encoder;
cbor_encoder_init(&extension_encoder, extension_buffer, sizeof(extension_buffer), 0);
ret = cbor_encoder_create_map(&extension_encoder, &map, extension_map_items);
CHECK_CBOR_RET(ret);

if (mc.ext_has_cred_blob) {
bool accepted = false;
if (mc.ext_cred_blob_len <= MAX_CRED_BLOB_LENGTH && mc.options.rk == OPTION_TRUE) {
accepted = true;
}
ret = cbor_encode_text_stringz(&map, "credBlob");
CHECK_CBOR_RET(ret);
ret = cbor_encode_boolean(&map, accepted);
CHECK_CBOR_RET(ret);
}
if (mc.ext_cred_protect != CRED_PROTECT_ABSENT) {
ret = cbor_encode_text_stringz(&map, "credProtect");
CHECK_CBOR_RET(ret);
ret = cbor_encode_int(&map, mc.ext_cred_protect);
CHECK_CBOR_RET(ret);
}
if (mc.ext_hmac_secret) {
ret = cbor_encode_text_stringz(&map, "hmac-secret");
CHECK_CBOR_RET(ret);
ret = cbor_encode_boolean(&map, true);
CHECK_CBOR_RET(ret);
}
ret = cbor_encoder_close_container(&extension_encoder, &map);
CHECK_CBOR_RET(ret);
extension_size = cbor_encoder_get_buffer_size(&extension_encoder, extension_buffer);
DBG_MSG("extension_size=%zu\n", extension_size);
}
if (mc.ext_large_blob_key) {
if (mc.options.rk != OPTION_TRUE) {
DBG_MSG("largeBlobKey requires rk\n");
return CTAP2_ERR_INVALID_OPTION;
}
// Generate key in Step 17
}
if (mc.ext_has_cred_blob) {
bool accepted = false;
if (mc.ext_cred_blob_len <= MAX_CRED_BLOB_LENGTH && mc.options.rk == OPTION_TRUE) {
accepted = true;
}
ret = cbor_encode_text_stringz(&map, "credBlob");
CHECK_CBOR_RET(ret);
ret = cbor_encode_boolean(&map, accepted);
CHECK_CBOR_RET(ret);
}
if (mc.ext_cred_protect != CRED_PROTECT_ABSENT) {
ret = cbor_encode_text_stringz(&map, "credProtect");
CHECK_CBOR_RET(ret);
ret = cbor_encode_int(&map, mc.ext_cred_protect);
CHECK_CBOR_RET(ret);
}
if (mc.ext_hmac_secret) {
ret = cbor_encode_text_stringz(&map, "hmac-secret");
CHECK_CBOR_RET(ret);
ret = cbor_encode_boolean(&map, true);
CHECK_CBOR_RET(ret);
}
ret = cbor_encoder_close_container(&extension_encoder, &map);
CHECK_CBOR_RET(ret);
size_t extension_size = cbor_encoder_get_buffer_size(&extension_encoder, extension_buffer);

// Now prepare the response
ret = cbor_encoder_create_map(encoder, &map, 3 /*fmt, authData, attStmt*/ + (mc.ext_large_blob_key ? 1 : 0));
Expand Down Expand Up @@ -971,7 +977,8 @@ static uint8_t ctap_get_assertion(CborEncoder *encoder, uint8_t *params, size_t

// 8. [N/A] If evidence of user interaction was provided as part of Step 6.2
// 9. If the "up" option is set to true or not present:
if (ga.options.up == OPTION_TRUE) {
// Note: This step is skipped in authenticatorGetNextAssertion
if (credential_counter == 0 && ga.options.up == OPTION_TRUE) {
// a) If the pin_uv_auth_param parameter is present then:
if (ga.parsed_params & PARAM_PIN_UV_AUTH_PARAM) {
if (!cp_get_user_present_flag_value()) {
Expand All @@ -998,83 +1005,86 @@ static uint8_t ctap_get_assertion(CborEncoder *encoder, uint8_t *params, size_t
// authenticator data. The set of keys in the authenticator extension outputs map MUST be equal to, or a subset
// of, the keys of the authenticator extension inputs map.

uint8_t extension_buffer[134], extension_size = 0;
CborEncoder extension_encoder, map, sub_map;
// build extensions
cbor_encoder_init(&extension_encoder, extension_buffer, sizeof(extension_buffer), 0);
ret = cbor_encoder_create_map(&extension_encoder, &map,
(ga.ext_cred_blob ? 1 : 0) +
// largeBlobKey has no outputs here
((ga.parsed_params & PARAM_HMAC_SECRET) ? 1 : 0));
CHECK_CBOR_RET(ret);
// Process credProtect extension
if (!check_credential_protect_requirements(&dc.credential_id, ga.allow_list_size > 0, uv)) return CTAP2_ERR_NO_CREDENTIALS;

// Process credBlob extension
if (ga.ext_cred_blob) {
ret = cbor_encode_text_stringz(&map, "credBlob");
CHECK_CBOR_RET(ret);
ret = cbor_encode_byte_string(&map, dc.cred_blob, dc.cred_blob_len);
CborEncoder map, sub_map;
uint8_t extension_buffer[MAX_EXTENSION_SIZE_IN_AUTH];
size_t extension_size = 0;
uint8_t extension_map_items = (ga.ext_cred_blob ? 1 : 0) +
// largeBlobKey has no outputs here
((ga.parsed_params & PARAM_HMAC_SECRET) ? 1 : 0);
if (extension_map_items > 0) {
CborEncoder extension_encoder;
// build extensions
cbor_encoder_init(&extension_encoder, extension_buffer, sizeof(extension_buffer), 0);
ret = cbor_encoder_create_map(&extension_encoder, &map, extension_map_items);
CHECK_CBOR_RET(ret);
}

// Process credProtect extension
if (!check_credential_protect_requirements(&dc.credential_id, ga.allow_list_size > 0, uv)) return CTAP2_ERR_NO_CREDENTIALS;
// Process credBlob extension
if (ga.ext_cred_blob) {
ret = cbor_encode_text_stringz(&map, "credBlob");
CHECK_CBOR_RET(ret);
ret = cbor_encode_byte_string(&map, dc.cred_blob, dc.cred_blob_len);
CHECK_CBOR_RET(ret);
}

// Process hmac-secret extension
if (ga.parsed_params & PARAM_HMAC_SECRET) {
if (credential_counter == 0) {
// If "up" is set to false, authenticator returns CTAP2_ERR_UNSUPPORTED_OPTION.
if (!up) return CTAP2_ERR_UNSUPPORTED_OPTION;
ret = cp_decapsulate(ga.ext_hmac_secret_key_agreement, ga.ext_hmac_secret_pin_protocol);
CHECK_PARSER_RET(ret);
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;
// Process hmac-secret extension
if (ga.parsed_params & PARAM_HMAC_SECRET) {
if (credential_counter == 0) {
// If "up" is set to false, authenticator returns CTAP2_ERR_UNSUPPORTED_OPTION.
if (!up) return CTAP2_ERR_UNSUPPORTED_OPTION;
ret = cp_decapsulate(ga.ext_hmac_secret_key_agreement, ga.ext_hmac_secret_pin_protocol);
CHECK_PARSER_RET(ret);
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;
}
}
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");
uint8_t hmac_secret_output[HMAC_SECRET_SALT_IV_SIZE + HMAC_SECRET_SALT_SIZE];
DBG_MSG("hmac-secret-salt: ");
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_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,
hmac_secret_output, ga.ext_hmac_secret_pin_protocol) < 0)
return CTAP2_ERR_UNHANDLED_REQUEST;
DBG_MSG("hmac-secret output: ");
PRINT_HEX(hmac_secret_output, 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));
}
}
uint8_t hmac_secret_output[HMAC_SECRET_SALT_IV_SIZE + HMAC_SECRET_SALT_SIZE];
DBG_MSG("hmac-secret-salt: ");
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_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,
hmac_secret_output, ga.ext_hmac_secret_pin_protocol) < 0)
return CTAP2_ERR_UNHANDLED_REQUEST;
DBG_MSG("hmac-secret output: ");
PRINT_HEX(hmac_secret_output, 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_enc_len);
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_enc_len);
CHECK_CBOR_RET(ret);
}
ret = cbor_encoder_close_container(&extension_encoder, &map);
CHECK_CBOR_RET(ret);
extension_size = cbor_encoder_get_buffer_size(&extension_encoder, extension_buffer);
DBG_MSG("extension_size=%zu\n", extension_size);
}
ret = cbor_encoder_close_container(&extension_encoder, &map);
CHECK_CBOR_RET(ret);
extension_size = cbor_encoder_get_buffer_size(&extension_encoder, extension_buffer);
if (extension_size == 1) extension_size = 0; // Skip the empty map
DBG_MSG("extension_size=%hhu\n", extension_size);

// 13. Sign the client_data_hash along with authData with the selected credential.
uint8_t map_items = 3;
Expand Down
7 changes: 5 additions & 2 deletions test-real/test-gpg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ test_ImportedKeys() {
"10 9,12 9" #secp256k1
"10 1,12 1" #25519
"4 2048,6 2048" #RSA2048
"4 3072,6 3072" #RSA3072
"4 4096,6 4096" #RSA4096
)
for ((i = 0; i < ${#ALGO_PAIRS[@]}; i++)); do
Expand All @@ -188,10 +189,12 @@ test_ImportedKeys() {
}

test_GeneratedKeys() {
ALGO_PAIRS=("2 3" #ECC p-256
ALGO_PAIRS=("1 2048" #RSA2048
"1 3072" #RSA3072
"1 4096" #RSA4096
"2 3" #ECC p-256
"2 4" #ECC p-384
"2 9" #secp256k1
"1 2048" #RSA2048
"2 1" #25519
)
for ((i = 0; i < ${#ALGO_PAIRS[@]}; i++)); do
Expand Down
11 changes: 6 additions & 5 deletions test-real/test-libfido2.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export LANG=en_US.UTF8
export TEST_TMP_DIR=/tmp/canokey-libfido2
export USER=`id -nu`
PIN=123456
NUMBER_OF_KEYS=8
NON_TTY="setsid -w"

oneTimeSetUp() {
Expand Down Expand Up @@ -235,7 +236,7 @@ test_DelAllRk() {
test_MC() {
echo $'RelyingPartyID UserID UserName CredID'
>"$TEST_TMP_DIR/rks"
for((i=1;i<=64;i++)); do
for((i=1;i<=$NUMBER_OF_KEYS;i++)); do
local rpid=$(makeRPID $i)
local uname=$(makeUserName $i)
makeCredAndStore "$rpid" "$uname" || return 1
Expand All @@ -244,7 +245,7 @@ test_MC() {
while IFS= read -r line
do
if [[ $nline == 0 ]]; then
assertEquals 'existing rk(s): 64' "$line"
assertEquals "existing rk(s): $NUMBER_OF_KEYS" "$line"
else
local fields=($line)
rpid=$(makeRPID $nline)
Expand All @@ -255,7 +256,7 @@ test_MC() {
}

test_DispName() {
local randSeq=$(seq 1 64 | shuf)
local randSeq=$(seq 1 $NUMBER_OF_KEYS | shuf)
for i in $randSeq; do
local rpid=$(makeRPID $i)
local fields=($(grep $rpid "$TEST_TMP_DIR/rks"))
Expand Down Expand Up @@ -311,7 +312,7 @@ test_LargeBlob() {
}

test_DelRk() {
local randSeq=$(seq 1 64 | shuf)
local randSeq=$(seq 1 $NUMBER_OF_KEYS | shuf)
local nrDel=0
for i in $randSeq; do
local rpid=$(makeRPID $i)
Expand All @@ -324,7 +325,7 @@ test_DelRk() {
FIDO2DelRkByID $credid
sed -i "/$rpid/d" "$TEST_TMP_DIR/rks"
((nrDel++))
if [[ $nrDel == 1 || $nrDel == 2 || $nrDel == 10 || $nrDel == 64 ]];then
if [[ $nrDel == 1 || $nrDel == 2 || $nrDel == 10 || $nrDel == $NUMBER_OF_KEYS ]];then
compareAllRk || return 1
fi
done
Expand Down
2 changes: 1 addition & 1 deletion test-real/test-piv.sh
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ test_FactoryReset() {
test_FillData() {
openssl req -x509 -newkey rsa:2048 -keyout $TEST_TMP_DIR/key.pem -out $TEST_TMP_DIR/cert.pem -days 365 -nodes -subj "/CN=www.example.com"
assertEquals 'openssl gen key' 0 $?
for s in 9a 9c 9d 9e; do
for s in 9a 9c 9d 9e 82 83; do
PIVImportKeyCert $s $TEST_TMP_DIR/key.pem ../test-via-pcsc/long-cert.pem
assertEquals 'import-key' 0 $?
done
Expand Down
Loading