diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d4ca4eba41..78ad5d53ca 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -141,6 +141,7 @@ set(PLATFORM-BITBOX02-SOURCES ${PLATFORM-BITBOX02-SOURCES} PARENT_SCOPE) set(SECURECHIP-SOURCES ${CMAKE_SOURCE_DIR}/src/atecc/atecc.c + ${CMAKE_SOURCE_DIR}/src/atecc/atecc_ops.c ${CMAKE_SOURCE_DIR}/src/optiga/pal/pal.c ${CMAKE_SOURCE_DIR}/src/optiga/pal/pal_gpio.c ${CMAKE_SOURCE_DIR}/src/optiga/pal/pal_i2c.c diff --git a/src/atecc/atecc.c b/src/atecc/atecc.c index 5698198d7c..511ca07e77 100644 --- a/src/atecc/atecc.c +++ b/src/atecc/atecc.c @@ -1,10 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "atecc.h" -#include "hardfault.h" -#include "securechip/securechip.h" #include -#include #include // disabling some warnings, as it's an external library. @@ -12,23 +9,12 @@ #pragma GCC diagnostic ignored "-Wint-conversion" #pragma GCC diagnostic ignored "-Wpedantic" #pragma GCC diagnostic ignored "-Wunused-parameter" +// clang-format off #include #include +// clang-format on #pragma GCC diagnostic pop -typedef enum { - ATECC_SLOT_IO_PROTECTION_KEY = 0, - ATECC_SLOT_AUTHKEY = 1, - ATECC_SLOT_ENCRYPTION_KEY = 2, - ATECC_SLOT_ROLLKEY = 3, - ATECC_SLOT_KDF = 4, - ATECC_SLOT_ATTESTATION = 5, - // Deprecated as the equivalent does not exist in the Optiga chip. - ATECC_SLOT_ECC_UNSAFE_SIGN_DEPRECATED = 6, - ATECC_SLOT_DATA0 = 9, - // The other slots are currently not in use. -} atecc_slot_t; - // Chip Configuration, generated with "make generate-atecc608-config" // The first 16 bytes, as well as the LockValue/LockConfig can't be changed and are ignored when // writing the configuration to the device. Locking is performed via the Lock command during setup, @@ -64,31 +50,19 @@ static uint8_t _configuration[ATCA_ECC_CONFIG_SIZE] = { // Number of times the first kdf slot can be used. #define MONOTONIC_COUNTER_MAX_USE (730500) -// This number of KDF iterations on the 2nd kdf slot when stretching the device -// password. -#define KDF_NUM_ITERATIONS (2) - -// The total individual size of the public key data slots (slots 9-15) is 72 bytes. Using encrypted -// read/write it is only possible to transmit 32 bytes. The last block is therefore 8 (72 = -// 32+32+8). -#define DATA_PUBLIC_KEY_SLOT_BLOCK_SIZE 32 - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpacked" -#pragma GCC diagnostic ignored "-Wattributes" -typedef union { - struct __attribute__((__packed__)) { - uint32_t u2f_counter; - } fields; - uint8_t bytes[DATA_PUBLIC_KEY_SLOT_BLOCK_SIZE]; -} data_9_0_t; - -#pragma GCC diagnostic pop - -static const securechip_interface_functions_t* _interface_functions = NULL; static uint8_t _serial_number[ATCA_SERIAL_NUM_SIZE] = {0}; static bool _serial_number_cached = false; +const uint8_t* atecc_serial_number(void) +{ + return _serial_number; +} + +bool atecc_serial_number_is_cached(void) +{ + return _serial_number_cached; +} + /** \brief initialize an I2C interface using given config. * \param[in] hal - opaque ptr to HAL data * \param[in] cfg - interface configuration @@ -226,11 +200,11 @@ static ATCA_STATUS _lock_slot(atecc_slot_t slot) return ATCA_SUCCESS; } -static ATCA_STATUS _factory_setup(void) +static ATCA_STATUS _factory_setup( + const uint8_t* io_protection_key, + const uint8_t* auth_key, + const uint8_t* encryption_key) { - if (_interface_functions == NULL) { - return (ATCA_STATUS)SC_ERR_IFS; - } bool is_config_locked = false; ATCA_STATUS result = atcab_is_locked(LOCK_ZONE_CONFIG, &is_config_locked); if (result != ATCA_SUCCESS) { @@ -271,26 +245,17 @@ static ATCA_STATUS _factory_setup(void) } if (is_config_locked && !is_data_locked) { // Write IO protection key. - uint8_t io_protection_key[32] = {0}; - UTIL_CLEANUP_32(io_protection_key); - _interface_functions->get_io_protection_key(io_protection_key); result = atcab_write_zone( ATCA_ZONE_DATA, ATECC_SLOT_IO_PROTECTION_KEY, 0, 0, io_protection_key, 32); if (result != ATCA_SUCCESS) { return result; } // Write auth key. - uint8_t auth_key[32] = {0}; - UTIL_CLEANUP_32(auth_key); - _interface_functions->get_auth_key(auth_key); result = atcab_write_zone(ATCA_ZONE_DATA, ATECC_SLOT_AUTHKEY, 0, 0, auth_key, 32); if (result != ATCA_SUCCESS) { return result; } // Write encryption key. - uint8_t encryption_key[32] = {0}; - UTIL_CLEANUP_32(encryption_key); - _interface_functions->get_encryption_key(encryption_key); result = atcab_write_zone(ATCA_ZONE_DATA, ATECC_SLOT_ENCRYPTION_KEY, 0, 0, encryption_key, 32); if (result != ATCA_SUCCESS) { @@ -375,19 +340,21 @@ static int _verify_config(void) return ATCA_SUCCESS; } -int atecc_setup(const securechip_interface_functions_t* ifs) +int atecc_setup( + const uint8_t* io_protection_key, + const uint8_t* auth_key, + const uint8_t* encryption_key) { - if (ifs == NULL) { + if (io_protection_key == NULL || auth_key == NULL || encryption_key == NULL) { return SC_ERR_IFS; } - _interface_functions = ifs; ATCA_STATUS result = atcab_init(&cfg); if (result != ATCA_SUCCESS) { return result; } #if FACTORYSETUP == 1 - result = _factory_setup(); + result = _factory_setup(io_protection_key, auth_key, encryption_key); if (result != ATCA_SUCCESS) { return result; } @@ -410,7 +377,7 @@ int atecc_setup(const securechip_interface_functions_t* ifs) * be called before using any slot requiring auth and whose KeyConfig.AuthKey is * ATECC_SLOT_AUTHKEY. */ -static ATCA_STATUS _authorize_key(void) +static ATCA_STATUS _authorize_key(const uint8_t* auth_key) { uint8_t num_in[NONCE_NUMIN_SIZE] = {0}; uint8_t rand_out[32] = {0}; @@ -436,18 +403,17 @@ static ATCA_STATUS _authorize_key(void) uint8_t response[32] = {0}; const uint8_t other_data[13] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - if (!_serial_number_cached) { - return ATCA_NOT_INITIALIZED; + uint8_t sn[9] = {0}; + result = atcab_read_serial_number(sn); + if (result != ATCA_SUCCESS) { + return result; } - uint8_t auth_key[32] = {0}; - UTIL_CLEANUP_32(auth_key); - _interface_functions->get_auth_key(auth_key); atca_check_mac_in_out_t checkmac_params = { // First SHA block from slot key, Second SHA block from TempKey. .mode = CHECKMAC_MODE_BLOCK2_TEMPKEY, .key_id = ATECC_SLOT_AUTHKEY, - .sn = _serial_number, + .sn = sn, .client_chal = NULL, // unused in this mode .client_resp = response, .other_data = other_data, @@ -470,318 +436,11 @@ static ATCA_STATUS _authorize_key(void) checkmac_params.other_data); } -/** - * Performs a roll-key operation on a ATECC_SLOT_ROLLKEY. - * @return ATCA_SUCCESS on success. - */ -static ATCA_STATUS _rollkey(void) +bool atecc_gen_attestation_key(const uint8_t* auth_key, uint8_t* pubkey_out) { - ATCA_STATUS result = _authorize_key(); - if (result != ATCA_SUCCESS) { - return result; - } - - uint8_t num_in[NONCE_NUMIN_SIZE] = {0}; - result = atcab_nonce_rand(num_in, NULL); - if (result != ATCA_SUCCESS) { - return result; - } - return atcab_derivekey(0, ATECC_SLOT_ROLLKEY, NULL); -} - -/** - * Writes a new random key to ATECC_SLOT_KDF. - * @return ATCA_SUCCESS on success. - */ -static ATCA_STATUS _update_kdf_key(void) -{ - uint8_t new_key[32] = {0}; - UTIL_CLEANUP_32(new_key); - _interface_functions->random_32_bytes(new_key); - uint8_t encryption_key[32] = {0}; - UTIL_CLEANUP_32(encryption_key); - _interface_functions->get_encryption_key(encryption_key); - - uint8_t nonce_contribution[32] = {0}; - UTIL_CLEANUP_32(nonce_contribution); - _interface_functions->random_32_bytes(nonce_contribution); -#if NONCE_NUMIN_SIZE > 32 - #error "size mismatch" -#endif - - ATCA_STATUS result = _authorize_key(); - if (result != ATCA_SUCCESS) { - return result; - } - - return atcab_write_enc( - ATECC_SLOT_KDF, 0, new_key, encryption_key, ATECC_SLOT_ENCRYPTION_KEY, nonce_contribution); -} - -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)) { - return SC_ERR_INVALID_ARGS; - } - if (msg == kdf_out) { - return SC_ERR_INVALID_ARGS; - } - - ATCA_STATUS result = _authorize_key(); - if (result != ATCA_SUCCESS) { - return result; - } - - uint8_t nonce_out[32] = {0}; - - // The result is hkdf_extract with the msg as ikm (input key material) and - // the slot key as the salt. hkdf info does not apply, as it is part of - // hkdf_expand, which is not performed. hkdf_extract is simply hmac. - // Python equivalent: - // import hmac, hashlib; hmac.new(slot_key, msg, hashlib.sha256).digest() - result = atcab_kdf( - KDF_MODE_SOURCE_SLOT | KDF_MODE_TARGET_OUTPUT_ENC | KDF_MODE_ALG_HKDF, - slot, - KDF_DETAILS_HKDF_MSG_LOC_INPUT | (len << 24), // << 24, not << 25 as - // described in the data - // sheet. - msg, - kdf_out, - nonce_out); - - // For PRF instead of HKDF, the Python equivalent is (msg = label+seed): - // from scapy.layers.tls.crypto.prf import _tls12_SHA256PRF - // _tls12_SHA256PRF(slot_key, msg, '', 32) - /* result = atcab_kdf( */ - /* KDF_MODE_SOURCE_SLOT | KDF_MODE_TARGET_OUTPUT | KDF_MODE_ALG_PRF, */ - /* slot, */ - /* KDF_DETAILS_PRF_KEY_LEN_32 | (len << 24), */ - /* msg, */ - /* kdf_out, */ - /* nonce_out); */ - if (result != ATCA_SUCCESS) { - return result; - } - // Output is encrypted with the io protection key. - uint8_t io_protection_key[32] = {0}; - UTIL_CLEANUP_32(io_protection_key); - _interface_functions->get_io_protection_key(io_protection_key); - atca_io_decrypt_in_out_t io_dec_params = { - .io_key = io_protection_key, - .out_nonce = nonce_out, - .data = kdf_out, - .data_size = 32, - }; - return atcah_io_decrypt(&io_dec_params); -} - -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, - securechip_password_stretch_algo_t password_stretch_algo, - uint8_t* stretched_out) -{ - (void)password; - if (password_stretch_algo != SECURECHIP_PASSWORD_STRETCH_ALGO_V0) { - return SC_ERR_INVALID_PASSWORD_STRETCH_ALGO; - } - if (!atecc_reset_keys()) { - return SC_ATECC_ERR_RESET_KEYS; - } - return atecc_stretch_password(password, password_stretch_algo, stretched_out); -} - -int atecc_stretch_password( - const char* password, - securechip_password_stretch_algo_t password_stretch_algo, - uint8_t* stretched_out) -{ - if (password_stretch_algo != SECURECHIP_PASSWORD_STRETCH_ALGO_V0) { - return SC_ERR_INVALID_PASSWORD_STRETCH_ALGO; - } - - uint8_t password_salted_hashed[32] = {0}; - UTIL_CLEANUP_32(password_salted_hashed); - if (!rust_salt_hash_data( - rust_util_bytes((const uint8_t*)password, strlen(password)), - "keystore_seed_access_in", - rust_util_bytes_mut(password_salted_hashed, sizeof(password_salted_hashed)))) { - return SC_ERR_SALT; - } - - uint8_t kdf_in[32] = {0}; - UTIL_CLEANUP_32(kdf_in); - memcpy(kdf_in, password_salted_hashed, 32); - - // First KDF on rollkey increments the monotonic counter. Call only once! - int securechip_result = _atecc_kdf(ATECC_SLOT_ROLLKEY, kdf_in, 32, stretched_out); - if (securechip_result) { - return securechip_result; - } - // Second KDF does not use the counter and we call it multiple times. - for (int i = 0; i < KDF_NUM_ITERATIONS; i++) { - memcpy(kdf_in, stretched_out, 32); - securechip_result = atecc_kdf(kdf_in, 32, stretched_out); - if (securechip_result) { - return securechip_result; - } - } - - if (!rust_salt_hash_data( - rust_util_bytes((const uint8_t*)password, strlen(password)), - "keystore_seed_access_out", - rust_util_bytes_mut(password_salted_hashed, sizeof(password_salted_hashed)))) { - return SC_ERR_SALT; - } - rust_hmac_sha256( - password_salted_hashed, sizeof(password_salted_hashed), stretched_out, 32, 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(); + ATCA_STATUS result = _authorize_key(auth_key); if (result != ATCA_SUCCESS) { return false; } return atcab_genkey(ATECC_SLOT_ATTESTATION, pubkey_out) == ATCA_SUCCESS; } - -bool atecc_attestation_sign(const uint8_t* msg, uint8_t* signature_out) -{ - ATCA_STATUS result = _authorize_key(); - if (result != ATCA_SUCCESS) { - return false; - } - return atcab_sign(ATECC_SLOT_ATTESTATION, msg, signature_out) == ATCA_SUCCESS; -} - -bool atecc_monotonic_increments_remaining(uint32_t* remaining_out) -{ - uint32_t counter; - if (atcab_counter_read(0, &counter) != ATCA_SUCCESS) { - return false; - } - if (COUNTER_MAX_VALUE < counter) { - Abort("ATECC returned an invalid value"); - } - *remaining_out = COUNTER_MAX_VALUE - counter; - return true; -} - -int atecc_random(uint8_t* rand_out) -{ - ATCA_STATUS result = ATCA_GEN_FAIL; - for (int retries = 0; retries < 5; retries++) { - result = atcab_random(rand_out); - if (result == ATCA_SUCCESS) { - return 0; - } - } - return result; -} - -#if APP_U2F == 1 || FACTORYSETUP == 1 -// Read a "standard" sized block from a data slot (must be 32 bytes) -static bool _read_data_slot_block(uint8_t* bytes, uint16_t slot, uint8_t block) -{ - uint8_t encryption_key[32] = {0}; - UTIL_CLEANUP_32(encryption_key); - _interface_functions->get_encryption_key(encryption_key); - - uint8_t nonce_contribution[32] = {0}; - UTIL_CLEANUP_32(nonce_contribution); - _interface_functions->random_32_bytes(nonce_contribution); - #if NONCE_NUMIN_SIZE > 32 - #error "size mismatch" - #endif - - ATCA_STATUS result = _authorize_key(); - if (result != ATCA_SUCCESS) { - return false; - } - return atcab_read_enc( - slot, block, bytes, encryption_key, ATECC_SLOT_ENCRYPTION_KEY, nonce_contribution) == - ATCA_SUCCESS; -} - -// Write a "standard" sized block from a data slot (must be 32 bytes) -static bool _write_data_slot_block(uint8_t* bytes, uint16_t slot, uint8_t block) -{ - uint8_t encryption_key[32] = {0}; - UTIL_CLEANUP_32(encryption_key); - _interface_functions->get_encryption_key(encryption_key); - - uint8_t nonce_contribution[32] = {0}; - UTIL_CLEANUP_32(nonce_contribution); - _interface_functions->random_32_bytes(nonce_contribution); - #if NONCE_NUMIN_SIZE > 32 - #error "size mismatch" - #endif - - ATCA_STATUS result = _authorize_key(); - if (result != ATCA_SUCCESS) { - return false; - } - result = atcab_write_enc( - slot, block, bytes, encryption_key, ATECC_SLOT_ENCRYPTION_KEY, nonce_contribution); - if (result != ATCA_SUCCESS) { - return false; - } - // Double-check by reading it back and comparing. - uint8_t written_bytes[32] = {0}; - if (!_read_data_slot_block(written_bytes, slot, block)) { - return false; - } - return MEMEQ(written_bytes, bytes, sizeof(written_bytes)); -} - -bool atecc_u2f_counter_set(uint32_t counter) -{ - data_9_0_t data = {0}; - if (!_read_data_slot_block(&data.bytes[0], ATECC_SLOT_DATA0, 0)) { - return false; - } - - data.fields.u2f_counter = counter; - - return _write_data_slot_block(&data.bytes[0], ATECC_SLOT_DATA0, 0); -} -#endif - -#if APP_U2F == 1 -bool atecc_u2f_counter_inc(uint32_t* counter) -{ - data_9_0_t data = {0}; - if (!_read_data_slot_block(&data.bytes[0], ATECC_SLOT_DATA0, 0)) { - return false; - } - - data.fields.u2f_counter += 1; - *counter = data.fields.u2f_counter; - - return _write_data_slot_block(&data.bytes[0], ATECC_SLOT_DATA0, 0); -} -#endif - -bool atecc_model(securechip_model_t* model_out) -{ - uint8_t revision[4] = {0}; - if (atcab_info(revision) != ATCA_SUCCESS) { - return false; - } - *model_out = revision[3] >= 0x03 ? ATECC_ATECC608B : ATECC_ATECC608A; - return true; -} diff --git a/src/atecc/atecc.h b/src/atecc/atecc.h index a3a5ec810b..4ffc3348a4 100644 --- a/src/atecc/atecc.h +++ b/src/atecc/atecc.h @@ -12,27 +12,76 @@ #include #include -USE_RESULT int atecc_setup(const securechip_interface_functions_t* ifs); -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, - securechip_password_stretch_algo_t password_stretch_algo, - uint8_t* stretched_out); -USE_RESULT int atecc_stretch_password( - const char* password, - securechip_password_stretch_algo_t password_stretch_algo, - 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); -USE_RESULT int atecc_random(uint8_t* rand_out); -#if APP_U2F == 1 || FACTORYSETUP == 1 -USE_RESULT bool atecc_u2f_counter_set(uint32_t counter); -#endif -#if APP_U2F == 1 -USE_RESULT bool atecc_u2f_counter_inc(uint32_t* counter); -#endif -USE_RESULT bool atecc_model(securechip_model_t* model_out); +typedef enum { + ATECC_SLOT_IO_PROTECTION_KEY = 0, + ATECC_SLOT_AUTHKEY = 1, + ATECC_SLOT_ENCRYPTION_KEY = 2, + ATECC_SLOT_ROLLKEY = 3, + ATECC_SLOT_KDF = 4, + ATECC_SLOT_ATTESTATION = 5, + // Deprecated as the equivalent does not exist in the Optiga chip. + ATECC_SLOT_ECC_UNSAFE_SIGN_DEPRECATED = 6, + ATECC_SLOT_DATA0 = 9, + // The other slots are currently not in use. +} atecc_slot_t; + +USE_RESULT int atecc_setup( + const uint8_t* io_protection_key, + const uint8_t* auth_key, + const uint8_t* encryption_key); +USE_RESULT bool atecc_gen_attestation_key(const uint8_t* auth_key, uint8_t* pubkey_out); + +const uint8_t* atecc_serial_number(void); +bool atecc_serial_number_is_cached(void); + +#define ATECC_OPS_STATUS_BUSY 0x100 + +int atecc_ops_get_status(void); +uint32_t atecc_ops_get_poll_delay_ms(void); +void atecc_ops_poll(void); + +USE_RESULT int atecc_cmd_start_nonce_rand(const uint8_t* num_in); +USE_RESULT int atecc_cmd_start_checkmac(const uint8_t* response); +USE_RESULT int atecc_cmd_start_random(void); +USE_RESULT int atecc_cmd_start_counter_read(void); +USE_RESULT int atecc_cmd_start_info_revision(void); +USE_RESULT int atecc_cmd_start_kdf(atecc_slot_t slot, const uint8_t* msg, size_t len); +USE_RESULT int atecc_cmd_start_derivekey_rollkey(void); +USE_RESULT int atecc_cmd_start_nonce_load_msgdigest(const uint8_t* msg); +USE_RESULT int atecc_cmd_start_sign_attestation(void); +USE_RESULT int atecc_cmd_start_gendig_encryption_key(void); +USE_RESULT int atecc_cmd_start_read_block(uint16_t slot, uint8_t block); +USE_RESULT int atecc_cmd_start_write_encrypted_block( + uint16_t slot, + uint8_t block, + const uint8_t* value, + const uint8_t* mac); + +USE_RESULT int atecc_cmd_read_random_response(uint8_t* out); +USE_RESULT int atecc_cmd_read_counter_response(uint32_t* counter_out); +USE_RESULT int atecc_cmd_read_info_response(uint8_t* out); +USE_RESULT int atecc_cmd_read_kdf_response(uint8_t* out_data, uint8_t* out_nonce); +USE_RESULT int atecc_cmd_read_sign_response(uint8_t* signature_out); +USE_RESULT int atecc_cmd_read_block_response(uint8_t* out); + +USE_RESULT int atecc_auth_compute_response( + const uint8_t* num_in, + const uint8_t* rand_out, + const uint8_t* auth_key, + uint8_t* response_out); +USE_RESULT int atecc_kdf_decrypt( + const uint8_t* io_protection_key, + const uint8_t* nonce_out, + uint8_t* data, + size_t data_size); +USE_RESULT int atecc_io_prepare_tempkey(const uint8_t* num_in, const uint8_t* rand_out); +USE_RESULT int atecc_io_apply_gendig(const uint8_t* encryption_key); +USE_RESULT int atecc_io_prepare_encrypted_write( + uint16_t key_id, + uint8_t block, + const uint8_t* input_data, + uint8_t* encrypted_out, + uint8_t* mac_out); +USE_RESULT int atecc_io_decrypt_block(uint8_t* data, size_t len); #endif diff --git a/src/atecc/atecc_ops.c b/src/atecc/atecc_ops.c new file mode 100644 index 0000000000..4e24e1c55b --- /dev/null +++ b/src/atecc/atecc_ops.c @@ -0,0 +1,760 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "atecc.h" + +#include +#include + +// disabling some warnings, as it's an external library. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wint-conversion" +#pragma GCC diagnostic ignored "-Wpedantic" +#pragma GCC diagnostic ignored "-Wunused-parameter" +// clang-format off +#include +#include +#include +#include +// clang-format on +#pragma GCC diagnostic pop + +// NOLINTBEGIN(bugprone-assignment-in-if-condition) + +typedef struct { + volatile int status; + struct { + bool active; + ATCADevice device; + uint32_t max_delay_count; + uint32_t next_poll_delay_ms; + } command; + ATCAPacket packet; + atca_temp_key_t io_temp_key; + uint8_t io_other_data[4]; +} atecc_async_ctx_t; + +static atecc_async_ctx_t _async = { + .status = ATCA_SUCCESS, +}; + +static void _atecc_async_reset_command(void) +{ + memset(&_async.command, 0, sizeof(_async.command)); + memset(&_async.packet, 0, sizeof(_async.packet)); + _async.status = ATECC_OPS_STATUS_BUSY; +} + +static void _atecc_async_finish(int status) +{ + _async.command.active = false; + _async.command.next_poll_delay_ms = 0; + _async.status = status; +} + +static ATCA_STATUS _atecc_async_init_command_device(void) +{ + _async.command.device = atcab_get_device(); + if (_async.command.device == NULL) { + return ATCA_NOT_INITIALIZED; + } + return ATCA_SUCCESS; +} + +static void _atecc_async_complete_command(ATCA_STATUS status) +{ + if (_async.command.device != NULL) { + atidle(_async.command.device->mIface); + } + _atecc_async_finish(status); +} + +// Async counterpart to the wake/send half of cryptoauthlib's calib_execute_command(). +static ATCA_STATUS _atecc_async_start_command(void) +{ + // Ignored by our ATCA_CUSTOM_IFACE HAL; set only for cryptoauthlib's native I2C iface below. + uint8_t word_address = 0xFF; + ATCA_STATUS status; + + _async.command.device = atcab_get_device(); + if (_async.command.device == NULL) { + return ATCA_NOT_INITIALIZED; + } + +#ifdef ATCA_NO_POLL + if ((status = calib_get_execution_time( + _async.packet.opcode, _async.command.device->mCommands)) != ATCA_SUCCESS) { + return status; + } + _async.command.max_delay_count = 0; + _async.command.next_poll_delay_ms = _async.command.device->mCommands->execution_time_msec; +#else + _async.command.max_delay_count = ATCA_POLLING_MAX_TIME_MSEC / ATCA_POLLING_FREQUENCY_TIME_MSEC; + _async.command.next_poll_delay_ms = ATCA_POLLING_INIT_TIME_MSEC; +#endif + + _async.command.active = true; + + if ((status = atwake(_async.command.device->mIface)) != ATCA_SUCCESS) { + _atecc_async_complete_command(status); + return status; + } + + if (ATCA_I2C_IFACE == _async.command.device->mIface->mIfaceCFG->iface_type) { + word_address = 0x03; + } + if ((status = atsend( + _async.command.device->mIface, + word_address, + (uint8_t*)&_async.packet, + _async.packet.txsize)) != ATCA_SUCCESS) { + _atecc_async_complete_command(status); + return status; + } + + return ATCA_SUCCESS; +} + +// Async counterpart to the receive/poll half of cryptoauthlib's calib_execute_command(). +static void _atecc_async_poll_command(void) +{ + ATCA_STATUS status; + uint16_t rxsize; + + if (_async.status != ATECC_OPS_STATUS_BUSY || !_async.command.active) { + return; + } + + memset(_async.packet.data, 0, sizeof(_async.packet.data)); + rxsize = sizeof(_async.packet.data); + status = atreceive(_async.command.device->mIface, 0, _async.packet.data, &rxsize); + if (status == ATCA_SUCCESS) { + if (rxsize < 4) { + status = rxsize > 0 ? ATCA_RX_FAIL : ATCA_RX_NO_RESPONSE; + } else if ((status = atCheckCrc(_async.packet.data)) == ATCA_SUCCESS) { + status = isATCAError(_async.packet.data); + } + _atecc_async_complete_command(status); + return; + } + +#ifndef ATCA_NO_POLL + if (_async.command.max_delay_count-- > 0) { + _async.command.next_poll_delay_ms = ATCA_POLLING_FREQUENCY_TIME_MSEC; + return; + } +#endif + + _atecc_async_complete_command(status); +} + +static int _atecc_async_launch(int status) +{ + if (status != ATCA_SUCCESS && _async.status == ATECC_OPS_STATUS_BUSY) { + _atecc_async_finish(status); + } + return status; +} + +// Ported from calib_nonce_rand() / calib_nonce_base() in cryptoauthlib/lib/calib/calib_nonce.c. +static ATCA_STATUS _atecc_async_start_nonce_rand(const uint8_t* num_in) +{ + ATCA_STATUS status; + + if ((status = _atecc_async_init_command_device()) != ATCA_SUCCESS) { + return status; + } + _async.packet.param1 = NONCE_MODE_SEED_UPDATE; + _async.packet.param2 = 0; + memcpy(_async.packet.data, num_in, NONCE_NUMIN_SIZE); + if ((status = atNonce(_async.command.device->mCommands, &_async.packet)) != ATCA_SUCCESS) { + return status; + } + return _atecc_async_start_command(); +} + +// Ported from calib_nonce_load() / calib_nonce_base() in cryptoauthlib/lib/calib/calib_nonce.c. +static ATCA_STATUS _atecc_async_start_nonce_load( + uint8_t target, + const uint8_t* num_in, + uint16_t num_in_size) +{ + uint8_t mode = NONCE_MODE_PASSTHROUGH | (NONCE_MODE_TARGET_MASK & target); + ATCA_STATUS status; + + if ((status = _atecc_async_init_command_device()) != ATCA_SUCCESS) { + return status; + } + if (num_in_size == 32) { + mode |= NONCE_MODE_INPUT_LEN_32; + } else if (num_in_size == 64) { + mode |= NONCE_MODE_INPUT_LEN_64; + } else { + return ATCA_BAD_PARAM; + } + + _async.packet.param1 = mode; + _async.packet.param2 = 0; + memcpy(_async.packet.data, num_in, num_in_size); + if ((status = atNonce(_async.command.device->mCommands, &_async.packet)) != ATCA_SUCCESS) { + return status; + } + return _atecc_async_start_command(); +} + +// Ported from calib_checkmac() in cryptoauthlib/lib/calib/calib_checkmac.c. +static ATCA_STATUS _atecc_async_start_checkmac(const uint8_t* response) +{ + static const uint8_t other_data[13] = {0}; + ATCA_STATUS status; + + if ((status = _atecc_async_init_command_device()) != ATCA_SUCCESS) { + return status; + } + _async.packet.param1 = CHECKMAC_MODE_BLOCK2_TEMPKEY; + _async.packet.param2 = ATECC_SLOT_AUTHKEY; + memset(&_async.packet.data[0], 0, CHECKMAC_CLIENT_CHALLENGE_SIZE); + memcpy(&_async.packet.data[32], response, CHECKMAC_CLIENT_RESPONSE_SIZE); + memcpy(&_async.packet.data[64], other_data, CHECKMAC_OTHER_DATA_SIZE); + if ((status = atCheckMAC(_async.command.device->mCommands, &_async.packet)) != ATCA_SUCCESS) { + return status; + } + return _atecc_async_start_command(); +} + +// Ported from calib_random() in cryptoauthlib/lib/calib/calib_random.c. +static ATCA_STATUS _atecc_async_start_random_command(void) +{ + ATCA_STATUS status; + + if ((status = _atecc_async_init_command_device()) != ATCA_SUCCESS) { + return status; + } + _async.packet.param1 = RANDOM_SEED_UPDATE; + _async.packet.param2 = 0; + if ((status = atRandom(_async.command.device->mCommands, &_async.packet)) != ATCA_SUCCESS) { + return status; + } + return _atecc_async_start_command(); +} + +// Ported from calib_counter_read() / calib_counter() in cryptoauthlib/lib/calib/calib_counter.c. +static ATCA_STATUS _atecc_async_start_counter_read_command(uint16_t counter_id) +{ + ATCA_STATUS status; + + if ((status = _atecc_async_init_command_device()) != ATCA_SUCCESS) { + return status; + } + _async.packet.param1 = COUNTER_MODE_READ; + _async.packet.param2 = counter_id; + if ((status = atCounter(_async.command.device->mCommands, &_async.packet)) != ATCA_SUCCESS) { + return status; + } + return _atecc_async_start_command(); +} + +// Ported from calib_info() / calib_info_base() in cryptoauthlib/lib/calib/calib_info.c. +static ATCA_STATUS _atecc_async_start_info_revision_command(void) +{ + ATCA_STATUS status; + + if ((status = _atecc_async_init_command_device()) != ATCA_SUCCESS) { + return status; + } + _async.packet.param1 = INFO_MODE_REVISION; + _async.packet.param2 = 0; + if ((status = atInfo(_async.command.device->mCommands, &_async.packet)) != ATCA_SUCCESS) { + return status; + } + return _atecc_async_start_command(); +} + +// Ported from calib_kdf() in cryptoauthlib/lib/calib/calib_kdf.c. +static ATCA_STATUS _atecc_async_start_kdf_command(atecc_slot_t slot, const uint8_t* msg, size_t len) +{ + ATCA_STATUS status; + + if ((status = _atecc_async_init_command_device()) != ATCA_SUCCESS) { + return status; + } + _async.packet.param1 = KDF_MODE_SOURCE_SLOT | KDF_MODE_TARGET_OUTPUT_ENC | KDF_MODE_ALG_HKDF; + _async.packet.param2 = slot; + _async.packet.data[0] = KDF_DETAILS_HKDF_MSG_LOC_INPUT; + _async.packet.data[1] = 0; + _async.packet.data[2] = 0; + _async.packet.data[3] = (uint8_t)len; + memcpy(&_async.packet.data[KDF_DETAILS_SIZE], msg, len); + if ((status = atKDF(_async.command.device->mCommands, &_async.packet)) != ATCA_SUCCESS) { + return status; + } + return _atecc_async_start_command(); +} + +// Ported from calib_derivekey() in cryptoauthlib/lib/calib/calib_derivekey.c. +static ATCA_STATUS _atecc_async_start_derivekey_command(uint8_t mode, uint16_t target_key) +{ + ATCA_STATUS status; + + if ((status = _atecc_async_init_command_device()) != ATCA_SUCCESS) { + return status; + } + _async.packet.param1 = mode; + _async.packet.param2 = target_key; + if ((status = atDeriveKey(_async.command.device->mCommands, &_async.packet, false)) != + ATCA_SUCCESS) { + return status; + } + return _atecc_async_start_command(); +} + +// Ported from calib_gendig() in cryptoauthlib/lib/calib/calib_gendig.c. +static ATCA_STATUS _atecc_async_start_gendig_command( + uint8_t zone, + uint16_t key_id, + const uint8_t* other_data, + uint8_t other_data_size) +{ + ATCA_STATUS status; + bool is_no_mac_key = false; + + if ((status = _atecc_async_init_command_device()) != ATCA_SUCCESS) { + return status; + } + _async.packet.param1 = zone; + _async.packet.param2 = key_id; + if (_async.packet.param1 == GENDIG_ZONE_SHARED_NONCE && other_data_size >= ATCA_BLOCK_SIZE) { + memcpy(&_async.packet.data[0], &other_data[0], ATCA_BLOCK_SIZE); + } else if (_async.packet.param1 == GENDIG_ZONE_DATA && other_data_size >= ATCA_WORD_SIZE) { + memcpy(&_async.packet.data[0], &other_data[0], ATCA_WORD_SIZE); + is_no_mac_key = true; + } + if ((status = atGenDig(_async.command.device->mCommands, &_async.packet, is_no_mac_key)) != + ATCA_SUCCESS) { + return status; + } + return _atecc_async_start_command(); +} + +// Ported from calib_read_zone() in cryptoauthlib/lib/calib/calib_read.c. +static ATCA_STATUS _atecc_async_start_read_zone_command( + uint8_t zone, + uint16_t slot, + uint8_t block, + uint8_t offset, + uint8_t len) +{ + ATCA_STATUS status; + uint16_t addr; + uint8_t addr_zone = zone & ~ATCA_ZONE_READWRITE_32; + + if ((status = _atecc_async_init_command_device()) != ATCA_SUCCESS) { + return status; + } + if ((status = calib_get_addr(addr_zone, slot, block, offset, &addr)) != ATCA_SUCCESS) { + return status; + } + if (len == ATCA_BLOCK_SIZE) { + zone |= ATCA_ZONE_READWRITE_32; + } + _async.packet.param1 = zone; + _async.packet.param2 = addr; + if ((status = atRead(_async.command.device->mCommands, &_async.packet)) != ATCA_SUCCESS) { + return status; + } + return _atecc_async_start_command(); +} + +// Ported from calib_write() in cryptoauthlib/lib/calib/calib_write.c. +static ATCA_STATUS _atecc_async_start_write_command( + uint8_t zone, + uint16_t address, + const uint8_t* value, + const uint8_t* mac) +{ + ATCA_STATUS status; + + if ((status = _atecc_async_init_command_device()) != ATCA_SUCCESS) { + return status; + } + _async.packet.param1 = zone; + _async.packet.param2 = address; + if (zone & ATCA_ZONE_READWRITE_32) { + memcpy(_async.packet.data, value, 32); + if (mac != NULL) { + memcpy(&_async.packet.data[32], mac, 32); + } + } else { + memcpy(_async.packet.data, value, 4); + } + if ((status = atWrite( + _async.command.device->mCommands, + &_async.packet, + mac && (zone & ATCA_ZONE_READWRITE_32))) != ATCA_SUCCESS) { + return status; + } + return _atecc_async_start_command(); +} + +// Ported from calib_sign_base() in cryptoauthlib/lib/calib/calib_sign.c. +static ATCA_STATUS _atecc_async_start_sign_base_command(uint8_t mode, uint16_t key_id) +{ + ATCA_STATUS status; + + if ((status = _atecc_async_init_command_device()) != ATCA_SUCCESS) { + return status; + } + _async.packet.param1 = mode; + _async.packet.param2 = key_id; + if ((status = atSign(_async.command.device->mCommands, &_async.packet)) != ATCA_SUCCESS) { + return status; + } + return _atecc_async_start_command(); +} + +// Ported from the write-command portion of calib_write_enc() in +// cryptoauthlib/lib/calib/calib_write.c. +static ATCA_STATUS _atecc_async_start_write_encrypted_data_slot_command( + uint16_t slot, + uint8_t block, + const uint8_t* value, + const uint8_t* mac) +{ + uint16_t addr; + ATCA_STATUS status; + + if ((status = calib_get_addr(ATCA_ZONE_DATA, slot, block, 0, &addr)) != ATCA_SUCCESS) { + return status; + } + return _atecc_async_start_write_command( + ATCA_ZONE_DATA | ATCA_ZONE_READWRITE_32 | ATCA_ZONE_ENCRYPTED, addr, value, mac); +} + +static int _atecc_async_extract_random_response(uint8_t* out) +{ + if (_async.packet.data[ATCA_COUNT_IDX] != RANDOM_RSP_SIZE) { + return ATCA_RX_FAIL; + } + if (out != NULL) { + memcpy(out, &_async.packet.data[ATCA_RSP_DATA_IDX], RANDOM_NUM_SIZE); + } + return ATCA_SUCCESS; +} + +static int _atecc_async_extract_counter_response(uint32_t* counter_out) +{ + if (_async.packet.data[ATCA_COUNT_IDX] != 7) { + return ATCA_RX_FAIL; + } + *counter_out = ((uint32_t)_async.packet.data[ATCA_RSP_DATA_IDX + 0] << 0) | + ((uint32_t)_async.packet.data[ATCA_RSP_DATA_IDX + 1] << 8) | + ((uint32_t)_async.packet.data[ATCA_RSP_DATA_IDX + 2] << 16) | + ((uint32_t)_async.packet.data[ATCA_RSP_DATA_IDX + 3] << 24); + return ATCA_SUCCESS; +} + +static int _atecc_async_extract_info_response(uint8_t* out) +{ + if (_async.packet.data[ATCA_COUNT_IDX] < 7) { + return ATCA_RX_FAIL; + } + memcpy(out, &_async.packet.data[ATCA_RSP_DATA_IDX], 4); + return ATCA_SUCCESS; +} + +static int _atecc_async_extract_kdf_response(uint8_t* out_data, uint8_t* out_nonce) +{ + if (_async.packet.data[ATCA_COUNT_IDX] < (ATCA_PACKET_OVERHEAD + 64)) { + return ATCA_RX_FAIL; + } + memcpy(out_data, &_async.packet.data[ATCA_RSP_DATA_IDX], 32); + memcpy(out_nonce, &_async.packet.data[ATCA_RSP_DATA_IDX + 32], 32); + return ATCA_SUCCESS; +} + +static int _atecc_async_extract_sign_response(uint8_t* signature_out) +{ + if (_async.packet.data[ATCA_COUNT_IDX] != (ATCA_SIG_SIZE + ATCA_PACKET_OVERHEAD)) { + return ATCA_RX_FAIL; + } + memcpy(signature_out, &_async.packet.data[ATCA_RSP_DATA_IDX], ATCA_SIG_SIZE); + return ATCA_SUCCESS; +} + +static int _atecc_async_extract_read_response(uint8_t* out, size_t len) +{ + if (_async.packet.data[ATCA_COUNT_IDX] < (ATCA_PACKET_OVERHEAD + len)) { + return ATCA_RX_FAIL; + } + memcpy(out, &_async.packet.data[ATCA_RSP_DATA_IDX], len); + return ATCA_SUCCESS; +} + +int atecc_auth_compute_response( + const uint8_t* num_in, + const uint8_t* rand_out, + const uint8_t* auth_key, + uint8_t* response_out) +{ + static const uint8_t other_data[13] = {0}; + atca_temp_key_t temp_key = {0}; + atca_nonce_in_out_t nonce_params = { + .mode = NONCE_MODE_SEED_UPDATE, + .zero = 0, + .num_in = num_in, + .rand_out = rand_out, + .temp_key = &temp_key, + }; + atca_check_mac_in_out_t checkmac_params; + ATCA_STATUS status; + + if (!atecc_serial_number_is_cached()) { + return ATCA_NOT_INITIALIZED; + } + if ((status = atcah_nonce(&nonce_params)) != ATCA_SUCCESS) { + return status; + } + + memset(&checkmac_params, 0, sizeof(checkmac_params)); + checkmac_params.mode = CHECKMAC_MODE_BLOCK2_TEMPKEY; + checkmac_params.key_id = ATECC_SLOT_AUTHKEY; + checkmac_params.sn = atecc_serial_number(); + checkmac_params.client_resp = response_out; + checkmac_params.other_data = other_data; + checkmac_params.slot_key = auth_key; + checkmac_params.temp_key = &temp_key; + return atcah_check_mac(&checkmac_params); +} + +int atecc_kdf_decrypt( + const uint8_t* io_protection_key, + const uint8_t* nonce_out, + uint8_t* data, + size_t data_size) +{ + atca_io_decrypt_in_out_t io_dec_params; + + if (data_size != ATCA_BLOCK_SIZE && data_size != 2 * ATCA_BLOCK_SIZE) { + return SC_ERR_INVALID_ARGS; + } + memset(&io_dec_params, 0, sizeof(io_dec_params)); + io_dec_params.io_key = io_protection_key; + io_dec_params.out_nonce = nonce_out; + io_dec_params.data = data; + io_dec_params.data_size = data_size; + return atcah_io_decrypt(&io_dec_params); +} + +int atecc_io_prepare_tempkey(const uint8_t* num_in, const uint8_t* rand_out) +{ + atca_nonce_in_out_t nonce_params = { + .mode = NONCE_MODE_SEED_UPDATE, + .zero = 0, + .num_in = num_in, + .rand_out = rand_out, + .temp_key = &_async.io_temp_key, + }; + ATCA_STATUS status; + + memset(&_async.io_temp_key, 0, sizeof(_async.io_temp_key)); + if ((status = atcah_nonce(&nonce_params)) != ATCA_SUCCESS) { + return status; + } + + _async.io_other_data[0] = ATCA_GENDIG; + _async.io_other_data[1] = GENDIG_ZONE_DATA; + _async.io_other_data[2] = (uint8_t)ATECC_SLOT_ENCRYPTION_KEY; + _async.io_other_data[3] = (uint8_t)(ATECC_SLOT_ENCRYPTION_KEY >> 8); + return ATCA_SUCCESS; +} + +int atecc_io_apply_gendig(const uint8_t* encryption_key) +{ + atca_gen_dig_in_out_t gen_dig_param; + + if (!atecc_serial_number_is_cached()) { + return ATCA_NOT_INITIALIZED; + } + memset(&gen_dig_param, 0, sizeof(gen_dig_param)); + gen_dig_param.key_id = ATECC_SLOT_ENCRYPTION_KEY; + gen_dig_param.is_key_nomac = false; + gen_dig_param.sn = atecc_serial_number(); + gen_dig_param.stored_value = encryption_key; + gen_dig_param.zone = GENDIG_ZONE_DATA; + gen_dig_param.other_data = _async.io_other_data; + gen_dig_param.temp_key = &_async.io_temp_key; + return atcah_gen_dig(&gen_dig_param); +} + +int atecc_io_prepare_encrypted_write( + uint16_t key_id, + uint8_t block, + const uint8_t* input_data, + uint8_t* encrypted_out, + uint8_t* mac_out) +{ + uint16_t addr; + atca_write_mac_in_out_t write_mac_param; + ATCA_STATUS status; + + if (!atecc_serial_number_is_cached()) { + return ATCA_NOT_INITIALIZED; + } + if ((status = calib_get_addr(ATCA_ZONE_DATA, key_id, block, 0, &addr)) != ATCA_SUCCESS) { + return status; + } + memset(&write_mac_param, 0, sizeof(write_mac_param)); + write_mac_param.zone = ATCA_ZONE_DATA | ATCA_ZONE_READWRITE_32 | ATCA_ZONE_ENCRYPTED; + write_mac_param.key_id = addr; + write_mac_param.sn = atecc_serial_number(); + write_mac_param.input_data = input_data; + write_mac_param.encrypted_data = encrypted_out; + write_mac_param.auth_mac = mac_out; + write_mac_param.temp_key = &_async.io_temp_key; + return atcah_write_auth_mac(&write_mac_param); +} + +int atecc_io_decrypt_block(uint8_t* data, size_t len) +{ + if (len > ATCA_BLOCK_SIZE) { + return SC_ERR_INVALID_ARGS; + } + for (size_t i = 0; i < len; i++) { + data[i] ^= _async.io_temp_key.value[i]; + } + return ATCA_SUCCESS; +} + +int atecc_ops_get_status(void) +{ + return _async.status; +} + +uint32_t atecc_ops_get_poll_delay_ms(void) +{ + return _async.command.next_poll_delay_ms; +} + +void atecc_ops_poll(void) +{ + _atecc_async_poll_command(); +} + +int atecc_cmd_start_nonce_rand(const uint8_t* num_in) +{ + _atecc_async_reset_command(); + return _atecc_async_launch(_atecc_async_start_nonce_rand(num_in)); +} + +int atecc_cmd_start_checkmac(const uint8_t* response) +{ + _atecc_async_reset_command(); + return _atecc_async_launch(_atecc_async_start_checkmac(response)); +} + +int atecc_cmd_start_random(void) +{ + _atecc_async_reset_command(); + return _atecc_async_launch(_atecc_async_start_random_command()); +} + +int atecc_cmd_start_counter_read(void) +{ + _atecc_async_reset_command(); + return _atecc_async_launch(_atecc_async_start_counter_read_command(0)); +} + +int atecc_cmd_start_info_revision(void) +{ + _atecc_async_reset_command(); + return _atecc_async_launch(_atecc_async_start_info_revision_command()); +} + +int atecc_cmd_start_kdf(atecc_slot_t slot, const uint8_t* msg, size_t len) +{ + _atecc_async_reset_command(); + if (len > 127 || (slot != ATECC_SLOT_ROLLKEY && slot != ATECC_SLOT_KDF)) { + return _atecc_async_launch(SC_ERR_INVALID_ARGS); + } + // The KDF command encodes the HKDF input length in one details byte, so len must be <=127 + // before launching the async command. This mirrors the old atcab_kdf() wrapper. + return _atecc_async_launch(_atecc_async_start_kdf_command(slot, msg, len)); +} + +int atecc_cmd_start_derivekey_rollkey(void) +{ + _atecc_async_reset_command(); + return _atecc_async_launch(_atecc_async_start_derivekey_command(0, ATECC_SLOT_ROLLKEY)); +} + +int atecc_cmd_start_nonce_load_msgdigest(const uint8_t* msg) +{ + _atecc_async_reset_command(); + return _atecc_async_launch(_atecc_async_start_nonce_load(NONCE_MODE_TARGET_MSGDIGBUF, msg, 32)); +} + +int atecc_cmd_start_sign_attestation(void) +{ + _atecc_async_reset_command(); + return _atecc_async_launch(_atecc_async_start_sign_base_command( + SIGN_MODE_EXTERNAL | SIGN_MODE_SOURCE_MSGDIGBUF, ATECC_SLOT_ATTESTATION)); +} + +int atecc_cmd_start_gendig_encryption_key(void) +{ + _atecc_async_reset_command(); + return _atecc_async_launch(_atecc_async_start_gendig_command( + GENDIG_ZONE_DATA, + ATECC_SLOT_ENCRYPTION_KEY, + _async.io_other_data, + sizeof(_async.io_other_data))); +} + +int atecc_cmd_start_read_block(uint16_t slot, uint8_t block) +{ + _atecc_async_reset_command(); + return _atecc_async_launch(_atecc_async_start_read_zone_command( + ATCA_ZONE_DATA | ATCA_ZONE_READWRITE_32, slot, block, 0, ATCA_BLOCK_SIZE)); +} + +int atecc_cmd_start_write_encrypted_block( + uint16_t slot, + uint8_t block, + const uint8_t* value, + const uint8_t* mac) +{ + _atecc_async_reset_command(); + return _atecc_async_launch( + _atecc_async_start_write_encrypted_data_slot_command(slot, block, value, mac)); +} + +int atecc_cmd_read_random_response(uint8_t* out) +{ + return _atecc_async_extract_random_response(out); +} + +int atecc_cmd_read_counter_response(uint32_t* counter_out) +{ + return _atecc_async_extract_counter_response(counter_out); +} + +int atecc_cmd_read_info_response(uint8_t* out) +{ + return _atecc_async_extract_info_response(out); +} + +int atecc_cmd_read_kdf_response(uint8_t* out_data, uint8_t* out_nonce) +{ + return _atecc_async_extract_kdf_response(out_data, out_nonce); +} + +int atecc_cmd_read_sign_response(uint8_t* signature_out) +{ + return _atecc_async_extract_sign_response(signature_out); +} + +int atecc_cmd_read_block_response(uint8_t* out) +{ + return _atecc_async_extract_read_response(out, ATCA_BLOCK_SIZE); +} + +// NOLINTEND(bugprone-assignment-in-if-condition) diff --git a/src/common_main.c b/src/common_main.c index a1d6f0e086..26d3be9141 100644 --- a/src/common_main.c +++ b/src/common_main.c @@ -32,13 +32,6 @@ static const memory_interface_functions_t _memory_interface_functions = { .random_32_bytes = random_32_bytes_mcu, }; -static const securechip_interface_functions_t _securechip_interface_functions = { - .get_auth_key = memory_get_authorization_key, - .get_io_protection_key = memory_get_io_protection_key, - .get_encryption_key = memory_get_encryption_key, - .random_32_bytes = random_32_bytes, -}; - void common_main(void) { mpu_bitbox02_init(); @@ -55,7 +48,7 @@ void common_main(void) } // rust_securechip_setup must come after memory_setup, so the io/auth keys to be // used are already initialized. - int securechip_result = rust_securechip_setup(&_securechip_interface_functions); + int securechip_result = rust_securechip_setup(); if (securechip_result) { char errmsg[100] = {0}; snprintf( diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 25f6187806..e5e738881e 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -239,6 +239,7 @@ dependencies = [ "der", "grounded", "hex_lit", + "p256", "util", "zeroize", ] diff --git a/src/rust/bitbox-hal/src/memory.rs b/src/rust/bitbox-hal/src/memory.rs index e66ee6fedd..75ad7bf59b 100644 --- a/src/rust/bitbox-hal/src/memory.rs +++ b/src/rust/bitbox-hal/src/memory.rs @@ -98,7 +98,9 @@ pub trait Memory { fn get_noise_static_private_key(&mut self) -> Result, ()>; fn check_noise_remote_static_pubkey(&mut self, pubkey: &[u8; 32]) -> bool; fn add_noise_remote_static_pubkey(&mut self, pubkey: &[u8; 32]) -> Result<(), ()>; + fn get_auth_key(&mut self, out: &mut [u8; 32]); fn get_io_protection_key(&mut self, out: &mut [u8; 32]); + fn get_encryption_key(&mut self, out: &mut [u8; 32]); fn get_salt_root(&mut self) -> Result>, ()>; fn get_attestation_pubkey_and_certificate( &mut self, diff --git a/src/rust/bitbox-hal/src/securechip.rs b/src/rust/bitbox-hal/src/securechip.rs index d87684b834..7d6f14c422 100644 --- a/src/rust/bitbox-hal/src/securechip.rs +++ b/src/rust/bitbox-hal/src/securechip.rs @@ -84,13 +84,22 @@ pub trait SecureChip { /// /// This must not increment a monotonic counter. /// + /// `memory` is used for persistent secrets needed by some secure-chip backends. + /// /// `msg` must be 32 bytes long. - async fn kdf(&mut self, msg: &[u8; 32]) -> Result>, Error>; + async fn kdf( + &mut self, + memory: &mut impl super::memory::Memory, + msg: &[u8; 32], + ) -> Result>, Error>; /// Signs a 32-byte attestation challenge and writes the raw 64-byte P-256 signature to /// `signature`. + /// + /// `memory` is used for persistent secrets needed by some secure-chip backends. async fn attestation_sign( &mut self, + memory: &mut impl super::memory::Memory, challenge: &[u8; 32], signature: &mut [u8; 64], ) -> Result<(), ()>; @@ -99,7 +108,7 @@ pub trait SecureChip { async fn monotonic_increments_remaining(&mut self) -> Result; /// Returns the detected secure-chip model. - fn model(&mut self) -> Result; + async fn model(&mut self) -> Result; /// Resets the secure-chip objects involved in password stretching. async fn reset_keys( @@ -111,10 +120,27 @@ pub trait SecureChip { #[cfg(feature = "app-u2f")] /// Sets the U2F counter to `counter`. /// + /// `random` is used by some secure-chip backends for authenticated encrypted writes. + /// + /// `memory` is used for persistent secrets needed by some secure-chip backends. + /// /// This is intended for initialization only. - async fn u2f_counter_set(&mut self, counter: u32) -> Result<(), ()>; + async fn u2f_counter_set( + &mut self, + random: &mut impl super::random::Random, + memory: &mut impl super::memory::Memory, + counter: u32, + ) -> Result<(), ()>; #[cfg(feature = "app-u2f")] /// Increments the U2F counter and returns the new value. - async fn u2f_counter_inc(&mut self) -> Result; + /// + /// `random` is used by some secure-chip backends for authenticated encrypted writes. + /// + /// `memory` is used for persistent secrets needed by some secure-chip backends. + async fn u2f_counter_inc( + &mut self, + random: &mut impl super::random::Random, + memory: &mut impl super::memory::Memory, + ) -> Result; } diff --git a/src/rust/bitbox-lvgl-sys/build.rs b/src/rust/bitbox-lvgl-sys/build.rs index 78253248d5..a5e07fe3b9 100644 --- a/src/rust/bitbox-lvgl-sys/build.rs +++ b/src/rust/bitbox-lvgl-sys/build.rs @@ -73,10 +73,7 @@ fn main() -> Result<(), &'static str> { let mut cmake_build = cmake::Config::new(&lvgl_dir); let lv_conf = match env::var("LV_CONF_PATH") { - Err(_) => { - println!("cargo::warning=LV_CONF_PATH missing, not using config file."); - manifest_dir.join("lv_conf.h") - } + Err(_) => manifest_dir.join("lv_conf.h"), Ok(lv_conf) => { let lv_conf: PathBuf = lv_conf.into(); lv_conf.canonicalize().expect("canonicalizing LV_CONF") diff --git a/src/rust/bitbox-platform-host/src/memory.rs b/src/rust/bitbox-platform-host/src/memory.rs index 7758772ffc..51ce273a8c 100644 --- a/src/rust/bitbox-platform-host/src/memory.rs +++ b/src/rust/bitbox-platform-host/src/memory.rs @@ -283,8 +283,16 @@ impl bitbox_hal::Memory for FakeMemory { Ok(()) } - fn get_io_protection_key(&mut self, _out: &mut [u8; 32]) { - panic!("unused") + fn get_auth_key(&mut self, out: &mut [u8; 32]) { + *out = [0; 32]; + } + + fn get_io_protection_key(&mut self, out: &mut [u8; 32]) { + *out = [0; 32]; + } + + fn get_encryption_key(&mut self, out: &mut [u8; 32]) { + *out = [0; 32]; } fn get_salt_root(&mut self) -> Result>, ()> { diff --git a/src/rust/bitbox-platform-host/src/securechip.rs b/src/rust/bitbox-platform-host/src/securechip.rs index 11c445cb45..1c86cb2970 100644 --- a/src/rust/bitbox-platform-host/src/securechip.rs +++ b/src/rust/bitbox-platform-host/src/securechip.rs @@ -125,7 +125,11 @@ impl bitbox_hal::SecureChip for FakeSecureChip { ))) } - async fn kdf(&mut self, msg: &[u8; 32]) -> Result>, Error> { + async fn kdf( + &mut self, + _memory: &mut impl bitbox_hal::Memory, + msg: &[u8; 32], + ) -> Result>, Error> { self.event_counter += 1; use bitcoin::hashes::{HashEngine, Hmac, HmacEngine, sha256}; @@ -147,6 +151,7 @@ impl bitbox_hal::SecureChip for FakeSecureChip { async fn attestation_sign( &mut self, + _memory: &mut impl bitbox_hal::Memory, challenge: &[u8; 32], signature: &mut [u8; 64], ) -> Result<(), ()> { @@ -160,7 +165,7 @@ impl bitbox_hal::SecureChip for FakeSecureChip { Ok(1) } - fn model(&mut self) -> Result { + async fn model(&mut self) -> Result { Ok(Model::Atecc608B) } @@ -179,13 +184,22 @@ impl bitbox_hal::SecureChip for FakeSecureChip { } #[cfg(feature = "app-u2f")] - async fn u2f_counter_set(&mut self, counter: u32) -> Result<(), ()> { + async fn u2f_counter_set( + &mut self, + _random: &mut impl bitbox_hal::Random, + _memory: &mut impl bitbox_hal::Memory, + counter: u32, + ) -> Result<(), ()> { self.u2f_counter = counter; Ok(()) } #[cfg(feature = "app-u2f")] - async fn u2f_counter_inc(&mut self) -> Result { + async fn u2f_counter_inc( + &mut self, + _random: &mut impl bitbox_hal::Random, + _memory: &mut impl bitbox_hal::Memory, + ) -> Result { self.u2f_counter = self.u2f_counter.wrapping_add(1); Ok(self.u2f_counter) } @@ -194,6 +208,8 @@ impl bitbox_hal::SecureChip for FakeSecureChip { #[cfg(test)] mod tests { use super::*; + use crate::memory::FakeMemory; + use crate::random::TestingRandom; use bitbox_hal::SecureChip; use hex_lit::hex; @@ -212,10 +228,24 @@ mod tests { #[async_test::test] async fn test_u2f_counter_inc() { let mut securechip = FakeSecureChip::new(); + let mut random = TestingRandom::new(); + let mut memory = FakeMemory::new(); assert_eq!(securechip.get_u2f_counter(), 0); - assert_eq!(securechip.u2f_counter_inc().await.unwrap(), 1); + assert_eq!( + securechip + .u2f_counter_inc(&mut random, &mut memory) + .await + .unwrap(), + 1 + ); assert_eq!(securechip.get_u2f_counter(), 1); - assert_eq!(securechip.u2f_counter_inc().await.unwrap(), 2); + assert_eq!( + securechip + .u2f_counter_inc(&mut random, &mut memory) + .await + .unwrap(), + 2 + ); assert_eq!(securechip.get_u2f_counter(), 2); } } diff --git a/src/rust/bitbox-securechip-sys/build.rs b/src/rust/bitbox-securechip-sys/build.rs index cd77f2cd9f..bd1250fb97 100644 --- a/src/rust/bitbox-securechip-sys/build.rs +++ b/src/rust/bitbox-securechip-sys/build.rs @@ -6,6 +6,7 @@ use std::path::PathBuf; use std::process::{Command, Output}; const ALLOWLIST_TYPES: &[&str] = &[ + "atecc_slot_t", "bool_t", "optiga_crypt_t", "optiga_hmac_type_t", @@ -17,24 +18,40 @@ const ALLOWLIST_TYPES: &[&str] = &[ "optiga_symmetric_key_type_t", "optiga_util_t", "securechip_error_t", - "securechip_interface_functions_t", "securechip_model_t", "securechip_password_stretch_algo_t", ]; const ALLOWLIST_FNS: &[&str] = &[ - "atecc_attestation_sign", + "atecc_auth_compute_response", + "atecc_cmd_read_block_response", + "atecc_cmd_read_counter_response", + "atecc_cmd_read_info_response", + "atecc_cmd_read_kdf_response", + "atecc_cmd_read_random_response", + "atecc_cmd_read_sign_response", + "atecc_cmd_start_checkmac", + "atecc_cmd_start_counter_read", + "atecc_cmd_start_derivekey_rollkey", + "atecc_cmd_start_gendig_encryption_key", + "atecc_cmd_start_info_revision", + "atecc_cmd_start_kdf", + "atecc_cmd_start_nonce_load_msgdigest", + "atecc_cmd_start_nonce_rand", + "atecc_cmd_start_random", + "atecc_cmd_start_read_block", + "atecc_cmd_start_sign_attestation", + "atecc_cmd_start_write_encrypted_block", "atecc_gen_attestation_key", - "atecc_init_new_password", - "atecc_kdf", - "atecc_model", - "atecc_monotonic_increments_remaining", - "atecc_random", - "atecc_reset_keys", + "atecc_io_apply_gendig", + "atecc_io_decrypt_block", + "atecc_io_prepare_encrypted_write", + "atecc_io_prepare_tempkey", + "atecc_kdf_decrypt", + "atecc_ops_get_status", + "atecc_ops_get_poll_delay_ms", + "atecc_ops_poll", "atecc_setup", - "atecc_stretch_password", - "atecc_u2f_counter_inc", - "atecc_u2f_counter_set", "optiga_crypt_clear_auto_state", "optiga_crypt_ecdsa_sign", "optiga_crypt_generate_auth_code", @@ -54,8 +71,10 @@ const ALLOWLIST_FNS: &[&str] = &[ ]; const ALLOWLIST_VARS: &[&str] = &[ + "ATECC_OPS_STATUS_BUSY", "ARBITRARY_DATA_OBJECT_TYPE_3_MAX_SIZE", "MONOTONIC_COUNTER_MAX_USE", + "NONCE_NUMIN_SIZE", "OID_AES_SYMKEY", "OID_ARBITRARY_DATA", "OID_COUNTER", @@ -79,6 +98,7 @@ const ALLOWLIST_VARS: &[&str] = &[ ]; const RUSTIFIED_ENUMS: &[&str] = &[ + "atecc_slot_t", "optiga_hmac_type", "optiga_hmac_type_t", "optiga_key_id", @@ -188,6 +208,10 @@ pub fn main() -> BuildResult<()> { .args(&definitions) .arg(format!("-I{}", src_dir.display())) .arg(format!("-I{}", external_dir.display())) + .arg(format!( + "-I{}", + external_dir.join("cryptoauthlib/lib").display() + )) .arg(format!( "-I{}", external_dir.join("optiga-trust-m/config").display() diff --git a/src/rust/bitbox-securechip-sys/wrapper.h b/src/rust/bitbox-securechip-sys/wrapper.h index da3362e828..eab0162f39 100644 --- a/src/rust/bitbox-securechip-sys/wrapper.h +++ b/src/rust/bitbox-securechip-sys/wrapper.h @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include #include diff --git a/src/rust/bitbox-securechip/Cargo.toml b/src/rust/bitbox-securechip/Cargo.toml index d5589be139..0be74cbd90 100644 --- a/src/rust/bitbox-securechip/Cargo.toml +++ b/src/rust/bitbox-securechip/Cargo.toml @@ -16,6 +16,7 @@ critical-section = { workspace = true } grounded = { workspace = true } util = { path = "../util", features = ["sha2"] } der = { version = "0.7.9", default-features = false } +p256 = { version = "0.13.2", default-features = false, features = ["arithmetic", "ecdsa"] } zeroize = { workspace = true } [features] diff --git a/src/rust/bitbox-securechip/src/atecc.rs b/src/rust/bitbox-securechip/src/atecc.rs index f6935c12f4..1bbf6e415c 100644 --- a/src/rust/bitbox-securechip/src/atecc.rs +++ b/src/rust/bitbox-securechip/src/atecc.rs @@ -1,119 +1,692 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{Error, Model, PasswordStretchAlgo, SecureChipError}; -use alloc::boxed::Box; -use bitbox_hal::Memory; +use alloc::{boxed::Box, format, string::String}; +use bitbox_hal::{Memory, Random, timer::Timer}; +use bitbox_securechip_sys::atecc_slot_t as Slot; +use core::fmt::Write; +use core::sync::atomic::{AtomicBool, Ordering}; +use util::sha2::hmac_sha256_overwrite; use zeroize::Zeroizing; -pub async fn attestation_sign(challenge: &[u8; 32], signature: &mut [u8; 64]) -> Result<(), ()> { - match unsafe { - bitbox_securechip_sys::atecc_attestation_sign(challenge.as_ptr(), signature.as_mut_ptr()) - } { - true => Ok(()), - false => Err(()), +#[cfg(not(test))] +#[path = "atecc/ops.rs"] +mod ops; +#[cfg(test)] +#[path = "atecc/ops_fake.rs"] +mod ops; + +const ATECC_OPS_STATUS_BUSY: i32 = bitbox_securechip_sys::ATECC_OPS_STATUS_BUSY as i32; +const KDF_LEN: usize = 32; +// This number of KDF iterations on the 2nd kdf slot when stretching the device password. +const KDF_NUM_ITERATIONS: usize = 2; +const RANDOM_RETRIES: usize = 5; +// ATECC encrypted read/write operates on 32-byte blocks. These helpers only support the standard +// full block size. +#[cfg(any(feature = "app-u2f", feature = "factory-setup"))] +const BLOCK_SIZE: usize = 32; +const COUNTER_MAX_VALUE: u32 = 2_097_151; +const SLOT_ROLLKEY: Slot = Slot::ATECC_SLOT_ROLLKEY; +const SLOT_KDF: Slot = Slot::ATECC_SLOT_KDF; +#[cfg(any(feature = "app-u2f", feature = "factory-setup"))] +const SLOT_DATA0: Slot = Slot::ATECC_SLOT_DATA0; + +// Several ATECC flows derive TempKey-like state across multiple chip commands and host helpers, so +// public operations must not interleave even though the low-level async machine is per-command. +static OP_IN_FLIGHT: AtomicBool = AtomicBool::new(false); + +struct OpGuard; + +impl Drop for OpGuard { + fn drop(&mut self) { + OP_IN_FLIGHT.store(false, Ordering::Release); } } -pub fn random() -> Result>, Error> { - let mut result = Box::new(Zeroizing::new([0u8; 32])); - let status = unsafe { bitbox_securechip_sys::atecc_random(result.as_mut_ptr()) }; - if status == 0 { - Ok(result) +// Some ATECC operations derive host/device state across multiple awaited chip commands, e.g. +// authorize+derivekey, nonce+gendig+encrypted read/write, or nonce-load+sign. Those sequences must +// not interleave with another high-level ATECC call, even though `ops.rs` only serializes one +// low-level command at a time. +fn begin_high_level_op() -> Result { + if OP_IN_FLIGHT.swap(true, Ordering::AcqRel) { + Err(Error::from_status(ATECC_OPS_STATUS_BUSY)) } else { - Err(Error::from_status(status)) + Ok(OpGuard) } } -pub fn monotonic_increments_remaining() -> Result { - let mut result = 0u32; - match unsafe { bitbox_securechip_sys::atecc_monotonic_increments_remaining(&mut result) } { - true => Ok(result), - false => Err(()), + +fn zeroed_secret() -> Box> { + Box::new(Zeroizing::new([0; N])) +} + +fn status_from_error(err: Error) -> i32 { + match err { + Error::SecureChip(err) => err as i32, + Error::Status(status) => status, } } -pub fn reset_keys() -> Result<(), ()> { - match unsafe { bitbox_securechip_sys::atecc_reset_keys() } { - true => Ok(()), - false => Err(()), +async fn random_32_bytes( + random: &mut impl Random, +) -> Result>, i32> { + let securechip_random = random_inner::().await.map_err(status_from_error)?; + Ok(bitbox_core_utils::random::random_32_bytes_with_mixin( + random, + securechip_random.as_ref(), + )) +} + +fn load_auth_key(memory: &mut impl Memory) -> Box> { + let mut auth_key = zeroed_secret(); + memory.get_auth_key(auth_key.as_mut()); + auth_key +} + +fn load_io_protection_key(memory: &mut impl Memory) -> Box> { + let mut io_protection_key = zeroed_secret(); + memory.get_io_protection_key(io_protection_key.as_mut()); + io_protection_key +} + +fn load_encryption_key(memory: &mut impl Memory) -> Box> { + let mut encryption_key = zeroed_secret(); + memory.get_encryption_key(encryption_key.as_mut()); + encryption_key +} + +/** + * This performs the CheckMac command on ATECC_SLOT_AUTHKEY. This needs to + * be called before using any slot requiring auth and whose KeyConfig.AuthKey is + * ATECC_SLOT_AUTHKEY. + */ +async fn authorize_key(memory: &mut impl Memory) -> Result<(), i32> { + let num_in = Zeroizing::new([0u8; ops::NONCE_NUMIN_SIZE]); + let rand_out = ops::chip_nonce_rand::(&num_in).await?; + let auth_key = load_auth_key(memory); + let response = ops::host_check_mac(&num_in, &rand_out, auth_key.as_ref()).await?; + ops::chip_checkmac::(&response).await +} + +/** + * Performs a roll-key operation on a ATECC_SLOT_ROLLKEY. + * @return ATCA_SUCCESS on success. + */ +async fn rollkey(memory: &mut impl Memory) -> Result<(), i32> { + authorize_key::(memory).await?; + + let num_in = Zeroizing::new([0u8; ops::NONCE_NUMIN_SIZE]); + let _rand_out = ops::chip_nonce_rand::(&num_in).await?; + ops::chip_derivekey_rollkey::().await +} + +/** + * Writes a new random key to ATECC_SLOT_KDF. + * @return ATCA_SUCCESS on success. + */ +async fn update_kdf_key( + random: &mut impl Random, + memory: &mut impl Memory, +) -> Result<(), i32> { + let new_key = random_32_bytes::(random).await?; + let nonce_contribution = random_32_bytes::(random).await?; + + authorize_key::(memory).await?; + + let mut num_in = Zeroizing::new([0u8; ops::NONCE_NUMIN_SIZE]); + (*num_in).copy_from_slice(&nonce_contribution[..ops::NONCE_NUMIN_SIZE]); + let rand_out = ops::chip_nonce_rand::(&num_in).await?; + ops::host_nonce(&num_in, &rand_out).await?; + ops::chip_gendig_encryption_key::().await?; + let encryption_key = load_encryption_key(memory); + ops::host_gendig(encryption_key.as_ref()).await?; + + let (cipher, mac) = ops::host_write_auth_mac(SLOT_KDF, 0, new_key.as_ref()).await?; + ops::chip_write_encrypted_block::(SLOT_KDF, 0, &cipher, &mac).await +} + +async fn atecc_kdf( + memory: &mut impl Memory, + slot: Slot, + msg: &[u8; 32], +) -> Result>, Error> { + authorize_key::(memory) + .await + .map_err(Error::from_status)?; + + // The result is hkdf_extract with the msg as ikm (input key material) and + // the slot key as the salt. hkdf info does not apply, as it is part of + // hkdf_expand, which is not performed. hkdf_extract is simply hmac. + // Python equivalent: + // import hmac, hashlib; hmac.new(slot_key, msg, hashlib.sha256).digest() + let (mut kdf_out, nonce_out) = ops::chip_kdf::(slot, msg) + .await + .map_err(Error::from_status)?; + + // The chip output is encrypted with the io protection key. + let io_protection_key = load_io_protection_key(memory); + ops::host_kdf_decrypt(io_protection_key.as_ref(), &nonce_out, &mut kdf_out) + .await + .map_err(Error::from_status)?; + Ok(Box::new(kdf_out)) +} + +// Read a "standard" sized block from a data slot (must be 32 bytes) +#[cfg(any(feature = "app-u2f", feature = "factory-setup"))] +async fn read_data_slot_block( + random: &mut impl Random, + memory: &mut impl Memory, + slot: Slot, + block: u8, +) -> Result, i32> { + let nonce_contribution = random_32_bytes::(random).await?; + + authorize_key::(memory).await?; + + let mut num_in = Zeroizing::new([0u8; ops::NONCE_NUMIN_SIZE]); + (*num_in).copy_from_slice(&nonce_contribution[..ops::NONCE_NUMIN_SIZE]); + let rand_out = ops::chip_nonce_rand::(&num_in).await?; + ops::host_nonce(&num_in, &rand_out).await?; + ops::chip_gendig_encryption_key::().await?; + let encryption_key = load_encryption_key(memory); + ops::host_gendig(encryption_key.as_ref()).await?; + + let mut data = ops::chip_read_block::(slot, block).await?; + ops::host_io_decrypt(&mut data).await?; + Ok(data) +} + +// Write a "standard" sized block from a data slot (must be 32 bytes) +#[cfg(any(feature = "app-u2f", feature = "factory-setup"))] +async fn write_data_slot_block( + random: &mut impl Random, + memory: &mut impl Memory, + bytes: &Zeroizing<[u8; BLOCK_SIZE]>, + slot: Slot, + block: u8, +) -> Result<(), i32> { + let nonce_contribution = random_32_bytes::(random).await?; + + authorize_key::(memory).await?; + + let mut num_in = Zeroizing::new([0u8; ops::NONCE_NUMIN_SIZE]); + (*num_in).copy_from_slice(&nonce_contribution[..ops::NONCE_NUMIN_SIZE]); + let rand_out = ops::chip_nonce_rand::(&num_in).await?; + ops::host_nonce(&num_in, &rand_out).await?; + ops::chip_gendig_encryption_key::().await?; + let encryption_key = load_encryption_key(memory); + ops::host_gendig(encryption_key.as_ref()).await?; + + let (cipher, mac) = ops::host_write_auth_mac(slot, block, bytes).await?; + ops::chip_write_encrypted_block::(slot, block, &cipher, &mac).await?; + + // Double-check by reading it back and comparing. + let written_bytes = read_data_slot_block::(random, memory, slot, block).await?; + if written_bytes[..BLOCK_SIZE] == bytes[..BLOCK_SIZE] { + Ok(()) + } else { + Err(-1) + } +} + +async fn random_inner() -> Result>, Error> { + let mut last_err = 0; + for _ in 0..RANDOM_RETRIES { + match ops::chip_random::().await { + Ok(random) => return Ok(Box::new(random)), + Err(err) => last_err = err, + } + } + Err(Error::from_status(last_err)) +} + +async fn reset_keys_inner( + random: &mut impl Random, + memory: &mut impl Memory, +) -> Result<(), ()> { + rollkey::(memory).await.map_err(|_| ())?; + update_kdf_key::(random, memory).await.map_err(|_| ()) +} + +async fn stretch_password_inner( + memory: &mut impl Memory, + password: &str, +) -> Result>, Error> { + let password_salted_hashed = + bitbox_core_utils::salt::hash_data(memory, password.as_bytes(), "keystore_seed_access_in") + .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_SALT))?; + + let mut kdf_in = zeroed_secret::(); + (*kdf_in).copy_from_slice(password_salted_hashed.as_slice()); + + // First KDF on rollkey increments the monotonic counter. Call only once! + let mut stretched = atecc_kdf::(memory, SLOT_ROLLKEY, kdf_in.as_ref()).await?; + // Second KDF does not use the counter and we call it multiple times. + for _ in 0..KDF_NUM_ITERATIONS { + (*kdf_in).copy_from_slice(stretched.as_slice()); + stretched = atecc_kdf::(memory, SLOT_KDF, kdf_in.as_ref()).await?; + } + + let password_salted_hashed = + bitbox_core_utils::salt::hash_data(memory, password.as_bytes(), "keystore_seed_access_out") + .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_SALT))?; + hmac_sha256_overwrite(password_salted_hashed.as_slice(), &mut stretched); + Ok(stretched) +} + +pub async fn attestation_sign( + memory: &mut impl Memory, + challenge: &[u8; 32], + signature: &mut [u8; 64], +) -> Result<(), ()> { + let _guard = begin_high_level_op().map_err(|_| ())?; + authorize_key::(memory).await.map_err(|_| ())?; + + // Keep the device RNG seeded before loading the external digest, matching calib_sign(). + let _throwaway = ops::chip_random::().await.map_err(|_| ())?; + ops::chip_nonce_load_msgdigest::(challenge) + .await + .map_err(|_| ())?; + let chip_signature = ops::chip_sign_attestation::().await.map_err(|_| ())?; + signature.copy_from_slice(&chip_signature); + Ok(()) +} + +pub async fn random() -> Result>, Error> { + let _guard = begin_high_level_op()?; + random_inner::().await +} + +pub async fn monotonic_increments_remaining() -> Result { + let _guard = begin_high_level_op().map_err(|_| ())?; + let counter = ops::chip_counter_read::().await.map_err(|_| ())?; + if COUNTER_MAX_VALUE < counter { + panic!("ATECC returned an invalid value"); } + Ok(COUNTER_MAX_VALUE.wrapping_sub(counter)) } -pub fn init_new_password( - _memory: &mut impl Memory, +pub async fn reset_keys( + random: &mut impl Random, + memory: &mut impl Memory, +) -> Result<(), ()> { + let _guard = begin_high_level_op().map_err(|_| ())?; + reset_keys_inner::(random, memory).await +} + +pub async fn init_new_password( + random: &mut impl Random, + memory: &mut impl Memory, password: &str, password_stretch_algo: PasswordStretchAlgo, ) -> Result>, Error> { - let password = util::strings::str_to_cstr_vec_zeroizing(password) - .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_INVALID_ARGS))?; - let mut stretched = Box::new(Zeroizing::new([0u8; 32])); - let status = unsafe { - bitbox_securechip_sys::atecc_init_new_password( - password.as_ptr().cast(), - password_stretch_algo, - stretched.as_mut_ptr(), - ) - }; - if status == 0 { - Ok(stretched) - } else { - Err(Error::from_status(status)) + if password_stretch_algo != PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0 { + return Err(Error::SecureChip( + SecureChipError::SC_ERR_INVALID_PASSWORD_STRETCH_ALGO, + )); } + + let _guard = begin_high_level_op()?; + reset_keys_inner::(random, memory) + .await + .map_err(|_| Error::SecureChip(SecureChipError::SC_ATECC_ERR_RESET_KEYS))?; + stretch_password_inner::(memory, password).await } -pub fn stretch_password( - _memory: &mut impl Memory, +pub async fn stretch_password( + memory: &mut impl Memory, password: &str, password_stretch_algo: PasswordStretchAlgo, ) -> Result>, Error> { - let password = util::strings::str_to_cstr_vec_zeroizing(password) - .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_INVALID_ARGS))?; - let mut stretched = Box::new(Zeroizing::new([0u8; 32])); - let status = unsafe { - bitbox_securechip_sys::atecc_stretch_password( - password.as_ptr().cast(), - password_stretch_algo, - stretched.as_mut_ptr(), - ) - }; - if status == 0 { - Ok(stretched) - } else { - Err(Error::from_status(status)) + if password_stretch_algo != PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0 { + return Err(Error::SecureChip( + SecureChipError::SC_ERR_INVALID_PASSWORD_STRETCH_ALGO, + )); } + + let _guard = begin_high_level_op()?; + stretch_password_inner::(memory, password).await +} + +pub async fn kdf( + memory: &mut impl Memory, + msg: &[u8; 32], +) -> Result>, Error> { + let _guard = begin_high_level_op()?; + atecc_kdf::(memory, SLOT_KDF, msg).await +} + +#[cfg(any(feature = "app-u2f", feature = "factory-setup"))] +pub async fn u2f_counter_set( + random: &mut impl Random, + memory: &mut impl Memory, + counter: u32, +) -> Result<(), ()> { + let _guard = begin_high_level_op().map_err(|_| ())?; + let mut data = read_data_slot_block::(random, memory, SLOT_DATA0, 0) + .await + .map_err(|_| ())?; + (*data)[..4].copy_from_slice(&counter.to_le_bytes()); + write_data_slot_block::(random, memory, &data, SLOT_DATA0, 0) + .await + .map_err(|_| ()) } -pub fn kdf(msg: &[u8; 32]) -> Result>, Error> { - let mut result = Box::new(Zeroizing::new([0u8; 32])); - let status = - unsafe { bitbox_securechip_sys::atecc_kdf(msg.as_ptr(), msg.len(), result.as_mut_ptr()) }; - if status == 0 { - Ok(result) +#[cfg(feature = "app-u2f")] +pub async fn u2f_counter_inc( + random: &mut impl Random, + memory: &mut impl Memory, +) -> Result { + let _guard = begin_high_level_op().map_err(|_| ())?; + let mut data = read_data_slot_block::(random, memory, SLOT_DATA0, 0) + .await + .map_err(|_| ())?; + let current = u32::from_le_bytes(data[..4].try_into().unwrap()).wrapping_add(1); + (*data)[..4].copy_from_slice(¤t.to_le_bytes()); + write_data_slot_block::(random, memory, &data, SLOT_DATA0, 0) + .await + .map_err(|_| ())?; + Ok(current) +} + +pub async fn model() -> Result { + let _guard = begin_high_level_op().map_err(|_| ())?; + let revision = ops::chip_info_revision::().await.map_err(|_| ())?; + Ok(if revision[3] >= 0x03 { + Model::ATECC_ATECC608B } else { - Err(Error::from_status(status)) + Model::ATECC_ATECC608A + }) +} + +fn manual_selftest_print_hex(print: &mut impl FnMut(&str), label: &str, bytes: &[u8]) { + for (chunk_index, chunk) in bytes.chunks(32).enumerate() { + let mut msg = String::new(); + if bytes.len() <= 32 { + writeln!(&mut msg, "{label} ok").unwrap(); + } else { + writeln!(&mut msg, "{label}[{chunk_index}] ok").unwrap(); + } + for byte in chunk { + write!(&mut msg, "{byte:02x}").unwrap(); + } + print(&msg); } } -#[cfg(any(feature = "app-u2f", feature = "factory-setup"))] -pub fn u2f_counter_set(counter: u32) -> Result<(), ()> { - match unsafe { bitbox_securechip_sys::atecc_u2f_counter_set(counter) } { - true => Ok(()), - false => Err(()), +fn manual_selftest_print_unit(print: &mut impl FnMut(&str), label: &str, result: Result<(), ()>) { + match result { + Ok(()) => print(&format!("{label}\nok")), + Err(()) => print(&format!("{label}\nerr")), } } -#[cfg(feature = "app-u2f")] -pub fn u2f_counter_inc() -> Result { - let mut counter = 0; - match unsafe { bitbox_securechip_sys::atecc_u2f_counter_inc(&mut counter) } { - true => Ok(counter), - false => Err(()), +fn manual_selftest_model_name(model: Model) -> &'static str { + match model { + Model::ATECC_ATECC608A => "ATECC608A", + Model::ATECC_ATECC608B => "ATECC608B", + Model::OPTIGA_TRUST_M_V3 => "optiga?", + } +} + +fn manual_selftest_generate_attestation_key( + memory: &mut impl Memory, + print: &mut impl FnMut(&str), +) -> Option<[u8; 64]> { + let auth_key = load_auth_key(memory); + let mut pubkey = [0u8; 64]; + if unsafe { + bitbox_securechip_sys::atecc_gen_attestation_key(auth_key.as_ptr(), pubkey.as_mut_ptr()) + } { + manual_selftest_print_hex(print, "attest_pubkey", &pubkey); + Some(pubkey) + } else { + print("attest_gen_key\nerr"); + None + } +} + +fn manual_selftest_verify_attestation_signature( + pubkey: &[u8; 64], + challenge: &[u8; 32], + signature: &[u8; 64], +) -> Result<(), ()> { + use p256::ecdsa::signature::hazmat::PrehashVerifier; + + let mut sec1_pubkey = [0u8; 65]; + sec1_pubkey[0] = 0x04; + sec1_pubkey[1..].copy_from_slice(pubkey); + let verifying_key = p256::ecdsa::VerifyingKey::from_sec1_bytes(&sec1_pubkey).map_err(|_| ())?; + let signature = p256::ecdsa::Signature::from_slice(signature).map_err(|_| ())?; + verifying_key + .verify_prehash(challenge, &signature) + .map_err(|_| ()) +} + +/// Runs a manual public ATECC API smoke-test and reports every result through `print`. +/// +/// This is intended for temporary on-device diagnostics. The key-reset and password-init steps +/// mutate the secure-chip password-stretching keys, and the U2F steps mutate the U2F counter. +pub async fn manual_selftest( + random: &mut impl Random, + memory: &mut impl Memory, + mut print: impl FnMut(&str), +) { + print("ATECC self-test\nstart"); + + match model::().await { + Ok(model) => print(&format!("model ok\n{}", manual_selftest_model_name(model))), + Err(()) => print("model\nerr"), + } + + match self::random::().await { + Ok(random) => manual_selftest_print_hex(&mut print, "random", random.as_slice()), + Err(err) => print(&format!("random\nerr {}", status_from_error(err))), + } + + match monotonic_increments_remaining::().await { + Ok(remaining) => print(&format!("mono_remaining ok\n{remaining}")), + Err(()) => print("mono_remaining\nerr"), + } + + let kdf_msg = [0x11; 32]; + manual_selftest_print_hex(&mut print, "kdf_msg", &kdf_msg); + match kdf::(memory, &kdf_msg).await { + Ok(kdf_out) => manual_selftest_print_hex(&mut print, "kdf", kdf_out.as_slice()), + Err(err) => print(&format!("kdf\nerr {}", status_from_error(err))), + } + + let challenge = [0x22; 32]; + let mut signature = [0u8; 64]; + if let Some(attestation_pubkey) = manual_selftest_generate_attestation_key(memory, &mut print) { + manual_selftest_print_hex(&mut print, "attest_msg", &challenge); + match attestation_sign::(memory, &challenge, &mut signature).await { + Ok(()) => { + manual_selftest_print_hex(&mut print, "attest_sig", &signature); + manual_selftest_print_unit( + &mut print, + "attest_verify", + manual_selftest_verify_attestation_signature( + &attestation_pubkey, + &challenge, + &signature, + ), + ); + } + Err(()) => print("attest_sign\nerr"), + } } + + print("reset_keys\nmutates keys"); + manual_selftest_print_unit( + &mut print, + "reset_keys", + reset_keys::(random, memory).await, + ); + + let password = "atecc-selftest"; + print("init_new_password\nmutates keys"); + let initialized_password = match init_new_password::( + random, + memory, + password, + PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0, + ) + .await + { + Ok(stretched) => { + manual_selftest_print_hex(&mut print, "init_new_pw", stretched.as_slice()); + Some(stretched) + } + Err(err) => { + print(&format!("init_new_pw\nerr {}", status_from_error(err))); + None + } + }; + + match stretch_password::( + memory, + password, + PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0, + ) + .await + { + Ok(stretched) => { + manual_selftest_print_hex(&mut print, "stretch_pw", stretched.as_slice()); + if let Some(initialized_password) = initialized_password { + if initialized_password.as_slice() == stretched.as_slice() { + print("stretch_cmp\nmatch"); + } else { + print("stretch_cmp\nmismatch"); + } + } + } + Err(err) => print(&format!("stretch_pw\nerr {}", status_from_error(err))), + } + + #[cfg(any(feature = "app-u2f", feature = "factory-setup"))] + { + print("u2f_counter_set\nmutates counter"); + manual_selftest_print_unit( + &mut print, + "u2f_counter_set", + u2f_counter_set::(random, memory, 1).await, + ); + } + + #[cfg(feature = "app-u2f")] + match u2f_counter_inc::(random, memory).await { + Ok(counter) => print(&format!("u2f_counter_inc ok\n{counter}")), + Err(()) => print("u2f_counter_inc\nerr"), + } + + #[cfg(not(any(feature = "app-u2f", feature = "factory-setup")))] + { + print("u2f_counter_set\nskip: feature off"); + } + + #[cfg(not(feature = "app-u2f"))] + { + print("u2f_counter_inc\nskip: feature off"); + } + + print("ATECC self-test\ndone"); } -pub fn model() -> Result { - let mut model = core::mem::MaybeUninit::uninit(); - match unsafe { bitbox_securechip_sys::atecc_model(model.as_mut_ptr()) } { - true => Ok(unsafe { model.assume_init() }), - false => Err(()), +#[cfg(test)] +mod tests { + use super::*; + use bitbox_platform_host::memory::FakeMemory; + use bitbox_platform_host::random::TestingRandom; + use core::{task::Poll, time::Duration}; + use hex_lit::hex; + + const SALT_ROOT_FIXED: [u8; 32] = [0x42; 32]; + const EXPECTED_STRETCHED_OUT_V0: [u8; 32] = + hex!("868f9f41fe3122cdcd0be180779401d7f68e1fa8744a5bbd4f3f117a316da1d5"); + + struct TestTimer; + + impl Timer for TestTimer { + async fn delay_for(_duration: Duration) {} + } + + fn setup_test() -> ( + std::sync::MutexGuard<'static, ()>, + FakeMemory, + TestingRandom, + ) { + let guard = ops::test_lock(); + ops::test_reset(); + let mut memory = FakeMemory::new(); + memory.set_salt_root(&SALT_ROOT_FIXED); + (guard, memory, TestingRandom::new()) + } + + #[async_test::test] + #[allow(clippy::await_holding_lock)] + async fn test_reset_keys() { + let (_guard, mut memory, mut random) = setup_test(); + reset_keys::(&mut random, &mut memory) + .await + .unwrap(); + assert_eq!(ops::test_get_derivekey_rollkey_calls(), 1); + assert_eq!(ops::test_get_kdf_key_write_calls(), 1); + } + + #[async_test::test] + #[allow(clippy::await_holding_lock)] + async fn test_stretch_password() { + let (_guard, mut memory, _random) = setup_test(); + let stretched = stretch_password::( + &mut memory, + "pw", + PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0, + ) + .await + .unwrap(); + assert_eq!(stretched.as_slice(), EXPECTED_STRETCHED_OUT_V0.as_slice(),); + assert_eq!(ops::test_get_rollkey_kdf_calls(), 1); + assert_eq!(ops::test_get_kdf_calls(), KDF_NUM_ITERATIONS); + } + + #[test] + fn test_random_busy() { + let (_guard, _memory, _random) = setup_test(); + ops::test_block_next_chip_command(); + let mut first: util::bb02_async::Task<_> = alloc::boxed::Box::pin(random::()); + assert!(matches!(util::bb02_async::spin(&mut first), Poll::Pending)); + + assert_eq!( + util::bb02_async::block_on(random::()), + Err(Error::Status(ATECC_OPS_STATUS_BUSY)), + ); + + ops::test_unblock_chip_command(); + drop(util::bb02_async::block_on(first).unwrap()); + } + + #[test] + fn test_model_busy() { + let (_guard, _memory, _random) = setup_test(); + ops::test_block_next_chip_command(); + let mut first: util::bb02_async::Task<_> = alloc::boxed::Box::pin(random::()); + assert!(matches!(util::bb02_async::spin(&mut first), Poll::Pending)); + + assert_eq!(util::bb02_async::block_on(model::()), Err(())); + + ops::test_unblock_chip_command(); + drop(util::bb02_async::block_on(first).unwrap()); + } + + #[test] + fn test_drop_releases_high_level_guard() { + let (_guard, _memory, _random) = setup_test(); + ops::test_block_next_chip_command(); + let mut first: util::bb02_async::Task<_> = alloc::boxed::Box::pin(random::()); + assert!(matches!(util::bb02_async::spin(&mut first), Poll::Pending)); + + drop(first); + + assert!(util::bb02_async::block_on(random::()).is_ok()); } } diff --git a/src/rust/bitbox-securechip/src/atecc/ops.rs b/src/rust/bitbox-securechip/src/atecc/ops.rs new file mode 100644 index 0000000000..63fbcc5abe --- /dev/null +++ b/src/rust/bitbox-securechip/src/atecc/ops.rs @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: Apache-2.0 + +use bitbox_hal::timer::Timer; +use bitbox_securechip_sys::atecc_slot_t as Slot; +use core::time::Duration; +use util::cell::SyncCell; +use zeroize::Zeroizing; + +const ATECC_OPS_STATUS_BUSY: i32 = bitbox_securechip_sys::ATECC_OPS_STATUS_BUSY as i32; +pub(super) const NONCE_NUMIN_SIZE: usize = bitbox_securechip_sys::NONCE_NUMIN_SIZE as usize; +const BLOCK_SIZE: usize = 32; +const SIGNATURE_SIZE: usize = 64; + +#[derive(Copy, Clone, Eq, PartialEq)] +enum AsyncOpState { + // No async Rust wrapper owns the shared ATECC command context. + Idle, + // One live Rust future launched an ATECC command and still needs to observe the result. + Running, + // The Rust future was dropped after launching the command, but the C command has not + // completed yet. The old command may still be using the shared command context, so a new call + // must not reset it yet. + Detached, +} + +// The ATECC C API exposes only one shared async command context and status variable, so the Rust +// wrappers can support only one in-flight async operation at a time. The extra Detached state is +// needed because cancellation must not free the slot until polling has really observed completion +// of the old command. +static STATE: SyncCell = SyncCell::new(AsyncOpState::Idle); + +struct AsyncOpGuard { + armed: bool, +} + +impl AsyncOpGuard { + fn new() -> Self { + Self { armed: true } + } + + fn disarm(&mut self) { + self.armed = false; + } +} + +impl Drop for AsyncOpGuard { + fn drop(&mut self) { + if !self.armed { + return; + } + + if poll_status() == ATECC_OPS_STATUS_BUSY { + STATE.write(AsyncOpState::Detached); + } else { + STATE.write(AsyncOpState::Idle); + } + } +} + +fn poll_status() -> i32 { + unsafe { bitbox_securechip_sys::atecc_ops_get_status() } +} + +fn ensure_success(status: i32) -> Result<(), i32> { + if status == 0 { Ok(()) } else { Err(status) } +} + +async fn poll_once() { + let delay_ms = unsafe { bitbox_securechip_sys::atecc_ops_get_poll_delay_ms() } as u64; + if delay_ms > 0 { + T::delay_for(Duration::from_millis(delay_ms)).await; + } + unsafe { + bitbox_securechip_sys::atecc_ops_poll(); + } +} + +async fn reclaim_detached_op() { + match STATE.read() { + AsyncOpState::Idle => { + // Another path already observed completion and released the slot, so there is nothing + // left to reclaim here. + return; + } + AsyncOpState::Running => { + // Sequential callers are required for this wrapper. Reaching this arm means some other + // live future is still using the shared ATECC command context. + panic!("concurrent async atecc operation not supported"); + } + AsyncOpState::Detached => { + // Detached means the old future is gone. Under the sequential-caller assumption, this + // recovery future is now the only one that can wait until polling observes completion + // and then release the shared command context for reuse. + } + } + + let mut guard = AsyncOpGuard::new(); + let _ = wait_until_not_busy::().await; + guard.disarm(); + STATE.write(AsyncOpState::Idle); +} + +async fn begin_async_op() -> AsyncOpGuard { + loop { + match STATE.read() { + AsyncOpState::Idle => { + // We are the only Rust future touching the shared ATECC command context. It is + // safe to launch a new command unless the C status still reports a busy operation, + // in which case a start helper would reset a command that may still be active. + if poll_status() == ATECC_OPS_STATUS_BUSY { + STATE.write(AsyncOpState::Detached); + reclaim_detached_op::().await; + continue; + } + STATE.write(AsyncOpState::Running); + return AsyncOpGuard::new(); + } + AsyncOpState::Running => { + // Sequential callers are required. If we are asked to start another operation while + // one live future still owns the shared command context, something above this + // wrapper broke that invariant. + panic!("concurrent async atecc operation not supported"); + } + AsyncOpState::Detached => { + // A previous future was cancelled after launching the command. Wait until polling + // has really observed completion before reusing the shared command context. + reclaim_detached_op::().await; + } + } + } +} + +fn end_async_op() { + STATE.write(AsyncOpState::Idle); +} + +async fn wait_with_cleanup( + mut guard: AsyncOpGuard, + initial_status: i32, +) -> Result<(), i32> { + let result = wait::(initial_status).await; + guard.disarm(); + end_async_op(); + result +} + +async fn wait_until_not_busy() -> i32 { + loop { + match poll_status() { + ATECC_OPS_STATUS_BUSY => poll_once::().await, + status => return status, + } + } +} + +async fn wait(initial_status: i32) -> Result<(), i32> { + if initial_status != 0 { + return Err(initial_status); + } + + match wait_until_not_busy::().await { + 0 => Ok(()), + status => Err(status), + } +} + +pub(super) async fn chip_nonce_rand( + num_in: &[u8; NONCE_NUMIN_SIZE], +) -> Result, i32> { + let mut rand_out = Zeroizing::new([0u8; 32]); + let guard = begin_async_op::().await; + wait_with_cleanup::(guard, unsafe { + bitbox_securechip_sys::atecc_cmd_start_nonce_rand(num_in.as_ptr()) + }) + .await?; + ensure_success(unsafe { + bitbox_securechip_sys::atecc_cmd_read_random_response((*rand_out).as_mut_ptr()) + })?; + Ok(rand_out) +} + +pub(super) async fn chip_checkmac(response: &Zeroizing<[u8; 32]>) -> Result<(), i32> { + let guard = begin_async_op::().await; + wait_with_cleanup::(guard, unsafe { + bitbox_securechip_sys::atecc_cmd_start_checkmac(response.as_ptr()) + }) + .await +} + +pub(super) async fn chip_random() -> Result, i32> { + let mut rand_out = Zeroizing::new([0u8; 32]); + let guard = begin_async_op::().await; + wait_with_cleanup::(guard, unsafe { + bitbox_securechip_sys::atecc_cmd_start_random() + }) + .await?; + ensure_success(unsafe { + bitbox_securechip_sys::atecc_cmd_read_random_response((*rand_out).as_mut_ptr()) + })?; + Ok(rand_out) +} + +pub(super) async fn chip_counter_read() -> Result { + let mut counter = 0u32; + let guard = begin_async_op::().await; + wait_with_cleanup::(guard, unsafe { + bitbox_securechip_sys::atecc_cmd_start_counter_read() + }) + .await?; + ensure_success(unsafe { + bitbox_securechip_sys::atecc_cmd_read_counter_response(&mut counter) + })?; + Ok(counter) +} + +pub(super) async fn chip_info_revision() -> Result<[u8; 4], i32> { + let mut revision = [0u8; 4]; + let guard = begin_async_op::().await; + wait_with_cleanup::(guard, unsafe { + bitbox_securechip_sys::atecc_cmd_start_info_revision() + }) + .await?; + ensure_success(unsafe { + bitbox_securechip_sys::atecc_cmd_read_info_response(revision.as_mut_ptr()) + })?; + Ok(revision) +} + +pub(super) async fn chip_kdf( + slot: Slot, + msg: &[u8; 32], +) -> Result<(Zeroizing<[u8; 32]>, Zeroizing<[u8; 32]>), i32> { + let mut kdf_out = Zeroizing::new([0u8; 32]); + let mut nonce_out = Zeroizing::new([0u8; 32]); + let guard = begin_async_op::().await; + wait_with_cleanup::(guard, unsafe { + bitbox_securechip_sys::atecc_cmd_start_kdf(slot, msg.as_ptr(), msg.len()) + }) + .await?; + ensure_success(unsafe { + bitbox_securechip_sys::atecc_cmd_read_kdf_response( + (*kdf_out).as_mut_ptr(), + (*nonce_out).as_mut_ptr(), + ) + })?; + Ok((kdf_out, nonce_out)) +} + +pub(super) async fn chip_derivekey_rollkey() -> Result<(), i32> { + let guard = begin_async_op::().await; + wait_with_cleanup::(guard, unsafe { + bitbox_securechip_sys::atecc_cmd_start_derivekey_rollkey() + }) + .await +} + +pub(super) async fn chip_nonce_load_msgdigest(msg: &[u8; 32]) -> Result<(), i32> { + let guard = begin_async_op::().await; + wait_with_cleanup::(guard, unsafe { + bitbox_securechip_sys::atecc_cmd_start_nonce_load_msgdigest(msg.as_ptr()) + }) + .await +} + +pub(super) async fn chip_sign_attestation() -> Result<[u8; SIGNATURE_SIZE], i32> { + let mut signature = [0u8; SIGNATURE_SIZE]; + let guard = begin_async_op::().await; + wait_with_cleanup::(guard, unsafe { + bitbox_securechip_sys::atecc_cmd_start_sign_attestation() + }) + .await?; + ensure_success(unsafe { + bitbox_securechip_sys::atecc_cmd_read_sign_response(signature.as_mut_ptr()) + })?; + Ok(signature) +} + +pub(super) async fn chip_gendig_encryption_key() -> Result<(), i32> { + let guard = begin_async_op::().await; + wait_with_cleanup::(guard, unsafe { + bitbox_securechip_sys::atecc_cmd_start_gendig_encryption_key() + }) + .await +} + +#[cfg(any(feature = "app-u2f", feature = "factory-setup"))] +pub(super) async fn chip_read_block( + slot: Slot, + block: u8, +) -> Result, i32> { + let mut data = Zeroizing::new([0u8; BLOCK_SIZE]); + let guard = begin_async_op::().await; + wait_with_cleanup::(guard, unsafe { + bitbox_securechip_sys::atecc_cmd_start_read_block(slot as u16, block) + }) + .await?; + ensure_success(unsafe { + bitbox_securechip_sys::atecc_cmd_read_block_response((*data).as_mut_ptr()) + })?; + Ok(data) +} + +pub(super) async fn chip_write_encrypted_block( + slot: Slot, + block: u8, + value: &Zeroizing<[u8; BLOCK_SIZE]>, + mac: &Zeroizing<[u8; BLOCK_SIZE]>, +) -> Result<(), i32> { + let guard = begin_async_op::().await; + wait_with_cleanup::(guard, unsafe { + bitbox_securechip_sys::atecc_cmd_start_write_encrypted_block( + slot as u16, + block, + value.as_ptr(), + mac.as_ptr(), + ) + }) + .await +} + +// The C helper first calculates the contents of TempKey with atcah_nonce(), then computes the +// client response with atcah_check_mac(). In this CheckMac mode, the first SHA block comes from +// the slot key and the second from TempKey, so Rust keeps those two host steps bundled here. +pub(super) async fn host_check_mac( + num_in: &[u8; NONCE_NUMIN_SIZE], + rand_out: &Zeroizing<[u8; 32]>, + auth_key: &[u8; 32], +) -> Result, i32> { + let mut response = Zeroizing::new([0u8; 32]); + ensure_success(unsafe { + bitbox_securechip_sys::atecc_auth_compute_response( + num_in.as_ptr(), + rand_out.as_ptr(), + auth_key.as_ptr(), + (*response).as_mut_ptr(), + ) + })?; + Ok(response) +} + +pub(super) async fn host_kdf_decrypt( + io_protection_key: &[u8; 32], + nonce_out: &Zeroizing<[u8; 32]>, + data: &mut Zeroizing<[u8; 32]>, +) -> Result<(), i32> { + ensure_success(unsafe { + bitbox_securechip_sys::atecc_kdf_decrypt( + io_protection_key.as_ptr(), + nonce_out.as_ptr(), + (*data).as_mut_ptr(), + BLOCK_SIZE, + ) + }) +} + +pub(super) async fn host_nonce( + num_in: &[u8; NONCE_NUMIN_SIZE], + rand_out: &Zeroizing<[u8; 32]>, +) -> Result<(), i32> { + ensure_success(unsafe { + bitbox_securechip_sys::atecc_io_prepare_tempkey(num_in.as_ptr(), rand_out.as_ptr()) + }) +} + +pub(super) async fn host_gendig(encryption_key: &[u8; 32]) -> Result<(), i32> { + ensure_success(unsafe { bitbox_securechip_sys::atecc_io_apply_gendig(encryption_key.as_ptr()) }) +} + +pub(super) async fn host_write_auth_mac( + slot: Slot, + block: u8, + input_data: &Zeroizing<[u8; BLOCK_SIZE]>, +) -> Result<(Zeroizing<[u8; BLOCK_SIZE]>, Zeroizing<[u8; BLOCK_SIZE]>), i32> { + let mut encrypted = Zeroizing::new([0u8; BLOCK_SIZE]); + let mut mac = Zeroizing::new([0u8; BLOCK_SIZE]); + ensure_success(unsafe { + bitbox_securechip_sys::atecc_io_prepare_encrypted_write( + slot as u16, + block, + input_data.as_ptr(), + (*encrypted).as_mut_ptr(), + (*mac).as_mut_ptr(), + ) + })?; + Ok((encrypted, mac)) +} + +#[cfg(any(feature = "app-u2f", feature = "factory-setup"))] +pub(super) async fn host_io_decrypt(data: &mut Zeroizing<[u8; BLOCK_SIZE]>) -> Result<(), i32> { + ensure_success(unsafe { + bitbox_securechip_sys::atecc_io_decrypt_block((*data).as_mut_ptr(), BLOCK_SIZE) + }) +} diff --git a/src/rust/bitbox-securechip/src/atecc/ops_fake.rs b/src/rust/bitbox-securechip/src/atecc/ops_fake.rs new file mode 100644 index 0000000000..f4064aead7 --- /dev/null +++ b/src/rust/bitbox-securechip/src/atecc/ops_fake.rs @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: Apache-2.0 + +#![allow(clippy::extra_unused_type_parameters)] + +use bitbox_hal::timer::Timer; +use bitbox_securechip_sys::atecc_slot_t as Slot; +use core::future::poll_fn; +use core::task::Poll; +use std::sync::{LazyLock, Mutex, MutexGuard}; +use util::sha2::hmac_sha256 as util_hmac_sha256; +use zeroize::Zeroizing; + +pub(super) const NONCE_NUMIN_SIZE: usize = bitbox_securechip_sys::NONCE_NUMIN_SIZE as usize; +const BLOCK_SIZE: usize = 32; +const SLOT_ROLLKEY: Slot = Slot::ATECC_SLOT_ROLLKEY; +const SLOT_KDF: Slot = Slot::ATECC_SLOT_KDF; +const SLOT_DATA0: Slot = Slot::ATECC_SLOT_DATA0; + +#[derive(Clone)] +struct FakeState { + data0_block0: [u8; BLOCK_SIZE], + block_next_chip_command: bool, + blocked_chip_command_active: bool, + derivekey_rollkey_calls: usize, + rollkey_kdf_calls: usize, + kdf_calls: usize, + kdf_key_write_calls: usize, +} + +impl Default for FakeState { + fn default() -> Self { + Self { + data0_block0: [0; BLOCK_SIZE], + block_next_chip_command: false, + blocked_chip_command_active: false, + derivekey_rollkey_calls: 0, + rollkey_kdf_calls: 0, + kdf_calls: 0, + kdf_key_write_calls: 0, + } + } +} + +static TEST_LOCK: Mutex<()> = Mutex::new(()); +static STATE: LazyLock> = LazyLock::new(|| Mutex::new(FakeState::default())); + +fn lock_state() -> MutexGuard<'static, FakeState> { + STATE.lock().unwrap() +} + +fn fake_hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] { + let mut result = [0u8; 32]; + util_hmac_sha256(key, data, &mut result); + result +} + +async fn maybe_block_next_chip_command() { + let should_block = { + let mut state = lock_state(); + if state.block_next_chip_command { + state.block_next_chip_command = false; + state.blocked_chip_command_active = true; + true + } else { + false + } + }; + + if !should_block { + return; + } + + poll_fn(|_| { + if lock_state().blocked_chip_command_active { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; +} + +pub(super) fn test_lock() -> MutexGuard<'static, ()> { + TEST_LOCK.lock().unwrap() +} + +pub(super) fn test_reset() { + *lock_state() = FakeState::default(); +} + +pub(super) fn test_block_next_chip_command() { + let mut state = lock_state(); + state.block_next_chip_command = true; +} + +pub(super) fn test_unblock_chip_command() { + let mut state = lock_state(); + state.blocked_chip_command_active = false; +} + +pub(super) fn test_get_derivekey_rollkey_calls() -> usize { + lock_state().derivekey_rollkey_calls +} + +pub(super) fn test_get_rollkey_kdf_calls() -> usize { + lock_state().rollkey_kdf_calls +} + +pub(super) fn test_get_kdf_calls() -> usize { + lock_state().kdf_calls +} + +pub(super) fn test_get_kdf_key_write_calls() -> usize { + lock_state().kdf_key_write_calls +} + +pub(super) async fn chip_nonce_rand( + _num_in: &[u8; NONCE_NUMIN_SIZE], +) -> Result, i32> { + maybe_block_next_chip_command().await; + Ok(Zeroizing::new([0u8; 32])) +} + +pub(super) async fn chip_checkmac(_response: &Zeroizing<[u8; 32]>) -> Result<(), i32> { + maybe_block_next_chip_command().await; + Ok(()) +} + +pub(super) async fn chip_random() -> Result, i32> { + maybe_block_next_chip_command().await; + Ok(Zeroizing::new([0u8; 32])) +} + +pub(super) async fn chip_counter_read() -> Result { + maybe_block_next_chip_command().await; + Ok(2_097_150) +} + +pub(super) async fn chip_info_revision() -> Result<[u8; 4], i32> { + maybe_block_next_chip_command().await; + Ok([0, 0, 0, 0x03]) +} + +pub(super) async fn chip_kdf( + slot: Slot, + msg: &[u8; 32], +) -> Result<(Zeroizing<[u8; 32]>, Zeroizing<[u8; 32]>), i32> { + maybe_block_next_chip_command().await; + + let key = if slot == SLOT_ROLLKEY { + let mut state = lock_state(); + state.rollkey_kdf_calls += 1; + b"atecc-rollkey".as_slice() + } else { + let mut state = lock_state(); + state.kdf_calls += 1; + b"atecc-kdf".as_slice() + }; + Ok(( + Zeroizing::new(fake_hmac_sha256(key, msg)), + Zeroizing::new([0u8; 32]), + )) +} + +pub(super) async fn chip_derivekey_rollkey() -> Result<(), i32> { + maybe_block_next_chip_command().await; + let mut state = lock_state(); + state.derivekey_rollkey_calls += 1; + Ok(()) +} + +pub(super) async fn chip_nonce_load_msgdigest(_msg: &[u8; 32]) -> Result<(), i32> { + maybe_block_next_chip_command().await; + Ok(()) +} + +pub(super) async fn chip_sign_attestation() -> Result<[u8; 64], i32> { + maybe_block_next_chip_command().await; + Err(-1) +} + +pub(super) async fn chip_gendig_encryption_key() -> Result<(), i32> { + maybe_block_next_chip_command().await; + Ok(()) +} + +pub(super) async fn chip_read_block( + slot: Slot, + block: u8, +) -> Result, i32> { + maybe_block_next_chip_command().await; + if slot == SLOT_DATA0 && block == 0 { + Ok(Zeroizing::new(lock_state().data0_block0)) + } else { + Ok(Zeroizing::new([0u8; BLOCK_SIZE])) + } +} + +pub(super) async fn chip_write_encrypted_block( + slot: Slot, + block: u8, + value: &Zeroizing<[u8; BLOCK_SIZE]>, + _mac: &Zeroizing<[u8; BLOCK_SIZE]>, +) -> Result<(), i32> { + maybe_block_next_chip_command().await; + if slot == SLOT_DATA0 && block == 0 { + let mut state = lock_state(); + state.data0_block0 = **value; + } + Ok(()) +} + +pub(super) async fn host_check_mac( + _num_in: &[u8; NONCE_NUMIN_SIZE], + _rand_out: &Zeroizing<[u8; 32]>, + _auth_key: &[u8; 32], +) -> Result, i32> { + Ok(Zeroizing::new([0u8; 32])) +} + +pub(super) async fn host_kdf_decrypt( + _io_protection_key: &[u8; 32], + _nonce_out: &Zeroizing<[u8; 32]>, + _data: &mut Zeroizing<[u8; 32]>, +) -> Result<(), i32> { + Ok(()) +} + +pub(super) async fn host_nonce( + _num_in: &[u8; NONCE_NUMIN_SIZE], + _rand_out: &Zeroizing<[u8; 32]>, +) -> Result<(), i32> { + Ok(()) +} + +pub(super) async fn host_gendig(_encryption_key: &[u8; 32]) -> Result<(), i32> { + Ok(()) +} + +pub(super) async fn host_write_auth_mac( + slot: Slot, + _block: u8, + input_data: &Zeroizing<[u8; BLOCK_SIZE]>, +) -> Result<(Zeroizing<[u8; BLOCK_SIZE]>, Zeroizing<[u8; BLOCK_SIZE]>), i32> { + if slot == SLOT_KDF { + let mut state = lock_state(); + state.kdf_key_write_calls += 1; + } + Ok(( + Zeroizing::new(**input_data), + Zeroizing::new([0u8; BLOCK_SIZE]), + )) +} + +pub(super) async fn host_io_decrypt(_data: &mut Zeroizing<[u8; BLOCK_SIZE]>) -> Result<(), i32> { + Ok(()) +} diff --git a/src/rust/bitbox-securechip/src/optiga.rs b/src/rust/bitbox-securechip/src/optiga.rs index 9d599d6ece..0cab168a2e 100644 --- a/src/rust/bitbox-securechip/src/optiga.rs +++ b/src/rust/bitbox-securechip/src/optiga.rs @@ -535,7 +535,7 @@ pub async fn u2f_counter_inc() -> Result { Ok(counter) } -pub fn model() -> Result { +pub async fn model() -> Result { Ok(Model::OPTIGA_TRUST_M_V3) } diff --git a/src/rust/bitbox02-rust/src/attestation.rs b/src/rust/bitbox02-rust/src/attestation.rs index b2bad6125a..f465b5277d 100644 --- a/src/rust/bitbox02-rust/src/attestation.rs +++ b/src/rust/bitbox02-rust/src/attestation.rs @@ -26,9 +26,14 @@ pub async fn perform(hal: &mut impl crate::hal::Hal, host_challenge: [u8; 32]) - )?; result.bootloader_hash = hal.memory().get_attestation_bootloader_hash(); let hash: [u8; 32] = Sha256::digest(host_challenge).into(); - hal.securechip() - .attestation_sign(&hash, &mut result.challenge_signature) - .await?; + { + let crate::hal::HalSubsystems { + securechip, memory, .. + } = hal.as_mut(); + securechip + .attestation_sign(memory, &hash, &mut result.challenge_signature) + .await?; + } Ok(result) } diff --git a/src/rust/bitbox02-rust/src/hww/api/device_info.rs b/src/rust/bitbox02-rust/src/hww/api/device_info.rs index 7252821a1b..642c6455c9 100644 --- a/src/rust/bitbox02-rust/src/hww/api/device_info.rs +++ b/src/rust/bitbox02-rust/src/hww/api/device_info.rs @@ -38,7 +38,7 @@ pub async fn process(hal: &mut impl crate::hal::Hal) -> Result version: crate::version::FIRMWARE_VERSION_SHORT.into(), mnemonic_passphrase_enabled: hal.memory().is_mnemonic_passphrase_enabled(), monotonic_increments_remaining: hal.securechip().monotonic_increments_remaining().await?, - securechip_model: match hal.securechip().model()? { + securechip_model: match hal.securechip().model().await? { securechip::Model::Atecc608A => "ATECC608A".into(), securechip::Model::Atecc608B => "ATECC608B".into(), securechip::Model::OptigaTrustM3 => "OPTIGA_TRUST_M_V3".into(), diff --git a/src/rust/bitbox02-rust/src/hww/api/restore.rs b/src/rust/bitbox02-rust/src/hww/api/restore.rs index ddd5e91c25..a5c5e4a793 100644 --- a/src/rust/bitbox02-rust/src/hww/api/restore.rs +++ b/src/rust/bitbox02-rust/src/hww/api/restore.rs @@ -79,7 +79,17 @@ pub async fn from_file( { // Ignore error - the U2f counter not being set can lead to problems with U2F, but it should // not fail the recovery, so the user can access their coins. - let _ = hal.securechip().u2f_counter_set(request.timestamp).await; + let _ = { + let crate::hal::HalSubsystems { + securechip, + random, + memory, + .. + } = hal.as_mut(); + securechip + .u2f_counter_set(random, memory, request.timestamp) + .await + }; } hal.memory().set_initialized().or(Err(Error::Memory))?; @@ -160,7 +170,15 @@ pub async fn from_mnemonic( { // Ignore error - the U2f counter not being set can lead to problems with U2F, but it should // not fail the recovery, so the user can access their coins. - let _ = hal.securechip().u2f_counter_set(timestamp).await; + let _ = { + let crate::hal::HalSubsystems { + securechip, + random, + memory, + .. + } = hal.as_mut(); + securechip.u2f_counter_set(random, memory, timestamp).await + }; } hal.memory().set_initialized().or(Err(Error::Memory))?; diff --git a/src/rust/bitbox02-rust/src/keystore.rs b/src/rust/bitbox02-rust/src/keystore.rs index 1da9c65f5e..bbc6366b66 100644 --- a/src/rust/bitbox02-rust/src/keystore.rs +++ b/src/rust/bitbox02-rust/src/keystore.rs @@ -37,6 +37,7 @@ pub trait KeystoreHal { fn memory(&mut self) -> &mut Self::Memory; fn random(&mut self) -> &mut Self::Random; fn securechip(&mut self) -> &mut Self::SecureChip; + fn memory_and_securechip(&mut self) -> (&mut Self::Memory, &mut Self::SecureChip); fn random_and_securechip(&mut self) -> (&mut Self::Random, &mut Self::SecureChip); } @@ -101,6 +102,10 @@ impl KeystoreHal self.securechip } + fn memory_and_securechip(&mut self) -> (&mut Self::Memory, &mut Self::SecureChip) { + (self.memory, self.securechip) + } + fn random_and_securechip(&mut self) -> (&mut Self::Random, &mut Self::SecureChip) { (self.random, self.securechip) } @@ -743,7 +748,10 @@ pub async fn stretch_retained_seed_encryption_key( let salted_in = bitbox_core_utils::salt::hash_data(hal.memory(), encryption_key, purpose_in) .map_err(|_| Error::Salt)?; - let kdf = hal.securechip().kdf(&salted_in).await?; + let kdf = { + let (memory, securechip) = hal.memory_and_securechip(); + securechip.kdf(memory, &salted_in).await? + }; let salted_out = bitbox_core_utils::salt::hash_data(hal.memory(), encryption_key, purpose_out) .map_err(|_| Error::Salt)?; diff --git a/src/rust/bitbox02-rust/src/reset.rs b/src/rust/bitbox02-rust/src/reset.rs index 0746034d1d..492e0dced5 100644 --- a/src/rust/bitbox02-rust/src/reset.rs +++ b/src/rust/bitbox02-rust/src/reset.rs @@ -43,7 +43,14 @@ pub(crate) async fn reset(hal: &mut impl crate::hal::Hal, status: bool) { { let mut u2f_ok = false; for _ in 0..5 { - if hal.securechip().u2f_counter_set(0).await.is_ok() { + let result = { + let subsystems = hal.as_mut(); + subsystems + .securechip + .u2f_counter_set(subsystems.random, subsystems.memory, 0) + .await + }; + if result.is_ok() { u2f_ok = true; break; } @@ -98,7 +105,15 @@ mod tests { hal.securechip.mock_reset_keys_fails(); // Simulate a non-zero U2F counter before reset. - hal.securechip.u2f_counter_set(42).await.unwrap(); + { + let securechip = &mut hal.securechip; + let random = &mut hal.random; + let memory = &mut hal.memory; + securechip + .u2f_counter_set(random, memory, 42) + .await + .unwrap(); + } hal.securechip.event_counter_reset(); reset(&mut hal, true).await; diff --git a/src/rust/bitbox02/src/hal.rs b/src/rust/bitbox02/src/hal.rs index abb99dd8af..3bb29509b5 100644 --- a/src/rust/bitbox02/src/hal.rs +++ b/src/rust/bitbox02/src/hal.rs @@ -15,7 +15,7 @@ pub struct BitBox02Hal { ui: ui::BitBox02Ui, sd: sd::BitBox02Sd, random: random::BitBox02Random, - securechip: securechip::BitBox02SecureChip, + securechip: securechip::BitBox02SecureChip, memory: memory::BitBox02Memory, eeprom: eeprom::BitBox02Eeprom, system: system::BitBox02System, @@ -31,7 +31,7 @@ impl BitBox02Hal { ui: ui::BitBox02Ui::new(), sd: sd::BitBox02Sd, random: random::BitBox02Random, - securechip: securechip::BitBox02SecureChip, + securechip: securechip::BitBox02SecureChip::new(), memory: memory::BitBox02Memory, eeprom: eeprom::BitBox02Eeprom, system: system::BitBox02System::new(), @@ -49,7 +49,7 @@ impl Hal for BitBox02Hal { type Ui = ui::BitBox02Ui; type Random = random::BitBox02Random; type Sd = sd::BitBox02Sd; - type SecureChip = securechip::BitBox02SecureChip; + type SecureChip = securechip::BitBox02SecureChip; type Memory = memory::BitBox02Memory; type Eeprom = eeprom::BitBox02Eeprom; type System = system::BitBox02System; diff --git a/src/rust/bitbox02/src/hal/memory.rs b/src/rust/bitbox02/src/hal/memory.rs index 145aef6b44..f75a8410b0 100644 --- a/src/rust/bitbox02/src/hal/memory.rs +++ b/src/rust/bitbox02/src/hal/memory.rs @@ -227,10 +227,18 @@ impl Memory for BitBox02Memory { crate::memory::add_noise_remote_static_pubkey(pubkey) } + fn get_auth_key(&mut self, out: &mut [u8; 32]) { + crate::memory::get_auth_key(out) + } + fn get_io_protection_key(&mut self, out: &mut [u8; 32]) { crate::memory::get_io_protection_key(out) } + fn get_encryption_key(&mut self, out: &mut [u8; 32]) { + crate::memory::get_encryption_key(out) + } + fn get_salt_root(&mut self) -> Result>, ()> { crate::memory::get_salt_root() } diff --git a/src/rust/bitbox02/src/hal/securechip.rs b/src/rust/bitbox02/src/hal/securechip.rs index 8fa5b3eb1a..b192e549c1 100644 --- a/src/rust/bitbox02/src/hal/securechip.rs +++ b/src/rust/bitbox02/src/hal/securechip.rs @@ -1,12 +1,19 @@ // SPDX-License-Identifier: Apache-2.0 use alloc::boxed::Box; +use core::marker::PhantomData; use bitbox_hal::SecureChip; use bitbox_hal::memory::PasswordStretchAlgo; use bitbox_hal::securechip::{Error, Model, SecureChipError}; -pub struct BitBox02SecureChip; +pub struct BitBox02SecureChip(PhantomData); + +impl BitBox02SecureChip { + pub const fn new() -> Self { + Self(PhantomData) + } +} fn to_hal_model(model: bitbox_securechip::Model) -> Model { match model { @@ -76,9 +83,11 @@ fn to_c_password_stretch_algo(algo: PasswordStretchAlgo) -> bitbox_securechip::P } } -impl SecureChip for BitBox02SecureChip { +impl SecureChip for BitBox02SecureChip { async fn random(&mut self) -> Result>, Error> { - crate::securechip::random().await.map_err(to_hal_error) + crate::securechip::random::() + .await + .map_err(to_hal_error) } async fn init_new_password( @@ -88,7 +97,7 @@ impl SecureChip for BitBox02SecureChip { password: &str, password_stretch_algo: PasswordStretchAlgo, ) -> Result>, Error> { - crate::securechip::init_new_password( + crate::securechip::init_new_password::( random, memory, password, @@ -104,7 +113,7 @@ impl SecureChip for BitBox02SecureChip { password: &str, password_stretch_algo: PasswordStretchAlgo, ) -> Result>, Error> { - crate::securechip::stretch_password( + crate::securechip::stretch_password::( memory, password, to_c_password_stretch_algo(password_stretch_algo), @@ -113,24 +122,31 @@ impl SecureChip for BitBox02SecureChip { .map_err(to_hal_error) } - async fn kdf(&mut self, msg: &[u8; 32]) -> Result>, Error> { - crate::securechip::kdf(msg).await.map_err(to_hal_error) + async fn kdf( + &mut self, + memory: &mut impl bitbox_hal::Memory, + msg: &[u8; 32], + ) -> Result>, Error> { + crate::securechip::kdf::(memory, msg) + .await + .map_err(to_hal_error) } async fn attestation_sign( &mut self, + memory: &mut impl bitbox_hal::Memory, challenge: &[u8; 32], signature: &mut [u8; 64], ) -> Result<(), ()> { - crate::securechip::attestation_sign(challenge, signature).await + crate::securechip::attestation_sign::(memory, challenge, signature).await } async fn monotonic_increments_remaining(&mut self) -> Result { - crate::securechip::monotonic_increments_remaining().await + crate::securechip::monotonic_increments_remaining::().await } - fn model(&mut self) -> Result { - crate::securechip::model().map(to_hal_model) + async fn model(&mut self) -> Result { + crate::securechip::model::().await.map(to_hal_model) } async fn reset_keys( @@ -138,17 +154,26 @@ impl SecureChip for BitBox02SecureChip { random: &mut impl bitbox_hal::Random, memory: &mut impl bitbox_hal::Memory, ) -> Result<(), ()> { - crate::securechip::reset_keys(random, memory).await + crate::securechip::reset_keys::(random, memory).await } #[cfg(feature = "app-u2f")] - async fn u2f_counter_set(&mut self, counter: u32) -> Result<(), ()> { - crate::securechip::u2f_counter_set(counter).await + async fn u2f_counter_set( + &mut self, + random: &mut impl bitbox_hal::Random, + memory: &mut impl bitbox_hal::Memory, + counter: u32, + ) -> Result<(), ()> { + crate::securechip::u2f_counter_set::(random, memory, counter).await } #[cfg(feature = "app-u2f")] - async fn u2f_counter_inc(&mut self) -> Result { - crate::securechip::u2f_counter_inc().await + async fn u2f_counter_inc( + &mut self, + random: &mut impl bitbox_hal::Random, + memory: &mut impl bitbox_hal::Memory, + ) -> Result { + crate::securechip::u2f_counter_inc::(random, memory).await } } @@ -288,16 +313,17 @@ mod tests { #[async_test::test] async fn test_kdf() { - let mut securechip = BitBox02SecureChip; + let mut securechip = BitBox02SecureChip::::new(); + let mut memory = crate::hal::memory::BitBox02Memory; let msg = [0u8; 32]; - let result = securechip.kdf(&msg).await.unwrap(); + let result = securechip.kdf(&mut memory, &msg).await.unwrap(); let expected = hex!("1c723ccd9597e76deb55f9fd6808014007bcb3d67fc060f1149aefb9be88f423"); assert_eq!(result.as_slice(), expected.as_slice()); } #[async_test::test] async fn test_init_new_password_invalid_password_stretch_algo() { - let mut securechip = BitBox02SecureChip; + let mut securechip = BitBox02SecureChip::::new(); let mut random = TestRandom; let mut memory = crate::hal::memory::BitBox02Memory; assert_eq!( diff --git a/src/rust/bitbox02/src/hal/system.rs b/src/rust/bitbox02/src/hal/system.rs index 6cf11c5b14..5d308fb561 100644 --- a/src/rust/bitbox02/src/hal/system.rs +++ b/src/rust/bitbox02/src/hal/system.rs @@ -1,6 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 use bitbox_hal::System; +#[cfg(not(any( + feature = "testing", + feature = "c-unit-testing", + feature = "simulator-graphical" +)))] +use bitbox_hal::{Memory, memory::SecurechipType}; use core::marker::PhantomData; use core::time::Duration; @@ -22,6 +28,30 @@ impl Default for BitBox02System { } } +#[cfg(not(any( + feature = "testing", + feature = "c-unit-testing", + feature = "simulator-graphical" +)))] +async fn manual_atecc_selftest() { + let mut memory = super::memory::BitBox02Memory; + match memory.get_securechip_type() { + Ok(SecurechipType::Atecc) => { + let mut random = super::random::BitBox02Random; + bitbox_securechip::atecc::manual_selftest::( + &mut random, + &mut memory, + |message| crate::screen_print_debug(message, 5000), + ) + .await; + } + Ok(SecurechipType::Optiga) => { + crate::screen_print_debug("ATECC self-test\nskip: optiga", 5000) + } + Err(()) => crate::screen_print_debug("ATECC self-test\nsecurechip type err", 5000), + } +} + impl System for BitBox02System { async fn startup() { let upside_down = crate::ui::choose_orientation().await; @@ -29,6 +59,13 @@ impl System for BitBox02System { crate::screen_rotate(); } + #[cfg(not(any( + feature = "testing", + feature = "c-unit-testing", + feature = "simulator-graphical" + )))] + manual_atecc_selftest::().await; + // During this delay the bb02 logotype is shown. Timer::delay_for(Duration::from_millis(1300)).await; diff --git a/src/rust/bitbox02/src/memory.rs b/src/rust/bitbox02/src/memory.rs index 415eea867d..ecfd6631f2 100644 --- a/src/rust/bitbox02/src/memory.rs +++ b/src/rust/bitbox02/src/memory.rs @@ -323,13 +323,11 @@ pub(crate) fn get_io_protection_key(out: &mut [u8; 32]) { unsafe { bitbox02_sys::memory_get_io_protection_key(out.as_mut_ptr()) } } -#[cfg(test)] -fn get_authorization_key(out: &mut [u8; 32]) { +pub(crate) fn get_auth_key(out: &mut [u8; 32]) { unsafe { bitbox02_sys::memory_get_authorization_key(out.as_mut_ptr()) } } -#[cfg(test)] -fn get_encryption_key(out: &mut [u8; 32]) { +pub(crate) fn get_encryption_key(out: &mut [u8; 32]) { unsafe { bitbox02_sys::memory_get_encryption_key(out.as_mut_ptr()) } } @@ -764,22 +762,22 @@ mod tests { assert!(memory_setup(memory_setup_rand_mock_test_functional)); let mut io_protection_key = [0u8; 32]; - let mut authorization_key = [0u8; 32]; + let mut auth_key = [0u8; 32]; let mut encryption_key = [0u8; 32]; get_io_protection_key(&mut io_protection_key); - get_authorization_key(&mut authorization_key); + get_auth_key(&mut auth_key); get_encryption_key(&mut encryption_key); let expected_io_protection_key = hex!("866b7a17a54a694eb6ea57d6754f76bc257cdd6bffc392813bbbd9fbedd5b145"); - let expected_authorization_key = + let expected_auth_key = hex!("42b36c8396572dcde005d737ddad0036deb3f02351f460c43901fa520530b7d2"); let expected_encryption_key = hex!("f4ee396cee35c5eccff2c9c7e9f0decbd1c82324f4b61c8ceeab819129abaf9c"); assert_eq!(io_protection_key, expected_io_protection_key); - assert_eq!(authorization_key, expected_authorization_key); + assert_eq!(auth_key, expected_auth_key); assert_eq!(encryption_key, expected_encryption_key); assert!(memory_setup(memory_setup_rand_mock_test_functional)); @@ -789,11 +787,11 @@ mod tests { assert!(bootloader_set_flags(bitbox02_sys::secfalse_u8 as u8, true)); get_io_protection_key(&mut io_protection_key); - get_authorization_key(&mut authorization_key); + get_auth_key(&mut auth_key); get_encryption_key(&mut encryption_key); assert_eq!(io_protection_key, expected_io_protection_key); - assert_eq!(authorization_key, expected_authorization_key); + assert_eq!(auth_key, expected_auth_key); assert_eq!(encryption_key, expected_encryption_key); } diff --git a/src/rust/bitbox02/src/securechip/imp.rs b/src/rust/bitbox02/src/securechip/imp.rs index 79bd31385f..1e1290c319 100644 --- a/src/rust/bitbox02/src/securechip/imp.rs +++ b/src/rust/bitbox02/src/securechip/imp.rs @@ -19,88 +19,111 @@ fn backend() -> Backend { BACKEND.read().unwrap() } -pub async fn attestation_sign(challenge: &[u8; 32], signature: &mut [u8; 64]) -> Result<(), ()> { +type DefaultTimer = crate::hal::timer::BitBox02Timer; + +pub async fn attestation_sign( + memory: &mut impl Memory, + challenge: &[u8; 32], + signature: &mut [u8; 64], +) -> Result<(), ()> { match backend() { - Backend::Atecc => atecc::attestation_sign(challenge, signature).await, + Backend::Atecc => atecc::attestation_sign::(memory, challenge, signature).await, Backend::Optiga => optiga::attestation_sign(challenge, signature).await, } } -pub async fn random() -> Result>, Error> { +pub async fn random() -> Result>, Error> { match backend() { - Backend::Atecc => atecc::random(), + Backend::Atecc => atecc::random::().await, Backend::Optiga => optiga::random().await, } } -pub async fn monotonic_increments_remaining() -> Result { +pub async fn monotonic_increments_remaining() -> Result { match backend() { - Backend::Atecc => atecc::monotonic_increments_remaining(), + Backend::Atecc => atecc::monotonic_increments_remaining::().await, Backend::Optiga => optiga::monotonic_increments_remaining().await, } } -pub async fn reset_keys(random: &mut impl Random, memory: &mut impl Memory) -> Result<(), ()> { +pub async fn reset_keys( + random: &mut impl Random, + memory: &mut impl Memory, +) -> Result<(), ()> { match backend() { - Backend::Atecc => atecc::reset_keys(), + Backend::Atecc => atecc::reset_keys::(random, memory).await, Backend::Optiga => optiga::reset_keys(random, memory).await, } } -pub async fn init_new_password( +pub async fn init_new_password( random: &mut impl Random, memory: &mut impl Memory, password: &str, password_stretch_algo: PasswordStretchAlgo, ) -> Result>, Error> { match backend() { - Backend::Atecc => atecc::init_new_password(memory, password, password_stretch_algo), + Backend::Atecc => { + atecc::init_new_password::(random, memory, password, password_stretch_algo).await + } Backend::Optiga => { optiga::init_new_password(random, memory, password, password_stretch_algo).await } } } -pub async fn stretch_password( +pub async fn stretch_password( memory: &mut impl Memory, password: &str, password_stretch_algo: PasswordStretchAlgo, ) -> Result>, Error> { match backend() { - Backend::Atecc => atecc::stretch_password(memory, password, password_stretch_algo), + Backend::Atecc => { + atecc::stretch_password::(memory, password, password_stretch_algo).await + } Backend::Optiga => optiga::stretch_password(memory, password, password_stretch_algo).await, } } /// Perform the secure chip KDF with the message in `msg` and return the zeroizing 32-byte /// result. -pub async fn kdf(msg: &[u8; 32]) -> Result>, Error> { +pub async fn kdf( + memory: &mut impl Memory, + msg: &[u8; 32], +) -> Result>, Error> { match backend() { - Backend::Atecc => atecc::kdf(msg), + Backend::Atecc => atecc::kdf::(memory, msg).await, Backend::Optiga => optiga::kdf(msg).await, } } #[cfg(any(feature = "app-u2f", feature = "factory-setup"))] -pub async fn u2f_counter_set(counter: u32) -> Result<(), ()> { +pub async fn u2f_counter_set( + random: &mut impl Random, + memory: &mut impl Memory, + counter: u32, +) -> Result<(), ()> { match backend() { - Backend::Atecc => atecc::u2f_counter_set(counter), + Backend::Atecc => atecc::u2f_counter_set::(random, memory, counter).await, Backend::Optiga => optiga::u2f_counter_set(counter).await, } } #[cfg(feature = "app-u2f")] -pub async fn u2f_counter_inc() -> Result { +pub async fn u2f_counter_inc( + random: &mut impl Random, + memory: &mut impl Memory, +) -> Result { match backend() { - Backend::Atecc => atecc::u2f_counter_inc(), + Backend::Atecc => atecc::u2f_counter_inc::(random, memory).await, Backend::Optiga => optiga::u2f_counter_inc().await, } } -pub fn model() -> Result { +pub async fn model() -> Result { match backend() { - Backend::Atecc => atecc::model(), - Backend::Optiga => optiga::model(), + Backend::Atecc => atecc::model::().await, + Backend::Optiga => optiga::model().await, } } @@ -125,11 +148,24 @@ pub extern "C" fn rust_securechip_init() -> bool { /// Returns `0` on success. Negative values are [`SecureChipError`] codes. Positive values are /// backend-specific status codes from CryptoAuthLib or the Optiga library. #[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_securechip_setup( - ifs: *const bitbox_securechip_sys::securechip_interface_functions_t, -) -> c_int { +pub unsafe extern "C" fn rust_securechip_setup() -> c_int { match backend() { - Backend::Atecc => unsafe { bitbox_securechip_sys::atecc_setup(ifs) }, + Backend::Atecc => { + let mut memory = crate::hal::memory::BitBox02Memory; + let mut io_protection_key = Box::new(Zeroizing::new([0u8; 32])); + let mut auth_key = Box::new(Zeroizing::new([0u8; 32])); + let mut encryption_key = Box::new(Zeroizing::new([0u8; 32])); + memory.get_io_protection_key(io_protection_key.as_mut()); + memory.get_auth_key(auth_key.as_mut()); + memory.get_encryption_key(encryption_key.as_mut()); + unsafe { + bitbox_securechip_sys::atecc_setup( + io_protection_key.as_ptr(), + auth_key.as_ptr(), + encryption_key.as_ptr(), + ) + } + } Backend::Optiga => unsafe { bitbox_securechip_sys::optiga_setup() }, } } @@ -139,14 +175,21 @@ pub unsafe extern "C" fn rust_securechip_setup( pub extern "C" fn rust_securechip_reset_keys() -> bool { let mut random = crate::hal::random::BitBox02Random; let mut memory = crate::hal::memory::BitBox02Memory; - util::bb02_async::block_on(reset_keys(&mut random, &mut memory)).is_ok() + util::bb02_async::block_on(reset_keys::(&mut random, &mut memory)).is_ok() } /// Generates a new device attestation key and writes the public key to `pubkey_out`. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_securechip_gen_attestation_key(pubkey_out: *mut u8) -> bool { match backend() { - Backend::Atecc => unsafe { bitbox_securechip_sys::atecc_gen_attestation_key(pubkey_out) }, + Backend::Atecc => { + let mut memory = crate::hal::memory::BitBox02Memory; + let mut auth_key = Box::new(Zeroizing::new([0u8; 32])); + memory.get_auth_key(auth_key.as_mut()); + unsafe { + bitbox_securechip_sys::atecc_gen_attestation_key(auth_key.as_ptr(), pubkey_out) + } + } Backend::Optiga => unsafe { bitbox_securechip_sys::optiga_gen_attestation_key(pubkey_out) }, } } @@ -154,7 +197,7 @@ pub unsafe extern "C" fn rust_securechip_gen_attestation_key(pubkey_out: *mut u8 /// Fills `rand_out` with 32 bytes of randomness from the secure chip. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_securechip_random(rand_out: *mut u8) -> bool { - match util::bb02_async::block_on(random()) { + match util::bb02_async::block_on(random::()) { Ok(random) => { unsafe { core::ptr::copy_nonoverlapping(random.as_ptr(), rand_out, 32); @@ -171,7 +214,14 @@ pub unsafe extern "C" fn rust_securechip_random(rand_out: *mut u8) -> bool { #[cfg(any(feature = "app-u2f", feature = "factory-setup"))] #[unsafe(no_mangle)] pub extern "C" fn rust_securechip_u2f_counter_set(counter: u32) -> bool { - util::bb02_async::block_on(u2f_counter_set(counter)).is_ok() + let mut random = crate::hal::random::BitBox02Random; + let mut memory = crate::hal::memory::BitBox02Memory; + util::bb02_async::block_on(u2f_counter_set::( + &mut random, + &mut memory, + counter, + )) + .is_ok() } #[cfg(feature = "app-u2f")] @@ -179,7 +229,9 @@ pub extern "C" fn rust_securechip_u2f_counter_set(counter: u32) -> bool { #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_securechip_u2f_counter_inc(counter: *mut u32) -> bool { assert!(!counter.is_null()); - match util::bb02_async::block_on(u2f_counter_inc()) { + let mut random = crate::hal::random::BitBox02Random; + let mut memory = crate::hal::memory::BitBox02Memory; + match util::bb02_async::block_on(u2f_counter_inc::(&mut random, &mut memory)) { Ok(current) => { unsafe { *counter = current; diff --git a/src/rust/bitbox02/src/securechip/imp_fake.rs b/src/rust/bitbox02/src/securechip/imp_fake.rs index d921679283..7bb5cbcadc 100644 --- a/src/rust/bitbox02/src/securechip/imp_fake.rs +++ b/src/rust/bitbox02/src/securechip/imp_fake.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 +#![allow(clippy::extra_unused_type_parameters)] + use alloc::boxed::Box; use bitbox_securechip::{Error, Model, PasswordStretchAlgo, SecureChipError}; use hex_lit::hex; @@ -24,26 +26,30 @@ fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] { out } -pub async fn attestation_sign(_challenge: &[u8; 32], _signature: &mut [u8; 64]) -> Result<(), ()> { +pub async fn attestation_sign( + _memory: &mut impl bitbox_hal::Memory, + _challenge: &[u8; 32], + _signature: &mut [u8; 64], +) -> Result<(), ()> { Err(()) } -pub async fn random() -> Result>, Error> { +pub async fn random() -> Result>, Error> { Ok(Box::new(Zeroizing::new([0u8; 32]))) } -pub async fn monotonic_increments_remaining() -> Result { +pub async fn monotonic_increments_remaining() -> Result { Ok(1) } -pub async fn reset_keys( +pub async fn reset_keys( _random: &mut impl bitbox_hal::Random, _memory: &mut impl bitbox_hal::Memory, ) -> Result<(), ()> { Ok(()) } -pub async fn init_new_password( +pub async fn init_new_password( _random: &mut impl bitbox_hal::Random, _memory: &mut impl bitbox_hal::Memory, password: &str, @@ -60,7 +66,7 @@ pub async fn init_new_password( )))) } -pub async fn stretch_password( +pub async fn stretch_password( _memory: &mut impl bitbox_hal::Memory, password: &str, _password_stretch_algo: PasswordStretchAlgo, @@ -73,24 +79,34 @@ pub async fn stretch_password( /// Perform the secure chip KDF with the message in `msg` and return the zeroizing 32-byte /// result. -pub async fn kdf(msg: &[u8; 32]) -> Result>, Error> { +pub async fn kdf( + _memory: &mut impl bitbox_hal::Memory, + msg: &[u8; 32], +) -> Result>, Error> { Ok(Box::new(Zeroizing::new(hmac_sha256(&KDF_KEY, msg)))) } #[cfg(any(feature = "app-u2f", feature = "factory-setup"))] -pub async fn u2f_counter_set(counter: u32) -> Result<(), ()> { +pub async fn u2f_counter_set( + _random: &mut impl bitbox_hal::Random, + _memory: &mut impl bitbox_hal::Memory, + counter: u32, +) -> Result<(), ()> { U2F_COUNTER.write(counter); Ok(()) } #[cfg(feature = "app-u2f")] -pub async fn u2f_counter_inc() -> Result { +pub async fn u2f_counter_inc( + _random: &mut impl bitbox_hal::Random, + _memory: &mut impl bitbox_hal::Memory, +) -> Result { let current = U2F_COUNTER.read().wrapping_add(1); U2F_COUNTER.write(current); Ok(current) } -pub fn model() -> Result { +pub async fn model() -> Result { Ok(Model::ATECC_ATECC608B) } @@ -99,7 +115,12 @@ pub fn model() -> Result { #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_securechip_u2f_counter_inc(counter: *mut u32) -> bool { assert!(!counter.is_null()); - match util::bb02_async::block_on(u2f_counter_inc()) { + let mut random = crate::hal::random::BitBox02Random; + let mut memory = crate::hal::memory::BitBox02Memory; + match util::bb02_async::block_on(u2f_counter_inc::( + &mut random, + &mut memory, + )) { Ok(current) => { unsafe { *counter = current; diff --git a/src/rust/bitbox03/src/memory.rs b/src/rust/bitbox03/src/memory.rs index 68f9ce3f56..cde7fc1eeb 100644 --- a/src/rust/bitbox03/src/memory.rs +++ b/src/rust/bitbox03/src/memory.rs @@ -153,7 +153,15 @@ impl hal::memory::Memory for BitBox03Memory { todo!() } + fn get_auth_key(&mut self, _out: &mut [u8; 32]) { + todo!() + } + fn get_io_protection_key(&mut self, _out: &mut [u8; 32]) { todo!() } + + fn get_encryption_key(&mut self, _out: &mut [u8; 32]) { + todo!() + } } diff --git a/src/rust/bitbox03/src/securechip.rs b/src/rust/bitbox03/src/securechip.rs index 6ef88f043a..1dc76fa630 100644 --- a/src/rust/bitbox03/src/securechip.rs +++ b/src/rust/bitbox03/src/securechip.rs @@ -33,6 +33,7 @@ impl hal::securechip::SecureChip for BitBox03SecureChip { async fn kdf( &mut self, + _memory: &mut impl bitbox_hal::Memory, _msg: &[u8; 32], ) -> Result>, bitbox_hal::securechip::Error> { @@ -41,6 +42,7 @@ impl hal::securechip::SecureChip for BitBox03SecureChip { async fn attestation_sign( &mut self, + _memory: &mut impl bitbox_hal::Memory, _challenge: &[u8; 32], _signature: &mut [u8; 64], ) -> Result<(), ()> { @@ -51,7 +53,7 @@ impl hal::securechip::SecureChip for BitBox03SecureChip { todo!() } - fn model(&mut self) -> Result { + async fn model(&mut self) -> Result { todo!() } @@ -64,12 +66,21 @@ impl hal::securechip::SecureChip for BitBox03SecureChip { } #[cfg(feature = "app-u2f")] - async fn u2f_counter_set(&mut self, _counter: u32) -> Result<(), ()> { + async fn u2f_counter_set( + &mut self, + _random: &mut impl bitbox_hal::Random, + _memory: &mut impl bitbox_hal::Memory, + _counter: u32, + ) -> Result<(), ()> { todo!() } #[cfg(feature = "app-u2f")] - async fn u2f_counter_inc(&mut self) -> Result { + async fn u2f_counter_inc( + &mut self, + _random: &mut impl bitbox_hal::Random, + _memory: &mut impl bitbox_hal::Memory, + ) -> Result { todo!() } } diff --git a/src/securechip/securechip.h b/src/securechip/securechip.h index 924a155502..f847614d50 100644 --- a/src/securechip/securechip.h +++ b/src/securechip/securechip.h @@ -47,23 +47,6 @@ typedef enum { SECURECHIP_PASSWORD_STRETCH_ALGO_V1, } securechip_password_stretch_algo_t; -typedef struct { - /** - * @param[out] key_out must be of size 32 - */ - void (*const get_auth_key)(uint8_t* key_out); - /** - * @param[out] key_out must be of size 32 - */ - void (*const get_io_protection_key)(uint8_t* key_out); - /** - * @param[out] key_out must be of size 32 - */ - void (*const get_encryption_key)(uint8_t* key_out); - - void (*const random_32_bytes)(uint8_t* buf); -} securechip_interface_functions_t; - /* The common securechip ABI is implemented in Rust and declared in rust/rust.h. */ typedef enum { diff --git a/test/simulator-graphical-bb03/Cargo.lock b/test/simulator-graphical-bb03/Cargo.lock index 1f0765f7be..1b35a3766c 100644 --- a/test/simulator-graphical-bb03/Cargo.lock +++ b/test/simulator-graphical-bb03/Cargo.lock @@ -271,6 +271,12 @@ dependencies = [ "rustc_version 0.2.3", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base58ck" version = "0.1.0" @@ -438,6 +444,7 @@ dependencies = [ "critical-section", "der", "grounded", + "p256", "util", "zeroize", ] @@ -874,6 +881,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "core-foundation" version = "0.9.4" @@ -1015,6 +1028,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -1071,6 +1096,10 @@ name = "der" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] [[package]] name = "deranged" @@ -1088,6 +1117,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -1129,6 +1159,19 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -1157,6 +1200,24 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "embedded-hal" version = "0.2.7" @@ -1286,6 +1347,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -1384,6 +1455,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1516,6 +1588,17 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "half" version = "2.7.1" @@ -2359,6 +2442,18 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "paste" version = "1.0.15" @@ -2488,6 +2583,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -2742,6 +2846,16 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rgb" version = "0.8.52" @@ -2847,6 +2961,19 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + [[package]] name = "secp256k1" version = "0.29.1" @@ -2972,6 +3099,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", + "rand_core 0.6.4", ] [[package]] diff --git a/test/simulator-graphical/Cargo.lock b/test/simulator-graphical/Cargo.lock index 4bdfa92a88..b03d44a249 100644 --- a/test/simulator-graphical/Cargo.lock +++ b/test/simulator-graphical/Cargo.lock @@ -233,6 +233,12 @@ dependencies = [ "rustc_version 0.2.3", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base58ck" version = "0.1.0" @@ -382,6 +388,7 @@ dependencies = [ "critical-section", "der", "grounded", + "p256", "util", "zeroize", ] @@ -825,6 +832,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "core-foundation" version = "0.9.4" @@ -957,6 +970,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -1013,6 +1038,10 @@ name = "der" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] [[package]] name = "deranged" @@ -1030,6 +1059,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -1071,6 +1101,19 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -1099,6 +1142,24 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "embedded-hal" version = "0.2.7" @@ -1228,6 +1289,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.3" @@ -1301,6 +1372,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1444,6 +1516,17 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "half" version = "2.6.0" @@ -2300,6 +2383,18 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "paste" version = "1.0.15" @@ -2406,6 +2501,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -2655,6 +2759,16 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rgb" version = "0.8.52" @@ -2760,6 +2874,19 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + [[package]] name = "secp256k1" version = "0.29.0" @@ -2894,6 +3021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", + "rand_core", ] [[package]]