Skip to content
2 changes: 1 addition & 1 deletion main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
58 changes: 46 additions & 12 deletions main/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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");
}
Expand All @@ -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);

Expand Down
7 changes: 7 additions & 0 deletions main/protocol.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions main/protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;

Expand All @@ -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 {
Expand Down
52 changes: 36 additions & 16 deletions main/self_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "esp_partition.h"
#include "esp_log.h"
#include <secp256k1.h>
#include <mbedtls/gcm.h>
#include <string.h>

#define TAG "self_test"
Expand All @@ -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;
}
Expand Down
5 changes: 5 additions & 0 deletions main/storage.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down
Loading