/* * SPDX-FileCopyrightText: 2023 Nordic Semiconductor ASA * SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "esp_random.h" #include "psa/crypto.h" #include "mesh/common.h" #include "mesh/crypto.h" #include "mesh/trace.h" #include "mesh/config.h" /* PSA Key ID range for BLE Mesh */ #define BT_MESH_PSA_KEY_ID_MIN 0x0001A000 #define BT_MESH_PSA_KEY_ID_RANGE_SIZE \ (2 * CONFIG_BLE_MESH_SUBNET_COUNT + 2 * CONFIG_BLE_MESH_APP_KEY_COUNT + 2) /* Internal DH key pair storage * * PSA functions expect BIG-ENDIAN format. */ static struct { bool is_ready; psa_key_id_t priv_key_id; uint8_t public_key[PUB_KEY_SIZE + 1]; /* Big-endian, PSA format: 0x04 + X + Y */ } dh_pair; /* Bitmap for tracking allocated key IDs */ static uint32_t pst_keys[(BT_MESH_PSA_KEY_ID_RANGE_SIZE + 31) / 32]; static psa_key_id_t keyid_alloc(void) { for (int i = 0; i < BT_MESH_PSA_KEY_ID_RANGE_SIZE; i++) { int word = i / 32; int bit = i % 32; if (!(pst_keys[word] & (1U << bit))) { pst_keys[word] |= (1U << bit); return BT_MESH_PSA_KEY_ID_MIN + i; } } return PSA_KEY_ID_NULL; } static int keyid_free(psa_key_id_t key_id) { if (key_id >= BT_MESH_PSA_KEY_ID_MIN && key_id < BT_MESH_PSA_KEY_ID_MIN + BT_MESH_PSA_KEY_ID_RANGE_SIZE) { int idx = key_id - BT_MESH_PSA_KEY_ID_MIN; int word = idx / 32; int bit = idx % 32; pst_keys[word] &= ~(1U << bit); return 0; } return -EIO; } static void keyid_assign(psa_key_id_t key_id) { if (key_id >= BT_MESH_PSA_KEY_ID_MIN && key_id < BT_MESH_PSA_KEY_ID_MIN + BT_MESH_PSA_KEY_ID_RANGE_SIZE) { int idx = key_id - BT_MESH_PSA_KEY_ID_MIN; int word = idx / 32; int bit = idx % 32; pst_keys[word] |= (1U << bit); } } int bt_mesh_crypto_init(void) { psa_status_t status = psa_crypto_init(); if (status != PSA_SUCCESS) { BT_ERR("PSA crypto init failed: %d", status); return -EIO; } dh_pair.is_ready = false; dh_pair.priv_key_id = PSA_KEY_ID_NULL; memset(pst_keys, 0, sizeof(pst_keys)); return 0; } int bt_mesh_encrypt(const struct bt_mesh_key *key, const uint8_t plaintext[16], uint8_t enc_data[16]) { size_t output_len; psa_status_t status; status = psa_cipher_encrypt(key->key, PSA_ALG_ECB_NO_PADDING, plaintext, 16, enc_data, 16, &output_len); if (status != PSA_SUCCESS || output_len != 16) { BT_ERR("PSA cipher encrypt failed: %d", status); return -EIO; } return 0; } int bt_mesh_ccm_encrypt(const struct bt_mesh_key *key, uint8_t nonce[13], const uint8_t *plaintext, size_t len, const uint8_t *aad, size_t aad_len, uint8_t *enc_data, size_t mic_size) { size_t output_len; psa_status_t status; psa_algorithm_t alg = PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, mic_size); status = psa_aead_encrypt(key->key, alg, nonce, 13, aad, aad_len, plaintext, len, enc_data, len + mic_size, &output_len); if (status != PSA_SUCCESS || output_len != len + mic_size) { BT_ERR("PSA AEAD encrypt failed: %d", status); return -EIO; } return 0; } int bt_mesh_ccm_decrypt(const struct bt_mesh_key *key, uint8_t nonce[13], const uint8_t *enc_data, size_t len, const uint8_t *aad, size_t aad_len, uint8_t *plaintext, size_t mic_size) { size_t output_len; psa_status_t status; psa_algorithm_t alg = PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, mic_size); status = psa_aead_decrypt(key->key, alg, nonce, 13, aad, aad_len, enc_data, len + mic_size, plaintext, len, &output_len); if (status != PSA_SUCCESS || output_len != len) { BT_ERR("PSA AEAD decrypt failed: %d", status); return -EIO; } return 0; } int bt_mesh_ccm_encrypt_raw_key(const uint8_t key[16], uint8_t nonce[13], const uint8_t *plaintext, size_t len, const uint8_t *aad, size_t aad_len, uint8_t *enc_data, size_t mic_size) { struct bt_mesh_key mesh_key; int err; err = bt_mesh_key_import(BT_MESH_KEY_TYPE_CCM, key, &mesh_key); if (err) { return err; } err = bt_mesh_ccm_encrypt(&mesh_key, nonce, plaintext, len, aad, aad_len, enc_data, mic_size); psa_destroy_key(mesh_key.key); return err; } int bt_mesh_ccm_decrypt_raw_key(const uint8_t key[16], uint8_t nonce[13], const uint8_t *enc_data, size_t len, const uint8_t *aad, size_t aad_len, uint8_t *plaintext, size_t mic_size) { struct bt_mesh_key mesh_key; int err; err = bt_mesh_key_import(BT_MESH_KEY_TYPE_CCM, key, &mesh_key); if (err) { return err; } err = bt_mesh_ccm_decrypt(&mesh_key, nonce, enc_data, len, aad, aad_len, plaintext, mic_size); psa_destroy_key(mesh_key.key); return err; } int bt_mesh_aes_cmac_mesh_key(const struct bt_mesh_key *key, struct bt_mesh_sg *sg, size_t sg_len, uint8_t mac[16]) { psa_mac_operation_t operation = PSA_MAC_OPERATION_INIT; psa_status_t status; size_t mac_len; status = psa_mac_sign_setup(&operation, key->key, PSA_ALG_CMAC); if (status != PSA_SUCCESS) { BT_ERR("PSA MAC setup failed: %d", status); return -EIO; } for (; sg_len; sg_len--, sg++) { status = psa_mac_update(&operation, sg->data, sg->len); if (status != PSA_SUCCESS) { psa_mac_abort(&operation); BT_ERR("PSA MAC update failed: %d", status); return -EIO; } } status = psa_mac_sign_finish(&operation, mac, 16, &mac_len); if (status != PSA_SUCCESS || mac_len != 16) { BT_ERR("PSA MAC finish failed: %d", status); return -EIO; } return 0; } int bt_mesh_aes_cmac_raw_key(const uint8_t key[16], struct bt_mesh_sg *sg, size_t sg_len, uint8_t mac[16]) { struct bt_mesh_key key_id; int err; err = bt_mesh_key_import(BT_MESH_KEY_TYPE_CMAC, key, &key_id); if (err) { return err; } err = bt_mesh_aes_cmac_mesh_key(&key_id, sg, sg_len, mac); psa_destroy_key(key_id.key); return err; } int bt_mesh_sha256_hmac_raw_key(const uint8_t key[32], struct bt_mesh_sg *sg, size_t sg_len, uint8_t mac[32]) { psa_mac_operation_t operation = PSA_MAC_OPERATION_INIT; psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_key_id_t key_id; psa_status_t status; size_t mac_len; int err = 0; /* Import HMAC key */ psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_MESSAGE); psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_VOLATILE); psa_set_key_algorithm(&attributes, PSA_ALG_HMAC(PSA_ALG_SHA_256)); psa_set_key_type(&attributes, PSA_KEY_TYPE_HMAC); psa_set_key_bits(&attributes, 256); status = psa_import_key(&attributes, key, 32, &key_id); if (status != PSA_SUCCESS) { BT_ERR("PSA import HMAC key failed: %d", status); return -EIO; } psa_reset_key_attributes(&attributes); status = psa_mac_sign_setup(&operation, key_id, PSA_ALG_HMAC(PSA_ALG_SHA_256)); if (status != PSA_SUCCESS) { BT_ERR("PSA HMAC setup failed: %d", status); err = -EIO; goto end; } for (; sg_len; sg_len--, sg++) { status = psa_mac_update(&operation, sg->data, sg->len); if (status != PSA_SUCCESS) { psa_mac_abort(&operation); BT_ERR("PSA HMAC update failed: %d", status); err = -EIO; goto end; } } status = psa_mac_sign_finish(&operation, mac, 32, &mac_len); if (status != PSA_SUCCESS || mac_len != 32) { BT_ERR("PSA HMAC finish failed: %d", status); err = -EIO; } end: psa_destroy_key(key_id); return err; } int bt_mesh_pub_key_gen(void) { psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT; psa_status_t status; uint8_t private_key[PRIV_KEY_SIZE]; size_t key_len; int err; /* Destroy any existing key */ if (dh_pair.priv_key_id != PSA_KEY_ID_NULL) { psa_destroy_key(dh_pair.priv_key_id); dh_pair.priv_key_id = PSA_KEY_ID_NULL; } dh_pair.is_ready = false; /* Generate a random private key (in little-endian format for storage) */ do { err = bt_mesh_rand(private_key, sizeof(private_key)); if (err) { BT_ERR("Failed to generate random private key"); return err; } /* Ensure the private key is valid (non-zero first bytes in BE) */ } while (private_key[0] == 0 && private_key[1] == 0); /* Configure key attributes for ECDH with P-256 */ psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_DERIVE); psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE); psa_set_key_algorithm(&key_attributes, PSA_ALG_ECDH); psa_set_key_type(&key_attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); psa_set_key_bits(&key_attributes, 256); /* Import the private key */ status = psa_import_key(&key_attributes, private_key, sizeof(private_key), &dh_pair.priv_key_id); if (status != PSA_SUCCESS) { BT_ERR("PSA import private key failed: %d", status); psa_reset_key_attributes(&key_attributes); return -EIO; } /* Export public key (PSA computes it from the private key) */ status = psa_export_public_key(dh_pair.priv_key_id, dh_pair.public_key, sizeof(dh_pair.public_key), &key_len); if (status != PSA_SUCCESS || key_len != PUB_KEY_SIZE + 1) { BT_ERR("PSA export public key failed: %d", status); psa_destroy_key(dh_pair.priv_key_id); dh_pair.priv_key_id = PSA_KEY_ID_NULL; psa_reset_key_attributes(&key_attributes); return -EIO; } dh_pair.is_ready = true; psa_reset_key_attributes(&key_attributes); return 0; } const uint8_t *bt_mesh_pub_key_get_raw(void) { if (!dh_pair.is_ready) { if (bt_mesh_pub_key_gen() != 0) { return NULL; } } return &dh_pair.public_key[1]; } void bt_mesh_set_private_key_raw(const uint8_t pri_key[32]) { psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_status_t status; size_t key_len; /* Destroy any existing key */ if (dh_pair.priv_key_id != PSA_KEY_ID_NULL) { psa_destroy_key(dh_pair.priv_key_id); dh_pair.priv_key_id = PSA_KEY_ID_NULL; } dh_pair.is_ready = false; /* Import the provided private key */ psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DERIVE); psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_VOLATILE); psa_set_key_algorithm(&attributes, PSA_ALG_ECDH); psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); psa_set_key_bits(&attributes, 256); status = psa_import_key(&attributes, pri_key, PRIV_KEY_SIZE, &dh_pair.priv_key_id); if (status != PSA_SUCCESS) { BT_ERR("PSA import private key failed: %d", status); psa_reset_key_attributes(&attributes); return; } /* Export public key (PSA computes it from the private key) */ status = psa_export_public_key(dh_pair.priv_key_id, dh_pair.public_key, sizeof(dh_pair.public_key), &key_len); if (status != PSA_SUCCESS || key_len != PUB_KEY_SIZE + 1) { BT_ERR("PSA export public key failed: %d", status); psa_destroy_key(dh_pair.priv_key_id); dh_pair.priv_key_id = PSA_KEY_ID_NULL; psa_reset_key_attributes(&attributes); return; } BT_DBG("Pubkey:%s", bt_hex(&dh_pair.public_key[1], PUB_KEY_SIZE)); BT_DBG("Privkey:%s", bt_hex(pri_key, PRIV_KEY_SIZE)); dh_pair.is_ready = true; psa_reset_key_attributes(&attributes); } bool bt_mesh_check_public_key_raw(const uint8_t key[64]) { psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_key_id_t key_id; psa_status_t status; uint8_t pub_be[PUB_KEY_SIZE + 1]; /* PSA requires 0x04 prefix for uncompressed point */ pub_be[0] = 0x04; /* Convert from little-endian to big-endian */ memcpy(&pub_be[1], key, 32); memcpy(&pub_be[33], key + 32, 32); psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_VERIFY_HASH); psa_set_key_algorithm(&attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_SECP_R1)); psa_set_key_bits(&attributes, 256); status = psa_import_key(&attributes, pub_be, sizeof(pub_be), &key_id); psa_reset_key_attributes(&attributes); if (status != PSA_SUCCESS) { return false; } psa_destroy_key(key_id); return true; } int bt_mesh_dhkey_gen_raw(const uint8_t *pub_key, const uint8_t *priv_key, uint8_t *dhkey) { psa_key_id_t priv_key_id = PSA_KEY_ID_NULL; uint8_t public_key_be[PUB_KEY_SIZE + 1]; psa_status_t status; size_t dh_key_len; int err = 0; if (priv_key) { psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; /* Import custom private key */ psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DERIVE); psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_VOLATILE); psa_set_key_algorithm(&attributes, PSA_ALG_ECDH); psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); psa_set_key_bits(&attributes, 256); status = psa_import_key(&attributes, priv_key, PRIV_KEY_SIZE, &priv_key_id); psa_reset_key_attributes(&attributes); if (status != PSA_SUCCESS) { BT_ERR("PSA import private key failed: %d", status); return -EIO; } } else { priv_key_id = dh_pair.priv_key_id; } /* Prepare public key with 0x04 prefix in big-endian format */ public_key_be[0] = 0x04; /* Convert from little-endian to big-endian */ memcpy(public_key_be + 1, pub_key, 32); memcpy(public_key_be + 33, pub_key + 32, 32); /* Calculate shared secret */ status = psa_raw_key_agreement(PSA_ALG_ECDH, priv_key_id, public_key_be, PUB_KEY_SIZE + 1, dhkey, DH_KEY_SIZE, &dh_key_len); if (status != PSA_SUCCESS || dh_key_len != DH_KEY_SIZE) { BT_ERR("PSA ECDH failed: %d", status); err = -EIO; } if (priv_key && priv_key_id != PSA_KEY_ID_NULL) { psa_destroy_key(priv_key_id); } return err; } int bt_mesh_key_import(enum bt_mesh_key_type type, const uint8_t in[16], struct bt_mesh_key *out) { psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT; psa_status_t status; psa_key_id_t key_id = PSA_KEY_ID_NULL; switch (type) { case BT_MESH_KEY_TYPE_ECB: psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE); psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); psa_set_key_algorithm(&key_attributes, PSA_ALG_ECB_NO_PADDING); break; case BT_MESH_KEY_TYPE_CCM: psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE); psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); psa_set_key_algorithm(&key_attributes, PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG(PSA_ALG_CCM, 4)); break; case BT_MESH_KEY_TYPE_CMAC: psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE); psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_SIGN_MESSAGE); psa_set_key_algorithm(&key_attributes, PSA_ALG_CMAC); break; case BT_MESH_KEY_TYPE_NET: #if CONFIG_BT_SETTINGS key_id = keyid_alloc(); if (key_id == PSA_KEY_ID_NULL) { return -ENOMEM; } psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_PERSISTENT); psa_set_key_id(&key_attributes, key_id); #else psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE); #endif psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_EXPORT); break; case BT_MESH_KEY_TYPE_APP: case BT_MESH_KEY_TYPE_DEV: #if CONFIG_BT_SETTINGS key_id = keyid_alloc(); if (key_id == PSA_KEY_ID_NULL) { return -ENOMEM; } psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_PERSISTENT); psa_set_key_id(&key_attributes, key_id); #else psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE); #endif psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT | PSA_KEY_USAGE_EXPORT); psa_set_key_algorithm(&key_attributes, PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG(PSA_ALG_CCM, 4)); break; default: return -EINVAL; } psa_set_key_type(&key_attributes, PSA_KEY_TYPE_AES); psa_set_key_bits(&key_attributes, 128); status = psa_import_key(&key_attributes, in, 16, &out->key); if (status == PSA_ERROR_ALREADY_EXISTS) { BT_WARN("Key 0x%04x already exists, destroying and reimporting", key_id); psa_destroy_key(key_id); status = psa_import_key(&key_attributes, in, 16, &out->key); } psa_reset_key_attributes(&key_attributes); if (status != PSA_SUCCESS) { BT_ERR("PSA import key failed: %d", status); if (key_id != PSA_KEY_ID_NULL) { keyid_free(key_id); } return -EIO; } return 0; } int bt_mesh_key_export(uint8_t out[16], const struct bt_mesh_key *in) { size_t data_length; psa_status_t status; status = psa_export_key(in->key, out, 16, &data_length); if (status != PSA_SUCCESS || data_length != 16) { BT_ERR("PSA export key failed: %d", status); return -EIO; } return 0; } void bt_mesh_key_assign(struct bt_mesh_key *dst, const struct bt_mesh_key *src) { memcpy(dst, src, sizeof(struct bt_mesh_key)); #if CONFIG_BT_SETTINGS keyid_assign(dst->key); #endif } int bt_mesh_key_destroy(const struct bt_mesh_key *key) { psa_status_t status; status = psa_destroy_key(key->key); if (status != PSA_SUCCESS) { BT_ERR("PSA destroy key failed: %d", status); return -EIO; } #if CONFIG_BT_SETTINGS return keyid_free(key->key); #else return 0; #endif } int bt_mesh_key_compare(const uint8_t raw_key[16], const struct bt_mesh_key *key) { uint8_t out[16]; int err; err = bt_mesh_key_export(out, key); if (err) { return err; } return memcmp(out, raw_key, 16); } int bt_mesh_encrypt_le(const uint8_t key[16], const uint8_t plaintext[16], uint8_t enc_data[16]) { psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_key_id_t key_id; psa_status_t status; uint8_t tmp[16]; size_t output_len; BT_DBG("key %s plaintext %s", bt_hex(key, 16), bt_hex(plaintext, 16)); /* Swap key bytes (LE to BE) */ sys_memcpy_swap(tmp, key, 16); psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT); psa_set_key_algorithm(&attributes, PSA_ALG_ECB_NO_PADDING); psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); psa_set_key_bits(&attributes, 128); status = psa_import_key(&attributes, tmp, 16, &key_id); psa_reset_key_attributes(&attributes); if (status != PSA_SUCCESS) { BT_ERR("PSA import key failed: %d", status); return -EINVAL; } /* Swap plaintext bytes and encrypt */ sys_memcpy_swap(tmp, plaintext, 16); status = psa_cipher_encrypt(key_id, PSA_ALG_ECB_NO_PADDING, tmp, 16, enc_data, 16, &output_len); psa_destroy_key(key_id); if (status != PSA_SUCCESS || output_len != 16) { BT_ERR("PSA encrypt failed: %d", status); return -EINVAL; } /* Swap result bytes (BE to LE) */ sys_mem_swap(enc_data, 16); BT_DBG("enc_data %s", bt_hex(enc_data, 16)); return 0; } int bt_mesh_encrypt_be(const uint8_t key[16], const uint8_t plaintext[16], uint8_t enc_data[16]) { psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_key_id_t key_id; psa_status_t status; size_t output_len; BT_DBG("key %s plaintext %s", bt_hex(key, 16), bt_hex(plaintext, 16)); psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT); psa_set_key_algorithm(&attributes, PSA_ALG_ECB_NO_PADDING); psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); psa_set_key_bits(&attributes, 128); status = psa_import_key(&attributes, key, 16, &key_id); psa_reset_key_attributes(&attributes); if (status != PSA_SUCCESS) { BT_ERR("PSA import key failed: %d", status); return -EINVAL; } status = psa_cipher_encrypt(key_id, PSA_ALG_ECB_NO_PADDING, plaintext, 16, enc_data, 16, &output_len); psa_destroy_key(key_id); if (status != PSA_SUCCESS || output_len != 16) { BT_ERR("PSA encrypt failed: %d", status); return -EINVAL; } BT_DBG("enc_data %s", bt_hex(enc_data, 16)); return 0; } #if 0 int bt_mesh_rand(void *buf, size_t len) { psa_status_t status; if (buf == NULL || len == 0) { return -EINVAL; } status = psa_generate_random(buf, len); return status == PSA_SUCCESS ? 0 : -EIO; } #endif