From 4b5de68912bb91733d9f1521601f0c24a5038982 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 14 Aug 2026 23:47:48 +0200 Subject: [PATCH] Share spend-before-compare PIN verification Signed-off-by: Pol Henarejos --- src/openpgp/openpgp.c | 122 +++++++++++++++++++++++------------------- src/openpgp/openpgp.h | 2 + src/openpgp/piv.c | 7 +-- 3 files changed, 72 insertions(+), 59 deletions(-) diff --git a/src/openpgp/openpgp.c b/src/openpgp/openpgp.c index 67fd422..fbfd78a 100644 --- a/src/openpgp/openpgp.c +++ b/src/openpgp/openpgp.c @@ -1003,33 +1003,74 @@ int pin_reset_retries(const file_t *pin, bool force) { return r; } -static int pin_wrong_retry(const file_t *pin) { - if (!pin) { +int pin_spend_retry(const file_t *pin, uint8_t *remaining) { + if (!pin || !remaining) { return PICOKEYS_ERR_NULL_PARAM; } file_t *pw_status = file_search_by_fid(EF_PW_PRIV, NULL, SPECIFY_EF); - if (!pw_status) { + if (!pw_status || !file_has_data(pw_status)) { return PICOKEYS_ERR_FILE_NOT_FOUND; } - uint8_t p[64]; - uint16_t status_len = MIN(file_get_size(pw_status), sizeof(p)); - if (3u + (pin->fid & 0xfu) >= status_len) { + uint16_t status_len = file_get_size(pw_status); + uint16_t retry_index = 3u + (pin->fid & 0xfu); + if (status_len > 64u || retry_index >= status_len) { return PICOKEYS_ERR_MEMORY_FATAL; } - memcpy(p, file_get_data(pw_status), status_len); - if (p[3 + (pin->fid & 0xf)] > 0) { - p[3 + (pin->fid & 0xf)] -= 1; - int r = file_put_data(pw_status, CONST_BYTE_ARRAY(p, status_len)); - if (r != PICOKEYS_OK) { - return r; - } - flash_commit(); - if (p[3 + (pin->fid & 0xf)] == 0) { - return PICOKEYS_ERR_BLOCKED; - } - return p[3 + (pin->fid & 0xf)]; + uint8_t status[64]; + memcpy(status, file_get_data(pw_status), status_len); + if (status[retry_index] == 0) { + return PICOKEYS_ERR_BLOCKED; } - return PICOKEYS_ERR_BLOCKED; + *remaining = --status[retry_index]; + int r = file_put_data(pw_status, CONST_BYTE_ARRAY(status, status_len)); + if (r != PICOKEYS_OK) { + return r; + } + flash_commit(); + if (!file_has_data(pw_status) || file_get_size(pw_status) != status_len || file_get_data(pw_status)[retry_index] != *remaining) { + return PICOKEYS_ERR_MEMORY_FATAL; + } + return PICOKEYS_OK; +} + +int pin_check_verifier(const file_t *pin, const uint8_t *data, size_t len, uint8_t offset, bool *mismatch) { + if (!file_has_data(pin)) { + return SW_REFERENCE_NOT_FOUND(); + } + if (mismatch) { + *mismatch = false; + } + if (file_get_size(pin) != sizeof(uint8_t[32]) + offset || (offset == 2 && file_get_data(pin)[1] != 1u)) { + return SW_CONDITIONS_NOT_SATISFIED(); + } + uint8_t dhash[32]; + if (offset == 1) { + double_hash_pin(CONST_BYTE_ARRAY(data, len), dhash); + } + else { + pin_derive_verifier(CONST_BYTE_ARRAY(data, len), dhash); + } + uint8_t remaining = 0; + int r = pin_spend_retry(pin, &remaining); + if (r == PICOKEYS_ERR_BLOCKED) { + return SW_PIN_BLOCKED(); + } + if (r != PICOKEYS_OK) { + return SW_MEMORY_FAILURE(); + } + if (mbedtls_ct_memcmp(file_get_data(pin) + offset, dhash, sizeof(dhash)) != 0) { + if (mismatch) { + *mismatch = true; + } + if (remaining == 0) { + return SW_PIN_BLOCKED(); + } + return set_res_sw(0x63, 0xc0 | remaining); + } + if (pin_reset_retries(pin, true) != PICOKEYS_OK) { + return SW_MEMORY_FAILURE(); + } + return SW_OK(); } static void clear_pin_access_status(const file_t *pin) { @@ -1046,55 +1087,24 @@ static void clear_pin_access_status(const file_t *pin) { } } -static bool pin_retry_blocked(const file_t *pin) { - file_t *pw_status = file_search_by_fid(EF_PW_PRIV, NULL, SPECIFY_EF); - if (!pin || !pw_status || !file_has_data(pw_status)) { - return false; - } - uint16_t status_len = MIN(file_get_size(pw_status), 64u); - uint16_t retry_idx = 3u + (pin->fid & 0xfu); - return retry_idx < status_len && file_get_data(pw_status)[retry_idx] == 0; -} - int check_pin(const file_t *pin, const uint8_t *data, size_t len) { if (!file_has_data(pin)) { return SW_REFERENCE_NOT_FOUND(); } - if (pin_retry_blocked(pin)) { - return SW_PIN_BLOCKED(); - } if (offered_pin_len_impossible(pin, len)) { return SW_WRONG_DATA(); } isUserAuthenticated = false; //has_pw1 = has_pw3 = false; - uint8_t dhash[32], off = 2; - if (file_get_size(pin) == 33) { - off = 1; - double_hash_pin(CONST_BYTE_ARRAY(data, len), dhash); - } - else { - pin_derive_verifier(CONST_BYTE_ARRAY(data, len), dhash); - } - if (sizeof(dhash) != file_get_size(pin) - off) { //1 byte for pin len and 1 byte for format - return SW_CONDITIONS_NOT_SATISFIED(); - } - if (mbedtls_ct_memcmp(file_get_data(pin) + off, dhash, sizeof(dhash)) != 0) { + uint8_t off = file_get_size(pin) == 33 ? 1 : 2; + bool mismatch = false; + int r = pin_check_verifier(pin, data, len, off, &mismatch); + if (mismatch) { clear_pin_access_status(pin); - int retries; - if ((retries = pin_wrong_retry(pin)) < PICOKEYS_OK) { - return SW_PIN_BLOCKED(); - } - return set_res_sw(0x63, 0xc0 | retries); } - - int r = pin_reset_retries(pin, false); - if (r == PICOKEYS_ERR_BLOCKED) { - return SW_PIN_BLOCKED(); - } - if (r != PICOKEYS_OK) { - return SW_MEMORY_FAILURE(); + if (r != 0x9000) { + return r; } if (off == 1) { uint8_t pin_data[34], *pin_sp = NULL; diff --git a/src/openpgp/openpgp.h b/src/openpgp/openpgp.h index 02f19a4..7a0d593 100644 --- a/src/openpgp/openpgp.h +++ b/src/openpgp/openpgp.h @@ -48,6 +48,8 @@ extern int rsa_sign(mbedtls_rsa_context *ctx, const uint8_t *data, size_t data_l extern int load_private_key_rsa(mbedtls_rsa_context *ctx, file_t *fkey, bool use_dek); extern int load_private_key_ecdsa(mbedtls_ecdsa_context *ctx, file_t *fkey, bool use_dek); extern int pin_reset_retries(const file_t *pin, bool force); +extern int pin_spend_retry(const file_t *pin, uint8_t *remaining); +extern int pin_check_verifier(const file_t *pin, const uint8_t *data, size_t len, uint8_t offset, bool *mismatch); #define ALGO_RSA 0x01 #define ALGO_ECDH 0x12 diff --git a/src/openpgp/piv.c b/src/openpgp/piv.c index 91e6377..1d62587 100644 --- a/src/openpgp/piv.c +++ b/src/openpgp/piv.c @@ -472,7 +472,8 @@ static int cmd_piv_verify(void) { return SW_INCORRECT_PARAMS(); } if (apdu.nc > 0) { - uint16_t ret = check_pin(pw, apdu.data, apdu.nc); + has_pwpiv = false; + uint16_t ret = pin_check_verifier(pw, apdu.data, apdu.nc, 2, NULL); if (ret == 0x9000) { has_pwpiv = true; hash_multi(CONST_BYTE_ARRAY(apdu.data, apdu.nc), session_pwpiv); @@ -1335,7 +1336,7 @@ static int cmd_piv_change_pin(void) { if (!ef) { return SW_MEMORY_FAILURE(); } - uint16_t ret = check_pin(ef, old_pin, PIV_PIN_WIRE_SIZE); + uint16_t ret = pin_check_verifier(ef, old_pin, PIV_PIN_WIRE_SIZE, 2, NULL); if (ret != 0x9000) { return ret; } @@ -1361,7 +1362,7 @@ static int cmd_piv_reset_retry(void) { if (!ef) { return SW_MEMORY_FAILURE(); } - uint16_t ret = check_pin(ef, old_puk, PIV_PIN_WIRE_SIZE); + uint16_t ret = pin_check_verifier(ef, old_puk, PIV_PIN_WIRE_SIZE, 2, NULL); if (ret != 0x9000) { return ret; }