diff --git a/pico-hsm-sdk b/pico-hsm-sdk index 46e7d3a..2700e3a 160000 --- a/pico-hsm-sdk +++ b/pico-hsm-sdk @@ -1 +1 @@ -Subproject commit 46e7d3a181fab92fb8e394ed9b579b2b2f6a54a0 +Subproject commit 2700e3ae7a6bd6a43b8bdf39f243df8e8c52e7bf diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index 955ef61..4ffc8bd 100644 --- a/src/fido/cbor_make_credential.c +++ b/src/fido/cbor_make_credential.c @@ -464,12 +464,12 @@ int cbor_make_credential(const uint8_t *data, size_t len) { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03)); CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, - self_attestation == false ? 3 : 2)); + self_attestation == false || is_nitrokey ? 3 : 2)); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "alg")); - CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation ? -alg : -FIDO2_ALG_ES256)); + CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation || is_nitrokey ? -alg : -FIDO2_ALG_ES256)); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "sig")); CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, sig, olen)); - if (self_attestation == false) { + if (self_attestation == false || is_nitrokey) { CborEncoder arrEncoder; file_t *ef_cert = NULL; if (enterpriseAttestation == 2) { diff --git a/src/fido/files.c b/src/fido/files.c index e32fafd..f072c5b 100644 --- a/src/fido/files.c +++ b/src/fido/files.c @@ -46,6 +46,8 @@ file_t file_entries[] = { { .fid = EF_LARGEBLOB, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Large Blob + { .fid = EF_OTP_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, + .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, { .fid = 0x0000, .parent = 0xff, .name = NULL, .type = FILE_TYPE_UNKNOWN, .data = NULL, .ef_structure = 0, .acl = { 0 } } //end }; diff --git a/src/fido/files.h b/src/fido/files.h index 5ef2dc7..ee8a211 100644 --- a/src/fido/files.h +++ b/src/fido/files.h @@ -36,6 +36,7 @@ #define EF_OATH_CODE 0xBAFF #define EF_OTP_SLOT1 0xBB00 #define EF_OTP_SLOT2 0xBB01 +#define EF_OTP_PIN 0x10A0 // Nitrokey OTP PIN extern file_t *ef_keydev; extern file_t *ef_certdev; diff --git a/src/fido/oath.c b/src/fido/oath.c index c5c1f73..92a02b2 100644 --- a/src/fido/oath.c +++ b/src/fido/oath.c @@ -22,9 +22,11 @@ #include "random.h" #include "version.h" #include "asn1.h" +#include "crypto_utils.h" #define MAX_OATH_CRED 255 #define CHALLENGE_LEN 8 +#define MAX_OTP_COUNTER 3 #define TAG_NAME 0x71 #define TAG_NAME_LIST 0x72 @@ -38,6 +40,9 @@ #define TAG_IMF 0x7a #define TAG_ALGO 0x7b #define TAG_TOUCH_RESPONSE 0x7c +#define TAG_PASSWORD 0x80 +#define TAG_NEW_PASSWORD 0x81 +#define TAG_PIN_COUNTER 0x82 #define ALG_HMAC_SHA1 0x01 #define ALG_HMAC_SHA256 0x02 @@ -88,6 +93,16 @@ app_t *oath_select(app_t *a, const uint8_t *aid, uint8_t aid_len) { memcpy(res_APDU + res_APDU_size, challenge, sizeof(challenge)); res_APDU_size += sizeof(challenge); } + file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF); + if (file_has_data(ef_otp_pin)) { + const uint8_t *pin_data = file_get_data(ef_otp_pin); + res_APDU[res_APDU_size++] = TAG_PIN_COUNTER; + res_APDU[res_APDU_size++] = 1; + res_APDU[res_APDU_size++] = *pin_data; + } + res_APDU[res_APDU_size++] = TAG_ALGO; + res_APDU[res_APDU_size++] = 1; + res_APDU[res_APDU_size++] = ALG_HMAC_SHA1; apdu.ne = res_APDU_size; return a; } @@ -252,6 +267,8 @@ int cmd_reset() { } } delete_file(search_dynamic_file(EF_OATH_CODE)); + flash_clear_file(search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF)); + low_flash_available(); validated = true; return SW_OK(); } @@ -479,6 +496,119 @@ int cmd_send_remaining() { return SW_OK(); } +int cmd_set_otp_pin() { + size_t pw_len = 0; + uint8_t *pw = NULL, hsh[33] = { 0 }; + file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF); + if (file_has_data(ef_otp_pin)) { + return SW_CONDITIONS_NOT_SATISFIED(); + } + if (asn1_find_tag(apdu.data, apdu.nc, TAG_PASSWORD, &pw_len, &pw) == false) { + return SW_INCORRECT_PARAMS(); + } + hsh[0] = MAX_OTP_COUNTER; + double_hash_pin(pw, pw_len, hsh + 1); + flash_write_data_to_file(ef_otp_pin, hsh, sizeof(hsh)); + low_flash_available(); + return SW_OK(); +} + +int cmd_change_otp_pin() { + size_t pw_len = 0, new_pw_len = 0; + uint8_t *pw = NULL, *new_pw = NULL, hsh[33] = { 0 }; + file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF); + if (!file_has_data(ef_otp_pin)) { + return SW_CONDITIONS_NOT_SATISFIED(); + } + if (asn1_find_tag(apdu.data, apdu.nc, TAG_PASSWORD, &pw_len, &pw) == false) { + return SW_INCORRECT_PARAMS(); + } + double_hash_pin(pw, pw_len, hsh + 1); + if (memcmp(file_get_data(ef_otp_pin) + 1, hsh + 1, 32) != 0) { + return SW_SECURITY_STATUS_NOT_SATISFIED(); + } + if (asn1_find_tag(apdu.data, apdu.nc, TAG_NEW_PASSWORD, &new_pw_len, &new_pw) == false) { + return SW_INCORRECT_PARAMS(); + } + hsh[0] = MAX_OTP_COUNTER; + double_hash_pin(new_pw, new_pw_len, hsh + 1); + flash_write_data_to_file(ef_otp_pin, hsh, sizeof(hsh)); + low_flash_available(); + return SW_OK(); +} + +int cmd_verify_otp_pin() { + size_t pw_len = 0; + uint8_t *pw = NULL, hsh[33] = { 0 }, data_hsh[33]; + file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF); + if (!file_has_data(ef_otp_pin)) { + return SW_CONDITIONS_NOT_SATISFIED(); + } + if (asn1_find_tag(apdu.data, apdu.nc, TAG_PASSWORD, &pw_len, &pw) == false) { + return SW_INCORRECT_PARAMS(); + } + double_hash_pin(pw, pw_len, hsh + 1); + memcpy(data_hsh, file_get_data(ef_otp_pin), sizeof(data_hsh)); + if (data_hsh[0] == 0 || memcmp(data_hsh + 1, hsh + 1, 32) != 0) { + if (data_hsh[0] > 0) { + data_hsh[0] -= 1; + } + flash_write_data_to_file(ef_otp_pin, data_hsh, sizeof(data_hsh)); + low_flash_available(); + validated = false; + return SW_SECURITY_STATUS_NOT_SATISFIED(); + } + data_hsh[0] = MAX_OTP_COUNTER; + flash_write_data_to_file(ef_otp_pin, data_hsh, sizeof(data_hsh)); + low_flash_available(); + validated = true; + return SW_OK(); +} + +int cmd_verify_hotp() { + size_t key_len = 0, chal_len = 0, name_len = 0, code_len = 0; + uint8_t *key = NULL, *chal = NULL, *name = NULL, *code = NULL; + uint32_t code_int = 0; + if (asn1_find_tag(apdu.data, apdu.nc, TAG_NAME, &name_len, &name) == false) { + return SW_INCORRECT_PARAMS(); + } + file_t *ef = find_oath_cred(name, name_len); + if (file_has_data(ef) == false) { + return SW_DATA_INVALID(); + } + if (asn1_find_tag(file_get_data(ef), file_get_size(ef), TAG_KEY, &key_len, &key) == false) { + return SW_INCORRECT_PARAMS(); + } + + if ((key[0] & OATH_TYPE_MASK) != OATH_TYPE_HOTP) { + return SW_DATA_INVALID(); + } + if (asn1_find_tag(file_get_data(ef), file_get_size(ef), TAG_IMF, &chal_len, + &chal) == false) { + return SW_INCORRECT_PARAMS(); + } + if (asn1_find_tag(apdu.data, apdu.nc, TAG_RESPONSE, &code_len, &code) == true) { + code_int = (code[0] << 24) | (code[1] << 16) | (code[2] << 8) | code[3]; + } + + int ret = calculate_oath(0x01, key, key_len, chal, chal_len); + if (ret != CCID_OK) { + return SW_EXEC_ERROR(); + } + uint32_t res_int = (res_APDU[2] << 24) | (res_APDU[3] << 16) | (res_APDU[4] << 8) | res_APDU[5]; + if (res_APDU[1] == 6) { + res_int %= (uint32_t)1e6; + } + else { + res_int %= (uint32_t)1e8; + } + if (res_int != code_int) { + return SW_WRONG_DATA(); + } + res_APDU_size = apdu.ne = 0; + return SW_OK(); +} + #define INS_PUT 0x01 #define INS_DELETE 0x02 #define INS_SET_CODE 0x03 @@ -488,6 +618,10 @@ int cmd_send_remaining() { #define INS_VALIDATE 0xa3 #define INS_CALC_ALL 0xa4 #define INS_SEND_REMAINING 0xa5 +#define INS_VERIFY_CODE 0xb1 +#define INS_VERIFY_PIN 0xb2 +#define INS_CHANGE_PIN 0xb3 +#define INS_SET_PIN 0xb4 static const cmd_t cmds[] = { { INS_PUT, cmd_put }, @@ -499,6 +633,10 @@ static const cmd_t cmds[] = { { INS_CALCULATE, cmd_calculate }, { INS_CALC_ALL, cmd_calculate_all }, { INS_SEND_REMAINING, cmd_send_remaining }, + { INS_SET_PIN, cmd_set_otp_pin }, + { INS_CHANGE_PIN, cmd_change_otp_pin }, + { INS_VERIFY_PIN, cmd_verify_otp_pin }, + { INS_VERIFY_CODE, cmd_verify_hotp }, { 0x00, 0x0 } };