| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2017 Intel Corporation. All rights reserved. |
| * |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <stdbool.h> |
| #include <sys/uio.h> |
| #include <wordexp.h> |
| |
| #include <readline/readline.h> |
| #include <readline/history.h> |
| #include <glib.h> |
| |
| #include "src/shared/util.h" |
| #include "src/shared/ecc.h" |
| #include "src/shared/shell.h" |
| |
| #include "gdbus/gdbus.h" |
| #include "mesh/node.h" |
| #include "mesh/gatt.h" |
| #include "mesh/crypto.h" |
| #include "mesh/mesh-net.h" |
| #include "mesh/util.h" |
| #include "mesh/agent.h" |
| #include "mesh/prov.h" |
| #include "mesh/net.h" |
| |
| /* Provisioning Security Levels */ |
| #define MESH_PROV_SEC_HIGH 2 |
| #define MESH_PROV_SEC_MED 1 |
| #define MESH_PROV_SEC_LOW 0 |
| |
| |
| #define PROV_INVITE 0x00 |
| #define PROV_CAPS 0x01 |
| #define PROV_START 0x02 |
| #define PROV_PUB_KEY 0x03 |
| #define PROV_INP_CMPLT 0x04 |
| #define PROV_CONFIRM 0x05 |
| #define PROV_RANDOM 0x06 |
| #define PROV_DATA 0x07 |
| #define PROV_COMPLETE 0x08 |
| #define PROV_FAILED 0x09 |
| |
| #define PROV_NO_OOB 0 |
| #define PROV_STATIC_OOB 1 |
| #define PROV_OUTPUT_OOB 2 |
| #define PROV_INPUT_OOB 3 |
| |
| #define PROV_ERR_INVALID_PDU 0x01 |
| #define PROV_ERR_INVALID_FORMAT 0x02 |
| #define PROV_ERR_UNEXPECTED_PDU 0x03 |
| #define PROV_ERR_CONFIRM_FAILED 0x04 |
| #define PROV_ERR_INSUF_RESOURCE 0x05 |
| #define PROV_ERR_DECRYPT_FAILED 0x06 |
| #define PROV_ERR_UNEXPECTED_ERR 0x07 |
| #define PROV_ERR_CANT_ASSIGN_ADDR 0x08 |
| |
| /* For Deployment, Security levels below HIGH are *not* recomended */ |
| static uint8_t prov_sec_level = MESH_PROV_SEC_MED; |
| |
| /* Expected Provisioning PDU sizes */ |
| static const uint16_t expected_pdu_size[] = { |
| 1 + 1, /* PROV_INVITE */ |
| 1 + 1 + 2 + 1 + 1 + 1 + 2 + 1 + 2, /* PROV_CAPS */ |
| 1 + 1 + 1 + 1 + 1 + 1, /* PROV_START */ |
| 1 + 64, /* PROV_PUB_KEY */ |
| 1, /* PROV_INP_CMPLT */ |
| 1 + 16, /* PROV_CONFIRM */ |
| 1 + 16, /* PROV_RANDOM */ |
| 1 + 16 + 2 + 1 + 4 + 2 + 8, /* PROV_DATA */ |
| 1, /* PROV_COMPLETE */ |
| 1 + 1, /* PROV_FAILED */ |
| }; |
| |
| typedef struct __packed { |
| uint8_t attention; |
| } __attribute__ ((packed)) prov_invite; |
| |
| typedef struct { |
| uint8_t num_ele; |
| uint16_t algorithms; |
| uint8_t pub_type; |
| uint8_t static_type; |
| uint8_t output_size; |
| uint16_t output_action; |
| uint8_t input_size; |
| uint16_t input_action; |
| } __attribute__ ((packed)) prov_caps; |
| |
| typedef struct { |
| uint8_t algorithm; |
| uint8_t pub_key; |
| uint8_t auth_method; |
| uint8_t auth_action; |
| uint8_t auth_size; |
| } __attribute__ ((packed)) prov_start; |
| |
| typedef struct { |
| prov_invite invite; |
| prov_caps caps; |
| prov_start start; |
| uint8_t prv_pub_key[64]; |
| uint8_t dev_pub_key[64]; |
| } __attribute__ ((packed)) conf_input; |
| |
| struct prov_data { |
| GDBusProxy *prov_in; |
| provision_done_cb prov_done; |
| void *user_data; |
| uint16_t net_idx; |
| uint16_t new_addr; |
| uint8_t state; |
| uint8_t eph_priv_key[32]; |
| uint8_t ecdh_secret[32]; |
| conf_input conf_in; |
| uint8_t rand_auth[32]; |
| uint8_t salt[16]; |
| uint8_t conf_key[16]; |
| uint8_t mesh_conf[16]; |
| uint8_t dev_key[16]; |
| }; |
| |
| static uint8_t u16_highest_bit(uint16_t mask) |
| { |
| uint8_t cnt = 0; |
| |
| if (!mask) return 0xff; |
| |
| while (mask & 0xfffe) { |
| cnt++; |
| mask >>= 1; |
| } |
| |
| return cnt; |
| } |
| |
| bool prov_open(struct mesh_node *node, GDBusProxy *prov_in, uint16_t net_idx, |
| provision_done_cb cb, void *user_data) |
| { |
| uint8_t invite[] = { PROXY_PROVISIONING_PDU, PROV_INVITE, 0x10 }; |
| struct prov_data *prov = node_get_prov(node); |
| |
| if (prov) return false; |
| |
| prov = g_new0(struct prov_data, 1); |
| prov->prov_in = prov_in; |
| prov->net_idx = net_idx; |
| prov->prov_done = cb; |
| prov->user_data = user_data; |
| node_set_prov(node, prov); |
| prov->conf_in.invite.attention = invite[2]; |
| prov->state = PROV_INVITE; |
| |
| bt_shell_printf("Open-Node: %p\n", node); |
| bt_shell_printf("Open-Prov: %p\n", prov); |
| bt_shell_printf("Open-Prov: proxy %p\n", prov_in); |
| |
| return mesh_gatt_write(prov_in, invite, sizeof(invite), NULL, node); |
| } |
| |
| static bool prov_send_prov_data(void *node) |
| { |
| struct prov_data *prov = node_get_prov(node); |
| uint8_t out[35] = { PROXY_PROVISIONING_PDU, PROV_DATA }; |
| uint8_t key[16]; |
| uint8_t nonce[13]; |
| uint64_t mic; |
| |
| if (prov == NULL) return false; |
| |
| mesh_crypto_session_key(prov->ecdh_secret, prov->salt, key); |
| mesh_crypto_nonce(prov->ecdh_secret, prov->salt, nonce); |
| mesh_crypto_device_key(prov->ecdh_secret, prov->salt, prov->dev_key); |
| |
| print_byte_array("S-Key\t", key, sizeof(key)); |
| print_byte_array("S-Nonce\t", nonce, sizeof(nonce)); |
| print_byte_array("DevKey\t", prov->dev_key, sizeof(prov->dev_key)); |
| |
| if (!net_get_key(prov->net_idx, out + 2)) |
| return false; |
| |
| put_be16(prov->net_idx, out + 2 + 16); |
| net_get_flags(prov->net_idx, out + 2 + 16 + 2); |
| put_be32(net_get_iv_index(NULL), out + 2 + 16 + 2 + 1); |
| put_be16(prov->new_addr, out + 2 + 16 + 2 + 1 + 4); |
| |
| print_byte_array("Data\t", out + 2, 16 + 2 + 1 + 4 + 2); |
| |
| mesh_crypto_aes_ccm_encrypt(nonce, key, |
| NULL, 0, |
| out + 2, |
| sizeof(out) - 2 - sizeof(mic), |
| out + 2, |
| &mic, sizeof(mic)); |
| |
| print_byte_array("DataEncrypted + mic\t", out + 2, sizeof(out) - 2); |
| |
| prov->state = PROV_DATA; |
| return mesh_gatt_write(prov->prov_in, out, sizeof(out), NULL, node); |
| } |
| |
| static bool prov_send_confirm(void *node) |
| { |
| struct prov_data *prov = node_get_prov(node); |
| uint8_t out[18] = { PROXY_PROVISIONING_PDU, PROV_CONFIRM }; |
| |
| if (prov == NULL) return false; |
| |
| mesh_get_random_bytes(prov->rand_auth, 16); |
| |
| mesh_crypto_aes_cmac(prov->conf_key, prov->rand_auth, |
| sizeof(prov->rand_auth), out + 2); |
| |
| prov->state = PROV_CONFIRM; |
| return mesh_gatt_write(prov->prov_in, out, sizeof(out), NULL, node); |
| } |
| |
| static void prov_out_oob_done(oob_type_t type, void *buf, uint16_t len, |
| void *node) |
| { |
| struct prov_data *prov = node_get_prov(node); |
| |
| if (prov == NULL) return; |
| |
| switch (type) { |
| default: |
| case NONE: |
| case OUTPUT: |
| prov_complete(node, PROV_ERR_INVALID_PDU); |
| return; |
| |
| case ASCII: |
| case HEXADECIMAL: |
| if (len > 16) |
| prov_complete(node, PROV_ERR_INVALID_PDU); |
| |
| memcpy(prov->rand_auth + 16, buf, len); |
| break; |
| |
| case DECIMAL: |
| if (len != 4) |
| prov_complete(node, PROV_ERR_INVALID_PDU); |
| |
| memcpy(prov->rand_auth + |
| sizeof(prov->rand_auth) - |
| sizeof(uint32_t), |
| buf, len); |
| break; |
| } |
| |
| prov_send_confirm(node); |
| } |
| |
| static uint32_t power_ten(uint8_t power) |
| { |
| uint32_t ret = 1; |
| |
| while (power--) |
| ret *= 10; |
| |
| return ret; |
| } |
| |
| char *in_action[3] = { |
| "Push", |
| "Twist", |
| "Enter" |
| }; |
| |
| static void prov_calc_ecdh(DBusMessage *message, void *node) |
| { |
| struct prov_data *prov = node_get_prov(node); |
| uint8_t action = prov->conf_in.start.auth_action; |
| uint8_t size = prov->conf_in.start.auth_size; |
| char in_oob_display[100]; |
| uint8_t *tmp = (void *) in_oob_display; |
| uint32_t in_oob; |
| |
| if (prov == NULL) return; |
| |
| /* Convert to Mesh byte order */ |
| memcpy(tmp, prov->conf_in.dev_pub_key, 64); |
| swap_u256_bytes(tmp); |
| swap_u256_bytes(tmp + 32); |
| |
| ecdh_shared_secret(tmp, prov->eph_priv_key, prov->ecdh_secret); |
| |
| /* Convert to Mesh byte order */ |
| swap_u256_bytes(prov->ecdh_secret); |
| |
| mesh_crypto_s1(&prov->conf_in, |
| sizeof(prov->conf_in), prov->salt); |
| |
| mesh_crypto_prov_conf_key(prov->ecdh_secret, |
| prov->salt, prov->conf_key); |
| |
| switch (prov->conf_in.start.auth_method) { |
| default: |
| prov_complete(node, PROV_ERR_INVALID_PDU); |
| break; |
| |
| case 0: /* No OOB */ |
| prov_send_confirm(node); |
| break; |
| |
| case 1: /* Static OOB */ |
| agent_input_request(HEXADECIMAL, |
| 16, |
| prov_out_oob_done, node); |
| break; |
| |
| case 2: /* Output OOB */ |
| if (action <= 3) |
| agent_input_request(DECIMAL, |
| size, |
| prov_out_oob_done, node); |
| else |
| agent_input_request(ASCII, |
| size, |
| prov_out_oob_done, node); |
| break; |
| |
| case 3: /* Input OOB */ |
| |
| if (action <= 2) { |
| mesh_get_random_bytes(&in_oob, sizeof(in_oob)); |
| in_oob %= power_ten(size); |
| sprintf(in_oob_display, "%s %d on device\n", |
| in_action[action], in_oob); |
| put_be32(in_oob, |
| prov->rand_auth + |
| sizeof(prov->rand_auth) - |
| sizeof(uint32_t)); |
| } else { |
| uint8_t in_ascii[9]; |
| int i = size; |
| |
| mesh_get_random_bytes(in_ascii, i); |
| |
| while (i--) { |
| in_ascii[i] = |
| in_ascii[i] % ((26 * 2) + 10); |
| if (in_ascii[i] >= 10 + 26) |
| in_ascii[i] += 'a' - (10 + 26); |
| else if (in_ascii[i] >= 10) |
| in_ascii[i] += 'A' - 10; |
| else |
| in_ascii[i] += '0'; |
| } |
| in_ascii[size] = '\0'; |
| memcpy(prov->rand_auth + 16, in_ascii, size); |
| sprintf(in_oob_display, |
| "Enter %s on device\n", |
| in_ascii); |
| } |
| bt_shell_printf("Agent String: %s\n", in_oob_display); |
| agent_output_request(in_oob_display); |
| break; |
| } |
| } |
| |
| static void prov_send_pub_key(struct mesh_node *node) |
| { |
| struct prov_data *prov = node_get_prov(node); |
| uint8_t out[66] = { PROXY_PROVISIONING_PDU, PROV_PUB_KEY }; |
| GDBusReturnFunction cb = NULL; |
| |
| if (prov == NULL) return; |
| |
| if (prov->conf_in.start.pub_key) |
| cb = prov_calc_ecdh; |
| |
| memcpy(out + 2, prov->conf_in.prv_pub_key, 64); |
| prov->state = PROV_PUB_KEY; |
| mesh_gatt_write(prov->prov_in, out, 66, cb, node); |
| } |
| |
| static void prov_oob_pub_key(oob_type_t type, void *buf, uint16_t len, |
| void *node) |
| { |
| struct prov_data *prov = node_get_prov(node); |
| |
| if (prov == NULL) return; |
| |
| memcpy(prov->conf_in.dev_pub_key, buf, 64); |
| prov_send_pub_key(node); |
| } |
| |
| static void prov_start_cmplt(DBusMessage *message, void *node) |
| { |
| struct prov_data *prov = node_get_prov(node); |
| |
| if (prov == NULL) return; |
| |
| if (prov->conf_in.start.pub_key) |
| agent_input_request(HEXADECIMAL, 64, prov_oob_pub_key, node); |
| else |
| prov_send_pub_key(node); |
| } |
| |
| bool prov_data_ready(struct mesh_node *node, uint8_t *buf, uint8_t len) |
| { |
| struct prov_data *prov = node_get_prov(node); |
| uint8_t sec_level = MESH_PROV_SEC_HIGH; |
| uint8_t out[35] = { PROXY_PROVISIONING_PDU }; |
| |
| if (prov == NULL || len < 2) return false; |
| |
| buf++; |
| len--; |
| |
| bt_shell_printf("Got provisioning data (%d bytes)\n", len); |
| |
| if (buf[0] > PROV_FAILED || expected_pdu_size[buf[0]] != len) |
| return prov_complete(node, PROV_ERR_INVALID_PDU); |
| |
| print_byte_array("\t", buf, len); |
| |
| if (buf[0] == PROV_FAILED) |
| return prov_complete(node, buf[1]); |
| |
| /* Check provisioning state */ |
| switch (prov->state) { |
| default: |
| return prov_complete(node, PROV_ERR_INVALID_PDU); |
| |
| case PROV_INVITE: |
| |
| if (buf[0] != PROV_CAPS) |
| return prov_complete(node, |
| PROV_ERR_INVALID_PDU); |
| |
| /* Normalize to beginning of packed Param struct */ |
| buf++; |
| len--; |
| |
| /* Save Capability values */ |
| memcpy(&prov->conf_in.caps, buf, len); |
| |
| sec_level = prov_get_sec_level(); |
| |
| if (sec_level == MESH_PROV_SEC_HIGH) { |
| |
| /* Enforce High Security */ |
| if (prov->conf_in.caps.pub_type != 1 && |
| prov->conf_in.caps.static_type != 1) |
| return prov_complete(node, |
| PROV_ERR_INVALID_PDU); |
| |
| } else if (sec_level == MESH_PROV_SEC_MED) { |
| |
| /* Enforce Medium Security */ |
| if (prov->conf_in.caps.pub_type != 1 && |
| prov->conf_in.caps.static_type != 1 && |
| prov->conf_in.caps.input_size == 0 && |
| prov->conf_in.caps.output_size == 0) |
| return prov_complete(node, |
| PROV_ERR_INVALID_PDU); |
| |
| } |
| |
| /* Num Elements cannot be Zero */ |
| if (prov->conf_in.caps.num_ele == 0) |
| return prov_complete(node, |
| PROV_ERR_INVALID_PDU); |
| |
| /* All nodes must support Algorithm 0x0001 */ |
| if (!(get_be16(buf + 1) & 0x0001)) |
| return prov_complete(node, |
| PROV_ERR_INVALID_PDU); |
| |
| /* Pub Key and Static type may not be > 1 */ |
| if (prov->conf_in.caps.pub_type > 0x01 || |
| prov->conf_in.caps.static_type > 0x01) |
| return prov_complete(node, |
| PROV_ERR_INVALID_PDU); |
| |
| prov->new_addr = |
| net_obtain_address(prov->conf_in.caps.num_ele); |
| |
| if (!prov->new_addr) |
| return prov_complete(node, |
| PROV_ERR_INVALID_PDU); |
| |
| out[1] = PROV_START; |
| prov->conf_in.start.algorithm = 0; |
| prov->conf_in.start.pub_key = |
| prov->conf_in.caps.pub_type; |
| |
| /* Compose START based on most secure values */ |
| if (prov->conf_in.caps.static_type) { |
| |
| prov->conf_in.start.auth_method = |
| PROV_STATIC_OOB; |
| |
| } else if (prov->conf_in.caps.output_size > |
| prov->conf_in.caps.input_size) { |
| |
| prov->conf_in.start.auth_method = |
| PROV_OUTPUT_OOB; |
| prov->conf_in.start.auth_action = |
| u16_highest_bit(get_be16(buf + 6)); |
| prov->conf_in.start.auth_size = |
| prov->conf_in.caps.output_size; |
| |
| } else if (prov->conf_in.caps.input_size > 0) { |
| |
| prov->conf_in.start.auth_method = |
| PROV_INPUT_OOB; |
| prov->conf_in.start.auth_action = |
| u16_highest_bit(get_be16(buf + 9)); |
| prov->conf_in.start.auth_size = |
| prov->conf_in.caps.input_size; |
| } |
| |
| /* Range Check START values */ |
| if (prov->conf_in.start.auth_size > 8) |
| return prov_complete(node, |
| PROV_ERR_INVALID_PDU); |
| |
| prov->state = PROV_START; |
| |
| memcpy(out + 2, &prov->conf_in.start, 5); |
| |
| ecc_make_key(prov->conf_in.prv_pub_key, |
| prov->eph_priv_key); |
| |
| /* Swap public key to share into Mesh byte ordering */ |
| swap_u256_bytes(prov->conf_in.prv_pub_key); |
| swap_u256_bytes(prov->conf_in.prv_pub_key + 32); |
| |
| return mesh_gatt_write(prov->prov_in, out, 7, |
| prov_start_cmplt, node); |
| |
| |
| case PROV_PUB_KEY: |
| if (buf[0] == PROV_PUB_KEY && |
| !prov->conf_in.start.pub_key) { |
| |
| memcpy(prov->conf_in.dev_pub_key, buf + 1, 64); |
| prov_calc_ecdh(NULL, node); |
| return true; |
| |
| } else if (buf[0] == PROV_INP_CMPLT) { |
| agent_output_request_cancel(); |
| return prov_send_confirm(node); |
| } else |
| return prov_complete(node, |
| PROV_ERR_INVALID_PDU); |
| |
| case PROV_CONFIRM: |
| if (buf[0] != PROV_CONFIRM) |
| return prov_complete(node, |
| PROV_ERR_INVALID_PDU); |
| |
| memcpy(prov->mesh_conf, buf + 1, 16); |
| |
| out[1] = PROV_RANDOM; |
| memcpy(out + 2, prov->rand_auth, 16); |
| |
| prov->state = PROV_RANDOM; |
| return mesh_gatt_write(prov->prov_in, out, 18, |
| NULL, node); |
| |
| case PROV_RANDOM: |
| if (buf[0] != PROV_RANDOM) |
| return prov_complete(node, |
| PROV_ERR_INVALID_PDU); |
| |
| /* Calculate New Salt while we still have |
| * both random values */ |
| mesh_crypto_prov_prov_salt(prov->salt, |
| prov->rand_auth, |
| buf + 1, |
| prov->salt); |
| |
| /* Calculate meshs Conf Value */ |
| memcpy(prov->rand_auth, buf + 1, 16); |
| mesh_crypto_aes_cmac(prov->conf_key, prov->rand_auth, |
| sizeof(prov->rand_auth), out + 1); |
| |
| /* Validate Mesh confirmation */ |
| if (memcmp(out + 1, prov->mesh_conf, 16) != 0) |
| return prov_complete(node, |
| PROV_ERR_INVALID_PDU); |
| |
| bt_shell_printf("Confirmation Validated\n"); |
| |
| prov_send_prov_data(node); |
| |
| return true; |
| |
| case PROV_DATA: |
| if (buf[0] != PROV_COMPLETE) |
| return prov_complete(node, |
| PROV_ERR_INVALID_PDU); |
| |
| return prov_complete(node, 0); |
| } |
| |
| |
| |
| /* Compose appropriate reply for the prov state message */ |
| /* Send reply via mesh_gatt_write() */ |
| /* If done, call prov_done calllback and free prov housekeeping data */ |
| bt_shell_printf("Got provisioning data (%d bytes)\n", len); |
| print_byte_array("\t", buf, len); |
| |
| return true; |
| } |
| |
| bool prov_complete(struct mesh_node *node, uint8_t status) |
| { |
| struct prov_data *prov = node_get_prov(node); |
| void *user_data; |
| provision_done_cb cb; |
| |
| if (prov == NULL) return false; |
| |
| if (status && prov->new_addr && prov->conf_in.caps.num_ele) { |
| net_release_address(prov->new_addr, prov->conf_in.caps.num_ele); |
| } |
| |
| if (!status) { |
| node_set_num_elements(node, prov->conf_in.caps.num_ele); |
| node_set_primary(node, prov->new_addr); |
| node_set_device_key(node, prov->dev_key); |
| node_net_key_add(node, prov->net_idx); |
| } |
| |
| user_data = prov->user_data; |
| cb = prov->prov_done; |
| g_free(prov); |
| node_set_prov(node, NULL); |
| if (cb) cb(user_data, status); |
| |
| return true; |
| } |
| |
| bool prov_set_sec_level(uint8_t level) |
| { |
| if (level > MESH_PROV_SEC_HIGH) |
| return false; |
| |
| prov_sec_level = level; |
| |
| return true; |
| } |
| |
| uint8_t prov_get_sec_level(void) |
| { |
| return prov_sec_level; |
| } |