Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions devices/ble_hci/common-hal/_bleio/Connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,18 @@ bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self) {
return self->connection->conn_handle != BLE_CONN_HANDLE_INVALID;
}

bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) {
return false;
}

mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self) {
return mp_const_none;
}

void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) {
mp_raise_NotImplementedError(NULL);
}

void common_hal_bleio_connection_disconnect(bleio_connection_internal_t *self) {
hci_disconnect(self->conn_handle);
}
Expand Down
33 changes: 19 additions & 14 deletions locale/circuitpython.pot
Original file line number Diff line number Diff line change
Expand Up @@ -1180,11 +1180,6 @@ msgstr ""
msgid "Timeout is too long: Maximum timeout length is %d seconds"
msgstr ""

#: ports/espressif/common-hal/_bleio/Characteristic.c
#: ports/espressif/common-hal/_bleio/Descriptor.c
msgid "MITM security not supported"
msgstr ""

#: ports/espressif/common-hal/_bleio/Characteristic.c
#: ports/nordic/common-hal/_bleio/Characteristic.c
#: ports/nordic/common-hal/_bleio/Descriptor.c
Expand Down Expand Up @@ -1213,6 +1208,20 @@ msgstr ""
msgid "Can't set CCCD on local Characteristic"
msgstr ""

#: ports/espressif/common-hal/_bleio/Connection.c
#: ports/espressif/common-hal/_bleio/__init__.c
#: ports/nordic/common-hal/_bleio/__init__.c
#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c
#: ports/zephyr-cp/common-hal/_bleio/Connection.c
#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c
#: shared-bindings/_bleio/CharacteristicBuffer.c
msgid "Not connected"
msgstr ""

#: ports/espressif/common-hal/_bleio/Connection.c
msgid "No pairing in progress"
msgstr ""

#: ports/espressif/common-hal/_bleio/Connection.c
#: ports/nordic/common-hal/_bleio/Connection.c
msgid "non-UUID found in service_uuids_whitelist"
Expand Down Expand Up @@ -1246,15 +1255,6 @@ msgstr ""
msgid "Invalid BLE parameter"
msgstr ""

#: ports/espressif/common-hal/_bleio/__init__.c
#: ports/nordic/common-hal/_bleio/__init__.c
#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c
#: ports/zephyr-cp/common-hal/_bleio/Connection.c
#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c
#: shared-bindings/_bleio/CharacteristicBuffer.c
msgid "Not connected"
msgstr ""

#: ports/espressif/common-hal/_bleio/__init__.c
#: shared-module/audiofilewriter/AudioFileWriter.c
msgid "Already in progress"
Expand Down Expand Up @@ -1728,6 +1728,11 @@ msgstr ""
msgid "Failed to connect: timeout"
msgstr ""

#: ports/nordic/common-hal/_bleio/Connection.c
#: ports/zephyr-cp/common-hal/_bleio/Connection.c
msgid "Numeric comparison pairing"
msgstr ""

#: ports/nordic/common-hal/_bleio/UUID.c
msgid "Unexpected nrfx uuid type"
msgstr ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ QSPI_FLASH_FILESYSTEM = 1
EXTERNAL_FLASH_DEVICES = "W25Q64JVxQ, S25FL064L"
LONGINT_IMPL = MPZ

CIRCUITPY_AESIO = 0
CIRCUITPY_I2CTARGET = 0
CIRCUITPY_PS2IO = 1
CIRCUITPY_JPEGIO = 0
Expand Down
1 change: 1 addition & 0 deletions ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ QSPI_FLASH_FILESYSTEM = 1
EXTERNAL_FLASH_DEVICES = "GD25Q16C, W25Q16JVxQ, W25Q16JVxM"
LONGINT_IMPL = MPZ

