mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
Merge branch 'feat/bluedroid_enc_adv_data' into 'master'
feat(ble/bluedroid): move Encrypted Advertising Data APIs into the host See merge request espressif/esp-idf!51898
This commit is contained in:
@@ -53,7 +53,7 @@ endmenu
|
||||
choice BT_SMP_CRYPTO_STACK
|
||||
prompt "SMP cryptographic stack"
|
||||
depends on (BT_BLE_SMP_ENABLE || BT_SMP_ENABLE || BT_NIMBLE_SECURITY_ENABLE || BT_LE_SECURITY_ENABLE || \
|
||||
BT_CTRL_BREDR_ENABLE)
|
||||
BT_CTRL_BREDR_ENABLE || BT_BLE_FEAT_ENC_ADV_DATA)
|
||||
default BT_SMP_CRYPTO_STACK_TINYCRYPT
|
||||
help
|
||||
Select the cryptographic library to use for SMP operations (AES, AES-CMAC, ECDH P-256).
|
||||
|
||||
@@ -355,6 +355,12 @@ if(CONFIG_BT_BLE_FEAT_CTE_EN)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(CONFIG_BT_BLE_FEAT_ENC_ADV_DATA)
|
||||
list(APPEND bluedroid_host_srcs
|
||||
"${CMAKE_CURRENT_LIST_DIR}/api/esp_ble_ead.c"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND)
|
||||
list(APPEND bluedroid_host_srcs
|
||||
"${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_ble_pseudo.c"
|
||||
|
||||
@@ -425,16 +425,6 @@ 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
|
||||
@@ -653,6 +643,36 @@ config BT_BLE_RPA_SUPPORTED
|
||||
For other BLE chips, devices support network privacy mode and device privacy mode,
|
||||
users can switch the two modes according to their own needs. So this option is enabled by default.
|
||||
|
||||
menu "Encrypted Advertising Data (EAD)"
|
||||
depends on BT_BLE_ENABLED
|
||||
|
||||
config BT_BLE_FEAT_ENC_ADV_DATA
|
||||
bool "Encrypted Advertising Data APIs"
|
||||
default n
|
||||
help
|
||||
Enable AES-CCM encrypt and decrypt APIs (esp_ble_ead_encrypt /
|
||||
esp_ble_ead_decrypt) for advertising payloads, as defined in
|
||||
Bluetooth Core Specification 5.4. Uses the same cryptographic
|
||||
stack as SMP (TinyCrypt or mbedTLS).
|
||||
|
||||
Enable this alone for a scanner/central that decrypts EAD with a
|
||||
pre-shared key, or when the peer publishes Key Material over GATT.
|
||||
|
||||
config BT_GATTS_KEY_MATERIAL_CHAR
|
||||
bool "Encrypted Data Key Material Characteristic"
|
||||
depends on BT_GATTS_ENABLE
|
||||
select BT_BLE_FEAT_ENC_ADV_DATA
|
||||
default n
|
||||
help
|
||||
Expose session key and IV through the GAP Key Material characteristic
|
||||
(UUID 0x2B88). A central can read it over an encrypted GATT link and
|
||||
then decrypt advertising data with esp_ble_ead_decrypt().
|
||||
|
||||
Selecting this option also enables Encrypted Advertising Data APIs.
|
||||
Set the published key with esp_ble_gap_set_key_material().
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Bluedroid debug option"
|
||||
config BT_BLUEDROID_MEM_DEBUG
|
||||
bool "Bluedroid memory debug"
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "ble_ead.h"
|
||||
#include <stdint.h>
|
||||
#include "esp_ble_ead.h"
|
||||
#include "esp_random.h"
|
||||
#include "esp_log.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if (CONFIG_BT_BLE_FEAT_ENC_ADV_DATA)
|
||||
|
||||
#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"
|
||||
@@ -24,7 +26,7 @@
|
||||
#endif
|
||||
|
||||
/* Additional Authenticated Data for EAD - EA (Encrypted Advertising) */
|
||||
static const uint8_t ble_ead_aad[BLE_EAD_AAD_SIZE] = { 0xEA };
|
||||
static const uint8_t ble_ead_aad[ESP_BLE_EAD_AAD_SIZE] = { 0xEA };
|
||||
|
||||
/**
|
||||
* @brief Generate randomizer with direction bit set
|
||||
@@ -32,14 +34,10 @@ static const uint8_t ble_ead_aad[BLE_EAD_AAD_SIZE] = { 0xEA };
|
||||
* 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])
|
||||
static int ble_ead_generate_randomizer(uint8_t randomizer[ESP_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);
|
||||
|
||||
esp_fill_random(randomizer, ESP_BLE_EAD_RANDOMIZER_SIZE);
|
||||
randomizer[ESP_BLE_EAD_RANDOMIZER_SIZE - 1] |= (1 << ESP_BLE_EAD_RANDOMIZER_DIRECTION_BIT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -48,48 +46,39 @@ static int ble_ead_generate_randomizer(uint8_t randomizer[BLE_EAD_RANDOMIZER_SIZ
|
||||
*
|
||||
* 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])
|
||||
static int ble_ead_generate_nonce(const uint8_t iv[ESP_BLE_EAD_IV_SIZE],
|
||||
const uint8_t randomizer[ESP_BLE_EAD_RANDOMIZER_SIZE],
|
||||
uint8_t nonce[ESP_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);
|
||||
memcpy(nonce, randomizer, ESP_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);
|
||||
|
||||
memcpy(nonce + ESP_BLE_EAD_RANDOMIZER_SIZE, iv, ESP_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)
|
||||
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");
|
||||
@@ -97,12 +86,7 @@ static int ble_aes_ccm_encrypt(const uint8_t *key, const uint8_t *nonce,
|
||||
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);
|
||||
ret = tc_ccm_config(&ccm_state, &sched, (uint8_t *)nonce, ESP_BLE_EAD_NONCE_SIZE, tag_len);
|
||||
if (ret != TC_CRYPTO_SUCCESS) {
|
||||
ESP_LOGE(TAG, "tc_ccm_config failed");
|
||||
memset(&sched, 0, sizeof(sched));
|
||||
@@ -110,12 +94,10 @@ static int ble_aes_ccm_encrypt(const uint8_t *key, const uint8_t *nonce,
|
||||
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);
|
||||
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));
|
||||
@@ -123,7 +105,6 @@ static int ble_aes_ccm_encrypt(const uint8_t *key, const uint8_t *nonce,
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Clear sensitive data from key schedule */
|
||||
memset(&sched, 0, sizeof(sched));
|
||||
memset(&ccm_state, 0, sizeof(ccm_state));
|
||||
return 0;
|
||||
@@ -135,31 +116,38 @@ static int ble_aes_ccm_encrypt(const uint8_t *key, const uint8_t *nonce,
|
||||
psa_algorithm_t alg = PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, tag_len);
|
||||
size_t output_length = 0;
|
||||
|
||||
/* Set key attributes */
|
||||
if (key == NULL || nonce == NULL || ciphertext == NULL) {
|
||||
ESP_LOGE(TAG, "Invalid input parameters");
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = psa_crypto_init();
|
||||
if (status != PSA_SUCCESS) {
|
||||
ESP_LOGE(TAG, "psa_crypto_init failed: %d", (int)status);
|
||||
return -1;
|
||||
}
|
||||
|
||||
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);
|
||||
psa_set_key_bits(&attributes, ESP_BLE_EAD_KEY_SIZE * 8);
|
||||
|
||||
/* Import key */
|
||||
status = psa_import_key(&attributes, key, BLE_EAD_KEY_SIZE, &key_id);
|
||||
status = psa_import_key(&attributes, key, ESP_BLE_EAD_KEY_SIZE, &key_id);
|
||||
if (status != PSA_SUCCESS) {
|
||||
ESP_LOGE(TAG, "psa_import_key failed: %d", status);
|
||||
ESP_LOGE(TAG, "psa_import_key failed: %d", (int)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,
|
||||
nonce, ESP_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);
|
||||
ESP_LOGE(TAG, "psa_aead_encrypt failed: %d", (int)status);
|
||||
psa_destroy_key(key_id);
|
||||
return -1;
|
||||
}
|
||||
@@ -173,47 +161,37 @@ static int ble_aes_ccm_encrypt(const uint8_t *key, const uint8_t *nonce,
|
||||
|
||||
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,
|
||||
size_t plaintext_capacity)
|
||||
const uint8_t *ciphertext, size_t ciphertext_len,
|
||||
const uint8_t *aad, size_t aad_len,
|
||||
uint8_t *plaintext, size_t tag_len,
|
||||
size_t plaintext_capacity)
|
||||
{
|
||||
#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;
|
||||
|
||||
if (plaintext_len > plaintext_capacity) {
|
||||
ESP_LOGE(TAG, "plaintext_len (%zu) > plaintext_capacity (%zu)", plaintext_len, plaintext_capacity);
|
||||
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");
|
||||
@@ -221,12 +199,7 @@ static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce,
|
||||
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);
|
||||
ret = tc_ccm_config(&ccm_state, &sched, (uint8_t *)nonce, ESP_BLE_EAD_NONCE_SIZE, tag_len);
|
||||
if (ret != TC_CRYPTO_SUCCESS) {
|
||||
ESP_LOGE(TAG, "tc_ccm_config failed");
|
||||
memset(&sched, 0, sizeof(sched));
|
||||
@@ -234,12 +207,10 @@ static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce,
|
||||
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);
|
||||
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));
|
||||
@@ -247,7 +218,6 @@ static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce,
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Clear sensitive data from key schedule */
|
||||
memset(&sched, 0, sizeof(sched));
|
||||
memset(&ccm_state, 0, sizeof(ccm_state));
|
||||
return 0;
|
||||
@@ -258,53 +228,51 @@ static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce,
|
||||
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;
|
||||
|
||||
if (plaintext_len > plaintext_capacity) {
|
||||
ESP_LOGE(TAG, "plaintext_len (%zu) > plaintext_capacity (%zu)", plaintext_len, plaintext_capacity);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set key attributes */
|
||||
status = psa_crypto_init();
|
||||
if (status != PSA_SUCCESS) {
|
||||
ESP_LOGE(TAG, "psa_crypto_init failed: %d", (int)status);
|
||||
return -1;
|
||||
}
|
||||
|
||||
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);
|
||||
psa_set_key_bits(&attributes, ESP_BLE_EAD_KEY_SIZE * 8);
|
||||
|
||||
/* Import key */
|
||||
status = psa_import_key(&attributes, key, BLE_EAD_KEY_SIZE, &key_id);
|
||||
status = psa_import_key(&attributes, key, ESP_BLE_EAD_KEY_SIZE, &key_id);
|
||||
if (status != PSA_SUCCESS) {
|
||||
ESP_LOGE(TAG, "psa_import_key failed: %d", status);
|
||||
ESP_LOGE(TAG, "psa_import_key failed: %d", (int)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,
|
||||
nonce, ESP_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);
|
||||
ESP_LOGE(TAG, "psa_aead_decrypt failed: %d", (int)status);
|
||||
psa_destroy_key(key_id);
|
||||
return -1;
|
||||
}
|
||||
@@ -318,65 +286,74 @@ static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce,
|
||||
|
||||
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)
|
||||
esp_err_t esp_ble_ead_encrypt(const uint8_t session_key[ESP_BLE_EAD_KEY_SIZE],
|
||||
const uint8_t iv[ESP_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];
|
||||
uint8_t nonce[ESP_BLE_EAD_NONCE_SIZE];
|
||||
|
||||
if (session_key == NULL) {
|
||||
ESP_LOGE(TAG, "session_key is NULL");
|
||||
return -1;
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (iv == NULL) {
|
||||
ESP_LOGE(TAG, "iv is NULL");
|
||||
return -1;
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (payload == NULL && payload_size > 0) {
|
||||
ESP_LOGE(TAG, "payload is NULL but payload_size > 0");
|
||||
return -1;
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (encrypted_payload == NULL) {
|
||||
ESP_LOGE(TAG, "encrypted_payload is NULL");
|
||||
return -1;
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* CSS Part A 1.23.2: plaintext shall consist of one or more AD structures */
|
||||
if (payload_size == 0) {
|
||||
ESP_LOGE(TAG, "payload_size is 0");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (payload_size > SIZE_MAX - (ESP_BLE_EAD_RANDOMIZER_SIZE + ESP_BLE_EAD_MIC_SIZE)) {
|
||||
ESP_LOGE(TAG, "payload_size too large");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Generate nonce with random randomizer */
|
||||
ret = ble_ead_generate_nonce(iv, NULL, nonce);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Copy randomizer to the start of encrypted payload */
|
||||
memcpy(encrypted_payload, nonce, BLE_EAD_RANDOMIZER_SIZE);
|
||||
memcpy(encrypted_payload, nonce, ESP_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);
|
||||
payload, payload_size,
|
||||
ble_ead_aad, ESP_BLE_EAD_AAD_SIZE,
|
||||
&encrypted_payload[ESP_BLE_EAD_RANDOMIZER_SIZE],
|
||||
ESP_BLE_EAD_MIC_SIZE);
|
||||
if (ret != 0) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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, size_t payload_capacity)
|
||||
esp_err_t esp_ble_ead_decrypt(const uint8_t session_key[ESP_BLE_EAD_KEY_SIZE],
|
||||
const uint8_t iv[ESP_BLE_EAD_IV_SIZE],
|
||||
const uint8_t *encrypted_payload, size_t encrypted_payload_size,
|
||||
uint8_t *payload, size_t payload_capacity)
|
||||
{
|
||||
int ret;
|
||||
uint8_t nonce[BLE_EAD_NONCE_SIZE];
|
||||
uint8_t nonce[ESP_BLE_EAD_NONCE_SIZE];
|
||||
const uint8_t *randomizer;
|
||||
const uint8_t *ciphertext;
|
||||
size_t ciphertext_len;
|
||||
@@ -384,56 +361,56 @@ int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE],
|
||||
|
||||
if (session_key == NULL) {
|
||||
ESP_LOGE(TAG, "session_key is NULL");
|
||||
return -1;
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (iv == NULL) {
|
||||
ESP_LOGE(TAG, "iv is NULL");
|
||||
return -1;
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (encrypted_payload == NULL) {
|
||||
ESP_LOGE(TAG, "encrypted_payload is NULL");
|
||||
return -1;
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (payload == NULL) {
|
||||
ESP_LOGE(TAG, "payload is NULL");
|
||||
return -1;
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (encrypted_payload_size < BLE_EAD_RANDOMIZER_SIZE + BLE_EAD_MIC_SIZE) {
|
||||
/* Randomizer + MIC + at least 1 byte of AD structure */
|
||||
if (encrypted_payload_size < ESP_BLE_EAD_RANDOMIZER_SIZE + ESP_BLE_EAD_MIC_SIZE + 1) {
|
||||
ESP_LOGE(TAG, "encrypted_payload_size too small");
|
||||
return -1;
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
expected_plaintext_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size);
|
||||
expected_plaintext_len = ESP_BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size);
|
||||
if (expected_plaintext_len > payload_capacity) {
|
||||
ESP_LOGE(TAG, "EAD plaintext length %zu exceeds payload buffer %zu",
|
||||
expected_plaintext_len, payload_capacity);
|
||||
return -1;
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* Extract randomizer from the start of encrypted payload */
|
||||
randomizer = encrypted_payload;
|
||||
ciphertext = &encrypted_payload[ESP_BLE_EAD_RANDOMIZER_SIZE];
|
||||
ciphertext_len = encrypted_payload_size - ESP_BLE_EAD_RANDOMIZER_SIZE;
|
||||
|
||||
/* 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;
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* 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,
|
||||
payload_capacity);
|
||||
ciphertext, ciphertext_len,
|
||||
ble_ead_aad, ESP_BLE_EAD_AAD_SIZE,
|
||||
payload, ESP_BLE_EAD_MIC_SIZE,
|
||||
payload_capacity);
|
||||
if (ret != 0) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_BT_BLE_FEAT_ENC_ADV_DATA */
|
||||
122
components/bt/host/bluedroid/api/include/api/esp_ble_ead.h
Normal file
122
components/bt/host/bluedroid/api/include/api/esp_ble_ead.h
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef __ESP_BLE_EAD_H__
|
||||
#define __ESP_BLE_EAD_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief BLE Encrypted Advertising Data (EAD)
|
||||
*
|
||||
* Based on Bluetooth Core Specification Version 5.4 and Core Specification
|
||||
* Supplement v11, Part A 1.23.
|
||||
*
|
||||
* Enable CONFIG_BT_BLE_FEAT_ENC_ADV_DATA to compile the encrypt/decrypt APIs.
|
||||
* A GATT server that publishes the session key should also enable
|
||||
* CONFIG_BT_GATTS_KEY_MATERIAL_CHAR and call esp_ble_gap_set_key_material().
|
||||
*/
|
||||
|
||||
#define ESP_BLE_EAD_KEY_SIZE 16 /*!< 128-bit session key */
|
||||
#define ESP_BLE_EAD_IV_SIZE 8 /*!< 64-bit Initialization Vector */
|
||||
#define ESP_BLE_EAD_RANDOMIZER_SIZE 5 /*!< 40-bit Randomizer */
|
||||
#define ESP_BLE_EAD_MIC_SIZE 4 /*!< 32-bit Message Integrity Check */
|
||||
#define ESP_BLE_EAD_NONCE_SIZE 13 /*!< 104-bit Nonce (Randomizer + IV) */
|
||||
#define ESP_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 ESP_BLE_EAD_RANDOMIZER_DIRECTION_BIT 7
|
||||
|
||||
/**
|
||||
* @brief Calculate encrypted payload size from plaintext size
|
||||
*
|
||||
* Encrypted payload layout: Randomizer || Ciphertext || MIC
|
||||
*/
|
||||
#define ESP_BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(payload_size) \
|
||||
(ESP_BLE_EAD_RANDOMIZER_SIZE + (payload_size) + ESP_BLE_EAD_MIC_SIZE)
|
||||
|
||||
/**
|
||||
* @brief Calculate decrypted payload size from encrypted payload size
|
||||
*/
|
||||
#define ESP_BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_size) \
|
||||
((encrypted_size) - ESP_BLE_EAD_RANDOMIZER_SIZE - ESP_BLE_EAD_MIC_SIZE)
|
||||
|
||||
/**
|
||||
* @brief Key material structure for EAD
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t session_key[ESP_BLE_EAD_KEY_SIZE]; /*!< 128-bit session key */
|
||||
uint8_t iv[ESP_BLE_EAD_IV_SIZE]; /*!< 64-bit Initialization Vector */
|
||||
} esp_ble_ead_key_material_t;
|
||||
|
||||
/**
|
||||
* @brief Encrypt advertising data using AES-CCM
|
||||
*
|
||||
* The resulting data in @p encrypted_payload has the following layout:
|
||||
* - Randomizer in the first ESP_BLE_EAD_RANDOMIZER_SIZE bytes
|
||||
* - Encrypted payload of @p payload_size bytes
|
||||
* - MIC in the last ESP_BLE_EAD_MIC_SIZE bytes
|
||||
*
|
||||
* The function must be called each time the RPA is updated or the advertising
|
||||
* data are modified. @p payload may contain one or more concatenated advertising
|
||||
* structures (length + type + data).
|
||||
*
|
||||
* @param[in] session_key 16-byte session key
|
||||
* @param[in] iv 8-byte Initialization Vector. Must be changed
|
||||
* each time the session key changes
|
||||
* @param[in] payload Plaintext advertising data to encrypt
|
||||
* @param[in] payload_size Size of plaintext data. Must be greater than 0
|
||||
* @param[out] encrypted_payload Output buffer for encrypted data.
|
||||
* Size must be at least ESP_BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(payload_size)
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: success
|
||||
* - ESP_ERR_INVALID_ARG: invalid argument
|
||||
* - ESP_FAIL: encryption failed
|
||||
*/
|
||||
esp_err_t esp_ble_ead_encrypt(const uint8_t session_key[ESP_BLE_EAD_KEY_SIZE],
|
||||
const uint8_t iv[ESP_BLE_EAD_IV_SIZE],
|
||||
const uint8_t *payload, size_t payload_size,
|
||||
uint8_t *encrypted_payload);
|
||||
|
||||
/**
|
||||
* @brief Decrypt advertising data using AES-CCM
|
||||
*
|
||||
* @param[in] session_key 16-byte session key
|
||||
* @param[in] iv 8-byte Initialization Vector
|
||||
* @param[in] encrypted_payload Encrypted advertising data (includes randomizer and MIC).
|
||||
* This should only contain the advertising data from the
|
||||
* received advertising structure, neither the length nor the type
|
||||
* @param[in] encrypted_payload_size Size of encrypted data
|
||||
* @param[out] payload Output buffer for decrypted data.
|
||||
* Use ESP_BLE_EAD_DECRYPTED_PAYLOAD_SIZE to get the right size
|
||||
* @param[in] payload_capacity Size of @p payload in bytes; must be >=
|
||||
* ESP_BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size)
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: success
|
||||
* - ESP_ERR_INVALID_ARG: invalid argument
|
||||
* - ESP_FAIL: decryption or authentication failed
|
||||
*/
|
||||
esp_err_t esp_ble_ead_decrypt(const uint8_t session_key[ESP_BLE_EAD_KEY_SIZE],
|
||||
const uint8_t iv[ESP_BLE_EAD_IV_SIZE],
|
||||
const uint8_t *encrypted_payload, size_t encrypted_payload_size,
|
||||
uint8_t *payload, size_t payload_capacity);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __ESP_BLE_EAD_H__ */
|
||||
@@ -364,6 +364,7 @@ typedef enum {
|
||||
ESP_BLE_AD_TYPE_TRANS_DISC_DATA = 0x26, /* relate to BTM_BLE_AD_TYPE_TRANS_DISC_DATA in stack/btm_ble_api.h */
|
||||
ESP_BLE_AD_TYPE_LE_SUPPORT_FEATURE = 0x27, /* relate to BTM_BLE_AD_TYPE_LE_SUPPORT_FEATURE in stack/btm_ble_api.h */
|
||||
ESP_BLE_AD_TYPE_CHAN_MAP_UPDATE = 0x28, /* relate to BTM_BLE_AD_TYPE_CHAN_MAP_UPDATE in stack/btm_ble_api.h */
|
||||
ESP_BLE_AD_TYPE_ENC_ADV_DATA = 0x31, /* relate to BTM_BLE_AD_TYPE_ENC_ADV_DATA in stack/btm_ble_api.h */
|
||||
ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE = 0xFF, /* relate to BTM_BLE_AD_MANUFACTURER_SPECIFIC_TYPE in stack/btm_ble_api.h */
|
||||
} esp_ble_adv_data_type;
|
||||
|
||||
@@ -3584,7 +3585,10 @@ esp_err_t esp_ble_gap_get_device_name(void);
|
||||
*
|
||||
* 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.
|
||||
* A central can read the characteristic over an encrypted GATT link and
|
||||
* decrypt Encrypted Advertising Data with esp_ble_ead_decrypt().
|
||||
* Enabling this API also requires CONFIG_BT_GATTS_KEY_MATERIAL_CHAR,
|
||||
* which selects CONFIG_BT_BLE_FEAT_ENC_ADV_DATA.
|
||||
*
|
||||
* @param[in] session_key - 16-byte (128-bit) session key for AES-CCM encryption
|
||||
* @param[in] iv - 8-byte (64-bit) initialization vector
|
||||
@@ -3593,6 +3597,7 @@ esp_err_t esp_ble_gap_get_device_name(void);
|
||||
* - ESP_OK : success
|
||||
* - other : failed
|
||||
*
|
||||
* @see esp_ble_ead_encrypt, esp_ble_ead_decrypt
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -359,6 +359,7 @@ typedef UINT32 tBTM_BLE_AD_MASK;
|
||||
#define BTM_BLE_AD_TYPE_TRANS_DISC_DATA 0x26
|
||||
#define BTM_BLE_AD_TYPE_LE_SUPPORT_FEATURE 0x27
|
||||
#define BTM_BLE_AD_TYPE_CHAN_MAP_UPDATE 0x28
|
||||
#define BTM_BLE_AD_TYPE_ENC_ADV_DATA 0x31
|
||||
|
||||
#define BTM_BLE_AD_TYPE_MANU HCI_EIR_MANUFACTURER_SPECIFIC_TYPE /* 0xff */
|
||||
typedef UINT8 tBTM_BLE_AD_TYPE;
|
||||
|
||||
@@ -47,6 +47,7 @@ BLE_DOCS = [
|
||||
'api-guides/low-power-mode/low-power-mode-ble.rst',
|
||||
'api-reference/bluetooth/bt_le.rst',
|
||||
'api-reference/bluetooth/esp_gap_ble.rst',
|
||||
'api-reference/bluetooth/esp_ble_ead.rst',
|
||||
'api-reference/bluetooth/esp_gatt_defs.rst',
|
||||
'api-reference/bluetooth/esp_gatts.rst',
|
||||
'api-reference/bluetooth/esp_gattc.rst',
|
||||
|
||||
@@ -85,6 +85,7 @@ INPUT = \
|
||||
$(PROJECT_PATH)/components/bt/esp_ble_iso/api/include/esp_ble_iso_common_api.h \
|
||||
$(PROJECT_PATH)/components/bt/host/bluedroid/api/include/api/esp_a2dp_api.h \
|
||||
$(PROJECT_PATH)/components/bt/host/bluedroid/api/include/api/esp_avrc_api.h \
|
||||
$(PROJECT_PATH)/components/bt/host/bluedroid/api/include/api/esp_ble_ead.h \
|
||||
$(PROJECT_PATH)/components/bt/host/bluedroid/api/include/api/esp_bt_defs.h \
|
||||
$(PROJECT_PATH)/components/bt/host/bluedroid/api/include/api/esp_bt_device.h \
|
||||
$(PROJECT_PATH)/components/bt/host/bluedroid/api/include/api/esp_bt_main.h \
|
||||
|
||||
@@ -25,6 +25,10 @@ The Bluetooth LE API in ESP-IDF is organized into the following parts:
|
||||
|
||||
Discovers and accesses services on remote servers (central role)
|
||||
|
||||
- :doc:`Bluetooth Low Energy Encrypted Advertising Data <esp_ble_ead>`
|
||||
|
||||
Encrypts and decrypts advertising payloads with AES-CCM (Bluetooth Core Specification 5.4)
|
||||
|
||||
.. only:: SOC_BLUFI_SUPPORTED
|
||||
|
||||
- :doc:`Bluetooth Low Energy BluFi <esp_blufi>`
|
||||
@@ -41,4 +45,5 @@ Each part typically includes an **Overview**, **Application Examples**, and **AP
|
||||
Bluetooth Low Energy GATT Define <esp_gatt_defs>
|
||||
Bluetooth Low Energy GATT Server <esp_gatts>
|
||||
Bluetooth Low Energy GATT Client <esp_gattc>
|
||||
Bluetooth Low Energy Encrypted Advertising Data <esp_ble_ead>
|
||||
:SOC_BLUFI_SUPPORTED: Bluetooth Low Energy BluFi <esp_blufi>
|
||||
|
||||
31
docs/en/api-reference/bluetooth/esp_ble_ead.rst
Normal file
31
docs/en/api-reference/bluetooth/esp_ble_ead.rst
Normal file
@@ -0,0 +1,31 @@
|
||||
Encrypted Advertising Data (EAD)
|
||||
================================
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Encrypted Advertising Data (EAD) was introduced in Bluetooth Core Specification 5.4. It allows a device to encrypt one or more advertising structures with AES-CCM, so that only peers that hold the corresponding session key and IV can recover the plaintext.
|
||||
|
||||
The Bluedroid host exposes this as a pair of synchronous APIs in ``esp_ble_ead.h``. Encryption and decryption are performed in the host and do not require a controller feature bit.
|
||||
|
||||
These APIs are compiled when ``CONFIG_BT_BLE_FEAT_ENC_ADV_DATA`` is enabled.
|
||||
|
||||
The GAP Key Material characteristic (UUID 0x2B88, ``CONFIG_BT_GATTS_KEY_MATERIAL_CHAR``) is the standard way for a peripheral to publish the session key and IV. Enabling that option also selects the EAD APIs. Call :cpp:func:`esp_ble_gap_set_key_material` to set the value so a peer can read it over an encrypted GATT connection, then decrypt with :cpp:func:`esp_ble_ead_decrypt`.
|
||||
|
||||
A central that already has a pre-shared key only needs ``CONFIG_BT_BLE_FEAT_ENC_ADV_DATA``.
|
||||
|
||||
Application Examples
|
||||
--------------------
|
||||
|
||||
- :example:`bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph` demonstrates encrypting advertising data and exposing Key Material through the GAP service.
|
||||
|
||||
- :example:`bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent` demonstrates scanning for encrypted advertising data and decrypting it after reading, or using, the Key Material.
|
||||
|
||||
In menuconfig, see ``Bluedroid Options`` > ``Encrypted Advertising Data (EAD)``.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/esp_ble_ead.inc
|
||||
@@ -10,6 +10,8 @@ Application Examples
|
||||
|
||||
- :example:`bluetooth/bluedroid/ble/gatt_security_server` demonstrates how to use ESP BLE security APIs on {IDF_TARGET_NAME} to establish a secure connection and encrypt communication with peer devices while acting as a GATT server.
|
||||
|
||||
- :example:`bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph` demonstrates Encrypted Advertising Data and how to publish Key Material with :cpp:func:`esp_ble_gap_set_key_material`. See also :doc:`esp_ble_ead`.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ ESP-IDF 中的低功耗蓝牙 API 包括以下部分:
|
||||
|
||||
发现并访问远程服务器的服务(中心设备角色)
|
||||
|
||||
- :doc:`Bluetooth Low Energy Encrypted Advertising Data <esp_ble_ead>`
|
||||
|
||||
使用 AES-CCM 加密和解密广播载荷(蓝牙核心规范 5.4)
|
||||
|
||||
.. only:: SOC_BLUFI_SUPPORTED
|
||||
|
||||
- :doc:`Bluetooth Low Energy BluFi <esp_blufi>`
|
||||
@@ -41,4 +45,5 @@ ESP-IDF 中的低功耗蓝牙 API 包括以下部分:
|
||||
Bluetooth Low Energy GATT Define <esp_gatt_defs>
|
||||
Bluetooth Low Energy GATT Server <esp_gatts>
|
||||
Bluetooth Low Energy GATT Client <esp_gattc>
|
||||
Bluetooth Low Energy Encrypted Advertising Data <esp_ble_ead>
|
||||
:SOC_BLUFI_SUPPORTED: Bluetooth Low Energy BluFi <esp_blufi>
|
||||
|
||||
31
docs/zh_CN/api-reference/bluetooth/esp_ble_ead.rst
Normal file
31
docs/zh_CN/api-reference/bluetooth/esp_ble_ead.rst
Normal file
@@ -0,0 +1,31 @@
|
||||
加密广播数据 (EAD)
|
||||
==================
|
||||
|
||||
:link_to_translation:`en:[English]`
|
||||
|
||||
概述
|
||||
--------
|
||||
|
||||
加密广播数据 (Encrypted Advertising Data,EAD) 引入于蓝牙核心规范 5.4。设备可以使用 AES-CCM 加密一段或多段广播结构,只有持有对应 Session Key 和 IV 的对端才能还原明文。
|
||||
|
||||
Bluedroid host 在 ``esp_ble_ead.h`` 中提供一组同步 API。加解密在 host 侧完成,不依赖控制器特性位。
|
||||
|
||||
启用 ``CONFIG_BT_BLE_FEAT_ENC_ADV_DATA`` 后会编译这些 API。
|
||||
|
||||
GAP Key Material 特征 (UUID 0x2B88,``CONFIG_BT_GATTS_KEY_MATERIAL_CHAR``) 是 Peripheral 发布 Session Key 和 IV 的标准做法。打开该选项会同时选中 EAD 加解密 API。调用 :cpp:func:`esp_ble_gap_set_key_material` 写入特征值后,对端可在加密 GATT 连接上读取,再用 :cpp:func:`esp_ble_ead_decrypt` 解密广播。
|
||||
|
||||
若 Central 已持有预共享密钥,只需打开 ``CONFIG_BT_BLE_FEAT_ENC_ADV_DATA``。
|
||||
|
||||
应用示例
|
||||
--------------------
|
||||
|
||||
- :example:`bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph` 演示如何加密广播数据,并通过 GAP 服务提供 Key Material。
|
||||
|
||||
- :example:`bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent` 演示如何扫描加密广播数据,并在读取或使用 Key Material 后解密。
|
||||
|
||||
在 menuconfig 中见 **Bluedroid Options → Encrypted Advertising Data (EAD)**。
|
||||
|
||||
API 参考
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/esp_ble_ead.inc
|
||||
@@ -10,6 +10,8 @@ GAP API
|
||||
|
||||
- :example:`bluetooth/bluedroid/ble/gatt_security_server` 演示使用 ESP 低功耗蓝牙 security API,{IDF_TARGET_NAME} 作为 GATT 服务器时如何建立安全连接并加密与对等设备的通信。
|
||||
|
||||
- :example:`bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph` 演示加密广播数据,并通过 :cpp:func:`esp_ble_gap_set_key_material` 发布 Key Material。另见 :doc:`esp_ble_ead`。
|
||||
|
||||
API 参考
|
||||
-------------
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
This example demonstrates how to receive and decrypt BLE Encrypted Advertising Data (EAD) with Bluedroid stack.
|
||||
|
||||
Decryption uses the host APIs in `esp_ble_ead.h` (`esp_ble_ead_decrypt`). Enable `CONFIG_BT_BLE_FEAT_ENC_ADV_DATA` (already set in `sdkconfig.defaults`).
|
||||
|
||||
## Overview
|
||||
|
||||
This central example works with the `enc_adv_data_prph` peripheral example to demonstrate:
|
||||
@@ -34,7 +36,7 @@ This central example works with the `enc_adv_data_prph` peripheral example to de
|
||||
│ ▼ │ │ key to decrypt │
|
||||
│ Store key │ │ │ │
|
||||
│ │ │ │ ▼ │
|
||||
│ ▼ │ │ ✅ Decrypt immediately │
|
||||
│ ▼ │ │ Decrypt immediately │
|
||||
│ Later scans: │ │ │
|
||||
│ ┌─────────┐ ┌─────────┐ │ │
|
||||
│ │ Central │──▶│ Periph │ │ │
|
||||
@@ -42,12 +44,12 @@ This central example works with the `enc_adv_data_prph` peripheral example to de
|
||||
│ │ │ │
|
||||
│ │ No connection needed │ │
|
||||
│ ▼ │ │
|
||||
│ ✅ Decrypt using stored key │ │
|
||||
│ Decrypt using stored key │ │
|
||||
│ │ │
|
||||
├────────────────────────────────┼────────────────────────────────────────────┤
|
||||
│ ✓ Secure key exchange │ ✓ No connection latency │
|
||||
│ ✓ Dynamic key support │ ✓ Simpler implementation │
|
||||
│ ✗ First-time connection needed │ ✗ Key must be pre-provisioned │
|
||||
│ + Secure key exchange │ + No connection latency │
|
||||
│ + Dynamic key support │ + Simpler implementation │
|
||||
│ - First-time connection needed │ - Key must be pre-provisioned │
|
||||
└────────────────────────────────┴────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -69,7 +71,7 @@ This central example works with the `enc_adv_data_prph` peripheral example to de
|
||||
│ │ 1. Scan │ │
|
||||
│ │ ──────────────────────────────────────────────────▶ │ │
|
||||
│ │ │ │
|
||||
│ │ 2. Receive Adv (UUID=0x2C01, Encrypted Data) │ │
|
||||
│ │ 2. Receive Adv (UUID=0x1800, Encrypted Data) │ │
|
||||
│ │ ◀────────────────────────────────────────────────── │ │
|
||||
│ │ │ │
|
||||
│ │ [No key yet - cannot decrypt] │ │
|
||||
@@ -100,7 +102,7 @@ This central example works with the `enc_adv_data_prph` peripheral example to de
|
||||
│ │ │ │
|
||||
│ │ 11. Decrypt using stored key (NO CONNECTION!) │ │
|
||||
│ │ ┌────────────────────────────────────────┐ │ │
|
||||
│ │ │ ble_ead_decrypt(session_key, iv, ...) │ │ │
|
||||
│ │ │ esp_ble_ead_decrypt(session_key, iv, ...) │ │ │
|
||||
│ │ │ Result: "prph" (decrypted name) │ │ │
|
||||
│ │ └────────────────────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
@@ -136,13 +138,13 @@ This central example works with the `enc_adv_data_prph` peripheral example to de
|
||||
│ │ │ │
|
||||
│ │ 3. Immediately decrypt (NO CONNECTION!) │ │
|
||||
│ │ ┌────────────────────────────────────────┐ │ │
|
||||
│ │ │ ble_ead_decrypt(pre_shared_key, ...) │ │ │
|
||||
│ │ │ esp_ble_ead_decrypt(pre_shared_key, ...) │ │ │
|
||||
│ │ │ Result: "prph" (decrypted name) │ │ │
|
||||
│ │ └────────────────────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ │
|
||||
│ ⚡ No connection overhead - instant decryption! │
|
||||
│ No connection overhead - instant decryption! │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
@@ -253,11 +255,11 @@ I (XXX) ENC_ADV_CENT: Decrypted device name: prph
|
||||
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: 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"
|
||||
I (XXX) ENC_ADV_CENT_SIMPLE: Decryption successful (no connection needed!)
|
||||
I (XXX) ENC_ADV_CENT_SIMPLE: Decrypted device name: "prph"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -4,5 +4,5 @@ else()
|
||||
set(MAIN_SRC "enc_adv_data_cent.c")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${MAIN_SRC} "ble_ead.c"
|
||||
idf_component_register(SRCS ${MAIN_SRC}
|
||||
INCLUDE_DIRS ".")
|
||||
|
||||
@@ -1,446 +0,0 @@
|
||||
/*
|
||||
* 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,
|
||||
size_t plaintext_capacity)
|
||||
{
|
||||
#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;
|
||||
|
||||
if (plaintext_len > plaintext_capacity) {
|
||||
ESP_LOGE(TAG, "plaintext_len (%zu) > plaintext_capacity (%zu)", plaintext_len, plaintext_capacity);
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
if (plaintext_len > plaintext_capacity) {
|
||||
ESP_LOGE(TAG, "plaintext_len (%zu) > plaintext_capacity (%zu)", plaintext_len, plaintext_capacity);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 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, size_t payload_capacity)
|
||||
{
|
||||
int ret;
|
||||
uint8_t nonce[BLE_EAD_NONCE_SIZE];
|
||||
const uint8_t *randomizer;
|
||||
const uint8_t *ciphertext;
|
||||
size_t ciphertext_len;
|
||||
size_t expected_plaintext_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;
|
||||
}
|
||||
|
||||
expected_plaintext_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size);
|
||||
if (expected_plaintext_len > payload_capacity) {
|
||||
ESP_LOGE(TAG, "EAD plaintext length %zu exceeds payload buffer %zu",
|
||||
expected_plaintext_len, payload_capacity);
|
||||
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,
|
||||
payload_capacity);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
* @param payload_capacity Size of @a payload in bytes; must be >=
|
||||
* 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, size_t payload_capacity);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BLE_EAD_H */
|
||||
@@ -30,7 +30,7 @@
|
||||
#include "esp_gatt_common_api.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "ble_ead.h"
|
||||
#include "esp_ble_ead.h"
|
||||
|
||||
#define TAG "ENC_ADV_CENT"
|
||||
|
||||
@@ -51,7 +51,7 @@ typedef struct {
|
||||
bool valid;
|
||||
esp_bd_addr_t addr;
|
||||
bool key_material_exist;
|
||||
ble_ead_key_material_t key_material;
|
||||
esp_ble_ead_key_material_t key_material;
|
||||
} peer_info_t;
|
||||
|
||||
static peer_info_t peers[MAX_PEERS] = {0};
|
||||
@@ -142,26 +142,26 @@ static void decrypt_enc_adv_data(const uint8_t *adv_data, uint8_t adv_len, const
|
||||
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) {
|
||||
if (enc_data_len < ESP_BLE_EAD_RANDOMIZER_SIZE + ESP_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);
|
||||
size_t dec_len = ESP_BLE_EAD_DECRYPTED_PAYLOAD_SIZE(enc_data_len);
|
||||
if (dec_len > sizeof(dec_data)) {
|
||||
ESP_LOGW(TAG, "Encrypted AD would yield %zu plaintext bytes; example buffer is %zu — skip",
|
||||
dec_len, sizeof(dec_data));
|
||||
break;
|
||||
}
|
||||
|
||||
int rc = ble_ead_decrypt(
|
||||
esp_err_t rc = esp_ble_ead_decrypt(
|
||||
peers[peer_idx].key_material.session_key,
|
||||
peers[peer_idx].key_material.iv,
|
||||
enc_data, enc_data_len,
|
||||
dec_data, sizeof(dec_data));
|
||||
|
||||
if (rc == 0) {
|
||||
if (rc == ESP_OK) {
|
||||
size_t safe_dec_len = dec_len;
|
||||
if (safe_dec_len > sizeof(dec_data)) {
|
||||
ESP_LOGW(TAG, "dec_len %zu > buffer %zu, clamping for log/parse",
|
||||
@@ -448,17 +448,17 @@ static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
||||
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)) {
|
||||
param->read.value_len == sizeof(esp_ble_ead_key_material_t)) {
|
||||
/* Store key material */
|
||||
int peer_idx = find_peer(gattc_remote_bda);
|
||||
if (peer_idx >= 0) {
|
||||
memcpy(&peers[peer_idx].key_material, param->read.value,
|
||||
sizeof(ble_ead_key_material_t));
|
||||
sizeof(esp_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));
|
||||
sizeof(esp_ble_ead_key_material_t));
|
||||
}
|
||||
|
||||
/* Disconnect and resume scanning */
|
||||
|
||||
@@ -24,18 +24,18 @@
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "ble_ead.h"
|
||||
#include "esp_ble_ead.h"
|
||||
|
||||
#define TAG "ENC_ADV_CENT_SIMPLE"
|
||||
|
||||
/* Custom service UUID to identify target device */
|
||||
#define CUSTOM_SERVICE_UUID 0x2C01
|
||||
/* GAP Service UUID advertised by enc_adv_data_prph (Key Material lives in GAP) */
|
||||
#define GAP_SERVICE_UUID 0x1800
|
||||
|
||||
/*
|
||||
* 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 = {
|
||||
static const esp_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
|
||||
@@ -72,7 +72,7 @@ static bool is_target_device(const uint8_t *adv_data, uint8_t adv_len)
|
||||
if (payload_len >= 2) {
|
||||
for (int i = 0; i + 1 < payload_len; i += 2) {
|
||||
uint16_t uuid = adv_data[offset + 2 + i] | (adv_data[offset + 3 + i] << 8);
|
||||
if (uuid == CUSTOM_SERVICE_UUID) {
|
||||
if (uuid == GAP_SERVICE_UUID) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -112,34 +112,34 @@ static void decrypt_adv_data_no_connect(const uint8_t *adv_data, uint8_t adv_len
|
||||
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) {
|
||||
if (enc_data_len < ESP_BLE_EAD_RANDOMIZER_SIZE + ESP_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);
|
||||
size_t dec_len = ESP_BLE_EAD_DECRYPTED_PAYLOAD_SIZE(enc_data_len);
|
||||
if (dec_len > sizeof(dec_data)) {
|
||||
ESP_LOGW(TAG, "Encrypted AD would yield %zu plaintext bytes; example buffer is %zu — skip",
|
||||
dec_len, sizeof(dec_data));
|
||||
return;
|
||||
}
|
||||
|
||||
int rc = ble_ead_decrypt(
|
||||
esp_err_t rc = esp_ble_ead_decrypt(
|
||||
pre_shared_key.session_key,
|
||||
pre_shared_key.iv,
|
||||
enc_data, enc_data_len,
|
||||
dec_data, sizeof(dec_data));
|
||||
|
||||
if (rc == 0) {
|
||||
if (rc == ESP_OK) {
|
||||
size_t safe_dec_len = dec_len;
|
||||
if (safe_dec_len > sizeof(dec_data)) {
|
||||
ESP_LOGW(TAG, "dec_len %zu > buffer %zu, clamping for log/parse",
|
||||
dec_len, sizeof(dec_data));
|
||||
safe_dec_len = sizeof(dec_data);
|
||||
}
|
||||
ESP_LOGI(TAG, "✅ Decryption successful (no connection needed!)");
|
||||
ESP_LOGI(TAG, "Decryption successful (no connection needed!)");
|
||||
ESP_LOGI(TAG, "Decrypted data (%zu bytes):", safe_dec_len);
|
||||
ESP_LOG_BUFFER_HEX(TAG, dec_data, safe_dec_len);
|
||||
|
||||
@@ -164,14 +164,14 @@ static void decrypt_adv_data_no_connect(const uint8_t *adv_data, uint8_t adv_len
|
||||
name_copy_end <= sizeof(dec_data) &&
|
||||
name_copy_end <= safe_dec_len) {
|
||||
memcpy(name, &dec_data[2], name_len);
|
||||
ESP_LOGI(TAG, "📛 Decrypted device name: \"%s\"", name);
|
||||
ESP_LOGI(TAG, "Decrypted device name: \"%s\"", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "❌ Decryption failed (rc=%d) - wrong key?", rc);
|
||||
ESP_LOGE(TAG, "Decryption failed (rc=%d) - wrong key?", rc);
|
||||
}
|
||||
return; /* Found and processed encrypted data */
|
||||
}
|
||||
@@ -194,8 +194,8 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param
|
||||
|
||||
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);
|
||||
ESP_LOGI(TAG, "Scanning started (no connection mode)");
|
||||
ESP_LOGI(TAG, "Looking for devices with UUID 0x%04X...", GAP_SERVICE_UUID);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Scan start failed: %d", param->scan_start_cmpl.status);
|
||||
}
|
||||
@@ -253,14 +253,14 @@ void app_main(void)
|
||||
/* 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_LOG_BUFFER_HEX(TAG, pre_shared_key.session_key, ESP_BLE_EAD_KEY_SIZE);
|
||||
ESP_LOGI(TAG, " IV:");
|
||||
ESP_LOG_BUFFER_HEX(TAG, pre_shared_key.iv, BLE_EAD_IV_SIZE);
|
||||
ESP_LOG_BUFFER_HEX(TAG, pre_shared_key.iv, ESP_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.");
|
||||
ESP_LOGI(TAG, "This example decrypts WITHOUT connecting!");
|
||||
ESP_LOGI(TAG, "Key must be pre-shared with peripheral.");
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
|
||||
# Enable SMP for security
|
||||
CONFIG_BT_BLE_SMP_ENABLE=y
|
||||
|
||||
# Encrypted Advertising Data APIs in Bluedroid host
|
||||
CONFIG_BT_BLE_FEAT_ENC_ADV_DATA=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
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
This example demonstrates how to use BLE Encrypted Advertising Data (EAD) feature with Bluedroid stack.
|
||||
|
||||
Encryption uses the host APIs in `esp_ble_ead.h` (`esp_ble_ead_encrypt`). Enable `CONFIG_BT_BLE_FEAT_ENC_ADV_DATA` (already set in `sdkconfig.defaults`).
|
||||
|
||||
## 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:
|
||||
@@ -30,7 +32,7 @@ The Encrypted Advertising Data feature (introduced in Bluetooth Core Specificati
|
||||
│ ┌──────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ BLE Advertising Packet │ │
|
||||
│ ├──────────┬─────────────┬────────────────┬────────────────────────────┤ │
|
||||
│ │ Flags │ Name "key" │ UUID 0x2C01 │ Encrypted Data (AD 0x31) │ │
|
||||
│ │ Flags │ Name "key" │ UUID 0x1800 │ Encrypted Data (AD 0x31) │ │
|
||||
│ │ (3B) │ (5B) │ (4B) │ (16B) │ │
|
||||
│ └──────────┴─────────────┴────────────────┴────────────────────────────┘ │
|
||||
│ │
|
||||
@@ -89,7 +91,7 @@ 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
|
||||
8 3 0x03 0x00 0x18 16-bit Service UUID: 0x1800
|
||||
12 16 0x31 [Encrypted Payload] Encrypted Advertising Data
|
||||
|
||||
Encrypted Payload Detail:
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
idf_component_register(SRCS "enc_adv_data_prph.c" "ble_ead.c"
|
||||
idf_component_register(SRCS "enc_adv_data_prph.c"
|
||||
INCLUDE_DIRS ".")
|
||||
|
||||
@@ -2,11 +2,13 @@ menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_ENABLE_KEY_MATERIAL
|
||||
bool "Enable Key Material characteristic in GAP Service"
|
||||
depends on BT_GATTS_ENABLE
|
||||
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.
|
||||
(UUID 0x2B88). This also enables Encrypted Advertising Data APIs
|
||||
(CONFIG_BT_BLE_FEAT_ENC_ADV_DATA).
|
||||
|
||||
This is the standard-compliant approach as defined in Bluetooth
|
||||
Core Specification Version 5.4.
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
* @param payload_capacity Size of @a payload in bytes; must be >=
|
||||
* 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, size_t payload_capacity);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BLE_EAD_H */
|
||||
@@ -30,7 +30,7 @@
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
#include "ble_ead.h"
|
||||
#include "esp_ble_ead.h"
|
||||
|
||||
#define TAG "ENC_ADV_PRPH"
|
||||
|
||||
@@ -48,7 +48,7 @@ static uint8_t unencrypted_adv_pattern[] = {
|
||||
};
|
||||
|
||||
/* Session key and IV for encryption - in real application, generate securely! */
|
||||
static ble_ead_key_material_t key_material = {
|
||||
static esp_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
|
||||
@@ -71,7 +71,7 @@ static esp_ble_adv_params_t adv_params = {
|
||||
};
|
||||
|
||||
/* Calculate encrypted payload size */
|
||||
#define ENCRYPTED_ADV_DATA_LEN BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(sizeof(unencrypted_adv_pattern))
|
||||
#define ENCRYPTED_ADV_DATA_LEN ESP_BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(sizeof(unencrypted_adv_pattern))
|
||||
|
||||
/**
|
||||
* @brief Encrypt advertising data and set raw advertising data
|
||||
@@ -80,16 +80,16 @@ static void set_encrypted_adv_data(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
uint8_t encrypted_adv_data[ENCRYPTED_ADV_DATA_LEN];
|
||||
int rc;
|
||||
esp_err_t 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) {
|
||||
rc = esp_ble_ead_encrypt(key_material.session_key, key_material.iv,
|
||||
unencrypted_adv_pattern, sizeof(unencrypted_adv_pattern),
|
||||
encrypted_adv_data);
|
||||
if (rc != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Encryption of adv data failed: %d", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
|
||||
# Enable SMP for security
|
||||
CONFIG_BT_BLE_SMP_ENABLE=y
|
||||
|
||||
# Encrypted Advertising Data APIs in Bluedroid host
|
||||
CONFIG_BT_BLE_FEAT_ENC_ADV_DATA=y
|
||||
CONFIG_BT_GATTS_KEY_MATERIAL_CHAR=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
|
||||
|
||||
@@ -32,8 +32,6 @@ KNOWN_MISSING = {
|
||||
'bluetooth/bluedroid/ble/ble_compatibility_test',
|
||||
'bluetooth/bluedroid/ble/ble_eddystone_receiver',
|
||||
'bluetooth/bluedroid/ble/ble_eddystone_sender',
|
||||
'bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent',
|
||||
'bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph',
|
||||
'bluetooth/bluedroid/ble/ble_hid_device_demo',
|
||||
'bluetooth/bluedroid/ble/ble_ibeacon',
|
||||
'bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent',
|
||||
|
||||
Reference in New Issue
Block a user