Make PIN DEK updates power-loss recoverable

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
Pol Henarejos
2026-08-14 19:35:12 +02:00
parent 86cc28d131
commit 090335b939
5 changed files with 152 additions and 12 deletions
+20 -6
View File
@@ -92,10 +92,6 @@ int cmd_change_pin(void) {
dhash[0] = new_pin_len;
dhash[1] = 0x1; // Format
pin_derive_verifier(CONST_BYTE_ARRAY(new_pin, new_pin_len), dhash + 2);
if ((r = file_put_data(pw, CONST_BYTE_ARRAY(dhash, sizeof(dhash)))) != PICOKEYS_OK) {
return SW_MEMORY_FAILURE();
}
if (P2(apdu) == 0x81) {
file_t *tf = file_search_by_fid(EF_DEK_PW1, NULL, SPECIFY_EF);
if (!tf) {
@@ -107,7 +103,16 @@ int cmd_change_pin(void) {
if ((r = encrypt_with_aad(session_pw1, CONST_BYTE_ARRAY(dek, DEK_SIZE), PIN_KDF_DEFAULT_VERSION, def + 1)) != PICOKEYS_OK) {
return SW_EXEC_ERROR();
}
r = file_put_data(tf, CONST_BYTE_ARRAY(def, sizeof(def)));
if ((r = pin_txn_stage(EF_PW1, dhash, session_pw1)) != PICOKEYS_OK) {
return SW_EXEC_ERROR();
}
r = file_put_data(pw, CONST_BYTE_ARRAY(dhash, sizeof(dhash)));
if (r == PICOKEYS_OK) {
r = file_put_data(tf, CONST_BYTE_ARRAY(def, sizeof(def)));
}
if (r == PICOKEYS_OK) {
r = pin_txn_delete(EF_PW1);
}
#ifdef ENABLE_ADMINLESS_MODE
if (r == PICOKEYS_OK && sync_adminless_pw3) {
r = openpgp_adminless_sync_pw3(new_pin, new_pin_len, dhash);
@@ -131,7 +136,16 @@ int cmd_change_pin(void) {
if ((r = encrypt_with_aad(session_pw3, CONST_BYTE_ARRAY(dek, DEK_SIZE), PIN_KDF_DEFAULT_VERSION, def + 1)) != PICOKEYS_OK) {
return SW_EXEC_ERROR();
}
r = file_put_data(tf, CONST_BYTE_ARRAY(def, sizeof(def)));
if ((r = pin_txn_stage(EF_PW3, dhash, session_pw3)) != PICOKEYS_OK) {
return SW_EXEC_ERROR();
}
r = file_put_data(pw, CONST_BYTE_ARRAY(dhash, sizeof(dhash)));
if (r == PICOKEYS_OK) {
r = file_put_data(tf, CONST_BYTE_ARRAY(def, sizeof(def)));
}
if (r == PICOKEYS_OK) {
r = pin_txn_delete(EF_PW3);
}
#ifdef ENABLE_ADMINLESS_MODE
if (r == PICOKEYS_OK) {
r = enable_adminless ? openpgp_adminless_enable() : clear_pw3 ? openpgp_adminless_reset() : openpgp_adminless_disable();
+9 -4
View File
@@ -129,10 +129,6 @@ int cmd_put_data(void) {
dhash[0] = apdu.nc;
dhash[1] = 0x1; // Format
pin_derive_verifier(CONST_BYTE_ARRAY(apdu.data, apdu.nc), dhash + 2);
if ((r = file_put_data(ef, CONST_BYTE_ARRAY(dhash, sizeof(dhash)))) != PICOKEYS_OK) {
return SW_MEMORY_FAILURE();
}
file_t *tf = file_search_by_fid(EF_DEK_RC, NULL, SPECIFY_EF);
if (!tf) {
return SW_REFERENCE_NOT_FOUND();
@@ -144,7 +140,16 @@ int cmd_put_data(void) {
if ((r = encrypt_with_aad(session_rc, CONST_BYTE_ARRAY(dek, DEK_SIZE), PIN_KDF_DEFAULT_VERSION, def + 1)) != PICOKEYS_OK) {
return SW_EXEC_ERROR();
}
if ((r = pin_txn_stage(EF_RC, dhash, session_rc)) != PICOKEYS_OK) {
return SW_EXEC_ERROR();
}
if ((r = file_put_data(ef, CONST_BYTE_ARRAY(dhash, sizeof(dhash)))) != PICOKEYS_OK) {
return SW_MEMORY_FAILURE();
}
r = file_put_data(tf, CONST_BYTE_ARRAY(def, sizeof(def)));
if (r == PICOKEYS_OK) {
r = pin_txn_delete(EF_RC);
}
if (r == PICOKEYS_OK) {
r = pin_reset_retries(ef, true);
}
+12 -2
View File
@@ -77,16 +77,26 @@ int cmd_reset_retry(void) {
uint8_t def[DEK_FILE_SIZE];
def[0] = 0x03;
pin_derive_session(CONST_BYTE_ARRAY(apdu.data + (apdu.nc - newpin_len), newpin_len), session_pw1);
encrypt_with_aad(session_pw1, CONST_BYTE_ARRAY(dek, DEK_SIZE), PIN_KDF_DEFAULT_VERSION, def + 1);
r = file_put_data(tf, CONST_BYTE_ARRAY(def, sizeof(def)));
if ((r = encrypt_with_aad(session_pw1, CONST_BYTE_ARRAY(dek, DEK_SIZE), PIN_KDF_DEFAULT_VERSION, def + 1)) != PICOKEYS_OK) {
return SW_EXEC_ERROR();
}
uint8_t dhash[34];
dhash[0] = newpin_len;
dhash[1] = 0x1; // Format
pin_derive_verifier(CONST_BYTE_ARRAY(apdu.data + (apdu.nc - newpin_len), newpin_len), dhash + 2);
if ((r = pin_txn_stage(EF_PW1, dhash, session_pw1)) != PICOKEYS_OK) {
return SW_MEMORY_FAILURE();
}
if ((r = file_put_data(pw, CONST_BYTE_ARRAY(dhash, sizeof(dhash)))) != PICOKEYS_OK) {
return SW_MEMORY_FAILURE();
}
if ((r = file_put_data(tf, CONST_BYTE_ARRAY(def, sizeof(def)))) != PICOKEYS_OK) {
return SW_MEMORY_FAILURE();
}
if ((r = pin_txn_delete(EF_PW1)) != PICOKEYS_OK) {
return SW_MEMORY_FAILURE();
}
#ifdef ENABLE_ADMINLESS_MODE
if (sync_adminless_pw3 && (r = openpgp_adminless_sync_pw3(apdu.data + (apdu.nc - newpin_len), newpin_len, dhash)) != PICOKEYS_OK) {
return SW_MEMORY_FAILURE();
+109
View File
@@ -33,6 +33,8 @@
#include "ccid/ccid.h"
#include "led/led.h"
#include "otp.h"
#include "object_provider.h"
#include "object_store.h"
#include "do.h"
#ifdef MBEDTLS_EDDSA_C
#include "mbedtls/eddsa.h"
@@ -532,6 +534,108 @@ void release_dek(void) {
extern bool has_pwpiv;
extern uint8_t session_pwpiv[32];
#define PIN_TXN_PAYLOAD_SIZE (2u + 34u + DEK_FILE_SIZE)
static const file_object_txn_layout_t pin_txn_layout_pw1 = {
.namespace_id = OPENPGP_OBJECT_NAMESPACE,
.object_type = EF_PW1,
.object_id = 1,
.record_fid = { 0x10a2, 0x10a3 },
.commit_fid = { 0x10a4, 0x10a5 }
};
static const file_object_txn_layout_t pin_txn_layout_rc = {
.namespace_id = OPENPGP_OBJECT_NAMESPACE,
.object_type = EF_RC,
.object_id = 1,
.record_fid = { 0x10a6, 0x10a7 },
.commit_fid = { 0x10a8, 0x10a9 }
};
static const file_object_txn_layout_t pin_txn_layout_pw3 = {
.namespace_id = OPENPGP_OBJECT_NAMESPACE,
.object_type = EF_PW3,
.object_id = 1,
.record_fid = { 0x10aa, 0x10ab },
.commit_fid = { 0x10ac, 0x10ad }
};
static const file_object_txn_layout_t *pin_txn_layout(uint16_t fid) {
if (fid == EF_PW1) {
return &pin_txn_layout_pw1;
}
if (fid == EF_RC) {
return &pin_txn_layout_rc;
}
if (fid == EF_PW3) {
return &pin_txn_layout_pw3;
}
return NULL;
}
int pin_txn_delete(uint16_t fid) {
const file_object_txn_layout_t *layout = pin_txn_layout(fid);
const file_object_authenticator_t *auth = openpgp_piv_object_manifest_authenticator();
if (!layout || !auth) {
return PICOKEYS_ERR_FILE_NOT_FOUND;
}
int r = file_object_txn_delete(layout, auth);
return r == PICOKEYS_ERR_FILE_NOT_FOUND ? PICOKEYS_OK : r;
}
int pin_txn_stage(uint16_t fid, const uint8_t verifier[34], const uint8_t *session) {
const file_object_txn_layout_t *layout = pin_txn_layout(fid);
const file_object_authenticator_t *auth = openpgp_piv_object_manifest_authenticator();
if (!layout || !auth || !verifier || !session) {
return PICOKEYS_ERR_FILE_NOT_FOUND;
}
uint8_t payload[PIN_TXN_PAYLOAD_SIZE];
put_uint16_be(fid, payload);
memcpy(payload + 2, verifier, 34);
payload[36] = 0x3;
int r = encrypt_with_aad(session, CONST_BYTE_ARRAY(dek, DEK_SIZE), PIN_KDF_DEFAULT_VERSION, payload + 37);
if (r == PICOKEYS_OK) {
r = file_object_txn_put(layout, auth, CONST_BYTE_ARRAY(payload, sizeof(payload)));
}
mbedtls_platform_zeroize(payload, sizeof(payload));
return r;
}
static int pin_txn_recover(const file_t *pin, const uint8_t *data, size_t len) {
const file_object_txn_layout_t *layout = pin_txn_layout(pin ? pin->fid : 0);
const file_object_authenticator_t *auth = openpgp_piv_object_manifest_authenticator();
if (!layout || !auth || !pin || !data) {
return PICOKEYS_OK;
}
file_object_txn_handle_t handle = FILE_OBJECT_TXN_INVALID_HANDLE;
int r = file_object_txn_open(layout, auth, &handle);
if (r == PICOKEYS_ERR_FILE_NOT_FOUND) {
return PICOKEYS_OK;
}
if (r != PICOKEYS_OK) {
return r;
}
uint8_t payload[PIN_TXN_PAYLOAD_SIZE];
r = file_object_txn_read_at(handle, auth, 0, BYTE_ARRAY(payload, sizeof(payload)));
file_object_txn_close(handle);
if (r != PICOKEYS_OK) {
return r;
}
if (get_uint16_be(payload) != pin->fid || file_get_size(pin) != 34 || memcmp(payload + 2, file_get_data(pin), 34) != 0) {
mbedtls_platform_zeroize(payload, sizeof(payload));
return pin_txn_delete(pin->fid);
}
uint8_t session[32];
pin_derive_session(CONST_BYTE_ARRAY(data, len), session);
r = decrypt_with_aad(session, CONST_BYTE_ARRAY(payload + 37, DEK_AAD_SIZE), PIN_KDF_DEFAULT_VERSION, dek);
mbedtls_platform_zeroize(session, sizeof(session));
if (r == PICOKEYS_OK) {
uint16_t dek_fid = pin->fid == EF_PW1 ? EF_DEK_PW1 : pin->fid == EF_PW3 ? EF_DEK_PW3 : EF_DEK_RC;
file_t *ef_dek = file_search_by_fid(dek_fid, NULL, SPECIFY_EF);
r = ef_dek ? file_put_data(ef_dek, CONST_BYTE_ARRAY(payload + 36, DEK_FILE_SIZE)) : PICOKEYS_ERR_FILE_NOT_FOUND;
}
mbedtls_platform_zeroize(payload, sizeof(payload));
return r == PICOKEYS_OK ? pin_txn_delete(pin->fid) : r;
}
int load_dek(void) {
if (!has_pw1 && !has_pw2 && !has_pw3 && !has_rc && !has_pwpiv) {
return PICOKEYS_NO_LOGIN;
@@ -1065,6 +1169,11 @@ int check_pin(const file_t *pin, const uint8_t *data, size_t len) {
has_pw3 = true;
pin_derive_session(CONST_BYTE_ARRAY(data, len), session_pw3);
}
if (pin->fid == EF_PW1 || pin->fid == EF_PW3 || pin->fid == EF_RC) {
if (pin_txn_recover(pin, data, len) != PICOKEYS_OK) {
return SW_EXEC_ERROR();
}
}
return SW_OK();
}
+2
View File
@@ -83,6 +83,8 @@ extern void signal_private_key_use(uint16_t uif_fid);
extern void scan_files_openpgp(void);
extern int load_aes_key(uint8_t *aes_key, size_t *key_size, file_t *fkey);
extern int load_key_data(file_t *fkey, byte_buffer_t *out, bool use_dek);
extern int pin_txn_stage(uint16_t fid, const uint8_t verifier[34], const uint8_t *session);
extern int pin_txn_delete(uint16_t fid);
extern int inc_sig_count(void);
int cmd_select(void);
int cmd_get_data(void);