CIRCUITPY_AESIO = 0
CIRCUITPY_I2CTARGET = 0
CIRCUITPY_JPEGIO = 0
CIRCUITPY_SPITARGET = 0
Expand Down
18 changes: 17 additions & 1 deletion ports/espressif/common-hal/_bleio/Adapter.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "nimble/nimble_port_freertos.h"
#include "host/ble_gap.h"
#include "host/ble_gatt.h"
#include "host/ble_sm.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
Expand Down Expand Up @@ -145,7 +146,13 @@ void common_hal_bleio_adapter_set_enabled(bleio_adapter_obj_t *self, bool enable
ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC;

ble_hs_cfg.sm_mitm = 0;
ble_hs_cfg.sm_sc = 0;
// LE Secure Connections is always offered. It's negotiated per pairing (a peer
// that only does legacy pairing still works) and is strictly better crypto, so
// there's no downside for existing "Just Works" users. Numeric-comparison pairing
// needs it. IO capability and the MITM requirement stay at the legacy defaults
// above until an attribute is constructed with a *_WITH_MITM permission, which
// raises them - see bleio_adapter_enable_mitm_pairing().
ble_hs_cfg.sm_sc = 1;
/* Stores the IRK */
ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ID;
ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ID;
Expand Down Expand Up @@ -197,6 +204,15 @@ bool common_hal_bleio_adapter_get_enabled(bleio_adapter_obj_t *self) {
return xTaskGetHandle("nimble_host") != NULL;
}

void bleio_adapter_enable_mitm_pairing(void) {
// ble_hs_cfg is a plain global read by the SM code at pairing time, so bumping it
// here (from Characteristic / Descriptor construction, after the adapter is up) takes
// effect for every subsequent pairing. Idempotent and one-way for the lifetime of
// the adapter - a characteristic that needs authentication doesn't stop needing it.
ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_DISP_YES_NO;
ble_hs_cfg.sm_mitm = 1;
}

bleio_address_obj_t *common_hal_bleio_adapter_get_address(bleio_adapter_obj_t *self) {
uint8_t address_bytes[6];
uint8_t address_type = BLE_ADDR_RANDOM;
Expand Down
7 changes: 7 additions & 0 deletions ports/espressif/common-hal/_bleio/Adapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,10 @@ typedef struct {

void bleio_adapter_gc_collect(bleio_adapter_obj_t *adapter);
void bleio_adapter_reset(bleio_adapter_obj_t *adapter);

// Raise the SM pairing configuration so an authenticated (MITM-protected) bond can be
// formed: advertise DISPLAY_YESNO IO capability and require MITM. Called from
// Characteristic / Descriptor construction when an attribute is given a *_WITH_MITM
// permission. A no-op-safe idempotent bump; devices with no such attribute keep the
// legacy "Just Works" defaults set in common_hal_bleio_adapter_set_enabled().
void bleio_adapter_enable_mitm_pairing(void);
20 changes: 14 additions & 6 deletions ports/espressif/common-hal/_bleio/Characteristic.c
Original file line number Diff line number Diff line change
Expand Up @@ -153,29 +153,37 @@ void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self,
if ((props & CHAR_PROP_WRITE_NO_RESPONSE) != 0) {
self->flags |= BLE_GATT_CHR_F_WRITE_NO_RSP;
}
if (read_perm == SECURITY_MODE_ENC_WITH_MITM || write_perm == SECURITY_MODE_ENC_WITH_MITM ||
read_perm == SECURITY_MODE_SIGNED_WITH_MITM || write_perm == SECURITY_MODE_SIGNED_WITH_MITM) {
mp_raise_NotImplementedError(MP_ERROR_TEXT("MITM security not supported"));
}
// The BLE_GATT_CHR_F_NOTIFY_INDICATE_* flags are set below to require encryption or
// authentication when writing the auto-generated CCCD, if reading the
// characteristic requires it. This matches the nordic port behavior.
// Without the flags, NimBLE registers the CCCD as writable on an unencrypted link,
// so an unpaired central can subscribe and nothing ever requires it to pair.
//
// TODO: This behavior was fixed in NimBLE 1.10.0. ESP-IDF 6.0.1 uses a fork of NimBLE.
//
// NimBLE's GATT layer has no dedicated "LESC" or "signed" characteristic flag:
// ENC_WITH_MITM, LESC_ENC_WITH_MITM and the SIGNED modes all map to the _AUTHEN
// flags, i.e. "require a man-in-the-middle-protected link". A *_WITH_MITM permission
// additionally makes the adapter offer numeric-comparison pairing (below) so such a
// link can actually be established.
if (read_perm == SECURITY_MODE_ENC_NO_MITM) {
self->flags |= BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
}
if (read_perm == SECURITY_MODE_SIGNED_NO_MITM) {
if (read_perm == SECURITY_MODE_ENC_WITH_MITM || read_perm == SECURITY_MODE_LESC_ENC_WITH_MITM ||
read_perm == SECURITY_MODE_SIGNED_NO_MITM || read_perm == SECURITY_MODE_SIGNED_WITH_MITM) {
self->flags |= BLE_GATT_CHR_F_READ_AUTHEN | BLE_GATT_CHR_F_NOTIFY_INDICATE_AUTHEN;
}
if (write_perm == SECURITY_MODE_ENC_NO_MITM) {
self->flags |= BLE_GATT_CHR_F_WRITE_ENC;
}
if (write_perm == SECURITY_MODE_SIGNED_NO_MITM) {
if (write_perm == SECURITY_MODE_ENC_WITH_MITM || write_perm == SECURITY_MODE_LESC_ENC_WITH_MITM ||
write_perm == SECURITY_MODE_SIGNED_NO_MITM || write_perm == SECURITY_MODE_SIGNED_WITH_MITM) {
self->flags |= BLE_GATT_CHR_F_WRITE_AUTHEN;
}
if (bleio_attribute_security_mode_requires_mitm(read_perm) ||
bleio_attribute_security_mode_requires_mitm(write_perm)) {
bleio_adapter_enable_mitm_pairing();
}

// If max_length is 0, then no storage is allocated.
if (max_length > 0) {
Expand Down
64 changes: 64 additions & 0 deletions ports/espressif/common-hal/_bleio/Connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "freertos/queue.h"

#include "host/ble_att.h"
#include "host/ble_sm.h"
#include "host/ble_store.h"

// Uncomment to turn on debug logging just in this file.
Expand All @@ -47,6 +48,7 @@ int bleio_connection_event_cb(struct ble_gap_event *event, void *connection_in)
case BLE_GAP_EVENT_DISCONNECT: {
connection->conn_handle = BLEIO_HANDLE_INVALID;
connection->pair_status = PAIR_NOT_PAIRED;
connection->mitm_protected = false;

#if CIRCUITPY_VERBOSE_BLE
mp_printf(&mp_plat_print, "event->disconnect.reason: 0x%x\n", event->disconnect.reason);
Expand Down Expand Up @@ -79,6 +81,34 @@ int bleio_connection_event_cb(struct ble_gap_event *event, void *connection_in)
ble_gap_conn_find(event->enc_change.conn_handle, &desc);
if (desc.sec_state.encrypted) {
connection->pair_status = PAIR_PAIRED;
} else if (connection->pair_status == PAIR_WAITING_NUMCMP) {
// Numeric comparison was rejected, or SM otherwise failed.
connection->pair_status = PAIR_NOT_PAIRED;
}
// .authenticated is set once the MITM leg (numeric comparison / passkey entry)
// completes; a plain "Just Works" bond leaves it clear. Surfaced as
// Connection.authenticated.
connection->mitm_protected = desc.sec_state.authenticated;
break;
}
case BLE_GAP_EVENT_PASSKEY_ACTION: {
// The peer started MITM pairing. An attribute was constructed with a
// *_WITH_MITM permission, which made the adapter advertise DISPLAY_YESNO IO
// capability (bleio_adapter_enable_mitm_pairing()), so NimBLE asks us to
// confirm a 6-digit numeric-comparison value. Stash it and enter
// PAIR_WAITING_NUMCMP; Python reads it via Connection.numeric_comparison
// and answers with confirm_pairing().
if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) {
connection->pairing_numcmp = event->passkey.params.numcmp;
connection->pair_status = PAIR_WAITING_NUMCMP;
} else {
// Passkey entry / OOB: a display + yes/no button can't service these.
// Fail fast instead of letting SM hang until its ~30 s timeout.
#if CIRCUITPY_VERBOSE_BLE
mp_printf(&mp_plat_print, "unsupported passkey action %d; terminating\n",
event->passkey.params.action);
#endif
ble_gap_terminate(event->passkey.conn_handle, BLE_ERR_AUTH_FAIL);
}
break;
}
Expand Down Expand Up @@ -167,6 +197,40 @@ void common_hal_bleio_connection_pair(bleio_connection_internal_t *self, bool bo
}
}

// LE Secure Connections numeric comparison. Unlike pair(), these don't block: the peer
// (a central) drives the SM procedure; we just observe the pending value and inject the
// yes/no. BLE_GAP_EVENT_PASSKEY_ACTION sets pair_status = PAIR_WAITING_NUMCMP.
mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self) {
if (self->connection == NULL || self->connection->pair_status != PAIR_WAITING_NUMCMP) {
return mp_const_none;
}
return mp_obj_new_int_from_uint(self->connection->pairing_numcmp);
}

void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) {
if (self->connection == NULL) {
mp_raise_ConnectionError(MP_ERROR_TEXT("Not connected"));
}
if (self->connection->pair_status != PAIR_WAITING_NUMCMP) {
mp_raise_bleio_BluetoothError(MP_ERROR_TEXT("No pairing in progress"));
}
struct ble_sm_io io = {
.action = BLE_SM_IOACT_NUMCMP,
.numcmp_accept = accept,
};
CHECK_NIMBLE_ERROR(ble_sm_inject_io(self->connection->conn_handle, &io));
// SM proceeds from here. On accept, ENC_CHANGE moves us to PAIR_PAIRED; on reject it
// won't, so drop straight back to PAIR_NOT_PAIRED rather than waiting for the peer.
self->connection->pair_status = accept ? PAIR_WAITING : PAIR_NOT_PAIRED;
}

bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) {
if (self->connection == NULL) {
return false;
}
return self->connection->mitm_protected;
}

mp_float_t common_hal_bleio_connection_get_connection_interval(bleio_connection_internal_t *self) {
while (self->conn_params_updating && !mp_hal_is_interrupted()) {
RUN_BACKGROUND_TASKS;
Expand Down
12 changes: 12 additions & 0 deletions ports/espressif/common-hal/_bleio/Connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
typedef enum {
PAIR_NOT_PAIRED,
PAIR_WAITING,
// The peer is running LE Secure Connections numeric-comparison pairing and NimBLE is
// waiting for us to confirm the 6-digit value. Python reads it from
// Connection.numeric_comparison and answers with confirm_pairing(). This is a
// peer-driven, non-blocking path; the blocking pair() does not use it.
PAIR_WAITING_NUMCMP,
PAIR_PAIRED,
} pair_status_t;

Expand Down Expand Up @@ -50,6 +55,13 @@ typedef struct {
uint16_t ediv;
volatile pair_status_t pair_status;
uint8_t sec_status; // Internal security status.
// The 6-digit LE Secure Connections numeric-comparison value stashed by
// BLE_GAP_EVENT_PASSKEY_ACTION. Only valid while pair_status == PAIR_WAITING_NUMCMP.
volatile uint32_t pairing_numcmp;
// True once the link is encrypted AND authenticated against MITM (numeric comparison
// or passkey entry completed). A plain encrypted "Just Works" link leaves this false.
// Surfaced to Python as Connection.authenticated.
volatile bool mitm_protected;
mp_obj_t connection_obj;
volatile bool conn_params_updating;
uint16_t mtu;
Expand Down
18 changes: 12 additions & 6 deletions ports/espressif/common-hal/_bleio/Descriptor.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#include "shared-bindings/_bleio/Service.h"
#include "shared-bindings/_bleio/UUID.h"

#include "common-hal/_bleio/Adapter.h"

#include "host/ble_att.h"

void common_hal_bleio_descriptor_construct(bleio_descriptor_obj_t *self, bleio_characteristic_obj_t *characteristic, bleio_uuid_obj_t *uuid, bleio_attribute_security_mode_t read_perm, bleio_attribute_security_mode_t write_perm, mp_int_t max_length, bool fixed_length, mp_buffer_info_t *initial_value_bufinfo) {
Expand All @@ -33,22 +35,26 @@ void common_hal_bleio_descriptor_construct(bleio_descriptor_obj_t *self, bleio_c
if (write_perm != SECURITY_MODE_NO_ACCESS) {
self->flags |= BLE_ATT_F_WRITE;
}
if (read_perm == SECURITY_MODE_ENC_WITH_MITM || write_perm == SECURITY_MODE_ENC_WITH_MITM ||
read_perm == SECURITY_MODE_SIGNED_WITH_MITM || write_perm == SECURITY_MODE_SIGNED_WITH_MITM) {
mp_raise_NotImplementedError(MP_ERROR_TEXT("MITM security not supported"));
}
// MITM / LESC-MITM / SIGNED all map to the _AUTHEN flags (require an authenticated
// link); see the matching block in Characteristic.c.
if (read_perm == SECURITY_MODE_ENC_NO_MITM) {
self->flags |= BLE_ATT_F_READ_ENC;
}
if (read_perm == SECURITY_MODE_SIGNED_NO_MITM) {
if (read_perm == SECURITY_MODE_ENC_WITH_MITM || read_perm == SECURITY_MODE_LESC_ENC_WITH_MITM ||
read_perm == SECURITY_MODE_SIGNED_NO_MITM || read_perm == SECURITY_MODE_SIGNED_WITH_MITM) {
self->flags |= BLE_ATT_F_READ_AUTHEN;
}
if (write_perm == SECURITY_MODE_ENC_NO_MITM) {
self->flags |= BLE_ATT_F_WRITE_ENC;
}
if (write_perm == SECURITY_MODE_SIGNED_NO_MITM) {
if (write_perm == SECURITY_MODE_ENC_WITH_MITM || write_perm == SECURITY_MODE_LESC_ENC_WITH_MITM ||
write_perm == SECURITY_MODE_SIGNED_NO_MITM || write_perm == SECURITY_MODE_SIGNED_WITH_MITM) {
self->flags |= BLE_ATT_F_WRITE_AUTHEN;
}
if (bleio_attribute_security_mode_requires_mitm(read_perm) ||
bleio_attribute_security_mode_requires_mitm(write_perm)) {
bleio_adapter_enable_mitm_pairing();
}

const mp_int_t max_length_max = BLE_ATT_ATTR_MAX_LEN;
if (max_length < 0 || max_length > max_length_max) {
Expand Down
1 change: 1 addition & 0 deletions ports/nordic/boards/bluemicro840/mpconfigboard.mk
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ USB_MANUFACTURER = "nrf52.jpconstantineau.com"
MCU_CHIP = nrf52840

INTERNAL_FLASH_FILESYSTEM = 1
CIRCUITPY_AESIO = 0
1 change: 1 addition & 0 deletions ports/nordic/boards/electronut_labs_blip/mpconfigboard.mk
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ CIRCUITPY_AUDIOIO = 0
CIRCUITPY_DISPLAYIO = 1
CIRCUITPY_STAGE = 1
CIRCUITPY_DIGITALINOUT_PROTOCOL = 0
CIRCUITPY_AESIO = 0
15 changes: 15 additions & 0 deletions ports/nordic/common-hal/_bleio/Connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,21 @@ bool common_hal_bleio_connection_get_paired(bleio_connection_obj_t *self) {
return self->connection->pair_status == PAIR_PAIRED;
}

// Authenticated (MITM-protected) pairing - numeric comparison - is implemented on the
// espressif port only. This port can't tell a "Just Works" bond from an authenticated
// one, so report the conservative answer rather than a guarantee it can't make.
bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) {
return false;
}

mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self) {
return mp_const_none;
}

void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) {
mp_raise_NotImplementedError(NULL);
}

bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self) {
if (self->connection == NULL) {
return false;
Expand Down
Loading
Loading