diff --git a/src/openpgp/cmd_change_pin.c b/src/openpgp/cmd_change_pin.c index 1ef5815..a6dc842 100644 --- a/src/openpgp/cmd_change_pin.c +++ b/src/openpgp/cmd_change_pin.c @@ -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(); diff --git a/src/openpgp/cmd_put_data.c b/src/openpgp/cmd_put_data.c index 0364660..ceb6a10 100644 --- a/src/openpgp/cmd_put_data.c +++ b/src/openpgp/cmd_put_data.c @@ -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); } diff --git a/src/openpgp/cmd_reset_retry.c b/src/openpgp/cmd_reset_retry.c index bb23c08..e545396 100644 --- a/src/openpgp/cmd_reset_retry.c +++ b/src/openpgp/cmd_reset_retry.c @@ -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(); diff --git a/src/openpgp/openpgp.c b/src/openpgp/openpgp.c index df83939..de9a5c1 100644 --- a/src/openpgp/openpgp.c +++ b/src/openpgp/openpgp.c @@ -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(); } diff --git a/src/openpgp/openpgp.h b/src/openpgp/openpgp.h index f1018a2..83471da 100644 --- a/src/openpgp/openpgp.h +++ b/src/openpgp/openpgp.h @@ -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);