|  | /* | 
|  | * Copyright (c) 2017 Nordic Semiconductor ASA | 
|  | * Copyright (c) 2015 Intel Corporation | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <stdbool.h> | 
|  | #include <stdint.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #include <zephyr/autoconf.h> | 
|  | #include <zephyr/bluetooth/hci_types.h> | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/sys/__assert.h> | 
|  | #include <zephyr/sys/atomic.h> | 
|  | #include <zephyr/sys/byteorder.h> | 
|  | #include <zephyr/sys/check.h> | 
|  | #include <zephyr/bluetooth/hci.h> | 
|  | #include <zephyr/logging/log.h> | 
|  | #include <zephyr/sys/slist.h> | 
|  | #include <zephyr/sys/util_macro.h> | 
|  | #include <psa/crypto.h> | 
|  | #include <psa/crypto_struct.h> | 
|  | #include <psa/crypto_types.h> | 
|  | #include <psa/crypto_values.h> | 
|  |  | 
|  | #include "long_wq.h" | 
|  | #include "ecc.h" | 
|  | #include "hci_core.h" | 
|  |  | 
|  | #define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL | 
|  | LOG_MODULE_REGISTER(bt_ecc); | 
|  |  | 
|  | static uint8_t pub_key[BT_PUB_KEY_LEN]; | 
|  | static sys_slist_t pub_key_cb_slist; | 
|  | static bt_dh_key_cb_t dh_key_cb; | 
|  |  | 
|  | static void generate_pub_key(struct k_work *work); | 
|  | static void generate_dh_key(struct k_work *work); | 
|  | K_WORK_DEFINE(pub_key_work, generate_pub_key); | 
|  | K_WORK_DEFINE(dh_key_work, generate_dh_key); | 
|  |  | 
|  | enum { | 
|  | PENDING_PUB_KEY, | 
|  | PENDING_DHKEY, | 
|  |  | 
|  | /* Total number of flags - must be at the end of the enum */ | 
|  | NUM_FLAGS, | 
|  | }; | 
|  |  | 
|  | static ATOMIC_DEFINE(flags, NUM_FLAGS); | 
|  |  | 
|  | static struct { | 
|  | uint8_t private_key_be[BT_PRIV_KEY_LEN]; | 
|  |  | 
|  | union { | 
|  | uint8_t public_key_be[BT_PUB_KEY_LEN]; | 
|  | uint8_t dhkey_be[BT_DH_KEY_LEN]; | 
|  | }; | 
|  | } ecc; | 
|  |  | 
|  | /* based on Core Specification 4.2 Vol 3. Part H 2.3.5.6.1 */ | 
|  | static const uint8_t debug_private_key_be[BT_PRIV_KEY_LEN] = { | 
|  | 0x3f, 0x49, 0xf6, 0xd4, 0xa3, 0xc5, 0x5f, 0x38, | 
|  | 0x74, 0xc9, 0xb3, 0xe3, 0xd2, 0x10, 0x3f, 0x50, | 
|  | 0x4a, 0xff, 0x60, 0x7b, 0xeb, 0x40, 0xb7, 0x99, | 
|  | 0x58, 0x99, 0xb8, 0xa6, 0xcd, 0x3c, 0x1a, 0xbd, | 
|  | }; | 
|  |  | 
|  | static const uint8_t debug_public_key[BT_PUB_KEY_LEN] = { | 
|  | /* X */ | 
|  | 0xe6, 0x9d, 0x35, 0x0e, 0x48, 0x01, 0x03, 0xcc, | 
|  | 0xdb, 0xfd, 0xf4, 0xac, 0x11, 0x91, 0xf4, 0xef, | 
|  | 0xb9, 0xa5, 0xf9, 0xe9, 0xa7, 0x83, 0x2c, 0x5e, | 
|  | 0x2c, 0xbe, 0x97, 0xf2, 0xd2, 0x03, 0xb0, 0x20, | 
|  | /* Y */ | 
|  | 0x8b, 0xd2, 0x89, 0x15, 0xd0, 0x8e, 0x1c, 0x74, | 
|  | 0x24, 0x30, 0xed, 0x8f, 0xc2, 0x45, 0x63, 0x76, | 
|  | 0x5c, 0x15, 0x52, 0x5a, 0xbf, 0x9a, 0x32, 0x63, | 
|  | 0x6d, 0xeb, 0x2a, 0x65, 0x49, 0x9c, 0x80, 0xdc | 
|  | }; | 
|  |  | 
|  | bool bt_pub_key_is_debug(uint8_t *cmp_pub_key) | 
|  | { | 
|  | return memcmp(cmp_pub_key, debug_public_key, BT_PUB_KEY_LEN) == 0; | 
|  | } | 
|  |  | 
|  | bool bt_pub_key_is_valid(const uint8_t key[BT_PUB_KEY_LEN]) | 
|  | { | 
|  | uint8_t key_be[BT_PUB_KEY_LEN + 1]; | 
|  | psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; | 
|  | psa_status_t ret; | 
|  | psa_key_id_t handle; | 
|  |  | 
|  | psa_set_key_type(&attr, PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_SECP_R1)); | 
|  | psa_set_key_bits(&attr, 256); | 
|  | psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_DERIVE); | 
|  | psa_set_key_algorithm(&attr, PSA_ALG_ECDH); | 
|  |  | 
|  | /* PSA expects secp256r1 public key to start with a predefined 0x04 byte */ | 
|  | key_be[0] = 0x04; | 
|  | sys_memcpy_swap(&key_be[1], key, BT_PUB_KEY_COORD_LEN); | 
|  | sys_memcpy_swap(&key_be[1 + BT_PUB_KEY_COORD_LEN], &key[BT_PUB_KEY_COORD_LEN], | 
|  | BT_PUB_KEY_COORD_LEN); | 
|  |  | 
|  | ret = psa_import_key(&attr, key_be, sizeof(key_be), &handle); | 
|  | psa_reset_key_attributes(&attr); | 
|  |  | 
|  | if (ret == PSA_SUCCESS) { | 
|  | psa_destroy_key(handle); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | LOG_ERR("psa_import_key() returned status %d", ret); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static void set_key_attributes(psa_key_attributes_t *attr) | 
|  | { | 
|  | psa_set_key_type(attr, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); | 
|  | psa_set_key_bits(attr, 256); | 
|  | psa_set_key_usage_flags(attr, PSA_KEY_USAGE_EXPORT | PSA_KEY_USAGE_DERIVE); | 
|  | psa_set_key_algorithm(attr, PSA_ALG_ECDH); | 
|  | } | 
|  |  | 
|  | static void generate_pub_key(struct k_work *work) | 
|  | { | 
|  | psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; | 
|  | struct bt_pub_key_cb *cb; | 
|  | psa_key_id_t key_id; | 
|  | uint8_t tmp_pub_key_buf[BT_PUB_KEY_LEN + 1]; | 
|  | size_t tmp_len; | 
|  | int err; | 
|  | psa_status_t ret; | 
|  |  | 
|  | set_key_attributes(&attr); | 
|  |  | 
|  | ret = psa_generate_key(&attr, &key_id); | 
|  | if (ret != PSA_SUCCESS) { | 
|  | LOG_ERR("Failed to generate ECC key %d", ret); | 
|  | err = BT_HCI_ERR_UNSPECIFIED; | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | ret = psa_export_public_key(key_id, tmp_pub_key_buf, sizeof(tmp_pub_key_buf), &tmp_len); | 
|  | if (ret != PSA_SUCCESS) { | 
|  | LOG_ERR("Failed to export ECC public key %d", ret); | 
|  | err = BT_HCI_ERR_UNSPECIFIED; | 
|  | goto done; | 
|  | } | 
|  | /* secp256r1 PSA exported public key has an extra 0x04 predefined byte at | 
|  | * the beginning of the buffer which is not part of the coordinate so | 
|  | * we remove that. | 
|  | */ | 
|  | memcpy(ecc.public_key_be, &tmp_pub_key_buf[1], BT_PUB_KEY_LEN); | 
|  |  | 
|  | ret = psa_export_key(key_id, ecc.private_key_be, BT_PRIV_KEY_LEN, &tmp_len); | 
|  | if (ret != PSA_SUCCESS) { | 
|  | LOG_ERR("Failed to export ECC private key %d", ret); | 
|  | err = BT_HCI_ERR_UNSPECIFIED; | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | ret = psa_destroy_key(key_id); | 
|  | if (ret != PSA_SUCCESS) { | 
|  | LOG_ERR("Failed to destroy ECC key ID %d", ret); | 
|  | err = BT_HCI_ERR_UNSPECIFIED; | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | sys_memcpy_swap(pub_key, ecc.public_key_be, BT_PUB_KEY_COORD_LEN); | 
|  | sys_memcpy_swap(&pub_key[BT_PUB_KEY_COORD_LEN], | 
|  | &ecc.public_key_be[BT_PUB_KEY_COORD_LEN], BT_PUB_KEY_COORD_LEN); | 
|  |  | 
|  | atomic_set_bit(bt_dev.flags, BT_DEV_HAS_PUB_KEY); | 
|  | err = 0; | 
|  |  | 
|  | done: | 
|  | atomic_clear_bit(flags, PENDING_PUB_KEY); | 
|  |  | 
|  | /* Change to cooperative priority while we do the callbacks */ | 
|  | k_sched_lock(); | 
|  |  | 
|  | SYS_SLIST_FOR_EACH_CONTAINER(&pub_key_cb_slist, cb, node) { | 
|  | if (cb->func) { | 
|  | cb->func(err ? NULL : pub_key); | 
|  | } | 
|  | } | 
|  |  | 
|  | sys_slist_init(&pub_key_cb_slist); | 
|  |  | 
|  | k_sched_unlock(); | 
|  | } | 
|  |  | 
|  | static void generate_dh_key(struct k_work *work) | 
|  | { | 
|  | int err; | 
|  |  | 
|  | psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; | 
|  | psa_key_id_t key_id; | 
|  | psa_status_t ret; | 
|  | /* PSA expects secp256r1 public key to start with a predefined 0x04 byte | 
|  | * at the beginning the buffer. | 
|  | */ | 
|  | uint8_t tmp_pub_key_buf[BT_PUB_KEY_LEN + 1] = { 0x04 }; | 
|  | size_t tmp_len; | 
|  |  | 
|  | set_key_attributes(&attr); | 
|  |  | 
|  | const uint8_t *priv_key = (IS_ENABLED(CONFIG_BT_USE_DEBUG_KEYS) ? | 
|  | debug_private_key_be : | 
|  | ecc.private_key_be); | 
|  | ret = psa_import_key(&attr, priv_key, BT_PRIV_KEY_LEN, &key_id); | 
|  | if (ret != PSA_SUCCESS) { | 
|  | err = -EIO; | 
|  | LOG_ERR("Failed to import the private key for key agreement %d", ret); | 
|  | goto exit; | 
|  | } | 
|  |  | 
|  | memcpy(&tmp_pub_key_buf[1], ecc.public_key_be, BT_PUB_KEY_LEN); | 
|  | ret = psa_raw_key_agreement(PSA_ALG_ECDH, key_id, tmp_pub_key_buf, sizeof(tmp_pub_key_buf), | 
|  | ecc.dhkey_be, BT_DH_KEY_LEN, &tmp_len); | 
|  | if (ret != PSA_SUCCESS) { | 
|  | err = -EIO; | 
|  | LOG_ERR("Raw key agreement failed %d", ret); | 
|  | goto exit; | 
|  | } | 
|  |  | 
|  | ret = psa_destroy_key(key_id); | 
|  | if (ret != PSA_SUCCESS) { | 
|  | LOG_ERR("Failed to destroy the key %d", ret); | 
|  | err = -EIO; | 
|  | goto exit; | 
|  | } | 
|  |  | 
|  | err = 0; | 
|  |  | 
|  | exit: | 
|  | /* Change to cooperative priority while we do the callback */ | 
|  | k_sched_lock(); | 
|  |  | 
|  | if (dh_key_cb) { | 
|  | bt_dh_key_cb_t cb = dh_key_cb; | 
|  |  | 
|  | dh_key_cb = NULL; | 
|  | atomic_clear_bit(flags, PENDING_DHKEY); | 
|  |  | 
|  | if (err) { | 
|  | cb(NULL); | 
|  | } else { | 
|  | uint8_t dhkey[BT_DH_KEY_LEN]; | 
|  |  | 
|  | sys_memcpy_swap(dhkey, ecc.dhkey_be, sizeof(ecc.dhkey_be)); | 
|  | cb(dhkey); | 
|  | } | 
|  | } | 
|  |  | 
|  | k_sched_unlock(); | 
|  | } | 
|  |  | 
|  | int bt_pub_key_gen(struct bt_pub_key_cb *new_cb) | 
|  | { | 
|  | struct bt_pub_key_cb *cb; | 
|  |  | 
|  | if (IS_ENABLED(CONFIG_BT_USE_DEBUG_KEYS)) { | 
|  | atomic_set_bit(bt_dev.flags, BT_DEV_HAS_PUB_KEY); | 
|  | __ASSERT_NO_MSG(new_cb->func != NULL); | 
|  | new_cb->func(debug_public_key); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (!new_cb) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | SYS_SLIST_FOR_EACH_CONTAINER(&pub_key_cb_slist, cb, node) { | 
|  | if (cb == new_cb) { | 
|  | LOG_DBG("Callback already registered"); | 
|  | return -EALREADY; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (atomic_test_bit(flags, PENDING_DHKEY)) { | 
|  | LOG_WRN("Busy performing another ECDH operation"); | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | sys_slist_prepend(&pub_key_cb_slist, &new_cb->node); | 
|  |  | 
|  | if (atomic_test_and_set_bit(flags, PENDING_PUB_KEY)) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | atomic_clear_bit(bt_dev.flags, BT_DEV_HAS_PUB_KEY); | 
|  |  | 
|  | if (IS_ENABLED(CONFIG_BT_LONG_WQ)) { | 
|  | bt_long_wq_submit(&pub_key_work); | 
|  | } else { | 
|  | k_work_submit(&pub_key_work); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void bt_pub_key_hci_disrupted(void) | 
|  | { | 
|  | struct bt_pub_key_cb *cb; | 
|  |  | 
|  | atomic_clear_bit(flags, PENDING_PUB_KEY); | 
|  |  | 
|  | SYS_SLIST_FOR_EACH_CONTAINER(&pub_key_cb_slist, cb, node) { | 
|  | if (cb->func) { | 
|  | cb->func(NULL); | 
|  | } | 
|  | } | 
|  |  | 
|  | sys_slist_init(&pub_key_cb_slist); | 
|  | } | 
|  |  | 
|  | const uint8_t *bt_pub_key_get(void) | 
|  | { | 
|  | if (IS_ENABLED(CONFIG_BT_USE_DEBUG_KEYS)) { | 
|  | return debug_public_key; | 
|  | } | 
|  |  | 
|  | if (atomic_test_bit(bt_dev.flags, BT_DEV_HAS_PUB_KEY)) { | 
|  | return pub_key; | 
|  | } | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | int bt_dh_key_gen(const uint8_t remote_pk[BT_PUB_KEY_LEN], bt_dh_key_cb_t cb) | 
|  | { | 
|  | if (dh_key_cb == cb) { | 
|  | return -EALREADY; | 
|  | } | 
|  |  | 
|  | if (!atomic_test_bit(bt_dev.flags, BT_DEV_HAS_PUB_KEY)) { | 
|  | return -EADDRNOTAVAIL; | 
|  | } | 
|  |  | 
|  | if (dh_key_cb || | 
|  | atomic_test_bit(flags, PENDING_PUB_KEY) || | 
|  | atomic_test_and_set_bit(flags, PENDING_DHKEY)) { | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | dh_key_cb = cb; | 
|  |  | 
|  | /* Convert X and Y coordinates from little-endian to | 
|  | * big-endian (expected by the crypto API). | 
|  | */ | 
|  | sys_memcpy_swap(ecc.public_key_be, remote_pk, BT_PUB_KEY_COORD_LEN); | 
|  | sys_memcpy_swap(&ecc.public_key_be[BT_PUB_KEY_COORD_LEN], | 
|  | &remote_pk[BT_PUB_KEY_COORD_LEN], BT_PUB_KEY_COORD_LEN); | 
|  |  | 
|  | if (IS_ENABLED(CONFIG_BT_LONG_WQ)) { | 
|  | bt_long_wq_submit(&dh_key_work); | 
|  | } else { | 
|  | k_work_submit(&dh_key_work); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #ifdef ZTEST_UNITTEST | 
|  | uint8_t const *bt_ecc_get_public_key(void) | 
|  | { | 
|  | return pub_key; | 
|  | } | 
|  |  | 
|  | uint8_t const *bt_ecc_get_internal_debug_public_key(void) | 
|  | { | 
|  | return debug_public_key; | 
|  | } | 
|  |  | 
|  | sys_slist_t *bt_ecc_get_pub_key_cb_slist(void) | 
|  | { | 
|  | return &pub_key_cb_slist; | 
|  | } | 
|  |  | 
|  | bt_dh_key_cb_t *bt_ecc_get_dh_key_cb(void) | 
|  | { | 
|  | return &dh_key_cb; | 
|  | } | 
|  | #endif /* ZTEST_UNITTEST */ |