Skip to content

Commit 76205fa

Browse files
committed
securechip/optiga: limit unlock to 10 failed attempts
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. The approach is inspired by Trezor, which has a similar setup a small monotonic counter: https://github.com/trezor/trezor-firmware/blob/78cce0ba04436b2ec8a72d00d157a1dd37055572/core/embed/sec/optiga/optiga.c#L35 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.
1 parent f507206 commit 76205fa

File tree

11 files changed

+599
-75
lines changed

11 files changed

+599
-75
lines changed

src/atecc/atecc.c

+17-8
Original file line numberDiff line numberDiff line change
@@ -522,14 +522,6 @@ static ATCA_STATUS _update_kdf_key(void)
522522
ATECC_SLOT_KDF, 0, new_key, encryption_key, ATECC_SLOT_ENCRYPTION_KEY, nonce_contribution);
523523
}
524524

525-
bool atecc_update_keys(void)
526-
{
527-
if (_rollkey() != ATCA_SUCCESS) {
528-
return false;
529-
}
530-
return _update_kdf_key() == ATCA_SUCCESS;
531-
}
532-
533525
static int _atecc_kdf(atecc_slot_t slot, const uint8_t* msg, size_t len, uint8_t* kdf_out)
534526
{
535527
if (len > 127 || (slot != ATECC_SLOT_ROLLKEY && slot != ATECC_SLOT_KDF)) {
@@ -592,6 +584,15 @@ int atecc_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out)
592584
return _atecc_kdf(ATECC_SLOT_KDF, msg, len, kdf_out);
593585
}
594586

587+
int atecc_init_new_password(const char* password)
588+
{
589+
(void)password;
590+
if (!atecc_reset_keys()) {
591+
return SC_ATECC_ERR_RESET_KEYS;
592+
}
593+
return 0;
594+
}
595+
595596
int atecc_stretch_password(const char* password, uint8_t* stretched_out)
596597
{
597598
uint8_t password_salted_hashed[32] = {0};
@@ -641,6 +642,14 @@ int atecc_stretch_password(const char* password, uint8_t* stretched_out)
641642
return 0;
642643
}
643644

645+
bool atecc_reset_keys(void)
646+
{
647+
if (_rollkey() != ATCA_SUCCESS) {
648+
return false;
649+
}
650+
return _update_kdf_key() == ATCA_SUCCESS;
651+
}
652+
644653
bool atecc_gen_attestation_key(uint8_t* pubkey_out)
645654
{
646655
ATCA_STATUS result = _authorize_key();

src/atecc/atecc.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@
2626
#include <stdint.h>
2727

2828
USE_RESULT int atecc_setup(const securechip_interface_functions_t* ifs);
29-
USE_RESULT bool atecc_update_keys(void);
3029
USE_RESULT int atecc_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out);
30+
USE_RESULT int atecc_init_new_password(const char* password);
3131
USE_RESULT int atecc_stretch_password(const char* password, uint8_t* stretched_out);
32+
USE_RESULT bool atecc_reset_keys(void);
3233
USE_RESULT bool atecc_gen_attestation_key(uint8_t* pubkey_out);
3334
USE_RESULT bool atecc_attestation_sign(const uint8_t* challenge, uint8_t* signature_out);
3435
USE_RESULT bool atecc_monotonic_increments_remaining(uint32_t* remaining_out);

src/factorysetup.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,12 @@ static void _api_msg(const uint8_t* input, size_t in_len, uint8_t* output, size_
277277
screen_print_debug("DONE", 0);
278278
break;
279279
case OP_SC_ROLLKEYS:
280-
if (!securechip_update_keys()) {
281-
screen_print_debug("rollkeys: failed", 0);
280+
if (!securechip_reset_keys()) {
281+
screen_print_debug("resetting securechip keys: failed", 0);
282282
result = ERR_FAILED;
283283
break;
284284
}
285-
screen_print_debug("rollkeys: success", 100);
285+
screen_print_debug("resetting securechip keys: success", 100);
286286
if (!securechip_u2f_counter_set(0)) {
287287
screen_print_debug("reset u2f counter", 0);
288288
result = ERR_FAILED;

src/keystore.c

+11-7
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,17 @@ static keystore_error_t _get_and_decrypt_seed(
181181
uint8_t secret[32];
182182
UTIL_CLEANUP_32(secret);
183183
int stretch_result = securechip_stretch_password(password, secret);
184-
if (securechip_result_out != NULL) {
185-
*securechip_result_out = stretch_result;
186-
}
187184
if (stretch_result) {
185+
if (stretch_result == SC_ERR_INCORRECT_PASSWORD) {
186+
// Our Optiga securechip implementation fails password stretching if the password is
187+
// wrong, so we can early-abort here. The ATECC stretches the password without checking
188+
// if the password is correct, and we determine if it is correct in the seed decryption
189+
// step below.
190+
return KEYSTORE_ERR_INCORRECT_PASSWORD;
191+
}
192+
if (securechip_result_out != NULL) {
193+
*securechip_result_out = stretch_result;
194+
}
188195
return KEYSTORE_ERR_SECURECHIP;
189196
}
190197
if (encrypted_len < 49) {
@@ -239,10 +246,7 @@ keystore_error_t keystore_encrypt_and_store_seed(
239246
if (!_validate_seed_length(seed_length)) {
240247
return KEYSTORE_ERR_SEED_SIZE;
241248
}
242-
// Update the two kdf keys before setting a new password. This already
243-
// happens on a device reset, but we do it here again anyway so the keys are
244-
// initialized also on first use, reducing trust in the factory setup.
245-
if (!securechip_update_keys()) {
249+
if (securechip_init_new_password(password)) {
246250
return KEYSTORE_ERR_SECURECHIP;
247251
}
248252
uint8_t secret[32] = {0};

0 commit comments

Comments
 (0)