diff --git a/devices/ble_hci/common-hal/_bleio/Connection.c b/devices/ble_hci/common-hal/_bleio/Connection.c index 30ed0af575c..028b68796b5 100644 --- a/devices/ble_hci/common-hal/_bleio/Connection.c +++ b/devices/ble_hci/common-hal/_bleio/Connection.c @@ -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); } diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index c55efbcf8c1..df114f0adbf 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -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 @@ -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" @@ -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" @@ -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 "" diff --git a/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk b/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk index 5c1cab422cb..1895f74f31b 100755 --- a/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk +++ b/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk @@ -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 diff --git a/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk b/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk index 2ad140094b3..4289a8799d9 100644 --- a/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk +++ b/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk @@ -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 diff --git a/ports/espressif/common-hal/_bleio/Adapter.c b/ports/espressif/common-hal/_bleio/Adapter.c index e97c84b9b95..34122a1048c 100644 --- a/ports/espressif/common-hal/_bleio/Adapter.c +++ b/ports/espressif/common-hal/_bleio/Adapter.c @@ -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" @@ -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; @@ -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; diff --git a/ports/espressif/common-hal/_bleio/Adapter.h b/ports/espressif/common-hal/_bleio/Adapter.h index 40f24cfcdfe..31bd853f2b4 100644 --- a/ports/espressif/common-hal/_bleio/Adapter.h +++ b/ports/espressif/common-hal/_bleio/Adapter.h @@ -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); diff --git a/ports/espressif/common-hal/_bleio/Characteristic.c b/ports/espressif/common-hal/_bleio/Characteristic.c index 9436376a811..6ea78629569 100644 --- a/ports/espressif/common-hal/_bleio/Characteristic.c +++ b/ports/espressif/common-hal/_bleio/Characteristic.c @@ -153,10 +153,6 @@ 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. @@ -164,18 +160,30 @@ void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self, // 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) { diff --git a/ports/espressif/common-hal/_bleio/Connection.c b/ports/espressif/common-hal/_bleio/Connection.c index a063f3ee1a8..ff8a4402001 100644 --- a/ports/espressif/common-hal/_bleio/Connection.c +++ b/ports/espressif/common-hal/_bleio/Connection.c @@ -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. @@ -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); @@ -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; } @@ -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; diff --git a/ports/espressif/common-hal/_bleio/Connection.h b/ports/espressif/common-hal/_bleio/Connection.h index 8d8e727e3da..143cc64d28a 100644 --- a/ports/espressif/common-hal/_bleio/Connection.h +++ b/ports/espressif/common-hal/_bleio/Connection.h @@ -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; @@ -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; diff --git a/ports/espressif/common-hal/_bleio/Descriptor.c b/ports/espressif/common-hal/_bleio/Descriptor.c index 3f2870740f1..617e862206f 100644 --- a/ports/espressif/common-hal/_bleio/Descriptor.c +++ b/ports/espressif/common-hal/_bleio/Descriptor.c @@ -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) { @@ -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) { diff --git a/ports/nordic/boards/bluemicro840/mpconfigboard.mk b/ports/nordic/boards/bluemicro840/mpconfigboard.mk index 16d36704a3e..93b8ac56fa6 100644 --- a/ports/nordic/boards/bluemicro840/mpconfigboard.mk +++ b/ports/nordic/boards/bluemicro840/mpconfigboard.mk @@ -6,3 +6,4 @@ USB_MANUFACTURER = "nrf52.jpconstantineau.com" MCU_CHIP = nrf52840 INTERNAL_FLASH_FILESYSTEM = 1 +CIRCUITPY_AESIO = 0 diff --git a/ports/nordic/boards/electronut_labs_blip/mpconfigboard.mk b/ports/nordic/boards/electronut_labs_blip/mpconfigboard.mk index fc59a101e45..4ead814dcab 100644 --- a/ports/nordic/boards/electronut_labs_blip/mpconfigboard.mk +++ b/ports/nordic/boards/electronut_labs_blip/mpconfigboard.mk @@ -12,3 +12,4 @@ CIRCUITPY_AUDIOIO = 0 CIRCUITPY_DISPLAYIO = 1 CIRCUITPY_STAGE = 1 CIRCUITPY_DIGITALINOUT_PROTOCOL = 0 +CIRCUITPY_AESIO = 0 diff --git a/ports/nordic/common-hal/_bleio/Connection.c b/ports/nordic/common-hal/_bleio/Connection.c index fbd3263b725..e34aa56c4e2 100644 --- a/ports/nordic/common-hal/_bleio/Connection.c +++ b/ports/nordic/common-hal/_bleio/Connection.c @@ -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; diff --git a/ports/silabs/common-hal/_bleio/Connection.c b/ports/silabs/common-hal/_bleio/Connection.c index 94e556dd0bf..4f5da679931 100644 --- a/ports/silabs/common-hal/_bleio/Connection.c +++ b/ports/silabs/common-hal/_bleio/Connection.c @@ -57,6 +57,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); +} + // Get connected status bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self) { if (self->connection == NULL) { diff --git a/ports/zephyr-cp/common-hal/_bleio/Connection.c b/ports/zephyr-cp/common-hal/_bleio/Connection.c index 77942728a5f..12b2a8efe5f 100644 --- a/ports/zephyr-cp/common-hal/_bleio/Connection.c +++ b/ports/zephyr-cp/common-hal/_bleio/Connection.c @@ -577,6 +577,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); +} + mp_obj_tuple_t *common_hal_bleio_connection_discover_remote_services(bleio_connection_obj_t *self, mp_obj_t service_uuids_whitelist) { bleio_connection_internal_t *connection = self->connection; if (connection == NULL || connection->conn == NULL) { diff --git a/shared-bindings/_bleio/Attribute.h b/shared-bindings/_bleio/Attribute.h index 51278dc0157..60a941a5772 100644 --- a/shared-bindings/_bleio/Attribute.h +++ b/shared-bindings/_bleio/Attribute.h @@ -14,3 +14,7 @@ extern const mp_obj_type_t bleio_attribute_type; extern void common_hal_bleio_attribute_security_mode_check_valid(bleio_attribute_security_mode_t security_mode); + +// True for the security modes that demand a man-in-the-middle-protected (authenticated) +// link: pairing must use numeric comparison or passkey entry, not "Just Works". +extern bool bleio_attribute_security_mode_requires_mitm(bleio_attribute_security_mode_t security_mode); diff --git a/shared-bindings/_bleio/Connection.c b/shared-bindings/_bleio/Connection.c index 4a81d753433..275fa0d435d 100644 --- a/shared-bindings/_bleio/Connection.c +++ b/shared-bindings/_bleio/Connection.c @@ -165,6 +165,78 @@ MP_PROPERTY_GETTER(bleio_connection_paired_obj, (mp_obj_t)&bleio_connection_get_paired_obj); +//| authenticated: bool +//| """True if the link is encrypted *and* authenticated against +//| man-in-the-middle attacks - i.e. LE Secure Connections numeric comparison (or +//| passkey entry) completed. A plain encrypted-but-unauthenticated ("Just Works") +//| link reads ``False``. +//| +//| Ports that don't implement authenticated pairing always report ``False``, +//| even for a bonded link.""" +static mp_obj_t bleio_connection_get_authenticated(mp_obj_t self_in) { + bleio_connection_obj_t *self = MP_OBJ_TO_PTR(self_in); + + return mp_obj_new_bool(common_hal_bleio_connection_get_authenticated(self)); +} +static MP_DEFINE_CONST_FUN_OBJ_1(bleio_connection_get_authenticated_obj, bleio_connection_get_authenticated); + +MP_PROPERTY_GETTER(bleio_connection_authenticated_obj, + (mp_obj_t)&bleio_connection_get_authenticated_obj); + + +//| numeric_comparison: Optional[int] +//| """When the peer has started LE Secure Connections pairing and is waiting for the +//| user to confirm a 6-digit numeric-comparison value, this is that value (an ``int``, +//| 0-999999); otherwise ``None``. Poll it, show the value to the user, then answer +//| with `confirm_pairing`. This is the peripheral side of pairing: the peer drives +//| the procedure, so unlike `pair` this never blocks. +//| +//| Numeric comparison happens automatically when a central pairs to reach a +//| characteristic whose ``read_perm`` / ``write_perm`` is +//| `Attribute.LESC_ENCRYPT_WITH_MITM`. A peripheral with a display and a button +//| services it roughly like this:: +//| +//| while connection.connected: +//| code = connection.numeric_comparison +//| if code is not None: +//| display.show(f"Pair? {code:06d}") +//| connection.confirm_pairing(accept=button_a.value) +//| if connection.authenticated: +//| break +//| time.sleep(0.1) +//| +//| Only implemented on the espressif port.""" +static mp_obj_t bleio_connection_get_numeric_comparison(mp_obj_t self_in) { + bleio_connection_obj_t *self = MP_OBJ_TO_PTR(self_in); + + return common_hal_bleio_connection_get_numeric_comparison(self); +} +static MP_DEFINE_CONST_FUN_OBJ_1(bleio_connection_get_numeric_comparison_obj, bleio_connection_get_numeric_comparison); + +MP_PROPERTY_GETTER(bleio_connection_numeric_comparison_obj, + (mp_obj_t)&bleio_connection_get_numeric_comparison_obj); + + +//| def confirm_pairing(self, accept: bool) -> None: +//| """Answer a pending numeric-comparison pairing request (see +//| `numeric_comparison`). Pass ``accept=True`` if the value shown on the +//| peer matches the one shown here, ``accept=False`` to reject and abort pairing. +//| +//| :raises ConnectionError: if the connection has dropped. +//| :raises _bleio.BluetoothError: if no numeric-comparison request is pending - +//| nothing was offered on `numeric_comparison`, or it already expired +//| because the peer disconnected or the pairing procedure timed out.""" +//| ... +//| +static mp_obj_t bleio_connection_confirm_pairing(mp_obj_t self_in, mp_obj_t accept_in) { + bleio_connection_obj_t *self = MP_OBJ_TO_PTR(self_in); + + common_hal_bleio_connection_confirm_pairing(self, mp_obj_is_true(accept_in)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(bleio_connection_confirm_pairing_obj, bleio_connection_confirm_pairing); + + //| connection_interval: float //| """Time between transmissions in milliseconds. Will be multiple of 1.25ms. Lower numbers //| increase speed and decrease latency but increase power consumption. @@ -223,12 +295,15 @@ MP_PROPERTY_GETTER(bleio_connection_max_packet_length_obj, static const mp_rom_map_elem_t bleio_connection_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_pair), MP_ROM_PTR(&bleio_connection_pair_obj) }, + { MP_ROM_QSTR(MP_QSTR_confirm_pairing), MP_ROM_PTR(&bleio_connection_confirm_pairing_obj) }, { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&bleio_connection_disconnect_obj) }, { MP_ROM_QSTR(MP_QSTR_discover_remote_services), MP_ROM_PTR(&bleio_connection_discover_remote_services_obj) }, // Properties { MP_ROM_QSTR(MP_QSTR_connected), MP_ROM_PTR(&bleio_connection_connected_obj) }, { MP_ROM_QSTR(MP_QSTR_paired), MP_ROM_PTR(&bleio_connection_paired_obj) }, + { MP_ROM_QSTR(MP_QSTR_authenticated), MP_ROM_PTR(&bleio_connection_authenticated_obj) }, + { MP_ROM_QSTR(MP_QSTR_numeric_comparison), MP_ROM_PTR(&bleio_connection_numeric_comparison_obj) }, { MP_ROM_QSTR(MP_QSTR_connection_interval), MP_ROM_PTR(&bleio_connection_connection_interval_obj) }, { MP_ROM_QSTR(MP_QSTR_max_packet_length), MP_ROM_PTR(&bleio_connection_max_packet_length_obj) }, }; diff --git a/shared-bindings/_bleio/Connection.h b/shared-bindings/_bleio/Connection.h index 4be4992fb2f..fbc856c7d45 100644 --- a/shared-bindings/_bleio/Connection.h +++ b/shared-bindings/_bleio/Connection.h @@ -18,6 +18,9 @@ void common_hal_bleio_connection_disconnect(bleio_connection_internal_t *self); bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self); mp_int_t common_hal_bleio_connection_get_max_packet_length(bleio_connection_internal_t *self); bool common_hal_bleio_connection_get_paired(bleio_connection_obj_t *self); +bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self); +mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self); +void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept); mp_obj_tuple_t *common_hal_bleio_connection_discover_remote_services(bleio_connection_obj_t *self, mp_obj_t service_uuids_whitelist); mp_float_t common_hal_bleio_connection_get_connection_interval(bleio_connection_internal_t *self); diff --git a/shared-module/_bleio/Attribute.c b/shared-module/_bleio/Attribute.c index b12f2b121de..cfc5999df1e 100644 --- a/shared-module/_bleio/Attribute.c +++ b/shared-module/_bleio/Attribute.c @@ -24,3 +24,14 @@ void common_hal_bleio_attribute_security_mode_check_valid(bleio_attribute_securi break; } } + +bool bleio_attribute_security_mode_requires_mitm(bleio_attribute_security_mode_t security_mode) { + switch (security_mode) { + case SECURITY_MODE_ENC_WITH_MITM: + case SECURITY_MODE_LESC_ENC_WITH_MITM: + case SECURITY_MODE_SIGNED_WITH_MITM: + return true; + default: + return false; + } +}