Skip to content

Commit

Permalink
securechip/optiga: limit unlock to 10 failed attempts
Browse files Browse the repository at this point in the history
The MCU limits unlock attempts to 10 before resetting.

The ATECC securechip further limits the total unlock
attempts (successful or failed) to a large monotonic counter, ~730k.

In the Optiga we have the same, but with a lower limit due to the chip
spec (~600k).

This commit additionally adds a small counter that limits the unlocks
to 10 failed attempts, same as the MCU. When the counte reaches the
limit, no further attempts are possible until reset.

When the correct password is entered, the small counter resets to 0.

To achieve this, three new slots are added that specifically deal with
this small counter. The PASSWORD_SECRET is a key included in the
password key stretch, so overwriting/resetting invalidates the
password and makes it impossible to unlock or brute force.

The PASSWORD_SECRET is initialized to a random value when a password
is set. It also authorizes changing the PASSWORD object and the
PASSWORD_COUNTER small counter in order to reset it. It can only be
read when authorized using the PASSWORD object, i.e. when entering the
correct password, which allows us to reset the counter and also to use
the PASSWORD_SECRET in the password stretching.

securechip_init_new_password and securechip_reset_keys are added to
the securechip interface to set the new password and to reset all keys
involved in the key stretch and the password.

When the small counter threshold is reached, further attempts always
fail until reset. The MCU also keeps track of the 10 attempts and
resets after 10 failed ones, so the securechip error condition is
never exercised, similar to the large lifetime counter.
  • Loading branch information
benma committed Dec 18, 2024
1 parent 53908eb commit 13ce06b
Show file tree
Hide file tree
Showing 11 changed files with 619 additions and 78 deletions.
25 changes: 17 additions & 8 deletions src/atecc/atecc.c
Original file line number Diff line number Diff line change
Expand Up @@ -522,14 +522,6 @@ static ATCA_STATUS _update_kdf_key(void)
ATECC_SLOT_KDF, 0, new_key, encryption_key, ATECC_SLOT_ENCRYPTION_KEY, nonce_contribution);
}

bool atecc_update_keys(void)
{
if (_rollkey() != ATCA_SUCCESS) {
return false;
}
return _update_kdf_key() == ATCA_SUCCESS;
}

static int _atecc_kdf(atecc_slot_t slot, const uint8_t* msg, size_t len, uint8_t* kdf_out)
{
if (len > 127 || (slot != ATECC_SLOT_ROLLKEY && slot != ATECC_SLOT_KDF)) {
Expand Down Expand Up @@ -592,6 +584,15 @@ int atecc_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out)
return _atecc_kdf(ATECC_SLOT_KDF, msg, len, kdf_out);
}

int atecc_init_new_password(const char* password)
{
(void)password;
if (!atecc_reset_keys()) {
return SC_ATECC_ERR_RESET_KEYS;
}
return 0;
}

int atecc_stretch_password(const char* password, uint8_t* stretched_out)
{
uint8_t password_salted_hashed[32] = {0};
Expand Down Expand Up @@ -641,6 +642,14 @@ int atecc_stretch_password(const char* password, uint8_t* stretched_out)
return 0;
}

bool atecc_reset_keys(void)
{
if (_rollkey() != ATCA_SUCCESS) {
return false;
}
return _update_kdf_key() == ATCA_SUCCESS;
}

bool atecc_gen_attestation_key(uint8_t* pubkey_out)
{
ATCA_STATUS result = _authorize_key();
Expand Down
3 changes: 2 additions & 1 deletion src/atecc/atecc.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@
#include <stdint.h>

USE_RESULT int atecc_setup(const securechip_interface_functions_t* ifs);
USE_RESULT bool atecc_update_keys(void);
USE_RESULT int atecc_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out);
USE_RESULT int atecc_init_new_password(const char* password);
USE_RESULT int atecc_stretch_password(const char* password, uint8_t* stretched_out);
USE_RESULT bool atecc_reset_keys(void);
USE_RESULT bool atecc_gen_attestation_key(uint8_t* pubkey_out);
USE_RESULT bool atecc_attestation_sign(const uint8_t* challenge, uint8_t* signature_out);
USE_RESULT bool atecc_monotonic_increments_remaining(uint32_t* remaining_out);
Expand Down
6 changes: 3 additions & 3 deletions src/factorysetup.c
Original file line number Diff line number Diff line change
Expand Up @@ -277,12 +277,12 @@ static void _api_msg(const uint8_t* input, size_t in_len, uint8_t* output, size_
screen_print_debug("DONE", 0);
break;
case OP_SC_ROLLKEYS:
if (!securechip_update_keys()) {
screen_print_debug("rollkeys: failed", 0);
if (!securechip_reset_keys()) {
screen_print_debug("resetting securechip keys: failed", 0);
result = ERR_FAILED;
break;
}
screen_print_debug("rollkeys: success", 100);
screen_print_debug("resetting securechip keys: success", 100);
if (!securechip_u2f_counter_set(0)) {
screen_print_debug("reset u2f counter", 0);
result = ERR_FAILED;
Expand Down
18 changes: 11 additions & 7 deletions src/keystore.c
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,17 @@ static keystore_error_t _get_and_decrypt_seed(
uint8_t secret[32];
UTIL_CLEANUP_32(secret);
int stretch_result = securechip_stretch_password(password, secret);
if (securechip_result_out != NULL) {
*securechip_result_out = stretch_result;
}
if (stretch_result) {
if (stretch_result == SC_ERR_INCORRECT_PASSWORD) {
// Our Optiga securechip implementation fails password stretching if the password is
// wrong, so we can early-abort here. The ATECC stretches the password without checking
// if the password is correct, and we determine if it is correct in the seed decryption
// step below.
return KEYSTORE_ERR_INCORRECT_PASSWORD;
}
if (securechip_result_out != NULL) {
*securechip_result_out = stretch_result;
}
return KEYSTORE_ERR_SECURECHIP;
}
if (encrypted_len < 49) {
Expand Down Expand Up @@ -239,10 +246,7 @@ keystore_error_t keystore_encrypt_and_store_seed(
if (!_validate_seed_length(seed_length)) {
return KEYSTORE_ERR_SEED_SIZE;
}
// Update the two kdf keys before setting a new password. This already
// happens on a device reset, but we do it here again anyway so the keys are
// initialized also on first use, reducing trust in the factory setup.
if (!securechip_update_keys()) {
if (securechip_init_new_password(password)) {
return KEYSTORE_ERR_SECURECHIP;
}
uint8_t secret[32] = {0};
Expand Down
Loading

0 comments on commit 13ce06b

Please sign in to comment.