| /* |
| * Copyright (c) 2016 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <errno.h> |
| #include <atomic.h> |
| |
| #include <zephyr.h> |
| #include <device.h> |
| |
| #define BT_DBG_ENABLED IS_ENABLED(NBLE_DEBUG_GAP) |
| #include <bluetooth/log.h> |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/conn.h> |
| |
| #include <misc/util.h> |
| |
| #include "gap_internal.h" |
| #include "conn_internal.h" |
| #include "conn.h" |
| |
| #define BT_SMP_IO_DISPLAY_ONLY 0x00 |
| #define BT_SMP_IO_DISPLAY_YESNO 0x01 |
| #define BT_SMP_IO_KEYBOARD_ONLY 0x02 |
| #define BT_SMP_IO_NO_INPUT_OUTPUT 0x03 |
| #define BT_SMP_IO_KEYBOARD_DISPLAY 0x04 |
| |
| #define BT_SMP_OOB_NOT_PRESENT 0x00 |
| #define BT_SMP_OOB_PRESENT 0x01 |
| |
| #define BT_SMP_MIN_ENC_KEY_SIZE 7 |
| #define BT_SMP_MAX_ENC_KEY_SIZE 16 |
| |
| enum { |
| SMP_FLAG_CFM_DELAYED, /* if confirm should be send when TK is valid */ |
| SMP_FLAG_ENC_PENDING, /* if waiting for an encryption change event */ |
| SMP_FLAG_KEYS_DISTR, /* if keys distribution phase is in progress */ |
| SMP_FLAG_PAIRING, /* if pairing is in progress */ |
| SMP_FLAG_TIMEOUT, /* if SMP timeout occurred */ |
| SMP_FLAG_SC, /* if LE Secure Connections is used */ |
| SMP_FLAG_PKEY_SEND, /* if should send Public Key when available */ |
| SMP_FLAG_DHKEY_PENDING, /* if waiting for local DHKey */ |
| SMP_FLAG_DHKEY_SEND, /* if should generate and send DHKey Check */ |
| SMP_FLAG_USER, /* if waiting for user input */ |
| SMP_FLAG_BOND, /* if bonding */ |
| SMP_FLAG_SC_DEBUG_KEY, /* if Secure Connection are using debug key */ |
| SMP_FLAG_SEC_REQ, /* if Security Request was sent/received */ |
| }; |
| |
| enum pairing_method { |
| JUST_WORKS, /* JustWorks pairing */ |
| PASSKEY_INPUT, /* Passkey Entry input */ |
| PASSKEY_DISPLAY, /* Passkey Entry display */ |
| PASSKEY_CONFIRM, /* Passkey confirm */ |
| PASSKEY_ROLE, /* Passkey Entry depends on role */ |
| }; |
| |
| struct bt_smp { |
| /* The channel this context is associated with (nble conn object)*/ |
| struct bt_conn *conn; |
| |
| /* Flags for SMP state machine */ |
| atomic_t flags; |
| |
| /* Type of method used for pairing */ |
| uint8_t method; |
| }; |
| |
| static struct bt_smp bt_smp_pool[CONFIG_BLUETOOTH_MAX_CONN]; |
| |
| static struct bt_smp *smp_chan_get(struct bt_conn *conn) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(bt_smp_pool); i++) { |
| struct bt_smp *smp = &bt_smp_pool[i]; |
| |
| if (smp->conn == conn) { |
| return smp; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void smp_reset(struct bt_smp *smp) |
| { |
| smp->flags = 0; |
| smp->method = 0; |
| smp->conn = NULL; |
| } |
| |
| void bt_smp_connected(struct bt_conn *conn) |
| { |
| struct bt_smp *smp = NULL; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(bt_smp_pool); i++) { |
| smp = &bt_smp_pool[i]; |
| |
| if (!smp->conn) { |
| break; |
| } |
| } |
| |
| /* Reset flags and states */ |
| smp_reset(smp); |
| |
| smp->conn = conn; |
| } |
| |
| void bt_smp_disconnected(struct bt_conn *conn) |
| { |
| struct bt_smp *smp = smp_chan_get(conn); |
| |
| if (smp) { |
| smp_reset(smp); |
| } |
| } |
| |
| /* based on table 2.8 Core Spec 2.3.5.1 Vol. 3 Part H */ |
| static const uint8_t gen_method_legacy[5 /* remote */][5 /* local */] = { |
| { JUST_WORKS, JUST_WORKS, PASSKEY_INPUT, JUST_WORKS, PASSKEY_INPUT }, |
| { JUST_WORKS, JUST_WORKS, PASSKEY_INPUT, JUST_WORKS, PASSKEY_INPUT }, |
| { PASSKEY_DISPLAY, PASSKEY_DISPLAY, PASSKEY_INPUT, JUST_WORKS, |
| PASSKEY_DISPLAY }, |
| { JUST_WORKS, JUST_WORKS, JUST_WORKS, JUST_WORKS, JUST_WORKS }, |
| { PASSKEY_DISPLAY, PASSKEY_DISPLAY, PASSKEY_INPUT, JUST_WORKS, |
| PASSKEY_ROLE }, |
| }; |
| |
| static uint8_t get_io_capa(void) |
| { |
| if (!nble.auth) { |
| return BT_SMP_IO_NO_INPUT_OUTPUT; |
| } |
| |
| /* Passkey Confirmation is valid only for LE SC */ |
| if (nble.auth->passkey_display && nble.auth->passkey_entry && |
| nble.auth->passkey_confirm) { |
| return BT_SMP_IO_KEYBOARD_DISPLAY; |
| } |
| |
| /* DisplayYesNo is useful only for LE SC */ |
| if (nble.auth->passkey_display && |
| nble.auth->passkey_confirm) { |
| return BT_SMP_IO_DISPLAY_YESNO; |
| } |
| |
| if (nble.auth->passkey_entry) { |
| return BT_SMP_IO_KEYBOARD_ONLY; |
| } |
| |
| if (nble.auth->passkey_display) { |
| return BT_SMP_IO_DISPLAY_ONLY; |
| } |
| |
| return BT_SMP_IO_NO_INPUT_OUTPUT; |
| } |
| |
| static uint8_t legacy_get_pair_method(struct bt_smp *smp, uint8_t remote_io) |
| { |
| uint8_t local_io = get_io_capa(); |
| uint8_t method; |
| |
| if (remote_io > BT_SMP_IO_KEYBOARD_DISPLAY) |
| return JUST_WORKS; |
| |
| method = gen_method_legacy[remote_io][local_io]; |
| |
| /* if both sides have KeyboardDisplay capabilities, initiator displays |
| * and responder inputs |
| */ |
| if (method == PASSKEY_ROLE) { |
| if (smp->conn->role == BT_HCI_ROLE_MASTER) { |
| method = PASSKEY_DISPLAY; |
| } else { |
| method = PASSKEY_INPUT; |
| } |
| } |
| |
| BT_DBG("local_io %u remote_io %u method %u", local_io, remote_io, |
| method); |
| |
| return method; |
| } |
| |
| static uint8_t get_auth(uint8_t auth) |
| { |
| if (get_io_capa() == BT_SMP_IO_NO_INPUT_OUTPUT) { |
| auth &= ~(BT_SMP_AUTH_MITM); |
| } else { |
| auth |= BT_SMP_AUTH_MITM; |
| } |
| |
| return auth; |
| } |
| |
| static uint8_t legacy_pairing_req(struct bt_smp *smp, |
| const struct nble_sec_param *par) |
| { |
| struct nble_sm_pairing_response_req req; |
| |
| smp->method = legacy_get_pair_method(smp, par->io_capabilities); |
| |
| BT_DBG("method %u io_caps %u", smp->method, par->io_capabilities); |
| |
| /* ask for consent if pairing is not due to sending SecReq*/ |
| if (smp->method == JUST_WORKS && |
| !atomic_test_bit(&smp->flags, SMP_FLAG_SEC_REQ) && |
| nble.auth && nble.auth->pairing_confirm) { |
| atomic_set_bit(&smp->flags, SMP_FLAG_USER); |
| nble.auth->pairing_confirm(smp->conn); |
| return 0; |
| } |
| |
| req.conn = smp->conn; |
| req.conn_handle = smp->conn->handle; |
| req.params.auth = get_auth(par->auth); |
| req.params.io_capabilities = get_io_capa(); |
| req.params.max_key_size = par->max_key_size; |
| req.params.min_key_size = par->min_key_size; |
| req.params.oob_flag = BT_SMP_OOB_NOT_PRESENT; |
| |
| nble_sm_pairing_response_req(&req); |
| |
| return 0; |
| } |
| |
| void on_nble_sm_pairing_request_evt(const struct nble_sm_pairing_request_evt *evt) |
| { |
| struct bt_conn *conn; |
| struct bt_smp *smp; |
| |
| BT_DBG(""); |
| |
| conn = bt_conn_lookup_handle(evt->conn_handle); |
| if (!conn) { |
| BT_ERR("Unable to find conn for handle %u", evt->conn_handle); |
| return; |
| } |
| |
| smp = smp_chan_get(conn); |
| if (!smp) { |
| BT_ERR("No smp"); |
| bt_conn_unref(conn); |
| return; |
| } |
| |
| atomic_set_bit(&smp->flags, SMP_FLAG_PAIRING); |
| |
| legacy_pairing_req(smp, &evt->sec_param); |
| |
| bt_conn_unref(conn); |
| } |
| |
| static void nble_start_security(struct bt_conn *conn) |
| { |
| struct nble_sm_security_req req = { 0 }; |
| |
| req.conn = conn, |
| req.conn_handle = conn->handle, |
| req.params.auth = get_auth(BT_SMP_AUTH_BONDING | BT_SMP_AUTH_MITM); |
| req.params.io_capabilities = get_io_capa(); |
| req.params.max_key_size = BT_SMP_MAX_ENC_KEY_SIZE; |
| req.params.min_key_size = BT_SMP_MIN_ENC_KEY_SIZE; |
| req.params.oob_flag = BT_SMP_OOB_NOT_PRESENT; |
| |
| /** |
| * nble stack generates either a smp security or pairing request |
| * depending on role. |
| */ |
| nble_sm_security_req(&req); |
| } |
| |
| int bt_smp_send_pairing_req(struct bt_conn *conn) |
| { |
| struct bt_smp *smp; |
| |
| BT_DBG(""); |
| |
| smp = smp_chan_get(conn); |
| if (!smp) { |
| return -ENOTCONN; |
| } |
| |
| /* pairing is in progress */ |
| if (atomic_test_bit(&smp->flags, SMP_FLAG_PAIRING)) { |
| return -EBUSY; |
| } |
| |
| /* TODO: Verify sec level reachable */ |
| |
| nble_start_security(conn); |
| |
| atomic_set_bit(&smp->flags, SMP_FLAG_PAIRING); |
| |
| return 0; |
| } |
| int bt_smp_send_security_req(struct bt_conn *conn) |
| { |
| struct bt_smp *smp; |
| |
| BT_DBG(""); |
| |
| smp = smp_chan_get(conn); |
| if (!smp) { |
| return -ENOTCONN; |
| } |
| |
| /* pairing is in progress */ |
| if (atomic_test_bit(&smp->flags, SMP_FLAG_PAIRING)) { |
| return -EBUSY; |
| } |
| |
| /* TODO: Verify sec level reachable */ |
| |
| nble_start_security(conn); |
| |
| atomic_set_bit(&smp->flags, SMP_FLAG_SEC_REQ); |
| |
| return 0; |
| } |
| |
| void on_nble_sm_security_request_evt(const struct nble_sm_security_request_evt *evt) |
| { |
| struct bt_conn *conn; |
| struct bt_smp *smp; |
| |
| BT_DBG(""); |
| |
| conn = bt_conn_lookup_handle(evt->conn_handle); |
| if (!conn) { |
| BT_ERR("Unable to find conn for handle %u", evt->conn_handle); |
| return; |
| } |
| |
| smp = smp_chan_get(conn); |
| if (!smp) { |
| BT_ERR("No smp"); |
| bt_conn_unref(conn); |
| return; |
| } |
| |
| BT_DBG("conn %p remote_io %u auth %u", conn, |
| evt->sec_param.io_capabilities, evt->sec_param.auth); |
| |
| smp->method = legacy_get_pair_method(smp, |
| evt->sec_param.io_capabilities); |
| |
| if (smp->method == JUST_WORKS && |
| nble.auth && nble.auth->pairing_confirm) { |
| atomic_set_bit(&smp->flags, SMP_FLAG_USER); |
| nble.auth->pairing_confirm(smp->conn); |
| goto done; |
| } |
| |
| bt_smp_send_pairing_req(conn); |
| |
| done: |
| atomic_set_bit(&smp->flags, SMP_FLAG_SEC_REQ); |
| bt_conn_unref(conn); |
| } |
| |
| void on_nble_sm_common_rsp(const struct nble_sm_common_rsp *rsp) |
| { |
| if (rsp->status) { |
| BT_ERR("GAP SM request failed: conn %p err %d", rsp->conn, |
| rsp->status); |
| |
| /* TODO: Handle error */ |
| return; |
| } |
| } |
| |
| void on_nble_sm_status_evt(const struct nble_sm_status_evt *ev) |
| { |
| struct bt_conn *conn; |
| struct bt_smp *smp; |
| |
| conn = bt_conn_lookup_handle(ev->conn_handle); |
| if (!conn) { |
| BT_ERR("Unable to find conn for handle %u", ev->conn_handle); |
| return; |
| } |
| |
| smp = smp_chan_get(conn); |
| if (!smp) { |
| BT_ERR("No smp for conn %p", conn); |
| return; |
| } |
| |
| BT_DBG("conn %p status %d evt_type %d sec_level %d enc_size %u", |
| conn, ev->status, ev->evt_type, ev->enc_link_sec.sec_level, |
| ev->enc_link_sec.enc_size); |
| |
| switch (ev->evt_type) { |
| case NBLE_GAP_SM_EVT_BONDING_COMPLETE: |
| BT_DBG("Bonding complete"); |
| if (ev->status) { |
| if (nble.auth && nble.auth->cancel) { |
| nble.auth->cancel(conn); |
| } |
| } |
| smp_reset(smp); |
| break; |
| case NBLE_GAP_SM_EVT_LINK_ENCRYPTED: |
| BT_DBG("Link encrypted"); |
| break; |
| case NBLE_GAP_SM_EVT_LINK_SECURITY_CHANGE: |
| BT_DBG("Security change"); |
| break; |
| default: |
| BT_ERR("Unknown event %d", ev->evt_type); |
| break; |
| } |
| |
| bt_conn_unref(conn); |
| } |
| |
| void on_nble_sm_passkey_disp_evt(const struct nble_sm_passkey_disp_evt *ev) |
| { |
| struct bt_conn *conn; |
| |
| conn = bt_conn_lookup_handle(ev->conn_handle); |
| if (!conn) { |
| BT_ERR("Unable to find conn for handle %u", ev->conn_handle); |
| return; |
| } |
| |
| BT_DBG("conn %p passkey %u", conn, ev->passkey); |
| |
| /* TODO: Check shall we store io_caps globally */ |
| if (get_io_capa() == BT_SMP_IO_DISPLAY_YESNO) { |
| if (nble.auth && nble.auth->passkey_confirm) { |
| nble.auth->passkey_confirm(conn, ev->passkey); |
| } |
| } else { |
| if (nble.auth && nble.auth->passkey_display) { |
| nble.auth->passkey_display(conn, ev->passkey); |
| } |
| } |
| |
| bt_conn_unref(conn); |
| } |
| |
| void on_nble_sm_passkey_req_evt(const struct nble_sm_passkey_req_evt *ev) |
| { |
| struct bt_conn *conn; |
| struct bt_smp *smp; |
| |
| conn = bt_conn_lookup_handle(ev->conn_handle); |
| if (!conn) { |
| BT_ERR("Unable to find conn for handle %u", ev->conn_handle); |
| return; |
| } |
| |
| smp = smp_chan_get(conn); |
| if (!smp) { |
| bt_conn_unref(conn); |
| return; |
| } |
| |
| BT_DBG("conn %p key_type %u", conn, ev->key_type); |
| |
| /* Set user input expected flag */ |
| atomic_set_bit(&smp->flags, SMP_FLAG_USER); |
| |
| if (ev->key_type == NBLE_GAP_SM_PK_PASSKEY) { |
| if (nble.auth && nble.auth->passkey_entry) { |
| nble.auth->passkey_entry(conn); |
| } |
| } |
| |
| bt_conn_unref(conn); |
| } |
| |
| static void nble_security_reply(struct bt_conn *conn, |
| struct nble_sm_passkey *par) |
| { |
| struct nble_sm_passkey_reply_req rsp = { |
| .conn = conn, |
| .conn_handle = conn->handle, |
| }; |
| |
| memcpy(&rsp.params, par, sizeof(*par)); |
| |
| nble_sm_passkey_reply_req(&rsp); |
| } |
| |
| static int sm_error(struct bt_conn *conn, uint8_t reason) |
| { |
| struct nble_sm_passkey params; |
| |
| params.type = NBLE_GAP_SM_REJECT; |
| params.reason = reason; |
| |
| nble_security_reply(conn, ¶ms); |
| |
| return 0; |
| } |
| |
| static void legacy_passkey_entry(struct bt_smp *smp, unsigned int passkey) |
| { |
| struct nble_sm_passkey pkey = { |
| .type = NBLE_SM_PK_PASSKEY, |
| .passkey = passkey, |
| }; |
| |
| BT_DBG("passkey %u", passkey); |
| |
| nble_security_reply(smp->conn, &pkey); |
| } |
| |
| int bt_smp_auth_cancel(struct bt_conn *conn) |
| { |
| BT_DBG(""); |
| |
| return sm_error(conn, BT_SMP_ERR_PASSKEY_ENTRY_FAILED); |
| } |
| |
| int bt_smp_auth_passkey_entry(struct bt_conn *conn, unsigned int passkey) |
| { |
| struct bt_smp *smp; |
| |
| BT_DBG("passkey %u", passkey); |
| |
| smp = smp_chan_get(conn); |
| if (!smp) { |
| return -EINVAL; |
| } |
| |
| if (!atomic_test_and_clear_bit(&smp->flags, SMP_FLAG_USER)) { |
| BT_ERR("Not expected user input"); |
| return -EINVAL; |
| } |
| |
| if (!atomic_test_bit(&smp->flags, SMP_FLAG_SC)) { |
| legacy_passkey_entry(smp, passkey); |
| return 0; |
| } |
| |
| return 0; |
| } |
| |
| int bt_smp_auth_pairing_confirm(struct bt_conn *conn) |
| { |
| struct bt_smp *smp; |
| struct nble_sm_pairing_response_req req; |
| |
| BT_DBG(""); |
| |
| smp = smp_chan_get(conn); |
| if (!smp) { |
| return -ENOTCONN; |
| } |
| |
| if (!atomic_test_and_clear_bit(&smp->flags, SMP_FLAG_USER)) { |
| BT_ERR("Not expected user input"); |
| return -EINVAL; |
| } |
| |
| if (conn->role == BT_CONN_ROLE_MASTER) { |
| bt_smp_send_pairing_req(conn); |
| } else { |
| req.conn = conn; |
| req.conn_handle = conn->handle; |
| req.params.auth = get_auth(BT_SMP_AUTH_BONDING); |
| req.params.io_capabilities = get_io_capa(); |
| req.params.max_key_size = BT_SMP_MAX_ENC_KEY_SIZE; |
| req.params.min_key_size = BT_SMP_MIN_ENC_KEY_SIZE; |
| req.params.oob_flag = BT_SMP_OOB_NOT_PRESENT; |
| |
| nble_sm_pairing_response_req(&req); |
| } |
| |
| return 0; |
| } |
| |
| int bt_smp_init(void) |
| { |
| BT_DBG(""); |
| |
| nble_get_bda_req(NULL); |
| |
| return 0; |
| } |