diff options
Diffstat (limited to 'src/libs/mynewt-nimble/nimble/host/src/ble_sm.c')
-rw-r--r-- | src/libs/mynewt-nimble/nimble/host/src/ble_sm.c | 2813 |
1 files changed, 2813 insertions, 0 deletions
diff --git a/src/libs/mynewt-nimble/nimble/host/src/ble_sm.c b/src/libs/mynewt-nimble/nimble/host/src/ble_sm.c new file mode 100644 index 00000000..cfd80fcb --- /dev/null +++ b/src/libs/mynewt-nimble/nimble/host/src/ble_sm.c @@ -0,0 +1,2813 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * L2CAP Security Manager (channel ID = 6). + * + * Design overview: + * + * L2CAP sm procedures are initiated by the application via function calls. + * Such functions return when either of the following happens: + * + * (1) The procedure completes (success or failure). + * (2) The procedure cannot proceed until a BLE peer responds. + * + * For (1), the result of the procedure if fully indicated by the function + * return code. + * For (2), the procedure result is indicated by an application-configured + * callback. The callback is executed when the procedure completes. + * + * Notes on thread-safety: + * 1. The ble_hs mutex must never be locked when an application callback is + * executed. A callback is free to initiate additional host procedures. + * 2. Keep the host mutex locked whenever: + * o A proc entry is read from or written to. + * o The proc list is read or modified. + */ + +#include <string.h> +#include <errno.h> +#include "nimble/ble.h" +#include "nimble/nimble_opt.h" +#include "host/ble_sm.h" +#include "ble_hs_priv.h" + +#if NIMBLE_BLE_SM + +/** Procedure timeout; 30 seconds. */ +#define BLE_SM_TIMEOUT_MS (30000) + +STAILQ_HEAD(ble_sm_proc_list, ble_sm_proc); + +typedef void ble_sm_rx_fn(uint16_t conn_handle, struct os_mbuf **om, + struct ble_sm_result *res); + +static ble_sm_rx_fn ble_sm_rx_noop; +static ble_sm_rx_fn ble_sm_pair_req_rx; +static ble_sm_rx_fn ble_sm_pair_rsp_rx; +static ble_sm_rx_fn ble_sm_confirm_rx; +static ble_sm_rx_fn ble_sm_random_rx; +static ble_sm_rx_fn ble_sm_fail_rx; +static ble_sm_rx_fn ble_sm_enc_info_rx; +static ble_sm_rx_fn ble_sm_master_id_rx; +static ble_sm_rx_fn ble_sm_id_info_rx; +static ble_sm_rx_fn ble_sm_id_addr_info_rx; +static ble_sm_rx_fn ble_sm_sign_info_rx; +static ble_sm_rx_fn ble_sm_sec_req_rx; + +static ble_sm_rx_fn * const ble_sm_dispatch[] = { + [BLE_SM_OP_PAIR_REQ] = ble_sm_pair_req_rx, + [BLE_SM_OP_PAIR_RSP] = ble_sm_pair_rsp_rx, + [BLE_SM_OP_PAIR_CONFIRM] = ble_sm_confirm_rx, + [BLE_SM_OP_PAIR_RANDOM] = ble_sm_random_rx, + [BLE_SM_OP_PAIR_FAIL] = ble_sm_fail_rx, + [BLE_SM_OP_ENC_INFO] = ble_sm_enc_info_rx, + [BLE_SM_OP_MASTER_ID] = ble_sm_master_id_rx, + [BLE_SM_OP_IDENTITY_INFO] = ble_sm_id_info_rx, + [BLE_SM_OP_IDENTITY_ADDR_INFO] = ble_sm_id_addr_info_rx, + [BLE_SM_OP_SIGN_INFO] = ble_sm_sign_info_rx, + [BLE_SM_OP_SEC_REQ] = ble_sm_sec_req_rx, + [BLE_SM_OP_PAIR_KEYPRESS_NOTIFY] = ble_sm_rx_noop, +#if MYNEWT_VAL(BLE_SM_SC) + [BLE_SM_OP_PAIR_PUBLIC_KEY] = ble_sm_sc_public_key_rx, + [BLE_SM_OP_PAIR_DHKEY_CHECK] = ble_sm_sc_dhkey_check_rx, +#else + [BLE_SM_OP_PAIR_PUBLIC_KEY] = ble_sm_rx_noop, + [BLE_SM_OP_PAIR_DHKEY_CHECK] = ble_sm_rx_noop, +#endif +}; + +struct hci_start_encrypt +{ + uint16_t connection_handle; + uint16_t encrypted_diversifier; + uint64_t random_number; + uint8_t long_term_key[16]; +}; + +typedef void ble_sm_state_fn(struct ble_sm_proc *proc, + struct ble_sm_result *res, void *arg); + +static ble_sm_state_fn ble_sm_pair_exec; +static ble_sm_state_fn ble_sm_confirm_exec; +static ble_sm_state_fn ble_sm_random_exec; +static ble_sm_state_fn ble_sm_ltk_start_exec; +static ble_sm_state_fn ble_sm_ltk_restore_exec; +static ble_sm_state_fn ble_sm_enc_start_exec; +static ble_sm_state_fn ble_sm_enc_restore_exec; +static ble_sm_state_fn ble_sm_key_exch_exec; +static ble_sm_state_fn ble_sm_sec_req_exec; + +static ble_sm_state_fn * const +ble_sm_state_dispatch[BLE_SM_PROC_STATE_CNT] = { + [BLE_SM_PROC_STATE_PAIR] = ble_sm_pair_exec, + [BLE_SM_PROC_STATE_CONFIRM] = ble_sm_confirm_exec, + [BLE_SM_PROC_STATE_RANDOM] = ble_sm_random_exec, + [BLE_SM_PROC_STATE_LTK_START] = ble_sm_ltk_start_exec, + [BLE_SM_PROC_STATE_LTK_RESTORE] = ble_sm_ltk_restore_exec, + [BLE_SM_PROC_STATE_ENC_START] = ble_sm_enc_start_exec, + [BLE_SM_PROC_STATE_ENC_RESTORE] = ble_sm_enc_restore_exec, + [BLE_SM_PROC_STATE_KEY_EXCH] = ble_sm_key_exch_exec, + [BLE_SM_PROC_STATE_SEC_REQ] = ble_sm_sec_req_exec, +#if MYNEWT_VAL(BLE_SM_SC) + [BLE_SM_PROC_STATE_PUBLIC_KEY] = ble_sm_sc_public_key_exec, + [BLE_SM_PROC_STATE_DHKEY_CHECK] = ble_sm_sc_dhkey_check_exec, +#else + [BLE_SM_PROC_STATE_PUBLIC_KEY] = NULL, + [BLE_SM_PROC_STATE_DHKEY_CHECK] = NULL, +#endif +}; + +static os_membuf_t ble_sm_proc_mem[ + OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_SM_MAX_PROCS), + sizeof (struct ble_sm_proc)) +]; + +static struct os_mempool ble_sm_proc_pool; + +/* Maintains the list of active security manager procedures. */ +static struct ble_sm_proc_list ble_sm_procs; + +static void ble_sm_pair_cfg(struct ble_sm_proc *proc); + + +/***************************************************************************** + * $debug * + *****************************************************************************/ + +#if MYNEWT_VAL(BLE_HS_DEBUG) + +static uint8_t ble_sm_dbg_next_pair_rand[16]; +static uint8_t ble_sm_dbg_next_pair_rand_set; +static uint16_t ble_sm_dbg_next_ediv; +static uint8_t ble_sm_dbg_next_ediv_set; +static uint64_t ble_sm_dbg_next_master_id_rand; +static uint8_t ble_sm_dbg_next_master_id_rand_set; +static uint8_t ble_sm_dbg_next_ltk[16]; +static uint8_t ble_sm_dbg_next_ltk_set; +static uint8_t ble_sm_dbg_next_csrk[16]; +static uint8_t ble_sm_dbg_next_csrk_set; + +void +ble_sm_dbg_set_next_pair_rand(uint8_t *next_pair_rand) +{ + memcpy(ble_sm_dbg_next_pair_rand, next_pair_rand, + sizeof ble_sm_dbg_next_pair_rand); + ble_sm_dbg_next_pair_rand_set = 1; +} + +void +ble_sm_dbg_set_next_ediv(uint16_t next_ediv) +{ + ble_sm_dbg_next_ediv = next_ediv; + ble_sm_dbg_next_ediv_set = 1; +} + +void +ble_sm_dbg_set_next_master_id_rand(uint64_t next_master_id_rand) +{ + ble_sm_dbg_next_master_id_rand = next_master_id_rand; + ble_sm_dbg_next_master_id_rand_set = 1; +} + +void +ble_sm_dbg_set_next_ltk(uint8_t *next_ltk) +{ + memcpy(ble_sm_dbg_next_ltk, next_ltk, + sizeof ble_sm_dbg_next_ltk); + ble_sm_dbg_next_ltk_set = 1; +} + +void +ble_sm_dbg_set_next_csrk(uint8_t *next_csrk) +{ + memcpy(ble_sm_dbg_next_csrk, next_csrk, + sizeof ble_sm_dbg_next_csrk); + ble_sm_dbg_next_csrk_set = 1; +} + +#endif + +static void +ble_sm_dbg_assert_no_cycles(void) +{ +#if MYNEWT_VAL(BLE_HS_DEBUG) + ble_sm_num_procs(); +#endif +} + +static void +ble_sm_dbg_assert_not_inserted(struct ble_sm_proc *proc) +{ +#if MYNEWT_VAL(BLE_HS_DEBUG) + struct ble_sm_proc *cur; + + STAILQ_FOREACH(cur, &ble_sm_procs, next) { + BLE_HS_DBG_ASSERT(cur != proc); + } +#endif +} + +/***************************************************************************** + * $misc * + *****************************************************************************/ + +/** + * Calculates the number of active SM procedures. + */ +int +ble_sm_num_procs(void) +{ + struct ble_sm_proc *proc; + int cnt; + + cnt = 0; + STAILQ_FOREACH(proc, &ble_sm_procs, next) { + BLE_HS_DBG_ASSERT(cnt < MYNEWT_VAL(BLE_SM_MAX_PROCS)); + cnt++; + } + + return cnt; +} + +int +ble_sm_gen_pair_rand(uint8_t *pair_rand) +{ + int rc; + +#if MYNEWT_VAL(BLE_HS_DEBUG) + if (ble_sm_dbg_next_pair_rand_set) { + ble_sm_dbg_next_pair_rand_set = 0; + memcpy(pair_rand, ble_sm_dbg_next_pair_rand, + sizeof ble_sm_dbg_next_pair_rand); + return 0; + } +#endif + + rc = ble_hs_hci_util_rand(pair_rand, 16); + if (rc != 0) { + return rc; + } + + return 0; +} + +static int +ble_sm_gen_ediv(struct ble_sm_master_id *master_id) +{ + int rc; + +#if MYNEWT_VAL(BLE_HS_DEBUG) + if (ble_sm_dbg_next_ediv_set) { + ble_sm_dbg_next_ediv_set = 0; + master_id->ediv = ble_sm_dbg_next_ediv; + return 0; + } +#endif + + rc = ble_hs_hci_util_rand(&master_id->ediv, sizeof master_id->ediv); + if (rc != 0) { + return rc; + } + + return 0; +} + +static int +ble_sm_gen_master_id_rand(struct ble_sm_master_id *master_id) +{ + int rc; + +#if MYNEWT_VAL(BLE_HS_DEBUG) + if (ble_sm_dbg_next_master_id_rand_set) { + ble_sm_dbg_next_master_id_rand_set = 0; + master_id->rand_val = ble_sm_dbg_next_master_id_rand; + return 0; + } +#endif + + rc = ble_hs_hci_util_rand(&master_id->rand_val, sizeof master_id->rand_val); + if (rc != 0) { + return rc; + } + + return 0; +} + +static int +ble_sm_gen_ltk(struct ble_sm_proc *proc, uint8_t *ltk) +{ + int rc; + +#if MYNEWT_VAL(BLE_HS_DEBUG) + if (ble_sm_dbg_next_ltk_set) { + ble_sm_dbg_next_ltk_set = 0; + memcpy(ltk, ble_sm_dbg_next_ltk, + sizeof ble_sm_dbg_next_ltk); + return 0; + } +#endif + + rc = ble_hs_hci_util_rand(ltk, proc->key_size); + if (rc != 0) { + return rc; + } + + /* Ensure proper key size */ + memset(ltk + proc->key_size, 0, sizeof proc->ltk - proc->key_size); + + return 0; +} + +static int +ble_sm_gen_csrk(struct ble_sm_proc *proc, uint8_t *csrk) +{ + int rc; + +#if MYNEWT_VAL(BLE_HS_DEBUG) + if (ble_sm_dbg_next_csrk_set) { + ble_sm_dbg_next_csrk_set = 0; + memcpy(csrk, ble_sm_dbg_next_csrk, + sizeof ble_sm_dbg_next_csrk); + return 0; + } +#endif + + rc = ble_hs_hci_util_rand(csrk, 16); + if (rc != 0) { + return rc; + } + + return 0; +} + +static void +ble_sm_proc_set_timer(struct ble_sm_proc *proc) +{ + proc->exp_os_ticks = ble_npl_time_get() + + ble_npl_time_ms_to_ticks32(BLE_SM_TIMEOUT_MS); + ble_hs_timer_resched(); +} + +static ble_sm_rx_fn * +ble_sm_dispatch_get(uint8_t op) +{ + if (op >= sizeof ble_sm_dispatch / sizeof ble_sm_dispatch[0]) { + return NULL; + } + + return ble_sm_dispatch[op]; +} + +/** + * Allocates a proc entry. + * + * @return An entry on success; null on failure. + */ +static struct ble_sm_proc * +ble_sm_proc_alloc(void) +{ + struct ble_sm_proc *proc; + + proc = os_memblock_get(&ble_sm_proc_pool); + if (proc != NULL) { + memset(proc, 0, sizeof *proc); + } + + return proc; +} + +/** + * Frees the specified proc entry. No-state if passed a null pointer. + */ +static void +ble_sm_proc_free(struct ble_sm_proc *proc) +{ + int rc; + + if (proc != NULL) { + ble_sm_dbg_assert_not_inserted(proc); +#if MYNEWT_VAL(BLE_HS_DEBUG) + memset(proc, 0xff, sizeof *proc); +#endif + rc = os_memblock_put(&ble_sm_proc_pool, proc); + BLE_HS_DBG_ASSERT_EVAL(rc == 0); + } +} + +static void +ble_sm_proc_remove(struct ble_sm_proc *proc, + struct ble_sm_proc *prev) +{ + if (prev == NULL) { + BLE_HS_DBG_ASSERT(STAILQ_FIRST(&ble_sm_procs) == proc); + STAILQ_REMOVE_HEAD(&ble_sm_procs, next); + } else { + BLE_HS_DBG_ASSERT(STAILQ_NEXT(prev, next) == proc); + STAILQ_REMOVE_AFTER(&ble_sm_procs, prev, next); + } + + ble_sm_dbg_assert_no_cycles(); +} + +static void +ble_sm_update_sec_state(uint16_t conn_handle, int encrypted, + int authenticated, int bonded, int key_size) +{ + struct ble_hs_conn *conn; + + conn = ble_hs_conn_find(conn_handle); + if (conn != NULL) { + conn->bhc_sec_state.encrypted = encrypted; + + /* Authentication and bonding are never revoked from a secure link */ + if (authenticated) { + conn->bhc_sec_state.authenticated = 1; + } + if (bonded) { + conn->bhc_sec_state.bonded = 1; + } + + if (key_size) { + conn->bhc_sec_state.key_size = key_size; + } + } +} + +static void +ble_sm_fill_store_value(const ble_addr_t *peer_addr, + int authenticated, + int sc, + struct ble_sm_keys *keys, + struct ble_store_value_sec *value_sec) +{ + memset(value_sec, 0, sizeof *value_sec); + + value_sec->peer_addr = *peer_addr; + + if (keys->ediv_rand_valid && keys->ltk_valid) { + value_sec->key_size = keys->key_size; + value_sec->ediv = keys->ediv; + value_sec->rand_num = keys->rand_val; + + memcpy(value_sec->ltk, keys->ltk, sizeof value_sec->ltk); + value_sec->ltk_present = 1; + + value_sec->authenticated = !!authenticated; + value_sec->sc = !!sc; + } + + if (keys->irk_valid) { + memcpy(value_sec->irk, keys->irk, sizeof value_sec->irk); + value_sec->irk_present = 1; + } + + if (keys->csrk_valid) { + memcpy(value_sec->csrk, keys->csrk, sizeof value_sec->csrk); + value_sec->csrk_present = 1; + } +} + +void +ble_sm_ia_ra(struct ble_sm_proc *proc, + uint8_t *out_iat, uint8_t *out_ia, + uint8_t *out_rat, uint8_t *out_ra) +{ + struct ble_hs_conn_addrs addrs; + struct ble_hs_conn *conn; + + conn = ble_hs_conn_find_assert(proc->conn_handle); + + ble_hs_conn_addrs(conn, &addrs); + + if (proc->flags & BLE_SM_PROC_F_INITIATOR) { + *out_iat = addrs.our_ota_addr.type; + memcpy(out_ia, addrs.our_ota_addr.val, 6); + + *out_rat = addrs.peer_ota_addr.type; + memcpy(out_ra, addrs.peer_ota_addr.val, 6); + } else { + *out_iat = addrs.peer_ota_addr.type; + memcpy(out_ia, addrs.peer_ota_addr.val, 6); + + *out_rat = addrs.our_ota_addr.type; + memcpy(out_ra, addrs.our_ota_addr.val, 6); + } +} + +static void +ble_sm_persist_keys(struct ble_sm_proc *proc) +{ + struct ble_store_value_sec value_sec; + struct ble_hs_conn *conn; + ble_addr_t peer_addr; + int authenticated; + int identity_ev = 0; + int sc; + + ble_hs_lock(); + + conn = ble_hs_conn_find(proc->conn_handle); + BLE_HS_DBG_ASSERT(conn != NULL); + + /* If we got an identity address, use that for key storage. */ + if (proc->peer_keys.addr_valid) { + peer_addr.type = proc->peer_keys.addr_type; + memcpy(peer_addr.val, proc->peer_keys.addr, sizeof peer_addr.val); + + conn->bhc_peer_addr = peer_addr; + + /* Update identity address in conn. + * If peer's rpa address is set then it means that the peer's address + * is an identity address. The peer's address type has to be + * set as 'ID' to allow resolve 'id' and 'ota' addresses properly in + * conn info. + */ + if (memcmp(BLE_ADDR_ANY->val, &conn->bhc_peer_rpa_addr.val, 6) != 0) { + switch (peer_addr.type) { + case BLE_ADDR_PUBLIC: + case BLE_ADDR_PUBLIC_ID: + conn->bhc_peer_addr.type = BLE_ADDR_PUBLIC_ID; + break; + + case BLE_ADDR_RANDOM: + case BLE_ADDR_RANDOM_ID: + conn->bhc_peer_addr.type = BLE_ADDR_RANDOM_ID; + break; + } + + identity_ev = 1; + } + } else { + peer_addr = conn->bhc_peer_addr; + peer_addr.type = + ble_hs_misc_peer_addr_type_to_id(conn->bhc_peer_addr.type); + } + + ble_hs_unlock(); + + if (identity_ev) { + ble_gap_identity_event(proc->conn_handle); + } + + authenticated = proc->flags & BLE_SM_PROC_F_AUTHENTICATED; + sc = proc->flags & BLE_SM_PROC_F_SC; + + ble_sm_fill_store_value(&peer_addr, authenticated, sc, &proc->our_keys, + &value_sec); + ble_store_write_our_sec(&value_sec); + + ble_sm_fill_store_value(&peer_addr, authenticated, sc, &proc->peer_keys, + &value_sec); + ble_store_write_peer_sec(&value_sec); +} + +static int +ble_sm_proc_matches(struct ble_sm_proc *proc, uint16_t conn_handle, + uint8_t state, int is_initiator) +{ + int proc_is_initiator; + + if (conn_handle != proc->conn_handle) { + return 0; + } + + if (state != BLE_SM_PROC_STATE_NONE && state != proc->state) { + return 0; + } + + proc_is_initiator = !!(proc->flags & BLE_SM_PROC_F_INITIATOR); + if (is_initiator != -1 && is_initiator != proc_is_initiator) { + return 0; + } + + return 1; +} + +/** + * Searches the main proc list for an entry whose connection handle and state + * code match those specified. + * + * @param conn_handle The connection handle to match against. + * @param state The state code to match against. + * @param is_initiator Matches on the proc's initiator flag: + * 0=non-initiator only + * 1=initiator only + * -1=don't care + * @param out_prev On success, the entry previous to the result is + * written here. + * + * @return The matching proc entry on success; + * null on failure. + */ +struct ble_sm_proc * +ble_sm_proc_find(uint16_t conn_handle, uint8_t state, int is_initiator, + struct ble_sm_proc **out_prev) +{ + struct ble_sm_proc *proc; + struct ble_sm_proc *prev; + + BLE_HS_DBG_ASSERT(ble_hs_locked_by_cur_task()); + + prev = NULL; + STAILQ_FOREACH(proc, &ble_sm_procs, next) { + if (ble_sm_proc_matches(proc, conn_handle, state, is_initiator)) { + if (out_prev != NULL) { + *out_prev = prev; + } + break; + } + + prev = proc; + } + + return proc; +} + +static void +ble_sm_insert(struct ble_sm_proc *proc) +{ +#if MYNEWT_VAL(BLE_HS_DEBUG) + struct ble_sm_proc *cur; + + STAILQ_FOREACH(cur, &ble_sm_procs, next) { + BLE_HS_DBG_ASSERT(cur != proc); + } +#endif + + STAILQ_INSERT_HEAD(&ble_sm_procs, proc, next); +} + +static int32_t +ble_sm_extract_expired(struct ble_sm_proc_list *dst_list) +{ + struct ble_sm_proc *proc; + struct ble_sm_proc *prev; + struct ble_sm_proc *next; + ble_npl_time_t now; + ble_npl_stime_t next_exp_in; + ble_npl_stime_t time_diff; + + now = ble_npl_time_get(); + STAILQ_INIT(dst_list); + + /* Assume each event is either expired or has infinite duration. */ + next_exp_in = BLE_HS_FOREVER; + + ble_hs_lock(); + + prev = NULL; + proc = STAILQ_FIRST(&ble_sm_procs); + while (proc != NULL) { + next = STAILQ_NEXT(proc, next); + + time_diff = proc->exp_os_ticks - now; + if (time_diff <= 0) { + /* Procedure has expired; move it to the destination list. */ + if (prev == NULL) { + STAILQ_REMOVE_HEAD(&ble_sm_procs, next); + } else { + STAILQ_REMOVE_AFTER(&ble_sm_procs, prev, next); + } + STAILQ_INSERT_HEAD(dst_list, proc, next); + } else { + if (time_diff < next_exp_in) { + next_exp_in = time_diff; + } + } + + prev = proc; + proc = next; + } + + ble_sm_dbg_assert_no_cycles(); + + ble_hs_unlock(); + + return next_exp_in; +} + +static void +ble_sm_rx_noop(uint16_t conn_handle, struct os_mbuf **om, + struct ble_sm_result *res) +{ + res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_CMD_NOT_SUPP); + res->sm_err = BLE_SM_ERR_CMD_NOT_SUPP; +} + +static uint8_t +ble_sm_build_authreq(void) +{ + return ble_hs_cfg.sm_bonding << 0 | + ble_hs_cfg.sm_mitm << 2 | + ble_hs_cfg.sm_sc << 3 | + ble_hs_cfg.sm_keypress << 4; +} + +static int +ble_sm_io_action(struct ble_sm_proc *proc, uint8_t *action) +{ + if (proc->flags & BLE_SM_PROC_F_SC) { + return ble_sm_sc_io_action(proc, action); + } else { + return ble_sm_lgcy_io_action(proc, action); + } +} + +int +ble_sm_ioact_state(uint8_t action) +{ + switch (action) { + case BLE_SM_IOACT_NONE: + return BLE_SM_PROC_STATE_NONE; + + case BLE_SM_IOACT_NUMCMP: + return BLE_SM_PROC_STATE_DHKEY_CHECK; + + case BLE_SM_IOACT_OOB_SC: + return BLE_SM_PROC_STATE_RANDOM; + + case BLE_SM_IOACT_OOB: + case BLE_SM_IOACT_INPUT: + case BLE_SM_IOACT_DISP: + return BLE_SM_PROC_STATE_CONFIRM; + + default: + BLE_HS_DBG_ASSERT(0); + return BLE_SM_PROC_STATE_NONE; + } +} + +int +ble_sm_proc_can_advance(struct ble_sm_proc *proc) +{ + uint8_t ioact; + int rc; + + rc = ble_sm_io_action(proc, &ioact); + if (rc != 0) { + BLE_HS_DBG_ASSERT(0); + } + + if (ble_sm_ioact_state(ioact) != proc->state) { + return 1; + } + + if (proc->flags & BLE_SM_PROC_F_IO_INJECTED && + proc->flags & BLE_SM_PROC_F_ADVANCE_ON_IO) { + + return 1; + } + + return 0; +} + +static void +ble_sm_exec(struct ble_sm_proc *proc, struct ble_sm_result *res, void *arg) +{ + ble_sm_state_fn *cb; + + memset(res, 0, sizeof *res); + + if (!ble_hs_conn_exists(proc->conn_handle)) { + res->app_status = BLE_HS_ENOTCONN; + } else { + BLE_HS_DBG_ASSERT(proc->state < BLE_SM_PROC_STATE_CNT); + cb = ble_sm_state_dispatch[proc->state]; + BLE_HS_DBG_ASSERT(cb != NULL); + cb(proc, res, arg); + } +} + +static void +ble_sm_pair_fail_tx(uint16_t conn_handle, uint8_t reason) +{ + struct ble_sm_pair_fail *cmd; + struct os_mbuf *txom; + int rc; + + BLE_HS_DBG_ASSERT(reason > 0 && reason < BLE_SM_ERR_MAX_PLUS_1); + + cmd = ble_sm_cmd_get(BLE_SM_OP_PAIR_FAIL, sizeof(*cmd), &txom); + if (cmd) { + cmd->reason = reason; + rc = ble_sm_tx(conn_handle, txom); + if (rc) { + BLE_HS_LOG(ERROR, "ble_sm_pair_fail_tx failed, rc = %d\n", rc); + } + } +} + +/** + * Reads a bond from storage. + */ +static int +ble_sm_read_bond(uint16_t conn_handle, struct ble_store_value_sec *out_bond) +{ + struct ble_store_key_sec key_sec; + struct ble_gap_conn_desc desc; + int rc; + + rc = ble_gap_conn_find(conn_handle, &desc); + if (rc != 0) { + return rc; + } + + memset(&key_sec, 0, sizeof key_sec); + key_sec.peer_addr = desc.peer_id_addr; + + rc = ble_store_read_peer_sec(&key_sec, out_bond); + return rc; +} + +/** + * Checks if the specified peer is already bonded. If it is, the application + * is queried about how to proceed: retry or ignore. The application should + * only indicate a retry if it deleted the old bond. + * + * @param conn_handle The handle of the connection over which the + * pairing request was received. + * @param proc_flags The security flags associated with the + * conflicting SM procedure. + * @param key_size The key size of the conflicting SM procedure. + * + * @return 0 if the procedure should continue; + * nonzero if the request should be ignored. + */ +static int +ble_sm_chk_repeat_pairing(uint16_t conn_handle, + ble_sm_proc_flags proc_flags, + uint8_t key_size) +{ + struct ble_gap_repeat_pairing rp; + struct ble_store_value_sec bond; + int rc; + + do { + /* If the peer isn't bonded, indicate that the pairing procedure should + * continue. + */ + rc = ble_sm_read_bond(conn_handle, &bond); + switch (rc) { + case 0: + break; + case BLE_HS_ENOENT: + return 0; + default: + return rc; + } + + /* Peer is already bonded. Ask the application what to do about it. */ + rp.conn_handle = conn_handle; + rp.cur_key_size = bond.key_size; + rp.cur_authenticated = bond.authenticated; + rp.cur_sc = bond.sc; + + rp.new_key_size = key_size; + rp.new_authenticated = !!(proc_flags & BLE_SM_PROC_F_AUTHENTICATED); + rp.new_sc = !!(proc_flags & BLE_SM_PROC_F_SC); + rp.new_bonding = !!(proc_flags & BLE_SM_PROC_F_BONDING); + + rc = ble_gap_repeat_pairing_event(&rp); + } while (rc == BLE_GAP_REPEAT_PAIRING_RETRY); + + BLE_HS_LOG(DEBUG, "silently ignoring pair request from bonded peer"); + + return BLE_HS_EALREADY; +} + +void +ble_sm_process_result(uint16_t conn_handle, struct ble_sm_result *res) +{ + struct ble_sm_proc *prev; + struct ble_sm_proc *proc; + int rm; + + rm = 0; + + while (1) { + ble_hs_lock(); + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_NONE, -1, + &prev); + + if (proc != NULL) { + if (res->execute) { + ble_sm_exec(proc, res, res->state_arg); + } + + if (res->app_status != 0) { + rm = 1; + } + + if (proc->state == BLE_SM_PROC_STATE_NONE) { + rm = 1; + } + + if (rm) { + ble_sm_proc_remove(proc, prev); + } else { + ble_sm_proc_set_timer(proc); + } + } + + if (res->sm_err != 0) { + ble_sm_pair_fail_tx(conn_handle, res->sm_err); + } + + ble_hs_unlock(); + + if (proc == NULL) { + break; + } + + if (res->enc_cb) { + BLE_HS_DBG_ASSERT(proc == NULL || rm); + ble_gap_enc_event(conn_handle, res->app_status, res->restore); + } + + if (res->app_status == 0 && + res->passkey_params.action != BLE_SM_IOACT_NONE) { + + ble_gap_passkey_event(conn_handle, &res->passkey_params); + } + + /* Persist keys if bonding has successfully completed. */ + if (res->app_status == 0 && + rm && + proc->flags & BLE_SM_PROC_F_BONDING) { + + ble_sm_persist_keys(proc); + } + + if (rm) { + ble_sm_proc_free(proc); + break; + } + + if (!res->execute) { + break; + } + + memset(res, 0, sizeof *res); + res->execute = 1; + } +} + +static void +ble_sm_key_dist(struct ble_sm_proc *proc, + uint8_t *out_init_key_dist, uint8_t *out_resp_key_dist) +{ + struct ble_sm_pair_cmd *pair_rsp; + + pair_rsp = (struct ble_sm_pair_cmd *) &proc->pair_rsp[1]; + + *out_init_key_dist = pair_rsp->init_key_dist; + *out_resp_key_dist = pair_rsp->resp_key_dist; + + /* Encryption info and master ID are only sent in legacy pairing. */ + if (proc->flags & BLE_SM_PROC_F_SC) { + *out_init_key_dist &= ~BLE_SM_PAIR_KEY_DIST_ENC; + *out_resp_key_dist &= ~BLE_SM_PAIR_KEY_DIST_ENC; + } +} + +static int +ble_sm_chk_store_overflow_by_type(int obj_type, uint16_t conn_handle) +{ +#if !MYNEWT_VAL(BLE_SM_BONDING) + return 0; +#endif + + int count; + int rc; + + rc = ble_store_util_count(obj_type, &count); + if (rc != 0) { + return rc; + } + + /* Pessimistically assume all active procs will persist bonds. */ + ble_hs_lock(); + count += ble_sm_num_procs(); + ble_hs_unlock(); + + if (count < MYNEWT_VAL(BLE_STORE_MAX_BONDS)) { + /* There is sufficient capacity for another bond. */ + return 0; + } + + /* No capacity for an additional bond. Tell the application to make + * room. + */ + rc = ble_store_full_event(obj_type, conn_handle); + if (rc != 0) { + return rc; + } + + return 0; +} + +static int +ble_sm_chk_store_overflow(uint16_t conn_handle) +{ + int rc; + + rc = ble_sm_chk_store_overflow_by_type(BLE_STORE_OBJ_TYPE_PEER_SEC, + conn_handle); + if (rc != 0) { + return rc; + } + + rc = ble_sm_chk_store_overflow_by_type(BLE_STORE_OBJ_TYPE_OUR_SEC, + conn_handle); + if (rc != 0) { + return rc; + } + + return 0; +} + +/***************************************************************************** + * $enc * + *****************************************************************************/ + +static int +ble_sm_start_encrypt_tx(struct hci_start_encrypt *params) +{ + struct ble_hci_le_start_encrypt_cp cmd; + + cmd.conn_handle = htole16(params->connection_handle); + cmd.div = htole16(params->encrypted_diversifier); + cmd.rand = htole64(params->random_number); + memcpy(cmd.ltk, params->long_term_key, sizeof(cmd.ltk)); + + return ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE, + BLE_HCI_OCF_LE_START_ENCRYPT), + &cmd, sizeof(cmd), NULL, 0); +} + +static void +ble_sm_enc_start_exec(struct ble_sm_proc *proc, struct ble_sm_result *res, + void *arg) +{ + struct hci_start_encrypt cmd; + int rc; + + BLE_HS_DBG_ASSERT(proc->flags & BLE_SM_PROC_F_INITIATOR); + + cmd.connection_handle = proc->conn_handle; + cmd.encrypted_diversifier = 0; + cmd.random_number = 0; + memcpy(cmd.long_term_key, proc->ltk, sizeof cmd.long_term_key); + + rc = ble_sm_start_encrypt_tx(&cmd); + if (rc != 0) { + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + res->app_status = rc; + res->enc_cb = 1; + } +} + +static void +ble_sm_enc_restore_exec(struct ble_sm_proc *proc, struct ble_sm_result *res, + void *arg) +{ + struct hci_start_encrypt *cmd; + + BLE_HS_DBG_ASSERT(proc->flags & BLE_SM_PROC_F_INITIATOR); + + cmd = arg; + BLE_HS_DBG_ASSERT(cmd != NULL); + + res->app_status = ble_sm_start_encrypt_tx(cmd); +} + +static void +ble_sm_enc_event_rx(uint16_t conn_handle, uint8_t evt_status, int encrypted) +{ + struct ble_sm_result res; + struct ble_sm_proc *proc; + int authenticated; + int bonded; + int key_size; + + memset(&res, 0, sizeof res); + + /* Assume no change in authenticated and bonded statuses. */ + authenticated = 0; + bonded = 0; + key_size = 0; + + ble_hs_lock(); + + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_NONE, -1, NULL); + if (proc != NULL) { + switch (proc->state) { + case BLE_SM_PROC_STATE_ENC_START: + /* We are completing a pairing procedure; keys may need to be + * exchanged. + */ + if (evt_status == 0) { + /* If the responder has any keys to send, it sends them + * first. + */ + proc->state = BLE_SM_PROC_STATE_KEY_EXCH; + if (!(proc->flags & BLE_SM_PROC_F_INITIATOR) || + proc->rx_key_flags == 0) { + + res.execute = 1; + } + + key_size = proc->key_size; + } else { + /* Failure or no keys to exchange; procedure is complete. */ + proc->state = BLE_SM_PROC_STATE_NONE; + } + if (proc->flags & BLE_SM_PROC_F_AUTHENTICATED) { + authenticated = 1; + } + break; + + case BLE_SM_PROC_STATE_ENC_RESTORE: + /* A secure link is being restored via the encryption + * procedure. Keys were exchanged during pairing; they don't + * get exchanged again now. Procedure is complete. + */ + BLE_HS_DBG_ASSERT(proc->rx_key_flags == 0); + proc->state = BLE_SM_PROC_STATE_NONE; + if (proc->flags & BLE_SM_PROC_F_AUTHENTICATED) { + authenticated = 1; + } + bonded = 1; + res.restore = 1; + + key_size = proc->key_size; + break; + + default: + /* The encryption change event is unexpected. We take the + * controller at its word that the state has changed and we + * terminate the procedure. + */ + proc->state = BLE_SM_PROC_STATE_NONE; + res.sm_err = BLE_SM_ERR_UNSPECIFIED; + break; + } + } + + if (evt_status == 0) { + /* Set the encrypted state of the connection as indicated in the + * event. + */ + ble_sm_update_sec_state(conn_handle, encrypted, authenticated, bonded, + key_size); + } + + /* Unless keys need to be exchanged, notify the application of the security + * change. If key exchange is pending, the application callback is + * triggered after exchange completes. + */ + if (proc == NULL || proc->state == BLE_SM_PROC_STATE_NONE) { + res.enc_cb = 1; + res.app_status = BLE_HS_HCI_ERR(evt_status); + } + + ble_hs_unlock(); + + ble_sm_process_result(conn_handle, &res); +} + +void +ble_sm_enc_change_rx(const struct ble_hci_ev_enrypt_chg *ev) +{ + /* For encrypted state: read LE-encryption bit; ignore BR/EDR and reserved + * bits. + */ + ble_sm_enc_event_rx(le16toh(ev->connection_handle), ev->status, + ev->enabled & 0x01); +} + +void +ble_sm_enc_key_refresh_rx(const struct ble_hci_ev_enc_key_refresh *ev) +{ + ble_sm_enc_event_rx(le16toh(ev->conn_handle), ev->status, 1); +} + +/***************************************************************************** + * $ltk * + *****************************************************************************/ + +static int +ble_sm_retrieve_ltk(uint16_t ediv, uint64_t rand, uint8_t peer_addr_type, + uint8_t *peer_addr, struct ble_store_value_sec *value_sec) +{ + struct ble_store_key_sec key_sec; + int rc; + + /* Tell applicaiton to look up LTK by peer address and ediv/rand pair. */ + memset(&key_sec, 0, sizeof key_sec); + key_sec.peer_addr.type = peer_addr_type; + memcpy(key_sec.peer_addr.val, peer_addr, 6); + key_sec.ediv = ediv; + key_sec.rand_num = rand; + key_sec.ediv_rand_present = 1; + + rc = ble_store_read_our_sec(&key_sec, value_sec); + return rc; +} + +static int +ble_sm_ltk_req_reply_tx(uint16_t conn_handle, const uint8_t *ltk) +{ + struct ble_hci_le_lt_key_req_reply_cp cmd; + struct ble_hci_le_lt_key_req_reply_rp rsp; + int rc; + + cmd.conn_handle = htole16(conn_handle); + memcpy(cmd.ltk, ltk, 16); + + rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE, + BLE_HCI_OCF_LE_LT_KEY_REQ_REPLY), + &cmd, sizeof(cmd), &rsp, sizeof(rsp)); + if (rc != 0) { + return rc; + } + + if (le16toh(rsp.conn_handle) != conn_handle) { + return BLE_HS_ECONTROLLER; + } + + return 0; +} + +static int +ble_sm_ltk_req_neg_reply_tx(uint16_t conn_handle) +{ + struct ble_hci_le_lt_key_req_neg_reply_cp cmd; + struct ble_hci_le_lt_key_req_neg_reply_cp rsp; + int rc; + + cmd.conn_handle = htole16(conn_handle); + rc = ble_hs_hci_cmd_tx(BLE_HCI_OP(BLE_HCI_OGF_LE, + BLE_HCI_OCF_LE_LT_KEY_REQ_NEG_REPLY), + &cmd, sizeof(cmd), &rsp, sizeof(rsp)); + if (rc != 0) { + return rc; + } + + if (le16toh(rsp.conn_handle) != conn_handle) { + return BLE_HS_ECONTROLLER; + } + + return 0; +} + +static void +ble_sm_ltk_start_exec(struct ble_sm_proc *proc, struct ble_sm_result *res, + void *arg) +{ + BLE_HS_DBG_ASSERT(!(proc->flags & BLE_SM_PROC_F_INITIATOR)); + + res->app_status = ble_sm_ltk_req_reply_tx(proc->conn_handle, proc->ltk); + if (res->app_status == 0) { + proc->state = BLE_SM_PROC_STATE_ENC_START; + } else { + res->enc_cb = 1; + } +} + +static void +ble_sm_ltk_restore_exec(struct ble_sm_proc *proc, struct ble_sm_result *res, + void *arg) +{ + struct ble_store_value_sec *value_sec; + + BLE_HS_DBG_ASSERT(!(proc->flags & BLE_SM_PROC_F_INITIATOR)); + + value_sec = arg; + + if (value_sec != NULL) { + /* Store provided a key; send it to the controller. */ + res->app_status = ble_sm_ltk_req_reply_tx( + proc->conn_handle, value_sec->ltk); + + if (res->app_status == 0) { + proc->key_size = value_sec->key_size; + if (value_sec->authenticated) { + proc->flags |= BLE_SM_PROC_F_AUTHENTICATED; + } + } else { + /* Notify the app if it provided a key and the procedure failed. */ + res->enc_cb = 1; + } + } else { + /* Application does not have the requested key in its database. Send a + * negative reply to the controller. + */ + ble_sm_ltk_req_neg_reply_tx(proc->conn_handle); + res->app_status = BLE_HS_ENOENT; + } + + if (res->app_status == 0) { + proc->state = BLE_SM_PROC_STATE_ENC_RESTORE; + } +} + +int +ble_sm_ltk_req_rx(const struct ble_hci_ev_le_subev_lt_key_req *ev) +{ + struct ble_store_value_sec value_sec; + struct ble_hs_conn_addrs addrs; + struct ble_sm_result res; + struct ble_sm_proc *proc; + struct ble_hs_conn *conn; + uint8_t peer_id_addr[6]; + int store_rc; + int restore; + + uint16_t conn_handle = le16toh(ev->conn_handle); + + memset(&res, 0, sizeof res); + + ble_hs_lock(); + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_NONE, 0, NULL); + if (proc == NULL) { + /* The peer is attempting to restore a encrypted connection via the + * encryption procedure. Create a proc entry to indicate that security + * establishment is in progress and execute the procedure after the + * mutex gets unlocked. + */ + restore = 1; + proc = ble_sm_proc_alloc(); + if (proc == NULL) { + res.app_status = BLE_HS_ENOMEM; + } else { + proc->conn_handle = conn_handle; + proc->state = BLE_SM_PROC_STATE_LTK_RESTORE; + ble_sm_insert(proc); + + res.execute = 1; + } + } else if (proc->state == BLE_SM_PROC_STATE_SEC_REQ) { + /* Same as above, except we solicited the encryption procedure by + * sending a security request. + */ + restore = 1; + proc->state = BLE_SM_PROC_STATE_LTK_RESTORE; + res.execute = 1; + } else if (proc->state == BLE_SM_PROC_STATE_LTK_START) { + /* Legacy pairing just completed. Send the short term key to the + * controller. + */ + restore = 0; + res.execute = 1; + } else { + /* The request is unexpected; nack and forget. */ + restore = 0; + ble_sm_ltk_req_neg_reply_tx(conn_handle); + proc = NULL; + } + + if (restore) { + conn = ble_hs_conn_find_assert(conn_handle); + ble_hs_conn_addrs(conn, &addrs); + memcpy(peer_id_addr, addrs.peer_id_addr.val, 6); + } + + ble_hs_unlock(); + + if (proc == NULL) { + return res.app_status; + } + + if (res.app_status == 0) { + if (restore) { + store_rc = ble_sm_retrieve_ltk(le16toh(ev->div), le64toh(ev->rand), + addrs.peer_id_addr.type, + peer_id_addr, &value_sec); + if (store_rc == 0) { + /* Send the key to the controller. */ + res.state_arg = &value_sec; + } else { + /* Send a nack to the controller. */ + res.state_arg = NULL; + } + } + } + + ble_sm_process_result(conn_handle, &res); + + return 0; +} + +/***************************************************************************** + * $random * + *****************************************************************************/ + +uint8_t * +ble_sm_our_pair_rand(struct ble_sm_proc *proc) +{ + if (proc->flags & BLE_SM_PROC_F_INITIATOR) { + return proc->randm; + } else { + return proc->rands; + } +} + +uint8_t * +ble_sm_peer_pair_rand(struct ble_sm_proc *proc) +{ + if (proc->flags & BLE_SM_PROC_F_INITIATOR) { + return proc->rands; + } else { + return proc->randm; + } +} + +static void +ble_sm_random_exec(struct ble_sm_proc *proc, struct ble_sm_result *res, + void *arg) +{ + if (proc->flags & BLE_SM_PROC_F_SC) { + ble_sm_sc_random_exec(proc, res); + } else { + ble_sm_lgcy_random_exec(proc, res); + } +} + +static void +ble_sm_random_rx(uint16_t conn_handle, struct os_mbuf **om, + struct ble_sm_result *res) +{ + struct ble_sm_pair_random *cmd; + struct ble_sm_proc *proc; + + res->app_status = ble_hs_mbuf_pullup_base(om, sizeof(*cmd)); + if (res->app_status != 0) { + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + res->enc_cb = 1; + return; + } + + cmd = (struct ble_sm_pair_random *)(*om)->om_data; + + ble_hs_lock(); + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_RANDOM, -1, NULL); + if (proc == NULL) { + res->app_status = BLE_HS_ENOENT; + } else { + memcpy(ble_sm_peer_pair_rand(proc), cmd->value, 16); + + if (proc->flags & BLE_SM_PROC_F_SC) { + ble_sm_sc_random_rx(proc, res); + } else { + ble_sm_lgcy_random_rx(proc, res); + } + } + ble_hs_unlock(); +} + +/***************************************************************************** + * $confirm * + *****************************************************************************/ + +static void +ble_sm_confirm_exec(struct ble_sm_proc *proc, struct ble_sm_result *res, + void *arg) +{ + if (!(proc->flags & BLE_SM_PROC_F_SC)) { + ble_sm_lgcy_confirm_exec(proc, res); + } else { + ble_sm_sc_confirm_exec(proc, res); + } +} + +static void +ble_sm_confirm_rx(uint16_t conn_handle, struct os_mbuf **om, + struct ble_sm_result *res) +{ + struct ble_sm_pair_confirm *cmd; + struct ble_sm_proc *proc; + uint8_t ioact; + + res->app_status = ble_hs_mbuf_pullup_base(om, sizeof(*cmd)); + if (res->app_status != 0) { + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + res->enc_cb = 1; + return; + } + + cmd = (struct ble_sm_pair_confirm *)(*om)->om_data; + + ble_hs_lock(); + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_CONFIRM, -1, NULL); + if (proc == NULL) { + res->app_status = BLE_HS_ENOENT; + } else { + memcpy(proc->confirm_peer, cmd->value, 16); + + if (proc->flags & BLE_SM_PROC_F_INITIATOR) { + proc->state = BLE_SM_PROC_STATE_RANDOM; + res->execute = 1; + } else { + int rc; + + rc = ble_sm_io_action(proc, &ioact); + if (rc != 0) { + BLE_HS_DBG_ASSERT(0); + } + + if (ble_sm_ioact_state(ioact) == proc->state) { + proc->flags |= BLE_SM_PROC_F_ADVANCE_ON_IO; + } + if (ble_sm_proc_can_advance(proc)) { + res->execute = 1; + } + } + } + ble_hs_unlock(); +} + +/***************************************************************************** + * $pair * + *****************************************************************************/ + +static uint8_t +ble_sm_state_after_pair(struct ble_sm_proc *proc) +{ + if (proc->flags & BLE_SM_PROC_F_SC) { + return BLE_SM_PROC_STATE_PUBLIC_KEY; + } else { + return BLE_SM_PROC_STATE_CONFIRM; + } +} + +static void +ble_sm_pair_cfg(struct ble_sm_proc *proc) +{ + struct ble_sm_pair_cmd *pair_req, *pair_rsp; + uint8_t init_key_dist; + uint8_t resp_key_dist; + uint8_t rx_key_dist; + uint8_t ioact; + int rc; + + pair_req = (struct ble_sm_pair_cmd *) &proc->pair_req[1]; + pair_rsp = (struct ble_sm_pair_cmd *) &proc->pair_rsp[1]; + + if (pair_req->authreq & BLE_SM_PAIR_AUTHREQ_SC && + pair_rsp->authreq & BLE_SM_PAIR_AUTHREQ_SC) { + + proc->flags |= BLE_SM_PROC_F_SC; + } + + ble_sm_key_dist(proc, &init_key_dist, &resp_key_dist); + if (proc->flags & BLE_SM_PROC_F_INITIATOR) { + rx_key_dist = resp_key_dist; + } else { + rx_key_dist = init_key_dist; + } + + if (pair_req->authreq & BLE_SM_PAIR_AUTHREQ_BOND && + pair_rsp->authreq & BLE_SM_PAIR_AUTHREQ_BOND) { + + proc->flags |= BLE_SM_PROC_F_BONDING; + } + + /* In legacy mode, bonding requires the exchange of keys + * at least from one side. If no key exchange was specified, + * pretend bonding is not enabled. + */ + if (!(proc->flags & BLE_SM_PROC_F_SC) && + (init_key_dist == 0 && resp_key_dist == 0)) { + + proc->flags &= ~BLE_SM_PROC_F_BONDING; + } + + proc->rx_key_flags = 0; + if (rx_key_dist & BLE_SM_PAIR_KEY_DIST_ENC) { + proc->rx_key_flags |= BLE_SM_KE_F_ENC_INFO | + BLE_SM_KE_F_MASTER_ID; + } + if (rx_key_dist & BLE_SM_PAIR_KEY_DIST_ID) { + proc->rx_key_flags |= BLE_SM_KE_F_ID_INFO | + BLE_SM_KE_F_ADDR_INFO; + } + if (rx_key_dist & BLE_SM_PAIR_KEY_DIST_SIGN) { + proc->rx_key_flags |= BLE_SM_KE_F_SIGN_INFO; + } + + proc->key_size = min(pair_req->max_enc_key_size, + pair_rsp->max_enc_key_size); + + rc = ble_sm_io_action(proc, &ioact); + BLE_HS_DBG_ASSERT_EVAL(rc == 0); +} + +static void +ble_sm_pair_base_fill(struct ble_sm_pair_cmd *cmd) +{ + cmd->io_cap = ble_hs_cfg.sm_io_cap; + cmd->oob_data_flag = ble_hs_cfg.sm_oob_data_flag; + cmd->authreq = ble_sm_build_authreq(); + cmd->max_enc_key_size = BLE_SM_PAIR_KEY_SZ_MAX; +} + +static void +ble_sm_pair_req_fill(struct ble_sm_proc *proc) +{ + struct ble_sm_pair_cmd *req; + + req = (void *)(proc->pair_req + 1); + + proc->pair_req[0] = BLE_SM_OP_PAIR_REQ; + ble_sm_pair_base_fill(req); + req->init_key_dist = ble_hs_cfg.sm_our_key_dist; + req->resp_key_dist = ble_hs_cfg.sm_their_key_dist; +} + +static void +ble_sm_pair_rsp_fill(struct ble_sm_proc *proc) +{ + const struct ble_sm_pair_cmd *req; + struct ble_sm_pair_cmd *rsp; + + req = (void *)(proc->pair_req + 1); + rsp = (void *)(proc->pair_rsp + 1); + + proc->pair_rsp[0] = BLE_SM_OP_PAIR_RSP; + ble_sm_pair_base_fill(rsp); + + /* The response's key distribution flags field is the intersection of + * the peer's preferences and our capabilities. + */ + rsp->init_key_dist = req->init_key_dist & + ble_hs_cfg.sm_their_key_dist; + rsp->resp_key_dist = req->resp_key_dist & + ble_hs_cfg.sm_our_key_dist; +} + +static void +ble_sm_pair_exec(struct ble_sm_proc *proc, struct ble_sm_result *res, + void *arg) +{ + struct ble_sm_pair_cmd *cmd; + struct os_mbuf *txom; + uint8_t ioact; + int is_req; + int rc; + + is_req = proc->flags & BLE_SM_PROC_F_INITIATOR; + + cmd = ble_sm_cmd_get(is_req ? BLE_SM_OP_PAIR_REQ : BLE_SM_OP_PAIR_RSP, + sizeof(*cmd), &txom); + if (cmd == NULL) { + rc = BLE_HS_ENOMEM; + goto err; + } + + if (is_req) { + ble_sm_pair_req_fill(proc); + memcpy(cmd, proc->pair_req + 1, sizeof(*cmd)); + } else { + /* The response was already generated when we processed the incoming + * request. + */ + memcpy(cmd, proc->pair_rsp + 1, sizeof(*cmd)); + + proc->state = ble_sm_state_after_pair(proc); + + rc = ble_sm_io_action(proc, &ioact); + BLE_HS_DBG_ASSERT(rc == 0); + + if (ble_sm_ioact_state(ioact) == proc->state) { + res->passkey_params.action = ioact; + } + } + + rc = ble_sm_tx(proc->conn_handle, txom); + if (rc != 0) { + goto err; + } + + res->app_status = ble_sm_gen_pair_rand(ble_sm_our_pair_rand(proc)); + if (res->app_status != 0) { + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + res->enc_cb = 1; + return; + } + + return; + +err: + res->app_status = rc; + + if (!is_req) { + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + } +} + +static bool +ble_sm_verify_auth_requirements(uint8_t authreq) +{ + /* For now we check only SC only mode. I.e.: when remote indicates + * to not support SC pairing, let us make sure legacy pairing is supported + * on our side. If not, we can fail right away. + */ + if (!(authreq & BLE_SM_PAIR_AUTHREQ_SC)) { + if (MYNEWT_VAL(BLE_SM_LEGACY) == 0) { + return false; + } + } + return true; +} + +static void +ble_sm_pair_req_rx(uint16_t conn_handle, struct os_mbuf **om, + struct ble_sm_result *res) +{ + struct ble_sm_pair_cmd *req; + struct ble_sm_proc *proc; + struct ble_sm_proc *prev; + struct ble_hs_conn *conn; + ble_sm_proc_flags proc_flags; + uint8_t key_size; + int rc; + + /* Silence spurious unused-variable warnings. */ + proc_flags = 0; + key_size = 0; + + res->app_status = ble_hs_mbuf_pullup_base(om, sizeof(*req)); + if (res->app_status != 0) { + return; + } + + req = (struct ble_sm_pair_cmd *)(*om)->om_data; + + ble_hs_lock(); + + /* XXX: Check connection state; reject if not appropriate. */ + /* XXX: Ensure enough time has passed since the previous failed pairing + * attempt. + */ + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_NONE, -1, &prev); + if (proc != NULL) { + /* Fail if procedure is in progress unless we sent a slave security + * request to peer. + */ + if (proc->state != BLE_SM_PROC_STATE_SEC_REQ) { + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_UNSPECIFIED); + ble_hs_unlock(); + return; + } + + /* Remove the procedure because it was allocated when + * sending the Slave Security Request and it will be allocated + * again later in this method. We should probably refactor this + * in the future. + */ + ble_sm_proc_remove(proc, prev); + ble_sm_proc_free(proc); + } + + ble_hs_unlock(); + + /* Check if there is storage capacity for a new bond. If there isn't, ask + * the application to make room. + */ + rc = ble_sm_chk_store_overflow(conn_handle); + if (rc != 0) { + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + res->app_status = rc; + return; + } + + ble_hs_lock(); + + proc = ble_sm_proc_alloc(); + if (proc != NULL) { + proc->conn_handle = conn_handle; + proc->state = BLE_SM_PROC_STATE_PAIR; + ble_sm_insert(proc); + + proc->pair_req[0] = BLE_SM_OP_PAIR_REQ; + memcpy(proc->pair_req + 1, req, sizeof(*req)); + + conn = ble_hs_conn_find_assert(proc->conn_handle); + if (conn->bhc_flags & BLE_HS_CONN_F_MASTER) { + res->sm_err = BLE_SM_ERR_CMD_NOT_SUPP; + res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_CMD_NOT_SUPP); + } else if (req->max_enc_key_size < BLE_SM_PAIR_KEY_SZ_MIN) { + res->sm_err = BLE_SM_ERR_ENC_KEY_SZ; + res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_ENC_KEY_SZ); + } else if (req->max_enc_key_size > BLE_SM_PAIR_KEY_SZ_MAX) { + res->sm_err = BLE_SM_ERR_INVAL; + res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_INVAL); + } else if (!ble_sm_verify_auth_requirements(req->authreq)) { + res->sm_err = BLE_SM_ERR_AUTHREQ; + res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_AUTHREQ); + } else { + /* The request looks good. Precalculate our pairing response and + * determine some properties of the imminent link. We need this + * information in case this is a repeated pairing attempt (i.e., we + * are already bonded to this peer). In that case, we include the + * information in a notification to the app. + */ + ble_sm_pair_rsp_fill(proc); + ble_sm_pair_cfg(proc); + + proc_flags = proc->flags; + key_size = proc->key_size; + res->execute = 1; + } + } + + ble_hs_unlock(); + + /* Check if we are already bonded to this peer. If so, give the + * application an opportunity to delete the old bond. + */ + if (res->app_status == 0) { + rc = ble_sm_chk_repeat_pairing(conn_handle, proc_flags, key_size); + if (rc != 0) { + /* The app indicated that the pairing request should be ignored. */ + res->app_status = rc; + res->execute = 0; + } + } +} + +static void +ble_sm_pair_rsp_rx(uint16_t conn_handle, struct os_mbuf **om, + struct ble_sm_result *res) +{ + struct ble_sm_pair_cmd *rsp; + struct ble_sm_proc *proc; + uint8_t ioact; + int rc; + + res->app_status = ble_hs_mbuf_pullup_base(om, sizeof(*rsp)); + if (res->app_status != 0) { + res->enc_cb = 1; + return; + } + + rsp = (struct ble_sm_pair_cmd *)(*om)->om_data; + + ble_hs_lock(); + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_PAIR, 1, NULL); + if (proc != NULL) { + proc->pair_rsp[0] = BLE_SM_OP_PAIR_RSP; + memcpy(proc->pair_rsp + 1, rsp, sizeof(*rsp)); + + if (rsp->max_enc_key_size < BLE_SM_PAIR_KEY_SZ_MIN) { + res->sm_err = BLE_SM_ERR_ENC_KEY_SZ; + res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_ENC_KEY_SZ); + } else if (rsp->max_enc_key_size > BLE_SM_PAIR_KEY_SZ_MAX) { + res->sm_err = BLE_SM_ERR_INVAL; + res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_INVAL); + } else { + ble_sm_pair_cfg(proc); + + rc = ble_sm_io_action(proc, &ioact); + if (rc != 0) { + res->sm_err = BLE_SM_ERR_AUTHREQ; + res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_AUTHREQ); + res->enc_cb = 1; + } else { + proc->state = ble_sm_state_after_pair(proc); + if (ble_sm_ioact_state(ioact) == proc->state) { + res->passkey_params.action = ioact; + } + if (ble_sm_proc_can_advance(proc)) { + res->execute = 1; + } + } + } + } + + ble_hs_unlock(); +} + +/***************************************************************************** + * $security request * + *****************************************************************************/ + +static void +ble_sm_sec_req_exec(struct ble_sm_proc *proc, struct ble_sm_result *res, + void *arg) +{ + struct ble_sm_sec_req *cmd; + struct os_mbuf *txom; + int rc; + + cmd = ble_sm_cmd_get(BLE_SM_OP_SEC_REQ, sizeof(*cmd), &txom); + if (!cmd) { + res->app_status = BLE_HS_ENOMEM; + return; + } + + cmd->authreq = ble_sm_build_authreq(); + rc = ble_sm_tx(proc->conn_handle, txom); + if (rc != 0) { + res->app_status = rc; + return; + } +} + +static void +ble_sm_sec_req_rx(uint16_t conn_handle, struct os_mbuf **om, + struct ble_sm_result *res) +{ + struct ble_store_value_sec value_sec; + struct ble_store_key_sec key_sec; + struct ble_hs_conn_addrs addrs; + struct ble_sm_sec_req *cmd; + struct ble_hs_conn *conn; + int authreq_mitm; + + res->app_status = ble_hs_mbuf_pullup_base(om, sizeof(*cmd)); + if (res->app_status != 0) { + return; + } + + cmd = (struct ble_sm_sec_req *)(*om)->om_data; + + /* XXX: Reject if: + * o authreq-reserved flags set? + */ + + ble_hs_lock(); + + conn = ble_hs_conn_find_assert(conn_handle); + if (!(conn->bhc_flags & BLE_HS_CONN_F_MASTER)) { + res->app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_CMD_NOT_SUPP); + res->sm_err = BLE_SM_ERR_CMD_NOT_SUPP; + } else { + /* We will be querying the SM database for a key corresponding to the + * sender; remember the sender's address while the connection list is + * locked. + */ + ble_hs_conn_addrs(conn, &addrs); + memset(&key_sec, 0, sizeof key_sec); + key_sec.peer_addr = addrs.peer_id_addr; + } + + ble_hs_unlock(); + + if (res->app_status == 0) { + /* If the peer is requesting a bonded connection, query database for an + * LTK corresponding to the sender. + */ + if (cmd->authreq & BLE_SM_PAIR_AUTHREQ_BOND) { + res->app_status = ble_store_read_peer_sec(&key_sec, &value_sec); + } else { + res->app_status = BLE_HS_ENOENT; + } + if (res->app_status == 0) { + /* Found a key corresponding to this peer. Make sure it meets the + * requested minimum authreq. + */ + authreq_mitm = cmd->authreq & BLE_SM_PAIR_AUTHREQ_MITM; + if (authreq_mitm && !value_sec.authenticated) { + res->app_status = BLE_HS_EREJECT; + } + } + + if (res->app_status == 0) { + res->app_status = ble_sm_enc_initiate(conn_handle, + value_sec.key_size, + value_sec.ltk, + value_sec.ediv, + value_sec.rand_num, + value_sec.authenticated); + } else { + res->app_status = ble_sm_pair_initiate(conn_handle); + } + } +} + +/***************************************************************************** + * $key exchange * + *****************************************************************************/ + +static void +ble_sm_key_exch_success(struct ble_sm_proc *proc, struct ble_sm_result *res) +{ + /* The procedure is now complete. Update connection bonded state and + * terminate procedure. + */ + ble_sm_update_sec_state(proc->conn_handle, 1, + !!(proc->flags & BLE_SM_PROC_F_AUTHENTICATED), + !!(proc->flags & BLE_SM_PROC_F_BONDING), + proc->key_size); + proc->state = BLE_SM_PROC_STATE_NONE; + + res->app_status = 0; + res->enc_cb = 1; +} + +static void +ble_sm_key_exch_exec(struct ble_sm_proc *proc, struct ble_sm_result *res, + void *arg) +{ + struct ble_sm_id_addr_info *addr_info; + struct ble_hs_conn_addrs addrs; + struct ble_sm_sign_info *sign_info; + struct ble_sm_master_id *master_id; + struct ble_sm_enc_info *enc_info; + struct ble_sm_id_info *id_info; + struct ble_hs_conn *conn; + uint8_t init_key_dist; + uint8_t resp_key_dist; + uint8_t our_key_dist; + struct os_mbuf *txom; + const uint8_t *irk; + int rc; + + ble_sm_key_dist(proc, &init_key_dist, &resp_key_dist); + if (proc->flags & BLE_SM_PROC_F_INITIATOR) { + our_key_dist = init_key_dist; + } else { + our_key_dist = resp_key_dist; + } + + if (our_key_dist & BLE_SM_PAIR_KEY_DIST_ENC) { + /* Send encryption information. */ + enc_info = ble_sm_cmd_get(BLE_SM_OP_ENC_INFO, sizeof(*enc_info), &txom); + if (!enc_info) { + rc = BLE_HS_ENOMEM; + goto err; + } + + rc = ble_sm_gen_ltk(proc, enc_info->ltk); + if (rc != 0) { + os_mbuf_free_chain(txom); + goto err; + } + + /* store LTK before sending since ble_sm_tx consumes tx mbuf */ + memcpy(proc->our_keys.ltk, enc_info->ltk, 16); + proc->our_keys.key_size = proc->key_size; + proc->our_keys.ltk_valid = 1; + + rc = ble_sm_tx(proc->conn_handle, txom); + if (rc != 0) { + goto err; + } + + /* Send master identification. */ + master_id = ble_sm_cmd_get(BLE_SM_OP_MASTER_ID, sizeof(*master_id), + &txom); + if (!master_id) { + rc = BLE_HS_ENOMEM; + goto err; + } + + rc = ble_sm_gen_ediv(master_id); + if (rc != 0) { + os_mbuf_free_chain(txom); + goto err; + } + rc = ble_sm_gen_master_id_rand(master_id); + if (rc != 0) { + os_mbuf_free_chain(txom); + goto err; + } + + proc->our_keys.ediv_rand_valid = 1; + proc->our_keys.rand_val = master_id->rand_val; + proc->our_keys.ediv = master_id->ediv; + + rc = ble_sm_tx(proc->conn_handle, txom); + if (rc != 0) { + goto err; + } + } + + if (our_key_dist & BLE_SM_PAIR_KEY_DIST_ID) { + /* Send identity information. */ + id_info = ble_sm_cmd_get(BLE_SM_OP_IDENTITY_INFO, sizeof(*id_info), + &txom); + if (!id_info) { + rc = BLE_HS_ENOMEM; + goto err; + } + + rc = ble_hs_pvcy_our_irk(&irk); + if (rc != 0) { + os_mbuf_free_chain(txom); + goto err; + } + + memcpy(id_info->irk, irk, 16); + proc->our_keys.irk_valid = 1; + + rc = ble_sm_tx(proc->conn_handle, txom); + if (rc != 0) { + goto err; + } + + /* Send identity address information. */ + addr_info = ble_sm_cmd_get(BLE_SM_OP_IDENTITY_ADDR_INFO, + sizeof(*addr_info), &txom); + if (!addr_info) { + rc = BLE_HS_ENOMEM; + goto err; + } + + conn = ble_hs_conn_find_assert(proc->conn_handle); + ble_hs_conn_addrs(conn, &addrs); + + addr_info->addr_type = addrs.our_id_addr.type; + memcpy(addr_info->bd_addr, addrs.our_id_addr.val, 6); + + proc->our_keys.addr_valid = 1; + memcpy(proc->our_keys.irk, irk, 16); + proc->our_keys.addr_type = addr_info->addr_type; + memcpy(proc->our_keys.addr, addr_info->bd_addr, 6); + + rc = ble_sm_tx(proc->conn_handle, txom); + if (rc != 0) { + goto err; + } + } + + if (our_key_dist & BLE_SM_PAIR_KEY_DIST_SIGN) { + /* Send signing information. */ + sign_info = ble_sm_cmd_get(BLE_SM_OP_SIGN_INFO, sizeof(*sign_info), + &txom); + if (!sign_info) { + rc = BLE_HS_ENOMEM; + goto err; + } + + rc = ble_sm_gen_csrk(proc, sign_info->sig_key); + if (rc != 0) { + os_mbuf_free_chain(txom); + goto err; + } + + proc->our_keys.csrk_valid = 1; + memcpy(proc->our_keys.csrk, sign_info->sig_key, 16); + + rc = ble_sm_tx(proc->conn_handle, txom); + if (rc != 0) { + goto err; + } + } + + if (proc->flags & BLE_SM_PROC_F_INITIATOR || proc->rx_key_flags == 0) { + /* The procedure is now complete. */ + ble_sm_key_exch_success(proc, res); + } + + return; + +err: + res->app_status = rc; + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + res->enc_cb = 1; +} + +static void +ble_sm_key_rxed(struct ble_sm_proc *proc, struct ble_sm_result *res) +{ + BLE_HS_LOG(DEBUG, "rx_key_flags=0x%02x\n", proc->rx_key_flags); + + if (proc->rx_key_flags == 0) { + /* The peer is done sending keys. If we are the initiator, we need to + * send ours. If we are the responder, the procedure is complete. + */ + if (proc->flags & BLE_SM_PROC_F_INITIATOR) { + res->execute = 1; + } else { + ble_sm_key_exch_success(proc, res); + } + } +} + +static void +ble_sm_enc_info_rx(uint16_t conn_handle, struct os_mbuf **om, + struct ble_sm_result *res) +{ + struct ble_sm_enc_info *cmd; + struct ble_sm_proc *proc; + + res->app_status = ble_hs_mbuf_pullup_base(om, sizeof(*cmd)); + if (res->app_status != 0) { + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + res->enc_cb = 1; + return; + } + + cmd = (struct ble_sm_enc_info *)(*om)->om_data; + + ble_hs_lock(); + + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_KEY_EXCH, -1, NULL); + if (proc == NULL) { + res->app_status = BLE_HS_ENOENT; + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + } else { + proc->rx_key_flags &= ~BLE_SM_KE_F_ENC_INFO; + proc->peer_keys.ltk_valid = 1; + memcpy(proc->peer_keys.ltk, cmd->ltk, 16); + proc->peer_keys.key_size = proc->key_size; + + ble_sm_key_rxed(proc, res); + } + + ble_hs_unlock(); +} + +static void +ble_sm_master_id_rx(uint16_t conn_handle, struct os_mbuf **om, + struct ble_sm_result *res) +{ + struct ble_sm_master_id *cmd; + struct ble_sm_proc *proc; + + res->app_status = ble_hs_mbuf_pullup_base(om, sizeof(*cmd)); + if (res->app_status != 0) { + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + res->enc_cb = 1; + return; + } + + cmd = (struct ble_sm_master_id *)(*om)->om_data; + + ble_hs_lock(); + + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_KEY_EXCH, -1, NULL); + if (proc == NULL) { + res->app_status = BLE_HS_ENOENT; + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + } else { + proc->rx_key_flags &= ~BLE_SM_KE_F_MASTER_ID; + proc->peer_keys.ediv_rand_valid = 1; + + proc->peer_keys.ediv = le16toh(cmd->ediv); + proc->peer_keys.rand_val = le64toh(cmd->rand_val); + + ble_sm_key_rxed(proc, res); + } + + ble_hs_unlock(); +} + +static void +ble_sm_id_info_rx(uint16_t conn_handle, struct os_mbuf **om, + struct ble_sm_result *res) +{ + struct ble_sm_id_info *cmd; + struct ble_sm_proc *proc; + + res->app_status = ble_hs_mbuf_pullup_base(om, sizeof(*cmd)); + if (res->app_status != 0) { + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + res->enc_cb = 1; + return; + } + + cmd = (struct ble_sm_id_info *)(*om)->om_data; + + ble_hs_lock(); + + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_KEY_EXCH, -1, NULL); + if (proc == NULL) { + res->app_status = BLE_HS_ENOENT; + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + } else { + proc->rx_key_flags &= ~BLE_SM_KE_F_ID_INFO; + + memcpy(proc->peer_keys.irk, cmd->irk, 16); + proc->peer_keys.irk_valid = 1; + + ble_sm_key_rxed(proc, res); + } + + ble_hs_unlock(); +} + +static void +ble_sm_id_addr_info_rx(uint16_t conn_handle, struct os_mbuf **om, + struct ble_sm_result *res) +{ + struct ble_sm_id_addr_info *cmd; + struct ble_sm_proc *proc; + + res->app_status = ble_hs_mbuf_pullup_base(om, sizeof(*cmd)); + if (res->app_status != 0) { + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + res->enc_cb = 1; + return; + } + + cmd = (struct ble_sm_id_addr_info *)(*om)->om_data; + + ble_hs_lock(); + + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_KEY_EXCH, -1, NULL); + if (proc == NULL) { + res->app_status = BLE_HS_ENOENT; + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + } else { + proc->rx_key_flags &= ~BLE_SM_KE_F_ADDR_INFO; + proc->peer_keys.addr_valid = 1; + proc->peer_keys.addr_type = cmd->addr_type; + memcpy(proc->peer_keys.addr, cmd->bd_addr, 6); + + ble_sm_key_rxed(proc, res); + } + + ble_hs_unlock(); +} + +static void +ble_sm_sign_info_rx(uint16_t conn_handle, struct os_mbuf **om, + struct ble_sm_result *res) +{ + struct ble_sm_sign_info *cmd; + struct ble_sm_proc *proc; + + res->app_status = ble_hs_mbuf_pullup_base(om, sizeof(*cmd)); + if (res->app_status != 0) { + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + res->enc_cb = 1; + return; + } + + cmd = (struct ble_sm_sign_info *)(*om)->om_data; + + ble_hs_lock(); + + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_KEY_EXCH, -1, NULL); + if (proc == NULL) { + res->app_status = BLE_HS_ENOENT; + res->sm_err = BLE_SM_ERR_UNSPECIFIED; + } else { + proc->rx_key_flags &= ~BLE_SM_KE_F_SIGN_INFO; + + memcpy(proc->peer_keys.csrk, cmd->sig_key, 16); + proc->peer_keys.csrk_valid = 1; + + ble_sm_key_rxed(proc, res); + } + + ble_hs_unlock(); +} + +/***************************************************************************** + * $fail * + *****************************************************************************/ + +static void +ble_sm_fail_rx(uint16_t conn_handle, struct os_mbuf **om, + struct ble_sm_result *res) +{ + struct ble_sm_pair_fail *cmd; + + res->enc_cb = 1; + + res->app_status = ble_hs_mbuf_pullup_base(om, sizeof(*cmd)); + if (res->app_status == 0) { + cmd = (struct ble_sm_pair_fail *)(*om)->om_data; + + res->app_status = BLE_HS_SM_PEER_ERR(cmd->reason); + } +} + +/***************************************************************************** + * $api * + *****************************************************************************/ + +/** + * Times out expired SM procedures. + * + * @return The number of ticks until this function should + * be called again. + */ +int32_t +ble_sm_timer(void) +{ + struct ble_sm_proc_list exp_list; + struct ble_sm_proc *proc; + int32_t ticks_until_exp; + + /* Remove timed-out procedures from the main list and insert them into a + * temporary list. This function also calculates the number of ticks until + * the next expiration will occur. + */ + ticks_until_exp = ble_sm_extract_expired(&exp_list); + + /* Notify application of each failure and free the corresponding procedure + * object. + * XXX: Mark connection as tainted; don't allow any subsequent SMP + * procedures without reconnect. + */ + while ((proc = STAILQ_FIRST(&exp_list)) != NULL) { + ble_gap_enc_event(proc->conn_handle, BLE_HS_ETIMEOUT, 0); + + STAILQ_REMOVE_HEAD(&exp_list, next); + ble_sm_proc_free(proc); + } + + return ticks_until_exp; +} + +/** + * Initiates the pairing procedure for the specified connection. + */ +int +ble_sm_pair_initiate(uint16_t conn_handle) +{ + struct ble_sm_result res; + struct ble_sm_proc *proc; + int rc; + + memset(&res, 0, sizeof(res)); + + /* Make sure a procedure isn't already in progress for this connection. */ + ble_hs_lock(); + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_NONE, -1, NULL); + ble_hs_unlock(); + + if (proc != NULL) { + res.app_status = BLE_HS_EALREADY; + return BLE_HS_EALREADY; + } + + /* Check if there is storage capacity for a new bond. If there isn't, ask + * the application to make room. + */ + rc = ble_sm_chk_store_overflow(conn_handle); + if (rc != 0) { + return rc; + } + + proc = ble_sm_proc_alloc(); + if (proc == NULL) { + res.app_status = BLE_HS_ENOMEM; + } else { + proc->conn_handle = conn_handle; + proc->state = BLE_SM_PROC_STATE_PAIR; + proc->flags |= BLE_SM_PROC_F_INITIATOR; + + ble_hs_lock(); + ble_sm_insert(proc); + ble_hs_unlock(); + + res.execute = 1; + } + + if (proc != NULL) { + ble_sm_process_result(conn_handle, &res); + } + + return res.app_status; +} + +int +ble_sm_slave_initiate(uint16_t conn_handle) +{ + struct ble_sm_result res; + struct ble_sm_proc *proc; + + memset(&res, 0, sizeof(res)); + + ble_hs_lock(); + + /* Make sure a procedure isn't already in progress for this connection. */ + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_NONE, -1, NULL); + if (proc != NULL) { + res.app_status = BLE_HS_EALREADY; + + /* Set pointer to null so that existing entry doesn't get freed. */ + proc = NULL; + } else { + proc = ble_sm_proc_alloc(); + if (proc == NULL) { + res.app_status = BLE_HS_ENOMEM; + } else { + proc->conn_handle = conn_handle; + proc->state = BLE_SM_PROC_STATE_SEC_REQ; + ble_sm_insert(proc); + + res.execute = 1; + } + } + + ble_hs_unlock(); + + if (proc != NULL) { + ble_sm_process_result(conn_handle, &res); + } + + return res.app_status; +} + +/** + * Initiates the encryption procedure for the specified connection. + */ +int +ble_sm_enc_initiate(uint16_t conn_handle, uint8_t key_size, + const uint8_t *ltk, uint16_t ediv, + uint64_t rand_val, int auth) +{ + struct ble_sm_result res; + struct ble_sm_proc *proc; + struct hci_start_encrypt cmd; + + memset(&res, 0, sizeof res); + + /* Make sure a procedure isn't already in progress for this connection. */ + ble_hs_lock(); + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_NONE, -1, NULL); + if (proc != NULL) { + res.app_status = BLE_HS_EALREADY; + + /* Set pointer to null so that existing entry doesn't get freed. */ + proc = NULL; + } else { + proc = ble_sm_proc_alloc(); + if (proc == NULL) { + res.app_status = BLE_HS_ENOMEM; + } else { + proc->conn_handle = conn_handle; + proc->key_size = key_size; + proc->state = BLE_SM_PROC_STATE_ENC_RESTORE; + proc->flags |= BLE_SM_PROC_F_INITIATOR; + if (auth) { + proc->flags |= BLE_SM_PROC_F_AUTHENTICATED; + } + ble_sm_insert(proc); + + cmd.connection_handle = conn_handle; + cmd.encrypted_diversifier = ediv; + cmd.random_number = rand_val; + memcpy(cmd.long_term_key, ltk, sizeof cmd.long_term_key); + + res.execute = 1; + res.state_arg = &cmd; + } + } + + ble_hs_unlock(); + + ble_sm_process_result(conn_handle, &res); + + return res.app_status; +} + +static int +ble_sm_rx(struct ble_l2cap_chan *chan) +{ + struct ble_sm_result res; + ble_sm_rx_fn *rx_cb; + uint8_t op; + uint16_t conn_handle; + struct os_mbuf **om; + int rc; + + STATS_INC(ble_l2cap_stats, sm_rx); + + conn_handle = ble_l2cap_get_conn_handle(chan); + if (conn_handle == BLE_HS_CONN_HANDLE_NONE) { + return BLE_HS_ENOTCONN; + } + + om = &chan->rx_buf; + BLE_HS_DBG_ASSERT(*om != NULL); + + rc = os_mbuf_copydata(*om, 0, 1, &op); + if (rc != 0) { + return BLE_HS_EBADDATA; + } + + /* Strip L2CAP SM header from the front of the mbuf. */ + os_mbuf_adj(*om, 1); + + rx_cb = ble_sm_dispatch_get(op); + if (rx_cb != NULL) { + memset(&res, 0, sizeof res); + + rx_cb(conn_handle, om, &res); + ble_sm_process_result(conn_handle, &res); + rc = res.app_status; + } else { + rc = BLE_HS_ENOTSUP; + } + + return rc; +} + +int +ble_sm_inject_io(uint16_t conn_handle, struct ble_sm_io *pkey) +{ + struct ble_sm_result res; + struct ble_sm_proc *proc; + int rc; + uint8_t action; + + memset(&res, 0, sizeof res); + + ble_hs_lock(); + + proc = ble_sm_proc_find(conn_handle, BLE_SM_PROC_STATE_NONE, -1, NULL); + if (proc == NULL) { + rc = BLE_HS_ENOENT; + } else if (proc->flags & BLE_SM_PROC_F_IO_INJECTED) { + rc = BLE_HS_EALREADY; + } else if ((ble_sm_io_action(proc, &action) == 0) && pkey->action != action) { + /* Application provided incorrect IO type. */ + rc = BLE_HS_EINVAL; + } else if (ble_sm_ioact_state(pkey->action) != proc->state) { + /* Procedure is not ready for user input. */ + rc = BLE_HS_EINVAL; + } else { + /* Assume valid input. */ + rc = 0; + + switch (pkey->action) { + case BLE_SM_IOACT_OOB: + proc->flags |= BLE_SM_PROC_F_IO_INJECTED; + memcpy(proc->tk, pkey->oob, 16); + if ((proc->flags & BLE_SM_PROC_F_INITIATOR) || + (proc->flags & BLE_SM_PROC_F_ADVANCE_ON_IO)) { + + res.execute = 1; + } + break; + + case BLE_SM_IOACT_INPUT: + case BLE_SM_IOACT_DISP: + if (pkey->passkey > 999999) { + rc = BLE_HS_EINVAL; + } else { + proc->flags |= BLE_SM_PROC_F_IO_INJECTED; + memset(proc->tk, 0, 16); + proc->tk[0] = (pkey->passkey >> 0) & 0xff; + proc->tk[1] = (pkey->passkey >> 8) & 0xff; + proc->tk[2] = (pkey->passkey >> 16) & 0xff; + proc->tk[3] = (pkey->passkey >> 24) & 0xff; + if ((proc->flags & BLE_SM_PROC_F_INITIATOR) || + (proc->flags & BLE_SM_PROC_F_ADVANCE_ON_IO)) { + + res.execute = 1; + } + } + break; + + case BLE_SM_IOACT_NUMCMP: + if (!pkey->numcmp_accept) { + res.app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_NUMCMP); + res.sm_err = BLE_SM_ERR_NUMCMP; + } else { + proc->flags |= BLE_SM_PROC_F_IO_INJECTED; + if (proc->flags & BLE_SM_PROC_F_INITIATOR || + proc->flags & BLE_SM_PROC_F_ADVANCE_ON_IO) { + + res.execute = 1; + } + } + break; + +#if MYNEWT_VAL(BLE_SM_SC) + case BLE_SM_IOACT_OOB_SC: + if (!ble_sm_sc_oob_data_check(proc, + (pkey->oob_sc_data.local != NULL), + (pkey->oob_sc_data.remote != NULL))) { + res.app_status = BLE_HS_SM_US_ERR(BLE_SM_ERR_OOB); + res.sm_err = BLE_SM_ERR_OOB; + } else { + proc->flags |= BLE_SM_PROC_F_IO_INJECTED; + proc->oob_data_local = pkey->oob_sc_data.local; + proc->oob_data_remote = pkey->oob_sc_data.remote; + + /* Execute Confirm step */ + ble_sm_sc_oob_confirm(proc, &res); + } + break; +#endif + + default: + BLE_HS_DBG_ASSERT(0); + rc = BLE_HS_EINVAL; + break; + } + } + + ble_hs_unlock(); + + /* If application provided invalid input, return error without modifying + * SMP state. + */ + if (rc != 0) { + return rc; + } + + ble_sm_process_result(conn_handle, &res); + return res.app_status; +} + +void +ble_sm_connection_broken(uint16_t conn_handle) +{ + struct ble_sm_result res; + + memset(&res, 0, sizeof res); + res.app_status = BLE_HS_ENOTCONN; + res.enc_cb = 1; + + ble_sm_process_result(conn_handle, &res); +} + +int +ble_sm_init(void) +{ + int rc; + + STAILQ_INIT(&ble_sm_procs); + + rc = os_mempool_init(&ble_sm_proc_pool, + MYNEWT_VAL(BLE_SM_MAX_PROCS), + sizeof (struct ble_sm_proc), + ble_sm_proc_mem, + "ble_sm_proc_pool"); + if (rc != 0) { + return rc; + } + + ble_sm_sc_init(); + + return 0; +} +#else +/* if pairing is not supported it is only needed to reply with Pairing + * Failed with 'Pairing not Supported' reason so this function can be very + * simple + */ +static int +ble_sm_rx(struct ble_l2cap_chan *chan) +{ + struct ble_sm_pair_fail *cmd; + struct os_mbuf *txom; + uint16_t handle; + int rc; + + handle = ble_l2cap_get_conn_handle(chan); + if (!handle) { + return BLE_HS_ENOTCONN; + } + + cmd = ble_sm_cmd_get(BLE_SM_OP_PAIR_FAIL, sizeof(*cmd), &txom); + if (cmd == NULL) { + return BLE_HS_ENOMEM; + } + + cmd->reason = BLE_SM_ERR_PAIR_NOT_SUPP; + + ble_hs_lock(); + rc = ble_sm_tx(handle, txom); + ble_hs_unlock(); + + return rc; +} +#endif + +struct ble_l2cap_chan * +ble_sm_create_chan(uint16_t conn_handle) +{ + struct ble_l2cap_chan *chan; + + chan = ble_l2cap_chan_alloc(conn_handle); + if (chan == NULL) { + return NULL; + } + + chan->scid = BLE_L2CAP_CID_SM; + chan->dcid = BLE_L2CAP_CID_SM; + chan->my_mtu = BLE_SM_MTU; + chan->rx_fn = ble_sm_rx; + + return chan; +} |