Merge branch 'feat/ble_smp_repairing_hardening' into 'master'

feat(ble/bluedroid): add Kconfig options for BLE re-pairing hardening

See merge request espressif/esp-idf!51221
This commit is contained in:
Island
2026-08-26 15:07:45 +08:00
12 changed files with 448 additions and 23 deletions

View File

@@ -507,6 +507,109 @@ config BT_BLE_SMP_BOND_NVS_FLASH
help
This select can save SMP bonding keys to nvs flash
config BT_BLE_SMP_HARDENED_REPAIRING
bool "Reject re-pairing that weakens an existing bond"
depends on BT_BLE_SMP_ENABLE
default y
help
Controls whether a peer that is already bonded may replace that bond with a
weaker one.
Enabled: when a bond already exists for the peer, a new pairing procedure is
refused if it would end up with less MITM protection, without Secure Connections
while the stored bond used it, or with a shorter encryption key. The security
level announced in a Security Request is also enforced on the pairing that
follows it. The link is dropped along with the pairing so that a peer cannot keep
retrying with different parameters. The drawback is that a peer which
legitimately needs to re-pair at a lower level is refused for good, for instance
one whose IO capabilities changed so that Passkey Entry became Just Works. Such a
peer has to be unbonded by the application first, using
esp_ble_remove_bond_device(). A refusal from this check always keeps the stored
bond, even when BT_BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL or
BT_BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_PERIPHERAL is enabled, so a peer cannot
erase the baseline by requesting a downgrade.
Disabled: any peer can replace an existing bond with one at a lower security
level, without Secure Connections, or with a shorter encryption key, and no
authentication is needed to do so. These are the re-pairing downgrades described
by the BLERP paper as V3, V4 and V6. Only choose this if a peer really has to
re-pair at a lower level and the application cannot unbond it beforehand.
config BT_BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL
bool "Remove the stored bond when pairing fails as Central"
depends on BT_BLE_SMP_ENABLE
default y
help
Controls whether the bonding keys kept in NVS are erased when a BLE pairing
procedure fails while the local device is the Central (link-layer master) on
that connection. A failed encryption attempt is reported through the same
authentication complete path and counts as a failure here. This is the role
typically used by GATT Client applications.
Enabled: historical Central behaviour and the default, so applications that rely
on a failed pairing implicitly unbonding the peer keep working after an upgrade.
It also means every peer able to make pairing fail can drop the bond without any
authentication, by simply sending a Pairing Failed, by letting the 30 second SMP
timeout expire, or by disconnecting in the middle of the procedure. As Central,
that implicit unbond is also what recovers when the peer deleted the bond on its
side and answers LL_ENC_REQ with KEY_MISSING.
Disabled: the bond survives a failed pairing in the Central role and unbonding
becomes an explicit application decision through esp_ble_remove_bond_device().
A stale LTK then makes every following connection fail to encrypt until the
application unbonds from the ESP_GAP_BLE_AUTH_CMPL_EVT failure callback,
preferably after a few attempts rather than on the first one.
A downgrade refused by BT_BLE_SMP_HARDENED_REPAIRING never erases the bond,
regardless of this option.
config BT_BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_PERIPHERAL
bool "Remove the stored bond when pairing fails as Peripheral"
depends on BT_BLE_SMP_ENABLE
default n
help
Controls whether the bonding keys kept in NVS are erased when a BLE pairing
procedure fails while the local device is the Peripheral (link-layer slave) on
that connection. A failed encryption attempt is reported through the same
authentication complete path and counts as a failure here. This is the role
typically used by GATT Server applications.
Disabled (default): the bond survives a failed pairing in the Peripheral role.
Unbonding is an explicit application decision through esp_ble_remove_bond_device().
This avoids losing the bond when the user walks away mid-pairing, when the SMP
timeout expires, or when a peer sends Pairing Failed. Legitimate re-pairing from
a Central that deleted its own bond is still possible through a new Pairing
Request, as long as BT_BLE_SMP_HARDENED_REPAIRING does not refuse a downgrade.
Enabled: restores the historical behaviour where any pairing failure as
Peripheral erases the NVS bond. Prefer this only if the application depends on
that implicit unbond and cannot call esp_ble_remove_bond_device() itself.
A downgrade refused by BT_BLE_SMP_HARDENED_REPAIRING never erases the bond,
regardless of this option.
config BT_BLE_SMP_UNBOND_ON_KEY_MISSING
bool "Drop the local LE keys when the peer reports it has no key"
depends on BT_BLE_SMP_ENABLE
default n
help
As Central, an encryption attempt using the stored LTK fails with "PIN or Key
Missing" when the peer has deleted the bond on its side. This option controls how
the stack reacts to that specific error.
Enabled: the local LE keys are discarded and the link is kept up, so the
application can re-pair straight away and recovers without user interaction. The
problem is that an impersonating device can report the same error, so a peer can
strip the stored security level without any authentication and then re-pair at a
weaker level. That is exactly the re-pairing downgrade the BLERP paper describes,
which is why this is not the default.
Disabled: the link is dropped and the keys are kept, so no peer can drop the bond
by refusing to encrypt. The problem is that the same stale LTK is then used again
on every following connection and encryption keeps failing, which never recovers
on its own. The application has to call esp_ble_remove_bond_device() from the
ESP_GAP_BLE_AUTH_CMPL_EVT failure callback to get out of it.
config BT_BLE_PERIPH_PSEUDO_ADDR_BOND
bool "Peripheral dual local-identity bond isolation (pseudo address)"
depends on BT_BLE_SMP_ENABLE && BT_BLE_50_EXTEND_ADV_EN

