Share spend-before-compare PIN verification

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
Pol Henarejos
2026-08-14 23:47:48 +02:00
parent 69edb21d71
commit 4b5de68912
3 changed files with 72 additions and 59 deletions
+66 -56
View File
@@ -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;
+2
View File
@@ -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
+4 -3
View File
@@ -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;
}