diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index f24e451..4aff22f 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -12,7 +12,7 @@ set(REQUIRES ) set(PRIV_REQUIRES - secp256k1-frost espressif__cjson espressif__esp_websocket_client libwally-core + secp256k1-frost espressif__cjson espressif__esp_websocket_client libwally-core nvs_flash ) if(CONFIG_KEEP_DISPLAY_ENABLED) diff --git a/main/main.c b/main/main.c index ac2463d..1d91fcd 100644 --- a/main/main.c +++ b/main/main.c @@ -65,6 +65,48 @@ static void handle_restart(const rpc_request_t *req, rpc_response_t *resp) { esp_restart(); } +static void handle_unlock(const rpc_request_t *req, rpc_response_t *resp) { + if (storage_crypto_is_initialized()) { + PROTOCOL_ERROR(resp, req->id, PROTOCOL_ERR_PARAMS, "Already unlocked"); + return; + } + + if (strlen(req->pin) == 0) { + PROTOCOL_ERROR(resp, req->id, ERR_PIN_INVALID, "PIN required"); + return; + } + + int ret = storage_crypto_init(req->pin); + if (ret == ERR_PIN_LOCKED) { + PROTOCOL_ERROR(resp, req->id, ERR_PIN_LOCKED, "Device locked"); + return; + } + if (ret == ERR_PIN_MUST_WAIT) { + PROTOCOL_ERROR(resp, req->id, ERR_PIN_MUST_WAIT, "Too many attempts, please wait"); + return; + } + if (ret == ERR_PIN_INVALID) { + PROTOCOL_ERROR(resp, req->id, ERR_PIN_INVALID, "Invalid PIN"); + return; + } + if (ret != 0) { + PROTOCOL_ERROR(resp, req->id, PROTOCOL_ERR_INTERNAL, "Unlock failed"); + return; + } + + int migrate_ret = storage_migrate_if_needed(); + if (migrate_ret == STORAGE_ERR_IO) { + storage_crypto_clear(); + PROTOCOL_ERROR(resp, req->id, PROTOCOL_ERR_STORAGE, "Migration failed"); + return; + } + if (migrate_ret != STORAGE_OK && migrate_ret != STORAGE_ERR_NOT_INIT) { + ESP_LOGW(TAG, "Storage migration warning: %d", migrate_ret); + } + + protocol_success(resp, req->id, "{\"unlocked\":true}"); +} + static void handle_list_shares(const rpc_request_t *req, rpc_response_t *resp) { char groups[STORAGE_MAX_SHARES][STORAGE_GROUP_LEN + 1]; int count = storage_list_shares(groups, STORAGE_MAX_SHARES); @@ -378,6 +420,9 @@ static void handle_request(const rpc_request_t *req, rpc_response_t *resp) { case RPC_METHOD_SESSION_LIST: frost_session_list(resp); break; + case RPC_METHOD_UNLOCK: + handle_unlock(req, resp); + break; default: PROTOCOL_ERROR(resp, req->id, PROTOCOL_ERR_METHOD, "Method not found"); } @@ -404,18 +449,7 @@ void app_main(void) { ag_random_delay_ms(AG_BOOT_DELAY_MIN_MS, AG_BOOT_DELAY_MAX_MS); - if (storage_crypto_init(NULL) != 0) { - ESP_LOGE(TAG, "Storage crypto init failed - share storage operations will be unavailable"); - } else { - int migrate_ret = storage_migrate_if_needed(); - if (migrate_ret == STORAGE_ERR_IO) { - ESP_LOGE(TAG, "Storage migration failed with IO error, restarting"); - esp_restart(); - } else if (migrate_ret != STORAGE_OK && migrate_ret != STORAGE_ERR_NOT_INIT) { - ESP_LOGW(TAG, "Storage migration failed: %d (continuing with existing data)", - migrate_ret); - } - } + ESP_LOGI(TAG, "PIN-protected storage enabled - share operations require PIN"); ag_random_delay_ms(AG_BOOT_DELAY_MIN_MS, AG_BOOT_DELAY_MAX_MS); diff --git a/main/protocol.c b/main/protocol.c index 0a35ce4..a333e4f 100644 --- a/main/protocol.c +++ b/main/protocol.c @@ -85,6 +85,8 @@ static rpc_method_t parse_method(const char *method) { return RPC_METHOD_SESSION_RESUME; if (strcmp(method, "frost_session_list") == 0) return RPC_METHOD_SESSION_LIST; + if (strcmp(method, "unlock") == 0) + return RPC_METHOD_UNLOCK; return RPC_METHOD_UNKNOWN; } @@ -206,6 +208,10 @@ int protocol_parse_request(const char *json, rpc_request_t *req) { if (passphrase && cJSON_IsString(passphrase)) { snprintf(req->passphrase, sizeof(req->passphrase), "%s", passphrase->valuestring); } + cJSON *pin = cJSON_GetObjectItem(params, "pin"); + if (pin && cJSON_IsString(pin)) { + snprintf(req->pin, sizeof(req->pin), "%s", pin->valuestring); + } } cJSON_Delete(root); @@ -215,6 +221,7 @@ int protocol_parse_request(const char *json, rpc_request_t *req) { void protocol_free_request(rpc_request_t *req) { if (req) { secure_memzero(req->passphrase, sizeof(req->passphrase)); + secure_memzero(req->pin, sizeof(req->pin)); if (req->psbt) { free(req->psbt); req->psbt = NULL; diff --git a/main/protocol.h b/main/protocol.h index a9f05ac..a033772 100644 --- a/main/protocol.h +++ b/main/protocol.h @@ -18,6 +18,7 @@ #define PROTOCOL_API_VERSION 1 #define PROTOCOL_MAX_PARTICIPANTS 16 #define PROTOCOL_COMMITMENT_HEX_LEN 264 +#define PROTOCOL_MAX_PIN_LEN 64 #define MAX_COMMITMENTS_SIZE ((PROTOCOL_MAX_PARTICIPANTS - 1) * PROTOCOL_COMMITMENT_HEX_LEN + 1) typedef enum { @@ -46,6 +47,7 @@ typedef enum { RPC_METHOD_EXPORT_SHARE, RPC_METHOD_SESSION_RESUME, RPC_METHOD_SESSION_LIST, + RPC_METHOD_UNLOCK, RPC_METHOD_UNKNOWN } rpc_method_t; @@ -66,6 +68,7 @@ typedef struct { size_t input_idx; char policy_bundle[5120]; char passphrase[256]; + char pin[PROTOCOL_MAX_PIN_LEN + 1]; } rpc_request_t; typedef struct { diff --git a/main/self_test.c b/main/self_test.c index 2e1d5d9..fbe1c8b 100644 --- a/main/self_test.c +++ b/main/self_test.c @@ -8,6 +8,7 @@ #include "esp_partition.h" #include "esp_log.h" #include +#include #include #define TAG "self_test" @@ -33,37 +34,56 @@ static const self_test_t tests[] = { {SELF_TEST_STORAGE_SLOTS, "storage_slots", self_test_storage_slots, false}, }; +static const uint8_t AES_GCM_TEST_KEY[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}; +static const uint8_t AES_GCM_TEST_NONCE[12] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +static const uint8_t AES_GCM_TEST_PT[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +static const uint8_t AES_GCM_TEST_CT[16] = {0x0e, 0xbc, 0xb5, 0xde, 0xb5, 0x2c, 0x83, 0xbd, + 0x08, 0xa8, 0xa9, 0x35, 0x18, 0x2c, 0x91, 0x99}; +static const uint8_t AES_GCM_TEST_TAG[16] = {0x38, 0x62, 0x64, 0x29, 0x86, 0xc9, 0x53, 0xe8, + 0x7b, 0x3c, 0xe9, 0x9b, 0x0d, 0xec, 0xdf, 0x34}; + int self_test_storage_crypto(void) { - if (!storage_crypto_is_initialized()) { - return -1; - } + mbedtls_gcm_context gcm; + mbedtls_gcm_init(&gcm); - uint8_t test_data[32]; - uint8_t encrypted[32]; - uint8_t decrypted[32]; - uint8_t nonce[STORAGE_CRYPTO_NONCE_SIZE]; - uint8_t tag[STORAGE_CRYPTO_TAG_SIZE]; int result = -1; + uint8_t output[16]; + uint8_t tag[16]; + + if (mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, AES_GCM_TEST_KEY, 256) != 0) { + goto cleanup; + } - if (rng_fill_checked(test_data, sizeof(test_data)) != 0) { + if (mbedtls_gcm_crypt_and_tag(&gcm, MBEDTLS_GCM_ENCRYPT, sizeof(AES_GCM_TEST_PT), + AES_GCM_TEST_NONCE, sizeof(AES_GCM_TEST_NONCE), NULL, 0, + AES_GCM_TEST_PT, output, sizeof(tag), tag) != 0) { goto cleanup; } - if (storage_crypto_encrypt(test_data, sizeof(test_data), NULL, 0, nonce, encrypted, tag) != 0) { + if (ct_compare(output, AES_GCM_TEST_CT, sizeof(AES_GCM_TEST_CT)) != 0 || + ct_compare(tag, AES_GCM_TEST_TAG, sizeof(AES_GCM_TEST_TAG)) != 0) { goto cleanup; } - if (storage_crypto_decrypt(encrypted, sizeof(encrypted), NULL, 0, nonce, tag, decrypted) != 0) { + if (mbedtls_gcm_auth_decrypt(&gcm, sizeof(AES_GCM_TEST_CT), AES_GCM_TEST_NONCE, + sizeof(AES_GCM_TEST_NONCE), NULL, 0, AES_GCM_TEST_TAG, + sizeof(AES_GCM_TEST_TAG), AES_GCM_TEST_CT, output) != 0) { goto cleanup; } - result = ct_compare(test_data, decrypted, sizeof(test_data)) == 0 ? 0 : -1; + if (ct_compare(output, AES_GCM_TEST_PT, sizeof(AES_GCM_TEST_PT)) != 0) { + goto cleanup; + } + + result = 0; cleanup: - secure_memzero(test_data, sizeof(test_data)); - secure_memzero(encrypted, sizeof(encrypted)); - secure_memzero(decrypted, sizeof(decrypted)); - secure_memzero(nonce, sizeof(nonce)); + mbedtls_gcm_free(&gcm); + secure_memzero(output, sizeof(output)); secure_memzero(tag, sizeof(tag)); return result; } diff --git a/main/storage.c b/main/storage.c index 0ce6900..f711109 100644 --- a/main/storage.c +++ b/main/storage.c @@ -502,6 +502,10 @@ int storage_load_share(const char *group, char *share_hex, size_t len) { if (!storage_crypto_is_initialized()) { return STORAGE_ERR_CRYPTO_NOT_INIT; } + int rate_check = storage_crypto_check_rate_limit(); + if (rate_check != 0) { + return rate_check; + } char padded_group[STORAGE_GROUP_LEN + 1]; storage_pad_group_name(padded_group, group); @@ -538,6 +542,7 @@ int storage_load_share(const char *group, char *share_hex, size_t len) { if (storage_crypto_decrypt(slot.share_data, actual_len, aad, aad_len, slot.nonce, slot.tag, decrypted) != 0) { ESP_LOGE(TAG, "Share decryption failed - tampered or wrong PIN"); + storage_crypto_record_attempt(false); secure_memzero(&slot, sizeof(slot)); return STORAGE_ERR_DECRYPT; } diff --git a/main/storage_crypto.c b/main/storage_crypto.c index 7020ae5..925cdc7 100644 --- a/main/storage_crypto.c +++ b/main/storage_crypto.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later #include "storage_crypto.h" +#include "error_codes.h" #include "random_utils.h" #include "crypto_asm.h" #include @@ -13,42 +14,101 @@ #ifdef ESP_PLATFORM #include "esp_mac.h" #include "esp_log.h" +#include "esp_timer.h" +#include "nvs_flash.h" +#include "nvs.h" +static uint32_t get_time_ms(void) { + return (uint32_t)(esp_timer_get_time() / 1000); +} #else #include +#include #define ESP_LOGW(tag, ...) \ fprintf(stderr, "W (%s): ", tag); \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "\n") +#define ESP_LOGE(tag, ...) \ + fprintf(stderr, "E (%s): ", tag); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n") +static uint32_t get_time_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint32_t)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000); +} #endif #define TAG "storage_crypto" #define DEVICE_ID_SIZE 6 +#define PIN_RATE_LIMIT_WINDOW_MS 60000 +#define PIN_RATE_LIMIT_MAX 3 +#define PIN_LOCKOUT_MS 300000 +#define PIN_LOCKOUT_FAILURE_THRESH 5 + +#define NVS_NAMESPACE "pin_rl" +#define NVS_KEY_FAILURES "failures" +#define NVS_KEY_LOCKOUT "lockout" + static uint8_t storage_key[STORAGE_CRYPTO_KEY_SIZE]; static bool key_initialized = false; +static uint32_t pin_attempt_times[PIN_RATE_LIMIT_MAX]; +static uint8_t pin_attempt_count = 0; +static uint32_t pin_lockout_until = 0; +static uint8_t pin_consecutive_failures = 0; + +static void load_rate_limit_state(void) { +#ifdef ESP_PLATFORM + nvs_handle_t handle; + if (nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle) == ESP_OK) { + uint8_t failures = 0; + uint32_t lockout = 0; + nvs_get_u8(handle, NVS_KEY_FAILURES, &failures); + nvs_get_u32(handle, NVS_KEY_LOCKOUT, &lockout); + nvs_close(handle); + pin_consecutive_failures = failures; + pin_lockout_until = lockout; + } +#endif +} + +static void save_rate_limit_state(void) { +#ifdef ESP_PLATFORM + nvs_handle_t handle; + if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle) == ESP_OK) { + nvs_set_u8(handle, NVS_KEY_FAILURES, pin_consecutive_failures); + nvs_set_u32(handle, NVS_KEY_LOCKOUT, pin_lockout_until); + nvs_commit(handle); + nvs_close(handle); + } +#endif +} + static int get_device_id(uint8_t device_id[DEVICE_ID_SIZE]) { #ifdef ESP_PLATFORM return esp_read_mac(device_id, ESP_MAC_EFUSE_FACTORY) == ESP_OK ? 0 : -1; #else - memset(device_id, 0x42, DEVICE_ID_SIZE); FILE *fp = fopen("/etc/machine-id", "r"); - if (!fp) - return 0; + if (!fp) { + ESP_LOGE(TAG, "Cannot read /etc/machine-id - device ID unavailable"); + return -1; + } char buf[13] = {0}; size_t bytes_read = fread(buf, 1, 12, fp); fclose(fp); if (bytes_read < 12) { - return 0; + ESP_LOGE(TAG, "Machine ID too short"); + return -1; } for (int i = 0; i < DEVICE_ID_SIZE; i++) { unsigned int val = 0; if (sscanf(buf + i * 2, "%2x", &val) != 1) { - memset(device_id, 0x42, DEVICE_ID_SIZE); - return 0; + ESP_LOGE(TAG, "Invalid machine ID format"); + return -1; } device_id[i] = (uint8_t)val; } @@ -86,27 +146,89 @@ static int derive_key(const uint8_t *device_id, size_t device_id_len, const uint return (ret == 0) ? 0 : -1; } -int storage_crypto_init(const char *pin) { - uint8_t device_id[DEVICE_ID_SIZE]; +static bool rate_limit_loaded = false; - if (get_device_id(device_id) != 0) { - return -1; +int storage_crypto_check_rate_limit(void) { + if (!rate_limit_loaded) { + load_rate_limit_state(); + rate_limit_loaded = true; + } + + uint32_t now = get_time_ms(); + + if (pin_lockout_until > 0) { + if ((int32_t)(now - pin_lockout_until) < 0) { + return ERR_PIN_LOCKED; + } + pin_lockout_until = 0; + pin_consecutive_failures = 0; + save_rate_limit_state(); + } + + int recent = 0; + for (int i = 0; i < pin_attempt_count; i++) { + if ((now - pin_attempt_times[i]) < PIN_RATE_LIMIT_WINDOW_MS) { + recent++; + } + } + if (recent >= PIN_RATE_LIMIT_MAX) { + return ERR_PIN_MUST_WAIT; + } + + return 0; +} + +void storage_crypto_record_attempt(bool success) { + uint32_t now = get_time_ms(); + + if (pin_attempt_count < PIN_RATE_LIMIT_MAX) { + pin_attempt_times[pin_attempt_count++] = now; + } else { + memmove(pin_attempt_times, pin_attempt_times + 1, + (PIN_RATE_LIMIT_MAX - 1) * sizeof(pin_attempt_times[0])); + pin_attempt_times[PIN_RATE_LIMIT_MAX - 1] = now; + } + + if (success) { + pin_consecutive_failures = 0; + } else { + if (pin_consecutive_failures < UINT8_MAX) { + pin_consecutive_failures++; + } + if (pin_consecutive_failures >= PIN_LOCKOUT_FAILURE_THRESH) { + pin_lockout_until = now + PIN_LOCKOUT_MS; + ESP_LOGW(TAG, "PIN lockout activated for %d seconds", PIN_LOCKOUT_MS / 1000); + } + } + save_rate_limit_state(); +} + +int storage_crypto_init(const char *pin) { + int rate_limit = storage_crypto_check_rate_limit(); + if (rate_limit != 0) { + return rate_limit; } size_t pin_len = pin ? strnlen(pin, STORAGE_CRYPTO_MAX_PIN_LEN + 1) : 0; - if (pin_len > STORAGE_CRYPTO_MAX_PIN_LEN) { - secure_memzero(device_id, sizeof(device_id)); - return -1; + if (pin_len == 0 || pin_len > STORAGE_CRYPTO_MAX_PIN_LEN) { + storage_crypto_record_attempt(false); + return ERR_PIN_INVALID; } - if (!pin || pin_len == 0) { - ESP_LOGW(TAG, "No PIN provided - using device-derived key only (not PIN-protected)"); + uint8_t device_id[DEVICE_ID_SIZE]; + if (get_device_id(device_id) != 0) { + return -1; } int ret = derive_key(device_id, sizeof(device_id), (const uint8_t *)pin, pin_len, storage_key); secure_memzero(device_id, sizeof(device_id)); - key_initialized = (ret == 0); + if (ret == 0) { + key_initialized = true; + storage_crypto_record_attempt(true); + } else { + storage_crypto_record_attempt(false); + } return ret; } @@ -172,3 +294,13 @@ int storage_crypto_decrypt(const uint8_t *ciphertext, size_t ciphertext_len, con mbedtls_gcm_free(&gcm); return (ret == 0) ? 0 : -1; } + +#ifdef UNIT_TEST +void storage_crypto_reset_rate_limit(void) { + pin_attempt_count = 0; + pin_lockout_until = 0; + pin_consecutive_failures = 0; + memset(pin_attempt_times, 0, sizeof(pin_attempt_times)); + rate_limit_loaded = false; +} +#endif diff --git a/main/storage_crypto.h b/main/storage_crypto.h index 1312e86..5eeeb36 100644 --- a/main/storage_crypto.h +++ b/main/storage_crypto.h @@ -17,6 +17,9 @@ int storage_crypto_init(const char *pin); bool storage_crypto_is_initialized(void); void storage_crypto_clear(void); +int storage_crypto_check_rate_limit(void); +void storage_crypto_record_attempt(bool success); + int storage_crypto_encrypt(const uint8_t *plaintext, size_t plaintext_len, const uint8_t *aad, size_t aad_len, uint8_t nonce[STORAGE_CRYPTO_NONCE_SIZE], uint8_t *ciphertext, uint8_t tag[STORAGE_CRYPTO_TAG_SIZE]); diff --git a/test/native/CMakeLists.txt b/test/native/CMakeLists.txt index 1570605..09672e2 100644 --- a/test/native/CMakeLists.txt +++ b/test/native/CMakeLists.txt @@ -92,12 +92,29 @@ if(EXISTS ${CJSON_DIR}/cJSON.c) target_compile_definitions(test_integration_full PRIVATE NATIVE_TEST=1 HAS_CJSON=1) endif() -add_executable(test_self_test test_self_test.c) -target_include_directories(test_self_test PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/mocks - ${MAIN_DIR} -) -target_compile_definitions(test_self_test PRIVATE NATIVE_TEST=1) +find_package(MbedTLS QUIET) +if(MbedTLS_FOUND) + add_executable(test_self_test test_self_test.c) + target_include_directories(test_self_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/mocks + ${MAIN_DIR} + ) + target_compile_definitions(test_self_test PRIVATE NATIVE_TEST=1 UNIT_TEST=1) + target_link_libraries(test_self_test MbedTLS::mbedcrypto) +else() + find_library(MBEDCRYPTO_LIB mbedcrypto) + if(MBEDCRYPTO_LIB) + add_executable(test_self_test test_self_test.c) + target_include_directories(test_self_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/mocks + ${MAIN_DIR} + ) + target_compile_definitions(test_self_test PRIVATE NATIVE_TEST=1 UNIT_TEST=1) + target_link_libraries(test_self_test ${MBEDCRYPTO_LIB}) + else() + message(STATUS "Skipping test_self_test (mbedtls not found)") + endif() +endif() add_executable(test_hw_entropy test_hw_entropy.c ${MAIN_DIR}/hw_entropy.c ${MAIN_DIR}/random_utils.c) target_include_directories(test_hw_entropy PRIVATE diff --git a/test/native/mocks/mbedtls/gcm.h b/test/native/mocks/mbedtls/gcm.h index 6c6012a..06db65d 100644 --- a/test/native/mocks/mbedtls/gcm.h +++ b/test/native/mocks/mbedtls/gcm.h @@ -8,6 +8,10 @@ #define MBEDTLS_GCM_ENCRYPT 1 #define MBEDTLS_CIPHER_ID_AES 0 +static uint8_t mock_gcm_key[32]; +static uint8_t mock_gcm_expected_ct[16]; +static uint8_t mock_gcm_expected_tag[16]; + typedef struct { int dummy; } mbedtls_gcm_context; @@ -24,8 +28,8 @@ static inline int mbedtls_gcm_setkey(mbedtls_gcm_context *ctx, int cipher, const unsigned int keybits) { (void)ctx; (void)cipher; - (void)key; (void)keybits; + memcpy(mock_gcm_key, key, 32); return 0; } @@ -39,8 +43,41 @@ static inline int mbedtls_gcm_crypt_and_tag(mbedtls_gcm_context *ctx, int mode, (void)iv_len; (void)add; (void)add_len; + static const uint8_t expected_key[32] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}; + static const uint8_t expected_ct[16] = {0x0e, 0xbc, 0xb5, 0xde, 0xb5, 0x2c, 0x83, 0xbd, + 0x08, 0xa8, 0xa9, 0x35, 0x18, 0x2c, 0x91, 0x99}; + static const uint8_t expected_tag_v[16] = {0x38, 0x62, 0x64, 0x29, 0x86, 0xc9, 0x53, 0xe8, + 0x7b, 0x3c, 0xe9, 0x9b, 0x0d, 0xec, 0xdf, 0x34}; + if (length == 16 && memcmp(mock_gcm_key, expected_key, 32) == 0) { + memcpy(output, expected_ct, 16); + memcpy(tag, expected_tag_v, 16); + memcpy(mock_gcm_expected_ct, expected_ct, 16); + memcpy(mock_gcm_expected_tag, expected_tag_v, 16); + } else { + memcpy(output, input, length); + memset(tag, 0x42, tag_len); + } + return 0; +} + +static inline int mbedtls_gcm_auth_decrypt(mbedtls_gcm_context *ctx, size_t length, + const uint8_t *iv, size_t iv_len, const uint8_t *add, + size_t add_len, const uint8_t *tag, size_t tag_len, + const uint8_t *input, uint8_t *output) { + (void)ctx; + (void)iv; + (void)iv_len; + (void)add; + (void)add_len; + if (length == 16 && memcmp(input, mock_gcm_expected_ct, 16) == 0 && + memcmp(tag, mock_gcm_expected_tag, tag_len) == 0) { + memset(output, 0, 16); + return 0; + } memcpy(output, input, length); - memset(tag, 0x42, tag_len); return 0; } diff --git a/test/native/mocks/storage_crypto.h b/test/native/mocks/storage_crypto.h index e3def3b..6cc3dd7 100644 --- a/test/native/mocks/storage_crypto.h +++ b/test/native/mocks/storage_crypto.h @@ -15,6 +15,7 @@ #define STORAGE_CRYPTO_MAX_PIN_LEN 64 static bool mock_crypto_initialized = true; +static int mock_rate_limit_result = 0; static uint8_t mock_last_encrypt_aad[128]; static size_t mock_last_encrypt_aad_len = 0; @@ -28,8 +29,29 @@ static inline void mock_crypto_reset_aad(void) { mock_last_decrypt_aad_len = 0; } +static inline int storage_crypto_check_rate_limit(void) { + return mock_rate_limit_result; +} + +static inline void storage_crypto_record_attempt(bool success) { + (void)success; +} + +#ifdef UNIT_TEST +static inline void storage_crypto_reset_rate_limit(void) { + mock_rate_limit_result = 0; +} +#endif + static inline int storage_crypto_init(const char *pin) { - (void)pin; + int rate_limit = storage_crypto_check_rate_limit(); + if (rate_limit != 0) { + return rate_limit; + } + size_t pin_len = pin ? strlen(pin) : 0; + if (pin_len == 0 || pin_len > STORAGE_CRYPTO_MAX_PIN_LEN) { + return -1; + } mock_crypto_initialized = true; return 0; } diff --git a/test/native/test_self_test.c b/test/native/test_self_test.c index e8e3d48..24a058f 100644 --- a/test/native/test_self_test.c +++ b/test/native/test_self_test.c @@ -104,6 +104,7 @@ int secp256k1_ec_pubkey_create(const secp256k1_context *ctx, secp256k1_pubkey *p return 1; } +#include #include "self_test.h" #include "self_test.c" @@ -134,12 +135,12 @@ static int test_storage_crypto_pass(void) { return 0; } -static int test_storage_crypto_fail_not_init(void) { - TEST("storage_crypto fails when not initialized"); +static int test_storage_crypto_uses_test_vectors(void) { + TEST("storage_crypto uses test vectors without initialized crypto"); reset_state(); mock_crypto_initialized = false; - if (self_test_storage_crypto() == 0) - FAIL("should fail"); + if (self_test_storage_crypto() != 0) + FAIL("should pass using test vectors"); PASS(); return 0; } @@ -241,7 +242,7 @@ static int test_run_all_pass(void) { static int test_run_all_fail_required(void) { TEST("run_all fails when required test fails"); reset_state(); - mock_crypto_initialized = false; + mock_secp256k1_ctx_fails = true; if (self_test_run_all() == 0) FAIL("should fail"); self_test_stats_t stats; @@ -269,7 +270,7 @@ int main(void) { int failures = 0; failures += test_storage_crypto_pass(); - failures += test_storage_crypto_fail_not_init(); + failures += test_storage_crypto_uses_test_vectors(); failures += test_crypto_lib_pass(); failures += test_crypto_lib_fail(); failures += test_flash_partitions_pass();