/* * Copyright (c) 2017 Intel Corporation * Copyright (c) 2020 Lingao Meng * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include "crypto.h" #include "mesh.h" #include "net.h" #include "access.h" #include "foundation.h" #include "prov.h" #define LOG_LEVEL CONFIG_BT_MESH_PROV_LOG_LEVEL #include LOG_MODULE_REGISTER(bt_mesh_prov); struct bt_mesh_prov_link bt_mesh_prov_link; const struct bt_mesh_prov *bt_mesh_prov; /* Verify specification defined length: */ BUILD_ASSERT(sizeof(bt_mesh_prov_link.conf_inputs) == 145, "Confirmation inputs shall be 145 bytes"); int bt_mesh_prov_reset_state(void) { int err; const size_t offset = offsetof(struct bt_mesh_prov_link, addr); atomic_clear(bt_mesh_prov_link.flags); (void)memset((uint8_t *)&bt_mesh_prov_link + offset, 0, sizeof(bt_mesh_prov_link) - offset); err = bt_mesh_pub_key_gen(); if (err) { LOG_ERR("Failed to generate public key (%d)", err); return err; } return 0; } static bt_mesh_output_action_t output_action(uint8_t action) { switch (action) { case OUTPUT_OOB_BLINK: return BT_MESH_BLINK; case OUTPUT_OOB_BEEP: return BT_MESH_BEEP; case OUTPUT_OOB_VIBRATE: return BT_MESH_VIBRATE; case OUTPUT_OOB_NUMBER: return BT_MESH_DISPLAY_NUMBER; case OUTPUT_OOB_STRING: return BT_MESH_DISPLAY_STRING; default: return BT_MESH_NO_OUTPUT; } } static bt_mesh_input_action_t input_action(uint8_t action) { switch (action) { case INPUT_OOB_PUSH: return BT_MESH_PUSH; case INPUT_OOB_TWIST: return BT_MESH_TWIST; case INPUT_OOB_NUMBER: return BT_MESH_ENTER_NUMBER; case INPUT_OOB_STRING: return BT_MESH_ENTER_STRING; default: return BT_MESH_NO_INPUT; } } static int check_output_auth(bt_mesh_output_action_t output, uint8_t size) { if (!output) { return -EINVAL; } if (!(bt_mesh_prov->output_actions & output)) { return -EINVAL; } if (size > bt_mesh_prov->output_size) { return -EINVAL; } return 0; } static int check_input_auth(bt_mesh_input_action_t input, uint8_t size) { if (!input) { return -EINVAL; } if (!(bt_mesh_prov->input_actions & input)) { return -EINVAL; } if (size > bt_mesh_prov->input_size) { return -EINVAL; } return 0; } static void get_auth_string(char *str, uint8_t size) { uint64_t value; bt_rand(&value, sizeof(value)); static const char characters[36] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; for (int i = 0; i < size; i++) { /* pull base-36 digits: */ int idx = value % 36; value = value / 36; str[i] = characters[idx]; } str[size] = '\0'; memcpy(bt_mesh_prov_link.auth, str, size); memset(bt_mesh_prov_link.auth + size, 0, sizeof(bt_mesh_prov_link.auth) - size); } static uint32_t get_auth_number(bt_mesh_output_action_t output, bt_mesh_input_action_t input, uint8_t size) { const uint32_t divider[PROV_IO_OOB_SIZE_MAX] = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; uint8_t auth_size = bt_mesh_prov_auth_size_get(); uint32_t num = 0; bt_rand(&num, sizeof(num)); if (output == BT_MESH_BLINK || output == BT_MESH_BEEP || output == BT_MESH_VIBRATE || input == BT_MESH_PUSH || input == BT_MESH_TWIST) { /* According to MshPRTv1.1: 5.4.2.4, blink, beep vibrate, push and twist should be * a random integer between 0 and 10^size, *exclusive*: */ num = (num % (divider[size - 1] - 1)) + 1; } else { num %= divider[size - 1]; } sys_put_be32(num, &bt_mesh_prov_link.auth[auth_size - sizeof(num)]); memset(bt_mesh_prov_link.auth, 0, auth_size - sizeof(num)); return num; } int bt_mesh_prov_auth(bool is_provisioner, uint8_t method, uint8_t action, uint8_t size) { bt_mesh_output_action_t output; bt_mesh_input_action_t input; uint8_t auth_size = bt_mesh_prov_auth_size_get(); int err; switch (method) { case AUTH_METHOD_NO_OOB: if (action || size) { return -EINVAL; } (void)memset(bt_mesh_prov_link.auth, 0, auth_size); return 0; case AUTH_METHOD_STATIC: if (action || size) { return -EINVAL; } atomic_set_bit(bt_mesh_prov_link.flags, OOB_STATIC_KEY); return 0; case AUTH_METHOD_OUTPUT: output = output_action(action); if (is_provisioner) { if (output == BT_MESH_DISPLAY_STRING) { input = BT_MESH_ENTER_STRING; atomic_set_bit(bt_mesh_prov_link.flags, WAIT_STRING); } else { input = BT_MESH_ENTER_NUMBER; atomic_set_bit(bt_mesh_prov_link.flags, WAIT_NUMBER); } return bt_mesh_prov->input(input, size); } err = check_output_auth(output, size); if (err) { return err; } if (output == BT_MESH_DISPLAY_STRING) { char str[9]; atomic_set_bit(bt_mesh_prov_link.flags, NOTIFY_INPUT_COMPLETE); get_auth_string(str, size); return bt_mesh_prov->output_string(str); } atomic_set_bit(bt_mesh_prov_link.flags, NOTIFY_INPUT_COMPLETE); return bt_mesh_prov->output_number(output, get_auth_number(output, BT_MESH_NO_INPUT, size)); case AUTH_METHOD_INPUT: input = input_action(action); if (!is_provisioner) { err = check_input_auth(input, size); if (err) { return err; } if (input == BT_MESH_ENTER_STRING) { atomic_set_bit(bt_mesh_prov_link.flags, WAIT_STRING); } else { atomic_set_bit(bt_mesh_prov_link.flags, WAIT_NUMBER); } return bt_mesh_prov->input(input, size); } if (input == BT_MESH_ENTER_STRING) { char str[9]; atomic_set_bit(bt_mesh_prov_link.flags, NOTIFY_INPUT_COMPLETE); get_auth_string(str, size); return bt_mesh_prov->output_string(str); } atomic_set_bit(bt_mesh_prov_link.flags, NOTIFY_INPUT_COMPLETE); output = BT_MESH_DISPLAY_NUMBER; return bt_mesh_prov->output_number(output, get_auth_number(BT_MESH_NO_OUTPUT, input, size)); default: return -EINVAL; } } int bt_mesh_input_number(uint32_t num) { uint8_t auth_size = bt_mesh_prov_auth_size_get(); LOG_DBG("%u", num); if (!atomic_test_and_clear_bit(bt_mesh_prov_link.flags, WAIT_NUMBER)) { return -EINVAL; } sys_put_be32(num, &bt_mesh_prov_link.auth[auth_size - sizeof(num)]); bt_mesh_prov_link.role->input_complete(); return 0; } int bt_mesh_input_string(const char *str) { LOG_DBG("%s", str); if (strlen(str) > PROV_IO_OOB_SIZE_MAX || strlen(str) > bt_mesh_prov_link.oob_size) { return -ENOTSUP; } if (!atomic_test_and_clear_bit(bt_mesh_prov_link.flags, WAIT_STRING)) { return -EINVAL; } memcpy(bt_mesh_prov_link.auth, str, strlen(str)); bt_mesh_prov_link.role->input_complete(); return 0; } const struct bt_mesh_prov *bt_mesh_prov_get(void) { return bt_mesh_prov; } bool bt_mesh_prov_active(void) { return atomic_test_bit(bt_mesh_prov_link.flags, LINK_ACTIVE); } static void prov_recv(const struct prov_bearer *bearer, void *cb_data, struct net_buf_simple *buf) { static const uint8_t op_len[10] = { [PROV_INVITE] = PDU_LEN_INVITE, [PROV_CAPABILITIES] = PDU_LEN_CAPABILITIES, [PROV_START] = PDU_LEN_START, [PROV_PUB_KEY] = PDU_LEN_PUB_KEY, [PROV_INPUT_COMPLETE] = PDU_LEN_INPUT_COMPLETE, [PROV_CONFIRM] = PDU_LEN_CONFIRM, [PROV_RANDOM] = PDU_LEN_RANDOM, [PROV_DATA] = PDU_LEN_DATA, [PROV_COMPLETE] = PDU_LEN_COMPLETE, [PROV_FAILED] = PDU_LEN_FAILED, }; uint8_t type = buf->data[0]; LOG_DBG("type 0x%02x len %u", type, buf->len); if (type >= ARRAY_SIZE(bt_mesh_prov_link.role->op)) { LOG_ERR("Unknown provisioning PDU type 0x%02x", type); bt_mesh_prov_link.role->error(PROV_ERR_NVAL_PDU); return; } if ((type != PROV_FAILED && type != bt_mesh_prov_link.expect) || !bt_mesh_prov_link.role->op[type]) { LOG_WRN("Unexpected msg 0x%02x != 0x%02x", type, bt_mesh_prov_link.expect); bt_mesh_prov_link.role->error(PROV_ERR_UNEXP_PDU); return; } uint8_t expected = 1 + op_len[type]; if (type == PROV_CONFIRM || type == PROV_RANDOM) { /* Expected length depends on Auth size */ expected = 1 + bt_mesh_prov_auth_size_get(); } if (buf->len != expected) { LOG_ERR("Invalid length %u for type 0x%02x", buf->len, type); bt_mesh_prov_link.role->error(PROV_ERR_NVAL_FMT); return; } bt_mesh_prov_link.role->op[type](&buf->data[1]); } static void prov_link_opened(const struct prov_bearer *bearer, void *cb_data) { atomic_set_bit(bt_mesh_prov_link.flags, LINK_ACTIVE); if (bt_mesh_prov->link_open) { bt_mesh_prov->link_open(bearer->type); } bt_mesh_prov_link.bearer = bearer; if (bt_mesh_prov_link.role->link_opened) { bt_mesh_prov_link.role->link_opened(); } } static void prov_link_closed(const struct prov_bearer *bearer, void *cb_data, enum prov_bearer_link_status reason) { LOG_DBG("%u", reason); if (bt_mesh_prov_link.role->link_closed) { bt_mesh_prov_link.role->link_closed(reason); } if (bt_mesh_prov->link_close) { bt_mesh_prov->link_close(bearer->type); } } static void prov_bearer_error(const struct prov_bearer *bearer, void *cb_data, uint8_t err) { if (bt_mesh_prov_link.role->error) { bt_mesh_prov_link.role->error(err); } } static const struct prov_bearer_cb prov_bearer_cb = { .link_opened = prov_link_opened, .link_closed = prov_link_closed, .error = prov_bearer_error, .recv = prov_recv, }; const struct prov_bearer_cb *bt_mesh_prov_bearer_cb_get(void) { return &prov_bearer_cb; } void bt_mesh_prov_complete(uint16_t net_idx, uint16_t addr) { if (bt_mesh_prov->complete) { bt_mesh_prov->complete(net_idx, addr); } } void bt_mesh_prov_reset(void) { if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV)) { bt_mesh_pb_adv_reset(); } if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT)) { bt_mesh_pb_gatt_reset(); } bt_mesh_prov_reset_state(); if (bt_mesh_prov->reset) { bt_mesh_prov->reset(); } } int bt_mesh_prov_init(const struct bt_mesh_prov *prov_info) { if (!prov_info) { LOG_ERR("No provisioning context provided"); return -EINVAL; } bt_mesh_prov = prov_info; if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV)) { bt_mesh_pb_adv_init(); } if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT)) { bt_mesh_pb_gatt_init(); } return bt_mesh_prov_reset_state(); }