View File

@@ -5054,6 +5054,18 @@ static UINT8 bta_dm_ble_smp_cback (tBTM_LE_EVT event, BD_ADDR bda, tBTM_LE_EVT_D
bdcpy(sec_event.auth_cmpl.bd_addr, bda);
#if BLE_INCLUDED == TRUE
BTM_ReadDevInfo(bda, &sec_event.auth_cmpl.dev_type, &sec_event.auth_cmpl.addr_type);
{
/* BTM_GetRole() is BR/EDR-only; take the LE role from the ACL or sec record. */
tACL_CONN *p_acl = btm_bda_to_acl(bda, BT_TRANSPORT_LE);
if (p_acl != NULL) {
sec_event.auth_cmpl.is_central = (p_acl->link_role == HCI_ROLE_MASTER);
} else {
tBTM_SEC_DEV_REC *p_dev_rec = btm_find_dev(bda);
if (p_dev_rec != NULL) {
sec_event.auth_cmpl.is_central = p_dev_rec->role_master;
}
}
}
#endif
p_name = BTM_SecReadDevName(bda);
if (p_name != NULL) {
@@ -5062,11 +5074,38 @@ static UINT8 bta_dm_ble_smp_cback (tBTM_LE_EVT event, BD_ADDR bda, tBTM_LE_EVT_D
sec_event.auth_cmpl.bd_name[0] = '\0';
}
if (p_data->complt.reason != 0) {
BOOLEAN remove_bond = FALSE;
sec_event.auth_cmpl.fail_reason = BTA_DM_AUTH_CONVERT_SMP_CODE(((UINT8)p_data->complt.reason));
/* delete this device entry from Sec Dev DB */
APPL_TRACE_WARNING("%s remove bond,rsn %d, BDA:0x%02X%02X%02X%02X%02X%02X", __func__, sec_event.auth_cmpl.fail_reason,
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
bta_dm_remove_sec_dev_entry(bda);
sec_event.auth_cmpl.keep_bond = p_data->complt.keep_bond;
if (p_data->complt.keep_bond) {
/* Local policy refused the procedure (hardened re-pairing). The bond is
what the check protects, so leave the Sec Dev DB and NVS alone. */
APPL_TRACE_WARNING("%s keep bond after local refusal,rsn %d, BDA:0x%02X%02X%02X%02X%02X%02X",
__func__, sec_event.auth_cmpl.fail_reason,
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
} else if (sec_event.auth_cmpl.is_central) {
#if (BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL == TRUE)
remove_bond = TRUE;
#endif
} else {
#if (BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_PERIPHERAL == TRUE)
remove_bond = TRUE;
#endif
}
if (remove_bond) {
/* delete this device entry from Sec Dev DB */
APPL_TRACE_WARNING("%s remove bond,rsn %d, role=%s, BDA:0x%02X%02X%02X%02X%02X%02X",
__func__, sec_event.auth_cmpl.fail_reason,
sec_event.auth_cmpl.is_central ? "central" : "peripheral",
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
bta_dm_remove_sec_dev_entry(bda);
} else if (!p_data->complt.keep_bond) {
APPL_TRACE_WARNING("%s keep bond after pairing fail,rsn %d, role=%s, BDA:0x%02X%02X%02X%02X%02X%02X",
__func__, sec_event.auth_cmpl.fail_reason,
sec_event.auth_cmpl.is_central ? "central" : "peripheral",
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
}
} else {
sec_event.auth_cmpl.success = TRUE;
if (!p_data->complt.smp_over_br) {

View File

@@ -749,6 +749,12 @@ typedef struct {
tBT_DEVICE_TYPE dev_type;
UINT8 auth_mode;
BOOLEAN sc_support; /* Denotes if peer device supported secure connection while bonding. */
/* TRUE when the stack refused the procedure on purpose (hardened re-pairing)
and the existing bond must not be erased. */
BOOLEAN keep_bond;
/* TRUE when the local device was Central (HCI master) for this LE procedure.
Used with REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL / _AS_PERIPHERAL. */
BOOLEAN is_central;
} tBTA_DM_AUTH_CMPL;

View File

@@ -168,6 +168,8 @@ void btc_dm_get_ble_local_keys(tBTA_DM_BLE_LOCAL_KEY_MASK *p_key_mask, BT_OCTET1
}
#if (BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL == TRUE) || \
(BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_PERIPHERAL == TRUE)
static void btc_dm_remove_ble_bonding_keys(void)
{
bt_bdaddr_t bd_addr;
@@ -183,6 +185,16 @@ static void btc_dm_remove_ble_bonding_keys(void)
btc_storage_remove_ble_bonding_keys(&bd_addr);
}
/* Role-specific erase policy. keep_bond (hardened re-pairing refusal) always wins. */
static BOOLEAN btc_dm_ble_should_remove_bond_on_fail(BOOLEAN is_central)
{
if (is_central) {
return (BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL == TRUE);
}
return (BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_PERIPHERAL == TRUE);
}
#endif
#if BLE_SMP_BOND_NVS_FLASH
static void btc_dm_save_ble_bonding_keys(void)
{
@@ -284,24 +296,51 @@ static void btc_dm_ble_auth_cmpl_evt (tBTA_DM_AUTH_CMPL *p_auth_cmpl)
#endif
} else {
/*Map the HCI fail reason to bt status */
BOOLEAN remove_bond = FALSE;
switch (p_auth_cmpl->fail_reason) {
case BTA_DM_AUTH_SMP_PAIR_AUTH_FAIL:
case BTA_DM_AUTH_SMP_CONFIRM_VALUE_FAIL:
btc_dm_remove_ble_bonding_keys();
status = BT_STATUS_AUTH_FAILURE;
remove_bond = TRUE;
break;
case BTA_DM_AUTH_SMP_PAIR_NOT_SUPPORT:
status = BT_STATUS_AUTH_REJECTED;
break;
default:
BTC_TRACE_WARNING ("%s, remove bond in flash bd_addr: %08x%04x", __func__,
BTC_TRACE_WARNING ("%s, pairing failed for bd_addr: %08x%04x", __func__,
(p_auth_cmpl->bd_addr[0] << 24) + (p_auth_cmpl->bd_addr[1] << 16) + (p_auth_cmpl->bd_addr[2] << 8) + p_auth_cmpl->bd_addr[3],
(p_auth_cmpl->bd_addr[4] << 8) + p_auth_cmpl->bd_addr[5]);
btc_dm_remove_ble_bonding_keys();
status = BT_STATUS_FAIL;
remove_bond = TRUE;
break;
}
if (p_auth_cmpl->keep_bond) {
/* Hardened re-pairing refused this procedure; never erase the baseline. */
BTC_TRACE_WARNING("%s, keeping bond after local refusal, fail_reason=%d, role=%s, bd_addr: %08x%04x",
__func__, p_auth_cmpl->fail_reason,
p_auth_cmpl->is_central ? "central" : "peripheral",
(p_auth_cmpl->bd_addr[0] << 24) + (p_auth_cmpl->bd_addr[1] << 16) + (p_auth_cmpl->bd_addr[2] << 8) + p_auth_cmpl->bd_addr[3],
(p_auth_cmpl->bd_addr[4] << 8) + p_auth_cmpl->bd_addr[5]);
#if (BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL == TRUE) || \
(BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_PERIPHERAL == TRUE)
} else if (remove_bond && btc_dm_ble_should_remove_bond_on_fail(p_auth_cmpl->is_central)) {
BTC_TRACE_WARNING("remove NVS bond, rsn %d, role=%s, bd_addr: %08x%04x",
p_auth_cmpl->fail_reason,
p_auth_cmpl->is_central ? "central" : "peripheral",
(p_auth_cmpl->bd_addr[0] << 24) + (p_auth_cmpl->bd_addr[1] << 16) + (p_auth_cmpl->bd_addr[2] << 8) + p_auth_cmpl->bd_addr[3],
(p_auth_cmpl->bd_addr[4] << 8) + p_auth_cmpl->bd_addr[5]);
btc_dm_remove_ble_bonding_keys();
#endif
} else if (remove_bond) {
BTC_TRACE_WARNING("%s, keeping bond after pairing fail, role=%s, unbond with esp_ble_remove_bond_device(), bd_addr: %08x%04x",
__func__,
p_auth_cmpl->is_central ? "central" : "peripheral",
(p_auth_cmpl->bd_addr[0] << 24) + (p_auth_cmpl->bd_addr[1] << 16) + (p_auth_cmpl->bd_addr[2] << 8) + p_auth_cmpl->bd_addr[3],
(p_auth_cmpl->bd_addr[4] << 8) + p_auth_cmpl->bd_addr[5]);
}
}
#if (CONFIG_BT_STACK_NO_LOG)

View File

@@ -244,6 +244,30 @@
#define UC_BT_BLE_PERIPH_PSEUDO_ADDR_BOND FALSE
#endif
#ifdef CONFIG_BT_BLE_SMP_HARDENED_REPAIRING
#define UC_BT_BLE_SMP_HARDENED_REPAIRING CONFIG_BT_BLE_SMP_HARDENED_REPAIRING
#else
#define UC_BT_BLE_SMP_HARDENED_REPAIRING FALSE
#endif
#ifdef CONFIG_BT_BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL
#define UC_BT_BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL CONFIG_BT_BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL
#else
#define UC_BT_BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL FALSE
#endif
#ifdef CONFIG_BT_BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_PERIPHERAL
#define UC_BT_BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_PERIPHERAL CONFIG_BT_BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_PERIPHERAL
#else
#define UC_BT_BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_PERIPHERAL FALSE
#endif
#ifdef CONFIG_BT_BLE_SMP_UNBOND_ON_KEY_MISSING
#define UC_BT_BLE_SMP_UNBOND_ON_KEY_MISSING CONFIG_BT_BLE_SMP_UNBOND_ON_KEY_MISSING
#else
#define UC_BT_BLE_SMP_UNBOND_ON_KEY_MISSING FALSE
#endif
#ifdef CONFIG_BT_BLE_42_FEATURES_SUPPORTED
#define UC_BT_BLE_42_FEATURES_SUPPORTED CONFIG_BT_BLE_42_FEATURES_SUPPORTED
#else

View File

@@ -269,6 +269,34 @@
#define BLE_PERIPH_PSEUDO_ADDR_BOND FALSE
#endif
/* Refuse a re-pairing that would end up weaker than the bond it replaces. */
#if (UC_BT_BLE_SMP_HARDENED_REPAIRING == TRUE)
#define BLE_SMP_HARDENED_REPAIRING TRUE
#else
#define BLE_SMP_HARDENED_REPAIRING FALSE
#endif
/* Erase the NVS bond when pairing/encryption fails while local device is Central. */
#if (UC_BT_BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL == TRUE)
#define BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL TRUE
#else
#define BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL FALSE
#endif
/* Erase the NVS bond when pairing/encryption fails while local device is Peripheral. */
#if (UC_BT_BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_PERIPHERAL == TRUE)
#define BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_PERIPHERAL TRUE
#else
#define BLE_SMP_REMOVE_BOND_ON_PAIR_FAIL_AS_PERIPHERAL FALSE
#endif
/* Discard the local LE keys when the peer rejects encryption with "PIN or Key Missing". */
#if (UC_BT_BLE_SMP_UNBOND_ON_KEY_MISSING == TRUE)
#define BLE_SMP_UNBOND_ON_KEY_MISSING TRUE
#else
#define BLE_SMP_UNBOND_ON_KEY_MISSING FALSE
#endif
#if (UC_BT_BLE_ENABLED ==TRUE)
#if (UC_BT_BLE_42_FEATURES_SUPPORTED == TRUE || BLE_50_FEATURE_SUPPORT == FALSE)
#define BLE_42_FEATURE_SUPPORT TRUE

View File

@@ -4251,12 +4251,36 @@ void btm_sec_encrypt_change (UINT16 handle, UINT8 status, UINT8 encr_enable)
}
if (p_acl && p_acl->transport == BT_TRANSPORT_LE) {
if (status == HCI_ERR_KEY_MISSING || status == HCI_ERR_AUTH_FAILURE ||
status == HCI_ERR_ENCRY_MODE_NOT_ACCEPTABLE) {
p_dev_rec->sec_flags &= ~ (BTM_SEC_LE_LINK_KEY_KNOWN);
BOOLEAN disconnect = (status != HCI_SUCCESS);
BD_ADDR pseudo_addr;
/* btm_ble_link_encrypted() runs the SMP state machine and the upper layer
callbacks, which may retire p_dev_rec, so keep our own copy of the address. */
memcpy(pseudo_addr, p_dev_rec->ble.pseudo_addr, BD_ADDR_LEN);
#if (BLE_SMP_UNBOND_ON_KEY_MISSING == TRUE)
/* The peer answered our LL_ENC_REQ saying it no longer holds the LTK. Forget our
copy and keep the link up so the application can pair again right away. This is
unauthenticated, an impersonator can report the same error to strip the stored
security level, which is why the option defaults to disabled. */
if (disconnect && status == HCI_ERR_KEY_MISSING && p_acl->link_role == BTM_ROLE_MASTER) {
BTM_TRACE_WARNING("%s peer reports no LTK, dropping the local LE keys\n", __func__);
p_dev_rec->sec_flags &= ~(BTM_SEC_LE_LINK_KEY_KNOWN);
p_dev_rec->ble.key_type = BTM_LE_KEY_NONE;
disconnect = FALSE;
}
#endif ///BLE_SMP_UNBOND_ON_KEY_MISSING == TRUE
btm_ble_link_encrypted(pseudo_addr, encr_enable);
/* A peer that refuses to encrypt must not be able to drop our bond, otherwise it
can strip the stored security level and then re-pair at a weaker one. Tear the
link down instead and keep the keys until the application unbonds. */
if (disconnect && BTM_IsAclConnectionUp(pseudo_addr, BT_TRANSPORT_LE)) {
BTM_TRACE_WARNING("%s LE encryption failed (status 0x%02x), disconnecting\n",
__func__, status);
btm_remove_acl(pseudo_addr, BT_TRANSPORT_LE);
}
btm_ble_link_encrypted(p_dev_rec->ble.pseudo_addr, encr_enable);
return;
} else {
/* BR/EDR connection, update the encryption key size to be 16 as always */

View File

@@ -1817,6 +1817,9 @@ typedef struct {
BOOLEAN is_pair_cancel;
BOOLEAN smp_over_br;
tSMP_AUTH_REQ auth_mode;
/* Mirrors tSMP_CMPL.keep_bond; layouts must stay identical because
btm_proc_smp_cback casts tSMP_EVT_DATA to tBTM_LE_EVT_DATA. */
BOOLEAN keep_bond;
} tBTM_LE_COMPLT;
#endif

View File

@@ -226,6 +226,9 @@ typedef struct {
BOOLEAN is_pair_cancel;
BOOLEAN smp_over_br;
tSMP_AUTH_REQ auth_mode;
/* TRUE when the local stack refused the procedure (e.g. hardened re-pairing)
and the stored bond must be kept regardless of REMOVE_BOND_ON_PAIR_FAIL_AS_*. */
BOOLEAN keep_bond;
} tSMP_CMPL;
typedef struct {

View File

@@ -338,6 +338,12 @@ typedef struct {
UINT32 static_passkey;
BOOLEAN accept_specified_sec_auth;
tSMP_AUTH_REQ origin_loc_auth_req;
#if (BLE_INCLUDED == TRUE && BLE_SMP_HARDENED_REPAIRING == TRUE)
BOOLEAN sec_req_rcvd; /* peer asked for security through a Security Request */
tSMP_AUTH_REQ sec_req_auth_req; /* AuthReq of that Security Request, peer_auth_req gets
overwritten by the Pairing Response that follows */
BOOLEAN keep_bond_on_fail; /* set when smp_repairing_is_allowed() refuses the procedure */
#endif
} tSMP_CB;
/* Server Action functions are of this type */
@@ -487,6 +493,9 @@ extern BOOLEAN smp_encrypt_data (UINT8 *key, UINT8 key_len,
UINT8 *plain_text, UINT8 pt_len,
tSMP_ENC *p_out);
extern BOOLEAN smp_command_has_invalid_parameters(tSMP_CB *p_cb);
#if (BLE_INCLUDED == TRUE && BLE_SMP_HARDENED_REPAIRING == TRUE)
extern BOOLEAN smp_repairing_is_allowed(tSMP_CB *p_cb, UINT8 *p_reason);
#endif
extern void smp_reject_unexpected_pairing_command(BD_ADDR bd_addr);
extern tSMP_ASSO_MODEL smp_select_association_model(tSMP_CB *p_cb);
extern void smp_reverse_array(UINT8 *arr, UINT8 len);

View File

@@ -260,13 +260,9 @@ void smp_send_pair_req(tSMP_CB *p_cb, tSMP_INT_DATA *p_data)
{
SMP_TRACE_DEBUG("%s", __func__);
#if (BLE_INCLUDED == TRUE)
tBTM_SEC_DEV_REC *p_dev_rec = btm_find_dev (p_cb->pairing_bda);
/* erase all keys when master sends pairing req*/
if (p_dev_rec) {
btm_sec_clear_ble_keys(p_dev_rec);
}
#endif ///BLE_INCLUDED == TRUE
/* Any existing bond is replaced only once the new pairing is authenticated,
see smp_check_auth_req(). */
/* do not manipulate the key, let app decide,
leave out to BTM to mandate key distribution for bonding case */
smp_send_cmd(SMP_OPCODE_PAIRING_REQ, p_cb);
@@ -485,6 +481,13 @@ void smp_proc_sec_req(tSMP_CB *p_cb, tSMP_INT_DATA *p_data)
reason = SMP_PAIR_AUTH_FAIL;
smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason);
} else {
#if (BLE_INCLUDED == TRUE && BLE_SMP_HARDENED_REPAIRING == TRUE)
/* Remember what the peer asked for here: peer_auth_req is overwritten by the
Pairing Response that follows, and smp_repairing_is_allowed() has to compare
the two to catch a peer that announces a high level and then downgrades. */
p_cb->sec_req_rcvd = TRUE;
p_cb->sec_req_auth_req = auth_req;
#endif
/* initialize local i/r key to be default keys */
p_cb->peer_auth_req = auth_req;
p_cb->local_r_key = p_cb->local_i_key = SMP_SEC_DEFAULT_KEY ;
@@ -574,13 +577,11 @@ void smp_proc_pair_cmd(tSMP_CB *p_cb, tSMP_INT_DATA *p_data)
{
UINT8 *p = (UINT8 *)p_data;
UINT8 reason = SMP_ENC_KEY_SIZE;
tBTM_SEC_DEV_REC *p_dev_rec = btm_find_dev (p_cb->pairing_bda);
SMP_TRACE_DEBUG("%s", __func__);
/* erase all keys if it is slave proc pairing req*/
if (p_dev_rec && (p_cb->role == HCI_ROLE_SLAVE)) {
btm_sec_clear_ble_keys(p_dev_rec);
}
/* Any existing bond is replaced only once the new pairing is authenticated, see
smp_check_auth_req(). An unauthenticated Pairing Request must not drop the bond. */
p_cb->flags |= SMP_PAIR_FLAG_ENC_AFTER_PAIR;
@@ -596,6 +597,7 @@ void smp_proc_pair_cmd(tSMP_CB *p_cb, tSMP_INT_DATA *p_data)
smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason);
return;
}
p_cb->accept_specified_sec_auth = bta_dm_co_ble_get_accept_auth_enable();
p_cb->origin_loc_auth_req = bta_dm_co_ble_get_auth_req();
if (p_cb->role == HCI_ROLE_SLAVE) {
@@ -625,6 +627,15 @@ void smp_proc_pair_cmd(tSMP_CB *p_cb, tSMP_INT_DATA *p_data)
auth |= SMP_AUTH_GEN_BOND;
}
p_cb->auth_mode = auth;
#if (BLE_INCLUDED == TRUE && BLE_SMP_HARDENED_REPAIRING == TRUE)
if (!smp_repairing_is_allowed(p_cb, &reason)) {
if (BTM_IsAclConnectionUp(p_cb->pairing_bda, BT_TRANSPORT_LE)) {
btm_remove_acl (p_cb->pairing_bda, BT_TRANSPORT_LE);
}
smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason);
return;
}
#endif
if (p_cb->accept_specified_sec_auth) {
if ((auth & p_cb->origin_loc_auth_req) != p_cb->origin_loc_auth_req ) {
SMP_TRACE_ERROR("%s pairing failed - slave requires auth is 0x%x but peer auth is 0x%x local auth is 0x%x",
@@ -664,6 +675,15 @@ void smp_proc_pair_cmd(tSMP_CB *p_cb, tSMP_INT_DATA *p_data)
auth |= SMP_AUTH_GEN_BOND;
}
p_cb->auth_mode = auth;
#if (BLE_INCLUDED == TRUE && BLE_SMP_HARDENED_REPAIRING == TRUE)
if (!smp_repairing_is_allowed(p_cb, &reason)) {
if (BTM_IsAclConnectionUp(p_cb->pairing_bda, BT_TRANSPORT_LE)) {
btm_remove_acl (p_cb->pairing_bda, BT_TRANSPORT_LE);
}
smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason);
return;
}
#endif
if (p_cb->accept_specified_sec_auth) {
if ((auth & p_cb->origin_loc_auth_req) != p_cb->origin_loc_auth_req ) {
SMP_TRACE_ERROR("%s pairing failed - master requires auth is 0x%x but peer auth is 0x%x local auth is 0x%x",
@@ -1364,6 +1384,22 @@ void smp_check_auth_req(tSMP_CB *p_cb, tSMP_INT_DATA *p_data)
"(i-initiator r-responder)\n",
__func__, enc_enable, p_cb->local_i_key, p_cb->local_r_key);
if (enc_enable == 1) {
#if (BLE_INCLUDED == TRUE)
/* The new pairing has been authenticated and the link is now encrypted with the
freshly generated key, so the previous bond can be dropped. Doing it here rather
than when the Pairing Request is exchanged keeps the old key in place for a
pairing that never completes. */
tBTM_SEC_DEV_REC *p_dev_rec = btm_find_dev(p_cb->pairing_bda);
if (p_dev_rec) {
if (p_dev_rec->ble.key_type & (BTM_LE_KEY_PENC | BTM_LE_KEY_LENC)) {
SMP_TRACE_DEBUG("LE replace bond after enc, BDA:0x%02X%02X%02X%02X%02X%02X",
p_cb->pairing_bda[0], p_cb->pairing_bda[1], p_cb->pairing_bda[2],
p_cb->pairing_bda[3], p_cb->pairing_bda[4], p_cb->pairing_bda[5]);
}
btm_sec_clear_ble_keys(p_dev_rec);
}
#endif ///BLE_INCLUDED == TRUE
if (p_cb->le_secure_connections_mode_is_used) {
/* In LE SC mode LTK is used instead of STK and has to be always saved */
p_cb->local_i_key |= SMP_SEC_KEY_TYPE_ENC;
@@ -1587,6 +1623,15 @@ void smp_process_io_response(tSMP_CB *p_cb, tSMP_INT_DATA *p_data)
auth |= SMP_AUTH_GEN_BOND;
}
p_cb->auth_mode = auth;
#if (BLE_INCLUDED == TRUE && BLE_SMP_HARDENED_REPAIRING == TRUE)
if (!smp_repairing_is_allowed(p_cb, &reason)) {
if (BTM_IsAclConnectionUp(p_cb->pairing_bda, BT_TRANSPORT_LE)) {
btm_remove_acl (p_cb->pairing_bda, BT_TRANSPORT_LE);
}
smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &reason);
return;
}
#endif
if (p_cb->accept_specified_sec_auth) {
if ((auth & p_cb->origin_loc_auth_req) != p_cb->origin_loc_auth_req ) {
SMP_TRACE_ERROR("pairing failed - slave requires auth is 0x%x but peer auth is 0x%x local auth is 0x%x",

View File

@@ -1044,6 +1044,10 @@ void smp_proc_pairing_cmpl(tSMP_CB *p_cb)
evt_data.cmplt.reason = p_cb->status;
evt_data.cmplt.smp_over_br = p_cb->smp_over_br;
evt_data.cmplt.auth_mode = 0;
#if (BLE_INCLUDED == TRUE && BLE_SMP_HARDENED_REPAIRING == TRUE)
/* Copied before smp_reset_control_value() zeros the control block. */
evt_data.cmplt.keep_bond = p_cb->keep_bond_on_fail;
#endif
#if (BLE_INCLUDED == TRUE)
tBTM_SEC_DEV_REC *p_rec = btm_find_dev (p_cb->pairing_bda);
if (p_cb->status == SMP_SUCCESS) {
@@ -1285,6 +1289,104 @@ BOOLEAN smp_parameter_unconditionally_invalid(tSMP_CB *p_cb)
return FALSE;
}
#if (BLE_INCLUDED == TRUE && BLE_SMP_HARDENED_REPAIRING == TRUE)
/*******************************************************************************
**
** Function smp_repairing_is_allowed
**
** Description Called once the association model of a pairing procedure is known.
** First pairing (no stored bond) is always allowed. On re-pairing,
** refuses when the peer drops AuthReq bits it announced in a Security
** Request, when the association model would weaken MITM/SC relative to
** the stored bond, or when the encryption key would get shorter.
**
** Returns TRUE when the pairing may continue. Otherwise FALSE, and *p_reason
** holds the SMP failure code to report to the peer. Also sets
** p_cb->keep_bond_on_fail so BTA/BTC keep the stored bond even when
** REMOVE_BOND_ON_PAIR_FAIL_AS_CENTRAL / _AS_PERIPHERAL is enabled. The
** caller should drop the link so the peer cannot retry with other
** parameters until one gets through.
**
*******************************************************************************/
BOOLEAN smp_repairing_is_allowed(tSMP_CB *p_cb, UINT8 *p_reason)
{
const UINT16 level_bits = SMP_AUTH_YN_BIT | SMP_SC_SUPPORT_BIT;
tBTM_SEC_DEV_REC *p_dev_rec;
UINT16 new_auth = p_cb->auth_mode;
UINT16 sec_req_level;
UINT16 old_auth;
UINT8 new_key_size;
p_dev_rec = btm_find_dev(p_cb->pairing_bda);
if (p_dev_rec == NULL ||
!(p_dev_rec->ble.key_type & (BTM_LE_KEY_PENC | BTM_LE_KEY_LENC))) {
/* First pairing with this peer, there is nothing to downgrade. A Security
Request that outruns what IO capabilities can deliver must not block it. */
SMP_TRACE_DEBUG("LE first pairing, skip downgrade check, BDA:0x%02X%02X%02X%02X%02X%02X",
p_cb->pairing_bda[0], p_cb->pairing_bda[1], p_cb->pairing_bda[2],
p_cb->pairing_bda[3], p_cb->pairing_bda[4], p_cb->pairing_bda[5]);
return TRUE;
}
/* A Security Request carries no authentication, but the pairing command that
follows must still claim the level it announced, otherwise the peer can
advertise a high level to force a re-pairing and then drop those bits in the
Pairing Response. Compare against peer_auth_req (the pairing command), not
against the association-model result: IO capabilities that force Just Works
are a local limitation, not a peer AuthReq downgrade. Only bits this side
also asked for are enforced. */
sec_req_level = p_cb->sec_req_auth_req & p_cb->loc_auth_req & level_bits;
if (p_cb->sec_req_rcvd &&
((p_cb->peer_auth_req & sec_req_level) != sec_req_level)) {
SMP_TRACE_ERROR("LE re-pair refuse: SR 0x%02x pair 0x%02x loc 0x%02x, BDA:0x%02X%02X%02X%02X%02X%02X",
p_cb->sec_req_auth_req, p_cb->peer_auth_req, p_cb->loc_auth_req,
p_cb->pairing_bda[0], p_cb->pairing_bda[1], p_cb->pairing_bda[2],
p_cb->pairing_bda[3], p_cb->pairing_bda[4], p_cb->pairing_bda[5]);
*p_reason = SMP_PAIR_AUTH_FAIL;
/* Keep the existing bond: this is a local policy refusal, not a peer that
proved the stored keys are gone. BTC must not erase NVS on this path. */
p_cb->keep_bond_on_fail = TRUE;
return FALSE;
}
old_auth = p_dev_rec->ble.auth_mode;
/* Bonds created before auth_mode was recorded, or restored from an older NVS layout,
only carry the security level. */
if (p_dev_rec->ble.keys.sec_level >= SMP_SEC_AUTHENTICATED) {
old_auth |= SMP_AUTH_YN_BIT;
}
if ((new_auth & old_auth & level_bits) != (old_auth & level_bits)) {
SMP_TRACE_ERROR("LE re-pair refuse: auth 0x%02x < bonded 0x%02x, BDA:0x%02X%02X%02X%02X%02X%02X",
new_auth, old_auth,
p_cb->pairing_bda[0], p_cb->pairing_bda[1], p_cb->pairing_bda[2],
p_cb->pairing_bda[3], p_cb->pairing_bda[4], p_cb->pairing_bda[5]);
*p_reason = SMP_PAIR_AUTH_FAIL;
p_cb->keep_bond_on_fail = TRUE;
return FALSE;
}
new_key_size = (p_cb->loc_enc_size < p_cb->peer_enc_size) ? p_cb->loc_enc_size
: p_cb->peer_enc_size;
if (new_key_size < p_dev_rec->ble.keys.key_size) {
SMP_TRACE_ERROR("LE re-pair refuse: key size %d < bonded %d, BDA:0x%02X%02X%02X%02X%02X%02X",
new_key_size, p_dev_rec->ble.keys.key_size,
p_cb->pairing_bda[0], p_cb->pairing_bda[1], p_cb->pairing_bda[2],
p_cb->pairing_bda[3], p_cb->pairing_bda[4], p_cb->pairing_bda[5]);
*p_reason = SMP_ENC_KEY_SIZE;
p_cb->keep_bond_on_fail = TRUE;
return FALSE;
}
SMP_TRACE_DEBUG("LE re-pair allowed: auth 0x%02x->0x%02x key %d->%d, BDA:0x%02X%02X%02X%02X%02X%02X",
old_auth, new_auth, p_dev_rec->ble.keys.key_size, new_key_size,
p_cb->pairing_bda[0], p_cb->pairing_bda[1], p_cb->pairing_bda[2],
p_cb->pairing_bda[3], p_cb->pairing_bda[4], p_cb->pairing_bda[5]);
return TRUE;
}
#endif ///BLE_INCLUDED == TRUE && BLE_SMP_HARDENED_REPAIRING == TRUE
/*******************************************************************************
**
** Function smp_reject_unexpected_pairing_command