mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
feat(ble/bluedroid): Support bluedroid encrypted advertising data
This commit is contained 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
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -3141,6 +3141,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
|
||||
|
||||
@@ -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)
|
||||
/*******************************************************************************
|
||||
**
|
||||
|
||||
@@ -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)
|
||||
/*******************************************************************************
|
||||
**
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3185,6 +3185,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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -616,6 +616,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
|
||||
|
||||
@@ -834,6 +834,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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 <chip_name>
|
||||
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.
|
||||
+8
@@ -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 ".")
|
||||
+22
@@ -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
|
||||
@@ -0,0 +1,426 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#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 "psa/crypto.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)
|
||||
psa_status_t status;
|
||||
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
|
||||
psa_key_id_t key_id = 0;
|
||||
psa_algorithm_t alg = PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, tag_len);
|
||||
size_t output_length = 0;
|
||||
|
||||
/* Validate inputs */
|
||||
if (key == NULL || nonce == NULL || ciphertext == NULL) {
|
||||
ESP_LOGE(TAG, "Invalid input parameters");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set key attributes */
|
||||
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT);
|
||||
psa_set_key_algorithm(&attributes, alg);
|
||||
psa_set_key_type(&attributes, PSA_KEY_TYPE_AES);
|
||||
psa_set_key_bits(&attributes, BLE_EAD_KEY_SIZE * 8);
|
||||
|
||||
/* Import key */
|
||||
status = psa_import_key(&attributes, key, BLE_EAD_KEY_SIZE, &key_id);
|
||||
if (status != PSA_SUCCESS) {
|
||||
ESP_LOGE(TAG, "psa_import_key failed: %d", status);
|
||||
psa_reset_key_attributes(&attributes);
|
||||
return -1;
|
||||
}
|
||||
psa_reset_key_attributes(&attributes);
|
||||
|
||||
/* Encrypt and authenticate */
|
||||
/* PSA AEAD encrypt outputs: ciphertext || tag */
|
||||
status = psa_aead_encrypt(key_id, alg,
|
||||
nonce, BLE_EAD_NONCE_SIZE,
|
||||
aad, aad_len,
|
||||
plaintext, plaintext_len,
|
||||
ciphertext, plaintext_len + tag_len,
|
||||
&output_length);
|
||||
if (status != PSA_SUCCESS) {
|
||||
ESP_LOGE(TAG, "psa_aead_encrypt failed: %d", status);
|
||||
psa_destroy_key(key_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (output_length != plaintext_len + tag_len) {
|
||||
ESP_LOGE(TAG, "psa_aead_encrypt output length mismatch: expected %zu, got %zu",
|
||||
plaintext_len + tag_len, output_length);
|
||||
psa_destroy_key(key_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
psa_destroy_key(key_id);
|
||||
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)
|
||||
psa_status_t status;
|
||||
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
|
||||
psa_key_id_t key_id = 0;
|
||||
psa_algorithm_t alg = PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, tag_len);
|
||||
size_t output_length = 0;
|
||||
/* 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 key attributes */
|
||||
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT);
|
||||
psa_set_key_algorithm(&attributes, alg);
|
||||
psa_set_key_type(&attributes, PSA_KEY_TYPE_AES);
|
||||
psa_set_key_bits(&attributes, BLE_EAD_KEY_SIZE * 8);
|
||||
|
||||
/* Import key */
|
||||
status = psa_import_key(&attributes, key, BLE_EAD_KEY_SIZE, &key_id);
|
||||
if (status != PSA_SUCCESS) {
|
||||
ESP_LOGE(TAG, "psa_import_key failed: %d", status);
|
||||
psa_reset_key_attributes(&attributes);
|
||||
return -1;
|
||||
}
|
||||
psa_reset_key_attributes(&attributes);
|
||||
|
||||
/* Decrypt and verify */
|
||||
/* PSA AEAD decrypt expects: ciphertext || tag */
|
||||
/* ciphertext_len here already includes tag length */
|
||||
status = psa_aead_decrypt(key_id, alg,
|
||||
nonce, BLE_EAD_NONCE_SIZE,
|
||||
aad, aad_len,
|
||||
ciphertext, ciphertext_len,
|
||||
plaintext, plaintext_len,
|
||||
&output_length);
|
||||
if (status != PSA_SUCCESS) {
|
||||
ESP_LOGE(TAG, "psa_aead_decrypt failed: %d", status);
|
||||
psa_destroy_key(key_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (output_length != plaintext_len) {
|
||||
ESP_LOGE(TAG, "psa_aead_decrypt output length mismatch: expected %zu, got %zu",
|
||||
plaintext_len, output_length);
|
||||
psa_destroy_key(key_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
psa_destroy_key(key_id);
|
||||
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 PSA 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;
|
||||
}
|
||||
@@ -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 <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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 */
|
||||
+519
@@ -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 <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#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");
|
||||
}
|
||||
+239
@@ -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 <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#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.");
|
||||
}
|
||||
+13
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 <chip_name>
|
||||
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.
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "enc_adv_data_prph.c" "ble_ead.c"
|
||||
INCLUDE_DIRS ".")
|
||||
+14
@@ -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
|
||||
@@ -0,0 +1,419 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#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 "psa/crypto.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)
|
||||
psa_status_t status;
|
||||
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
|
||||
psa_key_id_t key_id = 0;
|
||||
psa_algorithm_t alg = PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, tag_len);
|
||||
size_t output_length = 0;
|
||||
|
||||
/* Set key attributes */
|
||||
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT);
|
||||
psa_set_key_algorithm(&attributes, alg);
|
||||
psa_set_key_type(&attributes, PSA_KEY_TYPE_AES);
|
||||
psa_set_key_bits(&attributes, BLE_EAD_KEY_SIZE * 8);
|
||||
|
||||
/* Import key */
|
||||
status = psa_import_key(&attributes, key, BLE_EAD_KEY_SIZE, &key_id);
|
||||
if (status != PSA_SUCCESS) {
|
||||
ESP_LOGE(TAG, "psa_import_key failed: %d", status);
|
||||
psa_reset_key_attributes(&attributes);
|
||||
return -1;
|
||||
}
|
||||
psa_reset_key_attributes(&attributes);
|
||||
|
||||
/* Encrypt and authenticate */
|
||||
/* PSA AEAD encrypt outputs: ciphertext || tag */
|
||||
status = psa_aead_encrypt(key_id, alg,
|
||||
nonce, BLE_EAD_NONCE_SIZE,
|
||||
aad, aad_len,
|
||||
plaintext, plaintext_len,
|
||||
ciphertext, plaintext_len + tag_len,
|
||||
&output_length);
|
||||
if (status != PSA_SUCCESS) {
|
||||
ESP_LOGE(TAG, "psa_aead_encrypt failed: %d", status);
|
||||
psa_destroy_key(key_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (output_length != plaintext_len + tag_len) {
|
||||
ESP_LOGE(TAG, "psa_aead_encrypt output length mismatch: expected %zu, got %zu",
|
||||
plaintext_len + tag_len, output_length);
|
||||
psa_destroy_key(key_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
psa_destroy_key(key_id);
|
||||
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)
|
||||
psa_status_t status;
|
||||
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
|
||||
psa_key_id_t key_id = 0;
|
||||
psa_algorithm_t alg = PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, tag_len);
|
||||
size_t output_length = 0;
|
||||
/* 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 key attributes */
|
||||
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT);
|
||||
psa_set_key_algorithm(&attributes, alg);
|
||||
psa_set_key_type(&attributes, PSA_KEY_TYPE_AES);
|
||||
psa_set_key_bits(&attributes, BLE_EAD_KEY_SIZE * 8);
|
||||
|
||||
/* Import key */
|
||||
status = psa_import_key(&attributes, key, BLE_EAD_KEY_SIZE, &key_id);
|
||||
if (status != PSA_SUCCESS) {
|
||||
ESP_LOGE(TAG, "psa_import_key failed: %d", status);
|
||||
psa_reset_key_attributes(&attributes);
|
||||
return -1;
|
||||
}
|
||||
psa_reset_key_attributes(&attributes);
|
||||
|
||||
/* Decrypt and verify */
|
||||
/* PSA AEAD decrypt expects: ciphertext || tag */
|
||||
status = psa_aead_decrypt(key_id, alg,
|
||||
nonce, BLE_EAD_NONCE_SIZE,
|
||||
aad, aad_len,
|
||||
ciphertext, ciphertext_len,
|
||||
plaintext, plaintext_len,
|
||||
&output_length);
|
||||
if (status != PSA_SUCCESS) {
|
||||
ESP_LOGE(TAG, "psa_aead_decrypt failed: %d", status);
|
||||
psa_destroy_key(key_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (output_length != plaintext_len) {
|
||||
ESP_LOGE(TAG, "psa_aead_decrypt output length mismatch: expected %zu, got %zu",
|
||||
plaintext_len, output_length);
|
||||
psa_destroy_key(key_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
psa_destroy_key(key_id);
|
||||
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 PSA 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;
|
||||
}
|
||||
@@ -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 <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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 */
|
||||
+353
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#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));
|
||||
}
|
||||
+13
@@ -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
|
||||
Reference in New Issue
Block a user