diff --git a/components/bt/CMakeLists.txt b/components/bt/CMakeLists.txt index 9caf183ff7..af71a578fa 100644 --- a/components/bt/CMakeLists.txt +++ b/components/bt/CMakeLists.txt @@ -720,7 +720,12 @@ if(CONFIG_BT_ENABLED) ) endif() - if(NOT (CONFIG_BT_LE_CRYPTO_STACK_MBEDTLS OR CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS)) + # Compile TinyCrypt if: + # 1. Controller uses TinyCrypt (not mbedTLS), OR + # 2. NimBLE uses TinyCrypt (not mbedTLS), OR + # 3. Bluedroid Host SMP uses TinyCrypt + if(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT OR + (NOT CONFIG_BT_LE_CRYPTO_STACK_MBEDTLS AND NOT CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS)) list(APPEND include_dirs common/tinycrypt/include common/tinycrypt/port diff --git a/components/bt/common/Kconfig.in b/components/bt/common/Kconfig.in index 4bcfaa79ce..0c0b040ab4 100644 --- a/components/bt/common/Kconfig.in +++ b/components/bt/common/Kconfig.in @@ -6,6 +6,43 @@ config BT_ALARM_MAX_NUM This option decides the maximum number of alarms which could be used by Bluetooth host. +choice BT_SMP_CRYPTO_STACK + prompt "SMP cryptographic stack" + depends on (BT_BLE_SMP_ENABLE || BT_SMP_ENABLE || BT_NIMBLE_SECURITY_ENABLE) + default BT_SMP_CRYPTO_STACK_NATIVE + help + Select the cryptographic library to use for SMP operations (AES, AES-CMAC, ECDH P-256). + + config BT_SMP_CRYPTO_STACK_NATIVE + bool "Native Bluedroid implementation" + depends on (BT_BLE_SMP_ENABLE || BT_SMP_ENABLE) + help + Use the built-in Bluedroid cryptographic implementation. + This provides compatibility with all features. + This option is only available for Bluedroid host. + + config BT_SMP_CRYPTO_STACK_TINYCRYPT + bool "TinyCrypt" + help + Use TinyCrypt library for cryptographic operations. + TinyCrypt is a lightweight cryptographic library designed for constrained devices. + This can reduce code size compared to the native implementation. + This is the default option. + + config BT_SMP_CRYPTO_STACK_MBEDTLS + bool "mbedTLS" + select MBEDTLS_AES_C + select MBEDTLS_CMAC_C + select MBEDTLS_ECDH_C + select MBEDTLS_ECP_C + select MBEDTLS_ECP_DP_SECP256R1_ENABLED + help + Use mbedTLS library for cryptographic operations. + This can provide hardware acceleration on supported platforms and reduce code size + by sharing crypto implementations with other components. + +endchoice + menu "BLE Log" source "$IDF_PATH/components/bt/common/ble_log/Kconfig.in" endmenu diff --git a/components/bt/host/bluedroid/Kconfig.in b/components/bt/host/bluedroid/Kconfig.in index 2bd78f05f1..892ac18306 100644 --- a/components/bt/host/bluedroid/Kconfig.in +++ b/components/bt/host/bluedroid/Kconfig.in @@ -377,6 +377,16 @@ config BT_GATTS_SECURITY_LEVELS_CHAR help Enable LE GATT Security Levels Characteristic +config BT_GATTS_KEY_MATERIAL_CHAR + bool "Enable Encrypted Data Key Material Characteristic" + depends on BT_GATTS_ENABLE + default n + help + Enable the Encrypted Data Key Material characteristic in GAP service. + This characteristic allows advertising data to be decrypted and authenticated + using the key material (session key + IV) as defined in Bluetooth Core + Specification Version 5.4. The characteristic requires encrypted link to read. + menuconfig BT_GATTC_ENABLE bool "Include GATT client module(GATTC)" depends on BT_BLE_ENABLED diff --git a/components/bt/host/bluedroid/api/esp_gap_ble_api.c b/components/bt/host/bluedroid/api/esp_gap_ble_api.c index 2e60cf4dd4..aa6a979833 100644 --- a/components/bt/host/bluedroid/api/esp_gap_ble_api.c +++ b/components/bt/host/bluedroid/api/esp_gap_ble_api.c @@ -440,6 +440,28 @@ esp_err_t esp_ble_gap_get_device_name(void) return (btc_transfer_context(&msg, NULL, 0, NULL, NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) +esp_err_t esp_ble_gap_set_key_material(const uint8_t session_key[16], const uint8_t iv[8]) +{ + btc_msg_t msg = {0}; + btc_ble_gap_args_t arg; + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + if (session_key == NULL || iv == NULL) { + return ESP_ERR_INVALID_ARG; + } + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GAP_BLE; + msg.act = BTC_GAP_BLE_ACT_SET_KEY_MATERIAL; + memcpy(arg.set_key_material.session_key, session_key, 16); + memcpy(arg.set_key_material.iv, iv, 8); + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gap_args_t), NULL, NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} +#endif // (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) + esp_err_t esp_ble_gap_get_local_used_addr(esp_bd_addr_t local_used_addr, uint8_t * addr_type) { if(esp_bluedroid_get_status() != (ESP_BLUEDROID_STATUS_ENABLED)) { diff --git a/components/bt/host/bluedroid/api/include/api/esp_gap_ble_api.h b/components/bt/host/bluedroid/api/include/api/esp_gap_ble_api.h index 102fdfd66b..704e2c1086 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_gap_ble_api.h +++ b/components/bt/host/bluedroid/api/include/api/esp_gap_ble_api.h @@ -3120,6 +3120,25 @@ esp_err_t esp_ble_gap_set_device_name(const char *name); */ esp_err_t esp_ble_gap_get_device_name(void); +#if defined(CONFIG_BT_GATTS_KEY_MATERIAL_CHAR) && CONFIG_BT_GATTS_KEY_MATERIAL_CHAR +/** + * @brief Set the Encrypted Data Key Material in GAP service + * + * This function sets the session key and IV that will be exposed + * through the Key Material characteristic (UUID 0x2B88) in the GAP service. + * The Key Material allows central devices to decrypt encrypted advertising data. + * + * @param[in] session_key - 16-byte (128-bit) session key for AES-CCM encryption + * @param[in] iv - 8-byte (64-bit) initialization vector + * + * @return + * - ESP_OK : success + * - other : failed + * + */ +esp_err_t esp_ble_gap_set_key_material(const uint8_t session_key[16], const uint8_t iv[8]); +#endif // CONFIG_BT_GATTS_KEY_MATERIAL_CHAR + /** * @brief This function is called to get local used address and address type. * uint8_t *esp_bt_dev_get_address(void) get the public address diff --git a/components/bt/host/bluedroid/bta/dm/bta_dm_act.c b/components/bt/host/bluedroid/bta/dm/bta_dm_act.c index 724ea7c67a..5f6e490b5b 100644 --- a/components/bt/host/bluedroid/bta/dm/bta_dm_act.c +++ b/components/bt/host/bluedroid/bta/dm/bta_dm_act.c @@ -5406,6 +5406,22 @@ void bta_dm_ble_config_local_icon (tBTA_DM_MSG *p_data) BTM_BleConfigLocalIcon (p_data->ble_local_icon.icon); } +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) +/******************************************************************************* +** +** Function bta_dm_ble_set_key_material +** +** Description This function sets the Encrypted Data Key Material. +** +** +*******************************************************************************/ +void bta_dm_ble_set_key_material (tBTA_DM_MSG *p_data) +{ + BTM_BleSetKeyMaterial (p_data->ble_key_material.session_key, + p_data->ble_key_material.iv); +} +#endif + #if (BLE_HOST_BLE_OBSERVE_EN == TRUE) /******************************************************************************* ** diff --git a/components/bt/host/bluedroid/bta/dm/bta_dm_api.c b/components/bt/host/bluedroid/bta/dm/bta_dm_api.c index cd06eb4fd1..6dcce7beac 100644 --- a/components/bt/host/bluedroid/bta/dm/bta_dm_api.c +++ b/components/bt/host/bluedroid/bta/dm/bta_dm_api.c @@ -2223,6 +2223,41 @@ void BTA_DmBleConfigLocalIcon(uint16_t icon) } } +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) +/******************************************************************************* +** +** Function BTA_DmBleSetKeyMaterial +** +** Description Set the Encrypted Data Key Material in GAP service +** +** Parameters: session_key - 16-byte session key (must not be NULL) +** iv - 8-byte initialization vector (must not be NULL) +** +** Returns void +** +*******************************************************************************/ +void BTA_DmBleSetKeyMaterial(const uint8_t *session_key, const uint8_t *iv) +{ + tBTA_DM_API_KEY_MATERIAL *p_msg; + + if (session_key == NULL || iv == NULL) { + APPL_TRACE_ERROR("%s: NULL pointer parameter", __func__); + return; + } + + if ((p_msg = (tBTA_DM_API_KEY_MATERIAL *) osi_malloc(sizeof(tBTA_DM_API_KEY_MATERIAL))) != NULL) { + memset(p_msg, 0, sizeof(tBTA_DM_API_KEY_MATERIAL)); + + p_msg->hdr.event = BTA_DM_API_KEY_MATERIAL_EVT; + memcpy(p_msg->session_key, session_key, 16); + memcpy(p_msg->iv, iv, 8); + bta_sys_sendmsg(p_msg); + } else { + APPL_TRACE_ERROR("%s: failed to allocate memory", __func__); + } +} +#endif + #if (BLE_HOST_BLE_MULTI_ADV_EN == TRUE) /******************************************************************************* ** diff --git a/components/bt/host/bluedroid/bta/dm/bta_dm_main.c b/components/bt/host/bluedroid/bta/dm/bta_dm_main.c index 77bc60a7f2..1d69fb2434 100644 --- a/components/bt/host/bluedroid/bta/dm/bta_dm_main.c +++ b/components/bt/host/bluedroid/bta/dm/bta_dm_main.c @@ -170,6 +170,9 @@ const tBTA_DM_ACTION bta_dm_action[BTA_DM_MAX_EVT] = { bta_dm_ble_config_local_privacy, /* BTA_DM_API_LOCAL_PRIVACY_EVT */ #endif bta_dm_ble_config_local_icon, /* BTA_DM_API_LOCAL_ICON_EVT */ +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) + bta_dm_ble_set_key_material, /* BTA_DM_API_KEY_MATERIAL_EVT */ +#endif #if (BLE_42_ADV_EN == TRUE) bta_dm_ble_set_adv_params_all, /* BTA_DM_API_BLE_ADV_PARAM_All_EVT */ bta_dm_ble_set_adv_config, /* BTA_DM_API_BLE_SET_ADV_CONFIG_EVT */ diff --git a/components/bt/host/bluedroid/bta/dm/include/bta_dm_int.h b/components/bt/host/bluedroid/bta/dm/include/bta_dm_int.h index 9ef189a26f..bc625d3df8 100644 --- a/components/bt/host/bluedroid/bta/dm/include/bta_dm_int.h +++ b/components/bt/host/bluedroid/bta/dm/include/bta_dm_int.h @@ -159,6 +159,9 @@ enum { BTA_DM_API_LOCAL_PRIVACY_EVT, #endif BTA_DM_API_LOCAL_ICON_EVT, +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) + BTA_DM_API_KEY_MATERIAL_EVT, +#endif /*******This event added by Yulong at 2016/10/20 to support setting the ble advertising param by the APP******/ @@ -853,6 +856,14 @@ typedef struct { uint16_t icon; } tBTA_DM_API_LOCAL_ICON; +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) +typedef struct { + BT_HDR hdr; + uint8_t session_key[16]; + uint8_t iv[8]; +} tBTA_DM_API_KEY_MATERIAL; +#endif + /* set scan parameter for BLE connections */ typedef struct { BT_HDR hdr; @@ -1900,6 +1911,9 @@ typedef union { tBTA_DM_API_ENABLE_PRIVACY ble_remote_privacy; tBTA_DM_API_LOCAL_PRIVACY ble_local_privacy; tBTA_DM_API_LOCAL_ICON ble_local_icon; +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) + tBTA_DM_API_KEY_MATERIAL ble_key_material; +#endif tBTA_DM_API_BLE_ADV_PARAMS_ALL ble_set_adv_params_all; tBTA_DM_API_SET_ADV_CONFIG ble_set_adv_data; tBTA_DM_API_SET_ADV_CONFIG_RAW ble_set_adv_data_raw; @@ -2504,6 +2518,9 @@ extern void bta_dm_ble_stop_advertising(tBTA_DM_MSG *p_data); #endif // #if (BLE_HOST_STOP_ADV_UNUSED == TRUE) extern void bta_dm_ble_config_local_privacy (tBTA_DM_MSG *p_data); extern void bta_dm_ble_config_local_icon (tBTA_DM_MSG *p_data); +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) +extern void bta_dm_ble_set_key_material (tBTA_DM_MSG *p_data); +#endif extern void bta_dm_ble_set_adv_params_all(tBTA_DM_MSG *p_data); extern void bta_dm_ble_set_adv_config (tBTA_DM_MSG *p_data); extern void bta_dm_ble_set_adv_config_raw (tBTA_DM_MSG *p_data); diff --git a/components/bt/host/bluedroid/bta/include/bta/bta_api.h b/components/bt/host/bluedroid/bta/include/bta/bta_api.h index 23bf8915d9..a75e48943b 100644 --- a/components/bt/host/bluedroid/bta/include/bta/bta_api.h +++ b/components/bt/host/bluedroid/bta/include/bta/bta_api.h @@ -2939,6 +2939,22 @@ extern void BTA_DmBleConfigLocalPrivacy(BOOLEAN privacy_enable, tBTA_SET_LOCAL_P *******************************************************************************/ extern void BTA_DmBleConfigLocalIcon(uint16_t icon); +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) +/******************************************************************************* +** +** Function BTA_DmBleSetKeyMaterial +** +** Description Set the Encrypted Data Key Material in GAP service +** +** Parameters: session_key - 16-byte session key +** iv - 8-byte initialization vector +** +** Returns void +** +*******************************************************************************/ +extern void BTA_DmBleSetKeyMaterial(const uint8_t *session_key, const uint8_t *iv); +#endif + /******************************************************************************* ** ** Function BTA_DmBleEnableRemotePrivacy diff --git a/components/bt/host/bluedroid/btc/profile/std/gap/btc_gap_ble.c b/components/bt/host/bluedroid/btc/profile/std/gap/btc_gap_ble.c index 92a6ec9ff2..8d506d7110 100644 --- a/components/bt/host/bluedroid/btc/profile/std/gap/btc_gap_ble.c +++ b/components/bt/host/bluedroid/btc/profile/std/gap/btc_gap_ble.c @@ -3192,6 +3192,11 @@ void btc_gap_ble_call_handler(btc_msg_t *msg) BTA_DmBleGapCsProcEnable(arg_5->cs_procedure_enable_params.conn_handle, arg_5->cs_procedure_enable_params.config_id, arg_5->cs_procedure_enable_params.enable); break; #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) + case BTC_GAP_BLE_ACT_SET_KEY_MATERIAL: + BTA_DmBleSetKeyMaterial(arg->set_key_material.session_key, arg->set_key_material.iv); + break; +#endif default: break; } diff --git a/components/bt/host/bluedroid/btc/profile/std/include/btc_gap_ble.h b/components/bt/host/bluedroid/btc/profile/std/include/btc_gap_ble.h index 8f844a50b8..9784dbf126 100644 --- a/components/bt/host/bluedroid/btc/profile/std/include/btc_gap_ble.h +++ b/components/bt/host/bluedroid/btc/profile/std/include/btc_gap_ble.h @@ -160,6 +160,9 @@ typedef enum { BTC_GAP_BLE_CS_SET_PROCEDURE_PARAMS, BTC_GAP_BLE_CS_PROCEDURE_ENABLE, #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) + BTC_GAP_BLE_ACT_SET_KEY_MATERIAL, +#endif } btc_gap_ble_act_t; /* btc_ble_gap_args_t */ @@ -215,6 +218,13 @@ typedef union { struct cfg_local_icon_args { uint16_t icon; } cfg_local_icon; +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) + //BTC_GAP_BLE_ACT_SET_KEY_MATERIAL + struct set_key_material_args { + uint8_t session_key[16]; + uint8_t iv[8]; + } set_key_material; +#endif //BTC_GAP_BLE_ACT_UPDATE_WHITE_LIST struct update_white_list_args { bool add_remove; diff --git a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h index 70e967b44b..97bd25404f 100644 --- a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h +++ b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h @@ -467,6 +467,24 @@ #define UC_BT_BLE_SMP_BOND_NVS_FLASH FALSE #endif +#ifdef CONFIG_BT_SMP_CRYPTO_STACK_NATIVE +#define UC_BT_SMP_CRYPTO_STACK_NATIVE TRUE +#else +#define UC_BT_SMP_CRYPTO_STACK_NATIVE FALSE +#endif + +#ifdef CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS +#define UC_BT_SMP_CRYPTO_MBEDTLS TRUE +#else +#define UC_BT_SMP_CRYPTO_MBEDTLS FALSE +#endif + +#ifdef CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT +#define UC_BT_SMP_CRYPTO_TINYCRYPT TRUE +#else +#define UC_BT_SMP_CRYPTO_TINYCRYPT FALSE +#endif + //Device Name Maximum Length #ifdef CONFIG_BT_MAX_DEVICE_NAME_LEN #define UC_MAX_LOC_BD_NAME_LEN CONFIG_BT_MAX_DEVICE_NAME_LEN @@ -592,6 +610,12 @@ #define UC_BT_GATTS_SECURITY_LEVELS_CHAR FALSE #endif +#ifdef CONFIG_BT_GATTS_KEY_MATERIAL_CHAR +#define UC_BT_GATTS_KEY_MATERIAL_CHAR CONFIG_BT_GATTS_KEY_MATERIAL_CHAR +#else +#define UC_BT_GATTS_KEY_MATERIAL_CHAR FALSE +#endif + #ifdef CONFIG_BT_BLE_ACT_SCAN_REP_ADV_SCAN #define UC_BT_BLE_ACT_SCAN_REP_ADV_SCAN CONFIG_BT_BLE_ACT_SCAN_REP_ADV_SCAN #else diff --git a/components/bt/host/bluedroid/common/include/common/bt_target.h b/components/bt/host/bluedroid/common/include/common/bt_target.h index a25985b202..9ef207ebef 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_target.h +++ b/components/bt/host/bluedroid/common/include/common/bt_target.h @@ -517,6 +517,24 @@ #define BLE_SMP_BOND_NVS_FLASH FALSE #endif +#if (UC_BT_SMP_CRYPTO_STACK_NATIVE) +#define SMP_CRYPTO_STACK_NATIVE TRUE +#else +#define SMP_CRYPTO_STACK_NATIVE FALSE +#endif /* UC_BT_SMP_CRYPTO_STACK_NATIVE */ + +#if (UC_BT_SMP_CRYPTO_MBEDTLS) +#define SMP_CRYPTO_MBEDTLS TRUE +#else +#define SMP_CRYPTO_MBEDTLS FALSE +#endif /* UC_BT_SMP_CRYPTO_MBEDTLS */ + +#if (UC_BT_SMP_CRYPTO_TINYCRYPT) +#define SMP_CRYPTO_TINYCRYPT TRUE +#else +#define SMP_CRYPTO_TINYCRYPT FALSE +#endif /* UC_BT_SMP_CRYPTO_TINYCRYPT */ + #ifdef UC_BTDM_BLE_ADV_REPORT_FLOW_CTRL_SUPP #define BLE_ADV_REPORT_FLOW_CONTROL (UC_BTDM_BLE_ADV_REPORT_FLOW_CTRL_SUPP && BLE_INCLUDED) #endif /* UC_BTDM_BLE_ADV_REPORT_FLOW_CTRL_SUPP */ @@ -810,6 +828,12 @@ #define BT_GATTS_SECURITY_LEVELS_CHAR FALSE #endif +#if (UC_BT_GATTS_KEY_MATERIAL_CHAR == TRUE) +#define BT_GATTS_KEY_MATERIAL_CHAR TRUE +#else +#define BT_GATTS_KEY_MATERIAL_CHAR FALSE +#endif + #ifdef UC_BT_BLE_ACT_SCAN_REP_ADV_SCAN #define BTM_BLE_ACTIVE_SCAN_REPORT_ADV_SCAN_RSP_INDIVIDUALLY UC_BT_BLE_ACT_SCAN_REP_ADV_SCAN #endif diff --git a/components/bt/host/bluedroid/stack/btm/btm_ble_gap.c b/components/bt/host/bluedroid/stack/btm/btm_ble_gap.c index 61580de047..ad382d49b4 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_ble_gap.c +++ b/components/bt/host/bluedroid/stack/btm/btm_ble_gap.c @@ -1164,6 +1164,39 @@ void BTM_BleConfigLocalIcon(uint16_t icon) #endif } +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) +/******************************************************************************* +** +** Function BTM_BleSetKeyMaterial +** +** Description Set the Encrypted Data Key Material in GAP service +** +** Parameters session_key: 16-byte session key (must not be NULL) +** iv: 8-byte initialization vector (must not be NULL) +** +** Returns void +** +*******************************************************************************/ +void BTM_BleSetKeyMaterial(const uint8_t *session_key, const uint8_t *iv) +{ +#if (defined(GAP_INCLUDED) && GAP_INCLUDED == TRUE && GATTS_INCLUDED == TRUE) + tGAP_BLE_ATTR_VALUE p_value; + + if (session_key == NULL || iv == NULL) { + BTM_TRACE_ERROR("%s: NULL pointer parameter", __func__); + return; + } + + memset(&p_value, 0, sizeof(tGAP_BLE_ATTR_VALUE)); + memcpy(p_value.key_material.session_key, session_key, GAP_KEY_MATERIAL_SESSION_KEY_SIZE); + memcpy(p_value.key_material.iv, iv, GAP_KEY_MATERIAL_IV_SIZE); + GAP_BleAttrDBUpdate(GATT_UUID_GAP_KEY_MATERIAL, &p_value); +#else + BTM_TRACE_ERROR("%s\n", __func__); +#endif +} +#endif + /******************************************************************************* ** ** Function BTM_BleConfigConnParams diff --git a/components/bt/host/bluedroid/stack/gap/gap_ble.c b/components/bt/host/bluedroid/stack/gap/gap_ble.c index 7e4f453278..154fcca0b0 100644 --- a/components/bt/host/bluedroid/stack/gap/gap_ble.c +++ b/components/bt/host/bluedroid/stack/gap/gap_ble.c @@ -262,6 +262,13 @@ tGATT_STATUS gap_read_attr_value (UINT16 handle, tGATT_VALUE *p_value, BOOLEAN i p_value->len = 2; break; #endif // (BT_GATTS_SECURITY_LEVELS_CHAR == TRUE) +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) + case GATT_UUID_GAP_KEY_MATERIAL: + ARRAY_TO_STREAM(p, p_db_attr->attr_value.key_material.session_key, GAP_KEY_MATERIAL_SESSION_KEY_SIZE); + ARRAY_TO_STREAM(p, p_db_attr->attr_value.key_material.iv, GAP_KEY_MATERIAL_IV_SIZE); + p_value->len = GAP_KEY_MATERIAL_SIZE; + break; +#endif // (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) } return GATT_SUCCESS; } @@ -481,6 +488,20 @@ void gap_attr_db_init(void) p_db_attr++; #endif // (BT_GATTS_SECURITY_LEVELS_CHAR == TRUE) +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) + /* Add Encrypted Data Key Material Characteristic + * Per Bluetooth spec: readable only when authenticated and authorized, + * requires encrypted link to read. + */ + uuid.len = LEN_UUID_16; + uuid.uu.uuid16 = p_db_attr->uuid = GATT_UUID_GAP_KEY_MATERIAL; + p_db_attr->handle = GATTS_AddCharacteristic(service_handle, &uuid, + GATT_PERM_READ_ENCRYPTED, GATT_CHAR_PROP_BIT_READ, + NULL, NULL); + memset(&p_db_attr->attr_value.key_material, 0, sizeof(tGAP_BLE_KEY_MATERIAL)); + p_db_attr++; +#endif // (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) + /* start service now */ memset (&app_uuid.uu.uuid128, 0x81, LEN_UUID_128); @@ -512,6 +533,11 @@ void GAP_BleAttrDBUpdate(UINT16 attr_uuid, tGAP_BLE_ATTR_VALUE *p_value) GAP_TRACE_EVENT("GAP_BleAttrDBUpdate attr_uuid=0x%04x\n", attr_uuid); + if (p_value == NULL) { + GAP_TRACE_ERROR("GAP_BleAttrDBUpdate: NULL pointer parameter"); + return; + } + for (i = 0; i < GAP_MAX_CHAR_NUM; i ++, p_db_attr ++) { if (p_db_attr->uuid == attr_uuid) { GAP_TRACE_EVENT("Found attr_uuid=0x%04x\n", attr_uuid); @@ -540,6 +566,13 @@ void GAP_BleAttrDBUpdate(UINT16 attr_uuid, tGAP_BLE_ATTR_VALUE *p_value) break; #endif // (BT_GATTS_SECURITY_LEVELS_CHAR == TRUE) +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) + case GATT_UUID_GAP_KEY_MATERIAL: + memcpy(&p_db_attr->attr_value.key_material, &p_value->key_material, + sizeof(tGAP_BLE_KEY_MATERIAL)); + break; +#endif // (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) + } break; } diff --git a/components/bt/host/bluedroid/stack/gap/include/gap_int.h b/components/bt/host/bluedroid/stack/gap/include/gap_int.h index 175a27e695..8f5472589c 100644 --- a/components/bt/host/bluedroid/stack/gap/include/gap_int.h +++ b/components/bt/host/bluedroid/stack/gap/include/gap_int.h @@ -93,7 +93,11 @@ typedef struct { #if BLE_INCLUDED == TRUE +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) +#define GAP_MAX_CHAR_NUM 6 +#else #define GAP_MAX_CHAR_NUM 5 +#endif typedef struct { UINT16 handle; diff --git a/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h b/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h index 8961197865..33522b7aef 100644 --- a/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h +++ b/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h @@ -2948,6 +2948,22 @@ BOOLEAN BTM_BleConfigPrivacy(BOOLEAN enable, tBTM_SET_LOCAL_PRIVACY_CBACK *set_l *******************************************************************************/ void BTM_BleConfigLocalIcon(uint16_t icon); +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) +/******************************************************************************* +** +** Function BTM_BleSetKeyMaterial +** +** Description Set the Encrypted Data Key Material in GAP service +** +** Parameters session_key: 16-byte session key +** iv: 8-byte initialization vector +** +** Returns void +** +*******************************************************************************/ +void BTM_BleSetKeyMaterial(const uint8_t *session_key, const uint8_t *iv); +#endif + /******************************************************************************* ** ** Function BTM_BleConfigConnParams diff --git a/components/bt/host/bluedroid/stack/include/stack/gap_api.h b/components/bt/host/bluedroid/stack/include/stack/gap_api.h index a79d9748c8..81d353d022 100644 --- a/components/bt/host/bluedroid/stack/include/stack/gap_api.h +++ b/components/bt/host/bluedroid/stack/include/stack/gap_api.h @@ -113,6 +113,17 @@ typedef struct { UINT16 sp_tout; } tGAP_BLE_PREF_PARAM; +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) +#define GAP_KEY_MATERIAL_SESSION_KEY_SIZE 16 /* 128-bit session key */ +#define GAP_KEY_MATERIAL_IV_SIZE 8 /* 64-bit IV */ +#define GAP_KEY_MATERIAL_SIZE (GAP_KEY_MATERIAL_SESSION_KEY_SIZE + GAP_KEY_MATERIAL_IV_SIZE) + +typedef struct { + UINT8 session_key[GAP_KEY_MATERIAL_SESSION_KEY_SIZE]; + UINT8 iv[GAP_KEY_MATERIAL_IV_SIZE]; +} tGAP_BLE_KEY_MATERIAL; +#endif // (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) + typedef union { tGAP_BLE_PREF_PARAM conn_param; BD_ADDR reconn_bda; @@ -122,6 +133,9 @@ typedef union { #if (BT_GATTS_SECURITY_LEVELS_CHAR == TRUE) UINT16 security_level; #endif // (BT_GATTS_SECURITY_LEVELS_CHAR == TRUE) +#if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) + tGAP_BLE_KEY_MATERIAL key_material; +#endif // (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) } tGAP_BLE_ATTR_VALUE; diff --git a/components/bt/host/bluedroid/stack/include/stack/gattdefs.h b/components/bt/host/bluedroid/stack/include/stack/gattdefs.h index 2ae8cef839..8da14f4cb7 100644 --- a/components/bt/host/bluedroid/stack/include/stack/gattdefs.h +++ b/components/bt/host/bluedroid/stack/include/stack/gattdefs.h @@ -53,6 +53,7 @@ #define GATT_UUID_GAP_CENTRAL_ADDR_RESOL 0x2AA6 #define GATT_UUID_GAP_GATT_SECURITY_LEVELS 0x2BF5 +#define GATT_UUID_GAP_KEY_MATERIAL 0x2B88 /* Encrypted Data Key Material */ /* Attribute Profile Attribute UUID */ #define GATT_UUID_GATT_SRV_CHGD 0x2A05 diff --git a/components/bt/host/bluedroid/stack/smp/aes.c b/components/bt/host/bluedroid/stack/smp/aes.c index 18b56b877d..6555b6af97 100644 --- a/components/bt/host/bluedroid/stack/smp/aes.c +++ b/components/bt/host/bluedroid/stack/smp/aes.c @@ -48,6 +48,8 @@ /* add the target configuration to allow using internal data types and compilation options */ #include "common/bt_target.h" +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) + /* define if you have fast 32-bit types on your system */ #if 1 # define HAVE_UINT_32T @@ -569,7 +571,7 @@ return_type aes_set_key( const unsigned char key[], length_type keylen, aes_cont /* Encrypt a single block of 16 bytes */ -/* @breif change the name by snake for avoid the conflict with libcrypto */ +/* @brief change the name by snake for avoid the conflict with libcrypto */ return_type bluedroid_aes_encrypt( const unsigned char in[N_BLOCK], unsigned char out[N_BLOCK], const aes_context ctx[1] ) { if ( ctx->rnd ) { @@ -935,4 +937,6 @@ void bluedroid_aes_decrypt_256( const unsigned char in[N_BLOCK], unsigned char o copy_and_key( out, s1, o_key ); } +#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */ + #endif diff --git a/components/bt/host/bluedroid/stack/smp/include/aes.h b/components/bt/host/bluedroid/stack/smp/include/aes.h index 48495bb174..9c14e22dcf 100644 --- a/components/bt/host/bluedroid/stack/smp/include/aes.h +++ b/components/bt/host/bluedroid/stack/smp/include/aes.h @@ -31,6 +31,10 @@ #ifndef AES_H #define AES_H +#include "common/bt_target.h" + +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) + #if 1 # define AES_ENC_PREKEYED /* AES encryption with a precomputed key schedule */ #endif @@ -117,7 +121,7 @@ return_type aes_cbc_decrypt( const unsigned char *in, The encryption subroutines take a key in an array of bytes in key[L] where L is 16, 24 or 32 bytes for key lengths of 128, 192, and 256 bits respectively. They then encrypts the input - data, in[] with this key and put the reult in the output array + data, in[] with this key and put the result in the output array out[]. In addition, the second key array, o_key[L], is used to output the key that is needed by the decryption subroutine to reverse the encryption operation. The two key arrays can @@ -159,4 +163,6 @@ void bluedroid_aes_decrypt_256( const unsigned char in[N_BLOCK], unsigned char o_key[2 * N_BLOCK] ); #endif -#endif +#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */ + +#endif /* AES_H */ diff --git a/components/bt/host/bluedroid/stack/smp/include/p_256_ecc_pp.h b/components/bt/host/bluedroid/stack/smp/include/p_256_ecc_pp.h index 3b28e0c99a..7c2f7279eb 100644 --- a/components/bt/host/bluedroid/stack/smp/include/p_256_ecc_pp.h +++ b/components/bt/host/bluedroid/stack/smp/include/p_256_ecc_pp.h @@ -24,9 +24,12 @@ #pragma once -#include "p_256_multprecision.h" #include "common/bt_target.h" +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) + +#include "p_256_multprecision.h" + typedef unsigned long DWORD; typedef struct { @@ -72,3 +75,5 @@ bool ECC_CheckPointIsInElliCur_P256(Point *p); #define ECC_PointMult(q, p, n, keyLength) ECC_PointMult_Bin_NAF(q, p, n, keyLength) void p_256_init_curve(UINT32 keyLength); + +#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */ diff --git a/components/bt/host/bluedroid/stack/smp/include/p_256_multprecision.h b/components/bt/host/bluedroid/stack/smp/include/p_256_multprecision.h index 0a33b4e24f..83b7f1876f 100644 --- a/components/bt/host/bluedroid/stack/smp/include/p_256_multprecision.h +++ b/components/bt/host/bluedroid/stack/smp/include/p_256_multprecision.h @@ -24,6 +24,9 @@ #pragma once #include "stack/bt_types.h" +#include "common/bt_target.h" + +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) /* Type definitions */ typedef unsigned long DWORD; @@ -58,3 +61,5 @@ DWORD multiprecision_lshift(DWORD *c, DWORD *a, uint32_t keyLength); void multiprecision_mult(DWORD *c, DWORD *a, DWORD *b, uint32_t keyLength); void multiprecision_fast_mod(DWORD *c, DWORD *a); void multiprecision_fast_mod_P256(DWORD *c, DWORD *a); + +#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */ diff --git a/components/bt/host/bluedroid/stack/smp/p_256_curvepara.c b/components/bt/host/bluedroid/stack/smp/p_256_curvepara.c index abf9a8eecc..b3fca522de 100644 --- a/components/bt/host/bluedroid/stack/smp/p_256_curvepara.c +++ b/components/bt/host/bluedroid/stack/smp/p_256_curvepara.c @@ -23,6 +23,10 @@ ******************************************************************************/ #include +#include "common/bt_target.h" + +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) + #include "p_256_ecc_pp.h" void p_256_init_curve(UINT32 keyLength) @@ -76,3 +80,5 @@ void p_256_init_curve(UINT32 keyLength) ec->G.y[0] = 0x37bf51f5; } } + +#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */ diff --git a/components/bt/host/bluedroid/stack/smp/p_256_ecc_pp.c b/components/bt/host/bluedroid/stack/smp/p_256_ecc_pp.c index 1f49c652bd..d3b9452c1e 100644 --- a/components/bt/host/bluedroid/stack/smp/p_256_ecc_pp.c +++ b/components/bt/host/bluedroid/stack/smp/p_256_ecc_pp.c @@ -24,9 +24,12 @@ //#include //#include #include +#include "common/bt_target.h" + +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) + #include "p_256_ecc_pp.h" #include "p_256_multprecision.h" -#include "common/bt_target.h" #if SMP_DYNAMIC_MEMORY == FALSE elliptic_curve_t curve; @@ -281,3 +284,5 @@ bool ECC_CheckPointIsInElliCur_P256(Point *p) return true; } } + +#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */ diff --git a/components/bt/host/bluedroid/stack/smp/p_256_multprecision.c b/components/bt/host/bluedroid/stack/smp/p_256_multprecision.c index 1dbca13756..92291d98b3 100644 --- a/components/bt/host/bluedroid/stack/smp/p_256_multprecision.c +++ b/components/bt/host/bluedroid/stack/smp/p_256_multprecision.c @@ -24,6 +24,9 @@ #include #include "common/bt_target.h" + +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) + #include "p_256_ecc_pp.h" #include "p_256_multprecision.h" @@ -365,7 +368,7 @@ void multiprecision_fast_mod_P256(DWORD *c, DWORD *a) uint8_t UB; uint8_t UC; uint8_t UD; - uint8_t UE; + uint8_t U_E; uint8_t UF; uint8_t UG; DWORD U; @@ -381,7 +384,7 @@ void multiprecision_fast_mod_P256(DWORD *c, DWORD *a) // E = a[8] + a[9]; E = a[8]; E += a[9]; - UE = (E < a[9]); + U_E = (E < a[9]); // F = a[9] + a[10]; F = a[9]; @@ -418,7 +421,7 @@ void multiprecision_fast_mod_P256(DWORD *c, DWORD *a) c[0] = a[0]; c[0] += E; U = (c[0] < E); - U += UE; + U += U_E; U -= (c[0] < A); U -= UA; c[0] -= A; @@ -479,7 +482,7 @@ void multiprecision_fast_mod_P256(DWORD *c, DWORD *a) U -= (c[3] < a[15]); c[3] -= a[15]; U -= (c[3] < E); - U -= UE; + U -= U_E; c[3] -= E; if (U & 0x80000000) { @@ -546,7 +549,7 @@ void multiprecision_fast_mod_P256(DWORD *c, DWORD *a) c[6] += a[15]; U += (c[6] < a[15]); U -= (c[6] < E); - U -= UE; + U -= U_E; c[6] -= E; if (U & 0x80000000) { @@ -645,3 +648,5 @@ void multiprecision_inv_mod(DWORD *aminus, DWORD *u, uint32_t keyLength) multiprecision_copy(aminus, C, keyLength); } } + +#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */ diff --git a/components/bt/host/bluedroid/stack/smp/smp_act.c b/components/bt/host/bluedroid/stack/smp/smp_act.c index d05e1a7f7d..1c88b8dcd3 100644 --- a/components/bt/host/bluedroid/stack/smp/smp_act.c +++ b/components/bt/host/bluedroid/stack/smp/smp_act.c @@ -22,7 +22,15 @@ #include "btm_int.h" #include "stack/l2c_api.h" #include "smp_int.h" +#if (SMP_CRYPTO_MBEDTLS == TRUE) +#include "mbedtls/ecp.h" +#elif (SMP_CRYPTO_TINYCRYPT == TRUE) +#include "tinycrypt/ecc_dh.h" +#include "tinycrypt/ecc.h" +#include "tinycrypt/constants.h" +#else #include "p_256_ecc_pp.h" +#endif //#include "utils/include/bt_utils.h" #if SMP_INCLUDED == TRUE @@ -771,10 +779,98 @@ void smp_process_pairing_public_key(tSMP_CB *p_cb, tSMP_INT_DATA *p_data) } /* In order to prevent the x and y coordinates of the public key from being modified, we need to check whether the x and y coordinates are on the given elliptic curve. */ +#if (SMP_CRYPTO_MBEDTLS == TRUE) + { + /* + * mbedTLS validates the public key using mbedtls_ecp_check_pubkey. + */ + mbedtls_ecp_group grp = {0}; + mbedtls_ecp_point pt = {0}; + int rc; + UINT8 pub_be[BT_OCTET32_LEN + BT_OCTET32_LEN + 1]; /* 0x04 || X (32 bytes) || Y (32 bytes) */ + + mbedtls_ecp_group_init(&grp); + mbedtls_ecp_point_init(&pt); + + /* Load the group */ + rc = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1); + if (rc != 0) { + SMP_TRACE_ERROR("%s, Invalid Public key. mbedtls_ecp_group_load failed: %d\n", __func__, rc); + mbedtls_ecp_point_free(&pt); + mbedtls_ecp_group_free(&grp); + reason = SMP_INVALID_PARAMETERS; + smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason); + return; + } + + /* Construct peer public key in uncompressed format (0x04 || X || Y) */ + pub_be[0] = 0x04; + for (int i = 0; i < BT_OCTET32_LEN; i++) { + pub_be[1 + i] = p_cb->peer_publ_key.x[BT_OCTET32_LEN - 1 - i]; + pub_be[33 + i] = p_cb->peer_publ_key.y[BT_OCTET32_LEN - 1 - i]; + } + + /* Read public key */ + rc = mbedtls_ecp_point_read_binary(&grp, &pt, pub_be, sizeof(pub_be)); + if (rc != 0) { + SMP_TRACE_ERROR("%s, Invalid Public key. mbedtls_ecp_point_read_binary failed: %d\n", __func__, rc); + mbedtls_ecp_point_free(&pt); + mbedtls_ecp_group_free(&grp); + reason = SMP_INVALID_PARAMETERS; + smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason); + return; + } + + /* Validate public key - check if it's on the curve */ + rc = mbedtls_ecp_check_pubkey(&grp, &pt); + if (rc != 0) { + SMP_TRACE_ERROR("%s, Invalid Public key. mbedtls_ecp_check_pubkey failed: %d\n", __func__, rc); + mbedtls_ecp_point_free(&pt); + mbedtls_ecp_group_free(&grp); + reason = SMP_INVALID_PARAMETERS; + smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason); + return; + } + + /* Key is valid, clean up */ + mbedtls_ecp_point_free(&pt); + mbedtls_ecp_group_free(&grp); + } +#elif (SMP_CRYPTO_TINYCRYPT == TRUE) + { + /* + * TinyCrypt validates the public key using uECC_valid_public_key. + * TinyCrypt expects public key in format: X (32 bytes) || Y (32 bytes), no prefix. + */ + UINT8 pub_be[64]; /* TinyCrypt format: X (32 bytes) || Y (32 bytes), no prefix */ + + /* Convert peer public key from little-endian to big-endian */ + /* TinyCrypt format: X (32 bytes) || Y (32 bytes), no prefix */ + for (int i = 0; i < BT_OCTET32_LEN; i++) { + pub_be[i] = p_cb->peer_publ_key.x[BT_OCTET32_LEN - 1 - i]; + pub_be[BT_OCTET32_LEN + i] = p_cb->peer_publ_key.y[BT_OCTET32_LEN - 1 - i]; + } + + /* Validate public key - TinyCrypt will check if it's on the curve */ + /* uECC_valid_public_key returns 0 if valid, negative value if invalid */ + if (uECC_valid_public_key(pub_be, uECC_secp256r1()) < 0) { + SMP_TRACE_ERROR("%s, Invalid Public key. uECC_valid_public_key failed\n", __func__); + reason = SMP_INVALID_PARAMETERS; + smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason); + memset(pub_be, 0, sizeof(pub_be)); + return; + } + + /* Clear sensitive data from stack */ + memset(pub_be, 0, sizeof(pub_be)); + } +#else if (!ECC_CheckPointIsInElliCur_P256((Point *)&p_cb->peer_publ_key)) { SMP_TRACE_ERROR("%s, Invalid Public key.", __func__); smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason); + return; } +#endif /* SMP_CRYPTO_MBEDTLS */ p_cb->flags |= SMP_PAIR_FLAG_HAVE_PEER_PUBL_KEY; smp_wait_for_both_public_keys(p_cb, NULL); diff --git a/components/bt/host/bluedroid/stack/smp/smp_api.c b/components/bt/host/bluedroid/stack/smp/smp_api.c index 4ae836e0a9..0b00bf151c 100644 --- a/components/bt/host/bluedroid/stack/smp/smp_api.c +++ b/components/bt/host/bluedroid/stack/smp/smp_api.c @@ -35,7 +35,9 @@ #include "stack/hcimsgs.h" #include "stack/btu.h" +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) #include "p_256_ecc_pp.h" +#endif #include "osi/allocator.h" /******************************************************************************* @@ -51,12 +53,16 @@ void SMP_Init(void) { #if SMP_DYNAMIC_MEMORY smp_cb_ptr = (tSMP_CB *)osi_malloc(sizeof(tSMP_CB)); +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) curve_ptr = (elliptic_curve_t *)osi_malloc(sizeof(elliptic_curve_t)); curve_p256_ptr = (elliptic_curve_t *)osi_malloc(sizeof(elliptic_curve_t)); +#endif #endif memset(&smp_cb, 0, sizeof(tSMP_CB)); +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) memset(&curve, 0, sizeof(elliptic_curve_t)); memset(&curve_p256, 0, sizeof(elliptic_curve_t)); +#endif #if defined(SMP_INITIAL_TRACE_LEVEL) smp_cb.trace_level = SMP_INITIAL_TRACE_LEVEL; @@ -66,8 +72,10 @@ void SMP_Init(void) SMP_TRACE_EVENT ("%s", __FUNCTION__); smp_l2cap_if_init(); +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) /* initialization of P-256 parameters */ p_256_init_curve(KEY_LENGTH_DWORDS_P256); +#endif } void SMP_Free(void) @@ -75,8 +83,10 @@ void SMP_Free(void) memset(&smp_cb, 0, sizeof(tSMP_CB)); #if SMP_DYNAMIC_MEMORY FREE_AND_RESET(smp_cb_ptr); +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) FREE_AND_RESET(curve_ptr); FREE_AND_RESET(curve_p256_ptr); +#endif #endif /* #if SMP_DYNAMIC_MEMORY */ } diff --git a/components/bt/host/bluedroid/stack/smp/smp_cmac.c b/components/bt/host/bluedroid/stack/smp/smp_cmac.c index e47c56b71f..b097a05d23 100644 --- a/components/bt/host/bluedroid/stack/smp/smp_cmac.c +++ b/components/bt/host/bluedroid/stack/smp/smp_cmac.c @@ -32,7 +32,16 @@ #include "stack/btm_ble_api.h" #include "smp_int.h" #include "stack/hcimsgs.h" +#if (SMP_CRYPTO_MBEDTLS == TRUE) +#include "mbedtls/cipher.h" +#include "mbedtls/cmac.h" +#elif (SMP_CRYPTO_TINYCRYPT == TRUE) +#include "tinycrypt/aes.h" +#include "tinycrypt/cmac_mode.h" +#include "tinycrypt/constants.h" +#endif +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) typedef struct { UINT8 *text; UINT16 len; @@ -46,6 +55,7 @@ const BT_OCTET16 const_Rb = { 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */ void print128(BT_OCTET16 x, const UINT8 *key_name) { @@ -75,6 +85,7 @@ void print128(BT_OCTET16 x, const UINT8 *key_name) ** Returns void ** *******************************************************************************/ +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) static void padding ( BT_OCTET16 dest, UINT8 length ) { UINT8 i, *p = dest; @@ -83,6 +94,7 @@ static void padding ( BT_OCTET16 dest, UINT8 length ) p[BT_OCTET16_LEN - i - 1] = ( i == length ) ? 0x80 : 0; } } + /******************************************************************************* ** ** Function leftshift_onebit @@ -104,6 +116,8 @@ static void leftshift_onebit(UINT8 *input, UINT8 *output) } return; } +#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */ + /******************************************************************************* ** ** Function cmac_aes_cleanup @@ -113,6 +127,7 @@ static void leftshift_onebit(UINT8 *input, UINT8 *output) ** Returns void ** *******************************************************************************/ +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) static void cmac_aes_cleanup(void) { if (cmac_cb.text != NULL) { @@ -167,11 +182,12 @@ static BOOLEAN cmac_aes_k_calculate(BT_OCTET16 key, UINT8 *p_signature, UINT16 t return FALSE; } } + /******************************************************************************* ** ** Function cmac_prepare_last_block ** -** Description This function proceeed to prepare the last block of message +** Description This function proceed to prepare the last block of message ** Mn depending on the size of the message. ** ** Returns void @@ -197,6 +213,8 @@ static void cmac_prepare_last_block (BT_OCTET16 k1, BT_OCTET16 k2) smp_xor_128(&cmac_cb.text[0], k2); } } +#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */ + /******************************************************************************* ** ** Function cmac_subkey_cont @@ -206,6 +224,7 @@ static void cmac_prepare_last_block (BT_OCTET16 k1, BT_OCTET16 k2) ** Returns void ** *******************************************************************************/ +#if (SMP_CRYPTO_STACK_NATIVE == TRUE) static void cmac_subkey_cont(tSMP_ENC *p) { UINT8 k1[BT_OCTET16_LEN], k2[BT_OCTET16_LEN]; @@ -262,6 +281,8 @@ static BOOLEAN cmac_generate_subkey(BT_OCTET16 key) return ret; } +#endif /* SMP_CRYPTO_STACK_NATIVE == TRUE */ + /******************************************************************************* ** ** Function aes_cipher_msg_auth_code @@ -271,7 +292,7 @@ static BOOLEAN cmac_generate_subkey(BT_OCTET16 key) ** Parameters key - CMAC key in little endian order, expect SRK when used by SMP. ** input - text to be signed in little endian byte order. ** length - length of the input in byte. -** tlen - lenth of mac desired +** tlen - length of mac desired ** p_signature - data pointer to where signed data to be stored, tlen long. ** ** Returns FALSE if out of resources, TRUE in other cases. @@ -280,43 +301,189 @@ static BOOLEAN cmac_generate_subkey(BT_OCTET16 key) BOOLEAN aes_cipher_msg_auth_code(BT_OCTET16 key, UINT8 *input, UINT16 length, UINT16 tlen, UINT8 *p_signature) { - UINT16 len, diff; - UINT16 n = (length + BT_OCTET16_LEN - 1) / BT_OCTET16_LEN; /* n is number of rounds */ BOOLEAN ret = FALSE; SMP_TRACE_EVENT ("%s", __func__); - if (n == 0) { - n = 1; +#if (SMP_CRYPTO_MBEDTLS == TRUE) + { + /* + * mbedTLS CMAC implementation. + * Bluedroid and mbedTLS both use little-endian, so no byte order conversion needed. + */ + mbedtls_cipher_context_t ctx = {0}; + const mbedtls_cipher_info_t *cipher_info; + int rc; + + SMP_TRACE_DEBUG("AES128_CMAC (mbedTLS) started, length = %d", length); + + mbedtls_cipher_init(&ctx); + + cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB); + if (cipher_info == NULL) { + SMP_TRACE_ERROR("mbedtls_cipher_info_from_type failed"); + mbedtls_cipher_free(&ctx); + return FALSE; + } + + rc = mbedtls_cipher_setup(&ctx, cipher_info); + if (rc != 0) { + SMP_TRACE_ERROR("mbedtls_cipher_setup failed: %d", rc); + mbedtls_cipher_free(&ctx); + return FALSE; + } + + rc = mbedtls_cipher_cmac_starts(&ctx, key, 128); + if (rc != 0) { + SMP_TRACE_ERROR("mbedtls_cipher_cmac_starts failed: %d", rc); + mbedtls_cipher_free(&ctx); + return FALSE; + } + + if (length > 0 && input != NULL) { + rc = mbedtls_cipher_cmac_update(&ctx, input, length); + if (rc != 0) { + SMP_TRACE_ERROR("mbedtls_cipher_cmac_update failed: %d", rc); + mbedtls_cipher_free(&ctx); + return FALSE; + } + } + + UINT8 mac[BT_OCTET16_LEN]; + rc = mbedtls_cipher_cmac_finish(&ctx, mac); + mbedtls_cipher_free(&ctx); + + if (rc != 0) { + SMP_TRACE_ERROR("mbedtls_cipher_cmac_finish failed: %d", rc); + /* Clear sensitive data from stack */ + memset(mac, 0, sizeof(mac)); + return FALSE; + } + + /* Truncate to tlen bytes */ + for (UINT16 i = 0; i < tlen && i < BT_OCTET16_LEN; i++) { + p_signature[i] = mac[i]; + } + + /* Clear sensitive data from stack */ + memset(mac, 0, sizeof(mac)); + + ret = TRUE; } - len = n * BT_OCTET16_LEN; +#elif (SMP_CRYPTO_TINYCRYPT == TRUE) + { + /* + * TinyCrypt CMAC implementation. + * Bluedroid uses little-endian, TinyCrypt uses big-endian. + * We reverse the key and input, then reverse the output. + */ + struct tc_aes_key_sched_struct sched; + struct tc_cmac_struct state; + UINT8 key_be[BT_OCTET16_LEN]; + UINT8 *input_be = NULL; + UINT8 mac_be[BT_OCTET16_LEN]; - SMP_TRACE_DEBUG("AES128_CMAC started, allocate buffer size = %d", len); - /* allocate a memory space of multiple of 16 bytes to hold text */ - if ((cmac_cb.text = (UINT8 *)osi_malloc(len)) != NULL) { - cmac_cb.round = n; + SMP_TRACE_DEBUG("AES128_CMAC (TinyCrypt) started, length = %d", length); - memset(cmac_cb.text, 0, len); - diff = len - length; + /* Convert key from little-endian to big-endian */ + for (int i = 0; i < BT_OCTET16_LEN; i++) { + key_be[i] = key[BT_OCTET16_LEN - 1 - i]; + } - if (input != NULL && length > 0) { - memcpy(&cmac_cb.text[diff] , input, (int)length); - cmac_cb.len = length; + /* Setup CMAC */ + if (tc_cmac_setup(&state, key_be, &sched) == TC_CRYPTO_FAIL) { + SMP_TRACE_ERROR("tc_cmac_setup failed"); + memset(key_be, 0, sizeof(key_be)); + memset(&sched, 0, sizeof(sched)); + return FALSE; + } + + /* Allocate and convert input from little-endian to big-endian */ + if (length > 0) { + input_be = (UINT8 *)osi_malloc(length); + if (input_be == NULL) { + SMP_TRACE_ERROR("No resources for input_be"); + tc_cmac_erase(&state); + memset(key_be, 0, sizeof(key_be)); + memset(&sched, 0, sizeof(sched)); + return FALSE; + } + for (UINT16 i = 0; i < length; i++) { + input_be[i] = input[length - 1 - i]; + } + + /* Update CMAC with input data */ + if (tc_cmac_update(&state, input_be, length) == TC_CRYPTO_FAIL) { + SMP_TRACE_ERROR("tc_cmac_update failed"); + osi_free(input_be); + tc_cmac_erase(&state); + memset(key_be, 0, sizeof(key_be)); + memset(&sched, 0, sizeof(sched)); + return FALSE; + } + osi_free(input_be); + } + + /* Finalize CMAC */ + if (tc_cmac_final(mac_be, &state) == TC_CRYPTO_FAIL) { + SMP_TRACE_ERROR("tc_cmac_final failed"); + tc_cmac_erase(&state); + memset(key_be, 0, sizeof(key_be)); + memset(&sched, 0, sizeof(sched)); + return FALSE; + } + + /* Convert MAC from big-endian to little-endian and truncate to tlen bytes */ + for (UINT16 i = 0; i < tlen && i < BT_OCTET16_LEN; i++) { + p_signature[i] = mac_be[BT_OCTET16_LEN - 1 - i]; + } + + /* Clear sensitive data from stack */ + tc_cmac_erase(&state); + memset(key_be, 0, sizeof(key_be)); + memset(mac_be, 0, sizeof(mac_be)); + memset(&sched, 0, sizeof(sched)); + + ret = TRUE; + } +#else + { + UINT16 len, diff; + UINT16 n = (length + BT_OCTET16_LEN - 1) / BT_OCTET16_LEN; + + if (n == 0) { + n = 1; + } + len = n * BT_OCTET16_LEN; + + SMP_TRACE_DEBUG("AES128_CMAC started, allocate buffer size = %d", len); + /* allocate a memory space of multiple of 16 bytes to hold text */ + if ((cmac_cb.text = (UINT8 *)osi_malloc(len)) != NULL) { + cmac_cb.round = n; + + memset(cmac_cb.text, 0, len); + diff = len - length; + + if (input != NULL && length > 0) { + memcpy(&cmac_cb.text[diff] , input, (int)length); + cmac_cb.len = length; + } else { + cmac_cb.len = 0; + } + + /* prepare calculation for subkey s and last block of data */ + if (cmac_generate_subkey(key)) { + /* start calculation */ + ret = cmac_aes_k_calculate(key, p_signature, tlen); + } + /* clean up */ + cmac_aes_cleanup(); } else { - cmac_cb.len = 0; + ret = FALSE; + SMP_TRACE_ERROR("No resources"); } - - /* prepare calculation for subkey s and last block of data */ - if (cmac_generate_subkey(key)) { - /* start calculation */ - ret = cmac_aes_k_calculate(key, p_signature, tlen); - } - /* clean up */ - cmac_aes_cleanup(); - } else { - ret = FALSE; - SMP_TRACE_ERROR("No resources"); } +#endif /* SMP_CRYPTO_MBEDTLS */ return ret; } diff --git a/components/bt/host/bluedroid/stack/smp/smp_keys.c b/components/bt/host/bluedroid/stack/smp/smp_keys.c index 3b1d4ee8f1..f96751c72a 100644 --- a/components/bt/host/bluedroid/stack/smp/smp_keys.c +++ b/components/bt/host/bluedroid/stack/smp/smp_keys.c @@ -34,8 +34,29 @@ #include "btm_int.h" #include "btm_ble_int.h" #include "stack/hcimsgs.h" +#if (SMP_CRYPTO_MBEDTLS == TRUE) +#include "mbedtls/aes.h" +#include "mbedtls/ecdh.h" +#include "mbedtls/ecp.h" +#include "esp_random.h" + +/* Random number generator function for mbedTLS ECP operations */ +static int smp_mbedtls_rng(void *ctx, unsigned char *output, size_t len) +{ + (void)ctx; /* Unused parameter */ + esp_fill_random(output, len); + return 0; +} +#elif (SMP_CRYPTO_TINYCRYPT == TRUE) +#include "tinycrypt/aes.h" +#include "tinycrypt/cmac_mode.h" +#include "tinycrypt/ecc_dh.h" +#include "tinycrypt/ecc.h" +#include "tinycrypt/constants.h" +#else #include "aes.h" #include "p_256_ecc_pp.h" +#endif /* SMP_CRYPTO_MBEDTLS */ #include "device/controller.h" #ifndef SMP_MAX_ENC_REPEAT @@ -159,12 +180,11 @@ BOOLEAN smp_encrypt_data (UINT8 *key, UINT8 key_len, UINT8 *plain_text, UINT8 pt_len, tSMP_ENC *p_out) { - aes_context ctx; UINT8 *p_start = NULL; UINT8 *p = NULL; - UINT8 *p_rev_data = NULL; /* input data in big endilan format */ - UINT8 *p_rev_key = NULL; /* input key in big endilan format */ - UINT8 *p_rev_output = NULL; /* encrypted output in big endilan format */ + UINT8 *p_rev_data = NULL; /* input data in big endian format */ + UINT8 *p_rev_key = NULL; /* input key in big endian format */ + UINT8 *p_rev_output = NULL; /* encrypted output in big endian format */ SMP_TRACE_DEBUG ("%s\n", __func__); if ( (p_out == NULL ) || (key_len != SMP_ENCRYT_KEY_SIZE) ) { @@ -194,8 +214,64 @@ BOOLEAN smp_encrypt_data (UINT8 *key, UINT8 key_len, smp_debug_print_nbyte_little_endian(p_start, (const UINT8 *)"Plain text", SMP_ENCRYT_DATA_SIZE); #endif p_rev_output = p; - aes_set_key(p_rev_key, SMP_ENCRYT_KEY_SIZE, &ctx); - bluedroid_aes_encrypt(p_rev_data, p, &ctx); /* outputs in byte 48 to byte 63 */ + +#if (SMP_CRYPTO_MBEDTLS == TRUE) + { + mbedtls_aes_context ctx = {0}; + int rc; + + mbedtls_aes_init(&ctx); + + rc = mbedtls_aes_setkey_enc(&ctx, p_rev_key, 128); + if (rc != 0) { + SMP_TRACE_ERROR("%s mbedtls_aes_setkey_enc failed: %d\n", __func__, rc); + mbedtls_aes_free(&ctx); + /* Clear sensitive data before freeing */ + memset(p_start, 0, SMP_ENCRYT_DATA_SIZE * 4); + osi_free(p_start); + return FALSE; + } + + rc = mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, p_rev_data, p_rev_output); + mbedtls_aes_free(&ctx); + + if (rc != 0) { + SMP_TRACE_ERROR("%s mbedtls_aes_crypt_ecb failed: %d\n", __func__, rc); + /* Clear sensitive data before freeing */ + memset(p_start, 0, SMP_ENCRYT_DATA_SIZE * 4); + osi_free(p_start); + return FALSE; + } + } +#elif (SMP_CRYPTO_TINYCRYPT == TRUE) + { + struct tc_aes_key_sched_struct sched; + + /* TinyCrypt expects big-endian key and data */ + if (tc_aes128_set_encrypt_key(&sched, p_rev_key) == TC_CRYPTO_FAIL) { + SMP_TRACE_ERROR("%s tc_aes128_set_encrypt_key failed\n", __func__); + memset(&sched, 0, sizeof(sched)); + osi_free(p_start); + return FALSE; + } + + if (tc_aes_encrypt(p_rev_output, p_rev_data, &sched) == TC_CRYPTO_FAIL) { + SMP_TRACE_ERROR("%s tc_aes_encrypt failed\n", __func__); + memset(&sched, 0, sizeof(sched)); + osi_free(p_start); + return FALSE; + } + + /* Clear sensitive data from key schedule */ + memset(&sched, 0, sizeof(sched)); + } +#else + { + aes_context ctx; + aes_set_key(p_rev_key, SMP_ENCRYT_KEY_SIZE, &ctx); + bluedroid_aes_encrypt(p_rev_data, p_rev_output, &ctx); /* outputs in byte 48 to byte 63 */ + } +#endif /* SMP_CRYPTO_MBEDTLS */ p = p_out->param_buf; REVERSE_ARRAY_TO_STREAM (p, p_rev_output, SMP_ENCRYT_DATA_SIZE); @@ -207,6 +283,8 @@ BOOLEAN smp_encrypt_data (UINT8 *key, UINT8 key_len, p_out->status = HCI_SUCCESS; p_out->opcode = HCI_BLE_ENCRYPT; + /* Clear sensitive data (including key at byte 32-47) before freeing */ + memset(p_start, 0, SMP_ENCRYT_DATA_SIZE * 4); osi_free(p_start); return TRUE; @@ -1118,8 +1196,6 @@ void smp_continue_private_key_creation (tSMP_CB *p_cb, tBTM_RAND_ENC *p) *******************************************************************************/ void smp_process_private_key(tSMP_CB *p_cb) { - Point public_key; - BT_OCTET32 private_key; tSMP_LOC_OOB_DATA *p_loc_oob = &p_cb->sc_oob_data.loc_oob_data; SMP_TRACE_DEBUG ("%s", __FUNCTION__); @@ -1131,10 +1207,101 @@ void smp_process_private_key(tSMP_CB *p_cb) memcpy(p_cb->loc_publ_key.y, p_loc_oob->publ_key_used.y, BT_OCTET32_LEN); memcpy(p_cb->local_random, p_loc_oob->randomizer, BT_OCTET16_LEN); } else { +#if (SMP_CRYPTO_MBEDTLS == TRUE) + mbedtls_ecp_keypair keypair = {0}; + int rc; + size_t olen; + + mbedtls_ecp_keypair_init(&keypair); + + /* Load the group */ + rc = mbedtls_ecp_group_load(&keypair.MBEDTLS_PRIVATE(grp), MBEDTLS_ECP_DP_SECP256R1); + if (rc != 0) { + SMP_TRACE_ERROR("%s mbedtls_ecp_group_load failed: %d\n", __FUNCTION__, rc); + goto mbedtls_pubkey_cleanup; + } + + /* Import private key (little-endian) */ + rc = mbedtls_mpi_read_binary(&keypair.MBEDTLS_PRIVATE(d), p_cb->private_key, BT_OCTET32_LEN); + if (rc != 0) { + SMP_TRACE_ERROR("%s mbedtls_mpi_read_binary failed: %d\n", __FUNCTION__, rc); + goto mbedtls_pubkey_cleanup; + } + + /* Validate private key */ + rc = mbedtls_ecp_check_privkey(&keypair.MBEDTLS_PRIVATE(grp), &keypair.MBEDTLS_PRIVATE(d)); + if (rc != 0) { + SMP_TRACE_ERROR("%s mbedtls_ecp_check_privkey failed: %d\n", __FUNCTION__, rc); + goto mbedtls_pubkey_cleanup; + } + + /* Compute public key from private key */ + /* mbedtls_ecp_keypair_calc_public requires a non-NULL RNG function for side-channel protection */ + rc = mbedtls_ecp_keypair_calc_public(&keypair, smp_mbedtls_rng, NULL); + if (rc != 0) { + SMP_TRACE_ERROR("%s mbedtls_ecp_keypair_calc_public failed: %d\n", __FUNCTION__, rc); + goto mbedtls_pubkey_cleanup; + } + + /* Export public key in uncompressed format: 0x04 || X || Y */ + UINT8 pub_be[BT_OCTET32_LEN + BT_OCTET32_LEN + 1]; + rc = mbedtls_ecp_point_write_binary(&keypair.MBEDTLS_PRIVATE(grp), &keypair.MBEDTLS_PRIVATE(Q), + MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, pub_be, sizeof(pub_be)); + if (rc != 0 || olen != sizeof(pub_be)) { + SMP_TRACE_ERROR("%s mbedtls_ecp_point_write_binary failed: %d\n", __FUNCTION__, rc); + goto mbedtls_pubkey_cleanup; + } + + /* Convert X and Y from big-endian to little-endian */ + /* pub_be: 0x04 || X (32 bytes) || Y (32 bytes) */ + for (int i = 0; i < BT_OCTET32_LEN; i++) { + p_cb->loc_publ_key.x[i] = pub_be[1 + BT_OCTET32_LEN - 1 - i]; + p_cb->loc_publ_key.y[i] = pub_be[33 + BT_OCTET32_LEN - 1 - i]; + } + +mbedtls_pubkey_cleanup: + /* Clear sensitive data - mbedtls_ecp_keypair_free will zero the private key */ + mbedtls_ecp_keypair_free(&keypair); + /* Note: pub_be contains public key data, no need to clear */ +#elif (SMP_CRYPTO_TINYCRYPT == TRUE) + { + UINT8 pub_key[64]; /* TinyCrypt format: X (32 bytes) || Y (32 bytes), no prefix */ + UINT8 priv_be[BT_OCTET32_LEN]; + + /* Convert private key from little-endian to big-endian */ + for (int i = 0; i < BT_OCTET32_LEN; i++) { + priv_be[i] = p_cb->private_key[BT_OCTET32_LEN - 1 - i]; + } + + /* Compute public key from private key */ + /* uECC_compute_public_key returns 1 if successful, 0 if failed */ + if (uECC_compute_public_key(priv_be, pub_key, uECC_secp256r1()) != TC_CRYPTO_SUCCESS) { + SMP_TRACE_ERROR("%s uECC_compute_public_key failed\n", __FUNCTION__); + memset(priv_be, 0, sizeof(priv_be)); + memset(pub_key, 0, sizeof(pub_key)); + return; + } + + /* Convert X and Y from big-endian to little-endian */ + /* TinyCrypt format: X (32 bytes) || Y (32 bytes) */ + for (int i = 0; i < BT_OCTET32_LEN; i++) { + p_cb->loc_publ_key.x[i] = pub_key[BT_OCTET32_LEN - 1 - i]; + p_cb->loc_publ_key.y[i] = pub_key[BT_OCTET32_LEN + BT_OCTET32_LEN - 1 - i]; + } + + /* Clear sensitive data from stack */ + memset(priv_be, 0, sizeof(priv_be)); + memset(pub_key, 0, sizeof(pub_key)); + } +#else + Point public_key; + BT_OCTET32 private_key; + memcpy(private_key, p_cb->private_key, BT_OCTET32_LEN); ECC_PointMult(&public_key, &(curve_p256.G), (DWORD *) private_key, KEY_LENGTH_DWORDS_P256); memcpy(p_cb->loc_publ_key.x, public_key.x, BT_OCTET32_LEN); memcpy(p_cb->loc_publ_key.y, public_key.y, BT_OCTET32_LEN); +#endif /* SMP_CRYPTO_MBEDTLS */ } smp_debug_print_nbyte_little_endian (p_cb->private_key, (const UINT8 *)"private", @@ -1161,11 +1328,137 @@ void smp_process_private_key(tSMP_CB *p_cb) *******************************************************************************/ void smp_compute_dhkey (tSMP_CB *p_cb) { + SMP_TRACE_DEBUG ("%s\n", __FUNCTION__); + +#if (SMP_CRYPTO_MBEDTLS == TRUE) + mbedtls_ecp_group grp = {0}; + mbedtls_ecp_point Q = {0}; + mbedtls_mpi d = {0}; + mbedtls_mpi z = {0}; + int rc; + UINT8 peer_pub_be[BT_OCTET32_LEN + BT_OCTET32_LEN + 1]; /* 0x04 || X (32 bytes) || Y (32 bytes) */ + + mbedtls_ecp_group_init(&grp); + mbedtls_ecp_point_init(&Q); + mbedtls_mpi_init(&d); + mbedtls_mpi_init(&z); + + /* Load the group */ + rc = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1); + if (rc != 0) { + SMP_TRACE_ERROR("%s mbedtls_ecp_group_load failed: %d\n", __FUNCTION__, rc); + goto mbedtls_dhkey_cleanup; + } + + /* Import private key (little-endian) */ + rc = mbedtls_mpi_read_binary(&d, p_cb->private_key, BT_OCTET32_LEN); + if (rc != 0) { + SMP_TRACE_ERROR("%s mbedtls_mpi_read_binary failed: %d\n", __FUNCTION__, rc); + goto mbedtls_dhkey_cleanup; + } + + /* Construct peer public key in uncompressed format: 0x04 || X || Y */ + peer_pub_be[0] = 0x04; + for (int i = 0; i < BT_OCTET32_LEN; i++) { + peer_pub_be[1 + i] = p_cb->peer_publ_key.x[BT_OCTET32_LEN - 1 - i]; + peer_pub_be[33 + i] = p_cb->peer_publ_key.y[BT_OCTET32_LEN - 1 - i]; + } + + /* Read peer public key */ + rc = mbedtls_ecp_point_read_binary(&grp, &Q, peer_pub_be, sizeof(peer_pub_be)); + if (rc != 0) { + SMP_TRACE_ERROR("%s mbedtls_ecp_point_read_binary failed: %d\n", __FUNCTION__, rc); + goto mbedtls_dhkey_cleanup; + } + + /* Validate peer public key */ + rc = mbedtls_ecp_check_pubkey(&grp, &Q); + if (rc != 0) { + SMP_TRACE_ERROR("%s mbedtls_ecp_check_pubkey failed: %d\n", __FUNCTION__, rc); + goto mbedtls_dhkey_cleanup; + } + + /* Compute ECDH shared secret */ + /* mbedtls_ecdh_compute_shared requires a non-NULL RNG function for side-channel protection */ + rc = mbedtls_ecdh_compute_shared(&grp, &z, &Q, &d, smp_mbedtls_rng, NULL); + if (rc != 0) { + SMP_TRACE_ERROR("%s mbedtls_ecdh_compute_shared failed: %d\n", __FUNCTION__, rc); + goto mbedtls_dhkey_cleanup; + } + + /* Export shared secret (big-endian) and convert to little-endian for DHKey */ + UINT8 shared_secret[BT_OCTET32_LEN]; + rc = mbedtls_mpi_write_binary(&z, shared_secret, BT_OCTET32_LEN); + if (rc != 0) { + SMP_TRACE_ERROR("%s mbedtls_mpi_write_binary failed: %d\n", __FUNCTION__, rc); + goto mbedtls_dhkey_cleanup; + } + + /* Convert shared secret from big-endian to little-endian for DHKey */ + for (int i = 0; i < BT_OCTET32_LEN; i++) { + p_cb->dhkey[i] = shared_secret[BT_OCTET32_LEN - 1 - i]; + } + +mbedtls_dhkey_cleanup: + /* Clear sensitive data - mbedtls_mpi_free will zero the memory */ + mbedtls_mpi_free(&z); + mbedtls_mpi_free(&d); + mbedtls_ecp_point_free(&Q); + mbedtls_ecp_group_free(&grp); + /* Clear sensitive data from stack */ + memset(shared_secret, 0, sizeof(shared_secret)); + /* Note: peer_pub_be contains public key data, no need to clear */ +#elif (SMP_CRYPTO_TINYCRYPT == TRUE) + { + UINT8 priv_be[BT_OCTET32_LEN]; + UINT8 peer_pub_be[64]; /* TinyCrypt format: X (32 bytes) || Y (32 bytes), no prefix */ + UINT8 shared_secret[BT_OCTET32_LEN]; + + /* Convert private key from little-endian to big-endian */ + for (int i = 0; i < BT_OCTET32_LEN; i++) { + priv_be[i] = p_cb->private_key[BT_OCTET32_LEN - 1 - i]; + } + + /* Convert peer public key from little-endian to big-endian */ + /* TinyCrypt format: X (32 bytes) || Y (32 bytes), no prefix */ + for (int i = 0; i < BT_OCTET32_LEN; i++) { + peer_pub_be[i] = p_cb->peer_publ_key.x[BT_OCTET32_LEN - 1 - i]; + peer_pub_be[BT_OCTET32_LEN + i] = p_cb->peer_publ_key.y[BT_OCTET32_LEN - 1 - i]; + } + + /* Validate peer public key */ + /* uECC_valid_public_key returns 0 if valid, negative value if invalid */ + if (uECC_valid_public_key(peer_pub_be, uECC_secp256r1()) < 0) { + SMP_TRACE_ERROR("%s Invalid peer public key\n", __FUNCTION__); + memset(priv_be, 0, sizeof(priv_be)); + memset(peer_pub_be, 0, sizeof(peer_pub_be)); + return; + } + + /* Compute ECDH shared secret */ + /* uECC_shared_secret returns TC_CRYPTO_SUCCESS (1) if successful, TC_CRYPTO_FAIL (0) if failed */ + if (uECC_shared_secret(peer_pub_be, priv_be, shared_secret, uECC_secp256r1()) != TC_CRYPTO_SUCCESS) { + SMP_TRACE_ERROR("%s uECC_shared_secret failed\n", __FUNCTION__); + memset(priv_be, 0, sizeof(priv_be)); + memset(peer_pub_be, 0, sizeof(peer_pub_be)); + memset(shared_secret, 0, sizeof(shared_secret)); + return; + } + + /* Convert shared secret from big-endian to little-endian for DHKey */ + for (int i = 0; i < BT_OCTET32_LEN; i++) { + p_cb->dhkey[i] = shared_secret[BT_OCTET32_LEN - 1 - i]; + } + + /* Clear sensitive data from stack */ + memset(priv_be, 0, sizeof(priv_be)); + memset(peer_pub_be, 0, sizeof(peer_pub_be)); + memset(shared_secret, 0, sizeof(shared_secret)); + } +#else Point peer_publ_key, new_publ_key; BT_OCTET32 private_key; - SMP_TRACE_DEBUG ("%s\n", __FUNCTION__); - memcpy(private_key, p_cb->private_key, BT_OCTET32_LEN); memcpy(peer_publ_key.x, p_cb->peer_publ_key.x, BT_OCTET32_LEN); memcpy(peer_publ_key.y, p_cb->peer_publ_key.y, BT_OCTET32_LEN); @@ -1173,8 +1466,9 @@ void smp_compute_dhkey (tSMP_CB *p_cb) ECC_PointMult(&new_publ_key, &peer_publ_key, (DWORD *) private_key, KEY_LENGTH_DWORDS_P256); memcpy(p_cb->dhkey, new_publ_key.x, BT_OCTET32_LEN); +#endif /* SMP_CRYPTO_MBEDTLS */ - smp_debug_print_nbyte_little_endian (p_cb->dhkey, (const UINT8 *)"Old DHKey", + smp_debug_print_nbyte_little_endian (p_cb->dhkey, (const UINT8 *)"DHKey", BT_OCTET32_LEN); smp_debug_print_nbyte_little_endian (p_cb->private_key, (const UINT8 *)"private", @@ -1183,8 +1477,6 @@ void smp_compute_dhkey (tSMP_CB *p_cb) BT_OCTET32_LEN); smp_debug_print_nbyte_little_endian (p_cb->peer_publ_key.y, (const UINT8 *)"rem public(y)", BT_OCTET32_LEN); - smp_debug_print_nbyte_little_endian (p_cb->dhkey, (const UINT8 *)"Reverted DHKey", - BT_OCTET32_LEN); } /******************************************************************************* diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/CMakeLists.txt b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/CMakeLists.txt new file mode 100644 index 0000000000..6daf7bc1f0 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/CMakeLists.txt @@ -0,0 +1,8 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(enc_adv_data_cent) diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/README.md b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/README.md new file mode 100644 index 0000000000..7649b1e653 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/README.md @@ -0,0 +1,273 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | + +# BLE Encrypted Advertising Data Central Example (Bluedroid) + +This example demonstrates how to receive and decrypt BLE Encrypted Advertising Data (EAD) with Bluedroid stack. + +## Overview + +This central example works with the `enc_adv_data_prph` peripheral example to demonstrate: + +1. Scanning for devices that advertise encrypted data +2. Connecting to read the Key Material characteristic +3. Decrypting advertising data using the obtained key + +## Two Operation Modes + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Mode Selection β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ Mode 1: With Connection β”‚ Mode 2: No Connection β”‚ +β”‚ (Default) β”‚ (Pre-shared Key) β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ β”‚ +β”‚ First scan: β”‚ All scans: β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Central │══▢│ Periph β”‚ β”‚ β”‚ Central │──▢│ Periph β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ Connect β”‚ β”‚ β”‚ Scan only β”‚ +β”‚ β”‚ Read Key β”‚ β”‚ β–Ό β”‚ +β”‚ β”‚ Disconnect β”‚ β”‚ Use pre-configured β”‚ +β”‚ β–Ό β”‚ β”‚ key to decrypt β”‚ +β”‚ Store key β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β–Ό β”‚ +β”‚ β–Ό β”‚ β”‚ βœ… Decrypt immediately β”‚ +β”‚ Later scans: β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ Central │──▢│ Periph β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ No connection needed β”‚ β”‚ +β”‚ β–Ό β”‚ β”‚ +β”‚ βœ… Decrypt using stored key β”‚ β”‚ +β”‚ β”‚ β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ βœ“ Secure key exchange β”‚ βœ“ No connection latency β”‚ +β”‚ βœ“ Dynamic key support β”‚ βœ“ Simpler implementation β”‚ +β”‚ βœ— First-time connection needed β”‚ βœ— Key must be pre-provisioned β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## System Flow Diagram + +### Mode 1: With Connection (Default) + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Central Flow - With Connection Mode β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Central β”‚ β”‚ Peripheral β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ ════════════════ First Encounter ════════════════ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 1. Scan β”‚ β”‚ +β”‚ β”‚ ──────────────────────────────────────────────────▢ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 2. Receive Adv (UUID=0x2C01, Encrypted Data) β”‚ β”‚ +β”‚ β”‚ ◀────────────────────────────────────────────────── β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ [No key yet - cannot decrypt] β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 3. Connect β”‚ β”‚ +β”‚ β”‚ ═══════════════════════════════════════════════════▢│ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 4. Establish Encrypted Link (Pairing) β”‚ β”‚ +β”‚ β”‚ ◀═══════════════════════════════════════════════════│ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 5. Read Key Material Characteristic (0x2B88) β”‚ β”‚ +β”‚ β”‚ ═══════════════════════════════════════════════════▢│ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 6. Response: [Session Key (16B)] [IV (8B)] β”‚ β”‚ +β”‚ β”‚ ◀═══════════════════════════════════════════════════│ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 7. Store key in memory β”‚ β”‚ +β”‚ β”‚ 8. Disconnect β”‚ β”‚ +β”‚ β”‚ ═══════════════════════════════════════════════════▢│ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ ════════════════ Later Scans ════════════════════ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 9. Scan β”‚ β”‚ +β”‚ β”‚ ──────────────────────────────────────────────────▢ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 10. Receive Encrypted Adv Data β”‚ β”‚ +β”‚ β”‚ ◀────────────────────────────────────────────────── β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 11. Decrypt using stored key (NO CONNECTION!) β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ ble_ead_decrypt(session_key, iv, ...) β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ Result: "prph" (decrypted name) β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β–Ό β–Ό β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Mode 2: No Connection (Pre-shared Key) + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Central Flow - No Connection Mode β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Pre-configured Key (same as Peripheral) β”‚ β”‚ +β”‚ β”‚ Session Key: 19 6a 0a d1 2a 61 20 1e β”‚ β”‚ +β”‚ β”‚ 13 6e 2e d1 12 da a9 57 β”‚ β”‚ +β”‚ β”‚ IV: 9E 7a 00 ef b1 7a e7 46 β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Central β”‚ β”‚ Peripheral β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 1. Scan (passive) β”‚ β”‚ +β”‚ β”‚ ──────────────────────────────────────────────────▢ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 2. Receive Encrypted Adv Data β”‚ β”‚ +β”‚ β”‚ ◀────────────────────────────────────────────────── β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 3. Immediately decrypt (NO CONNECTION!) β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ ble_ead_decrypt(pre_shared_key, ...) β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ Result: "prph" (decrypted name) β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β–Ό β–Ό β”‚ +β”‚ β”‚ +β”‚ ⚑ No connection overhead - instant decryption! β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Decryption Process + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Decryption Process β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ Received Encrypted Advertising Data: β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Randomizer β”‚ Ciphertext β”‚ MIC β”‚ β”‚ +β”‚ β”‚ (5 bytes) β”‚ (6 bytes) β”‚ (4 bytes) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ β”‚ β”‚ +β”‚ 1. Extract Randomizer β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ +β”‚ β”‚ XX XX XX XX [D|XX] β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ β”‚ β”‚ +β”‚ 2. Build Nonce β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Randomizer (5B) + IV from Key Material (8B) = Nonce (13B) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β–Ό β–Ό β–Ό β”‚ +β”‚ 3. AES-CCM Decrypt + Verify β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Input: Ciphertext, MIC, Nonce, Session Key, AAD (0xEA) β”‚ β”‚ +β”‚ β”‚ Algorithm: AES-CCM-128 Authenticated Decryption β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ 4. Output: Decrypted Plaintext β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ [05] [09] [70 72 70 68] β”‚ β”‚ +β”‚ β”‚ Len Type 'p' 'r' 'p' 'h' β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β†’ Complete Local Name: "prph" β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## How to Use Example + +### Hardware Required + +* Two development boards with ESP32/ESP32-C2/ESP32-C3/ESP32-C5/ESP32-C6/ESP32-C61/ESP32-H2/ESP32-S3 SoC +* USB cables for power supply and programming + +### Setup + +1. Flash `enc_adv_data_prph` on one board (peripheral) +2. Flash `enc_adv_data_cent` on another board (central) + +### Configure the project + +```bash +idf.py set-target +idf.py menuconfig +``` + +In menuconfig, navigate to: +- `Example Configuration` β†’ `Central Mode` + - `With Connection` - Connect to read key (default) + - `No Connection` - Use pre-shared key + +### Build and Flash + +```bash +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type `Ctrl-]`.) + +### Example Output + +**Mode 1 - With Connection (first scan):** +``` +I (XXX) ENC_ADV_CENT: Encrypted Advertising Data Central started +I (XXX) ENC_ADV_CENT: Scanning started +I (XXX) ENC_ADV_CENT: Found target device: xx:xx:xx:xx:xx:xx +I (XXX) ENC_ADV_CENT: Connecting to get key material... +I (XXX) ENC_ADV_CENT: Connected, conn_id 0 +I (XXX) ENC_ADV_CENT: Authentication success +I (XXX) ENC_ADV_CENT: Key material received: +I (XXX) ENC_ADV_CENT: 19 6a 0a d1 2a 61 20 1e 13 6e 2e d1 12 da a9 57 9e 7a 00 ef b1 7a e7 46 +I (XXX) ENC_ADV_CENT: Disconnected +``` + +**Mode 1 - With Connection (subsequent scans):** +``` +I (XXX) ENC_ADV_CENT: Found target device: xx:xx:xx:xx:xx:xx +I (XXX) ENC_ADV_CENT: Have key material, decrypting... +I (XXX) ENC_ADV_CENT: Decryption successful! +I (XXX) ENC_ADV_CENT: Decrypted data: +I (XXX) ENC_ADV_CENT: 05 09 70 72 70 68 +I (XXX) ENC_ADV_CENT: Decrypted device name: prph +``` + +**Mode 2 - No Connection:** +``` +I (XXX) ENC_ADV_CENT_SIMPLE: ======================================== +I (XXX) ENC_ADV_CENT_SIMPLE: EAD Central - No Connection Mode +I (XXX) ENC_ADV_CENT_SIMPLE: ======================================== +I (XXX) ENC_ADV_CENT_SIMPLE: ⚑ This example decrypts WITHOUT connecting! +I (XXX) ENC_ADV_CENT_SIMPLE: πŸ” Scanning started (no connection mode) +... +I (XXX) ENC_ADV_CENT_SIMPLE: βœ… Decryption successful (no connection needed!) +I (XXX) ENC_ADV_CENT_SIMPLE: πŸ“› Decrypted device name: "prph" +``` + +## Troubleshooting + +### Authentication Failed +- Ensure both devices have matching security parameters +- Try erasing NVS on both devices: `idf.py erase_flash` + +### Decryption Failed +- Verify the peripheral and central use matching session key and IV +- Check that the encrypted advertising data format is correct + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/CMakeLists.txt b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/CMakeLists.txt new file mode 100644 index 0000000000..6bf438be39 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/CMakeLists.txt @@ -0,0 +1,8 @@ +if(CONFIG_EXAMPLE_MODE_NO_CONNECTION) + set(MAIN_SRC "enc_adv_data_cent_no_connect.c") +else() + set(MAIN_SRC "enc_adv_data_cent.c") +endif() + +idf_component_register(SRCS ${MAIN_SRC} "ble_ead.c" + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/Kconfig.projbuild b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/Kconfig.projbuild new file mode 100644 index 0000000000..a4c00b02ea --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/Kconfig.projbuild @@ -0,0 +1,22 @@ +menu "Example Configuration" + + choice EXAMPLE_MODE + prompt "Central Mode" + default EXAMPLE_MODE_WITH_CONNECTION + help + Select the central operation mode. + + config EXAMPLE_MODE_WITH_CONNECTION + bool "With Connection (read key from peripheral)" + help + Connect to peripheral to read Key Material characteristic. + This is the standard way when key is not pre-shared. + + config EXAMPLE_MODE_NO_CONNECTION + bool "No Connection (use pre-shared key)" + help + Use pre-configured key to decrypt without connecting. + Key must match the peripheral's key. + endchoice + +endmenu diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/ble_ead.c b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/ble_ead.c new file mode 100644 index 0000000000..92420dd406 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/ble_ead.c @@ -0,0 +1,394 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "ble_ead.h" +#include "esp_random.h" +#include "esp_log.h" +#include "sdkconfig.h" + +#define TAG "BLE_EAD" + +/* Select crypto library based on configuration */ +#if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT) +#include "tinycrypt/aes.h" +#include "tinycrypt/ccm_mode.h" +#include "tinycrypt/constants.h" +#elif defined(CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS) +#include "mbedtls/ccm.h" +#else +#error "Please select either CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT or CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS" +#endif + +/* Additional Authenticated Data for EAD - EA (Encrypted Advertising) */ +static const uint8_t ble_ead_aad[BLE_EAD_AAD_SIZE] = { 0xEA }; + +/** + * @brief Generate randomizer with direction bit set + * + * Per Bluetooth Core Spec Supplement v11, Part A 1.23.3: + * The MSB of the Randomizer shall be set to indicate direction + */ +static int ble_ead_generate_randomizer(uint8_t randomizer[BLE_EAD_RANDOMIZER_SIZE]) +{ + /* Generate random bytes */ + esp_fill_random(randomizer, BLE_EAD_RANDOMIZER_SIZE); + + /* Set direction bit (MSB of last byte) - required by spec */ + randomizer[BLE_EAD_RANDOMIZER_SIZE - 1] |= (1 << BLE_EAD_RANDOMIZER_DIRECTION_BIT); + + return 0; +} + +/** + * @brief Generate nonce from IV and randomizer + * + * Nonce = Randomizer (5 bytes) || IV (8 bytes) = 13 bytes + */ +static int ble_ead_generate_nonce(const uint8_t iv[BLE_EAD_IV_SIZE], + const uint8_t randomizer[BLE_EAD_RANDOMIZER_SIZE], + uint8_t nonce[BLE_EAD_NONCE_SIZE]) +{ + if (iv == NULL || nonce == NULL) { + return -1; + } + + /* Randomizer in first 5 bytes */ + if (randomizer != NULL) { + memcpy(nonce, randomizer, BLE_EAD_RANDOMIZER_SIZE); + } else { + /* Generate new randomizer with direction bit */ + ble_ead_generate_randomizer(nonce); + } + + /* IV in last 8 bytes */ + memcpy(nonce + BLE_EAD_RANDOMIZER_SIZE, iv, BLE_EAD_IV_SIZE); + + return 0; +} + +/** + * @brief AES-CCM encryption using selected crypto library + */ +static int ble_aes_ccm_encrypt(const uint8_t *key, const uint8_t *nonce, + const uint8_t *plaintext, size_t plaintext_len, + const uint8_t *aad, size_t aad_len, + uint8_t *ciphertext, size_t tag_len) +{ +#if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT) + struct tc_aes_key_sched_struct sched; + struct tc_ccm_mode_struct ccm_state; + int ret; + + /* Validate inputs */ + if (key == NULL || nonce == NULL || ciphertext == NULL) { + ESP_LOGE(TAG, "Invalid input parameters"); + return -1; + } + + /* Set AES encryption key */ + ret = tc_aes128_set_encrypt_key(&sched, key); + if (ret != TC_CRYPTO_SUCCESS) { + ESP_LOGE(TAG, "tc_aes128_set_encrypt_key failed"); + memset(&sched, 0, sizeof(sched)); + return -1; + } + + /* Configure CCM mode */ + ccm_state.sched = &sched; + ccm_state.nonce = (uint8_t *)nonce; + ccm_state.mlen = tag_len; + + ret = tc_ccm_config(&ccm_state, &sched, (uint8_t *)nonce, BLE_EAD_NONCE_SIZE, tag_len); + if (ret != TC_CRYPTO_SUCCESS) { + ESP_LOGE(TAG, "tc_ccm_config failed"); + memset(&sched, 0, sizeof(sched)); + memset(&ccm_state, 0, sizeof(ccm_state)); + return -1; + } + + /* Encrypt and generate tag */ + /* TinyCrypt outputs: ciphertext || tag */ + ret = tc_ccm_generation_encryption(ciphertext, plaintext_len + tag_len, + aad, aad_len, + plaintext, plaintext_len, + &ccm_state); + if (ret != TC_CRYPTO_SUCCESS) { + ESP_LOGE(TAG, "tc_ccm_generation_encryption failed"); + memset(&sched, 0, sizeof(sched)); + memset(&ccm_state, 0, sizeof(ccm_state)); + return -1; + } + + /* Clear sensitive data from key schedule */ + memset(&sched, 0, sizeof(sched)); + memset(&ccm_state, 0, sizeof(ccm_state)); + return 0; + +#elif defined(CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS) + mbedtls_ccm_context ctx = {0}; + int ret; + + /* Validate inputs */ + if (key == NULL || nonce == NULL || ciphertext == NULL) { + ESP_LOGE(TAG, "Invalid input parameters"); + return -1; + } + + mbedtls_ccm_init(&ctx); + + /* Set encryption key */ + ret = mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key, BLE_EAD_KEY_SIZE * 8); + if (ret != 0) { + ESP_LOGE(TAG, "mbedtls_ccm_setkey failed: %d", ret); + mbedtls_ccm_free(&ctx); + return -1; + } + + /* Encrypt and authenticate */ + /* mbedtls_ccm_encrypt_and_tag outputs: ciphertext || tag */ + ret = mbedtls_ccm_encrypt_and_tag(&ctx, plaintext_len, + nonce, BLE_EAD_NONCE_SIZE, + aad, aad_len, + plaintext, ciphertext, + ciphertext + plaintext_len, tag_len); + if (ret != 0) { + ESP_LOGE(TAG, "mbedtls_ccm_encrypt_and_tag failed: %d", ret); + mbedtls_ccm_free(&ctx); + return -1; + } + + mbedtls_ccm_free(&ctx); + return 0; +#else + #error "No crypto library selected" +#endif +} + +/** + * @brief AES-CCM decryption with authentication using selected crypto library + */ +static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce, + const uint8_t *ciphertext, size_t ciphertext_len, + const uint8_t *aad, size_t aad_len, + uint8_t *plaintext, size_t tag_len) +{ +#if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT) + struct tc_aes_key_sched_struct sched; + struct tc_ccm_mode_struct ccm_state; + int ret; + /* ciphertext_len here includes both ciphertext and tag */ + size_t plaintext_len; + + /* Validate inputs */ + if (key == NULL || nonce == NULL || ciphertext == NULL || plaintext == NULL) { + ESP_LOGE(TAG, "Invalid input parameters"); + return -1; + } + + /* Check for integer underflow */ + if (ciphertext_len < tag_len) { + ESP_LOGE(TAG, "ciphertext_len (%zu) < tag_len (%zu)", ciphertext_len, tag_len); + return -1; + } + + plaintext_len = ciphertext_len - tag_len; + + /* Set AES encryption key */ + ret = tc_aes128_set_encrypt_key(&sched, key); + if (ret != TC_CRYPTO_SUCCESS) { + ESP_LOGE(TAG, "tc_aes128_set_encrypt_key failed"); + memset(&sched, 0, sizeof(sched)); + return -1; + } + + /* Configure CCM mode */ + ccm_state.sched = &sched; + ccm_state.nonce = (uint8_t *)nonce; + ccm_state.mlen = tag_len; + + ret = tc_ccm_config(&ccm_state, &sched, (uint8_t *)nonce, BLE_EAD_NONCE_SIZE, tag_len); + if (ret != TC_CRYPTO_SUCCESS) { + ESP_LOGE(TAG, "tc_ccm_config failed"); + memset(&sched, 0, sizeof(sched)); + memset(&ccm_state, 0, sizeof(ccm_state)); + return -1; + } + + /* Decrypt and verify tag */ + /* TinyCrypt expects: ciphertext || tag */ + ret = tc_ccm_decryption_verification(plaintext, plaintext_len, + aad, aad_len, + (uint8_t *)ciphertext, ciphertext_len, + &ccm_state); + if (ret != TC_CRYPTO_SUCCESS) { + ESP_LOGE(TAG, "tc_ccm_decryption_verification failed"); + memset(&sched, 0, sizeof(sched)); + memset(&ccm_state, 0, sizeof(ccm_state)); + return -1; + } + + /* Clear sensitive data from key schedule */ + memset(&sched, 0, sizeof(sched)); + memset(&ccm_state, 0, sizeof(ccm_state)); + return 0; + +#elif defined(CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS) + mbedtls_ccm_context ctx = {0}; + int ret; + /* ciphertext_len here includes both ciphertext and tag */ + size_t plaintext_len; + + /* Validate inputs */ + if (key == NULL || nonce == NULL || ciphertext == NULL || plaintext == NULL) { + ESP_LOGE(TAG, "Invalid input parameters"); + return -1; + } + + /* Check for integer underflow */ + if (ciphertext_len < tag_len) { + ESP_LOGE(TAG, "ciphertext_len (%zu) < tag_len (%zu)", ciphertext_len, tag_len); + return -1; + } + + plaintext_len = ciphertext_len - tag_len; + + mbedtls_ccm_init(&ctx); + + /* Set decryption key */ + ret = mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key, BLE_EAD_KEY_SIZE * 8); + if (ret != 0) { + ESP_LOGE(TAG, "mbedtls_ccm_setkey failed: %d", ret); + mbedtls_ccm_free(&ctx); + return -1; + } + + /* Decrypt and verify */ + /* mbedtls_ccm_auth_decrypt expects: ciphertext || tag */ + /* ciphertext_len here already includes tag length */ + ret = mbedtls_ccm_auth_decrypt(&ctx, plaintext_len, + nonce, BLE_EAD_NONCE_SIZE, + aad, aad_len, + ciphertext, plaintext, + ciphertext + plaintext_len, tag_len); + if (ret != 0) { + ESP_LOGE(TAG, "mbedtls_ccm_auth_decrypt failed: %d", ret); + mbedtls_ccm_free(&ctx); + return -1; + } + + mbedtls_ccm_free(&ctx); + return 0; +#else + #error "No crypto library selected" +#endif +} + +int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], + const uint8_t iv[BLE_EAD_IV_SIZE], + const uint8_t *payload, size_t payload_size, + uint8_t *encrypted_payload) +{ + int ret; + uint8_t nonce[BLE_EAD_NONCE_SIZE]; + + if (session_key == NULL) { + ESP_LOGE(TAG, "session_key is NULL"); + return -1; + } + + if (iv == NULL) { + ESP_LOGE(TAG, "iv is NULL"); + return -1; + } + + if (payload == NULL && payload_size > 0) { + ESP_LOGE(TAG, "payload is NULL but payload_size > 0"); + return -1; + } + + if (encrypted_payload == NULL) { + ESP_LOGE(TAG, "encrypted_payload is NULL"); + return -1; + } + + /* Generate nonce with random randomizer */ + ret = ble_ead_generate_nonce(iv, NULL, nonce); + if (ret != 0) { + return ret; + } + + /* Copy randomizer to the start of encrypted payload */ + memcpy(encrypted_payload, nonce, BLE_EAD_RANDOMIZER_SIZE); + + /* Encrypt: output = ciphertext + MIC */ + ret = ble_aes_ccm_encrypt(session_key, nonce, + payload, payload_size, + ble_ead_aad, BLE_EAD_AAD_SIZE, + &encrypted_payload[BLE_EAD_RANDOMIZER_SIZE], + BLE_EAD_MIC_SIZE); + + return ret; +} + +int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], + const uint8_t iv[BLE_EAD_IV_SIZE], + const uint8_t *encrypted_payload, size_t encrypted_payload_size, + uint8_t *payload) +{ + int ret; + uint8_t nonce[BLE_EAD_NONCE_SIZE]; + const uint8_t *randomizer; + const uint8_t *ciphertext; + size_t ciphertext_len; + + if (session_key == NULL) { + ESP_LOGE(TAG, "session_key is NULL"); + return -1; + } + + if (iv == NULL) { + ESP_LOGE(TAG, "iv is NULL"); + return -1; + } + + if (encrypted_payload == NULL) { + ESP_LOGE(TAG, "encrypted_payload is NULL"); + return -1; + } + + if (payload == NULL) { + ESP_LOGE(TAG, "payload is NULL"); + return -1; + } + + if (encrypted_payload_size < BLE_EAD_RANDOMIZER_SIZE + BLE_EAD_MIC_SIZE) { + ESP_LOGE(TAG, "encrypted_payload_size too small"); + return -1; + } + + /* Extract randomizer from the start of encrypted payload */ + randomizer = encrypted_payload; + + /* Ciphertext + MIC follows the randomizer */ + ciphertext = &encrypted_payload[BLE_EAD_RANDOMIZER_SIZE]; + /* ciphertext_len includes both ciphertext and MIC (tag) for mbedTLS API */ + ciphertext_len = encrypted_payload_size - BLE_EAD_RANDOMIZER_SIZE; + + /* Generate nonce from randomizer and IV */ + ret = ble_ead_generate_nonce(iv, randomizer, nonce); + if (ret != 0) { + return ret; + } + + /* Decrypt and verify */ + ret = ble_aes_ccm_decrypt(session_key, nonce, + ciphertext, ciphertext_len, + ble_ead_aad, BLE_EAD_AAD_SIZE, + payload, BLE_EAD_MIC_SIZE); + + return ret; +} diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/ble_ead.h b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/ble_ead.h new file mode 100644 index 0000000000..a9bf8954eb --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/ble_ead.h @@ -0,0 +1,95 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef BLE_EAD_H +#define BLE_EAD_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief BLE Encrypted Advertising Data (EAD) definitions + * Based on Bluetooth Core Specification Version 5.4 + */ + +#define BLE_EAD_KEY_SIZE 16 /* 128-bit session key */ +#define BLE_EAD_IV_SIZE 8 /* 64-bit Initialization Vector */ +#define BLE_EAD_RANDOMIZER_SIZE 5 /* 40-bit Randomizer */ +#define BLE_EAD_MIC_SIZE 4 /* 32-bit Message Integrity Check */ +#define BLE_EAD_NONCE_SIZE 13 /* 104-bit Nonce (Randomizer + IV) */ +#define BLE_EAD_AAD_SIZE 1 /* Additional Authenticated Data size */ + +/* Direction bit position in Randomizer (MSB of last byte) + * Per Bluetooth Core Spec Supplement v11, Part A 1.23.3 + */ +#define BLE_EAD_RANDOMIZER_DIRECTION_BIT 7 + +/* AD Type for Encrypted Advertising Data (0x31) */ +#define ESP_BLE_AD_TYPE_ENC_ADV_DATA 0x31 + +/** + * @brief Calculate encrypted payload size from plaintext size + */ +#define BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(payload_size) \ + (BLE_EAD_RANDOMIZER_SIZE + (payload_size) + BLE_EAD_MIC_SIZE) + +/** + * @brief Calculate decrypted payload size from encrypted payload size + */ +#define BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_size) \ + ((encrypted_size) - BLE_EAD_RANDOMIZER_SIZE - BLE_EAD_MIC_SIZE) + +/** + * @brief Key material structure for EAD + */ +typedef struct { + uint8_t session_key[BLE_EAD_KEY_SIZE]; /* 128-bit session key */ + uint8_t iv[BLE_EAD_IV_SIZE]; /* 64-bit Initialization Vector */ +} ble_ead_key_material_t; + +/** + * @brief Encrypt advertising data using AES-CCM + * + * @param session_key 16-byte session key + * @param iv 8-byte Initialization Vector + * @param payload Plaintext advertising data to encrypt + * @param payload_size Size of plaintext data + * @param encrypted_payload Output buffer for encrypted data + * Size must be at least BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(payload_size) + * + * @return 0 on success, negative error code on failure + */ +int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], + const uint8_t iv[BLE_EAD_IV_SIZE], + const uint8_t *payload, size_t payload_size, + uint8_t *encrypted_payload); + +/** + * @brief Decrypt advertising data using AES-CCM + * + * @param session_key 16-byte session key + * @param iv 8-byte Initialization Vector + * @param encrypted_payload Encrypted advertising data (includes randomizer and MIC) + * @param encrypted_payload_size Size of encrypted data + * @param payload Output buffer for decrypted data + * Size must be at least BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size) + * + * @return 0 on success, negative error code on failure + */ +int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], + const uint8_t iv[BLE_EAD_IV_SIZE], + const uint8_t *encrypted_payload, size_t encrypted_payload_size, + uint8_t *payload); + +#ifdef __cplusplus +} +#endif + +#endif /* BLE_EAD_H */ diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/enc_adv_data_cent.c b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/enc_adv_data_cent.c new file mode 100644 index 0000000000..e03c3b86ad --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/enc_adv_data_cent.c @@ -0,0 +1,519 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +/** + * @brief BLE Encrypted Advertising Data Central Example + * + * This example demonstrates how to: + * 1. Scan for devices broadcasting encrypted advertising data + * 2. Connect to read Key Material characteristic + * 3. Decrypt the advertising data using the obtained key + * + * Based on Bluetooth Core Specification Version 5.4 - Encrypted Advertising Data + */ + +#include +#include +#include +#include +#include "nvs.h" +#include "nvs_flash.h" +#include "esp_bt.h" +#include "esp_gap_ble_api.h" +#include "esp_gattc_api.h" +#include "esp_gatt_defs.h" +#include "esp_bt_main.h" +#include "esp_gatt_common_api.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "ble_ead.h" + +#define TAG "ENC_ADV_CENT" + +/* Service and characteristic UUIDs */ +#define GAP_SERVICE_UUID 0x1800 /* GAP Service UUID */ +#define KEY_MATERIAL_CHAR_UUID 0x2B88 /* Key Material Characteristic UUID */ + +/* Profile configuration */ +#define PROFILE_NUM 1 +#define PROFILE_APP_ID 0 +#define INVALID_HANDLE 0 + +/* Maximum peers to track */ +#define MAX_PEERS 5 + +/* Peer information structure */ +typedef struct { + bool valid; + esp_bd_addr_t addr; + bool key_material_exist; + ble_ead_key_material_t key_material; +} peer_info_t; + +static peer_info_t peers[MAX_PEERS] = {0}; + +/* GATT client state */ +static bool is_connected = false; +static bool get_server = false; +static uint16_t conn_id_stored = 0; +static uint16_t service_start_handle = 0; +static uint16_t service_end_handle = 0; +static uint16_t key_material_char_handle = INVALID_HANDLE; +static esp_bd_addr_t current_peer_addr = {0}; + +/* GATT interface */ +static esp_gatt_if_t gattc_if_stored = ESP_GATT_IF_NONE; + +/* Scan parameters */ +static esp_ble_scan_params_t ble_scan_params = { + .scan_type = BLE_SCAN_TYPE_ACTIVE, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_interval = 0x50, + .scan_window = 0x30, + .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE, +}; + +/* Forward declarations */ +static void start_scan(void); + +/** + * @brief Find peer by address + */ +static int find_peer(const esp_bd_addr_t addr) +{ + for (int i = 0; i < MAX_PEERS; i++) { + if (peers[i].valid && memcmp(peers[i].addr, addr, sizeof(esp_bd_addr_t)) == 0) { + return i; + } + } + return -1; +} + +/** + * @brief Add or update peer + */ +static int add_peer(const esp_bd_addr_t addr) +{ + int idx = find_peer(addr); + if (idx >= 0) { + return idx; /* Already exists */ + } + + /* Find empty slot */ + for (int i = 0; i < MAX_PEERS; i++) { + if (!peers[i].valid) { + peers[i].valid = true; + memcpy(peers[i].addr, addr, sizeof(esp_bd_addr_t)); + peers[i].key_material_exist = false; + return i; + } + } + return -1; /* No space */ +} + +/** + * @brief Decrypt encrypted advertising data + */ +static void decrypt_enc_adv_data(const uint8_t *adv_data, uint8_t adv_len, const esp_bd_addr_t addr) +{ + int peer_idx = find_peer(addr); + if (peer_idx < 0 || !peers[peer_idx].key_material_exist) { + ESP_LOGW(TAG, "No key material for peer, cannot decrypt"); + return; + } + + uint8_t offset = 0; + while (offset < adv_len) { + uint8_t len = adv_data[offset]; + if (len == 0 || offset + len >= adv_len) { + break; + } + + uint8_t type = adv_data[offset + 1]; + if (type == ESP_BLE_AD_TYPE_ENC_ADV_DATA) { + /* Found encrypted advertising data */ + const uint8_t *enc_data = &adv_data[offset + 2]; + uint8_t enc_data_len = len - 1; /* Exclude type byte */ + + if (enc_data_len < BLE_EAD_RANDOMIZER_SIZE + BLE_EAD_MIC_SIZE) { + ESP_LOGW(TAG, "Encrypted data too short"); + break; + } + + uint8_t dec_data[32]; /* Buffer for decrypted data */ + size_t dec_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(enc_data_len); + + int rc = ble_ead_decrypt( + peers[peer_idx].key_material.session_key, + peers[peer_idx].key_material.iv, + enc_data, enc_data_len, + dec_data); + + if (rc == 0) { + ESP_LOGI(TAG, "Decryption successful!"); + ESP_LOGI(TAG, "Decrypted data:"); + ESP_LOG_BUFFER_HEX(TAG, dec_data, dec_len); + + /* Parse decrypted advertising structure */ + if (dec_len >= 2) { + uint8_t dec_type = dec_data[1]; + if (dec_type == ESP_BLE_AD_TYPE_NAME_CMPL || dec_type == ESP_BLE_AD_TYPE_NAME_SHORT) { + char name[32] = {0}; + size_t name_len = dec_data[0] - 1; + if (name_len < sizeof(name)) { + memcpy(name, &dec_data[2], name_len); + ESP_LOGI(TAG, "Decrypted device name: %s", name); + } + } + } + } else { + ESP_LOGE(TAG, "Decryption failed: %d", rc); + } + break; + } + offset += len + 1; + } +} + +/** + * @brief Check if device advertises GAP service UUID + */ +static bool should_connect(const uint8_t *adv_data, uint8_t adv_len) +{ + uint8_t offset = 0; + while (offset < adv_len) { + uint8_t len = adv_data[offset]; + if (len == 0 || offset + len >= adv_len) { + break; + } + + uint8_t type = adv_data[offset + 1]; + if (type == ESP_BLE_AD_TYPE_16SRV_CMPL || type == ESP_BLE_AD_TYPE_16SRV_PART) { + /* Check for GAP service UUID */ + for (int i = 0; i < len - 1; i += 2) { + uint16_t uuid = adv_data[offset + 2 + i] | (adv_data[offset + 3 + i] << 8); + if (uuid == GAP_SERVICE_UUID) { + return true; + } + } + } + offset += len + 1; + } + return false; +} + +/** + * @brief Start scanning + */ +static void start_scan(void) +{ + esp_ble_gap_start_scanning(30); /* Scan for 30 seconds */ +} + +/** + * @brief GAP event handler + */ +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + switch (event) { + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: + ESP_LOGI(TAG, "Scan parameters set"); + start_scan(); + break; + + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(TAG, "Scan start failed: %d", param->scan_start_cmpl.status); + } else { + ESP_LOGI(TAG, "Scanning started"); + } + break; + + case ESP_GAP_BLE_SCAN_RESULT_EVT: { + esp_ble_gap_cb_param_t *scan_result = param; + if (scan_result->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { + uint8_t *adv_data = scan_result->scan_rst.ble_adv; + uint8_t adv_len = scan_result->scan_rst.adv_data_len; + + if (should_connect(adv_data, adv_len)) { + ESP_LOGI(TAG, "Found target device: "ESP_BD_ADDR_STR"", + ESP_BD_ADDR_HEX(scan_result->scan_rst.bda)); + + int peer_idx = find_peer(scan_result->scan_rst.bda); + if (peer_idx >= 0 && peers[peer_idx].key_material_exist) { + /* Already have key, try to decrypt */ + ESP_LOGI(TAG, "Have key material, decrypting..."); + decrypt_enc_adv_data(adv_data, adv_len, scan_result->scan_rst.bda); + } else { + /* Need to connect and get key */ + if (!is_connected) { + ESP_LOGI(TAG, "Connecting to get key material..."); + add_peer(scan_result->scan_rst.bda); + memcpy(current_peer_addr, scan_result->scan_rst.bda, sizeof(esp_bd_addr_t)); + + esp_ble_gap_stop_scanning(); + + esp_ble_gatt_creat_conn_params_t conn_params = {0}; + memcpy(conn_params.remote_bda, scan_result->scan_rst.bda, ESP_BD_ADDR_LEN); + conn_params.remote_addr_type = scan_result->scan_rst.ble_addr_type; + conn_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC; + conn_params.is_direct = true; + conn_params.is_aux = false; + esp_ble_gattc_enh_open(gattc_if_stored, &conn_params); + } + } + } + } else if (scan_result->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) { + ESP_LOGI(TAG, "Scan complete"); + if (!is_connected) { + start_scan(); /* Restart scanning */ + } + } + break; + } + + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + ESP_LOGI(TAG, "Scan stopped"); + break; + + case ESP_GAP_BLE_SEC_REQ_EVT: + ESP_LOGI(TAG, "Security request"); + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); + break; + + case ESP_GAP_BLE_AUTH_CMPL_EVT: + if (param->ble_security.auth_cmpl.success) { + ESP_LOGI(TAG, "Authentication success"); + } else { + ESP_LOGW(TAG, "Authentication failed: 0x%x", param->ble_security.auth_cmpl.fail_reason); + } + break; + + default: + break; + } +} + +/** + * @brief GATTC event handler + */ +static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) +{ + switch (event) { + case ESP_GATTC_REG_EVT: + ESP_LOGI(TAG, "GATT client registered, status %d, if %d", param->reg.status, gattc_if); + gattc_if_stored = gattc_if; + esp_ble_gap_set_scan_params(&ble_scan_params); + break; + + case ESP_GATTC_CONNECT_EVT: + ESP_LOGI(TAG, "Connected, conn_id %d", param->connect.conn_id); + conn_id_stored = param->connect.conn_id; + is_connected = true; + + /* Request MTU exchange */ + esp_ble_gattc_send_mtu_req(gattc_if, param->connect.conn_id); + break; + + case ESP_GATTC_OPEN_EVT: + if (param->open.status != ESP_GATT_OK) { + ESP_LOGE(TAG, "Open failed: %d", param->open.status); + is_connected = false; + start_scan(); + } + break; + + case ESP_GATTC_CFG_MTU_EVT: + ESP_LOGI(TAG, "MTU configured: %d", param->cfg_mtu.mtu); + break; + + case ESP_GATTC_DIS_SRVC_CMPL_EVT: + ESP_LOGI(TAG, "Service discovery complete"); + /* Search for GAP service that contains Key Material characteristic */ + ESP_LOGI(TAG, "Searching for GAP service UUID 0x%04X", GAP_SERVICE_UUID); + esp_bt_uuid_t gap_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = GAP_SERVICE_UUID}, + }; + esp_ble_gattc_search_service(gattc_if, param->dis_srvc_cmpl.conn_id, &gap_uuid); + break; + + case ESP_GATTC_SEARCH_RES_EVT: + ESP_LOGI(TAG, "Service found, UUID 0x%04X, start_handle %d, end_handle %d", + param->search_res.srvc_id.uuid.uuid.uuid16, + param->search_res.start_handle, param->search_res.end_handle); + if (param->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 && + param->search_res.srvc_id.uuid.uuid.uuid16 == GAP_SERVICE_UUID) { + get_server = true; + service_start_handle = param->search_res.start_handle; + service_end_handle = param->search_res.end_handle; + } + break; + + case ESP_GATTC_SEARCH_CMPL_EVT: + ESP_LOGI(TAG, "Service search complete"); + if (get_server) { + /* Get characteristics */ + uint16_t count = 0; + esp_ble_gattc_get_attr_count(gattc_if, conn_id_stored, + ESP_GATT_DB_CHARACTERISTIC, + service_start_handle, + service_end_handle, + INVALID_HANDLE, &count); + + if (count > 0) { + esp_gattc_char_elem_t *char_elem = malloc(sizeof(esp_gattc_char_elem_t) * count); + if (char_elem) { + esp_bt_uuid_t km_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = KEY_MATERIAL_CHAR_UUID}, + }; + esp_ble_gattc_get_char_by_uuid(gattc_if, conn_id_stored, + service_start_handle, + service_end_handle, + km_uuid, char_elem, &count); + + if (count > 0) { + key_material_char_handle = char_elem[0].char_handle; + ESP_LOGI(TAG, "Key Material characteristic found, handle %d", key_material_char_handle); + + /* Read characteristic with encryption requirement + * GATT layer will automatically trigger encryption if needed */ + ESP_LOGI(TAG, "Reading key material (will trigger encryption if needed)..."); + esp_ble_gattc_read_char(gattc_if, conn_id_stored, + key_material_char_handle, ESP_GATT_AUTH_REQ_NO_MITM); + } + free(char_elem); + } + } + } + break; + + case ESP_GATTC_READ_CHAR_EVT: + if (param->read.status == ESP_GATT_OK) { + ESP_LOGI(TAG, "Read characteristic success, handle %d, len %d", + param->read.handle, param->read.value_len); + + if (param->read.handle == key_material_char_handle && + param->read.value_len == sizeof(ble_ead_key_material_t)) { + /* Store key material */ + int peer_idx = find_peer(current_peer_addr); + if (peer_idx >= 0) { + memcpy(&peers[peer_idx].key_material, param->read.value, + sizeof(ble_ead_key_material_t)); + peers[peer_idx].key_material_exist = true; + + ESP_LOGI(TAG, "Key material received:"); + ESP_LOG_BUFFER_HEX(TAG, &peers[peer_idx].key_material, + sizeof(ble_ead_key_material_t)); + } + + /* Disconnect and resume scanning */ + esp_ble_gattc_close(gattc_if, conn_id_stored); + } + } else { + ESP_LOGE(TAG, "Read failed: %d", param->read.status); + } + break; + + case ESP_GATTC_DISCONNECT_EVT: + ESP_LOGI(TAG, "Disconnected, reason 0x%02x", param->disconnect.reason); + is_connected = false; + get_server = false; + key_material_char_handle = INVALID_HANDLE; + start_scan(); + break; + + default: + break; + } +} + + +void app_main(void) +{ + esp_err_t ret; + + /* Initialize NVS */ + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + /* Release memory for Classic BT */ + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); + + /* Initialize BT controller */ + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(TAG, "initialize controller failed: %s", esp_err_to_name(ret)); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(TAG, "enable controller failed: %s", esp_err_to_name(ret)); + return; + } + + /* Initialize Bluedroid */ + esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT(); + ret = esp_bluedroid_init_with_cfg(&cfg); + if (ret) { + ESP_LOGE(TAG, "init bluetooth failed: %s", esp_err_to_name(ret)); + return; + } + + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(TAG, "enable bluetooth failed: %s", esp_err_to_name(ret)); + return; + } + + /* Register callbacks */ + ret = esp_ble_gap_register_callback(gap_event_handler); + if (ret) { + ESP_LOGE(TAG, "gap register error: %x", ret); + return; + } + + ret = esp_ble_gattc_register_callback(gattc_event_handler); + if (ret) { + ESP_LOGE(TAG, "gattc register error: %x", ret); + return; + } + + ret = esp_ble_gattc_app_register(PROFILE_APP_ID); + if (ret) { + ESP_LOGE(TAG, "gattc app register error: %x", ret); + return; + } + + /* Set MTU */ + esp_ble_gatt_set_local_mtu(500); + + /* Configure security parameters + * Using SC (Secure Connections) with bonding, no MITM (since IO_CAP is NONE) + */ + esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_BOND; /* SC + Bond, no MITM */ + esp_ble_io_cap_t io_cap = ESP_IO_CAP_NONE; + uint8_t key_size = 16; + uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; + uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; + + esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(auth_req)); + esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &io_cap, sizeof(io_cap)); + esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(key_size)); + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(init_key)); + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(rsp_key)); + + ESP_LOGI(TAG, "Encrypted Advertising Data Central started"); +} diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/enc_adv_data_cent_no_connect.c b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/enc_adv_data_cent_no_connect.c new file mode 100644 index 0000000000..62416b1ecc --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/enc_adv_data_cent_no_connect.c @@ -0,0 +1,239 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +/** + * @brief BLE Encrypted Advertising Data Central Example - No Connection Version + * + * This simplified example demonstrates decrypting advertising data WITHOUT connecting. + * The key material is pre-configured (same as peripheral). + * + * Use case: When the key is pre-shared or provisioned out-of-band. + */ + +#include +#include +#include +#include +#include "nvs.h" +#include "nvs_flash.h" +#include "esp_bt.h" +#include "esp_gap_ble_api.h" +#include "esp_bt_main.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "ble_ead.h" + +#define TAG "ENC_ADV_CENT_SIMPLE" + +/* Custom service UUID to identify target device */ +#define CUSTOM_SERVICE_UUID 0x2C01 + +/* + * Pre-shared Key Material - MUST match the Peripheral! + * In real applications, this would be provisioned securely. + */ +static const ble_ead_key_material_t pre_shared_key = { + .session_key = { + 0x19, 0x6a, 0x0a, 0xd1, 0x2a, 0x61, 0x20, 0x1e, + 0x13, 0x6e, 0x2e, 0xd1, 0x12, 0xda, 0xa9, 0x57 + }, + .iv = {0x9E, 0x7a, 0x00, 0xef, 0xb1, 0x7a, 0xe7, 0x46}, +}; + +/* Scan parameters */ +static esp_ble_scan_params_t ble_scan_params = { + .scan_type = BLE_SCAN_TYPE_PASSIVE, /* Passive scan is enough */ + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_interval = 0x50, + .scan_window = 0x30, + .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE, +}; + +/** + * @brief Check if device advertises our target service UUID + */ +static bool is_target_device(const uint8_t *adv_data, uint8_t adv_len) +{ + uint8_t offset = 0; + while (offset < adv_len) { + uint8_t len = adv_data[offset]; + if (len == 0 || offset + len >= adv_len) { + break; + } + + uint8_t type = adv_data[offset + 1]; + if (type == ESP_BLE_AD_TYPE_16SRV_CMPL || type == ESP_BLE_AD_TYPE_16SRV_PART) { + for (int i = 0; i < len - 1; i += 2) { + uint16_t uuid = adv_data[offset + 2 + i] | (adv_data[offset + 3 + i] << 8); + if (uuid == CUSTOM_SERVICE_UUID) { + return true; + } + } + } + offset += len + 1; + } + return false; +} + +/** + * @brief Decrypt encrypted advertising data using pre-shared key + * + * No connection required! + */ +static void decrypt_adv_data_no_connect(const uint8_t *adv_data, uint8_t adv_len, + const esp_bd_addr_t addr) +{ + uint8_t offset = 0; + + ESP_LOGI(TAG, "Processing advertising data from "ESP_BD_ADDR_STR"", + ESP_BD_ADDR_HEX(addr)); + + while (offset < adv_len) { + uint8_t len = adv_data[offset]; + if (len == 0 || offset + len >= adv_len) { + break; + } + + uint8_t type = adv_data[offset + 1]; + + /* Look for Encrypted Advertising Data (AD Type 0x31) */ + if (type == ESP_BLE_AD_TYPE_ENC_ADV_DATA) { + const uint8_t *enc_data = &adv_data[offset + 2]; + uint8_t enc_data_len = len - 1; /* Exclude type byte */ + + ESP_LOGI(TAG, "Found encrypted advertising data (%d bytes)", enc_data_len); + ESP_LOG_BUFFER_HEX(TAG, enc_data, enc_data_len); + + if (enc_data_len < BLE_EAD_RANDOMIZER_SIZE + BLE_EAD_MIC_SIZE) { + ESP_LOGW(TAG, "Encrypted data too short"); + break; + } + + /* Decrypt using pre-shared key */ + uint8_t dec_data[32]; + size_t dec_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(enc_data_len); + + int rc = ble_ead_decrypt( + pre_shared_key.session_key, + pre_shared_key.iv, + enc_data, enc_data_len, + dec_data); + + if (rc == 0) { + ESP_LOGI(TAG, "βœ… Decryption successful (no connection needed!)"); + ESP_LOGI(TAG, "Decrypted data (%d bytes):", dec_len); + ESP_LOG_BUFFER_HEX(TAG, dec_data, dec_len); + + /* Parse the decrypted advertising structure */ + if (dec_len >= 2) { + uint8_t inner_len = dec_data[0]; + uint8_t inner_type = dec_data[1]; + + if (inner_type == ESP_BLE_AD_TYPE_NAME_CMPL || + inner_type == ESP_BLE_AD_TYPE_NAME_SHORT) { + char name[32] = {0}; + size_t name_len = inner_len - 1; + if (name_len < sizeof(name) && name_len <= dec_len - 2) { + memcpy(name, &dec_data[2], name_len); + ESP_LOGI(TAG, "πŸ“› Decrypted device name: \"%s\"", name); + } + } + } + } else { + ESP_LOGE(TAG, "❌ Decryption failed (rc=%d) - wrong key?", rc); + } + return; /* Found and processed encrypted data */ + } + offset += len + 1; + } + + ESP_LOGW(TAG, "No encrypted advertising data found in this packet"); +} + +/** + * @brief GAP event handler + */ +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + switch (event) { + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: + ESP_LOGI(TAG, "Scan parameters set, starting scan..."); + esp_ble_gap_start_scanning(0); /* Scan indefinitely */ + break; + + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + if (param->scan_start_cmpl.status == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(TAG, "πŸ” Scanning started (no connection mode)"); + ESP_LOGI(TAG, "Looking for devices with UUID 0x%04X...", CUSTOM_SERVICE_UUID); + } else { + ESP_LOGE(TAG, "Scan start failed: %d", param->scan_start_cmpl.status); + } + break; + + case ESP_GAP_BLE_SCAN_RESULT_EVT: { + esp_ble_gap_cb_param_t *scan_result = param; + + if (scan_result->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { + uint8_t *adv_data = scan_result->scan_rst.ble_adv; + uint8_t adv_len = scan_result->scan_rst.adv_data_len; + + /* Check if this is our target device */ + if (is_target_device(adv_data, adv_len)) { + /* Decrypt without connecting! */ + decrypt_adv_data_no_connect(adv_data, adv_len, scan_result->scan_rst.bda); + } + } + break; + } + + default: + break; + } +} + +void app_main(void) +{ + esp_err_t ret; + + ESP_LOGI(TAG, "========================================"); + ESP_LOGI(TAG, " EAD Central - No Connection Mode"); + ESP_LOGI(TAG, "========================================"); + + /* Initialize NVS */ + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg)); + ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE)); + + esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_bluedroid_init_with_cfg(&cfg)); + ESP_ERROR_CHECK(esp_bluedroid_enable()); + + ESP_ERROR_CHECK(esp_ble_gap_register_callback(gap_event_handler)); + + /* Display pre-shared key */ + ESP_LOGI(TAG, "Using pre-shared key material:"); + ESP_LOGI(TAG, " Session Key:"); + ESP_LOG_BUFFER_HEX(TAG, pre_shared_key.session_key, BLE_EAD_KEY_SIZE); + ESP_LOGI(TAG, " IV:"); + ESP_LOG_BUFFER_HEX(TAG, pre_shared_key.iv, BLE_EAD_IV_SIZE); + + /* Start scanning */ + ESP_ERROR_CHECK(esp_ble_gap_set_scan_params(&ble_scan_params)); + + ESP_LOGI(TAG, ""); + ESP_LOGI(TAG, "⚑ This example decrypts WITHOUT connecting!"); + ESP_LOGI(TAG, " Key must be pre-shared with peripheral."); +} diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/sdkconfig.defaults new file mode 100644 index 0000000000..ae0ee30898 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/sdkconfig.defaults @@ -0,0 +1,13 @@ +# Enable BLE +CONFIG_BT_ENABLED=y +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_BLE_ENABLED=y +CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y +# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set + +# Enable SMP for security +CONFIG_BT_BLE_SMP_ENABLE=y + +# Select crypto library for EAD (Encrypted Advertising Data) +# Options: CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT or CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS +CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT=y diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/CMakeLists.txt b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/CMakeLists.txt new file mode 100644 index 0000000000..b78df5cd06 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/CMakeLists.txt @@ -0,0 +1,8 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(enc_adv_data_prph) diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/README.md b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/README.md new file mode 100644 index 0000000000..95e86da46b --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/README.md @@ -0,0 +1,172 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | + +# BLE Encrypted Advertising Data Peripheral Example (Bluedroid) + +This example demonstrates how to use BLE Encrypted Advertising Data (EAD) feature with Bluedroid stack. + +## Overview + +The Encrypted Advertising Data feature (introduced in Bluetooth Core Specification 5.4) allows devices to encrypt portions of their advertising data using AES-CCM. This enables: + +- Privacy protection for sensitive advertising data +- Selective disclosure of advertising data to authorized devices +- Enhanced security for BLE advertising + +## System Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PERIPHERAL (This Example) β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Original Data │───▢│ AES-CCM Encrypt │───▢│ Encrypted Adv Data β”‚ β”‚ +β”‚ β”‚ "prph" (name) β”‚ β”‚ (Session Key+IV)β”‚ β”‚ (Randomizer+Cipher β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +MIC) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ BLE Advertising Packet β”‚ β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ +β”‚ β”‚ Flags β”‚ Name "key" β”‚ UUID 0x2C01 β”‚ Encrypted Data (AD 0x31) β”‚ β”‚ +β”‚ β”‚ (3B) β”‚ (5B) β”‚ (4B) β”‚ (16B) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ GATT Server β”‚ β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ +β”‚ β”‚ GAP Service (0x1800) β”‚ β”‚ +β”‚ β”‚ └── Key Material Characteristic (0x2B88) β”‚ β”‚ +β”‚ β”‚ └── Value: [Session Key (16B)] [IV (8B)] β”‚ β”‚ +β”‚ β”‚ └── Permission: Read (Encrypted Link Required) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Encryption Flow + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Encryption Process β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ 1. Generate Random Randomizer (5 bytes) β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ XX XX XX XX [D|XX] β”‚ D = Direction Bit = 1 β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ 2. Build Nonce (13 bytes) β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Randomizer (5B) β”‚ IV (8B) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ 3. AES-CCM Encryption β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Plaintext β”‚ + Session Key + Nonce + AAD (0xEA) β”‚ +β”‚ β”‚ [05 09 p r p h]β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ 4. Output: Encrypted Payload β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Randomizer (5B) β”‚ Ciphertext (6B) β”‚ MIC (4B) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Advertising Data Format + +``` +Complete Advertising Packet (29 bytes): + +Offset Length Type Data Description +────── ────── ──── ──── ─────────── +0 2 0x01 0x06 Flags: LE General Discoverable +3 4 0x09 'k' 'e' 'y' Complete Local Name +8 3 0x03 0x01 0x2C 16-bit Service UUID: 0x2C01 +12 16 0x31 [Encrypted Payload] Encrypted Advertising Data + +Encrypted Payload Detail: +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Randomizer β”‚ Ciphertext β”‚ MIC β”‚ +β”‚ (5 bytes) β”‚ (6 bytes) β”‚ (4 bytes) β”‚ +β”‚ Random + Dir=1 β”‚ AES-CCM output β”‚ Auth Tag β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## How to Use Example + +### Hardware Required + +* A development board with ESP32/ESP32-C2/ESP32-C3/ESP32-C5/ESP32-C6/ESP32-C61/ESP32-H2/ESP32-S3 SoC +* A USB cable for power supply and programming + +### Configure the project + +```bash +idf.py set-target +idf.py menuconfig +``` + +### Build and Flash + +```bash +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type `Ctrl-]`.) + +### Example Output + +``` +I (XXX) ENC_ADV_PRPH: Encrypted Advertising Data Peripheral started +I (XXX) ENC_ADV_PRPH: Key Material (Session Key + IV): +I (XXX) ENC_ADV_PRPH: 19 6a 0a d1 2a 61 20 1e 13 6e 2e d1 12 da a9 57 9e 7a 00 ef b1 7a e7 46 +I (XXX) ENC_ADV_PRPH: Data before encryption: +I (XXX) ENC_ADV_PRPH: 05 09 70 72 70 68 +I (XXX) ENC_ADV_PRPH: Encryption of adv data done successfully +I (XXX) ENC_ADV_PRPH: Raw advertising data set complete +I (XXX) ENC_ADV_PRPH: Advertising start successfully +``` + +## Testing with Central + +Use the `enc_adv_data_cent` example as the central device: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PERIPHERAL β”‚ β”‚ CENTRAL β”‚ +β”‚ (This Example) β”‚ β”‚ (enc_adv_data_ β”‚ +β”‚ β”‚ β”‚ cent) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β”‚ 1. Broadcast Encrypted Adv β”‚ + β”‚ ─────────────────────────────────────▢│ + β”‚ β”‚ + β”‚ 2. Connect (first time only) β”‚ + β”‚ ◀═══════════════════════════════════ β”‚ + β”‚ β”‚ + β”‚ 3. Read Key Material (0x2B88) β”‚ + β”‚ ═══════════════════════════════════▢ β”‚ + β”‚ β”‚ + β”‚ 4. Return Session Key + IV β”‚ + β”‚ ◀═══════════════════════════════════ β”‚ + β”‚ β”‚ + β”‚ 5. Disconnect β”‚ + β”‚ ◀═══════════════════════════════════ β”‚ + β”‚ β”‚ + β”‚ 6. Future: Decrypt without connect β”‚ + β”‚ ─────────────────────────────────────▢│ + β”‚ β”‚ + β–Ό β–Ό +``` + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/CMakeLists.txt b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/CMakeLists.txt new file mode 100644 index 0000000000..a9fa58c00e --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "enc_adv_data_prph.c" "ble_ead.c" + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/Kconfig.projbuild b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/Kconfig.projbuild new file mode 100644 index 0000000000..305d0bfab6 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/Kconfig.projbuild @@ -0,0 +1,14 @@ +menu "Example Configuration" + + config EXAMPLE_ENABLE_KEY_MATERIAL + bool "Enable Key Material characteristic in GAP Service" + default y + select BT_GATTS_KEY_MATERIAL_CHAR + help + Enable the Key Material characteristic in the built-in GAP service + (UUID 0x1800) using the Bluedroid stack's support for this feature. + + This is the standard-compliant approach as defined in Bluetooth + Core Specification Version 5.4. + +endmenu diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/ble_ead.c b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/ble_ead.c new file mode 100644 index 0000000000..cc0abeca1d --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/ble_ead.c @@ -0,0 +1,387 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "ble_ead.h" +#include "esp_random.h" +#include "esp_log.h" +#include "sdkconfig.h" + +#define TAG "BLE_EAD" + +/* Select crypto library based on configuration */ +#if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT) +#include "tinycrypt/aes.h" +#include "tinycrypt/ccm_mode.h" +#include "tinycrypt/constants.h" +#elif defined(CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS) +#include "mbedtls/ccm.h" +#else +#error "Please select either CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT or CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS" +#endif + +/* Additional Authenticated Data for EAD - EA (Encrypted Advertising) */ +static const uint8_t ble_ead_aad[BLE_EAD_AAD_SIZE] = { 0xEA }; + +/** + * @brief Generate randomizer with direction bit set + * + * Per Bluetooth Core Spec Supplement v11, Part A 1.23.3: + * The MSB of the Randomizer shall be set to indicate direction + */ +static int ble_ead_generate_randomizer(uint8_t randomizer[BLE_EAD_RANDOMIZER_SIZE]) +{ + /* Generate random bytes */ + esp_fill_random(randomizer, BLE_EAD_RANDOMIZER_SIZE); + + /* Set direction bit (MSB of last byte) - required by spec */ + randomizer[BLE_EAD_RANDOMIZER_SIZE - 1] |= (1 << BLE_EAD_RANDOMIZER_DIRECTION_BIT); + + return 0; +} + +/** + * @brief Generate nonce from IV and randomizer + * + * Nonce = Randomizer (5 bytes) || IV (8 bytes) = 13 bytes + */ +static int ble_ead_generate_nonce(const uint8_t iv[BLE_EAD_IV_SIZE], + const uint8_t randomizer[BLE_EAD_RANDOMIZER_SIZE], + uint8_t nonce[BLE_EAD_NONCE_SIZE]) +{ + if (iv == NULL || nonce == NULL) { + return -1; + } + + /* Randomizer in first 5 bytes */ + if (randomizer != NULL) { + memcpy(nonce, randomizer, BLE_EAD_RANDOMIZER_SIZE); + } else { + /* Generate new randomizer with direction bit */ + ble_ead_generate_randomizer(nonce); + } + + /* IV in last 8 bytes */ + memcpy(nonce + BLE_EAD_RANDOMIZER_SIZE, iv, BLE_EAD_IV_SIZE); + + return 0; +} + +/** + * @brief AES-CCM encryption using selected crypto library + */ +static int ble_aes_ccm_encrypt(const uint8_t *key, const uint8_t *nonce, + const uint8_t *plaintext, size_t plaintext_len, + const uint8_t *aad, size_t aad_len, + uint8_t *ciphertext, size_t tag_len) +{ +#if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT) + struct tc_aes_key_sched_struct sched; + struct tc_ccm_mode_struct ccm_state; + int ret; + + /* Validate inputs */ + if (key == NULL || nonce == NULL || ciphertext == NULL) { + ESP_LOGE(TAG, "Invalid input parameters"); + return -1; + } + + /* Set AES encryption key */ + ret = tc_aes128_set_encrypt_key(&sched, key); + if (ret != TC_CRYPTO_SUCCESS) { + ESP_LOGE(TAG, "tc_aes128_set_encrypt_key failed"); + memset(&sched, 0, sizeof(sched)); + return -1; + } + + /* Configure CCM mode */ + ccm_state.sched = &sched; + ccm_state.nonce = (uint8_t *)nonce; + ccm_state.mlen = tag_len; + + ret = tc_ccm_config(&ccm_state, &sched, (uint8_t *)nonce, BLE_EAD_NONCE_SIZE, tag_len); + if (ret != TC_CRYPTO_SUCCESS) { + ESP_LOGE(TAG, "tc_ccm_config failed"); + memset(&sched, 0, sizeof(sched)); + memset(&ccm_state, 0, sizeof(ccm_state)); + return -1; + } + + /* Encrypt and generate tag */ + /* TinyCrypt outputs: ciphertext || tag */ + ret = tc_ccm_generation_encryption(ciphertext, plaintext_len + tag_len, + aad, aad_len, + plaintext, plaintext_len, + &ccm_state); + if (ret != TC_CRYPTO_SUCCESS) { + ESP_LOGE(TAG, "tc_ccm_generation_encryption failed"); + memset(&sched, 0, sizeof(sched)); + memset(&ccm_state, 0, sizeof(ccm_state)); + return -1; + } + + /* Clear sensitive data from key schedule */ + memset(&sched, 0, sizeof(sched)); + memset(&ccm_state, 0, sizeof(ccm_state)); + return 0; + +#elif defined(CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS) + mbedtls_ccm_context ctx = {0}; + int ret; + + mbedtls_ccm_init(&ctx); + + /* Set encryption key */ + ret = mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key, BLE_EAD_KEY_SIZE * 8); + if (ret != 0) { + ESP_LOGE(TAG, "mbedtls_ccm_setkey failed: %d", ret); + mbedtls_ccm_free(&ctx); + return -1; + } + + /* Encrypt and authenticate */ + /* mbedtls_ccm_encrypt_and_tag outputs: ciphertext || tag */ + ret = mbedtls_ccm_encrypt_and_tag(&ctx, plaintext_len, + nonce, BLE_EAD_NONCE_SIZE, + aad, aad_len, + plaintext, ciphertext, + ciphertext + plaintext_len, tag_len); + if (ret != 0) { + ESP_LOGE(TAG, "mbedtls_ccm_encrypt_and_tag failed: %d", ret); + mbedtls_ccm_free(&ctx); + return -1; + } + + mbedtls_ccm_free(&ctx); + return 0; +#else + #error "No crypto library selected" +#endif +} + +/** + * @brief AES-CCM decryption with authentication using selected crypto library + */ +static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce, + const uint8_t *ciphertext, size_t ciphertext_len, + const uint8_t *aad, size_t aad_len, + uint8_t *plaintext, size_t tag_len) +{ +#if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT) + struct tc_aes_key_sched_struct sched; + struct tc_ccm_mode_struct ccm_state; + int ret; + /* ciphertext_len here includes both ciphertext and tag */ + size_t plaintext_len; + + /* Validate inputs */ + if (key == NULL || nonce == NULL || ciphertext == NULL || plaintext == NULL) { + ESP_LOGE(TAG, "Invalid input parameters"); + return -1; + } + + /* Check for integer underflow */ + if (ciphertext_len < tag_len) { + ESP_LOGE(TAG, "ciphertext_len (%zu) < tag_len (%zu)", ciphertext_len, tag_len); + return -1; + } + + plaintext_len = ciphertext_len - tag_len; + + /* Set AES encryption key */ + ret = tc_aes128_set_encrypt_key(&sched, key); + if (ret != TC_CRYPTO_SUCCESS) { + ESP_LOGE(TAG, "tc_aes128_set_encrypt_key failed"); + memset(&sched, 0, sizeof(sched)); + return -1; + } + + /* Configure CCM mode */ + ccm_state.sched = &sched; + ccm_state.nonce = (uint8_t *)nonce; + ccm_state.mlen = tag_len; + + ret = tc_ccm_config(&ccm_state, &sched, (uint8_t *)nonce, BLE_EAD_NONCE_SIZE, tag_len); + if (ret != TC_CRYPTO_SUCCESS) { + ESP_LOGE(TAG, "tc_ccm_config failed"); + memset(&sched, 0, sizeof(sched)); + memset(&ccm_state, 0, sizeof(ccm_state)); + return -1; + } + + /* Decrypt and verify tag */ + /* TinyCrypt expects: ciphertext || tag */ + ret = tc_ccm_decryption_verification(plaintext, plaintext_len, + aad, aad_len, + (uint8_t *)ciphertext, ciphertext_len, + &ccm_state); + if (ret != TC_CRYPTO_SUCCESS) { + ESP_LOGE(TAG, "tc_ccm_decryption_verification failed"); + memset(&sched, 0, sizeof(sched)); + memset(&ccm_state, 0, sizeof(ccm_state)); + return -1; + } + + /* Clear sensitive data from key schedule */ + memset(&sched, 0, sizeof(sched)); + memset(&ccm_state, 0, sizeof(ccm_state)); + return 0; + +#elif defined(CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS) + mbedtls_ccm_context ctx = {0}; + int ret; + /* ciphertext_len here includes both ciphertext and tag */ + size_t plaintext_len; + + /* Validate inputs */ + if (key == NULL || nonce == NULL || ciphertext == NULL || plaintext == NULL) { + ESP_LOGE(TAG, "Invalid input parameters"); + return -1; + } + + /* Check for integer underflow */ + if (ciphertext_len < tag_len) { + ESP_LOGE(TAG, "ciphertext_len (%zu) < tag_len (%zu)", ciphertext_len, tag_len); + return -1; + } + + plaintext_len = ciphertext_len - tag_len; + + mbedtls_ccm_init(&ctx); + + /* Set decryption key */ + ret = mbedtls_ccm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key, BLE_EAD_KEY_SIZE * 8); + if (ret != 0) { + ESP_LOGE(TAG, "mbedtls_ccm_setkey failed: %d", ret); + mbedtls_ccm_free(&ctx); + return -1; + } + + /* Decrypt and verify */ + /* mbedtls_ccm_auth_decrypt expects: ciphertext || tag */ + ret = mbedtls_ccm_auth_decrypt(&ctx, plaintext_len, + nonce, BLE_EAD_NONCE_SIZE, + aad, aad_len, + ciphertext, plaintext, + ciphertext + plaintext_len, tag_len); + if (ret != 0) { + ESP_LOGE(TAG, "mbedtls_ccm_auth_decrypt failed: %d", ret); + mbedtls_ccm_free(&ctx); + return -1; + } + + mbedtls_ccm_free(&ctx); + return 0; +#else + #error "No crypto library selected" +#endif +} + +int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], + const uint8_t iv[BLE_EAD_IV_SIZE], + const uint8_t *payload, size_t payload_size, + uint8_t *encrypted_payload) +{ + int ret; + uint8_t nonce[BLE_EAD_NONCE_SIZE]; + + if (session_key == NULL) { + ESP_LOGE(TAG, "session_key is NULL"); + return -1; + } + + if (iv == NULL) { + ESP_LOGE(TAG, "iv is NULL"); + return -1; + } + + if (payload == NULL && payload_size > 0) { + ESP_LOGE(TAG, "payload is NULL but payload_size > 0"); + return -1; + } + + if (encrypted_payload == NULL) { + ESP_LOGE(TAG, "encrypted_payload is NULL"); + return -1; + } + + /* Generate nonce with random randomizer */ + ret = ble_ead_generate_nonce(iv, NULL, nonce); + if (ret != 0) { + return ret; + } + + /* Copy randomizer to the start of encrypted payload */ + memcpy(encrypted_payload, nonce, BLE_EAD_RANDOMIZER_SIZE); + + /* Encrypt: output = ciphertext + MIC */ + ret = ble_aes_ccm_encrypt(session_key, nonce, + payload, payload_size, + ble_ead_aad, BLE_EAD_AAD_SIZE, + &encrypted_payload[BLE_EAD_RANDOMIZER_SIZE], + BLE_EAD_MIC_SIZE); + + return ret; +} + +int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], + const uint8_t iv[BLE_EAD_IV_SIZE], + const uint8_t *encrypted_payload, size_t encrypted_payload_size, + uint8_t *payload) +{ + int ret; + uint8_t nonce[BLE_EAD_NONCE_SIZE]; + const uint8_t *randomizer; + const uint8_t *ciphertext; + size_t ciphertext_len; + + if (session_key == NULL) { + ESP_LOGE(TAG, "session_key is NULL"); + return -1; + } + + if (iv == NULL) { + ESP_LOGE(TAG, "iv is NULL"); + return -1; + } + + if (encrypted_payload == NULL) { + ESP_LOGE(TAG, "encrypted_payload is NULL"); + return -1; + } + + if (payload == NULL) { + ESP_LOGE(TAG, "payload is NULL"); + return -1; + } + + if (encrypted_payload_size < BLE_EAD_RANDOMIZER_SIZE + BLE_EAD_MIC_SIZE) { + ESP_LOGE(TAG, "encrypted_payload_size too small"); + return -1; + } + + /* Extract randomizer from the start of encrypted payload */ + randomizer = encrypted_payload; + + /* Ciphertext + MIC follows the randomizer */ + ciphertext = &encrypted_payload[BLE_EAD_RANDOMIZER_SIZE]; + /* ciphertext_len includes both ciphertext and MIC (tag) for mbedTLS API */ + ciphertext_len = encrypted_payload_size - BLE_EAD_RANDOMIZER_SIZE; + + /* Generate nonce from randomizer and IV */ + ret = ble_ead_generate_nonce(iv, randomizer, nonce); + if (ret != 0) { + return ret; + } + + /* Decrypt and verify */ + ret = ble_aes_ccm_decrypt(session_key, nonce, + ciphertext, ciphertext_len, + ble_ead_aad, BLE_EAD_AAD_SIZE, + payload, BLE_EAD_MIC_SIZE); + + return ret; +} diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/ble_ead.h b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/ble_ead.h new file mode 100644 index 0000000000..a9bf8954eb --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/ble_ead.h @@ -0,0 +1,95 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef BLE_EAD_H +#define BLE_EAD_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief BLE Encrypted Advertising Data (EAD) definitions + * Based on Bluetooth Core Specification Version 5.4 + */ + +#define BLE_EAD_KEY_SIZE 16 /* 128-bit session key */ +#define BLE_EAD_IV_SIZE 8 /* 64-bit Initialization Vector */ +#define BLE_EAD_RANDOMIZER_SIZE 5 /* 40-bit Randomizer */ +#define BLE_EAD_MIC_SIZE 4 /* 32-bit Message Integrity Check */ +#define BLE_EAD_NONCE_SIZE 13 /* 104-bit Nonce (Randomizer + IV) */ +#define BLE_EAD_AAD_SIZE 1 /* Additional Authenticated Data size */ + +/* Direction bit position in Randomizer (MSB of last byte) + * Per Bluetooth Core Spec Supplement v11, Part A 1.23.3 + */ +#define BLE_EAD_RANDOMIZER_DIRECTION_BIT 7 + +/* AD Type for Encrypted Advertising Data (0x31) */ +#define ESP_BLE_AD_TYPE_ENC_ADV_DATA 0x31 + +/** + * @brief Calculate encrypted payload size from plaintext size + */ +#define BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(payload_size) \ + (BLE_EAD_RANDOMIZER_SIZE + (payload_size) + BLE_EAD_MIC_SIZE) + +/** + * @brief Calculate decrypted payload size from encrypted payload size + */ +#define BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_size) \ + ((encrypted_size) - BLE_EAD_RANDOMIZER_SIZE - BLE_EAD_MIC_SIZE) + +/** + * @brief Key material structure for EAD + */ +typedef struct { + uint8_t session_key[BLE_EAD_KEY_SIZE]; /* 128-bit session key */ + uint8_t iv[BLE_EAD_IV_SIZE]; /* 64-bit Initialization Vector */ +} ble_ead_key_material_t; + +/** + * @brief Encrypt advertising data using AES-CCM + * + * @param session_key 16-byte session key + * @param iv 8-byte Initialization Vector + * @param payload Plaintext advertising data to encrypt + * @param payload_size Size of plaintext data + * @param encrypted_payload Output buffer for encrypted data + * Size must be at least BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(payload_size) + * + * @return 0 on success, negative error code on failure + */ +int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], + const uint8_t iv[BLE_EAD_IV_SIZE], + const uint8_t *payload, size_t payload_size, + uint8_t *encrypted_payload); + +/** + * @brief Decrypt advertising data using AES-CCM + * + * @param session_key 16-byte session key + * @param iv 8-byte Initialization Vector + * @param encrypted_payload Encrypted advertising data (includes randomizer and MIC) + * @param encrypted_payload_size Size of encrypted data + * @param payload Output buffer for decrypted data + * Size must be at least BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size) + * + * @return 0 on success, negative error code on failure + */ +int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], + const uint8_t iv[BLE_EAD_IV_SIZE], + const uint8_t *encrypted_payload, size_t encrypted_payload_size, + uint8_t *payload); + +#ifdef __cplusplus +} +#endif + +#endif /* BLE_EAD_H */ diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/enc_adv_data_prph.c b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/enc_adv_data_prph.c new file mode 100644 index 0000000000..7520bbc9d8 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/enc_adv_data_prph.c @@ -0,0 +1,353 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +/** + * @brief BLE Encrypted Advertising Data Peripheral Example + * + * This example demonstrates how to: + * 1. Encrypt advertising data using AES-CCM + * 2. Broadcast encrypted advertising data + * 3. Provide Key Material characteristic for central devices to read + * + * Based on Bluetooth Core Specification Version 5.4 - Encrypted Advertising Data + */ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_bt.h" +#include "esp_gap_ble_api.h" +#include "esp_gatts_api.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" +#include "esp_gatt_common_api.h" +#include "ble_ead.h" + +#define TAG "ENC_ADV_PRPH" + +/* Device name */ +#define DEVICE_NAME "ENC_ADV_PRPH" +#define GAP_SERVICE_UUID 0x1800 /* GAP Service UUID */ + +/* Profile configuration */ +#define PROFILE_NUM 1 +#define PROFILE_APP_ID 0 + +/* Unencrypted advertising pattern to be encrypted */ +static uint8_t unencrypted_adv_pattern[] = { + 0x05, 0x09, 'p', 'r', 'p', 'h' /* Complete Local Name: "prph" */ +}; + +/* Session key and IV for encryption - in real application, generate securely! */ +static ble_ead_key_material_t key_material = { + .session_key = { + 0x19, 0x6a, 0x0a, 0xd1, 0x2a, 0x61, 0x20, 0x1e, + 0x13, 0x6e, 0x2e, 0xd1, 0x12, 0xda, 0xa9, 0x57 + }, + .iv = {0x9E, 0x7a, 0x00, 0xef, 0xb1, 0x7a, 0xe7, 0x46}, +}; + +/* GATT state */ +static esp_gatt_if_t gatts_if_stored = ESP_GATT_IF_NONE; +static uint16_t conn_id_stored = 0; +static bool is_connected = false; + +/* Advertising parameters */ +static esp_ble_adv_params_t adv_params = { + .adv_int_min = 0x20, + .adv_int_max = 0x40, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; + +/* Calculate encrypted payload size */ +#define ENCRYPTED_ADV_DATA_LEN BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(sizeof(unencrypted_adv_pattern)) + +/** + * @brief Encrypt advertising data and set raw advertising data + */ +static void set_encrypted_adv_data(void) +{ + esp_err_t ret; + uint8_t encrypted_adv_data[ENCRYPTED_ADV_DATA_LEN]; + int rc; + + ESP_LOGI(TAG, "Data before encryption:"); + ESP_LOG_BUFFER_HEX(TAG, unencrypted_adv_pattern, sizeof(unencrypted_adv_pattern)); + + /* Encrypt the advertising data */ + rc = ble_ead_encrypt(key_material.session_key, key_material.iv, + unencrypted_adv_pattern, sizeof(unencrypted_adv_pattern), + encrypted_adv_data); + if (rc != 0) { + ESP_LOGE(TAG, "Encryption of adv data failed: %d", rc); + return; + } + + ESP_LOGI(TAG, "Encryption of adv data done successfully"); + ESP_LOGI(TAG, "Data after encryption:"); + ESP_LOG_BUFFER_HEX(TAG, encrypted_adv_data, sizeof(encrypted_adv_data)); + + /* + * Build raw advertising data: + * - Flags (3 bytes) + * - Complete Local Name (device name) + * - Complete 16-bit Service UUIDs (for central to recognize) + * - Encrypted Advertising Data + */ + uint8_t raw_adv_data[31]; + uint8_t pos = 0; + + /* Flags */ + raw_adv_data[pos++] = 0x02; /* Length */ + raw_adv_data[pos++] = ESP_BLE_AD_TYPE_FLAG; + raw_adv_data[pos++] = ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT; + + /* Complete Local Name - "key" (short name for recognition) */ + raw_adv_data[pos++] = 0x04; /* Length */ + raw_adv_data[pos++] = ESP_BLE_AD_TYPE_NAME_CMPL; + raw_adv_data[pos++] = 'k'; + raw_adv_data[pos++] = 'e'; + raw_adv_data[pos++] = 'y'; + + /* Complete 16-bit Service UUIDs - GAP Service (0x1800) */ + raw_adv_data[pos++] = 0x03; /* Length */ + raw_adv_data[pos++] = ESP_BLE_AD_TYPE_16SRV_CMPL; + raw_adv_data[pos++] = GAP_SERVICE_UUID & 0xFF; + raw_adv_data[pos++] = (GAP_SERVICE_UUID >> 8) & 0xFF; + + /* Encrypted Advertising Data */ + raw_adv_data[pos++] = ENCRYPTED_ADV_DATA_LEN + 1; /* Length */ + raw_adv_data[pos++] = ESP_BLE_AD_TYPE_ENC_ADV_DATA; + memcpy(&raw_adv_data[pos], encrypted_adv_data, ENCRYPTED_ADV_DATA_LEN); + pos += ENCRYPTED_ADV_DATA_LEN; + + /* Set raw advertising data */ + ret = esp_ble_gap_config_adv_data_raw(raw_adv_data, pos); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "config raw adv data failed: %s", esp_err_to_name(ret)); + } +} + +/** + * @brief Start advertising + */ +static void start_advertising(void) +{ + esp_err_t ret = esp_ble_gap_start_advertising(&adv_params); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "start advertising failed: %s", esp_err_to_name(ret)); + } +} + +/** + * @brief GAP event handler + */ +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + switch (event) { + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: + ESP_LOGI(TAG, "Raw advertising data set complete"); + start_advertising(); + break; + + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: + if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(TAG, "Advertising start failed: %d", param->adv_start_cmpl.status); + } else { + ESP_LOGI(TAG, "Advertising start successfully"); + } + break; + + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(TAG, "Advertising stop failed: %d", param->adv_stop_cmpl.status); + } else { + ESP_LOGI(TAG, "Advertising stop successfully"); + } + break; + + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: + ESP_LOGI(TAG, "Connection params update, status %d, conn_int %d, latency %d, timeout %d", + param->update_conn_params.status, + param->update_conn_params.conn_int, + param->update_conn_params.latency, + param->update_conn_params.timeout); + break; + + case ESP_GAP_BLE_SEC_REQ_EVT: + ESP_LOGI(TAG, "Security request received"); + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); + break; + + case ESP_GAP_BLE_AUTH_CMPL_EVT: + ESP_LOGI(TAG, "Authentication complete, addr_type %d, addr "ESP_BD_ADDR_STR"", + param->ble_security.auth_cmpl.addr_type, + ESP_BD_ADDR_HEX(param->ble_security.auth_cmpl.bd_addr)); + if (param->ble_security.auth_cmpl.success) { + ESP_LOGI(TAG, "Authentication success, auth_mode %d", param->ble_security.auth_cmpl.auth_mode); + } else { + ESP_LOGW(TAG, "Authentication failed, reason 0x%x", param->ble_security.auth_cmpl.fail_reason); + } + break; + + default: + break; + } +} + +/** + * @brief GATTS profile event handler + */ +static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t *param) +{ + switch (event) { + case ESP_GATTS_REG_EVT: + ESP_LOGI(TAG, "GATT server register, status %d, app_id %d, gatts_if %d", + param->reg.status, param->reg.app_id, gatts_if); + gatts_if_stored = gatts_if; + + /* Set device name */ + esp_ble_gap_set_device_name(DEVICE_NAME); + + /* Set encrypted advertising data */ + set_encrypted_adv_data(); + + /* Set Key Material in GAP service + * The Key Material characteristic is part of the built-in GAP service + */ + ESP_LOGI(TAG, "Setting Key Material in GAP service"); + esp_ble_gap_set_key_material(key_material.session_key, key_material.iv); + break; + + case ESP_GATTS_CONNECT_EVT: + ESP_LOGI(TAG, "Connected, conn_id %d, remote "ESP_BD_ADDR_STR"", + param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda)); + conn_id_stored = param->connect.conn_id; + is_connected = true; + + /* Update connection parameters */ + esp_ble_conn_update_params_t conn_params = {0}; + memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); + conn_params.latency = 0; + conn_params.max_int = 0x20; + conn_params.min_int = 0x10; + conn_params.timeout = 400; + esp_ble_gap_update_conn_params(&conn_params); + break; + + case ESP_GATTS_DISCONNECT_EVT: + ESP_LOGI(TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x", + ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason); + is_connected = false; + + /* Re-encrypt and restart advertising with new randomizer */ + set_encrypted_adv_data(); + break; + + case ESP_GATTS_MTU_EVT: + ESP_LOGI(TAG, "MTU exchange, MTU %d", param->mtu.mtu); + break; + + default: + break; + } +} + +void app_main(void) +{ + esp_err_t ret; + + /* Initialize NVS */ + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + /* Release memory for Classic BT */ + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); + + /* Initialize BT controller */ + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(TAG, "initialize controller failed: %s", esp_err_to_name(ret)); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(TAG, "enable controller failed: %s", esp_err_to_name(ret)); + return; + } + + /* Initialize Bluedroid */ + esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT(); + ret = esp_bluedroid_init_with_cfg(&cfg); + if (ret) { + ESP_LOGE(TAG, "init bluetooth failed: %s", esp_err_to_name(ret)); + return; + } + + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(TAG, "enable bluetooth failed: %s", esp_err_to_name(ret)); + return; + } + + /* Register callbacks */ + ret = esp_ble_gatts_register_callback(gatts_event_handler); + if (ret) { + ESP_LOGE(TAG, "gatts register error: %x", ret); + return; + } + + ret = esp_ble_gap_register_callback(gap_event_handler); + if (ret) { + ESP_LOGE(TAG, "gap register error: %x", ret); + return; + } + + /* Register GATT application */ + ret = esp_ble_gatts_app_register(PROFILE_APP_ID); + if (ret) { + ESP_LOGE(TAG, "gatts app register error: %x", ret); + return; + } + + /* Set MTU */ + esp_ble_gatt_set_local_mtu(500); + + /* Configure security parameters for Key Material characteristic access + * Using SC (Secure Connections) with bonding, no MITM (since IO_CAP is NONE) + */ + esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_BOND; /* SC + Bond, no MITM */ + esp_ble_io_cap_t io_cap = ESP_IO_CAP_NONE; + uint8_t key_size = 16; + uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; + uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; + + esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(auth_req)); + esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &io_cap, sizeof(io_cap)); + esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(key_size)); + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(init_key)); + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(rsp_key)); + + ESP_LOGI(TAG, "Encrypted Advertising Data Peripheral started"); + ESP_LOGI(TAG, "Key Material (Session Key + IV):"); + ESP_LOG_BUFFER_HEX(TAG, &key_material, sizeof(key_material)); +} diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/sdkconfig.defaults new file mode 100644 index 0000000000..ae0ee30898 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/sdkconfig.defaults @@ -0,0 +1,13 @@ +# Enable BLE +CONFIG_BT_ENABLED=y +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_BLE_ENABLED=y +CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y +# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set + +# Enable SMP for security +CONFIG_BT_BLE_SMP_ENABLE=y + +# Select crypto library for EAD (Encrypted Advertising Data) +# Options: CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT or CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS +CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT=y