feat(esp_security): add ECDH1 deployment mode to Key Manager driver

This commit is contained in:
harshal.patil
2026-04-30 09:29:40 +05:30
parent 92bb2641b4
commit 9aba184c43
8 changed files with 583 additions and 39 deletions
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import hashlib
import hmac
@@ -146,6 +146,39 @@ def generate_k1_G(k1_bytes: bytes) -> tuple:
return k1_G, k1_G
def compute_ecdh1_x(k1_be: bytes, k2_le: bytes) -> int:
"""Compute x(k1*k2*G) on NIST P-256 as an integer.
k1 is interpreted big-endian (matches the user's k1*G computation in
generate_k1_G); k2 is interpreted little-endian (the KM's convention
for k2 recovered from k2_info). The deployed-key byte string is the
big-endian encoding of the returned integer.
"""
k1_int = int.from_bytes(k1_be, byteorder='big')
k2_int = int.from_bytes(k2_le, byteorder='little')
generator = NIST256p.generator.to_affine()
point = (k1_int * k2_int) * generator
return int(point.x())
def generate_ecdsa_pub_from_scalar(scalar_int: int, curve_size_bits: int) -> tuple:
"""Compute (scalar * G).x, .y for the given P-N curve and return them
as little-endian bytes (the ECDSA peripheral's exported-pubkey order)."""
if curve_size_bits == 192:
curve = ec.SECP192R1()
elif curve_size_bits == 256:
curve = ec.SECP256R1()
elif curve_size_bits == 384:
curve = ec.SECP384R1()
else:
raise ValueError(f'Unsupported curve size: {curve_size_bits}')
private_key = ec.derive_private_key(scalar_int, curve)
pub_numbers = private_key.public_key().public_numbers()
pubx = pub_numbers.x.to_bytes(curve_size_bits // 8, byteorder='little')
puby = pub_numbers.y.to_bytes(curve_size_bits // 8, byteorder='little')
return pubx, puby
def generate_hmac_test_data(key: bytes) -> tuple:
hmac_message = (
b'Deleniti voluptas explicabo et assumenda. Sed et aliquid minus quis. '
@@ -265,6 +298,10 @@ def write_to_c_header(
ds_encrypted_input_params_3072: bytes,
ds_result_3072: bytes,
ds_iv: bytes,
ecdh1_xts_test_data: list,
ecdh1_hmac_result: bytes,
ecdh1_p256_pubx: bytes,
ecdh1_p256_puby: bytes,
) -> None:
with open('key_manager_test_cases.h', 'w', encoding='utf-8') as file:
header_content = f"""#include <stdint.h>
@@ -327,6 +364,21 @@ typedef struct test_data_ecdh0 {{
uint8_t k1_G[2][64];
}} test_data_ecdh0_mode_t;
// ECDH1 takes the AES-mode-style (init_key, k2_info) inputs plus the ECDH
// k1*G public point (sourced from test_data_ecdh0.k1_G to share the same
// k1 across ECDH0 and ECDH1 tests). k1_encrypted is unused, so it isn't in
// this struct.
typedef struct test_data_ecdh1 {{
uint8_t init_key[32];
uint8_t k2_info[64];
uint8_t plaintext_data[128];
union {{
test_xts_data_t xts_test_data[TEST_COUNT];
test_ecdsa_data_t ecdsa_test_data;
test_hmac_data_t hmac_test_data;
}};
}} test_data_ecdh1_mode_t;
// For 32-byte k1 key
test_data_aes_mode_t test_data_xts_aes_128 = {{
.init_key = {{ {key_to_c_format(init_key)} }},
@@ -431,6 +483,44 @@ test_data_aes_mode_t test_data_ds = {{
.ds_encrypted_input_params_iv = {{ {key_to_c_format(ds_iv)} }},
}},
}};
"""
# Per-key-type ECDH1 instances. Expected per-peripheral outputs
# (XTS ciphertext, HMAC result, ECDSA pubkey) are computed off-device
# using the deployed key x(k1*k2*G).
header_content += f"""
test_data_ecdh1_mode_t test_data_ecdh1_xts_aes_128 = {{
.init_key = {{ {key_to_c_format(init_key)} }},
.k2_info = {{ {key_to_c_format(k2_info)} }},
.plaintext_data = {{ {key_to_c_format(bytes(range(1, 129)))} }},
.xts_test_data = {{
"""
for data_size, flash_address, ciphertext in ecdh1_xts_test_data:
header_content += (
f'\t\t{{.data_size = {data_size}, '
f'.data_offset = 0x{flash_address:x}, '
f'.ciphertext = {{{key_to_c_format(ciphertext)}}}}},\n'
)
header_content += '\t}\n};\n'
header_content += f"""
test_data_ecdh1_mode_t test_data_ecdh1_hmac = {{
.init_key = {{ {key_to_c_format(init_key)} }},
.k2_info = {{ {key_to_c_format(k2_info)} }},
.hmac_test_data = {{
.message = {{ {key_to_c_format(hmac_message)} }},
.hmac_result = {{ {key_to_c_format(ecdh1_hmac_result)} }}
}}
}};
test_data_ecdh1_mode_t test_data_ecdh1_ecdsa = {{
.init_key = {{ {key_to_c_format(init_key)} }},
.k2_info = {{ {key_to_c_format(k2_info)} }},
.ecdsa_test_data = {{
.ecdsa_p256_pubx = {{ {key_to_c_format(ecdh1_p256_pubx)} }},
.ecdsa_p256_puby = {{ {key_to_c_format(ecdh1_p256_puby)} }},
}}
}};
"""
file.write(header_content)
@@ -490,6 +580,18 @@ def generate_tests_cases() -> None:
hmac_message, hmac_result = generate_hmac_test_data(k1_32)
# ECDH1: deployed key bytes = big-endian x(k1*k2*G). Per-peripheral
# effective key:
# - XTS-AES-128: key = x_be (32 BE bytes)
# - HMAC: key = x_le (= x_be[::-1], the slot is read LE)
# - ECDSA-P256: scalar = x_int mod n_p256
ecdh1_x_int = compute_ecdh1_x(k1_32, k2)
ecdh1_x_be = ecdh1_x_int.to_bytes(32, byteorder='big')
ecdh1_x_le = ecdh1_x_be[::-1]
ecdh1_xts_test_data = generate_xts_test_data(ecdh1_x_be)
ecdh1_hmac_result = hmac.HMAC(ecdh1_x_le, hmac_message, hashlib.sha256).digest()
ecdh1_p256_pubx, ecdh1_p256_puby = generate_ecdsa_pub_from_scalar(ecdh1_x_int % NIST256p.order, 256)
ds_iv = os.urandom(16)
ds_message_4096, ds_encrypted_input_params_4096, ds_result_4096 = generate_ds_encrypted_input_params(
@@ -531,6 +633,10 @@ def generate_tests_cases() -> None:
ds_encrypted_input_params_3072,
ds_result_3072,
ds_iv,
ecdh1_xts_test_data,
ecdh1_hmac_result,
ecdh1_p256_pubx,
ecdh1_p256_puby,
)
File diff suppressed because one or more lines are too long
@@ -506,6 +506,118 @@ static void key_mgr_test_ds_aes_mode(void)
}
#endif /* SOC_KEY_MANAGER_DS_KEY_DEPLOY */
/* ---- ECDH1 deployment helpers ----
*
* Per-key-type test vectors (k2_info, init_key, expected XTS ciphertext /
* HMAC result / ECDSA-P256 pubkey) are precomputed off-device by
* gen_key_manager_test_cases.py against the same committed k1_64.bin /
* k2.bin / init_key.bin / rand_num.bin used by the AES-mode and ECDH0
* tests, and emitted as test_data_ecdh1_xts_aes_128 / test_data_ecdh1_hmac
* / test_data_ecdh1_ecdsa instances of test_data_aes_mode_t. That lets the
* AES-mode per-peripheral verifiers (test_xts_aes_key_aes_mode,
* key_mgr_test_hmac_key_aes_mode, test_ecdsa_key_aes_mode) compare HW
* output bitwise against the precomputed expected values without any
* on-device key-derivation. k1*G (the ECDH input the user supplies) is
* shared with the ECDH0 tests via test_data_ecdh0.k1_G[0]. */
#if SOC_KEY_MANAGER_HMAC_KEY_DEPLOY
static void key_mgr_test_hmac_ecdh1_mode(void)
{
static esp_key_mgr_ecdh1_key_config_t key_config;
memcpy(key_config.k2_info, (uint8_t*) test_data_ecdh1_hmac.k2_info, KEY_MGR_K2_INFO_SIZE);
memcpy(key_config.k1_G[0], (uint8_t*) test_data_ecdh0.k1_G[0], KEY_MGR_ECDH0_INFO_SIZE);
memcpy(key_config.sw_init_key, (uint8_t*) test_data_ecdh1_hmac.init_key, KEY_MGR_SW_INIT_KEY_SIZE);
key_config.use_pre_generated_sw_init_key = 1;
key_config.key_type = ESP_KEY_MGR_HMAC_KEY;
static esp_key_mgr_key_recovery_info_t key_recovery_info;
TEST_ASSERT_EQUAL(ESP_OK, esp_key_mgr_deploy_key_in_ecdh1_mode(&key_config, &key_recovery_info));
TEST_ASSERT_EQUAL(ESP_OK, esp_key_mgr_activate_key(&key_recovery_info));
uint8_t hw_mac[32] = { 0 };
TEST_ASSERT_EQUAL(ESP_OK, hmac_calculate(HMAC_KEY_KM,
test_data_ecdh1_hmac.hmac_test_data.message,
sizeof(test_data_ecdh1_hmac.hmac_test_data.message),
hw_mac));
TEST_ASSERT_EQUAL_HEX8_ARRAY(test_data_ecdh1_hmac.hmac_test_data.hmac_result,
hw_mac, sizeof(hw_mac));
TEST_ASSERT_EQUAL(ESP_OK, esp_key_mgr_deactivate_key(key_recovery_info.key_type));
}
#endif /* SOC_KEY_MANAGER_HMAC_KEY_DEPLOY */
#if SOC_KEY_MANAGER_FE_KEY_DEPLOY && SOC_KEY_MANAGER_FE_KEY_DEPLOY_XTS_AES_128
/* Common XTS-AES verify body. Used by both the AES-mode and ECDH1-mode XTS
* tests, which carry their own struct types but share the same plaintext +
* expected-ciphertext layout. */
static void verify_xts_aes_test_data(const uint8_t *plaintext_data,
const test_xts_data_t *xts_test_data)
{
const esp_partition_t *partition = get_test_storage_partition();
ESP_ERROR_CHECK(esp_partition_erase_range(partition, 0, partition->size));
uint8_t read_data[128];
for (int i = 0; i < TEST_COUNT; i++) {
memset(read_data, 0, sizeof(read_data));
uint32_t address = xts_test_data[i].data_offset;
uint32_t data_size = xts_test_data[i].data_size;
ESP_ERROR_CHECK(esp_flash_write_encrypted(NULL, address, plaintext_data, data_size));
ESP_ERROR_CHECK(esp_flash_read(NULL, read_data, address, data_size));
TEST_ASSERT_EQUAL_HEX8_ARRAY(xts_test_data[i].ciphertext, read_data, data_size);
}
}
static void key_mgr_test_xts_aes_128_ecdh1_mode(void)
{
static esp_key_mgr_ecdh1_key_config_t key_config;
memcpy(key_config.k2_info, (uint8_t*) test_data_ecdh1_xts_aes_128.k2_info, KEY_MGR_K2_INFO_SIZE);
memcpy(key_config.k1_G[0], (uint8_t*) test_data_ecdh0.k1_G[0], KEY_MGR_ECDH0_INFO_SIZE);
memcpy(key_config.sw_init_key, (uint8_t*) test_data_ecdh1_xts_aes_128.init_key, KEY_MGR_SW_INIT_KEY_SIZE);
key_config.use_pre_generated_sw_init_key = 1;
key_config.key_type = ESP_KEY_MGR_FLASH_XTS_AES_KEY;
key_config.key_len = ESP_KEY_MGR_XTS_AES_LEN_128;
static esp_key_mgr_key_recovery_info_t key_recovery_info;
TEST_ASSERT_EQUAL(ESP_OK, esp_key_mgr_deploy_key_in_ecdh1_mode(&key_config, &key_recovery_info));
TEST_ASSERT_EQUAL(ESP_OK, esp_key_mgr_activate_key(&key_recovery_info));
verify_xts_aes_test_data(test_data_ecdh1_xts_aes_128.plaintext_data,
test_data_ecdh1_xts_aes_128.xts_test_data);
TEST_ASSERT_EQUAL(ESP_OK, esp_key_mgr_deactivate_key(key_recovery_info.key_type));
}
#endif /* SOC_KEY_MANAGER_FE_KEY_DEPLOY && SOC_KEY_MANAGER_FE_KEY_DEPLOY_XTS_AES_128 */
#if SOC_KEY_MANAGER_ECDSA_KEY_DEPLOY
static void key_mgr_test_ecdsa_p256_ecdh1_mode(void)
{
static esp_key_mgr_ecdh1_key_config_t key_config;
memcpy(key_config.k2_info, (uint8_t*) test_data_ecdh1_ecdsa.k2_info, KEY_MGR_K2_INFO_SIZE);
memcpy(key_config.k1_G[0], (uint8_t*) test_data_ecdh0.k1_G[0], KEY_MGR_ECDH0_INFO_SIZE);
memcpy(key_config.sw_init_key, (uint8_t*) test_data_ecdh1_ecdsa.init_key, KEY_MGR_SW_INIT_KEY_SIZE);
key_config.use_pre_generated_sw_init_key = 1;
key_config.key_type = ESP_KEY_MGR_ECDSA_KEY;
key_config.key_len = ESP_KEY_MGR_ECDSA_LEN_256;
static esp_key_mgr_key_recovery_info_t key_recovery_info;
TEST_ASSERT_EQUAL(ESP_OK, esp_key_mgr_deploy_key_in_ecdh1_mode(&key_config, &key_recovery_info));
TEST_ASSERT_EQUAL(ESP_OK, esp_key_mgr_activate_key(&key_recovery_info));
#if SOC_ECDSA_SUPPORT_DETERMINISTIC_MODE
test_ecdsa_key_aes_mode(ECDSA_CURVE_SECP256R1, sha_digest,
test_data_ecdh1_ecdsa.ecdsa_test_data.ecdsa_p256_pubx,
test_data_ecdh1_ecdsa.ecdsa_test_data.ecdsa_p256_puby,
ECDSA_K_TYPE_DETERMINISITIC);
#endif
test_ecdsa_key_aes_mode(ECDSA_CURVE_SECP256R1, sha_digest,
test_data_ecdh1_ecdsa.ecdsa_test_data.ecdsa_p256_pubx,
test_data_ecdh1_ecdsa.ecdsa_test_data.ecdsa_p256_puby,
ECDSA_K_TYPE_TRNG);
TEST_ASSERT_EQUAL(ESP_OK, esp_key_mgr_deactivate_key(key_recovery_info.key_type));
}
#endif /* SOC_KEY_MANAGER_ECDSA_KEY_DEPLOY */
TEST_GROUP(key_manager);
TEST_SETUP(key_manager)
@@ -535,6 +647,11 @@ TEST(key_manager, xts_key_128_ecdh0_deployment)
key_mgr_test_xts_aes_128_ecdh0_mode();
}
TEST(key_manager, xts_key_128_ecdh1_deployment)
{
key_mgr_test_xts_aes_128_ecdh1_mode();
}
#if CONFIG_CRYPTO_TEST_APP_ENABLE_FPGA_TESTS
TEST(key_manager, xts_key_128_random_deployment)
{
@@ -597,6 +714,11 @@ TEST(key_manager, ecdsa_p256_key_ecdh0_deployment)
key_mgr_test_ecdsa_key_ecdh0_mode(ESP_KEY_MGR_ECDSA_LEN_256);
}
TEST(key_manager, ecdsa_p256_key_ecdh1_deployment)
{
key_mgr_test_ecdsa_p256_ecdh1_mode();
}
TEST(key_manager, ecdsa_p256_key_random_deployment)
{
key_mgr_test_ecdsa_key_random_mode(ESP_KEY_MGR_ECDSA_LEN_256);
@@ -631,6 +753,11 @@ TEST(key_manager, hmac_key_ecdh0_deployment)
key_mgr_test_hmac_ecdh0_mode();
}
TEST(key_manager, hmac_key_ecdh1_deployment)
{
key_mgr_test_hmac_ecdh1_mode();
}
TEST(key_manager, hmac_key_random_deployment)
{
key_mgr_test_hmac_random_mode();
@@ -650,6 +777,7 @@ TEST_GROUP_RUNNER(key_manager)
#if SOC_KEY_MANAGER_FE_KEY_DEPLOY_XTS_AES_128
RUN_TEST_CASE(key_manager, xts_aes_128_key_aes_deployment);
RUN_TEST_CASE(key_manager, xts_key_128_ecdh0_deployment);
RUN_TEST_CASE(key_manager, xts_key_128_ecdh1_deployment);
#if CONFIG_CRYPTO_TEST_APP_ENABLE_FPGA_TESTS
// This tests expects Flash encryption to be enabled as the test compares the decrypted flash data with the plaintext data
RUN_TEST_CASE(key_manager, xts_key_128_random_deployment);
@@ -672,6 +800,7 @@ TEST_GROUP_RUNNER(key_manager)
RUN_TEST_CASE(key_manager, ecdsa_p256_key_aes_deployment);
RUN_TEST_CASE(key_manager, ecdsa_p256_key_ecdh0_deployment);
RUN_TEST_CASE(key_manager, ecdsa_p256_key_ecdh1_deployment);
RUN_TEST_CASE(key_manager, ecdsa_p256_key_random_deployment);
#if SOC_ECDSA_SUPPORT_CURVE_P384
@@ -684,6 +813,7 @@ TEST_GROUP_RUNNER(key_manager)
#if SOC_KEY_MANAGER_HMAC_KEY_DEPLOY
RUN_TEST_CASE(key_manager, hmac_key_aes_deployment);
RUN_TEST_CASE(key_manager, hmac_key_ecdh0_deployment);
RUN_TEST_CASE(key_manager, hmac_key_ecdh1_deployment);
RUN_TEST_CASE(key_manager, hmac_key_random_deployment);
#endif /* SOC_KEY_MANAGER_HMAC_KEY_DEPLOY */