/* * 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 #include #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, res->bonded); } 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(); res.bonded = bonded; 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, 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; }