Commit adds destruction of the persisted in PSA ITS key if mesh does not own it (zero bit in the bitmap of persisted keys). This is not standard mesh behavior, but might happen if something happens between removing key data in mesh and in the crypto library (for example power off in bettwen). Previously, mesh wasn't able to import key with gotten stuck key id. The current fix reproduces more robust behavior. Signed-off-by: Aleksandr Khromykh <aleksandr.khromykh@nordicsemi.no>
529 lines
13 KiB
C
529 lines
13 KiB
C
/*
|
|
* Copyright (c) 2023 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <errno.h>
|
|
|
|
#include <zephyr/bluetooth/mesh.h>
|
|
#include <zephyr/psa/key_ids.h>
|
|
#include <zephyr/sys/check.h>
|
|
|
|
#define LOG_LEVEL CONFIG_BT_MESH_CRYPTO_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(bt_mesh_crypto_psa);
|
|
|
|
#include "mesh.h"
|
|
#include "crypto.h"
|
|
#include "prov.h"
|
|
|
|
/* Mesh requires to keep in persistent memory network keys (2 keys per subnetwork),
|
|
* application keys (2 real keys per 1 configured) and device key + device key candidate.
|
|
*/
|
|
#if defined CONFIG_BT_MESH_CDB
|
|
#define BT_MESH_CDB_KEY_ID_RANGE_SIZE (2 * SUBNET_COUNT + \
|
|
2 * APP_KEY_COUNT + NODE_COUNT)
|
|
#else
|
|
#define BT_MESH_CDB_KEY_ID_RANGE_SIZE 0
|
|
#endif
|
|
|
|
#define BT_MESH_PSA_KEY_ID_MIN ZEPHYR_PSA_BT_MESH_KEY_ID_RANGE_BEGIN
|
|
|
|
#define BT_MESH_PSA_KEY_ID_RANGE_SIZE (2 * CONFIG_BT_MESH_SUBNET_COUNT + \
|
|
2 * CONFIG_BT_MESH_APP_KEY_COUNT + 2 + BT_MESH_CDB_KEY_ID_RANGE_SIZE)
|
|
BUILD_ASSERT(BT_MESH_PSA_KEY_ID_RANGE_SIZE <= ZEPHYR_PSA_BT_MESH_KEY_ID_RANGE_SIZE,
|
|
"PSA key ID range exceeds officially allocated range.");
|
|
|
|
BUILD_ASSERT(PSA_MAC_LENGTH(PSA_KEY_TYPE_AES, 128, PSA_ALG_CMAC) == 16,
|
|
"MAC length should be 16 bytes for 128-bits key for CMAC-AES");
|
|
|
|
BUILD_ASSERT(PSA_MAC_LENGTH(PSA_KEY_TYPE_HMAC, 256, PSA_ALG_HMAC(PSA_ALG_SHA_256)) == 32,
|
|
"MAC length should be 32 bytes for 256-bits key for HMAC-SHA");
|
|
|
|
static struct {
|
|
bool is_ready;
|
|
psa_key_id_t priv_key_id;
|
|
uint8_t public_key_be[PUB_KEY_SIZE + 1];
|
|
} dh_pair;
|
|
|
|
static ATOMIC_DEFINE(pst_keys, BT_MESH_PSA_KEY_ID_RANGE_SIZE);
|
|
|
|
int bt_mesh_crypto_init(void)
|
|
{
|
|
if (psa_crypto_init() != PSA_SUCCESS) {
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_mesh_encrypt(const struct bt_mesh_key *key, const uint8_t plaintext[16],
|
|
uint8_t enc_data[16])
|
|
{
|
|
uint32_t output_len;
|
|
psa_status_t status;
|
|
int err = 0;
|
|
|
|
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) {
|
|
err = -EIO;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
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)
|
|
{
|
|
uint32_t output_len;
|
|
psa_status_t status;
|
|
int err = 0;
|
|
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) {
|
|
err = -EIO;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
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)
|
|
{
|
|
uint32_t output_len;
|
|
psa_status_t status;
|
|
int err = 0;
|
|
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) {
|
|
err = -EIO;
|
|
}
|
|
|
|
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_algorithm_t alg = PSA_ALG_CMAC;
|
|
psa_status_t status;
|
|
|
|
status = psa_mac_sign_setup(&operation, key->key, alg);
|
|
if (status != PSA_SUCCESS) {
|
|
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);
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
size_t mac_len;
|
|
|
|
status = psa_mac_sign_finish(&operation, mac, 16, &mac_len);
|
|
if (status != PSA_SUCCESS) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (mac_len != 16) {
|
|
return -ERANGE;
|
|
}
|
|
|
|
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_algorithm_t alg = PSA_ALG_HMAC(PSA_ALG_SHA_256);
|
|
|
|
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
|
|
psa_key_id_t key_id;
|
|
|
|
psa_status_t status;
|
|
int err = 0;
|
|
|
|
/* Import a 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) {
|
|
err = -EIO;
|
|
goto end;
|
|
}
|
|
|
|
psa_reset_key_attributes(&attributes);
|
|
|
|
status = psa_mac_sign_setup(&operation, key_id, alg);
|
|
if (status != PSA_SUCCESS) {
|
|
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);
|
|
err = -EIO;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
size_t mac_len;
|
|
|
|
status = psa_mac_sign_finish(&operation, mac, 32, &mac_len);
|
|
if (status != PSA_SUCCESS) {
|
|
err = -EIO;
|
|
goto end;
|
|
}
|
|
|
|
if (mac_len != 32) {
|
|
err = -ERANGE;
|
|
}
|
|
|
|
end:
|
|
/* Destroy the key */
|
|
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;
|
|
int err = 0;
|
|
size_t key_len;
|
|
|
|
psa_destroy_key(dh_pair.priv_key_id);
|
|
dh_pair.is_ready = false;
|
|
|
|
/* Crypto settings for ECDH using the SHA256 hashing algorithm,
|
|
* the secp256r1 curve
|
|
*/
|
|
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);
|
|
|
|
/* Generate a key pair */
|
|
status = psa_generate_key(&key_attributes, &dh_pair.priv_key_id);
|
|
if (status != PSA_SUCCESS) {
|
|
err = -EIO;
|
|
goto end;
|
|
}
|
|
|
|
status = psa_export_public_key(dh_pair.priv_key_id, dh_pair.public_key_be,
|
|
sizeof(dh_pair.public_key_be), &key_len);
|
|
if (status != PSA_SUCCESS) {
|
|
err = -EIO;
|
|
goto end;
|
|
}
|
|
|
|
if (key_len != PUB_KEY_SIZE + 1) {
|
|
err = -ERANGE;
|
|
goto end;
|
|
}
|
|
|
|
dh_pair.is_ready = true;
|
|
|
|
end:
|
|
psa_reset_key_attributes(&key_attributes);
|
|
|
|
return err;
|
|
}
|
|
|
|
const uint8_t *bt_mesh_pub_key_get(void)
|
|
{
|
|
return dh_pair.is_ready ? dh_pair.public_key_be + 1 : NULL;
|
|
}
|
|
|
|
BUILD_ASSERT(PSA_RAW_KEY_AGREEMENT_OUTPUT_SIZE(PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1),
|
|
256) == DH_KEY_SIZE,
|
|
"Diffie-Hellman shared secret size should be the same in PSA and Bluetooth Mesh");
|
|
|
|
BUILD_ASSERT(PSA_KEY_EXPORT_ECC_PUBLIC_KEY_MAX_SIZE(256) == PUB_KEY_SIZE + 1,
|
|
"Exported PSA public key should be 1 byte larger than Bluetooth Mesh public key");
|
|
|
|
int bt_mesh_dhkey_gen(const uint8_t *pub_key, const uint8_t *priv_key, uint8_t *dhkey)
|
|
{
|
|
int err = 0;
|
|
psa_key_id_t priv_key_id = PSA_KEY_ID_NULL;
|
|
uint8_t public_key_repr[PUB_KEY_SIZE + 1];
|
|
psa_status_t status;
|
|
size_t dh_key_len;
|
|
|
|
if (priv_key) {
|
|
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
|
|
|
|
/* Import a 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);
|
|
if (status != PSA_SUCCESS) {
|
|
err = -EIO;
|
|
goto end;
|
|
}
|
|
|
|
psa_reset_key_attributes(&attributes);
|
|
} else {
|
|
priv_key_id = dh_pair.priv_key_id;
|
|
}
|
|
|
|
/* For elliptic curve key pairs for Weierstrass curve families (PSA_ECC_FAMILY_SECP_R1)
|
|
* the representations of public key is:
|
|
* - The byte 0x04;
|
|
* - x_P as a ceiling(m/8)-byte string, big-endian;
|
|
* - y_P as a ceiling(m/8)-byte string, big-endian.
|
|
*/
|
|
public_key_repr[0] = 0x04;
|
|
memcpy(public_key_repr + 1, pub_key, PUB_KEY_SIZE);
|
|
|
|
/* Calculate the secret */
|
|
status = psa_raw_key_agreement(PSA_ALG_ECDH, priv_key_id, public_key_repr,
|
|
PUB_KEY_SIZE + 1, dhkey, DH_KEY_SIZE, &dh_key_len);
|
|
if (status != PSA_SUCCESS) {
|
|
err = -EIO;
|
|
goto end;
|
|
}
|
|
|
|
if (dh_key_len != DH_KEY_SIZE) {
|
|
err = -ERANGE;
|
|
}
|
|
|
|
end:
|
|
|
|
if (priv_key) {
|
|
psa_destroy_key(priv_key_id);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static psa_key_id_t keyid_alloc(void)
|
|
{
|
|
for (int i = 0; i < BT_MESH_PSA_KEY_ID_RANGE_SIZE; i++) {
|
|
if (!atomic_test_bit(pst_keys, i)) {
|
|
atomic_set_bit(pst_keys, i);
|
|
return BT_MESH_PSA_KEY_ID_MIN + i;
|
|
}
|
|
}
|
|
|
|
return PSA_KEY_ID_NULL;
|
|
}
|
|
|
|
static int keyid_free(psa_key_id_t key_id)
|
|
{
|
|
if (IN_RANGE(key_id, BT_MESH_PSA_KEY_ID_MIN,
|
|
BT_MESH_PSA_KEY_ID_MIN + BT_MESH_PSA_KEY_ID_RANGE_SIZE - 1)) {
|
|
atomic_clear_bit(pst_keys, key_id - BT_MESH_PSA_KEY_ID_MIN);
|
|
return 0;
|
|
}
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
static void keyid_assign(psa_key_id_t key_id)
|
|
{
|
|
if (IN_RANGE(key_id, BT_MESH_PSA_KEY_ID_MIN,
|
|
BT_MESH_PSA_KEY_ID_MIN + BT_MESH_PSA_KEY_ID_RANGE_SIZE - 1)) {
|
|
atomic_set_bit(pst_keys, key_id - BT_MESH_PSA_KEY_ID_MIN);
|
|
}
|
|
}
|
|
|
|
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;
|
|
int err = 0;
|
|
|
|
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 (IS_ENABLED(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);
|
|
}
|
|
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 (IS_ENABLED(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);
|
|
}
|
|
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 -EIO;
|
|
}
|
|
|
|
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) {
|
|
LOG_WRN("Key with ID 0x%4x already exists (desync between mesh and PSA ITS)",
|
|
key_id);
|
|
(void)psa_destroy_key(key_id);
|
|
status = psa_import_key(&key_attributes, in, 16, &out->key);
|
|
}
|
|
|
|
err = status == PSA_SUCCESS ? 0 : -EIO;
|
|
if (err && key_id != PSA_KEY_ID_NULL) {
|
|
keyid_free(key_id);
|
|
}
|
|
|
|
psa_reset_key_attributes(&key_attributes);
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_mesh_key_export(uint8_t out[16], const struct bt_mesh_key *in)
|
|
{
|
|
size_t data_length;
|
|
|
|
if (psa_export_key(in->key, out, 16, &data_length) != PSA_SUCCESS) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (data_length != 16) {
|
|
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 (IS_ENABLED(CONFIG_BT_SETTINGS)) {
|
|
keyid_assign(dst->key);
|
|
}
|
|
}
|
|
|
|
int bt_mesh_key_destroy(const struct bt_mesh_key *key)
|
|
{
|
|
if (psa_destroy_key(key->key) != PSA_SUCCESS) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
|
|
return keyid_free(key->key);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
__weak int bt_rand(void *buf, size_t len)
|
|
{
|
|
CHECKIF(buf == NULL || len == 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return psa_generate_random(buf, len) == PSA_SUCCESS ? 0 : -EIO;
|
|
}
|