What changed, and why it matters
This commit is a large refactoring that moves the password-stretching and secure-chip password operations from C into Rust for the BitBox02 firmware. It does not add new user-facing features or change the cryptographic algorithm; it reimplements the same OPTIGA secure-chip flows (HMAC verification, symmetric encryption, key generation, counter resets) in Rust with async wrappers. The old C implementation and its unit tests are removed, and equivalent Rust unit tests with deterministic fakes are added. There is no indication in the commit that this fixes a known security vulnerability.
Treat this as a high-risk refactoring rather than a vulnerability patch. Review the new Rust async wrappers for lifetime and concurrency correctness, especially the static `StaticBytes`/`GroundedCell` buffers shared across futures, ensure zeroization happens on all error paths, verify that the fake-based unit tests adequately cover real OPTIGA error conditions (e.g., 0x802F incorrect password, counter exhaustion, and cleanup failures), and run hardware-in-the-loop tests to confirm identical behavior with the old C implementation.
Security signals we found
Large refactor of security-critical password-stretching code
Move from C to Rust with async FFI wrappers for OPTIGA commands
Use of static buffers (`StaticBytes`, `GroundedCell`) to satisfy async C callback lifetime requirements
Zeroization of static buffers after async operations
Preservation of monotonic counter logic for brute-force protection
No algorithmic changes observed; same salts and KDF flow retained
Removal of C unit tests and addition of equivalent Rust tests with fakes
Evidence from the diff
The change ports OPTIGA password operations (init_new_password, stretch_password, reset_keys, KDF helpers, authorization, counter management) from src/optiga/optiga.c and optiga_ops.c into src/rust/bitbox-securechip/src/optiga.rs and optiga/ops.rs. It introduces async Rust bindings for previously synchronous C wrappers (optiga_util_write_data, optiga_crypt_symmetric_encrypt, optiga_crypt_generate_auth_code, optiga_crypt_hmac_verify, optiga_crypt_clear_auto_state, etc.), updates the SecureChip trait to be async and accept a Memory reference, and propagates those changes through bitbox02, bitbox03, simulator, and keystore/reset call sites. The C unit test file test/unit-test/test_optiga.c is deleted and replaced by Rust tests in optiga.rs using a new ops_fake.rs deterministic fake. The cryptographic construction (salted SHA-256, HMAC-SHA256, CMAC-based internal KDF, monotonic counter logic) is preserved.
Changed components
src/optiga/optiga.csrc/optiga/optiga.hsrc/optiga/optiga_ops.csrc/optiga/optiga_ops.hsrc/rust/bitbox-securechip/src/optiga.rssrc/rust/bitbox-securechip/src/optiga/ops.rssrc/rust/bitbox-securechip/src/optiga/ops_fake.rssrc/rust/bitbox-hal/src/securechip.rssrc/rust/bitbox02/src/securechip/imp.rssrc/rust/bitbox02-rust/src/keystore.rssrc/rust/bitbox02-rust/src/reset.rsInspect captured patch +1681 / −1762
diff --git a/src/optiga/optiga.c b/src/optiga/optiga.c
index e4e1923..33e5919 100644
--- a/src/optiga/optiga.c
+++ b/src/optiga/optiga.c
@@ -30,10 +30,6 @@
#define VERIFY_METADATA 0
#endif
-// This number of KDF iterations on the external kdf slot when stretching the device
-// password using the V0 algorithm.
-#define KDF_NUM_ITERATIONS_V0 (2)
-
// Struct stored in the arbitrary data object.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpacked"
@@ -456,36 +452,7 @@ static const uint8_t _counter_hmac_writeprotected_metadata[] = {
0x00,
};
-static int _authorize(uint16_t oid_auth, const uint8_t* auth_secret, size_t auth_secret_len)
-{
- optiga_lib_status_t res;
-
- uint8_t random_data[32] = {0};
-
- res = optiga_ops_crypt_generate_auth_code_sync(
- _crypt, OPTIGA_RNG_TYPE_TRNG, NULL, 0, random_data, sizeof(random_data));
- if (res != OPTIGA_CRYPT_SUCCESS) {
- util_log("generate auth code failed: %x", res);
- return res;
- }
-
- uint8_t hmac[32] = {0};
- rust_hmac_sha256(auth_secret, auth_secret_len, random_data, sizeof(random_data), hmac);
- res = optiga_ops_crypt_hmac_verify_sync(
- _crypt,
- OPTIGA_HMAC_SHA_256,
- oid_auth,
- random_data,
- sizeof(random_data),
- hmac,
- sizeof(hmac));
- if (res != OPTIGA_CRYPT_SUCCESS) {
- util_log("auth failed: %x %x", oid_auth, res);
- return res;
- }
- return 0;
-}
-
+#if FACTORYSETUP == 1 || FACTORY_DURING_PROD == 1
static int _reset_counter(uint16_t oid, uint32_t limit)
{
// Configure the monotonic counter.
@@ -499,6 +466,7 @@ static int _reset_counter(uint16_t oid, uint32_t limit)
return optiga_ops_util_write_data_sync(
_util, oid, OPTIGA_UTIL_ERASE_AND_WRITE, 0, counter_buf, sizeof(counter_buf));
}
+#endif
#if APP_U2F == 1 || FACTORYSETUP == 1
static bool _read_arbitrary_data(arbitrary_data_t* data_out)
@@ -1112,407 +1080,6 @@ static int _maybe_update_config_v1(void)
return 0;
}
-static int _set_password(
- const uint8_t* password_secret,
- size_t password_secret_len,
- const uint8_t* auth_password,
- size_t auth_password_len)
-{
- uint8_t auth_password_salted_hashed[32] = {0};
- UTIL_CLEANUP_32(auth_password_salted_hashed);
-
- optiga_lib_status_t res = _authorize(OID_PASSWORD_SECRET, password_secret, password_secret_len);
- if (res != OPTIGA_UTIL_SUCCESS) {
- goto cleanup;
- }
-
- if (!rust_salt_hash_data(
- rust_util_bytes(auth_password, auth_password_len),
- "optiga_password",
- rust_util_bytes_mut(
- auth_password_salted_hashed, sizeof(auth_password_salted_hashed)))) {
- res = SC_ERR_SALT;
- goto cleanup;
- }
-
- res = optiga_ops_util_write_data_sync(
- _util,
- OID_PASSWORD,
- OPTIGA_UTIL_ERASE_AND_WRITE,
- 0x00,
- auth_password_salted_hashed,
- sizeof(auth_password_salted_hashed));
- if (res != OPTIGA_UTIL_SUCCESS) {
- goto cleanup;
- }
-
- // We add one extra to the counter threshold, as afterwards, we will
- // write to the write-protected hmac slot, which increments the counter.
- res = _reset_counter(OID_COUNTER_PASSWORD, SMALL_MONOTONIC_COUNTER_MAX_USE + 1);
- if (res != OPTIGA_LIB_SUCCESS) {
- goto cleanup;
- }
-
-cleanup: {
- optiga_lib_status_t res_clear =
- optiga_ops_crypt_clear_auto_state_sync(_crypt, OID_PASSWORD_SECRET);
- if (res != OPTIGA_UTIL_SUCCESS) {
- return res;
- }
- return res_clear;
-}
-}
-
-static int _kdf_hmac(uint16_t optiga_oid, const uint8_t* msg, size_t len, uint8_t* mac_out)
-{
- if (len != 32) {
- return SC_ERR_INVALID_ARGS;
- }
-
- optiga_lib_status_t res;
- // The equivalient of python `mac_out = hmac.new(key, msg[:len], hashlib.sha256).digest()`
-
- uint32_t mac_out_len = 32;
-
- res = optiga_ops_crypt_hmac_sync(
- _crypt, OPTIGA_HMAC_SHA_256, optiga_oid, msg, len, mac_out, &mac_out_len);
- if (res != OPTIGA_LIB_SUCCESS) {
- util_log("kdf fail err=%x", res);
- return res;
- }
- if (mac_out_len != 32) {
- return SC_OPTIGA_ERR_UNEXPECTED_LEN;
- }
-
- return 0;
-}
-
-static int _kdf_internal(const uint8_t* msg, size_t len, uint8_t* kdf_out)
-{
- if (len != 32) {
- return SC_ERR_INVALID_ARGS;
- }
- optiga_lib_status_t res;
-
- uint8_t mac_out[16] = {0};
- uint32_t mac_out_len = sizeof(mac_out);
-
- res = optiga_ops_crypt_symmetric_encrypt_sync(
- _crypt,
- OPTIGA_SYMMETRIC_CMAC,
- OID_AES_SYMKEY,
- msg,
- len,
- NULL,
- 0,
- NULL,
- 0,
- mac_out,
- &mac_out_len);
- if (res != OPTIGA_LIB_SUCCESS) {
- return res;
- }
- if (mac_out_len != sizeof(mac_out)) {
- return SC_OPTIGA_ERR_UNEXPECTED_LEN;
- }
- rust_sha256(mac_out, mac_out_len, kdf_out);
- return 0;
-}
-
-static int _set_hmac_writeprotected(
- const uint8_t* hmac_key,
- const uint8_t* auth_password,
- size_t auth_password_len)
-{
- uint8_t auth_password_salted_hashed[32] = {0};
- UTIL_CLEANUP_32(auth_password_salted_hashed);
- if (!rust_salt_hash_data(
- rust_util_bytes(auth_password, auth_password_len),
- "optiga_password",
- rust_util_bytes_mut(
- auth_password_salted_hashed, sizeof(auth_password_salted_hashed)))) {
- return SC_ERR_SALT;
- }
-
- optiga_lib_status_t res =
- _authorize(OID_PASSWORD, auth_password_salted_hashed, sizeof(auth_password_salted_hashed));
- if (res) {
- goto cleanup;
- }
-
- res = optiga_ops_util_write_data_sync(
- _util, OID_HMAC_WRITEPROTECTED, OPTIGA_UTIL_ERASE_AND_WRITE, 0x00, hmac_key, 32);
- if (res) {
- util_log("failed updating the hmac-writeprotected key: %x", res);
- goto cleanup;
- }
-
- res = _reset_counter(OID_COUNTER_HMAC_WRITEPROTECTED, SMALL_MONOTONIC_COUNTER_MAX_USE);
- if (res) {
- goto cleanup;
- }
-
-cleanup: {
- optiga_lib_status_t res_clear = optiga_ops_crypt_clear_auto_state_sync(_crypt, OID_PASSWORD);
- return res ? res : res_clear;
-}
-}
-
-static int _v1_get_auth_password(
- const char* password,
- const uint8_t* hmac_key,
- uint8_t* stretched_password_out)
-{
- 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)),
- "optiga_password_stretch_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 internal key increments the large monotonic counter. Call only once!
- int securechip_result = _kdf_internal(kdf_in, 32, stretched_password_out);
- if (securechip_result) {
- return securechip_result;
- }
- // Second KDF increments the small monotonic counter in `OID_HMAC_WRITEPROTECTED`. Call only
- // once!
- memcpy(kdf_in, stretched_password_out, 32);
- if (hmac_key != NULL) {
- rust_hmac_sha256(hmac_key, 32, kdf_in, 32, stretched_password_out);
- } else {
- securechip_result = _kdf_hmac(OID_HMAC_WRITEPROTECTED, kdf_in, 32, stretched_password_out);
- if (securechip_result) {
- if (securechip_result == 0x802F) {
- return SC_ERR_INCORRECT_PASSWORD;
- }
- return securechip_result;
- }
- }
-
- return 0;
-}
-
-static int _v1_combine(
- const char* password,
- const uint8_t* auth_password,
- const uint8_t* password_secret,
- uint8_t* stretched_out)
-{
- rust_hmac_sha256(password_secret, 32, auth_password, 32, stretched_out);
-
- 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)),
- "optiga_password_stretch_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;
-}
-
-int optiga_init_new_password(
- const char* password,
- securechip_password_stretch_algo_t password_stretch_algo,
- uint8_t* stretched_out)
-{
- if (password_stretch_algo != SECURECHIP_PASSWORD_STRETCH_ALGO_V1) {
- // New passwords must use the latest algo.
- return SC_ERR_INVALID_PASSWORD_STRETCH_ALGO;
- }
-
- // Set new hmac key.
- uint8_t new_hmac_key[32] = {0};
- UTIL_CLEANUP_32(new_hmac_key);
- _ifs->random_32_bytes(new_hmac_key);
- optiga_lib_status_t res = optiga_ops_util_write_data_sync(
- _util, OID_HMAC, OPTIGA_UTIL_ERASE_AND_WRITE, 0x00, new_hmac_key, sizeof(new_hmac_key));
- if (res != OPTIGA_UTIL_SUCCESS) {
- util_log("failed updating the hmac key: %x", res);
- return res;
- }
-
- // Set new symmetric key.
- optiga_key_id_t keyid = OPTIGA_KEY_ID_SECRET_BASED;
- res = optiga_ops_crypt_symmetric_generate_key_sync(
- _crypt, OPTIGA_SYMMETRIC_AES_256, OPTIGA_KEY_USAGE_ENCRYPTION, false, &keyid);
- if (res != OPTIGA_UTIL_SUCCESS) {
- util_log("failed updating the sym key: %x", res);
- return res;
- }
-
- uint8_t password_secret[32] = {0};
- UTIL_CLEANUP_32(password_secret);
- _ifs->random_32_bytes(password_secret);
-
- res = optiga_ops_util_write_data_sync(
- _util,
- OID_PASSWORD_SECRET,
- OPTIGA_UTIL_ERASE_AND_WRITE,
- 0x00,
- password_secret,
- sizeof(password_secret));
- if (res != OPTIGA_UTIL_SUCCESS) {
- return res;
- }
-
- uint8_t new_hmac_writeprotected_key[32] = {0};
- UTIL_CLEANUP_32(new_hmac_writeprotected_key);
- _ifs->random_32_bytes(new_hmac_writeprotected_key);
-
- uint8_t auth_password[32] = {0};
- UTIL_CLEANUP_32(auth_password);
- res = _v1_get_auth_password(password, new_hmac_writeprotected_key, auth_password);
- if (res) {
- return res;
- }
-
- res = _set_password(
- password_secret, sizeof(password_secret), auth_password, sizeof(auth_password));
- if (res) {
- return res;
- }
-
- res =
- _set_hmac_writeprotected(new_hmac_writeprotected_key, auth_password, sizeof(auth_password));
- if (res) {
- return res;
- }
-
- return _v1_combine(password, auth_password, password_secret, stretched_out);
-}
-
-bool optiga_reset_keys(void)
-{
- // This resets the OID_AES_SYMKEY and OID_HMAC/OID_HMAC_WRITEPROTECTED keys, as well as the
- // OID_PASSWORD_SECRET and OID_PASSWORD keys. A password is needed because updating the
- // OID_PASSWORD key requires auth using the OID_PASSWORD_SECRET key, but any password is fine
- // for the purpose of resetting the keys.
-
- // We reset using V1, the latest algorithm. It covers resetting everything from V0 as well.
- uint8_t stretched[32];
- return optiga_init_new_password("", SECURECHIP_PASSWORD_STRETCH_ALGO_V1, stretched) == 0;
-}
-
-static int _optiga_verify_password_v0(const char* password, uint8_t* password_secret_out)
-{
- 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)),
- "optiga_password",
- rust_util_bytes_mut(password_salted_hashed, sizeof(password_salted_hashed)))) {
- return SC_ERR_SALT;
- }
-
- optiga_lib_status_t res =
- _authorize(OID_PASSWORD, password_salted_hashed, sizeof(password_salted_hashed));
- if (res != OPTIGA_LIB_SUCCESS) {
- goto cleanup;
- }
-
- uint16_t password_secret_size = 32;
- res = optiga_ops_util_read_data_sync(
- _util, OID_PASSWORD_SECRET, 0, password_secret_out, &password_secret_size);
- if (res != OPTIGA_LIB_SUCCESS) {
- goto cleanup;
- }
- if (password_secret_size != 32) {
- res = SC_OPTIGA_ERR_UNEXPECTED_LEN;
- goto cleanup;
- }
-
- res = _authorize(OID_PASSWORD_SECRET, password_secret_out, password_secret_size);
- if (res != OPTIGA_LIB_SUCCESS) {
- goto cleanup;
- }
-
- res = _reset_counter(OID_COUNTER_PASSWORD, SMALL_MONOTONIC_COUNTER_MAX_USE);
- if (res != OPTIGA_LIB_SUCCESS) {
- goto cleanup;
- }
-
-cleanup: {
- optiga_lib_status_t res_clear1 = optiga_ops_crypt_clear_auto_state_sync(_crypt, OID_PASSWORD);
- optiga_lib_status_t res_clear2 =
- optiga_ops_crypt_clear_auto_state_sync(_crypt, OID_PASSWORD_SECRET);
- if (res != OPTIGA_UTIL_SUCCESS) {
- return res;
- }
- if (res_clear1) {
- return res_clear1;
- }
- return res_clear2;
-}
-}
-
-static int _optiga_verify_password_v1(const uint8_t* auth_password, uint8_t* password_secret_out)
-{
- uint8_t auth_password_salted_hashed[32] = {0};
- UTIL_CLEANUP_32(auth_password_salted_hashed);
- if (!rust_salt_hash_data(
- rust_util_bytes(auth_password, 32),
- "optiga_password",
- rust_util_bytes_mut(
- auth_password_salted_hashed, sizeof(auth_password_salted_hashed)))) {
- return SC_ERR_SALT;
- }
-
- optiga_lib_status_t res =
- _authorize(OID_PASSWORD, auth_password_salted_hashed, sizeof(auth_password_salted_hashed));
- if (res) {
- goto cleanup;
- }
-
- uint16_t password_secret_size = 32;
- res = optiga_ops_util_read_data_sync(
- _util, OID_PASSWORD_SECRET, 0, password_secret_out, &password_secret_size);
- if (res) {
- goto cleanup;
- }
- if (password_secret_size != 32) {
- res = SC_OPTIGA_ERR_UNEXPECTED_LEN;
- goto cleanup;
- }
-
- res = _authorize(OID_PASSWORD_SECRET, password_secret_out, password_secret_size);
- if (res) {
- goto cleanup;
- }
-
- res = _reset_counter(OID_COUNTER_PASSWORD, SMALL_MONOTONIC_COUNTER_MAX_USE);
- if (res) {
- goto cleanup;
- }
-
- res = _reset_counter(OID_COUNTER_HMAC_WRITEPROTECTED, SMALL_MONOTONIC_COUNTER_MAX_USE);
- if (res) {
- goto cleanup;
- }
-
-cleanup: {
- optiga_lib_status_t res_clear1 = optiga_ops_crypt_clear_auto_state_sync(_crypt, OID_PASSWORD);
- optiga_lib_status_t res_clear2 =
- optiga_ops_crypt_clear_auto_state_sync(_crypt, OID_PASSWORD_SECRET);
- if (res) {
- return res;
- }
- if (res_clear1) {
- return res_clear1;
- }
- return res_clear2;
-}
-}
-
#if VERIFY_METADATA == 1
static int _verify_metadata_config(void)
{
@@ -1700,106 +1267,6 @@ int optiga_setup(const securechip_interface_functions_t* ifs)
return 0;
}
-int optiga_kdf_external(const uint8_t* msg, size_t len, uint8_t* mac_out)
-{
- return _kdf_hmac(OID_HMAC, msg, len, mac_out);
-}
-
-static int _stretch_password_v0(const char* password, uint8_t* stretched_out)
-{
- 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)),
- "optiga_password_stretch_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 internal key increments the large monotonic counter. Call only once!
- int securechip_result = _kdf_internal(kdf_in, 32, stretched_out);
- if (securechip_result) {
- return securechip_result;
- }
- // Second KDF does not use any counters and we call it multiple times.
- for (int i = 0; i < KDF_NUM_ITERATIONS_V0; i++) {
- memcpy(kdf_in, stretched_out, 32);
- securechip_result = optiga_kdf_external(kdf_in, 32, stretched_out);
- if (securechip_result) {
- return securechip_result;
- }
- }
-
- // Verify password incrementing the small monotonic counter.
- // We do this after the above KDF stretch so the big monotonic counter is also incremented.
- uint8_t password_secret[32] = {0};
- UTIL_CLEANUP_32(password_secret);
- int res = _optiga_verify_password_v0(password, password_secret);
- if (res) {
- if (res == 0x802F) {
- return SC_ERR_INCORRECT_PASSWORD;
- }
- return res;
- }
-
- rust_hmac_sha256(password_secret, sizeof(password_secret), stretched_out, 32, stretched_out);
-
- if (!rust_salt_hash_data(
- rust_util_bytes((const uint8_t*)password, strlen(password)),
- "optiga_password_stretch_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;
-}
-
-static int _stretch_password_v1(const char* password, uint8_t* stretched_out)
-{
- uint8_t auth_password[32] = {0};
- UTIL_CLEANUP_32(auth_password);
- // Get auth password. This increments the small monotonic counter in
- // `OID_COUNTER_HMAC_WRITEPROTECTED` and the large monotonic counter.
- int res = _v1_get_auth_password(password, NULL, auth_password);
- if (res) {
- return res;
- }
- // Verify password incrementing the small monotonic counter in `OID_COUNTER_PASSWORD`.
- uint8_t password_secret[32] = {0};
- UTIL_CLEANUP_32(password_secret);
- res = _optiga_verify_password_v1(auth_password, password_secret);
- if (res) {
- if (res == 0x802F) {
- return SC_ERR_INCORRECT_PASSWORD;
- }
- return res;
- }
-
- return _v1_combine(password, auth_password, password_secret, stretched_out);
-}
-
-int optiga_stretch_password(
- const char* password,
- securechip_password_stretch_algo_t password_stretch_algo,
- uint8_t* stretched_out)
-{
- switch (password_stretch_algo) {
- case SECURECHIP_PASSWORD_STRETCH_ALGO_V0:
- util_log("stretching password using algo v0");
- return _stretch_password_v0(password, stretched_out);
- case SECURECHIP_PASSWORD_STRETCH_ALGO_V1:
- util_log("stretching password using algo v1");
- return _stretch_password_v1(password, stretched_out);
- default:
- return SC_ERR_INVALID_PASSWORD_STRETCH_ALGO;
- }
-}
-
bool optiga_gen_attestation_key(uint8_t* pubkey_out)
{
optiga_key_id_t slot = OPTIGA_KEY_ID_E0F1;
@@ -1855,6 +1322,15 @@ optiga_crypt_t* optiga_crypt_instance(void)
return _crypt;
}
+bool optiga_ifs_random_32_bytes(uint8_t* rand_out)
+{
+ if (_ifs == NULL || rand_out == NULL) {
+ return false;
+ }
+ _ifs->random_32_bytes(rand_out);
+ return true;
+}
+
// rand_out must be 32 bytes
int optiga_random(uint8_t* rand_out)
{
diff --git a/src/optiga/optiga.h b/src/optiga/optiga.h
index 90f0ad9..0cbcf4e 100644
--- a/src/optiga/optiga.h
+++ b/src/optiga/optiga.h
@@ -9,7 +9,6 @@
#include "securechip/securechip.h"
#include <platform/platform_config.h>
#include <stdbool.h>
-#include <stddef.h>
#include <stdint.h>
typedef struct optiga_util optiga_util_t;
@@ -92,20 +91,11 @@ typedef struct optiga_crypt optiga_crypt_t;
#define METADATA_MAX_SIZE (44 + 2)
USE_RESULT int optiga_setup(const securechip_interface_functions_t* ifs);
-USE_RESULT int optiga_kdf_external(const uint8_t* msg, size_t len, uint8_t* mac_out);
-USE_RESULT int optiga_init_new_password(
- const char* password,
- securechip_password_stretch_algo_t password_stretch_algo,
- uint8_t* stretched_out);
-USE_RESULT int optiga_stretch_password(
- const char* password,
- securechip_password_stretch_algo_t password_stretch_algo,
- uint8_t* stretched_out);
-USE_RESULT bool optiga_reset_keys(void);
USE_RESULT bool optiga_gen_attestation_key(uint8_t* pubkey_out);
USE_RESULT bool optiga_attestation_sign(const uint8_t* challenge, uint8_t* signature_out);
USE_RESULT optiga_util_t* optiga_util_instance(void);
USE_RESULT optiga_crypt_t* optiga_crypt_instance(void);
+USE_RESULT bool optiga_ifs_random_32_bytes(uint8_t* rand_out);
USE_RESULT int optiga_random(uint8_t* rand_out);
#if APP_U2F == 1 || FACTORYSETUP == 1
USE_RESULT bool optiga_u2f_counter_set(uint32_t counter);
diff --git a/src/optiga/optiga_ops.c b/src/optiga/optiga_ops.c
index 05c43a6..e593d07 100644
--- a/src/optiga/optiga_ops.c
+++ b/src/optiga/optiga_ops.c
@@ -134,26 +134,10 @@ optiga_lib_status_t optiga_ops_util_close_application_sync(
return res;
}
-optiga_lib_status_t optiga_ops_crypt_hmac_sync(
- optiga_crypt_t* me,
- optiga_hmac_type_t type,
- uint16_t secret,
- const uint8_t* input_data,
- uint32_t input_data_length,
- uint8_t* mac,
- uint32_t* mac_length)
-{
- _optiga_lib_status = OPTIGA_LIB_BUSY;
- optiga_lib_status_t res =
- optiga_crypt_hmac(me, type, secret, input_data, input_data_length, mac, mac_length);
- _WAIT(res, _optiga_lib_status);
- return res;
-}
-
optiga_lib_status_t optiga_ops_crypt_ecc_generate_keypair_sync(
optiga_crypt_t* me,
optiga_ecc_curve_t curve_id,
- uint8_t key_usage,
+ optiga_key_usage_t key_usage,
bool_t export_private_key,
void* private_key,
uint8_t* public_key,
@@ -181,36 +165,6 @@ optiga_lib_status_t optiga_ops_crypt_ecdsa_sign_sync(
return res;
}
-optiga_lib_status_t optiga_ops_crypt_symmetric_encrypt_sync(
- optiga_crypt_t* me,
- optiga_symmetric_encryption_mode_t encryption_mode,
- optiga_key_id_t symmetric_key_oid,
- const uint8_t* plain_data,
- uint32_t plain_data_length,
- const uint8_t* iv,
- uint16_t iv_length,
- const uint8_t* associated_data,
- uint16_t associated_data_length,
- uint8_t* encrypted_data,
- uint32_t* encrypted_data_length)
-{
- _optiga_lib_status = OPTIGA_LIB_BUSY;
- optiga_lib_status_t res = optiga_crypt_symmetric_encrypt(
- me,
- encryption_mode,
- symmetric_key_oid,
- plain_data,
- plain_data_length,
- iv,
- iv_length,
- associated_data,
- associated_data_length,
- encrypted_data,
- encrypted_data_length);
- _WAIT(res, _optiga_lib_status);
- return res;
-}
-
optiga_lib_status_t optiga_ops_crypt_random_sync(
optiga_crypt_t* me,
optiga_rng_type_t rng_type,
@@ -222,56 +176,3 @@ optiga_lib_status_t optiga_ops_crypt_random_sync(
_WAIT(res, _optiga_lib_status);
return res;
}
-
-optiga_lib_status_t optiga_ops_crypt_symmetric_generate_key_sync(
- optiga_crypt_t* me,
- optiga_symmetric_key_type_t key_type,
- uint8_t key_usage,
- bool_t export_symmetric_key,
- void* symmetric_key)
-{
- _optiga_lib_status = OPTIGA_LIB_BUSY;
- optiga_lib_status_t res = optiga_crypt_symmetric_generate_key(
- me, key_type, key_usage, export_symmetric_key, symmetric_key);
- _WAIT(res, _optiga_lib_status);
- return res;
-}
-
-optiga_lib_status_t optiga_ops_crypt_generate_auth_code_sync(
- optiga_crypt_t* me,
- optiga_rng_type_t rng_type,
- const uint8_t* optional_data,
- uint16_t optional_data_length,
- uint8_t* random_data,
- uint16_t random_data_length)
-{
- _optiga_lib_status = OPTIGA_LIB_BUSY;
- optiga_lib_status_t res = optiga_crypt_generate_auth_code(
- me, rng_type, optional_data, optional_data_length, random_data, random_data_length);
- _WAIT(res, _optiga_lib_status);
- return res;
-}
-
-optiga_lib_status_t optiga_ops_crypt_clear_auto_state_sync(optiga_crypt_t* me, uint16_t secret)
-{
- _optiga_lib_status = OPTIGA_LIB_BUSY;
- optiga_lib_status_t res = optiga_crypt_clear_auto_state(me, secret);
- _WAIT(res, _optiga_lib_status);
- return res;
-}
-
-optiga_lib_status_t optiga_ops_crypt_hmac_verify_sync(
- optiga_crypt_t* me,
- optiga_hmac_type_t type,
- uint16_t secret,
- const uint8_t* input_data,
- uint32_t input_data_length,
- const uint8_t* hmac,
- uint32_t hmac_length)
-{
- _optiga_lib_status = OPTIGA_LIB_BUSY;
- optiga_lib_status_t res = optiga_crypt_hmac_verify(
- me, type, secret, input_data, input_data_length, hmac, hmac_length);
- _WAIT(res, _optiga_lib_status);
- return res;
-}
diff --git a/src/optiga/optiga_ops.h b/src/optiga/optiga_ops.h
index 9f4f467..2d87708 100644
--- a/src/optiga/optiga_ops.h
+++ b/src/optiga/optiga_ops.h
@@ -46,19 +46,10 @@ optiga_lib_status_t optiga_ops_util_close_application_sync(
optiga_util_t* me,
bool_t perform_hibernate);
-optiga_lib_status_t optiga_ops_crypt_hmac_sync(
- optiga_crypt_t* me,
- optiga_hmac_type_t type,
- uint16_t secret,
- const uint8_t* input_data,
- uint32_t input_data_length,
- uint8_t* mac,
- uint32_t* mac_length);
-
optiga_lib_status_t optiga_ops_crypt_ecc_generate_keypair_sync(
optiga_crypt_t* me,
optiga_ecc_curve_t curve_id,
- uint8_t key_usage,
+ optiga_key_usage_t key_usage,
bool_t export_private_key,
void* private_key,
uint8_t* public_key,
@@ -72,49 +63,10 @@ optiga_lib_status_t optiga_ops_crypt_ecdsa_sign_sync(
uint8_t* signature,
uint16_t* signature_length);
-optiga_lib_status_t optiga_ops_crypt_symmetric_encrypt_sync(
- optiga_crypt_t* me,
- optiga_symmetric_encryption_mode_t encryption_mode,
- optiga_key_id_t symmetric_key_oid,
- const uint8_t* plain_data,
- uint32_t plain_data_length,
- const uint8_t* iv,
- uint16_t iv_length,
- const uint8_t* associated_data,
- uint16_t associated_data_length,
- uint8_t* encrypted_data,
- uint32_t* encrypted_data_length);
-
optiga_lib_status_t optiga_ops_crypt_random_sync(
optiga_crypt_t* me,
optiga_rng_type_t rng_type,
uint8_t* random_data,
uint16_t random_data_length);
-optiga_lib_status_t optiga_ops_crypt_symmetric_generate_key_sync(
- optiga_crypt_t* me,
- optiga_symmetric_key_type_t key_type,
- uint8_t key_usage,
- bool_t export_symmetric_key,
- void* symmetric_key);
-
-optiga_lib_status_t optiga_ops_crypt_generate_auth_code_sync(
- optiga_crypt_t* me,
- optiga_rng_type_t rng_type,
- const uint8_t* optional_data,
- uint16_t optional_data_length,
- uint8_t* random_data,
- uint16_t random_data_length);
-
-optiga_lib_status_t optiga_ops_crypt_clear_auto_state_sync(optiga_crypt_t* me, uint16_t secret);
-
-optiga_lib_status_t optiga_ops_crypt_hmac_verify_sync(
- optiga_crypt_t* me,
- optiga_hmac_type_t type,
- uint16_t secret,
- const uint8_t* input_data,
- uint32_t input_data_length,
- const uint8_t* hmac,
- uint32_t hmac_length);
-
#endif // _OPTIGA_OPS_H_
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index bba3734..fa5759d 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -221,9 +221,14 @@ dependencies = [
name = "bitbox-securechip"
version = "0.1.0"
dependencies = [
+ "async_test",
+ "bitbox-core-utils",
+ "bitbox-hal",
+ "bitbox-platform-host",
"bitbox-securechip-sys",
"critical-section",
"grounded",
+ "hex_lit",
"util",
"zeroize",
]
@@ -250,6 +255,7 @@ dependencies = [
name = "bitbox02"
version = "0.1.0"
dependencies = [
+ "async_test",
"bip39",
"bitbox-aes",
"bitbox-bytequeue",
diff --git a/src/rust/bitbox-hal/src/securechip.rs b/src/rust/bitbox-hal/src/securechip.rs
index 94ab69c..40ec40e 100644
--- a/src/rust/bitbox-hal/src/securechip.rs
+++ b/src/rust/bitbox-hal/src/securechip.rs
@@ -57,8 +57,11 @@ pub trait SecureChip {
/// This reinitializes the secure-chip state used for password derivation and returns the same
/// 32-byte value as [`stretch_password`] for the same `password` and
/// `password_stretch_algo`, but may require fewer secure-chip operations.
- fn init_new_password(
+ ///
+ /// `memory` is used for persistent secrets needed during derivation, such as the salt root.
+ async fn init_new_password(
&mut self,
+ memory: &mut impl super::memory::Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error>;
@@ -67,8 +70,9 @@ pub trait SecureChip {
///
/// The returned value is always 32 bytes long. Calling this function increments the relevant
/// secure-chip monotonic counter.
- fn stretch_password(
+ async fn stretch_password(
&mut self,
+ memory: &mut impl super::memory::Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error>;
@@ -95,7 +99,7 @@ pub trait SecureChip {
fn model(&mut self) -> Result<Model, ()>;
/// Resets the secure-chip objects involved in password stretching.
- fn reset_keys(&mut self) -> Result<(), ()>;
+ async fn reset_keys(&mut self, memory: &mut impl super::memory::Memory) -> Result<(), ()>;
#[cfg(feature = "app-u2f")]
/// Sets the U2F counter to `counter`.
diff --git a/src/rust/bitbox-platform-host/src/securechip.rs b/src/rust/bitbox-platform-host/src/securechip.rs
index fe9ded6..f676601 100644
--- a/src/rust/bitbox-platform-host/src/securechip.rs
+++ b/src/rust/bitbox-platform-host/src/securechip.rs
@@ -78,8 +78,9 @@ impl bitbox_hal::SecureChip for FakeSecureChip {
)))
}
- fn init_new_password(
+ async fn init_new_password(
&mut self,
+ _memory: &mut impl bitbox_hal::Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
@@ -98,8 +99,9 @@ impl bitbox_hal::SecureChip for FakeSecureChip {
)))
}
- fn stretch_password(
+ async fn stretch_password(
&mut self,
+ _memory: &mut impl bitbox_hal::Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
@@ -161,7 +163,7 @@ impl bitbox_hal::SecureChip for FakeSecureChip {
Ok(Model::Atecc608B)
}
- fn reset_keys(&mut self) -> Result<(), ()> {
+ async fn reset_keys(&mut self, _memory: &mut impl bitbox_hal::Memory) -> Result<(), ()> {
if self.reset_keys_fail_once {
self.reset_keys_fail_once = false;
Err(())
diff --git a/src/rust/bitbox-securechip-sys/build.rs b/src/rust/bitbox-securechip-sys/build.rs
index af5bb7f..f639233 100644
--- a/src/rust/bitbox-securechip-sys/build.rs
+++ b/src/rust/bitbox-securechip-sys/build.rs
@@ -6,9 +6,15 @@ use std::path::PathBuf;
use std::process::{Command, Output};
const ALLOWLIST_TYPES: &[&str] = &[
+ "bool_t",
"optiga_crypt_t",
"optiga_hmac_type_t",
+ "optiga_key_id_t",
+ "optiga_key_usage_t",
"optiga_lib_status_t",
+ "optiga_rng_type_t",
+ "optiga_symmetric_encryption_mode_t",
+ "optiga_symmetric_key_type_t",
"optiga_util_t",
"securechip_error_t",
"securechip_interface_functions_t",
@@ -30,37 +36,67 @@ const ALLOWLIST_FNS: &[&str] = &[
"atecc_u2f_counter_inc",
"atecc_u2f_counter_set",
"optiga_attestation_sign",
+ "optiga_crypt_clear_auto_state",
+ "optiga_crypt_generate_auth_code",
"optiga_crypt_hmac",
+ "optiga_crypt_hmac_verify",
"optiga_crypt_instance",
+ "optiga_crypt_symmetric_generate_key",
+ "optiga_crypt_symmetric_encrypt",
+ "optiga_ifs_random_32_bytes",
"optiga_gen_attestation_key",
- "optiga_init_new_password",
"optiga_ops_get_status",
"optiga_ops_set_status_busy",
"optiga_random",
- "optiga_reset_keys",
"optiga_setup",
- "optiga_stretch_password",
"optiga_u2f_counter_inc",
"optiga_u2f_counter_set",
"optiga_util_instance",
"optiga_util_read_data",
+ "optiga_util_write_data",
];
const ALLOWLIST_VARS: &[&str] = &[
"ARBITRARY_DATA_OBJECT_TYPE_3_MAX_SIZE",
"MONOTONIC_COUNTER_MAX_USE",
+ "OID_AES_SYMKEY",
"OID_COUNTER",
+ "OID_COUNTER_HMAC_WRITEPROTECTED",
+ "OID_COUNTER_PASSWORD",
"OID_HMAC",
+ "OID_HMAC_WRITEPROTECTED",
+ "OID_PASSWORD",
+ "OID_PASSWORD_SECRET",
+ "OPTIGA_CRYPT_SUCCESS",
+ "OPTIGA_CRYPT_ERROR",
+ "OPTIGA_CRYPT_ERROR_INVALID_INPUT",
+ "OPTIGA_CRYPT_ERROR_MEMORY_INSUFFICIENT",
"OPTIGA_LIB_BUSY",
"OPTIGA_LIB_SUCCESS",
+ "OPTIGA_UTIL_ERROR",
+ "OPTIGA_UTIL_ERROR_INVALID_INPUT",
+ "OPTIGA_UTIL_ERROR_MEMORY_INSUFFICIENT",
"OPTIGA_UTIL_SUCCESS",
+ "OPTIGA_UTIL_ERASE_AND_WRITE",
+ "SMALL_MONOTONIC_COUNTER_MAX_USE",
];
const RUSTIFIED_ENUMS: &[&str] = &[
"optiga_hmac_type",
- "securechip_password_stretch_algo_t",
+ "optiga_hmac_type_t",
+ "optiga_key_id",
+ "optiga_key_id_t",
+ "optiga_key_usage",
+ "optiga_key_usage_t",
+ "optiga_rng_type",
+ "optiga_rng_type_t",
+ "optiga_symmetric_encryption_mode",
+ "optiga_symmetric_encryption_mode_t",
+ "optiga_symmetric_key_type",
+ "optiga_symmetric_key_type_t",
"securechip_error_t",
"securechip_model_t",
+ "securechip_password_stretch_algo_t",
];
type BuildResult<T> = Result<T, String>;
diff --git a/src/rust/bitbox-securechip/Cargo.toml b/src/rust/bitbox-securechip/Cargo.toml
index f3d7773..b420a56 100644
--- a/src/rust/bitbox-securechip/Cargo.toml
+++ b/src/rust/bitbox-securechip/Cargo.toml
@@ -9,11 +9,18 @@ description = "Safe Rust bindings for securechip code in BitBox firmware"
license = "Apache-2.0"
[dependencies]
+bitbox-hal = { path = "../bitbox-hal" }
+bitbox-core-utils = { path = "../bitbox-core-utils" }
bitbox-securechip-sys = { path = "../bitbox-securechip-sys" }
critical-section = { workspace = true }
grounded = { workspace = true }
-util = { path = "../util" }
+util = { path = "../util", features = ["sha2"] }
zeroize = { workspace = true }
[features]
app-u2f = []
+
+[dev-dependencies]
+async_test = { path = "../async_test" }
+bitbox-platform-host = { path = "../bitbox-platform-host" }
+hex_lit = { workspace = true, features = ["rust_v_1_46"] }
diff --git a/src/rust/bitbox-securechip/src/atecc.rs b/src/rust/bitbox-securechip/src/atecc.rs
index 1eadea9..a9ae41a 100644
--- a/src/rust/bitbox-securechip/src/atecc.rs
+++ b/src/rust/bitbox-securechip/src/atecc.rs
@@ -2,6 +2,7 @@
use crate::{Error, Model, PasswordStretchAlgo, SecureChipError};
use alloc::boxed::Box;
+use bitbox_hal::Memory;
use zeroize::Zeroizing;
pub fn attestation_sign(challenge: &[u8; 32], signature: &mut [u8; 64]) -> Result<(), ()> {
@@ -38,6 +39,7 @@ pub fn reset_keys() -> Result<(), ()> {
}
pub fn init_new_password(
+ _memory: &mut impl Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
@@ -59,6 +61,7 @@ pub fn init_new_password(
}
pub fn stretch_password(
+ _memory: &mut impl Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
diff --git a/src/rust/bitbox-securechip/src/lib.rs b/src/rust/bitbox-securechip/src/lib.rs
index f1c9f86..5c7e3be 100644
--- a/src/rust/bitbox-securechip/src/lib.rs
+++ b/src/rust/bitbox-securechip/src/lib.rs
@@ -3,6 +3,8 @@
#![no_std]
extern crate alloc;
+#[cfg(test)]
+extern crate std;
use bitbox_securechip_sys as ffi;
diff --git a/src/rust/bitbox-securechip/src/optiga.rs b/src/rust/bitbox-securechip/src/optiga.rs
index 3dde9d3..c2fb146 100644
--- a/src/rust/bitbox-securechip/src/optiga.rs
+++ b/src/rust/bitbox-securechip/src/optiga.rs
@@ -2,16 +2,348 @@
use crate::{Error, Model, PasswordStretchAlgo, SecureChipError};
use alloc::boxed::Box;
+use bitbox_hal::Memory;
+use util::sha2::{hmac_sha256, hmac_sha256_overwrite, sha256};
use zeroize::Zeroizing;
+#[cfg(not(test))]
+#[path = "optiga/ops.rs"]
+mod ops;
+#[cfg(test)]
+#[path = "optiga/ops_fake.rs"]
mod ops;
+const OID_AES_SYMKEY: u16 = bitbox_securechip_sys::OID_AES_SYMKEY as u16;
const OID_COUNTER: u16 = bitbox_securechip_sys::OID_COUNTER as u16;
-const MONOTONIC_COUNTER_MAX_USE: u32 = bitbox_securechip_sys::MONOTONIC_COUNTER_MAX_USE;
+const OID_COUNTER_HMAC_WRITEPROTECTED: u16 =
+ bitbox_securechip_sys::OID_COUNTER_HMAC_WRITEPROTECTED as u16;
+const OID_COUNTER_PASSWORD: u16 = bitbox_securechip_sys::OID_COUNTER_PASSWORD as u16;
const OID_HMAC: u16 = bitbox_securechip_sys::OID_HMAC as u16;
+const OID_HMAC_WRITEPROTECTED: u16 = bitbox_securechip_sys::OID_HMAC_WRITEPROTECTED as u16;
+const OID_PASSWORD: u16 = bitbox_securechip_sys::OID_PASSWORD as u16;
+const OID_PASSWORD_SECRET: u16 = bitbox_securechip_sys::OID_PASSWORD_SECRET as u16;
+const MONOTONIC_COUNTER_MAX_USE: u32 = bitbox_securechip_sys::MONOTONIC_COUNTER_MAX_USE;
+const SMALL_MONOTONIC_COUNTER_MAX_USE: u32 = bitbox_securechip_sys::SMALL_MONOTONIC_COUNTER_MAX_USE;
const KDF_LEN: usize = 32;
const OPTIGA_HMAC_SHA_256: bitbox_securechip_sys::optiga_hmac_type_t =
bitbox_securechip_sys::optiga_hmac_type::OPTIGA_HMAC_SHA_256;
+// This number of KDF iterations on the external kdf slot when stretching the device
+// password using the V0 algorithm.
+const KDF_NUM_ITERATIONS_V0: usize = 2;
+const OPTIGA_HMAC_VERIFY_FAIL: i32 = 0x802F;
+const OPTIGA_KEY_USAGE_ENCRYPTION: bitbox_securechip_sys::optiga_key_usage_t =
+ bitbox_securechip_sys::optiga_key_usage::OPTIGA_KEY_USAGE_ENCRYPTION;
+const OPTIGA_RNG_TYPE_TRNG: bitbox_securechip_sys::optiga_rng_type_t =
+ bitbox_securechip_sys::optiga_rng_type::OPTIGA_RNG_TYPE_TRNG;
+const OPTIGA_SYMMETRIC_AES_256: bitbox_securechip_sys::optiga_symmetric_key_type_t =
+ bitbox_securechip_sys::optiga_symmetric_key_type::OPTIGA_SYMMETRIC_AES_256;
+const OPTIGA_SYMMETRIC_CMAC: bitbox_securechip_sys::optiga_symmetric_encryption_mode_t =
+ bitbox_securechip_sys::optiga_symmetric_encryption_mode::OPTIGA_SYMMETRIC_CMAC;
+
+fn zeroed_secret<const N: usize>() -> Box<Zeroizing<[u8; N]>> {
+ Box::new(Zeroizing::new([0; N]))
+}
+
+fn key_id_from_oid(oid: u16) -> bitbox_securechip_sys::optiga_key_id_t {
+ match oid {
+ OID_AES_SYMKEY => bitbox_securechip_sys::optiga_key_id::OPTIGA_KEY_ID_SECRET_BASED,
+ _ => panic!("unexpected optiga key oid"),
+ }
+}
+
+async fn authorize(oid_auth: u16, auth_secret: &[u8; KDF_LEN]) -> Result<(), Error> {
+ let mut random_data = zeroed_secret::<KDF_LEN>();
+ ops::crypt_generate_auth_code(OPTIGA_RNG_TYPE_TRNG, random_data.as_mut_slice()).await?;
+
+ let mut hmac = zeroed_secret::<KDF_LEN>();
+ hmac_sha256(auth_secret, random_data.as_slice(), &mut hmac);
+ ops::crypt_hmac_verify(
+ OPTIGA_HMAC_SHA_256,
+ oid_auth,
+ random_data.as_slice(),
+ hmac.as_slice(),
+ )
+ .await
+}
+
+async fn reset_counter(oid: u16, limit: u32) -> Result<(), Error> {
+ let mut counter_buf = [0u8; 8];
+ counter_buf[4..8].copy_from_slice(&limit.to_be_bytes());
+ ops::util_write_data(
+ oid,
+ bitbox_securechip_sys::OPTIGA_UTIL_ERASE_AND_WRITE as u8,
+ 0,
+ &counter_buf,
+ )
+ .await
+}
+
+async fn kdf_hmac(
+ optiga_oid: u16,
+ msg: &[u8; KDF_LEN],
+ mac_out: &mut [u8; KDF_LEN],
+) -> Result<(), Error> {
+ ops::crypt_hmac(OPTIGA_HMAC_SHA_256, optiga_oid, msg, mac_out).await
+}
+
+async fn kdf_internal(msg: &[u8; KDF_LEN], kdf_out: &mut [u8; KDF_LEN]) -> Result<(), Error> {
+ let mut mac_out = zeroed_secret::<16>();
+ ops::crypt_symmetric_encrypt(
+ OPTIGA_SYMMETRIC_CMAC,
+ key_id_from_oid(OID_AES_SYMKEY),
+ msg,
+ mac_out.as_mut_slice(),
+ )
+ .await?;
+
+ sha256(mac_out.as_slice(), kdf_out);
+ Ok(())
+}
+
+async fn set_password(
+ memory: &mut impl Memory,
+ password_secret: &[u8; KDF_LEN],
+ auth_password: &[u8; KDF_LEN],
+) -> Result<(), Error> {
+ let result = async {
+ authorize(OID_PASSWORD_SECRET, password_secret).await?;
+ let auth_password_salted_hashed =
+ bitbox_core_utils::salt::hash_data(memory, auth_password, "optiga_password")
+ .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_SALT))?;
+ ops::util_write_data(
+ OID_PASSWORD,
+ bitbox_securechip_sys::OPTIGA_UTIL_ERASE_AND_WRITE as u8,
+ 0,
+ auth_password_salted_hashed.as_slice(),
+ )
+ .await?;
+ // Add one extra to the counter threshold, as afterwards writing the
+ // write-protected HMAC slot increments the counter.
+ reset_counter(OID_COUNTER_PASSWORD, SMALL_MONOTONIC_COUNTER_MAX_USE + 1).await
+ }
+ .await;
+ let cleanup_result = ops::crypt_clear_auto_state(OID_PASSWORD_SECRET).await;
+ result?;
+ cleanup_result
+}
+
+async fn set_hmac_writeprotected(
+ memory: &mut impl Memory,
+ hmac_key: &[u8; KDF_LEN],
+ auth_password: &[u8; KDF_LEN],
+) -> Result<(), Error> {
+ let result = async {
+ let auth_password_salted_hashed =
+ bitbox_core_utils::salt::hash_data(memory, auth_password, "optiga_password")
+ .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_SALT))?;
+ authorize(OID_PASSWORD, &auth_password_salted_hashed).await?;
+ ops::util_write_data(
+ OID_HMAC_WRITEPROTECTED,
+ bitbox_securechip_sys::OPTIGA_UTIL_ERASE_AND_WRITE as u8,
+ 0,
+ hmac_key,
+ )
+ .await?;
+ reset_counter(
+ OID_COUNTER_HMAC_WRITEPROTECTED,
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ )
+ .await
+ }
+ .await;
+ let cleanup_result = ops::crypt_clear_auto_state(OID_PASSWORD).await;
+ result?;
+ cleanup_result
+}
+
+async fn v1_get_auth_password(
+ memory: &mut impl Memory,
+ password: &str,
+ hmac_key: Option<&[u8; KDF_LEN]>,
+ stretched_password_out: &mut [u8; KDF_LEN],
+) -> Result<(), Error> {
+ let password_salted_hashed = bitbox_core_utils::salt::hash_data(
+ memory,
+ password.as_bytes(),
+ "optiga_password_stretch_in",
+ )
+ .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_SALT))?;
+
+ let mut kdf_in = zeroed_secret::<KDF_LEN>();
+ kdf_in.copy_from_slice(password_salted_hashed.as_slice());
+
+ // First KDF on the internal key increments the large monotonic counter. Call only once!
+ kdf_internal(&kdf_in, stretched_password_out).await?;
+
+ // Second KDF increments the small monotonic counter in `OID_HMAC_WRITEPROTECTED`. Call only
+ // once!
+ kdf_in.copy_from_slice(stretched_password_out);
+ if let Some(hmac_key) = hmac_key {
+ hmac_sha256(hmac_key, kdf_in.as_slice(), stretched_password_out);
+ } else {
+ match kdf_hmac(OID_HMAC_WRITEPROTECTED, &kdf_in, stretched_password_out).await {
+ Ok(()) => {}
+ Err(Error::Status(OPTIGA_HMAC_VERIFY_FAIL)) => {
+ return Err(Error::SecureChip(
+ SecureChipError::SC_ERR_INCORRECT_PASSWORD,
+ ));
+ }
+ Err(err) => return Err(err),
+ }
+ }
+
+ Ok(())
+}
+
+fn v1_combine(
+ memory: &mut impl Memory,
+ password: &str,
+ auth_password: &[u8; KDF_LEN],
+ password_secret: &[u8; KDF_LEN],
+ stretched_out: &mut [u8; KDF_LEN],
+) -> Result<(), Error> {
+ hmac_sha256(password_secret, auth_password, stretched_out);
+
+ let password_salted_hashed = bitbox_core_utils::salt::hash_data(
+ memory,
+ password.as_bytes(),
+ "optiga_password_stretch_out",
+ )
+ .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_SALT))?;
+
+ hmac_sha256_overwrite(password_salted_hashed.as_slice(), stretched_out);
+ Ok(())
+}
+
+async fn optiga_verify_password_v0(
+ memory: &mut impl Memory,
+ password: &str,
+ password_secret_out: &mut [u8; KDF_LEN],
+) -> Result<(), Error> {
+ let password_salted_hashed =
+ bitbox_core_utils::salt::hash_data(memory, password.as_bytes(), "optiga_password")
+ .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_SALT))?;
+
+ let result = async {
+ authorize(OID_PASSWORD, &password_salted_hashed).await?;
+ ops::util_read_data(OID_PASSWORD_SECRET, 0, password_secret_out).await?;
+ authorize(OID_PASSWORD_SECRET, password_secret_out).await?;
+ reset_counter(OID_COUNTER_PASSWORD, SMALL_MONOTONIC_COUNTER_MAX_USE).await
+ }
+ .await;
+ let res_clear1 = ops::crypt_clear_auto_state(OID_PASSWORD).await;
+ let res_clear2 = ops::crypt_clear_auto_state(OID_PASSWORD_SECRET).await;
+ result?;
+ res_clear1?;
+ res_clear2
+}
+
+async fn optiga_verify_password_v1(
+ memory: &mut impl Memory,
+ auth_password: &[u8; KDF_LEN],
+ password_secret_out: &mut [u8; KDF_LEN],
+) -> Result<(), Error> {
+ let auth_password_salted_hashed =
+ bitbox_core_utils::salt::hash_data(memory, auth_password, "optiga_password")
+ .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_SALT))?;
+
+ let result = async {
+ authorize(OID_PASSWORD, &auth_password_salted_hashed).await?;
+ ops::util_read_data(OID_PASSWORD_SECRET, 0, password_secret_out).await?;
+ authorize(OID_PASSWORD_SECRET, password_secret_out).await?;
+ reset_counter(OID_COUNTER_PASSWORD, SMALL_MONOTONIC_COUNTER_MAX_USE).await?;
+ reset_counter(
+ OID_COUNTER_HMAC_WRITEPROTECTED,
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ )
+ .await
+ }
+ .await;
+ let res_clear1 = ops::crypt_clear_auto_state(OID_PASSWORD).await;
+ let res_clear2 = ops::crypt_clear_auto_state(OID_PASSWORD_SECRET).await;
+ result?;
+ res_clear1?;
+ res_clear2
+}
+
+async fn stretch_password_v0(
+ memory: &mut impl Memory,
+ password: &str,
+ stretched_out: &mut [u8; KDF_LEN],
+) -> Result<(), Error> {
+ let password_salted_hashed = bitbox_core_utils::salt::hash_data(
+ memory,
+ password.as_bytes(),
+ "optiga_password_stretch_in",
+ )
+ .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_SALT))?;
+
+ let mut kdf_in = zeroed_secret::<KDF_LEN>();
+ kdf_in.copy_from_slice(password_salted_hashed.as_slice());
+
+ // First KDF on the internal key increments the large monotonic counter. Call only once!
+ kdf_internal(&kdf_in, stretched_out).await?;
+ // Second KDF does not use any counters and we call it multiple times.
+ for _ in 0..KDF_NUM_ITERATIONS_V0 {
+ kdf_in.copy_from_slice(stretched_out);
+ kdf_hmac(OID_HMAC, &kdf_in, stretched_out).await?;
+ }
+
+ // Verify password, incrementing the small monotonic counter.
+ // Do this after the above KDF stretch so the big monotonic counter is also incremented.
+ let mut password_secret = zeroed_secret::<KDF_LEN>();
+ match optiga_verify_password_v0(memory, password, &mut password_secret).await {
+ Ok(()) => {}
+ Err(Error::Status(OPTIGA_HMAC_VERIFY_FAIL)) => {
+ return Err(Error::SecureChip(
+ SecureChipError::SC_ERR_INCORRECT_PASSWORD,
+ ));
+ }
+ Err(err) => return Err(err),
+ }
+
+ hmac_sha256_overwrite(password_secret.as_slice(), stretched_out);
+
+ let password_salted_hashed = bitbox_core_utils::salt::hash_data(
+ memory,
+ password.as_bytes(),
+ "optiga_password_stretch_out",
+ )
+ .map_err(|_| Error::SecureChip(SecureChipError::SC_ERR_SALT))?;
+ hmac_sha256_overwrite(password_salted_hashed.as_slice(), stretched_out);
+ Ok(())
+}
+
+async fn stretch_password_v1(
+ memory: &mut impl Memory,
+ password: &str,
+ stretched_out: &mut [u8; KDF_LEN],
+) -> Result<(), Error> {
+ let mut auth_password = zeroed_secret::<KDF_LEN>();
+ // Get auth password. This increments the small monotonic counter in
+ // `OID_COUNTER_HMAC_WRITEPROTECTED` and the large monotonic counter.
+ v1_get_auth_password(memory, password, None, &mut auth_password).await?;
+
+ let mut password_secret = zeroed_secret::<KDF_LEN>();
+ // Verify password, incrementing the small monotonic counter in `OID_COUNTER_PASSWORD`.
+ match optiga_verify_password_v1(memory, &auth_password, &mut password_secret).await {
+ Ok(()) => {}
+ Err(Error::Status(OPTIGA_HMAC_VERIFY_FAIL)) => {
+ return Err(Error::SecureChip(
+ SecureChipError::SC_ERR_INCORRECT_PASSWORD,
+ ));
+ }
+ Err(err) => return Err(err),
+ }
+
+ v1_combine(
+ memory,
+ password,
+ &auth_password,
+ &password_secret,
+ stretched_out,
+ )
+}
pub fn attestation_sign(challenge: &[u8; 32], signature: &mut [u8; 64]) -> Result<(), ()> {
match unsafe {
@@ -23,7 +355,7 @@ pub fn attestation_sign(challenge: &[u8; 32], signature: &mut [u8; 64]) -> Resul
}
pub fn random() -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
- let mut result = Box::new(Zeroizing::new([0u8; 32]));
+ let mut result = zeroed_secret::<32>();
let status = unsafe { bitbox_securechip_sys::optiga_random(result.as_mut_ptr()) };
if status == 0 {
Ok(result)
@@ -44,57 +376,103 @@ pub async fn monotonic_increments_remaining() -> Result<u32, ()> {
Ok(MONOTONIC_COUNTER_MAX_USE - counter)
}
-pub fn reset_keys() -> Result<(), ()> {
- match unsafe { bitbox_securechip_sys::optiga_reset_keys() } {
- true => Ok(()),
- false => Err(()),
- }
+pub async fn reset_keys(memory: &mut impl Memory) -> Result<(), ()> {
+ // This resets `OID_AES_SYMKEY` and the `OID_HMAC`/`OID_HMAC_WRITEPROTECTED` keys, as well as
+ // the `OID_PASSWORD_SECRET` and `OID_PASSWORD` keys. A password is needed because updating the
+ // `OID_PASSWORD` key requires auth using the `OID_PASSWORD_SECRET` key, but any password is
+ // fine for the purpose of resetting the keys.
+ //
+ // We reset using V1, the latest algorithm. It covers resetting everything from V0 as well.
+ init_new_password(
+ memory,
+ "",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1,
+ )
+ .await
+ .map(|_| ())
+ .map_err(|_| ())
}
-pub fn init_new_password(
+pub async fn init_new_password(
+ memory: &mut impl Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<Zeroizing<[u8; 32]>>, 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::optiga_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_V1 {
+ // New passwords must use the latest algo.
+ return Err(Error::SecureChip(
+ SecureChipError::SC_ERR_INVALID_PASSWORD_STRETCH_ALGO,
+ ));
}
+
+ let mut stretched = zeroed_secret::<KDF_LEN>();
+ let mut new_hmac_key = zeroed_secret::<KDF_LEN>();
+ ops::ifs_random_32_bytes(&mut new_hmac_key)?;
+ // Set new HMAC key.
+ ops::util_write_data(
+ OID_HMAC,
+ bitbox_securechip_sys::OPTIGA_UTIL_ERASE_AND_WRITE as u8,
+ 0,
+ new_hmac_key.as_slice(),
+ )
+ .await?;
+ // Set new symmetric key.
+ ops::crypt_symmetric_generate_key(OPTIGA_SYMMETRIC_AES_256, OPTIGA_KEY_USAGE_ENCRYPTION)
+ .await?;
+
+ let mut password_secret = zeroed_secret::<KDF_LEN>();
+ ops::ifs_random_32_bytes(&mut password_secret)?;
+ ops::util_write_data(
+ OID_PASSWORD_SECRET,
+ bitbox_securechip_sys::OPTIGA_UTIL_ERASE_AND_WRITE as u8,
+ 0,
+ password_secret.as_slice(),
+ )
+ .await?;
+
+ let mut new_hmac_writeprotected_key = zeroed_secret::<KDF_LEN>();
+ ops::ifs_random_32_bytes(&mut new_hmac_writeprotected_key)?;
+
+ let mut auth_password = zeroed_secret::<KDF_LEN>();
+ v1_get_auth_password(
+ memory,
+ password,
+ Some(&new_hmac_writeprotected_key),
+ &mut auth_password,
+ )
+ .await?;
+ set_password(memory, &password_secret, &auth_password).await?;
+ set_hmac_writeprotected(memory, &new_hmac_writeprotected_key, &auth_password).await?;
+ v1_combine(
+ memory,
+ password,
+ &auth_password,
+ &password_secret,
+ stretched.as_mut(),
+ )?;
+
+ Ok(stretched)
}
-pub fn stretch_password(
+pub async fn stretch_password(
+ memory: &mut impl Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<Zeroizing<[u8; 32]>>, 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::optiga_stretch_password(
- password.as_ptr().cast(),
- password_stretch_algo,
- stretched.as_mut_ptr(),
- )
- };
- if status == 0 {
- Ok(stretched)
- } else {
- Err(Error::from_status(status))
+ let mut stretched = zeroed_secret::<KDF_LEN>();
+ match password_stretch_algo {
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0 => {
+ stretch_password_v0(memory, password, stretched.as_mut()).await?
+ }
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1 => {
+ stretch_password_v1(memory, password, stretched.as_mut()).await?
+ }
}
+ Ok(stretched)
}
pub async fn kdf(msg: &[u8; KDF_LEN]) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
- let mut result = Box::new(Zeroizing::new([0u8; 32]));
+ let mut result = zeroed_secret::<KDF_LEN>();
ops::crypt_hmac(OPTIGA_HMAC_SHA_256, OID_HMAC, msg, result.as_mut()).await?;
Ok(result)
}
@@ -110,3 +488,431 @@ pub fn u2f_counter_set(counter: u32) -> Result<(), ()> {
pub fn model() -> Result<Model, ()> {
Ok(Model::OPTIGA_TRUST_M_V3)
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use bitbox_platform_host::memory::FakeMemory;
+ use hex_lit::hex;
+
+ //------------------------------------------------------------------------------
+ // Fixed test vectors / keys (deterministic fakes).
+
+ const SALT_ROOT_FIXED: [u8; 32] = [0x42; 32];
+ fn setup_test() -> (std::sync::MutexGuard<'static, ()>, FakeMemory) {
+ let guard = ops::test_lock();
+ ops::test_reset();
+ let mut memory = FakeMemory::new();
+ // Provides a fixed salt root for deterministic hash_data() results.
+ memory.set_salt_root(&SALT_ROOT_FIXED);
+ (guard, memory)
+ }
+
+ // Expected stretched_out for password "pw" for the V0 algorithm given the deterministic fake
+ // constants in ops_fake.rs.
+ //
+ // Repro script (mirrors stretch_password() with the unit test fakes):
+ // ```python
+ // import hashlib, hmac
+ //
+ // def sha256(b: bytes) -> bytes:
+ // return hashlib.sha256(b).digest()
+ //
+ // def hmac_sha256(key: bytes, msg: bytes) -> bytes:
+ // return hmac.new(key, msg, hashlib.sha256).digest()
+ //
+ // def salt_hash_data(data: bytes, purpose: bytes, salt_root: bytes) -> bytes:
+ // return sha256(salt_root + purpose + data)
+ //
+ // def kdf_internal(msg: bytes, cmac_key: bytes) -> bytes:
+ // # crypt_symmetric_encrypt_sync fake: HMAC-SHA256(cmac_key, msg)[:16]
+ // return sha256(hmac_sha256(cmac_key, msg)[:16])
+ //
+ // def kdf_hmac(msg: bytes, hmac_key: bytes) -> bytes:
+ // # crypt_hmac_sync fake: HMAC-SHA256(hmac_key, msg)
+ // return hmac_sha256(hmac_key, msg)
+ //
+ // salt_root = bytes([0x42]) * 32
+ // cmac_key = bytes([0xA0]) * 32
+ // hmac_key = bytes([0xB0]) * 32
+ // password_secret = bytes([0x99]) * 32
+ // password = b"pw"
+ //
+ // kdf_in = salt_hash_data(password, b"optiga_password_stretch_in", salt_root)
+ // stretched = kdf_internal(kdf_in, cmac_key)
+ // for _ in range(2):
+ // stretched = kdf_hmac(stretched, hmac_key)
+ // stretched = hmac_sha256(password_secret, stretched)
+ // out_salt = salt_hash_data(password, b"optiga_password_stretch_out", salt_root)
+ // stretched = hmac_sha256(out_salt, stretched)
+ // print(stretched.hex())
+ // ```
+ const EXPECTED_STRETCHED_OUT_V0: [u8; 32] =
+ hex!("c41f87b7c9f3169c14f3f26287093c311819067776f6163b8a0fdf3dfb8b8ebb");
+
+ // Expected stretched_out for password "pw" for the V1 algorithm given the deterministic fake
+ // constants in ops_fake.rs.
+ //
+ // Repro script (mirrors stretch_password() with the unit test fakes):
+ // ```python
+ // import hashlib, hmac
+ //
+ // def sha256(b: bytes) -> bytes:
+ // return hashlib.sha256(b).digest()
+ //
+ // def hmac_sha256(key: bytes, msg: bytes) -> bytes:
+ // return hmac.new(key, msg, hashlib.sha256).digest()
+ //
+ // def salt_hash_data(data: bytes, purpose: bytes, salt_root: bytes) -> bytes:
+ // return sha256(salt_root + purpose + data)
+ //
+ // def kdf_internal(msg: bytes, cmac_key: bytes) -> bytes:
+ // # crypt_symmetric_encrypt_sync fake: HMAC-SHA256(cmac_key, msg)[:16]
+ // return sha256(hmac_sha256(cmac_key, msg)[:16])
+ //
+ // def kdf_hmac(msg: bytes, hmac_key: bytes) -> bytes:
+ // # crypt_hmac_sync fake: HMAC-SHA256(hmac_key, msg)
+ // return hmac_sha256(hmac_key, msg)
+ //
+ // salt_root = bytes([0x42]) * 32
+ // cmac_key = bytes([0xA0]) * 32
+ // hmac_writeprotected_key = bytes([0xC0]) * 32
+ // password_secret = bytes([0x99]) * 32
+ // password = b"pw"
+ //
+ // kdf_in = salt_hash_data(password, b"optiga_password_stretch_in", salt_root)
+ // stretched = kdf_internal(kdf_in, cmac_key)
+ // stretched = kdf_hmac(stretched, hmac_writeprotected_key)
+ // stretched = hmac_sha256(password_secret, stretched)
+ // out_salt = salt_hash_data(password, b"optiga_password_stretch_out", salt_root)
+ // stretched = hmac_sha256(out_salt, stretched)
+ // print(stretched.hex())
+ // ```
+ const EXPECTED_STRETCHED_OUT_V1: [u8; 32] =
+ hex!("c59ec3c3b1c45f7e7639a629f5b34d1e4dc508f3b5b9577dd9dd57eecf496751");
+
+ fn seed_v0_password(memory: &mut FakeMemory, password: &str) {
+ // Seed the OID_PASSWORD and OID_PASSWORD_COUNTER objects as if they were
+ // provisioned earlier.
+ let oid_password =
+ bitbox_core_utils::salt::hash_data(memory, password.as_bytes(), "optiga_password")
+ .unwrap();
+ ops::test_seed_oid_password(&oid_password);
+ ops::test_set_counter(OID_COUNTER_PASSWORD, 0, SMALL_MONOTONIC_COUNTER_MAX_USE);
+ }
+
+ #[async_test::test]
+ #[allow(clippy::await_holding_lock)]
+ async fn test_optiga_stretch_password_v0_success() {
+ let (_guard, mut memory) = setup_test();
+ seed_v0_password(&mut memory, "pw");
+
+ let stretched_out = stretch_password(
+ &mut memory,
+ "pw",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0,
+ )
+ .await
+ .unwrap();
+
+ assert_eq!(
+ stretched_out.as_slice(),
+ EXPECTED_STRETCHED_OUT_V0.as_slice()
+ );
+ // Successful password verification resets the small monotonic counter/threshold.
+ assert_eq!(ops::test_get_counter(OID_COUNTER_PASSWORD), 0);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_PASSWORD),
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ );
+ }
+
+ #[async_test::test]
+ #[allow(clippy::await_holding_lock)]
+ async fn test_optiga_stretch_password_v0_attempt_counter() {
+ let (_guard, mut memory) = setup_test();
+ seed_v0_password(&mut memory, "pw");
+
+ assert_eq!(
+ stretch_password(
+ &mut memory,
+ "wrong",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0,
+ )
+ .await,
+ Err(Error::SecureChip(
+ SecureChipError::SC_ERR_INCORRECT_PASSWORD,
+ )),
+ );
+ assert_eq!(ops::test_get_counter(OID_COUNTER_PASSWORD), 1);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_PASSWORD),
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ );
+
+ assert_eq!(
+ stretch_password(
+ &mut memory,
+ "wrong",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0,
+ )
+ .await,
+ Err(Error::SecureChip(
+ SecureChipError::SC_ERR_INCORRECT_PASSWORD,
+ )),
+ );
+ assert_eq!(ops::test_get_counter(OID_COUNTER_PASSWORD), 2);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_PASSWORD),
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ );
+
+ stretch_password(
+ &mut memory,
+ "pw",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0,
+ )
+ .await
+ .unwrap();
+ assert_eq!(ops::test_get_counter(OID_COUNTER_PASSWORD), 0);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_PASSWORD),
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ );
+
+ for _ in 0..SMALL_MONOTONIC_COUNTER_MAX_USE {
+ assert_eq!(
+ stretch_password(
+ &mut memory,
+ "wrong",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0,
+ )
+ .await,
+ Err(Error::SecureChip(
+ SecureChipError::SC_ERR_INCORRECT_PASSWORD,
+ )),
+ );
+ }
+ assert_eq!(
+ ops::test_get_counter(OID_COUNTER_PASSWORD),
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ );
+
+ // After exhausting all allowed attempts, a correct password fails as well.
+ assert_eq!(
+ stretch_password(
+ &mut memory,
+ "pw",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V0,
+ )
+ .await,
+ Err(Error::SecureChip(
+ SecureChipError::SC_ERR_INCORRECT_PASSWORD,
+ )),
+ );
+ assert_eq!(
+ ops::test_get_counter(OID_COUNTER_PASSWORD),
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ );
+ }
+
+ #[async_test::test]
+ #[allow(clippy::await_holding_lock)]
+ // Test that after initializing a new password, exhausting all allowed attempts locks means a
+ // correct password fails as well.
+ // Attempts after init are special because the PASSWORD_COUNTER init/threshold are offset by 1.
+ async fn test_optiga_password_v1_stretch_exhaust_fails_after_init() {
+ let (_guard, mut memory) = setup_test();
+
+ let stretched = init_new_password(
+ &mut memory,
+ "pw",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1,
+ )
+ .await
+ .unwrap();
+ assert_eq!(stretched.as_slice(), EXPECTED_STRETCHED_OUT_V1.as_slice());
+
+ // Counter & threshold of password counter. After init, it is at 1, but the threshold is
+ // increased by 1, so the number of attempts is still 10.
+ assert_eq!(ops::test_get_counter(OID_COUNTER_PASSWORD), 1);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_PASSWORD),
+ SMALL_MONOTONIC_COUNTER_MAX_USE + 1,
+ );
+ // Counter & threshold of hmac_writeprotected counter.
+ assert_eq!(ops::test_get_counter(OID_COUNTER_HMAC_WRITEPROTECTED), 0);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_HMAC_WRITEPROTECTED),
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ );
+
+ // Exhaust all attempts.
+ for i in 1..=SMALL_MONOTONIC_COUNTER_MAX_USE {
+ assert_eq!(
+ stretch_password(
+ &mut memory,
+ "wrong",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1,
+ )
+ .await,
+ Err(Error::SecureChip(
+ SecureChipError::SC_ERR_INCORRECT_PASSWORD,
+ )),
+ );
+
+ // Counter & threshold of password counter.
+ assert_eq!(ops::test_get_counter(OID_COUNTER_PASSWORD), 1 + i);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_PASSWORD),
+ SMALL_MONOTONIC_COUNTER_MAX_USE + 1,
+ );
+ // Counter & threshold of hmac_writeprotected counter.
+ assert_eq!(ops::test_get_counter(OID_COUNTER_HMAC_WRITEPROTECTED), i);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_HMAC_WRITEPROTECTED),
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ );
+ }
+
+ // Even a correct password doesn't work.
+ assert_eq!(
+ stretch_password(
+ &mut memory,
+ "pw",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1,
+ )
+ .await,
+ Err(Error::SecureChip(
+ SecureChipError::SC_ERR_INCORRECT_PASSWORD,
+ )),
+ );
+ let stretched = [0u8; KDF_LEN];
+ assert_eq!(stretched.as_slice(), [0u8; KDF_LEN].as_slice());
+ }
+
+ #[async_test::test]
+ #[allow(clippy::await_holding_lock)]
+ // Test that after initializing a new password, one can make a few failed stretch attempts, and
+ // that doing a correct attempt resets the counters.
+ async fn test_optiga_password_v1() {
+ let (_guard, mut memory) = setup_test();
+
+ let stretched = init_new_password(
+ &mut memory,
+ "pw",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1,
+ )
+ .await
+ .unwrap();
+ assert_eq!(stretched.as_slice(), EXPECTED_STRETCHED_OUT_V1.as_slice());
+
+ // Counter & threshold of password counter. After init, it is at 1, but the threshold is
+ // increased by 1, so the number of attempts is still 10.
+ assert_eq!(ops::test_get_counter(OID_COUNTER_PASSWORD), 1);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_PASSWORD),
+ SMALL_MONOTONIC_COUNTER_MAX_USE + 1,
+ );
+ // Counter & threshold of hmac_writeprotected counter.
+ assert_eq!(ops::test_get_counter(OID_COUNTER_HMAC_WRITEPROTECTED), 0);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_HMAC_WRITEPROTECTED),
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ );
+
+ // A few failed attempts:
+ for i in 1..=2 {
+ assert_eq!(
+ stretch_password(
+ &mut memory,
+ "wrong",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1,
+ )
+ .await,
+ Err(Error::SecureChip(
+ SecureChipError::SC_ERR_INCORRECT_PASSWORD,
+ )),
+ );
+
+ // Counter & threshold of password counter.
+ assert_eq!(ops::test_get_counter(OID_COUNTER_PASSWORD), 1 + i);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_PASSWORD),
+ SMALL_MONOTONIC_COUNTER_MAX_USE + 1,
+ );
+ // Counter & threshold of hmac_writeprotected counter.
+ assert_eq!(ops::test_get_counter(OID_COUNTER_HMAC_WRITEPROTECTED), i);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_HMAC_WRITEPROTECTED),
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ );
+ }
+
+ // Correct attempt gets the right stretched value and resets counters.
+ let stretched = stretch_password(
+ &mut memory,
+ "pw",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1,
+ )
+ .await
+ .unwrap();
+ assert_eq!(stretched.as_slice(), EXPECTED_STRETCHED_OUT_V1.as_slice());
+ // Counter & threshold of password counter.
+ assert_eq!(ops::test_get_counter(OID_COUNTER_PASSWORD), 0);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_PASSWORD),
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ );
+ // Counter & threshold of hmac_writeprotected counter.
+ assert_eq!(ops::test_get_counter(OID_COUNTER_HMAC_WRITEPROTECTED), 0);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_HMAC_WRITEPROTECTED),
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ );
+
+ // Exhaust all attempts. The PASSWORD_COUNTER during this is different to above as the
+ // counter/threshold was reset to 0/MAX after the correct stretch attempt.
+ for i in 1..=SMALL_MONOTONIC_COUNTER_MAX_USE {
+ assert_eq!(
+ stretch_password(
+ &mut memory,
+ "wrong",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1,
+ )
+ .await,
+ Err(Error::SecureChip(
+ SecureChipError::SC_ERR_INCORRECT_PASSWORD,
+ )),
+ );
+
+ // Counter & threshold of password counter.
+ assert_eq!(ops::test_get_counter(OID_COUNTER_PASSWORD), i);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_PASSWORD),
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ );
+ // Counter & threshold of hmac_writeprotected counter.
+ assert_eq!(ops::test_get_counter(OID_COUNTER_HMAC_WRITEPROTECTED), i);
+ assert_eq!(
+ ops::test_get_threshold(OID_COUNTER_HMAC_WRITEPROTECTED),
+ SMALL_MONOTONIC_COUNTER_MAX_USE,
+ );
+ }
+
+ // Even a correct password doesn't work anymore.
+ assert_eq!(
+ stretch_password(
+ &mut memory,
+ "pw",
+ PasswordStretchAlgo::SECURECHIP_PASSWORD_STRETCH_ALGO_V1,
+ )
+ .await,
+ Err(Error::SecureChip(
+ SecureChipError::SC_ERR_INCORRECT_PASSWORD,
+ )),
+ );
+ let stretched = [0u8; KDF_LEN];
+ assert_eq!(stretched.as_slice(), [0u8; KDF_LEN].as_slice());
+ }
+}
diff --git a/src/rust/bitbox-securechip/src/optiga/ops.rs b/src/rust/bitbox-securechip/src/optiga/ops.rs
index 2be864b..935e6a8 100644
--- a/src/rust/bitbox-securechip/src/optiga/ops.rs
+++ b/src/rust/bitbox-securechip/src/optiga/ops.rs
@@ -347,3 +347,217 @@ pub(super) async fn crypt_hmac(
MAC.zeroize();
Ok(())
}
+
+pub(super) async fn util_write_data(
+ oid: u16,
+ write_type: u8,
+ offset: u16,
+ buffer: &[u8],
+) -> Result<(), Error> {
+ // Static because the Optiga library keeps a raw pointer to the input buffer until the async
+ // callback completes, and the Rust future may be dropped before that happens.
+ static INPUT: StaticBytes<ASYNC_BUF_MAX_SIZE> = StaticBytes::const_init();
+
+ if buffer.len() > ASYNC_BUF_MAX_SIZE {
+ panic!("optiga async write larger than max supported size");
+ }
+ let input_len: u16 = buffer.len().try_into().unwrap();
+ let util = unsafe { bitbox_securechip_sys::optiga_util_instance() };
+
+ INPUT.copy_from_slice(buffer);
+ let result = run_async_op(|| unsafe {
+ bitbox_securechip_sys::optiga_util_write_data(
+ util,
+ oid,
+ write_type,
+ offset,
+ INPUT.as_mut_ptr(),
+ input_len,
+ )
+ })
+ .await
+ .map_err(|status| Error::from_status(status as i32));
+ INPUT.zeroize();
+ result
+}
+
+pub(super) async fn crypt_symmetric_encrypt(
+ encryption_mode: bitbox_securechip_sys::optiga_symmetric_encryption_mode_t,
+ symmetric_key_oid: bitbox_securechip_sys::optiga_key_id_t,
+ plain_data: &[u8],
+ encrypted_data: &mut [u8],
+) -> Result<(), Error> {
+ // Static because the Optiga library keeps raw pointers to the input, output and length until
+ // the async callback completes, and the Rust future may be dropped before that happens.
+ static INPUT: StaticBytes<{ super::KDF_LEN }> = StaticBytes::const_init();
+ static OUTPUT: StaticBytes<16> = StaticBytes::const_init();
+ static OUTPUT_LEN: GroundedCell<u32> = GroundedCell::const_init();
+
+ if plain_data.len() > super::KDF_LEN || encrypted_data.len() > 16 {
+ return Err(Error::SecureChip(SecureChipError::SC_ERR_INVALID_ARGS));
+ }
+
+ let crypt = unsafe { bitbox_securechip_sys::optiga_crypt_instance() };
+ let input_len: u32 = plain_data.len().try_into().unwrap();
+ let requested_output_len = encrypted_data.len();
+
+ INPUT.copy_from_slice(plain_data);
+ OUTPUT.clear();
+ unsafe {
+ OUTPUT_LEN.get().write(requested_output_len as u32);
+ }
+ let result = run_async_op(|| unsafe {
+ bitbox_securechip_sys::optiga_crypt_symmetric_encrypt(
+ crypt,
+ encryption_mode,
+ symmetric_key_oid,
+ INPUT.as_mut_ptr(),
+ input_len,
+ core::ptr::null(),
+ 0,
+ core::ptr::null(),
+ 0,
+ OUTPUT.as_mut_ptr(),
+ OUTPUT_LEN.get(),
+ )
+ })
+ .await
+ .map_err(|status| Error::from_status(status as i32));
+ if let Err(err) = result {
+ INPUT.zeroize();
+ OUTPUT.zeroize();
+ return Err(err);
+ }
+
+ if unsafe { OUTPUT_LEN.get().read() as usize } != requested_output_len {
+ INPUT.zeroize();
+ OUTPUT.zeroize();
+ return Err(Error::SecureChip(
+ SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_LEN,
+ ));
+ }
+ OUTPUT.copy_to_slice(encrypted_data);
+ INPUT.zeroize();
+ OUTPUT.zeroize();
+ Ok(())
+}
+
+pub(super) async fn crypt_generate_auth_code(
+ rng_type: bitbox_securechip_sys::optiga_rng_type_t,
+ random_data: &mut [u8],
+) -> Result<(), Error> {
+ // Static because the Optiga library keeps a raw pointer to the output buffer until the async
+ // callback completes, and the Rust future may be dropped before that happens.
+ static RANDOM: StaticBytes<{ super::KDF_LEN }> = StaticBytes::const_init();
+
+ if random_data.len() > super::KDF_LEN {
+ return Err(Error::SecureChip(SecureChipError::SC_ERR_INVALID_ARGS));
+ }
+
+ let crypt = unsafe { bitbox_securechip_sys::optiga_crypt_instance() };
+ let random_data_len: u16 = random_data.len().try_into().unwrap();
+
+ RANDOM.clear();
+ let result = run_async_op(|| unsafe {
+ bitbox_securechip_sys::optiga_crypt_generate_auth_code(
+ crypt,
+ rng_type,
+ core::ptr::null(),
+ 0,
+ RANDOM.as_mut_ptr(),
+ random_data_len,
+ )
+ })
+ .await
+ .map_err(|status| Error::from_status(status as i32));
+ if let Err(err) = result {
+ RANDOM.zeroize();
+ return Err(err);
+ }
+
+ RANDOM.copy_to_slice(random_data);
+ RANDOM.zeroize();
+ Ok(())
+}
+
+pub(super) async fn crypt_hmac_verify(
+ hmac_type: bitbox_securechip_sys::optiga_hmac_type_t,
+ secret: u16,
+ input_data: &[u8],
+ hmac: &[u8],
+) -> Result<(), Error> {
+ // Static because the Optiga library keeps raw pointers to the input buffers until the async
+ // callback completes, and the Rust future may be dropped before that happens.
+ static INPUT: StaticBytes<{ super::KDF_LEN }> = StaticBytes::const_init();
+ static HMAC: StaticBytes<{ super::KDF_LEN }> = StaticBytes::const_init();
+
+ if input_data.len() > super::KDF_LEN || hmac.len() > super::KDF_LEN {
+ return Err(Error::SecureChip(SecureChipError::SC_ERR_INVALID_ARGS));
+ }
+
+ let crypt = unsafe { bitbox_securechip_sys::optiga_crypt_instance() };
+ let input_data_len: u32 = input_data.len().try_into().unwrap();
+ let hmac_len: u32 = hmac.len().try_into().unwrap();
+
+ INPUT.copy_from_slice(input_data);
+ HMAC.copy_from_slice(hmac);
+ let result = run_async_op(|| unsafe {
+ bitbox_securechip_sys::optiga_crypt_hmac_verify(
+ crypt,
+ hmac_type,
+ secret,
+ INPUT.as_mut_ptr(),
+ input_data_len,
+ HMAC.as_mut_ptr(),
+ hmac_len,
+ )
+ })
+ .await
+ .map_err(|status| Error::from_status(status as i32));
+ INPUT.zeroize();
+ HMAC.zeroize();
+ result
+}
+
+pub(super) async fn crypt_symmetric_generate_key(
+ key_type: bitbox_securechip_sys::optiga_symmetric_key_type_t,
+ key_usage: bitbox_securechip_sys::optiga_key_usage_t,
+) -> Result<(), Error> {
+ // Static because the Optiga library keeps a raw pointer to the key id output until the async
+ // callback completes, and the Rust future may be dropped before that happens.
+ static KEYID: GroundedCell<bitbox_securechip_sys::optiga_key_id_t> = GroundedCell::uninit();
+
+ let crypt = unsafe { bitbox_securechip_sys::optiga_crypt_instance() };
+
+ unsafe {
+ KEYID
+ .get()
+ .write(super::key_id_from_oid(super::OID_AES_SYMKEY));
+ }
+ run_async_op(|| unsafe {
+ bitbox_securechip_sys::optiga_crypt_symmetric_generate_key(
+ crypt,
+ key_type,
+ key_usage as u8,
+ 0,
+ KEYID.get().cast(),
+ )
+ })
+ .await
+ .map_err(|status| Error::from_status(status as i32))
+}
+
+pub(super) async fn crypt_clear_auto_state(secret: u16) -> Result<(), Error> {
+ let crypt = unsafe { bitbox_securechip_sys::optiga_crypt_instance() };
+ run_async_op(|| unsafe { bitbox_securechip_sys::optiga_crypt_clear_auto_state(crypt, secret) })
+ .await
+ .map_err(|status| Error::from_status(status as i32))
+}
+
+pub(super) fn ifs_random_32_bytes(rand_out: &mut [u8; super::KDF_LEN]) -> Result<(), Error> {
+ if unsafe { bitbox_securechip_sys::optiga_ifs_random_32_bytes(rand_out.as_mut_ptr()) } {
+ Ok(())
+ } else {
+ Err(Error::SecureChip(SecureChipError::SC_ERR_IFS))
+ }
+}
diff --git a/src/rust/bitbox-securechip/src/optiga/ops_fake.rs b/src/rust/bitbox-securechip/src/optiga/ops_fake.rs
new file mode 100644
index 0000000..e94dcc0
--- /dev/null
+++ b/src/rust/bitbox-securechip/src/optiga/ops_fake.rs
@@ -0,0 +1,431 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::Error;
+use std::sync::{LazyLock, Mutex, MutexGuard};
+
+//------------------------------------------------------------------------------
+// Fixed test vectors / keys (deterministic fakes).
+
+const KDF_CMAC_KEY_FIXED: [u8; super::KDF_LEN] = [0xA0; super::KDF_LEN];
+const KDF_HMAC_KEY_FIXED: [u8; super::KDF_LEN] = [0xB0; super::KDF_LEN];
+const KDF_HMAC_WRITEPROTECTED_KEY_FIXED: [u8; super::KDF_LEN] = [0xC0; super::KDF_LEN];
+const PASSWORD_SECRET_FIXED: [u8; super::KDF_LEN] = [0x99; super::KDF_LEN];
+const AUTH_CODE_RANDOM_FIXED: [u8; super::KDF_LEN] = [0x77; super::KDF_LEN];
+const OPTIGA_CRYPT_ERROR: i32 = bitbox_securechip_sys::OPTIGA_CRYPT_ERROR as i32;
+const OPTIGA_CRYPT_ERROR_INVALID_INPUT: i32 =
+ bitbox_securechip_sys::OPTIGA_CRYPT_ERROR_INVALID_INPUT as i32;
+const OPTIGA_CRYPT_ERROR_MEMORY_INSUFFICIENT: i32 =
+ bitbox_securechip_sys::OPTIGA_CRYPT_ERROR_MEMORY_INSUFFICIENT as i32;
+const OPTIGA_UTIL_ERROR: i32 = bitbox_securechip_sys::OPTIGA_UTIL_ERROR as i32;
+const OPTIGA_UTIL_ERROR_INVALID_INPUT: i32 =
+ bitbox_securechip_sys::OPTIGA_UTIL_ERROR_INVALID_INPUT as i32;
+const OPTIGA_UTIL_ERROR_MEMORY_INSUFFICIENT: i32 =
+ bitbox_securechip_sys::OPTIGA_UTIL_ERROR_MEMORY_INSUFFICIENT as i32;
+
+#[derive(Clone)]
+struct FakeState {
+ oid_password: [u8; super::KDF_LEN],
+ oid_password_set: bool,
+ oid_counter_password_buf: [u8; 8],
+ oid_counter_hmac_writeprotected_buf: [u8; 8],
+ authorized_password: bool,
+ authorized_password_secret: bool,
+ random_ctr: usize,
+}
+
+impl Default for FakeState {
+ fn default() -> Self {
+ Self {
+ oid_password: [0; super::KDF_LEN],
+ oid_password_set: false,
+ oid_counter_password_buf: [0; 8],
+ oid_counter_hmac_writeprotected_buf: [0; 8],
+ authorized_password: false,
+ authorized_password_secret: false,
+ random_ctr: 0,
+ }
+ }
+}
+
+static TEST_LOCK: Mutex<()> = Mutex::new(());
+static STATE: LazyLock<Mutex<FakeState>> = LazyLock::new(|| Mutex::new(FakeState::default()));
+
+fn lock_state() -> MutexGuard<'static, FakeState> {
+ STATE.lock().unwrap()
+}
+
+fn counter_buf(state: &FakeState, oid: u16) -> &[u8; 8] {
+ match oid {
+ super::OID_COUNTER_PASSWORD => &state.oid_counter_password_buf,
+ super::OID_COUNTER_HMAC_WRITEPROTECTED => &state.oid_counter_hmac_writeprotected_buf,
+ _ => panic!("unexpected counter oid"),
+ }
+}
+
+fn counter_buf_mut(state: &mut FakeState, oid: u16) -> &mut [u8; 8] {
+ match oid {
+ super::OID_COUNTER_PASSWORD => &mut state.oid_counter_password_buf,
+ super::OID_COUNTER_HMAC_WRITEPROTECTED => &mut state.oid_counter_hmac_writeprotected_buf,
+ _ => panic!("unexpected counter oid"),
+ }
+}
+
+fn get_counter_from_buf(buf: &[u8; 8]) -> u32 {
+ u32::from_be_bytes(buf[..4].try_into().unwrap())
+}
+
+fn get_threshold_from_buf(buf: &[u8; 8]) -> u32 {
+ u32::from_be_bytes(buf[4..].try_into().unwrap())
+}
+
+fn set_counter_in_buf(buf: &mut [u8; 8], counter: u32) {
+ buf[..4].copy_from_slice(&counter.to_be_bytes());
+}
+
+fn set_threshold_in_buf(buf: &mut [u8; 8], threshold: u32) {
+ buf[4..].copy_from_slice(&threshold.to_be_bytes());
+}
+
+fn compute_hmac(key: &[u8], data: &[u8]) -> [u8; super::KDF_LEN] {
+ let mut out = [0u8; super::KDF_LEN];
+ super::hmac_sha256(key, data, &mut out);
+ out
+}
+
+pub(super) fn test_lock() -> MutexGuard<'static, ()> {
+ TEST_LOCK.lock().unwrap()
+}
+
+pub(super) fn test_reset() {
+ *lock_state() = FakeState::default();
+}
+
+pub(super) fn test_seed_oid_password(password_hash: &[u8; super::KDF_LEN]) {
+ let mut state = lock_state();
+ state.oid_password = *password_hash;
+ state.oid_password_set = true;
+}
+
+pub(super) fn test_set_counter(oid: u16, counter: u32, threshold: u32) {
+ let mut state = lock_state();
+ let buf = counter_buf_mut(&mut state, oid);
+ set_counter_in_buf(buf, counter);
+ set_threshold_in_buf(buf, threshold);
+}
+
+pub(super) fn test_get_counter(oid: u16) -> u32 {
+ let state = lock_state();
+ get_counter_from_buf(counter_buf(&state, oid))
+}
+
+pub(super) fn test_get_threshold(oid: u16) -> u32 {
+ let state = lock_state();
+ get_threshold_from_buf(counter_buf(&state, oid))
+}
+
+//------------------------------------------------------------------------------
+// Fake optiga_ops API surface (unit-test seam).
+
+pub(super) async fn util_read_data(oid: u16, offset: u16, out: &mut [u8]) -> Result<(), Error> {
+ if offset != 0 {
+ return Err(Error::from_status(OPTIGA_UTIL_ERROR_INVALID_INPUT));
+ }
+
+ let state = lock_state();
+ match oid {
+ super::OID_PASSWORD_SECRET => {
+ if !state.authorized_password {
+ return Err(Error::from_status(OPTIGA_UTIL_ERROR));
+ }
+ if out.len() != super::KDF_LEN {
+ return Err(Error::from_status(OPTIGA_UTIL_ERROR_MEMORY_INSUFFICIENT));
+ }
+ out.copy_from_slice(&PASSWORD_SECRET_FIXED);
+ Ok(())
+ }
+ super::OID_COUNTER => {
+ if out.len() != 4 {
+ return Err(Error::from_status(OPTIGA_UTIL_ERROR_MEMORY_INSUFFICIENT));
+ }
+ out.fill(0);
+ Ok(())
+ }
+ _ => Err(Error::from_status(OPTIGA_UTIL_ERROR_INVALID_INPUT)),
+ }
+}
+
+pub(super) async fn crypt_hmac(
+ hmac_type: bitbox_securechip_sys::optiga_hmac_type_t,
+ secret: u16,
+ msg: &[u8],
+ mac_out: &mut [u8; super::KDF_LEN],
+) -> Result<(), Error> {
+ crypt_hmac_sync(hmac_type, secret, msg, mac_out)
+}
+
+pub(super) async fn util_write_data(
+ oid: u16,
+ write_type: u8,
+ offset: u16,
+ buffer: &[u8],
+) -> Result<(), Error> {
+ util_write_data_sync(oid, write_type, offset, buffer)
+}
+
+pub(super) async fn crypt_symmetric_encrypt(
+ encryption_mode: bitbox_securechip_sys::optiga_symmetric_encryption_mode_t,
+ symmetric_key_oid: bitbox_securechip_sys::optiga_key_id_t,
+ plain_data: &[u8],
+ encrypted_data: &mut [u8],
+) -> Result<(), Error> {
+ crypt_symmetric_encrypt_sync(
+ encryption_mode,
+ symmetric_key_oid,
+ plain_data,
+ encrypted_data,
+ )
+}
+
+pub(super) async fn crypt_generate_auth_code(
+ rng_type: bitbox_securechip_sys::optiga_rng_type_t,
+ random_data: &mut [u8],
+) -> Result<(), Error> {
+ crypt_generate_auth_code_sync(rng_type, random_data)
+}
+
+pub(super) async fn crypt_hmac_verify(
+ hmac_type: bitbox_securechip_sys::optiga_hmac_type_t,
+ secret: u16,
+ input_data: &[u8],
+ hmac: &[u8],
+) -> Result<(), Error> {
+ crypt_hmac_verify_sync(hmac_type, secret, input_data, hmac)
+}
+
+pub(super) async fn crypt_symmetric_generate_key(
+ key_type: bitbox_securechip_sys::optiga_symmetric_key_type_t,
+ key_usage: bitbox_securechip_sys::optiga_key_usage_t,
+) -> Result<(), Error> {
+ crypt_symmetric_generate_key_sync(key_type, key_usage)
+}
+
+pub(super) async fn crypt_clear_auto_state(secret: u16) -> Result<(), Error> {
+ crypt_clear_auto_state_sync(secret)
+}
+
+pub(super) fn ifs_random_32_bytes(rand_out: &mut [u8; super::KDF_LEN]) -> Result<(), Error> {
+ let mut state = lock_state();
+ // There are only three calls to this at the moment, all in init_new_password.
+ let src = match state.random_ctr {
+ 0 => &KDF_HMAC_KEY_FIXED,
+ 1 => &PASSWORD_SECRET_FIXED,
+ 2 => &KDF_HMAC_WRITEPROTECTED_KEY_FIXED,
+ _ => unreachable!(),
+ };
+ rand_out.copy_from_slice(src);
+ state.random_ctr = (state.random_ctr + 1) % 3;
+ Ok(())
+}
+
+pub(super) fn util_write_data_sync(
+ oid: u16,
+ _write_type: u8,
+ offset: u16,
+ buffer: &[u8],
+) -> Result<(), Error> {
+ if offset != 0 {
+ return Err(Error::from_status(OPTIGA_UTIL_ERROR_INVALID_INPUT));
+ }
+
+ let mut state = lock_state();
+ match oid {
+ super::OID_PASSWORD => {
+ if !state.authorized_password_secret || buffer.len() != super::KDF_LEN {
+ return Err(Error::from_status(if !state.authorized_password_secret {
+ OPTIGA_UTIL_ERROR
+ } else {
+ OPTIGA_UTIL_ERROR_INVALID_INPUT
+ }));
+ }
+ state.oid_password.copy_from_slice(buffer);
+ state.oid_password_set = true;
+ Ok(())
+ }
+ super::OID_COUNTER_PASSWORD | super::OID_COUNTER_HMAC_WRITEPROTECTED => {
+ if oid == super::OID_COUNTER_HMAC_WRITEPROTECTED && !state.authorized_password {
+ return Err(Error::from_status(OPTIGA_UTIL_ERROR));
+ }
+ if buffer.len() != 8 {
+ return Err(Error::from_status(OPTIGA_UTIL_ERROR_INVALID_INPUT));
+ }
+ counter_buf_mut(&mut state, oid).copy_from_slice(buffer);
+ Ok(())
+ }
+ super::OID_HMAC => {
+ if buffer.len() != super::KDF_LEN {
+ return Err(Error::from_status(OPTIGA_UTIL_ERROR_INVALID_INPUT));
+ }
+ assert_eq!(buffer, KDF_HMAC_KEY_FIXED.as_slice());
+ Ok(())
+ }
+ super::OID_HMAC_WRITEPROTECTED => {
+ if !state.authorized_password || buffer.len() != super::KDF_LEN {
+ return Err(Error::from_status(if !state.authorized_password {
+ OPTIGA_UTIL_ERROR
+ } else {
+ OPTIGA_UTIL_ERROR_INVALID_INPUT
+ }));
+ }
+ assert_eq!(buffer, KDF_HMAC_WRITEPROTECTED_KEY_FIXED.as_slice());
+ Ok(())
+ }
+ super::OID_PASSWORD_SECRET => {
+ assert_eq!(buffer, PASSWORD_SECRET_FIXED.as_slice());
+ Ok(())
+ }
+ // Accept other writes without emulating full semantics (counter reset, hmac key, etc.).
+ _ => Ok(()),
+ }
+}
+
+pub(super) fn crypt_hmac_sync(
+ hmac_type: bitbox_securechip_sys::optiga_hmac_type_t,
+ secret: u16,
+ input_data: &[u8],
+ mac_out: &mut [u8],
+) -> Result<(), Error> {
+ // Use hmac_sha256 with a different fixed key and msg as the value.
+ if hmac_type != super::OPTIGA_HMAC_SHA_256 {
+ return Err(Error::from_status(OPTIGA_CRYPT_ERROR_INVALID_INPUT));
+ }
+ if mac_out.len() != super::KDF_LEN {
+ return Err(Error::from_status(OPTIGA_CRYPT_ERROR_MEMORY_INSUFFICIENT));
+ }
+
+ let mut state = lock_state();
+ let key = match secret {
+ super::OID_HMAC => &KDF_HMAC_KEY_FIXED,
+ super::OID_HMAC_WRITEPROTECTED => {
+ // Emulate the small monotonic counter that is attached to using the
+ // hmac_writeprotected slot. Stored as {counter_be_u32, threshold_be_u32}.
+ let buf = counter_buf_mut(&mut state, super::OID_COUNTER_HMAC_WRITEPROTECTED);
+ let counter = get_counter_from_buf(buf);
+ let threshold = get_threshold_from_buf(buf);
+ if counter >= threshold {
+ return Err(Error::from_status(super::OPTIGA_HMAC_VERIFY_FAIL));
+ }
+ set_counter_in_buf(buf, counter + 1);
+ &KDF_HMAC_WRITEPROTECTED_KEY_FIXED
+ }
+ _ => return Err(Error::from_status(OPTIGA_CRYPT_ERROR)),
+ };
+
+ mac_out.copy_from_slice(&compute_hmac(key, input_data));
+ Ok(())
+}
+
+pub(super) fn crypt_symmetric_encrypt_sync(
+ encryption_mode: bitbox_securechip_sys::optiga_symmetric_encryption_mode_t,
+ symmetric_key_oid: bitbox_securechip_sys::optiga_key_id_t,
+ plain_data: &[u8],
+ encrypted_data: &mut [u8],
+) -> Result<(), Error> {
+ // Use hmac_sha256 with a fixed key and msg as the value, truncated to 16 bytes.
+ if encryption_mode != super::OPTIGA_SYMMETRIC_CMAC
+ || symmetric_key_oid != super::key_id_from_oid(super::OID_AES_SYMKEY)
+ {
+ return Err(Error::from_status(OPTIGA_CRYPT_ERROR_INVALID_INPUT));
+ }
+ if encrypted_data.len() != 16 {
+ return Err(Error::from_status(OPTIGA_CRYPT_ERROR_MEMORY_INSUFFICIENT));
+ }
+
+ let out = compute_hmac(&KDF_CMAC_KEY_FIXED, plain_data);
+ encrypted_data.copy_from_slice(&out[..16]);
+ Ok(())
+}
+
+pub(super) fn crypt_symmetric_generate_key_sync(
+ key_type: bitbox_securechip_sys::optiga_symmetric_key_type_t,
+ key_usage: bitbox_securechip_sys::optiga_key_usage_t,
+) -> Result<(), Error> {
+ if key_type != super::OPTIGA_SYMMETRIC_AES_256
+ || key_usage != super::OPTIGA_KEY_USAGE_ENCRYPTION
+ {
+ return Err(Error::from_status(OPTIGA_CRYPT_ERROR_INVALID_INPUT));
+ }
+ // We keep using the fixed cmac key in the tests.
+ Ok(())
+}
+
+pub(super) fn crypt_generate_auth_code_sync(
+ rng_type: bitbox_securechip_sys::optiga_rng_type_t,
+ random_data: &mut [u8],
+) -> Result<(), Error> {
+ if rng_type != super::OPTIGA_RNG_TYPE_TRNG {
+ return Err(Error::from_status(OPTIGA_CRYPT_ERROR_INVALID_INPUT));
+ }
+ if random_data.len() != super::KDF_LEN {
+ return Err(Error::from_status(OPTIGA_CRYPT_ERROR_INVALID_INPUT));
+ }
+ random_data.copy_from_slice(&AUTH_CODE_RANDOM_FIXED);
+ Ok(())
+}
+
+pub(super) fn crypt_hmac_verify_sync(
+ hmac_type: bitbox_securechip_sys::optiga_hmac_type_t,
+ secret: u16,
+ input_data: &[u8],
+ hmac: &[u8],
+) -> Result<(), Error> {
+ if hmac_type != super::OPTIGA_HMAC_SHA_256 {
+ return Err(Error::from_status(OPTIGA_CRYPT_ERROR_INVALID_INPUT));
+ }
+ if input_data.len() != super::KDF_LEN || hmac.len() != super::KDF_LEN {
+ return Err(Error::from_status(OPTIGA_CRYPT_ERROR_INVALID_INPUT));
+ }
+
+ let mut state = lock_state();
+ let key = match secret {
+ super::OID_PASSWORD_SECRET => &PASSWORD_SECRET_FIXED,
+ super::OID_PASSWORD => {
+ // Emulate the small monotonic counter that is attached to password authorization.
+ // Stored as {counter_be_u32, threshold_be_u32}.
+ let buf = counter_buf_mut(&mut state, super::OID_COUNTER_PASSWORD);
+ let counter = get_counter_from_buf(buf);
+ let threshold = get_threshold_from_buf(buf);
+ if counter >= threshold {
+ return Err(Error::from_status(super::OPTIGA_HMAC_VERIFY_FAIL));
+ }
+ set_counter_in_buf(buf, counter + 1);
+
+ if !state.oid_password_set {
+ return Err(Error::from_status(OPTIGA_CRYPT_ERROR));
+ }
+ &state.oid_password
+ }
+ _ => return Err(Error::from_status(OPTIGA_CRYPT_ERROR_INVALID_INPUT)),
+ };
+
+ let computed = compute_hmac(key, input_data);
+ if computed != hmac {
+ return Err(Error::from_status(super::OPTIGA_HMAC_VERIFY_FAIL));
+ }
+
+ match secret {
+ super::OID_PASSWORD => state.authorized_password = true,
+ super::OID_PASSWORD_SECRET => state.authorized_password_secret = true,
+ _ => {}
+ }
+ Ok(())
+}
+
+pub(super) fn crypt_clear_auto_state_sync(secret: u16) -> Result<(), Error> {
+ let mut state = lock_state();
+ match secret {
+ super::OID_PASSWORD => state.authorized_password = false,
+ super::OID_PASSWORD_SECRET => state.authorized_password_secret = false,
+ _ => {}
+ }
+ Ok(())
+}
diff --git a/src/rust/bitbox02-rust/src/keystore.rs b/src/rust/bitbox02-rust/src/keystore.rs
index 2f3c06e..4f1454d 100644
--- a/src/rust/bitbox02-rust/src/keystore.rs
+++ b/src/rust/bitbox02-rust/src/keystore.rs
@@ -318,9 +318,13 @@ async fn encrypt_and_store_seed_internal(
let password_stretch_algo = default_password_stretch_algo(hal)?;
- let secret = hal
- .securechip()
- .init_new_password(password, password_stretch_algo)?;
+ let secret = {
+ let subsystems = hal.as_mut();
+ subsystems
+ .securechip
+ .init_new_password(subsystems.memory, password, password_stretch_algo)
+ .await?
+ };
let iv_rand = bitbox_core_utils::random::random_32_bytes_from_hal(hal)?;
let iv: &[u8; 16] = iv_rand.first_chunk::<16>().unwrap();
@@ -414,7 +418,7 @@ fn check_retained_seed(hal: &mut impl KeystoreHal, seed: &[u8]) -> Result<(), ()
Ok(())
}
-fn get_and_decrypt_seed(
+async fn get_and_decrypt_seed(
hal: &mut impl crate::hal::Hal,
password: &str,
) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
@@ -426,9 +430,13 @@ fn get_and_decrypt_seed(
// wrong, so it already returns an error here. The ATECC stretches the password without checking
// if the password is correct, and we determine if it is correct in the seed decryption
// step below.
- let secret = hal
- .securechip()
- .stretch_password(password, password_stretch_algo)?;
+ let secret = {
+ let subsystems = hal.as_mut();
+ subsystems
+ .securechip
+ .stretch_password(subsystems.memory, password, password_stretch_algo)
+ .await?
+ };
let seed = match bitbox_aes::decrypt_with_hmac(secret.as_slice(), &encrypted) {
Ok(seed) => seed,
Err(()) => return Err(Error::IncorrectPassword),
@@ -458,7 +466,7 @@ pub async fn unlock(
}
hal.system().communication_timeout_reset(LONG_TIMEOUT);
hal.eeprom().increment_unlock_attempts();
- let seed = match get_and_decrypt_seed(hal, password) {
+ let seed = match get_and_decrypt_seed(hal, password).await {
Ok(seed) => seed,
err @ Err(_) => {
if get_remaining_unlock_attempts(hal) == 0 {
@@ -1557,7 +1565,12 @@ mod tests {
let encrypted = {
let secret = mock_hal
.securechip
- .stretch_password(password, memory::PasswordStretchAlgo::V0)
+ .stretch_password(
+ &mut mock_hal.memory,
+ password,
+ memory::PasswordStretchAlgo::V0,
+ )
+ .await
.unwrap();
let iv: &[u8; 16] = &[0xaau8; 16];
diff --git a/src/rust/bitbox02-rust/src/reset.rs b/src/rust/bitbox02-rust/src/reset.rs
index d48a628..4b9be20 100644
--- a/src/rust/bitbox02-rust/src/reset.rs
+++ b/src/rust/bitbox02-rust/src/reset.rs
@@ -23,7 +23,11 @@ pub(crate) async fn reset(hal: &mut impl crate::hal::Hal, status: bool) {
// errors.
let mut reset_ok = false;
for _ in 0..5 {
- if hal.securechip().reset_keys().is_ok() {
+ let result = {
+ let subsystems = hal.as_mut();
+ subsystems.securechip.reset_keys(subsystems.memory).await
+ };
+ if result.is_ok() {
reset_ok = true;
break;
}
diff --git a/src/rust/bitbox02/Cargo.toml b/src/rust/bitbox02/Cargo.toml
index 6b3597b..2b9d74c 100644
--- a/src/rust/bitbox02/Cargo.toml
+++ b/src/rust/bitbox02/Cargo.toml
@@ -28,6 +28,7 @@ sha2 = { workspace = true }
hex_lit = { workspace = true }
[dev-dependencies]
+async_test = { path = "../async_test" }
bitbox-aes = { path = "../bitbox-aes" }
bitbox-framed-serial-link = { path = "../bitbox-framed-serial-link" }
diff --git a/src/rust/bitbox02/src/hal/securechip.rs b/src/rust/bitbox02/src/hal/securechip.rs
index 272c556..300854c 100644
--- a/src/rust/bitbox02/src/hal/securechip.rs
+++ b/src/rust/bitbox02/src/hal/securechip.rs
@@ -81,27 +81,33 @@ impl SecureChip for BitBox02SecureChip {
crate::securechip::random().map_err(to_hal_error)
}
- fn init_new_password(
+ async fn init_new_password(
&mut self,
+ memory: &mut impl bitbox_hal::Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
crate::securechip::init_new_password(
+ memory,
password,
to_c_password_stretch_algo(password_stretch_algo),
)
+ .await
.map_err(to_hal_error)
}
- fn stretch_password(
+ async fn stretch_password(
&mut self,
+ memory: &mut impl bitbox_hal::Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
crate::securechip::stretch_password(
+ memory,
password,
to_c_password_stretch_algo(password_stretch_algo),
)
+ .await
.map_err(to_hal_error)
}
@@ -125,8 +131,8 @@ impl SecureChip for BitBox02SecureChip {
crate::securechip::model().map(to_hal_model)
}
- fn reset_keys(&mut self) -> Result<(), ()> {
- crate::securechip::reset_keys()
+ async fn reset_keys(&mut self, memory: &mut impl bitbox_hal::Memory) -> Result<(), ()> {
+ crate::securechip::reset_keys(memory).await
}
#[cfg(feature = "app-u2f")]
@@ -256,20 +262,23 @@ mod tests {
);
}
- #[test]
- fn test_kdf() {
+ #[async_test::test]
+ async fn test_kdf() {
let mut securechip = BitBox02SecureChip;
let msg = [0u8; 32];
- let result = util::bb02_async::block_on(securechip.kdf(&msg)).unwrap();
+ let result = securechip.kdf(&msg).await.unwrap();
let expected = hex!("1c723ccd9597e76deb55f9fd6808014007bcb3d67fc060f1149aefb9be88f423");
assert_eq!(result.as_slice(), expected.as_slice());
}
- #[test]
- fn test_init_new_password_invalid_password_stretch_algo() {
+ #[async_test::test]
+ async fn test_init_new_password_invalid_password_stretch_algo() {
let mut securechip = BitBox02SecureChip;
+ let mut memory = crate::hal::memory::BitBox02Memory;
assert_eq!(
- securechip.init_new_password("password", PasswordStretchAlgo::V0),
+ securechip
+ .init_new_password(&mut memory, "password", PasswordStretchAlgo::V0)
+ .await,
Err(Error::SecureChip(
SecureChipError::InvalidPasswordStretchAlgo,
)),
diff --git a/src/rust/bitbox02/src/securechip/imp.rs b/src/rust/bitbox02/src/securechip/imp.rs
index f8fe8d8..6a33cbe 100644
--- a/src/rust/bitbox02/src/securechip/imp.rs
+++ b/src/rust/bitbox02/src/securechip/imp.rs
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
use alloc::boxed::Box;
+use bitbox_hal::Memory;
use bitbox_securechip::{Error, Model, PasswordStretchAlgo, atecc, optiga};
use core::ffi::c_int;
use util::cell::SyncCell;
@@ -39,30 +40,32 @@ pub async fn monotonic_increments_remaining() -> Result<u32, ()> {
}
}
-pub fn reset_keys() -> Result<(), ()> {
+pub async fn reset_keys(memory: &mut impl Memory) -> Result<(), ()> {
match backend() {
Backend::Atecc => atecc::reset_keys(),
- Backend::Optiga => optiga::reset_keys(),
+ Backend::Optiga => optiga::reset_keys(memory).await,
}
}
-pub fn init_new_password(
+pub async fn init_new_password(
+ memory: &mut impl Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
match backend() {
- Backend::Atecc => atecc::init_new_password(password, password_stretch_algo),
- Backend::Optiga => optiga::init_new_password(password, password_stretch_algo),
+ Backend::Atecc => atecc::init_new_password(memory, password, password_stretch_algo),
+ Backend::Optiga => optiga::init_new_password(memory, password, password_stretch_algo).await,
}
}
-pub fn stretch_password(
+pub async fn stretch_password(
+ memory: &mut impl Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
match backend() {
- Backend::Atecc => atecc::stretch_password(password, password_stretch_algo),
- Backend::Optiga => optiga::stretch_password(password, password_stretch_algo),
+ Backend::Atecc => atecc::stretch_password(memory, password, password_stretch_algo),
+ Backend::Optiga => optiga::stretch_password(memory, password, password_stretch_algo).await,
}
}
@@ -123,11 +126,8 @@ pub unsafe extern "C" fn rust_securechip_setup(
/// Resets the secure-chip objects involved in password stretching.
#[unsafe(no_mangle)]
pub extern "C" fn rust_securechip_reset_keys() -> bool {
- match backend() {
- Backend::Atecc => atecc::reset_keys(),
- Backend::Optiga => optiga::reset_keys(),
- }
- .is_ok()
+ let mut memory = crate::hal::memory::BitBox02Memory;
+ util::bb02_async::block_on(reset_keys(&mut memory)).is_ok()
}
/// Generates a new device attestation key and writes the public key to `pubkey_out`.
diff --git a/src/rust/bitbox02/src/securechip/imp_fake.rs b/src/rust/bitbox02/src/securechip/imp_fake.rs
index 4c57372..07379db 100644
--- a/src/rust/bitbox02/src/securechip/imp_fake.rs
+++ b/src/rust/bitbox02/src/securechip/imp_fake.rs
@@ -36,11 +36,12 @@ pub async fn monotonic_increments_remaining() -> Result<u32, ()> {
Ok(1)
}
-pub fn reset_keys() -> Result<(), ()> {
+pub async fn reset_keys(_memory: &mut impl bitbox_hal::Memory) -> Result<(), ()> {
Ok(())
}
-pub fn init_new_password(
+pub async fn init_new_password(
+ _memory: &mut impl bitbox_hal::Memory,
password: &str,
password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
@@ -55,7 +56,8 @@ pub fn init_new_password(
))))
}
-pub fn stretch_password(
+pub async fn stretch_password(
+ _memory: &mut impl bitbox_hal::Memory,
password: &str,
_password_stretch_algo: PasswordStretchAlgo,
) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
diff --git a/src/rust/bitbox03/src/securechip.rs b/src/rust/bitbox03/src/securechip.rs
index a4d66ca..bc633f2 100644
--- a/src/rust/bitbox03/src/securechip.rs
+++ b/src/rust/bitbox03/src/securechip.rs
@@ -10,8 +10,9 @@ impl hal::securechip::SecureChip for BitBox03SecureChip {
todo!()
}
- fn init_new_password(
+ async fn init_new_password(
&mut self,
+ _memory: &mut impl bitbox_hal::Memory,
_password: &str,
_password_stretch_algo: bitbox_hal::memory::PasswordStretchAlgo,
) -> Result<alloc::boxed::Box<zeroize::Zeroizing<[u8; 32]>>, bitbox_hal::securechip::Error>
@@ -19,8 +20,9 @@ impl hal::securechip::SecureChip for BitBox03SecureChip {
todo!()
}
- fn stretch_password(
+ async fn stretch_password(
&mut self,
+ _memory: &mut impl bitbox_hal::Memory,
_password: &str,
_password_stretch_algo: bitbox_hal::memory::PasswordStretchAlgo,
) -> Result<alloc::boxed::Box<zeroize::Zeroizing<[u8; 32]>>, bitbox_hal::securechip::Error>
@@ -52,7 +54,7 @@ impl hal::securechip::SecureChip for BitBox03SecureChip {
todo!()
}
- fn reset_keys(&mut self) -> Result<(), ()> {
+ async fn reset_keys(&mut self, _memory: &mut impl bitbox_hal::Memory) -> Result<(), ()> {
todo!()
}
diff --git a/src/rust/util/src/lib.rs b/src/rust/util/src/lib.rs
index 2ba2a6d..597742f 100644
--- a/src/rust/util/src/lib.rs
+++ b/src/rust/util/src/lib.rs
@@ -17,7 +17,7 @@ mod waker_fn;
#[cfg(feature = "p256")]
mod p256;
#[cfg(feature = "sha2")]
-mod sha2;
+pub mod sha2;
// for `format!`
#[macro_use]
diff --git a/src/rust/util/src/sha2.rs b/src/rust/util/src/sha2.rs
index 87eeae4..00b812c 100644
--- a/src/rust/util/src/sha2.rs
+++ b/src/rust/util/src/sha2.rs
@@ -7,6 +7,32 @@ use core::ffi::{c_uchar, c_void};
use sha2::Digest;
use sha2::Sha256;
+fn sha256_result(data: &[u8]) -> [u8; 32] {
+ Sha256::digest(data).into()
+}
+
+fn hmac_sha256_result(key: &[u8], data: &[u8]) -> [u8; 32] {
+ use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine, sha256};
+
+ let mut engine = HmacEngine::<sha256::Hash>::new(key);
+ engine.input(data);
+ let hmac_result: Hmac<sha256::Hash> = Hmac::from_engine(engine);
+ hmac_result.to_byte_array()
+}
+
+pub fn sha256(data: &[u8], out: &mut [u8; 32]) {
+ out.copy_from_slice(&sha256_result(data));
+}
+
+pub fn hmac_sha256(key: &[u8], data: &[u8], out: &mut [u8; 32]) {
+ out.copy_from_slice(&hmac_sha256_result(key, data));
+}
+
+pub fn hmac_sha256_overwrite(key: &[u8], out: &mut [u8; 32]) {
+ let result = hmac_sha256_result(key, out);
+ out.copy_from_slice(&result);
+}
+
/// Result must be freed by calling `rust_sha256_finish()` or `rust_sha256_free()`.
#[unsafe(no_mangle)]
pub extern "C" fn rust_sha256_new() -> *mut c_void {
@@ -47,13 +73,12 @@ pub unsafe extern "C" fn rust_sha256_finish(ctx: *mut *mut c_void, out: *mut c_u
/// `data` must be a valid buffer for `len` bytes. `out` must be 32 bytes long.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_sha256(data: *const c_void, len: usize, out: *mut c_uchar) {
- let hash = {
+ let result = {
let data = unsafe { core::slice::from_raw_parts(data as *const u8, len) };
- Sha256::digest(data)
+ sha256_result(data)
};
-
let out = unsafe { core::slice::from_raw_parts_mut(out, 32) };
- out.copy_from_slice(&hash[..]);
+ out.copy_from_slice(&result);
}
/// # Safety
@@ -71,18 +96,11 @@ pub unsafe extern "C" fn rust_hmac_sha256(
data_len: usize,
out: *mut c_uchar,
) {
- use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine, sha256};
-
- let result: [u8; 32] = {
+ let result = {
let key = unsafe { core::slice::from_raw_parts(key as *const u8, key_len) };
let data = unsafe { core::slice::from_raw_parts(data as *const u8, data_len) };
-
- let mut engine = HmacEngine::<sha256::Hash>::new(key);
- engine.input(data);
- let hmac_result: Hmac<sha256::Hash> = Hmac::from_engine(engine);
- hmac_result.to_byte_array()
+ hmac_sha256_result(key, data)
};
-
let out = unsafe { core::slice::from_raw_parts_mut(out, 32) };
out.copy_from_slice(&result);
}
diff --git a/test/simulator-graphical-bb03/Cargo.lock b/test/simulator-graphical-bb03/Cargo.lock
index 61c4dd5..36da2df 100644
--- a/test/simulator-graphical-bb03/Cargo.lock
+++ b/test/simulator-graphical-bb03/Cargo.lock
@@ -425,6 +425,8 @@ dependencies = [
name = "bitbox-securechip"
version = "0.1.0"
dependencies = [
+ "bitbox-core-utils",
+ "bitbox-hal",
"bitbox-securechip-sys",
"critical-section",
"grounded",
@@ -3376,6 +3378,7 @@ dependencies = [
"critical-section",
"hex",
"num-bigint",
+ "sha2",
"time",
"zeroize",
]
diff --git a/test/simulator-graphical/Cargo.lock b/test/simulator-graphical/Cargo.lock
index 2bf8691..71df7c9 100644
--- a/test/simulator-graphical/Cargo.lock
+++ b/test/simulator-graphical/Cargo.lock
@@ -369,6 +369,8 @@ dependencies = [
name = "bitbox-securechip"
version = "0.1.0"
dependencies = [
+ "bitbox-core-utils",
+ "bitbox-hal",
"bitbox-securechip-sys",
"critical-section",
"grounded",
diff --git a/test/unit-test/CMakeLists.txt b/test/unit-test/CMakeLists.txt
index ae3a3ba..3e7d56c 100644
--- a/test/unit-test/CMakeLists.txt
+++ b/test/unit-test/CMakeLists.txt
@@ -35,8 +35,6 @@ else()
""
random
"-Wl,--wrap=rand,--wrap=rust_sha256"
- optiga
- "-Wl,--wrap=rust_salt_hash_data"
ui_components
""
ui_util
@@ -72,26 +70,6 @@ else()
target_include_directories(${EXE} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
- if(TEST_NAME STREQUAL "optiga")
- target_sources(${EXE} PRIVATE
- ${CMAKE_SOURCE_DIR}/src/optiga/optiga.c
- )
- # Needed by optiga-trust-m headers used by src/optiga/optiga.c.
- target_include_directories(${EXE} SYSTEM PRIVATE
- ${CMAKE_SOURCE_DIR}/external
- ${CMAKE_SOURCE_DIR}/external/optiga-trust-m/config
- ${CMAKE_SOURCE_DIR}/external/optiga-trust-m/include
- ${CMAKE_SOURCE_DIR}/external/optiga-trust-m/include/cmd
- ${CMAKE_SOURCE_DIR}/external/optiga-trust-m/include/common
- ${CMAKE_SOURCE_DIR}/external/optiga-trust-m/include/ifx_i2c
- ${CMAKE_SOURCE_DIR}/external/optiga-trust-m/include/pal
- ${CMAKE_SOURCE_DIR}/external/optiga-trust-m/include/comms
- ${CMAKE_SOURCE_DIR}/external/optiga-trust-m/external/mbedtls/include
- )
- # Optiga config must be defined both when compiling the optiga lib, and also when compiling
- # our sources.
- target_compile_definitions(${EXE} PRIVATE OPTIGA_LIB_EXTERNAL="optiga_config.h")
- endif()
add_test(NAME test_${TEST_NAME} COMMAND ${EXE})
endforeach()
endif()
diff --git a/test/unit-test/test_optiga.c b/test/unit-test/test_optiga.c
deleted file mode 100644
index a786c48..0000000
--- a/test/unit-test/test_optiga.c
+++ /dev/null
@@ -1,943 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-#include <setjmp.h>
-#include <stdarg.h>
-#include <stddef.h>
-#include <cmocka.h>
-
-#include <fake_memory.h>
-#include <memory/memory.h>
-#include <optiga/optiga.h>
-#include <optiga/optiga_ops.h>
-
-#include <common/optiga_lib_return_codes.h>
-#include <optiga_crypt.h>
-#include <optiga_util.h>
-#include <pal/pal_os_timer.h>
-#include <rust/rust.h>
-
-#include <stdint.h>
-#include <string.h>
-
-//------------------------------------------------------------------------------
-// Fixed test vectors / keys (deterministic fakes).
-
-static const uint8_t _salt_root_fixed[32] = {
- 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
- 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
-};
-
-static const uint8_t _password_secret_fixed[32] = {
- 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
- 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
-};
-
-static const uint8_t _kdf_cmac_key_fixed[32] = {
- 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
- 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
-};
-
-static const uint8_t _kdf_hmac_key_fixed[32] = {
- 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0,
- 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0,
-};
-
-static const uint8_t _kdf_hmac_writeprotected_key_fixed[32] = {
- 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,
- 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,
-};
-
-static const uint8_t _auth_code_random_fixed[32] = {
- 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
- 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77,
-};
-
-// Expected stretched_out for password "pw" for the V0 algorithm given the above fakes.
-//
-// Repro script (mirrors optiga_stretch_password() with the unit test fakes):
-// ```python
-// import hashlib, hmac
-//
-// def sha256(b: bytes) -> bytes:
-// return hashlib.sha256(b).digest()
-//
-// def hmac_sha256(key: bytes, msg: bytes) -> bytes:
-// return hmac.new(key, msg, hashlib.sha256).digest()
-//
-// def salt_hash_data(data: bytes, purpose: bytes, salt_root: bytes) -> bytes:
-// return sha256(salt_root + purpose + data)
-//
-// def kdf_internal(msg: bytes, cmac_key: bytes) -> bytes:
-// # optiga_ops_crypt_symmetric_encrypt_sync fake: HMAC-SHA256(cmac_key, msg)[:16]
-// return sha256(hmac_sha256(cmac_key, msg)[:16])
-//
-// def kdf_hmac(msg: bytes, hmac_key: bytes) -> bytes:
-// # optiga_ops_crypt_hmac_sync fake: HMAC-SHA256(hmac_key, msg)
-// return hmac_sha256(hmac_key, msg)
-//
-// salt_root = bytes([0x42]) * 32
-// cmac_key = bytes([0xA0]) * 32
-// hmac_key = bytes([0xB0]) * 32
-// password_secret = bytes([0x99]) * 32
-// password = b"pw"
-//
-// kdf_in = salt_hash_data(password, b"optiga_password_stretch_in", salt_root)
-// stretched = kdf_internal(kdf_in, cmac_key)
-// for _ in range(2):
-// stretched = kdf_hmac(stretched, hmac_key)
-// stretched = hmac_sha256(password_secret, stretched)
-// out_salt = salt_hash_data(password, b"optiga_password_stretch_out", salt_root)
-// stretched = hmac_sha256(out_salt, stretched)
-// print(stretched.hex())
-// ```
-static const uint8_t _expected_stretched_out_v0[32] = {
- 0xC4, 0x1F, 0x87, 0xB7, 0xC9, 0xF3, 0x16, 0x9C, 0x14, 0xF3, 0xF2, 0x62, 0x87, 0x09, 0x3C, 0x31,
- 0x18, 0x19, 0x06, 0x77, 0x76, 0xF6, 0x16, 0x3B, 0x8A, 0x0F, 0xDF, 0x3D, 0xFB, 0x8B, 0x8E, 0xBB,
-};
-
-// Expected stretched_out for password "pw" for the V1 algorithm given the above fakes.
-//
-// Repro script (mirrors optiga_stretch_password() with the unit test fakes):
-// ```python
-// import hashlib, hmac
-//
-// def sha256(b: bytes) -> bytes:
-// return hashlib.sha256(b).digest()
-//
-// def hmac_sha256(key: bytes, msg: bytes) -> bytes:
-// return hmac.new(key, msg, hashlib.sha256).digest()
-//
-// def salt_hash_data(data: bytes, purpose: bytes, salt_root: bytes) -> bytes:
-// return sha256(salt_root + purpose + data)
-//
-// def kdf_internal(msg: bytes, cmac_key: bytes) -> bytes:
-// # optiga_ops_crypt_symmetric_encrypt_sync fake: HMAC-SHA256(cmac_key, msg)[:16]
-// return sha256(hmac_sha256(cmac_key, msg)[:16])
-//
-// def kdf_hmac(msg: bytes, hmac_key: bytes) -> bytes:
-// # optiga_ops_crypt_hmac_sync fake: HMAC-SHA256(hmac_key, msg)
-// return hmac_sha256(hmac_key, msg)
-//
-// salt_root = bytes([0x42]) * 32
-// cmac_key = bytes([0xA0]) * 32
-// hmac_writeprotected_key = bytes([0xC0]) * 32
-// password_secret = bytes([0x99]) * 32
-// password = b"pw"
-//
-// kdf_in = salt_hash_data(password, b"optiga_password_stretch_in", salt_root)
-// stretched = kdf_internal(kdf_in, cmac_key)
-// stretched = kdf_hmac(stretched, hmac_writeprotected_key)
-// stretched = hmac_sha256(password_secret, stretched)
-// out_salt = salt_hash_data(password, b"optiga_password_stretch_out", salt_root)
-// stretched = hmac_sha256(out_salt, stretched)
-// print(stretched.hex())
-// ```
-static const uint8_t _expected_stretched_out_v1[32] = {
- 0XC5, 0X9E, 0XC3, 0XC3, 0XB1, 0XC4, 0X5F, 0X7E, 0X76, 0X39, 0XA6, 0X29, 0XF5, 0XB3, 0X4D, 0X1E,
- 0X4D, 0XC5, 0X08, 0XF3, 0XB5, 0XB9, 0X57, 0X7D, 0XD9, 0XDD, 0X57, 0XEE, 0XCF, 0X49, 0X67, 0X51,
-};
-
-static uint8_t _hmac_writeprotected_metadata[METADATA_MAX_SIZE] = {0};
-static uint16_t _hmac_writeprotected_metadata_len = 0;
-static uint8_t _counter_hmac_writeprotected_metadata[METADATA_MAX_SIZE] = {0};
-static uint16_t _counter_hmac_writeprotected_metadata_len = 0;
-
-//------------------------------------------------------------------------------
-// Minimal securechip interface fakes.
-
-static void _dummy_get_key(uint8_t* key_out)
-{
- memset(key_out, 0, 32);
-}
-
-static void _mock_random_32_bytes(uint8_t* buf)
-{
- // There are only three calls to this at the moment, all in init_new_password.
- static int mock_random_ctr = 0;
- if (mock_random_ctr == 0) {
- memcpy(buf, _kdf_hmac_key_fixed, 32);
- } else if (mock_random_ctr == 1) {
- memcpy(buf, _password_secret_fixed, 32);
- } else if (mock_random_ctr == 2) {
- memcpy(buf, _kdf_hmac_writeprotected_key_fixed, 32);
- }
- mock_random_ctr = (mock_random_ctr + 1) % 3;
-}
-
-static const securechip_interface_functions_t _ifs = {
- .get_auth_key = _dummy_get_key,
- .get_io_protection_key = _dummy_get_key,
- .get_encryption_key = _dummy_get_key,
- .random_32_bytes = _mock_random_32_bytes,
-};
-
-//------------------------------------------------------------------------------
-// Linker-wrapped rust_salt_hash_data: same as salt.rs, but with a fixed salt_root.
-
-bool __wrap_rust_salt_hash_data(struct Bytes data, const char* purpose, struct BytesMut hash_out)
-{
- void* ctx = rust_sha256_new();
- if (ctx == NULL) {
- return false;
- }
- rust_sha256_update(ctx, _salt_root_fixed, sizeof(_salt_root_fixed));
- rust_sha256_update(ctx, purpose, strlen(purpose));
- rust_sha256_update(ctx, data.buf, data.len);
- rust_sha256_finish(&ctx, hash_out.buf);
- return true;
-}
-
-//------------------------------------------------------------------------------
-// Minimal OPTIGA + PAL stubs required by optiga.c during setup.
-
-static optiga_util_t _fake_util;
-static optiga_crypt_t _fake_crypt;
-
-static uint8_t _oid_password[32];
-static bool _oid_password_set;
-
-static uint8_t _oid_counter_password_buf[8];
-static uint8_t _oid_counter_hmac_writeprotected_buf[8];
-
-static bool _authorized_password;
-static bool _authorized_password_secret;
-
-static void _setup_test(void)
-{
- fake_memory_factoryreset();
- memory_optiga_config_version_t config_version;
- assert_true(memory_get_optiga_config_version(&config_version));
- assert_int_equal(config_version, MEMORY_OPTIGA_CONFIG_V0);
-
- memset(_oid_password, 0, sizeof(_oid_password));
- _oid_password_set = false;
- _authorized_password = false;
- _authorized_password_secret = false;
-
- // Initial metadata mock is minimal and only contains the LCSO state, as we read that out first
- // to determine if these slot needs to be configured.
- uint8_t metadata[5] = {
- // Metadata tag in the data object
- 0x20,
- // Number of bytes that follow
- 0x03,
- 0xC0,
- 0x01,
- // Forces a config update, as it is not OPERATIONAL.
- LCSO_STATE_CREATION,
- };
- memcpy(_hmac_writeprotected_metadata, metadata, sizeof(metadata));
- _hmac_writeprotected_metadata_len = sizeof(metadata);
- memcpy(_counter_hmac_writeprotected_metadata, metadata, sizeof(metadata));
- _counter_hmac_writeprotected_metadata_len = sizeof(metadata);
-
- assert_int_equal(optiga_setup(&_ifs), 0);
- // After setup, the config is updated.
- assert_true(memory_get_optiga_config_version(&config_version));
- assert_int_equal(config_version, MEMORY_OPTIGA_CONFIG_V1);
-}
-
-static uint32_t _get_counter(uint16_t oid)
-{
- switch (oid) {
- case OID_COUNTER_PASSWORD:
- return optiga_common_get_uint32(&_oid_counter_password_buf[0]);
- case OID_COUNTER_HMAC_WRITEPROTECTED:
- return optiga_common_get_uint32(&_oid_counter_hmac_writeprotected_buf[0]);
- default:
- fail();
- return 0;
- }
-}
-
-static uint32_t _get_threshold(uint16_t oid)
-{
- switch (oid) {
- case OID_COUNTER_PASSWORD:
- return optiga_common_get_uint32(&_oid_counter_password_buf[4]);
- case OID_COUNTER_HMAC_WRITEPROTECTED:
- return optiga_common_get_uint32(&_oid_counter_hmac_writeprotected_buf[4]);
- default:
- fail();
- return 0;
- }
-}
-
-pal_status_t pal_timer_init(void)
-{
- return PAL_STATUS_SUCCESS;
-}
-
-uint32_t optiga_common_get_uint32(const uint8_t* p_input_buffer)
-{
- return ((uint32_t)p_input_buffer[0] << 24) | ((uint32_t)p_input_buffer[1] << 16) |
- ((uint32_t)p_input_buffer[2] << 8) | (uint32_t)p_input_buffer[3];
-}
-
-void optiga_common_set_uint32(uint8_t* p_output_buffer, uint32_t four_byte_value)
-{
- p_output_buffer[0] = (uint8_t)(four_byte_value >> 24);
- p_output_buffer[1] = (uint8_t)(four_byte_value >> 16);
- p_output_buffer[2] = (uint8_t)(four_byte_value >> 8);
- p_output_buffer[3] = (uint8_t)(four_byte_value);
-}
-
-void optiga_util_set_comms_params(optiga_util_t* me, uint8_t parameter_type, uint8_t value)
-{
-#ifdef OPTIGA_COMMS_SHIELDED_CONNECTION
- if (parameter_type == OPTIGA_COMMS_PROTECTION_LEVEL) {
- me->protection_level = value;
- } else if (parameter_type == OPTIGA_COMMS_PROTOCOL_VERSION) {
- me->protocol_version = value;
- }
-#else
- (void)me;
- (void)parameter_type;
- (void)value;
-#endif
-}
-
-void optiga_crypt_set_comms_params(optiga_crypt_t* me, uint8_t parameter_type, uint8_t value)
-{
-#ifdef OPTIGA_COMMS_SHIELDED_CONNECTION
- if (parameter_type == OPTIGA_COMMS_PROTECTION_LEVEL) {
- me->protection_level = value;
- } else if (parameter_type == OPTIGA_COMMS_PROTOCOL_VERSION) {
- me->protocol_version = value;
- }
-#else
- (void)me;
- (void)parameter_type;
- (void)value;
-#endif
-}
-
-//------------------------------------------------------------------------------
-// Fake optiga_ops API surface (unit-test seam).
-
-optiga_lib_status_t optiga_ops_create(optiga_util_t** util_out, optiga_crypt_t** crypt_out)
-{
- // Handler/callback not currently needed in tests, so it is set to NULL.
-
- memset(&_fake_util, 0, sizeof(_fake_util));
- _fake_util.caller_context = OPTIGA_INSTANCE_ID_0;
- _fake_util.handler = NULL;
- _fake_util.instance_state = 0;
-#ifdef OPTIGA_COMMS_SHIELDED_CONNECTION
- _fake_util.protection_level = OPTIGA_COMMS_FULL_PROTECTION;
- _fake_util.protocol_version = OPTIGA_COMMS_PROTOCOL_VERSION_PRE_SHARED_SECRET;
-#endif
- *util_out = &_fake_util;
-
- memset(&_fake_crypt, 0, sizeof(_fake_crypt));
- _fake_crypt.my_cmd = NULL;
- _fake_crypt.caller_context = OPTIGA_INSTANCE_ID_0;
- _fake_crypt.handler = NULL;
- _fake_crypt.instance_state = 0;
-#ifdef OPTIGA_COMMS_SHIELDED_CONNECTION
- _fake_crypt.protection_level = OPTIGA_COMMS_FULL_PROTECTION;
- _fake_crypt.protocol_version = OPTIGA_COMMS_PROTOCOL_VERSION_PRE_SHARED_SECRET;
-#endif
- *crypt_out = &_fake_crypt;
-
- return 0;
-}
-
-optiga_lib_status_t optiga_ops_util_open_application_sync(optiga_util_t* me, bool_t perform_restore)
-{
- (void)me;
- (void)perform_restore;
- return OPTIGA_LIB_SUCCESS;
-}
-
-optiga_lib_status_t optiga_ops_util_close_application_sync(
- optiga_util_t* me,
- bool_t perform_hibernate)
-{
- (void)me;
- (void)perform_hibernate;
- return OPTIGA_UTIL_ERROR;
-}
-
-optiga_lib_status_t optiga_ops_util_read_metadata_sync(
- optiga_util_t* me,
- uint16_t optiga_oid,
- uint8_t* buffer,
- uint16_t* length)
-{
- (void)me;
- switch (optiga_oid) {
- case OID_HMAC_WRITEPROTECTED:
- memcpy(buffer, _hmac_writeprotected_metadata, _hmac_writeprotected_metadata_len);
- *length = _hmac_writeprotected_metadata_len;
- return OPTIGA_UTIL_SUCCESS;
- case OID_COUNTER_HMAC_WRITEPROTECTED:
- memcpy(
- buffer,
- _counter_hmac_writeprotected_metadata,
- _counter_hmac_writeprotected_metadata_len);
- *length = _counter_hmac_writeprotected_metadata_len;
- return OPTIGA_UTIL_SUCCESS;
- default:
- return OPTIGA_UTIL_ERROR;
- }
-}
-
-optiga_lib_status_t optiga_ops_util_write_metadata_sync(
- optiga_util_t* me,
- uint16_t optiga_oid,
- const uint8_t* buffer,
- uint8_t length)
-{
- (void)me;
- switch (optiga_oid) {
- case OID_HMAC_WRITEPROTECTED:
- memcpy(_hmac_writeprotected_metadata, buffer, length);
- _hmac_writeprotected_metadata_len = length;
- return OPTIGA_UTIL_SUCCESS;
- case OID_COUNTER_HMAC_WRITEPROTECTED:
- memcpy(_counter_hmac_writeprotected_metadata, buffer, length);
- _counter_hmac_writeprotected_metadata_len = length;
- return OPTIGA_UTIL_SUCCESS;
- default:
- return OPTIGA_UTIL_ERROR;
- }
-}
-
-optiga_lib_status_t optiga_ops_util_read_data_sync(
- optiga_util_t* me,
- uint16_t optiga_oid,
- uint16_t offset,
- uint8_t* buffer,
- uint16_t* length)
-{
- (void)me;
- (void)offset;
-
- if (optiga_oid == OID_PASSWORD_SECRET) {
- if (!_authorized_password) {
- return OPTIGA_UTIL_ERROR;
- }
- if (*length < 32) {
- return OPTIGA_UTIL_ERROR_MEMORY_INSUFFICIENT;
- }
- memcpy(buffer, _password_secret_fixed, 32);
- *length = 32;
- return OPTIGA_UTIL_SUCCESS;
- }
- if (optiga_oid == OID_COUNTER) {
- if (*length < 4) {
- return OPTIGA_UTIL_ERROR_MEMORY_INSUFFICIENT;
- }
- memset(buffer, 0, 4);
- *length = 4;
- return OPTIGA_UTIL_SUCCESS;
- }
- if (optiga_oid == OID_ARBITRARY_DATA) {
- memset(buffer, 0, *length);
- return OPTIGA_UTIL_SUCCESS;
- }
-
- return OPTIGA_UTIL_ERROR_INVALID_INPUT;
-}
-
-optiga_lib_status_t optiga_ops_util_write_data_sync(
- optiga_util_t* me,
- uint16_t optiga_oid,
- uint8_t write_type,
- uint16_t offset,
- const uint8_t* buffer,
- uint16_t length)
-{
- (void)me;
- (void)write_type;
- (void)offset;
-
- if (optiga_oid == OID_PASSWORD) {
- if (!_authorized_password_secret) {
- return OPTIGA_UTIL_ERROR;
- }
- if (length != 32) {
- return OPTIGA_UTIL_ERROR_INVALID_INPUT;
- }
- memcpy(_oid_password, buffer, 32);
- _oid_password_set = true;
- return OPTIGA_UTIL_SUCCESS;
- }
- if (optiga_oid == OID_COUNTER_PASSWORD) {
- if (length != sizeof(_oid_counter_password_buf)) {
- return OPTIGA_UTIL_ERROR_INVALID_INPUT;
- }
- memcpy(_oid_counter_password_buf, buffer, sizeof(_oid_counter_password_buf));
- return OPTIGA_UTIL_SUCCESS;
- }
- if (optiga_oid == OID_HMAC) {
- if (length != 32) {
- return OPTIGA_UTIL_ERROR_INVALID_INPUT;
- }
- assert_memory_equal(buffer, _kdf_hmac_key_fixed, 32);
- return OPTIGA_UTIL_SUCCESS;
- }
- if (optiga_oid == OID_HMAC_WRITEPROTECTED) {
- if (!_authorized_password) {
- return OPTIGA_UTIL_ERROR;
- }
- if (length != 32) {
- return OPTIGA_UTIL_ERROR_INVALID_INPUT;
- }
- assert_memory_equal(buffer, _kdf_hmac_writeprotected_key_fixed, 32);
- return OPTIGA_UTIL_SUCCESS;
- }
- if (optiga_oid == OID_COUNTER_HMAC_WRITEPROTECTED) {
- if (!_authorized_password) {
- return OPTIGA_UTIL_ERROR;
- }
- if (length != sizeof(_oid_counter_hmac_writeprotected_buf)) {
- return OPTIGA_UTIL_ERROR_INVALID_INPUT;
- }
- memcpy(
- _oid_counter_hmac_writeprotected_buf,
- buffer,
- sizeof(_oid_counter_hmac_writeprotected_buf));
- return OPTIGA_UTIL_SUCCESS;
- }
- // Accept other writes without emulating full semantics (counter reset, hmac key, etc.).
- (void)buffer;
- (void)length;
- return OPTIGA_UTIL_SUCCESS;
-}
-
-optiga_lib_status_t optiga_ops_crypt_generate_auth_code_sync(
- optiga_crypt_t* me,
- optiga_rng_type_t rng_type,
- const uint8_t* optional_data,
- uint16_t optional_data_length,
- uint8_t* random_data,
- uint16_t random_data_length)
-{
- (void)me;
- (void)rng_type;
- (void)optional_data;
- (void)optional_data_length;
- if (random_data_length != sizeof(_auth_code_random_fixed)) {
- return OPTIGA_CRYPT_ERROR_INVALID_INPUT;
- }
- memcpy(random_data, _auth_code_random_fixed, sizeof(_auth_code_random_fixed));
- return OPTIGA_CRYPT_SUCCESS;
-}
-
-optiga_lib_status_t optiga_ops_crypt_hmac_verify_sync(
- optiga_crypt_t* me,
- optiga_hmac_type_t type,
- uint16_t secret,
- const uint8_t* input_data,
- uint32_t input_data_length,
- const uint8_t* hmac,
- uint32_t hmac_length)
-{
- (void)me;
- (void)type;
-
- if (hmac_length != 32 || input_data_length != 32) {
- return OPTIGA_CRYPT_ERROR_INVALID_INPUT;
- }
-
- const uint8_t* key = NULL;
- uintptr_t key_len = 0;
- if (secret == OID_PASSWORD_SECRET) {
- key = _password_secret_fixed;
- key_len = sizeof(_password_secret_fixed);
- } else if (secret == OID_PASSWORD) {
- // Emulate the small monotonic counter that is attached to password authorization.
- // Stored as {counter_be_u32, threshold_be_u32}.
- uint32_t counter = _get_counter(OID_COUNTER_PASSWORD);
- uint32_t threshold = _get_threshold(OID_COUNTER_PASSWORD);
- if (counter >= threshold) {
- return 0x802F;
- }
- counter++;
- optiga_common_set_uint32(&_oid_counter_password_buf[0], counter);
-
- if (!_oid_password_set) {
- return OPTIGA_CRYPT_ERROR;
- }
- key = _oid_password;
- key_len = sizeof(_oid_password);
- } else {
- return OPTIGA_CRYPT_ERROR_INVALID_INPUT;
- }
-
- uint8_t computed[32] = {0};
- rust_hmac_sha256(key, key_len, input_data, input_data_length, computed);
- if (memcmp(computed, hmac, 32) != 0) {
- return 0x802F;
- }
-
- if (secret == OID_PASSWORD) {
- _authorized_password = true;
- } else if (secret == OID_PASSWORD_SECRET) {
- _authorized_password_secret = true;
- }
- return OPTIGA_CRYPT_SUCCESS;
-}
-
-optiga_lib_status_t optiga_ops_crypt_clear_auto_state_sync(optiga_crypt_t* me, uint16_t secret)
-{
- (void)me;
- if (secret == OID_PASSWORD) {
- _authorized_password = false;
- } else if (secret == OID_PASSWORD_SECRET) {
- _authorized_password_secret = false;
- }
- return OPTIGA_CRYPT_SUCCESS;
-}
-
-optiga_lib_status_t optiga_ops_crypt_symmetric_generate_key_sync(
- optiga_crypt_t* me,
- optiga_symmetric_key_type_t key_type,
- uint8_t key_usage,
- bool_t export_symmetric_key,
- void* symmetric_key)
-{
- (void)me;
- (void)key_type;
- (void)key_usage;
- (void)export_symmetric_key;
- assert_int_equal(*(optiga_key_id_t*)symmetric_key, OID_AES_SYMKEY);
-
- // We keep using the fixed cmac key in the tests.
- return OPTIGA_CRYPT_SUCCESS;
-}
-
-optiga_lib_status_t optiga_ops_crypt_random_sync(
- optiga_crypt_t* me,
- optiga_rng_type_t rng_type,
- uint8_t* random_data, // NOLINT(readability-non-const-parameter)
- uint16_t random_data_length)
-{
- (void)me;
- (void)rng_type;
- (void)random_data;
- (void)random_data_length;
- return OPTIGA_CRYPT_ERROR;
-}
-
-// Use rust_hmac_sha256 with a fixed key and msg as the value, truncated to 16 bytes.
-optiga_lib_status_t optiga_ops_crypt_symmetric_encrypt_sync(
- optiga_crypt_t* me,
- optiga_symmetric_encryption_mode_t encryption_mode,
- optiga_key_id_t symmetric_key_oid,
- const uint8_t* plain_data,
- uint32_t plain_data_length,
- const uint8_t* iv,
- uint16_t iv_length,
- const uint8_t* associated_data,
- uint16_t associated_data_length,
- uint8_t* encrypted_data,
- uint32_t* encrypted_data_length)
-{
- (void)me;
- (void)encryption_mode;
- assert_int_equal(symmetric_key_oid, OID_AES_SYMKEY);
- (void)iv;
- (void)iv_length;
- (void)associated_data;
- (void)associated_data_length;
-
- uint8_t out[32] = {0};
- rust_hmac_sha256(
- _kdf_cmac_key_fixed, sizeof(_kdf_cmac_key_fixed), plain_data, plain_data_length, out);
-
- if (*encrypted_data_length < 16) {
- return OPTIGA_CRYPT_ERROR_MEMORY_INSUFFICIENT;
- }
- memcpy(encrypted_data, out, 16);
- *encrypted_data_length = 16;
- return OPTIGA_CRYPT_SUCCESS;
-}
-
-// Use rust_hmac_sha256 with a different fixed key and msg as the value.
-optiga_lib_status_t optiga_ops_crypt_hmac_sync(
- optiga_crypt_t* me,
- optiga_hmac_type_t type,
- uint16_t secret,
- const uint8_t* input_data,
- uint32_t input_data_length,
- uint8_t* mac,
- uint32_t* mac_length)
-{
- (void)me;
- (void)type;
- const uint8_t* key;
- switch (secret) {
- case OID_HMAC:
- key = _kdf_hmac_key_fixed;
- break;
- case OID_HMAC_WRITEPROTECTED:
- key = _kdf_hmac_writeprotected_key_fixed;
-
- // Emulate the small monotonic counter that is attached to using the hmac_writeprotected
- // slot. Stored as {counter_be_u32, threshold_be_u32}.
- uint32_t counter = _get_counter(OID_COUNTER_HMAC_WRITEPROTECTED);
- uint32_t threshold = _get_threshold(OID_COUNTER_HMAC_WRITEPROTECTED);
- if (counter >= threshold) {
- return 0x802F;
- }
- counter++;
- optiga_common_set_uint32(&_oid_counter_hmac_writeprotected_buf[0], counter);
- break;
- default:
- fail_msg("unexpected slot id");
- return OPTIGA_CRYPT_ERROR;
- }
- if (*mac_length < 32) {
- return OPTIGA_CRYPT_ERROR_MEMORY_INSUFFICIENT;
- }
- rust_hmac_sha256(key, 32, input_data, input_data_length, mac);
- *mac_length = 32;
- return OPTIGA_CRYPT_SUCCESS;
-}
-
-// Unused by these tests, but required to satisfy optiga.c link dependencies.
-optiga_lib_status_t optiga_ops_crypt_ecc_generate_keypair_sync(
- optiga_crypt_t* me,
- optiga_ecc_curve_t curve_id,
- uint8_t key_usage,
- bool_t export_private_key,
- void* private_key,
- uint8_t* public_key, // NOLINT(readability-non-const-parameter)
- uint16_t* public_key_length) // NOLINT(readability-non-const-parameter)
-{
- (void)me;
- (void)curve_id;
- (void)key_usage;
- (void)export_private_key;
- (void)private_key;
- (void)public_key;
- (void)public_key_length;
- return OPTIGA_CRYPT_ERROR;
-}
-
-optiga_lib_status_t optiga_ops_crypt_ecdsa_sign_sync(
- optiga_crypt_t* me,
- const uint8_t* digest,
- uint8_t digest_length,
- optiga_key_id_t private_key,
- uint8_t* signature, // NOLINT(readability-non-const-parameter)
- uint16_t* signature_length) // NOLINT(readability-non-const-parameter)
-{
- (void)me;
- (void)digest;
- (void)digest_length;
- (void)private_key;
- (void)signature;
- (void)signature_length;
- return OPTIGA_CRYPT_ERROR;
-}
-
-//------------------------------------------------------------------------------
-// Tests
-
-static void test_optiga_stretch_password_v0_success(void** state)
-{
- (void)state;
- _setup_test();
-
- // Seed the OID_PASSWORD and OID_PASSWORD_COUNTER objects as if they were provisioned earlier.
- assert_true(rust_salt_hash_data(
- rust_util_bytes((const uint8_t*)"pw", 2),
- "optiga_password",
- rust_util_bytes_mut(_oid_password, sizeof(_oid_password))));
- _oid_password_set = true;
- const uint8_t counter_reset_buf[8] = {0, 0, 0, 0, 0, 0, 0, SMALL_MONOTONIC_COUNTER_MAX_USE};
- memcpy(_oid_counter_password_buf, counter_reset_buf, sizeof(_oid_counter_password_buf));
-
- uint8_t stretched_out[32] = {0};
- assert_int_equal(
- optiga_stretch_password("pw", SECURECHIP_PASSWORD_STRETCH_ALGO_V0, stretched_out), 0);
- assert_memory_equal(
- stretched_out, _expected_stretched_out_v0, sizeof(_expected_stretched_out_v0));
- // Successful password verification resets the small monotonic counter/threshold.
- assert_int_equal(_get_counter(OID_COUNTER_PASSWORD), 0);
- assert_int_equal(_get_threshold(OID_COUNTER_PASSWORD), SMALL_MONOTONIC_COUNTER_MAX_USE);
-}
-
-static void test_optiga_stretch_password_v0_attempt_counter(void** state)
-{
- (void)state;
- _setup_test();
-
- // Seed the OID_PASSWORD and OID_PASSWORD_COUNTER objects as if they were provisioned earlier.
- assert_true(rust_salt_hash_data(
- rust_util_bytes((const uint8_t*)"pw", 2),
- "optiga_password",
- rust_util_bytes_mut(_oid_password, sizeof(_oid_password))));
- _oid_password_set = true;
- const uint8_t counter_reset_buf[8] = {0, 0, 0, 0, 0, 0, 0, SMALL_MONOTONIC_COUNTER_MAX_USE};
- memcpy(_oid_counter_password_buf, counter_reset_buf, sizeof(_oid_counter_password_buf));
-
- uint8_t stretched_out[32] = {0};
-
- assert_int_equal(
- optiga_stretch_password("wrong", SECURECHIP_PASSWORD_STRETCH_ALGO_V0, stretched_out),
- SC_ERR_INCORRECT_PASSWORD);
- assert_int_equal(_get_counter(OID_COUNTER_PASSWORD), 1);
- assert_int_equal(_get_threshold(OID_COUNTER_PASSWORD), SMALL_MONOTONIC_COUNTER_MAX_USE);
-
- assert_int_equal(
- optiga_stretch_password("wrong", SECURECHIP_PASSWORD_STRETCH_ALGO_V0, stretched_out),
- SC_ERR_INCORRECT_PASSWORD);
- assert_int_equal(_get_counter(OID_COUNTER_PASSWORD), 2);
- assert_int_equal(_get_threshold(OID_COUNTER_PASSWORD), SMALL_MONOTONIC_COUNTER_MAX_USE);
-
- assert_int_equal(
- optiga_stretch_password("pw", SECURECHIP_PASSWORD_STRETCH_ALGO_V0, stretched_out), 0);
- assert_int_equal(_get_counter(OID_COUNTER_PASSWORD), 0);
- assert_int_equal(_get_threshold(OID_COUNTER_PASSWORD), SMALL_MONOTONIC_COUNTER_MAX_USE);
-
- for (int i = 0; i < SMALL_MONOTONIC_COUNTER_MAX_USE; i++) {
- assert_int_equal(
- optiga_stretch_password("wrong", SECURECHIP_PASSWORD_STRETCH_ALGO_V0, stretched_out),
- SC_ERR_INCORRECT_PASSWORD);
- }
- assert_int_equal(
- optiga_common_get_uint32(&_oid_counter_password_buf[0]), SMALL_MONOTONIC_COUNTER_MAX_USE);
-
- // After exhausting all allowed attempts, a correct password fails as well.
- assert_int_equal(
- optiga_stretch_password("pw", SECURECHIP_PASSWORD_STRETCH_ALGO_V0, stretched_out),
- SC_ERR_INCORRECT_PASSWORD);
- assert_int_equal(
- optiga_common_get_uint32(&_oid_counter_password_buf[0]), SMALL_MONOTONIC_COUNTER_MAX_USE);
-}
-
-// Test that after initializing a new password, exhausting all allowed attempts locks means a
-// correct password fails as well.
-// Attempts after init are special because the PASSWORD_COUNTER init/threshold are offset by 1.
-static void test_optiga_password_v1_stretch_exhaust_fails_after_init(void** state)
-{
- (void)state;
- _setup_test();
-
- uint8_t stretched[32] = {0};
- assert_int_equal(
- optiga_init_new_password("pw", SECURECHIP_PASSWORD_STRETCH_ALGO_V1, stretched), 0);
- assert_memory_equal(stretched, _expected_stretched_out_v1, sizeof(_expected_stretched_out_v1));
-
- // Counter & threshold of password counter. After init, it is at 1, but the threshold is
- // increased by 1, so the number of attempts is still 10.
- assert_int_equal(_get_counter(OID_COUNTER_PASSWORD), 1);
- assert_int_equal(_get_threshold(OID_COUNTER_PASSWORD), SMALL_MONOTONIC_COUNTER_MAX_USE + 1);
- // Counter & threshold of hmac_writeprotected counter.
- assert_int_equal(_get_counter(OID_COUNTER_HMAC_WRITEPROTECTED), 0);
- assert_int_equal(
- _get_threshold(OID_COUNTER_HMAC_WRITEPROTECTED), SMALL_MONOTONIC_COUNTER_MAX_USE);
-
- // Exhaust all attempts.
- for (int i = 1; i <= SMALL_MONOTONIC_COUNTER_MAX_USE; i++) {
- assert_int_equal(
- optiga_stretch_password("wrong", SECURECHIP_PASSWORD_STRETCH_ALGO_V1, stretched),
- SC_ERR_INCORRECT_PASSWORD);
-
- // Counter & threshold of password counter.
- assert_int_equal(_get_counter(OID_COUNTER_PASSWORD), 1 + i);
- assert_int_equal(_get_threshold(OID_COUNTER_PASSWORD), SMALL_MONOTONIC_COUNTER_MAX_USE + 1);
- // Counter & threshold of hmac_writeprotected counter.
- assert_int_equal(_get_counter(OID_COUNTER_HMAC_WRITEPROTECTED), i);
- assert_int_equal(
- _get_threshold(OID_COUNTER_HMAC_WRITEPROTECTED), SMALL_MONOTONIC_COUNTER_MAX_USE);
- }
-
- // Even a correct password doesn't work.
- memset(stretched, 0x00, sizeof(stretched));
- assert_int_equal(
- optiga_stretch_password("pw", SECURECHIP_PASSWORD_STRETCH_ALGO_V1, stretched),
- SC_ERR_INCORRECT_PASSWORD);
- uint8_t zero[32] = {0};
- assert_memory_equal(stretched, zero, sizeof(stretched));
-}
-
-// Test that after initializing a new password, one can make a few failed stretch attempts, and that
-// doing a correct attempt resets the counters.
-static void test_optiga_password_v1(void** state)
-{
- (void)state;
- _setup_test();
-
- uint8_t stretched[32] = {0};
- assert_int_equal(
- optiga_init_new_password("pw", SECURECHIP_PASSWORD_STRETCH_ALGO_V1, stretched), 0);
- assert_memory_equal(stretched, _expected_stretched_out_v1, sizeof(_expected_stretched_out_v1));
-
- // Counter & threshold of password counter. After init, it is at 1, but the threshold is
- // increased by 1, so the number of attempts is still 10.
- assert_int_equal(_get_counter(OID_COUNTER_PASSWORD), 1);
- assert_int_equal(_get_threshold(OID_COUNTER_PASSWORD), SMALL_MONOTONIC_COUNTER_MAX_USE + 1);
- // Counter & threshold of hmac_writeprotected counter.
- assert_int_equal(_get_counter(OID_COUNTER_HMAC_WRITEPROTECTED), 0);
- assert_int_equal(
- _get_threshold(OID_COUNTER_HMAC_WRITEPROTECTED), SMALL_MONOTONIC_COUNTER_MAX_USE);
-
- // A few failed attempts:
- for (int i = 1; i <= 2; i++) {
- assert_int_equal(
- optiga_stretch_password("wrong", SECURECHIP_PASSWORD_STRETCH_ALGO_V1, stretched),
- SC_ERR_INCORRECT_PASSWORD);
-
- // Counter & threshold of password counter.
- assert_int_equal(_get_counter(OID_COUNTER_PASSWORD), 1 + i);
- assert_int_equal(_get_threshold(OID_COUNTER_PASSWORD), SMALL_MONOTONIC_COUNTER_MAX_USE + 1);
- // Counter & threshold of hmac_writeprotected counter.
- assert_int_equal(_get_counter(OID_COUNTER_HMAC_WRITEPROTECTED), i);
- assert_int_equal(
- _get_threshold(OID_COUNTER_HMAC_WRITEPROTECTED), SMALL_MONOTONIC_COUNTER_MAX_USE);
- }
-
- // Correct attempt gets the right stretched value and resets counters.
- memset(stretched, 0x00, sizeof(stretched));
- assert_int_equal(
- optiga_stretch_password("pw", SECURECHIP_PASSWORD_STRETCH_ALGO_V1, stretched), 0);
- assert_memory_equal(stretched, _expected_stretched_out_v1, sizeof(_expected_stretched_out_v1));
- // Counter & threshold of password counter.
- assert_int_equal(_get_counter(OID_COUNTER_PASSWORD), 0);
- assert_int_equal(_get_threshold(OID_COUNTER_PASSWORD), SMALL_MONOTONIC_COUNTER_MAX_USE);
- // Counter & threshold of hmac_writeprotected counter.
- assert_int_equal(_get_counter(OID_COUNTER_HMAC_WRITEPROTECTED), 0);
- assert_int_equal(
- _get_threshold(OID_COUNTER_HMAC_WRITEPROTECTED), SMALL_MONOTONIC_COUNTER_MAX_USE);
-
- // Exhaust all attempts. The PASSWORD_COUNTER during this is different to above as the
- // counter/threshold was reset to 0/MAX after the correct stretch attempt.
- for (int i = 1; i <= SMALL_MONOTONIC_COUNTER_MAX_USE; i++) {
- assert_int_equal(
- optiga_stretch_password("wrong", SECURECHIP_PASSWORD_STRETCH_ALGO_V1, stretched),
- SC_ERR_INCORRECT_PASSWORD);
-
- // Counter & threshold of password counter.
- assert_int_equal(_get_counter(OID_COUNTER_PASSWORD), i);
- assert_int_equal(_get_threshold(OID_COUNTER_PASSWORD), SMALL_MONOTONIC_COUNTER_MAX_USE);
- // Counter & threshold of hmac_writeprotected counter.
- assert_int_equal(_get_counter(OID_COUNTER_HMAC_WRITEPROTECTED), i);
- assert_int_equal(
- _get_threshold(OID_COUNTER_HMAC_WRITEPROTECTED), SMALL_MONOTONIC_COUNTER_MAX_USE);
- }
-
- // Even a correct password doesn't work anymore.
- memset(stretched, 0x00, sizeof(stretched));
- assert_int_equal(
- optiga_stretch_password("pw", SECURECHIP_PASSWORD_STRETCH_ALGO_V1, stretched),
- SC_ERR_INCORRECT_PASSWORD);
- uint8_t zero[32] = {0};
- assert_memory_equal(stretched, zero, sizeof(stretched));
-}
-
-int main(void)
-{
- const struct CMUnitTest tests[] = {
- cmocka_unit_test(test_optiga_stretch_password_v0_success),
- cmocka_unit_test(test_optiga_stretch_password_v0_attempt_counter),
- cmocka_unit_test(test_optiga_password_v1_stretch_exhaust_fails_after_init),
- cmocka_unit_test(test_optiga_password_v1),
- };
- return cmocka_run_group_tests(tests, NULL, NULL);
-}
Why this scored 34/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.