From f4538d40728895827b77d6f5e7101cc3715e7b8e Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Fri, 29 Dec 2023 15:31:18 +0530 Subject: [PATCH 1/4] nimble/host: add support for asynchronous authorization This commit introduces handling authorization in nimble. Each operation on characteristic that has authorize flags will require authorization. Application can respond in GAP authorization event callback with accept, reject or pending response. The latter response should trigger user action to either accept or reject request and finish handling att request. ble_att_svr_create_foo_rsp helpers are introduced to hanldle preparing and sending ATT responses. This is done on top of PR #1668 by SumeetSingh19 --- apps/bleprph/src/main.c | 8 + nimble/host/include/host/ble_gap.h | 45 +++ nimble/host/include/host/ble_hs.h | 3 + nimble/host/src/ble_att_priv.h | 41 +++ nimble/host/src/ble_att_svr.c | 471 +++++++++++++++++++++++------ nimble/host/src/ble_gap.c | 21 ++ nimble/host/src/ble_gap_priv.h | 2 + 7 files changed, 506 insertions(+), 85 deletions(-) diff --git a/apps/bleprph/src/main.c b/apps/bleprph/src/main.c index b9f2d26416..936b0da1ed 100644 --- a/apps/bleprph/src/main.c +++ b/apps/bleprph/src/main.c @@ -270,6 +270,14 @@ bleprph_gap_event(struct ble_gap_event *event, void *arg) phy_update(event->phy_updated.tx_phy); return 0; #endif + + case BLE_GAP_EVENT_AUTHORIZE: + MODLOG_DFLT(INFO, "authorize event: conn_handle=%d attr_handle=," + " is_read=%d\n", + event->authorize.conn_handle, + event->authorize.attr_handle, + event->authorize.access_opcode); + return BLE_GAP_AUTHORIZE_REJECT; } return 0; diff --git a/nimble/host/include/host/ble_gap.h b/nimble/host/include/host/ble_gap.h index 9a1ead0049..153df3cde8 100644 --- a/nimble/host/include/host/ble_gap.h +++ b/nimble/host/include/host/ble_gap.h @@ -261,6 +261,9 @@ struct hci_conn_update; /** GAP event: BIG (Broadcast Isochronous Group) information report */ #define BLE_GAP_EVENT_BIGINFO_REPORT 30 +/** GAP event: Authorization request for GATT operations */ +#define BLE_GAP_EVENT_AUTHORIZE 31 + /** @} */ /** @@ -295,6 +298,22 @@ struct hci_conn_update; /** @} */ +/** + * @defgroup GAP Authorize event possible responses. + * @{ + */ + +/** GAP Authorize event response: reject */ +#define BLE_GAP_AUTHORIZE_REJECT 0 + +/** GAP Authorize event response: accept */ +#define BLE_GAP_AUTHORIZE_ACCEPT 1 + +/** GAP Authorize event response: pending */ +#define BLE_GAP_AUTHORIZE_PENDING 2 + +/** @} */ + /** Connection security state */ struct ble_gap_sec_state { /** If connection is encrypted */ @@ -1308,6 +1327,32 @@ struct ble_gap_event { uint8_t length; } unhandled_hci; #endif + + /** + * GATT Authorization Event. Ask the user to authorize a GATT + * read/write operation. + * + * Valid for the following event types: + * o BLE_GAP_EVENT_AUTHORIZE + * + * Valid responses from user: + * o BLE_GAP_AUTHORIZE_ACCEPT + * o BLE_GAP_AUTHORIZE_REJECT + * o BLE_GAP_AUTHORIZE_PENDING + */ + struct { + /* Connection Handle */ + uint16_t conn_handle; + + /* Attribute handle of the attribute being accessed. */ + uint16_t attr_handle; + + /* ATT access opcode. */ + uint8_t access_opcode; + + /* Channel ID on which request has been received */ + uint16_t cid; + } authorize; }; }; diff --git a/nimble/host/include/host/ble_hs.h b/nimble/host/include/host/ble_hs.h index 5079648fad..c42d655ee3 100644 --- a/nimble/host/include/host/ble_hs.h +++ b/nimble/host/include/host/ble_hs.h @@ -159,6 +159,9 @@ extern "C" { /** Operation stalled. */ #define BLE_HS_ESTALLED 31 +/** Operation pending. */ +#define BLE_HS_EPENDING 32 + /** Error base for ATT errors */ #define BLE_HS_ERR_ATT_BASE 0x100 diff --git a/nimble/host/src/ble_att_priv.h b/nimble/host/src/ble_att_priv.h index 2bc3da361b..234aaf0455 100644 --- a/nimble/host/src/ble_att_priv.h +++ b/nimble/host/src/ble_att_priv.h @@ -125,6 +125,20 @@ struct ble_att_svr_conn { ble_npl_time_t basc_prep_timeout_at; }; +struct pending_attr { + SLIST_ENTRY(pending_attr) next; + uint16_t conn_handle; + uint16_t attr_handle; + uint8_t offset; + struct os_mbuf *om; + uint16_t access_opcode; + uint8_t att_opcode; + uint16_t cid; +}; + +SLIST_HEAD(pending_attr_list_head, pending_attr); +extern struct pending_attr_list_head pending_attr_list; + /** * Handles a host attribute request. * @@ -226,6 +240,33 @@ void ble_att_svr_restore_range(uint16_t start_handle, uint16_t end_handle); int ble_att_svr_tx_error_rsp(uint16_t conn_handle, uint16_t cid, struct os_mbuf *txom, uint8_t req_op, uint16_t handle, uint8_t error_code); +int ble_att_svr_create_read_rsp(uint16_t conn_handle, uint16_t cid, + struct os_mbuf *om, int hs_status, + uint8_t att_op, uint16_t err_handle, + uint16_t offset, uint8_t *out_att_err); +int ble_att_svr_create_write_rsp(uint16_t conn_handle, uint16_t cid, + struct os_mbuf **om, int hs_status, + struct ble_att_write_req *req, + uint16_t offset, uint16_t handle, + uint8_t *out_att_err); +int ble_att_svr_create_read_mult_rsp(uint16_t conn_handle, uint16_t cid, + struct os_mbuf **om, int hs_status, + uint8_t *att_err, uint16_t *err_handle); +int ble_att_svr_create_read_mult_var_len_rsp(uint16_t conn_handle, + uint16_t cid, struct os_mbuf **om, + int hs_status, uint8_t *att_err, + uint16_t *err_handle); +int ble_att_svr_create_prep_write_rsp(uint16_t conn_handle, uint16_t cid, + struct os_mbuf **om, + struct ble_att_prep_write_cmd *req, + int hs_status, uint8_t att_err, + uint16_t err_handle); +int ble_att_svr_check_author_perm(uint16_t conn_handle, uint16_t attr_handle, + uint8_t *att_err, uint16_t access_opcode, + uint16_t cid); +int ble_att_svr_tx_rsp(uint16_t conn_handle, uint16_t cid, int hs_status, + struct os_mbuf *om, uint8_t att_op, + uint8_t err_status, uint16_t err_handle); /*** $clt */ /** An information-data entry in a find information response. */ diff --git a/nimble/host/src/ble_att_svr.c b/nimble/host/src/ble_att_svr.c index e584cae32d..050577d5af 100644 --- a/nimble/host/src/ble_att_svr.c +++ b/nimble/host/src/ble_att_svr.c @@ -20,6 +20,7 @@ #include #include #include +#include #include "os/os.h" #include "host/ble_att.h" #include "nimble/ble.h" @@ -81,6 +82,46 @@ ble_att_svr_entry_free(struct ble_att_svr_entry *entry) os_memblock_put(&ble_att_svr_entry_pool, entry); } +struct pending_attr_list_head pending_attr_list = + SLIST_HEAD_INITIALIZER(pending_attr_list); + +static void pending_att_prepare(uint16_t conn_handle, + uint16_t attr_handle, + uint8_t offset, struct os_mbuf *om, + uint8_t access_opcode, uint8_t att_opcode, + uint16_t cid) +{ + struct pending_attr *pending; + struct pending_attr *e; + struct pending_attr *last; + + pending = malloc(sizeof(struct pending_attr)); + + memset(pending, 0, sizeof(*pending)); + + pending->conn_handle = conn_handle; + pending->attr_handle = attr_handle; + pending->offset = offset; + pending->om = om; + pending->access_opcode = access_opcode; + pending->att_opcode = att_opcode; + pending->cid = cid; + ble_hs_lock(); + if (SLIST_EMPTY(&pending_attr_list)) { + SLIST_INSERT_HEAD(&pending_attr_list, pending, next); + } else { + last = NULL; + /* find last and insert at tail */ + SLIST_FOREACH(e, &pending_attr_list, next) { + if ((SLIST_NEXT(e, next) = NULL)) { + last = e; + SLIST_INSERT_AFTER(last, pending, next); + } + } + } + ble_hs_unlock(); +} + /** * Allocate the next handle id and return it. * @@ -244,6 +285,61 @@ ble_att_svr_get_sec_state(uint16_t conn_handle, ble_hs_unlock(); } +int +ble_att_svr_check_author_perm(uint16_t conn_handle, uint16_t attr_handle, + uint8_t *att_err, uint16_t access_opcode, + uint16_t cid) +{ + struct ble_att_svr_entry *entry; + int author; + int rc; + + entry = ble_att_svr_find_by_handle(attr_handle); + if (entry == NULL) { + if (att_err != NULL) { + *att_err = BLE_ATT_ERR_INVALID_HANDLE; + } + return BLE_HS_ENOENT; + } + + if (access_opcode == BLE_ATT_ACCESS_OP_READ) { + if (!(entry->ha_flags & BLE_ATT_F_READ)) { + *att_err = BLE_ATT_ERR_READ_NOT_PERMITTED; + return BLE_HS_EREJECT; + } + + author = entry->ha_flags & BLE_ATT_F_READ_AUTHOR; + } else { + if (!(entry->ha_flags & BLE_ATT_F_WRITE)) { + *att_err = BLE_ATT_ERR_WRITE_NOT_PERMITTED; + return BLE_HS_EREJECT; + } + + author = entry->ha_flags & BLE_ATT_F_WRITE_AUTHOR; + } + + if (!author) { + return 0; + } + + rc = ble_gap_authorize_event(conn_handle, attr_handle, access_opcode, + cid); + + switch (rc) { + case BLE_GAP_AUTHORIZE_ACCEPT: + return 0; + + case BLE_GAP_AUTHORIZE_REJECT: + *att_err = BLE_ATT_ERR_INSUFFICIENT_AUTHOR; + return BLE_HS_ATT_ERR(*att_err); + + case BLE_GAP_AUTHORIZE_PENDING: + return BLE_HS_EPENDING; + } + + return rc; +} + static int ble_att_svr_check_perms(uint16_t conn_handle, int is_read, struct ble_att_svr_entry *entry, @@ -254,7 +350,6 @@ ble_att_svr_check_perms(uint16_t conn_handle, int is_read, struct ble_store_key_sec key_sec; struct ble_hs_conn_addrs addrs; struct ble_hs_conn *conn; - int author; int authen; int enc; int rc; @@ -267,7 +362,6 @@ ble_att_svr_check_perms(uint16_t conn_handle, int is_read, enc = entry->ha_flags & BLE_ATT_F_READ_ENC; authen = entry->ha_flags & BLE_ATT_F_READ_AUTHEN; - author = entry->ha_flags & BLE_ATT_F_READ_AUTHOR; } else { if (!(entry->ha_flags & BLE_ATT_F_WRITE)) { *out_att_err = BLE_ATT_ERR_WRITE_NOT_PERMITTED; @@ -276,11 +370,10 @@ ble_att_svr_check_perms(uint16_t conn_handle, int is_read, enc = entry->ha_flags & BLE_ATT_F_WRITE_ENC; authen = entry->ha_flags & BLE_ATT_F_WRITE_AUTHEN; - author = entry->ha_flags & BLE_ATT_F_WRITE_AUTHOR; } /* Bail early if this operation doesn't require security. */ - if (!enc && !authen && !author) { + if (!enc && !authen) { return 0; } @@ -332,10 +425,6 @@ ble_att_svr_check_perms(uint16_t conn_handle, int is_read, return BLE_HS_ATT_ERR(*out_att_err); } - if (author) { - /* XXX: Prompt user for authorization. */ - } - return 0; } @@ -471,6 +560,28 @@ ble_att_svr_read_flat(uint16_t conn_handle, return rc; } +int +ble_att_svr_create_read_rsp(uint16_t conn_handle, uint16_t cid, + struct os_mbuf *om, int hs_status, uint8_t att_op, + uint16_t err_handle, uint16_t offset, + uint8_t *out_att_err) +{ + int rc = 0; + + if (hs_status != 0) { + rc = hs_status; + goto done; + } + + rc = ble_att_svr_read_handle(conn_handle, err_handle, offset, om, + out_att_err); + +done: + rc = ble_att_svr_tx_rsp(conn_handle, cid, rc, om, att_op, + *out_att_err, err_handle); + return rc; +} + int ble_att_svr_read_handle(uint16_t conn_handle, uint16_t attr_handle, uint16_t offset, struct os_mbuf *om, @@ -621,7 +732,7 @@ ble_att_svr_tx_error_rsp(uint16_t conn_handle, uint16_t cid, struct os_mbuf *txo * of the error message's attribute handle * field. */ -static int +int ble_att_svr_tx_rsp(uint16_t conn_handle, uint16_t cid, int hs_status, struct os_mbuf *om, uint8_t att_op, uint8_t err_status, uint16_t err_handle) { @@ -1501,14 +1612,21 @@ ble_att_svr_rx_read(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom) goto done; } - rc = ble_att_svr_read_handle(conn_handle, err_handle, 0, txom, &att_err); - if (rc != 0) { - goto done; + rc = ble_att_svr_check_author_perm(conn_handle, err_handle, &att_err, + BLE_ATT_ACCESS_OP_READ, cid); + + if (rc == BLE_HS_EPENDING) { + pending_att_prepare(conn_handle, err_handle, 0, txom, + BLE_ATT_ACCESS_OP_READ, BLE_ATT_OP_READ_REQ, cid); + txom = NULL; + return rc; } done: - rc = ble_att_svr_tx_rsp(conn_handle, cid, rc, txom, BLE_ATT_OP_READ_REQ, - att_err, err_handle); + rc = ble_att_svr_create_read_rsp(conn_handle, cid, txom, rc, + BLE_ATT_OP_READ_REQ, err_handle, 0, + &att_err); + return rc; } @@ -1529,6 +1647,7 @@ ble_att_svr_rx_read_blob(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rx txom = NULL; att_err = 0; err_handle = 0; + offset = 0; rc = ble_att_svr_pullup_req_base(rxom, sizeof(*req), &att_err); if (rc != 0) { @@ -1551,8 +1670,17 @@ ble_att_svr_rx_read_blob(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rx goto done; } - rc = ble_att_svr_read_handle(conn_handle, err_handle, offset, - txom, &att_err); + rc = ble_att_svr_check_author_perm(conn_handle, err_handle, &att_err, + BLE_ATT_ACCESS_OP_READ, cid); + + if (rc == BLE_HS_EPENDING) { + pending_att_prepare(conn_handle, err_handle, 0, txom, + BLE_ATT_ACCESS_OP_READ, BLE_ATT_OP_READ_BLOB_REQ, + cid); + txom = NULL; + return rc; + } + if (rc != 0) { goto done; } @@ -1560,8 +1688,9 @@ ble_att_svr_rx_read_blob(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rx rc = 0; done: - rc = ble_att_svr_tx_rsp(conn_handle, cid, rc, txom, BLE_ATT_OP_READ_BLOB_REQ, - att_err, err_handle); + rc = ble_att_svr_create_read_rsp(conn_handle, cid, txom, rc, + BLE_ATT_OP_READ_BLOB_REQ, err_handle, + offset, &att_err); return rc; } @@ -1626,6 +1755,36 @@ ble_att_svr_build_read_mult_rsp(uint16_t conn_handle, uint16_t cid, return rc; } +int +ble_att_svr_create_read_mult_rsp(uint16_t conn_handle, uint16_t cid, + struct os_mbuf **om, int hs_status, + uint8_t *att_err, uint16_t *err_handle) +{ + struct os_mbuf *txom; + + int rc = 0; + txom = NULL; + + if (hs_status != 0) { + rc = ble_att_svr_pkt(om, &txom, att_err); + if (rc != 0) { + *err_handle = 0; + goto done; + } + rc = hs_status; + goto done; + } + + rc = ble_att_svr_build_read_mult_rsp(conn_handle, cid, om, &txom, att_err, + err_handle); + +done: + rc = ble_att_svr_tx_rsp(conn_handle, cid, rc, txom, + BLE_ATT_OP_READ_MULT_REQ, *att_err, + *err_handle); + return rc; +} + int ble_att_svr_rx_read_mult(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom) { @@ -1633,21 +1792,43 @@ ble_att_svr_rx_read_mult(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rx return BLE_HS_ENOTSUP; #endif - struct os_mbuf *txom; uint16_t err_handle; uint8_t att_err; - int rc; + uint8_t offset; + int rc = 0; /* Initialize some values in case of early error. */ - txom = NULL; err_handle = 0; att_err = 0; + offset = 0; + + /* Iterate through requested handles to check authorization */ + while ((OS_MBUF_PKTLEN(*rxom) - offset) >= 2) { + os_mbuf_copydata(*rxom, offset, 2, &err_handle); + offset += 2; + + rc = ble_att_svr_check_author_perm(conn_handle, err_handle, &att_err, + BLE_ATT_ACCESS_OP_READ, cid); + + if (rc == BLE_HS_EPENDING) { + pending_att_prepare(conn_handle, 0, offset, *rxom, + BLE_ATT_ACCESS_OP_READ, + BLE_ATT_OP_READ_MULT_REQ, cid); + /* Always reuse rxom mbuf for response */ + *rxom = NULL; + return rc; + } - rc = ble_att_svr_build_read_mult_rsp(conn_handle, cid, rxom, &txom, &att_err, - &err_handle); + if (rc != 0) { + goto done; + } + } + +done: + rc = ble_att_svr_create_read_mult_rsp(conn_handle, cid, rxom, rc, + &att_err, &err_handle); - return ble_att_svr_tx_rsp(conn_handle, cid, rc, txom, BLE_ATT_OP_READ_MULT_REQ, - att_err, err_handle); + return rc; } static int @@ -1732,6 +1913,33 @@ ble_att_svr_build_read_mult_rsp_var(uint16_t conn_handle, uint16_t cid, return rc; } +int +ble_att_svr_create_read_mult_var_len_rsp(uint16_t conn_handle, uint16_t cid, + struct os_mbuf **om, int hs_status, + uint8_t *att_err, + uint16_t *err_handle) +{ + struct os_mbuf *txom; + int rc; + + rc = 0; + txom = NULL; + + if (hs_status != 0) { + rc = hs_status; + goto done; + } + + rc = ble_att_svr_build_read_mult_rsp_var(conn_handle, cid, om, &txom, + att_err, err_handle); + +done: + rc = ble_att_svr_tx_rsp(conn_handle, cid, rc, txom, + BLE_ATT_OP_READ_MULT_VAR_REQ, *att_err, + *err_handle); + return rc; +} + int ble_att_svr_rx_read_mult_var(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom) { @@ -1739,22 +1947,43 @@ ble_att_svr_rx_read_mult_var(uint16_t conn_handle, uint16_t cid, struct os_mbuf return BLE_HS_ENOTSUP; #endif - struct os_mbuf *txom; uint16_t err_handle; uint8_t att_err; + uint8_t offset; int rc; /* Initialize some values in case of early error. */ - txom = NULL; err_handle = 0; att_err = 0; + offset = 0; + rc = 0; - rc = ble_att_svr_build_read_mult_rsp_var(conn_handle, cid, rxom, &txom, &att_err, - &err_handle); + /* Iterate through requested handles to check authorization */ + while ((OS_MBUF_PKTLEN(*rxom) - offset) >= 2) { + os_mbuf_copydata(*rxom, offset, 2, &err_handle); + offset += 2; + + rc = ble_att_svr_check_author_perm(conn_handle, err_handle, &att_err, + BLE_ATT_ACCESS_OP_READ, cid); + + if (rc == BLE_HS_EPENDING) { + pending_att_prepare(conn_handle, 0, offset, *rxom, + BLE_ATT_ACCESS_OP_READ, + BLE_ATT_OP_READ_MULT_REQ, cid); + /* Always reuse rxom mbuf for response */ + *rxom = NULL; + return rc; + } - return ble_att_svr_tx_rsp(conn_handle, cid, rc, txom, - BLE_ATT_OP_READ_MULT_VAR_REQ, - att_err, err_handle); + if (rc != 0) { + goto done; + } + } + +done: + return ble_att_svr_create_read_mult_var_len_rsp(conn_handle, cid, rxom, + rc, &att_err, + &err_handle); } static int @@ -2096,6 +2325,48 @@ ble_att_svr_build_write_rsp(struct os_mbuf **rxom, struct os_mbuf **out_txom, return rc; } +int +ble_att_svr_create_write_rsp(uint16_t conn_handle, uint16_t cid, + struct os_mbuf **om, int hs_status, + struct ble_att_write_req *req, uint16_t offset, + uint16_t handle, uint8_t *out_att_err) +{ + struct os_mbuf *txom; + + txom = NULL; + int rc = 0; + + if (hs_status != 0) { + rc = hs_status; + goto done; + } + + /* Allocate the write response. This must be done prior to processing the + * request. See the note at the top of this file for details. + */ + rc = ble_att_svr_build_write_rsp(om, &txom, out_att_err); + if (rc != 0) { + goto done; + } + + /* Strip the request base from the front of the mbuf. */ + os_mbuf_adj(*om, sizeof(*req)); + + rc = ble_att_svr_write_handle(conn_handle, handle, + offset, om, out_att_err); + + if (rc != 0) { + goto done; + } + + rc = 0; + +done: + rc = ble_att_svr_tx_rsp(conn_handle, cid, rc, txom, BLE_ATT_OP_WRITE_REQ, + *out_att_err, handle); + return rc; +} + int ble_att_svr_rx_write(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom) { @@ -2104,13 +2375,12 @@ ble_att_svr_rx_write(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom) #endif struct ble_att_write_req *req; - struct os_mbuf *txom; uint16_t handle; uint8_t att_err; int rc; /* Initialize some values in case of early error. */ - txom = NULL; + req = NULL; att_err = 0; handle = 0; @@ -2123,27 +2393,21 @@ ble_att_svr_rx_write(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom) handle = le16toh(req->bawq_handle); - /* Allocate the write response. This must be done prior to processing the - * request. See the note at the top of this file for details. - */ - rc = ble_att_svr_build_write_rsp(rxom, &txom, &att_err); - if (rc != 0) { - goto done; - } + rc = ble_att_svr_check_author_perm(conn_handle, handle, &att_err, + BLE_ATT_ACCESS_OP_WRITE, cid); - /* Strip the request base from the front of the mbuf. */ - os_mbuf_adj(*rxom, sizeof(*req)); - - rc = ble_att_svr_write_handle(conn_handle, handle, 0, rxom, &att_err); - if (rc != 0) { - goto done; + if (rc == BLE_HS_EPENDING) { + pending_att_prepare(conn_handle, handle, 0, *rxom, + BLE_ATT_ACCESS_OP_WRITE, BLE_ATT_OP_WRITE_REQ, + cid); + *rxom = NULL; + return rc; } - rc = 0; - done: - rc = ble_att_svr_tx_rsp(conn_handle, cid, rc, txom, BLE_ATT_OP_WRITE_REQ, - att_err, handle); + rc = ble_att_svr_create_write_rsp(conn_handle, cid, rxom, rc, req, 0, + handle, &att_err); + return rc; } @@ -2433,45 +2697,25 @@ ble_att_svr_insert_prep_entry(uint16_t conn_handle, return 0; } -int -ble_att_svr_rx_prep_write(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom) +int ble_att_svr_create_prep_write_rsp(uint16_t conn_handle, uint16_t cid, + struct os_mbuf **om, + struct ble_att_prep_write_cmd *req, + int hs_status, uint8_t att_err, + uint16_t err_handle) { -#if !MYNEWT_VAL(BLE_ATT_SVR_QUEUED_WRITE) - return BLE_HS_ENOTSUP; -#endif - - struct ble_att_prep_write_cmd *req; struct ble_att_svr_entry *attr_entry; struct os_mbuf *txom; - uint16_t err_handle; - uint8_t att_err; - int rc; - /* Initialize some values in case of early error. */ + int rc = 0; txom = NULL; - att_err = 0; - err_handle = 0; - rc = ble_att_svr_pullup_req_base(rxom, sizeof(*req), &att_err); - if (rc != 0) { + if (hs_status != 0) { + rc = hs_status; goto done; } - req = (struct ble_att_prep_write_cmd *)(*rxom)->om_data; - - err_handle = le16toh(req->bapc_handle); - attr_entry = ble_att_svr_find_by_handle(le16toh(req->bapc_handle)); - /* A prepare write request gets rejected for the following reasons: - * 1. Insufficient authorization. - * 2. Insufficient authentication. - * 3. Insufficient encryption key size (XXX: Not checked). - * 4. Insufficient encryption (XXX: Not checked). - * 5. Invalid handle. - * 6. Write not permitted. - */ - /* <5> */ if (attr_entry == NULL) { rc = BLE_HS_ENOENT; @@ -2479,15 +2723,16 @@ ble_att_svr_rx_prep_write(uint16_t conn_handle, uint16_t cid, struct os_mbuf **r goto done; } - /* <1>, <2>, <4>, <6> */ + /* <2>, <4>, <6> */ rc = ble_att_svr_check_perms(conn_handle, 0, attr_entry, &att_err); + if (rc != 0) { goto done; } ble_hs_lock(); rc = ble_att_svr_insert_prep_entry(conn_handle, le16toh(req->bapc_handle), - le16toh(req->bapc_offset), *rxom, + le16toh(req->bapc_offset), *om, &att_err); ble_hs_unlock(); @@ -2495,8 +2740,8 @@ ble_att_svr_rx_prep_write(uint16_t conn_handle, uint16_t cid, struct os_mbuf **r * request except for op code. On error, the buffer contents will get * cleared before the error gets written. */ - txom = *rxom; - *rxom = NULL; + txom = *om; + *om = NULL; if (rc != 0) { goto done; @@ -2509,8 +2754,63 @@ ble_att_svr_rx_prep_write(uint16_t conn_handle, uint16_t cid, struct os_mbuf **r rc = 0; done: - rc = ble_att_svr_tx_rsp(conn_handle, cid, rc, txom, BLE_ATT_OP_PREP_WRITE_REQ, - att_err, err_handle); + rc = ble_att_svr_tx_rsp(conn_handle, cid, rc, txom, + BLE_ATT_OP_PREP_WRITE_REQ, att_err, err_handle); + + return rc; +} + +int +ble_att_svr_rx_prep_write(uint16_t conn_handle, uint16_t cid, struct os_mbuf **rxom) +{ +#if !MYNEWT_VAL(BLE_ATT_SVR_QUEUED_WRITE) + return BLE_HS_ENOTSUP; +#endif + + struct ble_att_prep_write_cmd *req; + uint16_t err_handle; + uint8_t att_err; + int rc; + + /* Initialize some values in case of early error. */ + req = NULL; + att_err = 0; + err_handle = 0; + + rc = ble_att_svr_pullup_req_base(rxom, sizeof(*req), &att_err); + if (rc != 0) { + goto done; + } + + req = (struct ble_att_prep_write_cmd *)(*rxom)->om_data; + + err_handle = le16toh(req->bapc_handle); + + /* A prepare write request gets rejected for the following reasons: + * 1. Insufficient authorization. + * 2. Insufficient authentication. + * 3. Insufficient encryption key size (XXX: Not checked). + * 4. Insufficient encryption (XXX: Not checked). + * 5. Invalid handle. + * 6. Write not permitted. + */ + + /* <1> */ + rc = ble_att_svr_check_author_perm(conn_handle, err_handle, &att_err, + BLE_ATT_ACCESS_OP_WRITE, cid); + if (rc == BLE_HS_EPENDING) { + pending_att_prepare(conn_handle, err_handle, 0, *rxom, + BLE_ATT_ACCESS_OP_WRITE, BLE_ATT_OP_PREP_WRITE_REQ, + cid); + /* Always reuse rxom mbuf for response */ + *rxom = NULL; + return rc; + } + +done: /* <2>, <4>, <6> */ + rc = ble_att_svr_create_prep_write_rsp(conn_handle, cid, rxom, req, rc, + att_err, err_handle); + return rc; } @@ -2942,6 +3242,7 @@ ble_att_svr_init(void) STAILQ_INIT(&ble_att_svr_list); STAILQ_INIT(&ble_att_svr_hidden_list); + SLIST_INIT(&pending_attr_list); ble_att_svr_id = 0; diff --git a/nimble/host/src/ble_gap.c b/nimble/host/src/ble_gap.c index 6369b44788..b353e5b693 100644 --- a/nimble/host/src/ble_gap.c +++ b/nimble/host/src/ble_gap.c @@ -6864,6 +6864,27 @@ ble_gap_unhandled_hci_event(bool is_le_meta, bool is_vs, const void *buf, } #endif +int +ble_gap_authorize_event(uint16_t conn_handle, uint16_t attr_handle, + uint16_t access_opcode, uint16_t cid) +{ +#if MYNEWT_VAL(BLE_ROLE_PERIPHERAL) + struct ble_gap_event event; + int rc; + + memset(&event, 0, sizeof event); + event.type = BLE_GAP_EVENT_AUTHORIZE; + event.authorize.conn_handle = conn_handle; + event.authorize.attr_handle = attr_handle; + event.authorize.access_opcode = access_opcode; + event.authorize.cid = cid; + + rc = ble_gap_call_conn_event_cb(&event, conn_handle); + return rc; +#endif + return BLE_GAP_AUTHORIZE_REJECT; +} + /***************************************************************************** * $preempt * *****************************************************************************/ diff --git a/nimble/host/src/ble_gap_priv.h b/nimble/host/src/ble_gap_priv.h index 2f875ddf0e..5f38dae630 100644 --- a/nimble/host/src/ble_gap_priv.h +++ b/nimble/host/src/ble_gap_priv.h @@ -144,6 +144,8 @@ void ble_gap_pairing_complete_event(uint16_t conn_handle, int status); void ble_gap_unhandled_hci_event(bool is_le_meta, bool is_vs, const void *buf, uint8_t len); int ble_gap_master_in_progress(void); +int ble_gap_authorize_event(uint16_t conn_handle, uint16_t attr_handle, + uint16_t access_opcode, uint16_t cid); void ble_gap_preempt(void); void ble_gap_preempt_done(void); From 338734d55b11663774373535838e62a86968862e Mon Sep 17 00:00:00 2001 From: Piotr Narajowski Date: Wed, 19 Mar 2025 19:09:43 +0100 Subject: [PATCH 2/4] apps: btshell: add support for authorization For authorization testing purposes two characteristics with authorize flags and authorize shell command are added. --- apps/btshell/src/btshell.h | 17 +++++++++ apps/btshell/src/cmd.c | 70 +++++++++++++++++++++++++++++++++++++ apps/btshell/src/gatt_svr.c | 19 ++++++++-- apps/btshell/src/main.c | 23 ++++++++++++ 4 files changed, 127 insertions(+), 2 deletions(-) diff --git a/apps/btshell/src/btshell.h b/apps/btshell/src/btshell.h index d8bf83d2ff..933f4c1849 100644 --- a/apps/btshell/src/btshell.h +++ b/apps/btshell/src/btshell.h @@ -97,6 +97,23 @@ struct btshell_scan_opts { extern struct btshell_conn btshell_conns[MYNEWT_VAL(BLE_MAX_CONNECTIONS)]; extern int btshell_num_conns; +/* BLE_GATT_READ_MAX_ATTRS * (1 ATT + 1 EATT chan) */ +#define PENDING_ATTR_MAX MYNEWT_VAL(BLE_GATT_READ_MAX_ATTRS) * 2 + +struct auth_attr { + uint16_t conn_handle; + uint16_t attr_handle; +}; + +extern struct auth_attr authorized_attrs[PENDING_ATTR_MAX]; + +struct pend_attr { + uint16_t attr_handle; + uint16_t cid; +}; + +extern struct pend_attr pending_attr; + int btshell_exchange_mtu(uint16_t conn_handle); int btshell_disc_svcs(uint16_t conn_handle); int btshell_disc_svc_by_uuid(uint16_t conn_handle, const ble_uuid_t *uuid); diff --git a/apps/btshell/src/cmd.c b/apps/btshell/src/cmd.c index 3380de7547..adb5694354 100644 --- a/apps/btshell/src/cmd.c +++ b/apps/btshell/src/cmd.c @@ -176,6 +176,71 @@ cmd_parse_addr(const char *prefix, ble_addr_t *addr) return parse_dev_addr(prefix, cmd_addr_type, addr); } +static int +pending_operation_authorize(int argc, char **argv) +{ + uint16_t conn_handle; + uint16_t attr_handle; + bool auth; + int rc; + + rc = parse_arg_init(argc - 1, argv + 1); + if (rc != 0) { + return rc; + } + + conn_handle = parse_arg_uint16("conn", &rc); + if (rc != 0) { + console_printf("invalid 'conn' parameter\n"); + return rc; + } + + attr_handle = parse_arg_uint16("attr", &rc); + if (rc != 0) { + console_printf("invalid 'attr' parameter\n"); + return rc; + } + + auth = parse_arg_bool_dflt("auth", 1, &rc); + if (rc != 0) { + console_printf("invalid 'auth' parameter\n"); + return rc; + } + + if (auth) { + for (int i = 0; i < PENDING_ATTR_MAX; i++) { + if (authorized_attrs[i].conn_handle == 0 && + authorized_attrs[i].attr_handle == 0) { + authorized_attrs[i].conn_handle = conn_handle; + authorized_attrs[i].attr_handle = attr_handle; + break; + } + } + attr_handle = 0; + } else { + attr_handle = pending_attr.attr_handle; + } + + ble_gatts_pending_req_auth(conn_handle, attr_handle, pending_attr.cid); + + return 0; +} + +#if MYNEWT_VAL(SHELL_CMD_HELP) +static const struct shell_param authorize_params[] = { + {"conn", "connection handle parameter, usage: ="}, + {"attr", "attribute handle parameter to authorize, usage: ="}, + {"auth", "whether to authorize access, usage: =[0-1], default=1"}, + {NULL, NULL} +}; + +static const struct shell_cmd_help authorize_help = { + .summary = "authorize command", + .usage = NULL, + .params = authorize_params, +}; +#endif + /***************************************************************************** * $advertise * *****************************************************************************/ @@ -4374,6 +4439,11 @@ static const struct shell_cmd_help leaudio_broadcast_stop_help = { #endif /* BLE_AUDIO && BLE_ISO_BROADCAST_SOURCE */ static const struct shell_cmd btshell_commands[] = { + { + .sc_cmd = "authorize", + .sc_cmd_func = pending_operation_authorize, + .help = &authorize_help, + }, #if MYNEWT_VAL(BLE_EXT_ADV) { .sc_cmd = "advertise-configure", diff --git a/apps/btshell/src/gatt_svr.c b/apps/btshell/src/gatt_svr.c index dc025ca143..52d9898b45 100644 --- a/apps/btshell/src/gatt_svr.c +++ b/apps/btshell/src/gatt_svr.c @@ -46,6 +46,7 @@ #define PTS_DSC_READ_WRITE 0x000b #define PTS_DSC_READ_WRITE_ENC 0x000c #define PTS_DSC_READ_WRITE_AUTHEN 0x000d +#define PTS_CHR_READ_WRITE_AUTHOR 0x000e #define PTS_LONG_SVC 0x0011 #define PTS_LONG_CHR_READ 0x0012 @@ -60,6 +61,7 @@ #define PTS_LONG_DSC_READ_WRITE 0x001b #define PTS_LONG_DSC_READ_WRITE_ENC 0x001c #define PTS_LONG_DSC_READ_WRITE_AUTHEN 0x001d +#define PTS_LONG_CHR_READ_WRITE_AUTHOR 0x0020 #define PTS_INC_SVC 0x001e #define PTS_CHR_READ_WRITE_ALT 0x001f @@ -178,8 +180,14 @@ static const struct ble_gatt_svc_def gatt_svr_svcs[] = { 0, /* No more descriptors in this characteristic. */ } } }, { - 0, /* No more characteristics in this service. */ - } }, + .uuid = PTS_UUID_DECLARE(PTS_CHR_READ_WRITE_AUTHOR), + .access_cb = gatt_svr_access_test, + .flags = BLE_GATT_CHR_F_READ_AUTHOR | BLE_GATT_CHR_F_READ | + BLE_GATT_CHR_F_WRITE_AUTHOR | BLE_GATT_CHR_F_WRITE + }, { + 0, /* No more characteristics in this service. */ + } + }, }, { @@ -206,6 +214,11 @@ static const struct ble_gatt_svc_def gatt_svr_svcs[] = { .uuid = PTS_UUID_DECLARE(PTS_LONG_CHR_READ_WRITE_ALT), .access_cb = gatt_svr_long_access_test, .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE, + },{ + .uuid = PTS_UUID_DECLARE(PTS_LONG_CHR_READ_WRITE_AUTHOR), + .access_cb = gatt_svr_long_access_test, + .flags = BLE_GATT_CHR_F_READ_AUTHOR | BLE_GATT_CHR_F_READ | + BLE_GATT_CHR_F_WRITE_AUTHOR | BLE_GATT_CHR_F_WRITE }, { .uuid = PTS_UUID_DECLARE(PTS_LONG_CHR_READ_WRITE_ENC), .access_cb = gatt_svr_long_access_test, @@ -427,6 +440,7 @@ gatt_svr_access_test(uint16_t conn_handle, uint16_t attr_handle, case PTS_CHR_READ_WRITE_ENC: case PTS_CHR_READ_WRITE_AUTHEN: case PTS_CHR_READ_WRITE_ALT: + case PTS_CHR_READ_WRITE_AUTHOR: if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { rc = gatt_svr_chr_write(ctxt->om,0, sizeof gatt_svr_pts_static_val, @@ -531,6 +545,7 @@ gatt_svr_long_access_test(uint16_t conn_handle, uint16_t attr_handle, case PTS_LONG_CHR_READ_WRITE_ENC: case PTS_LONG_CHR_READ_WRITE_AUTHEN: + case PTS_LONG_CHR_READ_WRITE_AUTHOR: if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { rc = gatt_svr_chr_write(ctxt->om,0, sizeof gatt_svr_pts_static_long_val, diff --git a/apps/btshell/src/main.c b/apps/btshell/src/main.c index 0db012e98c..b4cef6a8a7 100644 --- a/apps/btshell/src/main.c +++ b/apps/btshell/src/main.c @@ -135,6 +135,8 @@ int btshell_full_disc_prev_chr_val; struct ble_sm_sc_oob_data oob_data_local; struct ble_sm_sc_oob_data oob_data_remote; +struct auth_attr authorized_attrs[PENDING_ATTR_MAX]; +struct pend_attr pending_attr; #if MYNEWT_VAL(BLE_AUDIO) && MYNEWT_VAL(BLE_ISO_BROADCAST_SOURCE) static struct {struct ble_audio_base *base; uint8_t adv_instance;} @@ -1292,6 +1294,7 @@ btshell_gap_event(struct ble_gap_event *event, void *arg) struct ble_gap_conn_desc desc; int conn_idx; int rc; + int i; #if MYNEWT_VAL(BLE_PERIODIC_ADV) struct psync *psync; #endif @@ -1318,6 +1321,7 @@ btshell_gap_event(struct ble_gap_event *event, void *arg) if (conn_idx != -1) { btshell_conn_delete_idx(conn_idx); } + memset(&authorized_attrs, 0, sizeof(authorized_attrs)); return btshell_restart_adv(event); #if MYNEWT_VAL(BLE_EXT_ADV) @@ -1555,6 +1559,25 @@ btshell_gap_event(struct ble_gap_event *event, void *arg) return 0; #endif #endif + case BLE_GAP_EVENT_AUTHORIZE: + for (i = 0; i < PENDING_ATTR_MAX; i++) { + if (authorized_attrs[i].conn_handle == + event->authorize.conn_handle && authorized_attrs[i].attr_handle == + event->authorize.attr_handle) { + console_printf("Access to attribute %d already authorized\n", + event->authorize.attr_handle); + return BLE_GAP_AUTHORIZE_ACCEPT; + } + } + console_printf("Authorize access to attribute: conn_handle=%d," + "access_opcode=%d, cid=%d, attr=%d\n", + event->authorize.conn_handle, + event->authorize.access_opcode, + event->authorize.cid, + event->authorize.attr_handle); + pending_attr.cid = event->authorize.cid; + pending_attr.attr_handle = event->authorize.attr_handle; + return BLE_GAP_AUTHORIZE_PENDING; default: return 0; } From 61fb49402539c0e219d85cc77a3f3e70a684dffd Mon Sep 17 00:00:00 2001 From: Piotr Narajowski Date: Thu, 20 Mar 2025 10:53:51 +0100 Subject: [PATCH 3/4] apps: bttester: handle GAP authorization event BLE_GAP_AUTHORIZE_REJECT is always returned when authorized attribute is being accessed. This is needed for GATT/SR tests that requires rejecting ATT requests with insufficient authorization error. --- apps/bttester/src/btp_gap.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/bttester/src/btp_gap.c b/apps/bttester/src/btp_gap.c index f6747b0f49..00f0280db4 100644 --- a/apps/bttester/src/btp_gap.c +++ b/apps/bttester/src/btp_gap.c @@ -1594,6 +1594,10 @@ gap_event_cb(struct ble_gap_event *event, void *arg) periodic_transfer_received(event); break; #endif + case BLE_GAP_EVENT_AUTHORIZE: + console_printf("Authorize event: conn_handle=%d", + event->authorize.conn_handle); + return BLE_GAP_AUTHORIZE_REJECT; default: break; } From 3424aca094f71ccbd5e8e2880e2d3f1f53ca2246 Mon Sep 17 00:00:00 2001 From: Piotr Narajowski Date: Thu, 20 Mar 2025 12:26:22 +0100 Subject: [PATCH 4/4] nimble/host: add gatts method for async authorization ble_gatts_pending_req_auth method can be used to prepare and send response to pending ATT request e.g. when user has to authorize operation on attribute that requires authorization --- nimble/host/include/host/ble_gatt.h | 17 ++++ nimble/host/src/ble_gatts.c | 123 ++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/nimble/host/include/host/ble_gatt.h b/nimble/host/include/host/ble_gatt.h index 229d99dda3..edcb47e3b1 100644 --- a/nimble/host/include/host/ble_gatt.h +++ b/nimble/host/include/host/ble_gatt.h @@ -1214,6 +1214,23 @@ int ble_gatts_start(void); */ int ble_gatts_peer_cl_sup_feat_get(uint16_t conn_handle, uint8_t *out_supported_feat, uint8_t len); +/** + * Prepares and sends a response for pending ATT request. + * + * @param conn_handle The connection over which to authorize a + * pending ATT procedure + * @param attr_handle The Handle of characteristic to perform att + * procedure on. + * @param cid L2CAP channel ID on which request has been + * received + * + * @return 0 on success; + * BLE_HS_EAUTHOR if no matching attribute is + * found on pending_attr_list. + */ +int ble_gatts_pending_req_auth(uint16_t conn_handle, uint16_t attr_handle, + uint16_t cid); + #ifdef __cplusplus } #endif diff --git a/nimble/host/src/ble_gatts.c b/nimble/host/src/ble_gatts.c index ee6a8a3c65..ac6451326c 100644 --- a/nimble/host/src/ble_gatts.c +++ b/nimble/host/src/ble_gatts.c @@ -2034,6 +2034,129 @@ ble_gatts_find_dsc(const ble_uuid_t *svc_uuid, const ble_uuid_t *chr_uuid, } } +int ble_gatts_pending_req_auth(uint16_t conn_handle, uint16_t attr_handle, + uint16_t cid) +{ + struct ble_att_prep_write_cmd *prep_req; + struct ble_att_write_req *write_req; + struct pending_attr *pend_attr; + struct pending_attr *attr; + uint8_t att_err; + int rc; + + /* Silence warnings */ + rc = 0; + att_err = 0; + pend_attr = NULL; + + ble_hs_lock(); + SLIST_FOREACH(attr, &pending_attr_list, next) { + if (attr->conn_handle == conn_handle && + attr->cid == cid) { + pend_attr = attr; + } + } + ble_hs_unlock(); + + if (!pend_attr) { + /* No matching attribute was found on pending_attr_list */ + return BLE_HS_ENOENT; + } + + if (attr_handle != 0) { + att_err = BLE_ATT_ERR_INSUFFICIENT_AUTHOR; + rc = BLE_HS_EAUTHOR; + ble_att_svr_tx_rsp(conn_handle, cid, rc, pend_attr->om, + pend_attr->att_opcode, att_err, attr_handle); + goto done; + } + + switch(pend_attr->att_opcode) { + case BLE_ATT_OP_READ_REQ: + rc = ble_att_svr_create_read_rsp(conn_handle,cid, pend_attr->om, rc, + BLE_ATT_OP_READ_REQ, + pend_attr->attr_handle, + pend_attr->offset, &att_err); + goto done; + + case BLE_ATT_OP_READ_BLOB_REQ: + rc = ble_att_svr_create_read_rsp(conn_handle,cid, pend_attr->om, rc, + BLE_ATT_OP_READ_BLOB_REQ, + pend_attr->attr_handle, + pend_attr->offset, &att_err); + goto done; + + case BLE_ATT_OP_WRITE_REQ: + write_req = (struct ble_att_write_req *)(pend_attr)->om->om_data; + rc = ble_att_svr_create_write_rsp(conn_handle, cid, &pend_attr->om, + rc, write_req, pend_attr->offset, + pend_attr->attr_handle, &att_err); + goto done; + + case BLE_ATT_OP_READ_MULT_REQ: + /* iterate through remaining requested handles */ + while ((OS_MBUF_PKTLEN(pend_attr->om) - pend_attr->offset) >= 2) { + os_mbuf_copydata(pend_attr->om, pend_attr->offset, 2, + &attr_handle); + pend_attr->offset += 2; + rc = ble_att_svr_check_author_perm(conn_handle, attr_handle, + &att_err, + BLE_ATT_ACCESS_OP_WRITE, cid); + if (rc == BLE_HS_EPENDING) { + return rc; + } + if (rc != 0) { + break; + } + } + + rc = ble_att_svr_create_read_mult_rsp(conn_handle, cid, &pend_attr->om, + rc, &att_err, &attr_handle); + goto done; + + case BLE_ATT_OP_READ_MULT_VAR_REQ: + /* iterate through remaining requested handles */ + while ((OS_MBUF_PKTLEN(pend_attr->om) - pend_attr->offset) >= 2) { + os_mbuf_copydata(pend_attr->om, pend_attr->offset, 2, + &attr_handle); + pend_attr->offset += 2; + rc = ble_att_svr_check_author_perm(conn_handle, attr_handle, + &att_err, + BLE_ATT_ACCESS_OP_WRITE, cid); + if (rc == BLE_HS_EPENDING) { + return rc; + } + if (rc != 0) { + break; + } + } + + rc = ble_att_svr_create_read_mult_var_len_rsp(conn_handle, cid, + &pend_attr->om, rc, + &att_err, &attr_handle); + goto done; + + case BLE_ATT_OP_PREP_WRITE_REQ: + prep_req = (struct ble_att_prep_write_cmd *)(pend_attr)->om->om_data; + rc = ble_att_svr_create_prep_write_rsp(conn_handle, cid, + &pend_attr->om, prep_req, rc, + att_err, attr_handle); + return rc; + } + +done: + ble_hs_lock(); + SLIST_FOREACH(attr, &pending_attr_list, next) { + if (attr->conn_handle == conn_handle && + attr->cid == cid) { + SLIST_REMOVE(&pending_attr_list, attr, pending_attr, next); + } + } + ble_hs_unlock(); + + return rc; +} + int ble_gatts_add_svcs(const struct ble_gatt_svc_def *svcs) {