--- mesh/prov-acceptor.c | 711 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 711 insertions(+) create mode 100644 mesh/prov-acceptor.c diff --git a/mesh/prov-acceptor.c b/mesh/prov-acceptor.c new file mode 100644 index 000000000..8c3e2e358 --- /dev/null +++ b/mesh/prov-acceptor.c @@ -0,0 +1,711 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/select.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <fcntl.h> +#include <termios.h> + +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <getopt.h> +#include <time.h> +#include <ell/ell.h> + +#include "mesh/mesh-defs.h" +#include "src/shared/ecc.h" + +#include "mesh/util.h" +#include "mesh/net_keys.h" +#include "mesh/crypto.h" +#include "mesh/net.h" +#include "mesh/error.h" +#include "mesh/prov.h" +#include "mesh/provision.h" +#include "mesh/pb-adv.h" +#include "mesh/mesh.h" +#include "mesh/agent.h" + +/* Quick size sanity check */ +static const uint16_t expected_pdu_size[] = { + 2, /* PROV_INVITE */ + 12, /* PROV_CAPS */ + 6, /* PROV_START */ + 65, /* PROV_PUB_KEY */ + 1, /* PROV_INP_CMPLT */ + 17, /* PROV_CONFIRM */ + 17, /* PROV_RANDOM */ + 34, /* PROV_DATA */ + 1, /* PROV_COMPLETE */ + 2, /* PROV_FAILED */ +}; + +#define BEACON_TYPE_UNPROVISIONED 0x00 + +static const uint8_t pkt_filter = MESH_AD_TYPE_PROVISION; +static const uint8_t bec_filter[] = {MESH_AD_TYPE_BEACON, + BEACON_TYPE_UNPROVISIONED}; + +enum acp_state { + ACP_PROV_IDLE = 0, + ACP_PROV_CAPS_SENT, + ACP_PROV_CAPS_ACKED, + ACP_PROV_KEY_SENT, + ACP_PROV_KEY_ACKED, + ACP_PROV_INP_CMPLT_SENT, + ACP_PROV_INP_CMPLT_ACKED, + ACP_PROV_CONF_SENT, + ACP_PROV_CONF_ACKED, + ACP_PROV_RAND_SENT, + ACP_PROV_RAND_ACKED, + ACP_PROV_CMPLT_SENT, + ACP_PROV_FAIL_SENT, +}; + +#define MAT_REMOTE_PUBLIC 0x01 +#define MAT_LOCAL_PRIVATE 0x02 +#define MAT_RAND_AUTH 0x04 +#define MAT_SECRET (MAT_REMOTE_PUBLIC | MAT_LOCAL_PRIVATE) + +struct mesh_prov_acceptor { + mesh_prov_acceptor_complete_func_t cmplt; + prov_trans_tx_t trans_tx; + void *agent; + void *caller_data; + void *trans_data; + struct l_timeout *timeout; + uint32_t to_secs; + enum acp_state state; + uint8_t transport; + uint8_t material; + uint8_t expected; + int8_t previous; + struct conf_input conf_inputs; + uint8_t calc_key[16]; + uint8_t salt[16]; + uint8_t confirm[16]; + uint8_t s_key[16]; + uint8_t s_nonce[13]; + uint8_t private_key[32]; + uint8_t secret[32]; + uint8_t rand_auth_workspace[48]; +}; + +static struct mesh_prov_acceptor *prov = NULL; + +static void acceptor_free() +{ + + if (prov) { + l_timeout_remove(prov->timeout); + } + + mesh_send_cancel(bec_filter, sizeof(bec_filter)); + mesh_send_cancel(&pkt_filter, sizeof(pkt_filter)); + + if (prov->trans_tx) { + if (prov->transport == PB_ADV) + pb_adv_unreg(prov->trans_data); +#if defined(GATT_ENABLED) + /* TODO: Cleanup GATT bearer if exists */ + else if (prov->transport == PB_GATT) + pb_gatt_unreg(prov->trans_data); +#endif + } + + l_free(prov); + prov = NULL; +} + +static void acp_prov_close(void *user_data, uint8_t reason) +{ + /* TODO: Handle Close */ +} + +static void prov_to(struct l_timeout *timeout, void *user_data) +{ + struct mesh_prov_acceptor *rx_prov = user_data; + uint8_t fail_code[2] = {PROV_FAILED, PROV_ERR_UNEXPECTED_ERR}; + + if (rx_prov != prov) + return; + + prov->timeout = NULL; + + if (prov->cmplt && prov->trans_tx) { + prov->cmplt(prov->caller_data, PROV_ERR_TIMEOUT, NULL); + prov->cmplt = NULL; + prov->trans_tx(prov->trans_data, fail_code, 2); + prov->timeout = l_timeout_create(1, prov_to, prov, NULL); + return; + } + + acceptor_free(); +} + +static void acp_prov_open(void *user_data, prov_trans_tx_t trans_tx, + void *trans_data, uint8_t transport) +{ + struct mesh_prov_acceptor *rx_prov = user_data; + + /* Only one provisioning session may be open at a time */ + if (rx_prov != prov) + return; + + /* Only one provisioning session may be open at a time */ + if (prov->trans_tx && prov->trans_tx != trans_tx && + prov->transport != transport) + return; + + if (transport == PB_ADV) { +#if defined(GATT_ENABLED) + /* TODO: Disable PB-GATT */ +#endif + } + +#if defined(GATT_ENABLED) + else if (transport == PB_GATT) { + /* TODO: Disable PB-ADV */ + pb_adv_unreg(prov); + } +#endif + + else + return; + + prov->trans_tx = trans_tx; + prov->transport = transport; + prov->trans_data = trans_data; + prov->timeout = l_timeout_create(prov->to_secs, prov_to, prov, NULL); +} + +static void swap_u256_bytes(uint8_t *u256) +{ + int i; + + /* End-to-End byte reflection of 32 octet buffer */ + for (i = 0; i < 16; i++) { + u256[i] ^= u256[31 - i]; + u256[31 - i] ^= u256[i]; + u256[i] ^= u256[31 - i]; + } +} + +static void prov_calc_secret(const uint8_t *pub, const uint8_t *priv, + uint8_t *secret) +{ + uint8_t tmp[64]; + + /* Convert to ECC byte order */ + memcpy(tmp, pub, 64); + swap_u256_bytes(tmp); + swap_u256_bytes(tmp + 32); + + ecdh_shared_secret(tmp, priv, secret); + + /* Convert to Mesh byte order */ + swap_u256_bytes(secret); +} + +static void acp_credentials(struct mesh_prov_acceptor *prov) +{ + prov_calc_secret(prov->conf_inputs.prv_pub_key, + prov->private_key, prov->secret); + + mesh_crypto_s1(&prov->conf_inputs, + sizeof(prov->conf_inputs), prov->salt); + + mesh_crypto_prov_conf_key(prov->secret, prov->salt, + prov->calc_key); + + l_getrandom(prov->rand_auth_workspace, 16); + + print_packet("PublicKeyProv", prov->conf_inputs.prv_pub_key, 64); + print_packet("PublicKeyDev", prov->conf_inputs.dev_pub_key, 64); + print_packet("PrivateKeyLocal", prov->private_key, 32); + print_packet("ConfirmationInputs", &prov->conf_inputs, + sizeof(prov->conf_inputs)); + print_packet("ECDHSecret", prov->secret, 32); + print_packet("LocalRandom", prov->rand_auth_workspace, 16); + print_packet("ConfirmationSalt", prov->salt, 16); + print_packet("ConfirmationKey", prov->calc_key, 16); +} + +static uint32_t digit_mod(uint8_t power) +{ + uint32_t ret = 1; + + while (power--) + ret *= 10; + + return ret; +} + +static void number_cb(void *user_data, mesh_error_t err, uint32_t number) +{ + struct mesh_prov_acceptor *rx_prov = user_data; + uint8_t out[2]; + + if (prov != rx_prov) + return; + + if (err) { + out[0] = PROV_FAILED; + out[1] = PROV_ERR_UNEXPECTED_ERR; + prov->trans_tx(prov->trans_data, out, 2); + return; + } + + /* Save two copies, to generate two confirmation values */ + l_put_be32(number, prov->rand_auth_workspace + 28); + l_put_be32(number, prov->rand_auth_workspace + 44); + prov->material |= MAT_RAND_AUTH; + out[0] = PROV_INP_CMPLT; + prov->trans_tx(prov->trans_data, out, 1); +} + +static void static_cb(void *user_data, mesh_error_t err, + uint8_t *key, uint32_t len) +{ + struct mesh_prov_acceptor *rx_prov = user_data; + uint8_t out[2]; + + if (prov != rx_prov) + return; + + if (err || !key || len != 16) { + out[0] = PROV_FAILED; + out[1] = PROV_ERR_UNEXPECTED_ERR; + prov->trans_tx(prov->trans_data, out, 2); + return; + } + + /* Save two copies, to generate two confirmation values */ + memcpy(prov->rand_auth_workspace + 16, key, 16); + memcpy(prov->rand_auth_workspace + 32, key, 16); + prov->material |= MAT_RAND_AUTH; +} + +static void priv_key_cb(void *user_data, mesh_error_t err, + uint8_t *key, uint32_t len) +{ + struct mesh_prov_acceptor *rx_prov = user_data; + uint8_t out[2]; + + if (prov != rx_prov) + return; + + if (err || !key || len != 32) { + out[0] = PROV_FAILED; + out[1] = PROV_ERR_UNEXPECTED_ERR; + prov->trans_tx(prov->trans_data, out, 2); + return; + } + + memcpy(prov->private_key, key, 32); + ecc_make_public_key(prov->private_key, + prov->conf_inputs.dev_pub_key); + + /* Convert to Mesh byte order */ + swap_u256_bytes(prov->conf_inputs.dev_pub_key); + swap_u256_bytes(prov->conf_inputs.dev_pub_key + 32); + + prov->material |= MAT_LOCAL_PRIVATE; + if ((prov->material & MAT_SECRET) == MAT_SECRET) + acp_credentials(prov); +} + +static void acp_prov_rx(void *user_data, const uint8_t *data, uint16_t len) +{ + struct mesh_prov_acceptor *rx_prov = user_data; + struct mesh_prov_node_info *info; + uint8_t *out; + uint8_t type = *data++; + uint8_t fail_code[2]; + uint32_t oob_key; + uint64_t decode_mic; + bool result; + + if (rx_prov != prov || !prov->trans_tx) + return; + + l_debug("Provisioning packet received type: %2.2x (%u octets)", + type, len); + + if (type == prov->previous) { + l_error("Ignore repeated %2.2x packet", type); + return; + } else if (type > prov->expected || type < prov->previous) { + l_error("Expected %2.2x, Got:%2.2x", prov->expected, type); + fail_code[1] = PROV_ERR_UNEXPECTED_PDU; + goto failure; + } + + if (type >= L_ARRAY_SIZE(expected_pdu_size) || + len != expected_pdu_size[type]) { + l_error("Expected PDU size %d, Got %d (type: %2.2x)", + len, expected_pdu_size[type], type); + fail_code[1] = PROV_ERR_INVALID_FORMAT; + goto failure; + } + + switch (type){ + case PROV_INVITE: /* Prov Invite */ + /* Prov Capabilities */ + out = l_malloc(1 + sizeof(struct mesh_net_prov_caps)); + out[0] = PROV_CAPS; + memcpy(out + 1, &prov->conf_inputs.caps, + sizeof(prov->conf_inputs.caps)); + + prov->conf_inputs.invite.attention = data[0]; + + prov->state = ACP_PROV_CAPS_SENT; + prov->expected = PROV_START; + prov->trans_tx(prov->trans_data, + out, sizeof(prov->conf_inputs.caps) + 1); + l_free(out); + break; + + case PROV_START: /* Prov Start */ + memcpy(&prov->conf_inputs.start, data, + sizeof(prov->conf_inputs.start)); + + if (prov->conf_inputs.start.algorithm || + prov->conf_inputs.start.pub_key > 1 || + prov->conf_inputs.start.auth_method > 3) { + fail_code[1] = PROV_ERR_INVALID_FORMAT; + goto failure; + } + + if (prov->conf_inputs.start.pub_key) { + if (prov->conf_inputs.caps.pub_type) { + /* Prompt Agent for Private Key of OOB */ + mesh_agent_request_private_key(prov->agent, + priv_key_cb, prov); + } else { + fail_code[1] = PROV_ERR_INVALID_PDU; + goto failure; + } + } else { + /* Ephemeral Public Key requested */ + ecc_make_key(prov->conf_inputs.dev_pub_key, + prov->private_key); + swap_u256_bytes(prov->conf_inputs.dev_pub_key); + swap_u256_bytes(prov->conf_inputs.dev_pub_key + 32); + prov->material |= MAT_LOCAL_PRIVATE; + } + + prov->expected = PROV_PUB_KEY; + break; + + case PROV_PUB_KEY: /* Public Key */ + /* Save Key */ + memcpy(prov->conf_inputs.prv_pub_key, data, 64); + prov->material |= MAT_REMOTE_PUBLIC; + prov->expected = PROV_CONFIRM; + + if ((prov->material & MAT_SECRET) != MAT_SECRET) + return; + + acp_credentials(prov); + + if (!prov->conf_inputs.start.pub_key) { + out = l_malloc(65); + out[0] = PROV_PUB_KEY; + memcpy(out + 1, prov->conf_inputs.dev_pub_key, 64); + prov->trans_tx(prov->trans_data, out, 65); + l_free(out); + } + + /* Start Step 3 */ + switch (prov->conf_inputs.start.auth_method) { + default: + case 0: + /* Auth Type 3c - No OOB */ + break; + + case 1: + /* Auth Type 3c - Static OOB */ + /* Prompt Agent for Static OOB */ + fail_code[1] = mesh_agent_request_static(prov->agent, + static_cb, prov); + + if (fail_code[1]) + goto failure; + + break; + + case 2: + /* Auth Type 3a - Output OOB */ + l_getrandom(&oob_key, sizeof(oob_key)); + oob_key %= digit_mod(prov->conf_inputs.start.auth_size); + + /* Save two copies, for two confirmation values */ + l_put_be32(oob_key, prov->rand_auth_workspace + 28); + l_put_be32(oob_key, prov->rand_auth_workspace + 44); + prov->material |= MAT_RAND_AUTH; + + if (prov->conf_inputs.start.auth_action == + PROV_ACTION_OUT_ALPHA) { + /* TODO: Construst NUL-term string to pass */ + fail_code[1] = mesh_agent_display_string( + prov->agent, NULL, NULL, prov); + } else { + /* Ask Agent to Display U32 */ + fail_code[1] = mesh_agent_display_number( + prov->agent, false, + prov->conf_inputs.start.auth_action, + oob_key, NULL, prov); + } + + if (fail_code[1]) + goto failure; + + break; + + case 3: + /* Auth Type 3b - input OOB */ + /* Prompt Agent for Input OOB */ + if (prov->conf_inputs.start.auth_action == + PROV_ACTION_IN_ALPHA) { + fail_code[1] = mesh_agent_prompt_alpha( + prov->agent, + static_cb, prov); + } else { + fail_code[1] = mesh_agent_prompt_number( + prov->agent, false, + prov->conf_inputs.start.auth_action, + number_cb, prov); + } + + if (fail_code[1]) + goto failure; + + break; + } + + prov->expected = PROV_CONFIRM; + break; + + case PROV_CONFIRM: /* Confirmation */ + out = l_malloc(17); + out[0] = PROV_CONFIRM; + + /* Calculate and Send our Confirmation */ + mesh_crypto_aes_cmac(prov->calc_key, prov->rand_auth_workspace, + 32, out + 1); + prov->trans_tx(prov->trans_data, out, 17); + l_free(out); + + /* Save Provisioners confirmation for later compare */ + memcpy(prov->confirm, data, 16); + prov->expected = PROV_RANDOM; + break; + + case PROV_RANDOM: /* Random Value */ + out = l_malloc(17); + /* Calculate Session key (needed later) while data is fresh */ + mesh_crypto_prov_prov_salt(prov->salt, data, + prov->rand_auth_workspace, + prov->salt); + mesh_crypto_session_key(prov->secret, prov->salt, prov->s_key); + mesh_crypto_nonce(prov->secret, prov->salt, prov->s_nonce); + + /* Calculate expected Provisioner Confirm */ + memcpy(prov->rand_auth_workspace + 16, data, 16); + mesh_crypto_aes_cmac(prov->calc_key, + prov->rand_auth_workspace + 16, 32, out); + + /* Compare our calculation with Provisioners */ + if (memcmp(out, prov->confirm, 16)) { + fail_code[1] = PROV_ERR_CONFIRM_FAILED; + l_free(out); + goto failure; + } + + /* Send Random value we used */ + out[0] = PROV_RANDOM; + memcpy(out + 1, prov->rand_auth_workspace, 16); + prov->trans_tx(prov->trans_data, out, 17); + l_free(out); + prov->expected = PROV_DATA; + break; + + case PROV_DATA: /* Provisioning Data */ + + /* Calculate our device key */ + mesh_crypto_device_key(prov->secret, + prov->salt, + prov->calc_key); + + /* Decrypt new node data into workspace */ + mesh_crypto_aes_ccm_decrypt(prov->s_nonce, prov->s_key, + NULL, 0, + data, len - 1, prov->rand_auth_workspace, + &decode_mic, sizeof(decode_mic)); + + /* Validate that the data hasn't been messed with in transit */ + if (l_get_be64(data + 25) != decode_mic) { + l_error("Provisioning Failed-MIC compare"); + fail_code[1] = PROV_ERR_DECRYPT_FAILED; + goto failure; + } + + info = l_malloc(sizeof(struct mesh_prov_node_info)); + + memcpy(info->device_key, prov->calc_key, 16); + memcpy(info->net_key, prov->rand_auth_workspace, 16); + info->net_index = l_get_be16(prov->rand_auth_workspace + 16); + info->flags = prov->rand_auth_workspace[18]; + info->iv_index = l_get_be32(prov->rand_auth_workspace + 19); + info->unicast = l_get_be16(prov->rand_auth_workspace + 23); + + result = prov->cmplt(prov->caller_data, PROV_ERR_SUCCESS, info); + prov->cmplt = NULL; + l_free(info); + + if (result) { + prov->rand_auth_workspace[0] = PROV_COMPLETE; + prov->trans_tx(prov->trans_data, + prov->rand_auth_workspace, 1); + goto cleanup; + } else { + fail_code[1] = PROV_ERR_UNEXPECTED_ERR; + goto failure; + } + break; + + case PROV_FAILED: /* Provisioning Error -- abort */ + /* TODO: Call Complete Callback (Fail)*/ + prov->cmplt(prov->caller_data, + data[0] ? data[0] : PROV_ERR_UNEXPECTED_ERR, + NULL); + prov->cmplt = NULL; + goto cleanup; + } + + prov->previous = type; + return; + +failure: + fail_code[0] = PROV_FAILED; + prov->trans_tx(prov->trans_data, fail_code, 2); + if (prov->cmplt) + prov->cmplt(prov->caller_data, fail_code[1], NULL); + prov->cmplt = NULL; + +cleanup: + l_timeout_remove(prov->timeout); + + /* Give PB Link 5 seconds to end session */ + prov->timeout = l_timeout_create(5, prov_to, prov, NULL); +} + +static void acp_prov_ack(void *user_data, uint8_t msg_num) +{ + /* TODO: Handle PB-ADV Ack */ +} + + +/* This starts unprovisioned device beacon */ +bool acceptor_start(uint8_t num_ele, uint8_t uuid[16], + uint16_t algorithms, uint32_t timeout, + struct mesh_agent *agent, + mesh_prov_acceptor_complete_func_t complete_cb, + void *caller_data) +{ + struct mesh_agent_prov_caps *caps; + uint8_t beacon[24] = {MESH_AD_TYPE_BEACON, + BEACON_TYPE_UNPROVISIONED}; + uint8_t len = sizeof(beacon) - sizeof(uint32_t); + bool result; + + /* Invoked from Join() method in mesh-api.txt, to join a + * remote mesh network. + */ + +#if defined(GATT_ENABLED) + /* If we support PB-GATT, that means we need to set up a GATT + * service, and need the existence of bluetoothd, with it's + * own LE capable controller. That is a battle for another day. + * We will *always* support PB-ADV. + */ +#endif + if (prov) + return false; + + prov = l_new(struct mesh_prov_acceptor, 1); + prov->to_secs = timeout; + prov->agent = agent; + prov->cmplt = complete_cb; + prov->previous = -1; + prov->caller_data = caller_data; + + caps = mesh_agent_get_caps(agent); + + /* TODO: Should we sanity check values here or elsewhere? */ + prov->conf_inputs.caps.num_ele = num_ele; + prov->conf_inputs.caps.pub_type = caps->pub_type; + prov->conf_inputs.caps.static_type = caps->static_type; + prov->conf_inputs.caps.output_size = caps->output_size; + prov->conf_inputs.caps.input_size = caps->input_size; + + /* Store UINT16 values in Over-the-Air order, in packed structure + * for crypto inputs */ + l_put_be16(algorithms, &prov->conf_inputs.caps.algorithms); + l_put_be16(caps->output_action, &prov->conf_inputs.caps.output_action); + l_put_be16(caps->input_action, &prov->conf_inputs.caps.input_action); + + /* Compose Unprovisioned Beacon */ + memcpy(beacon + 2, uuid, 16); + l_put_be16(caps->oob_info, beacon + 18); + if (caps->oob_info & OOB_INFO_URI_HASH){ + l_put_be32(caps->uri_hash, beacon + 20); + len += sizeof(uint32_t); + } + + /* Infinitely Beacon until Canceled, or Provisioning Starts */ + result = mesh_send_pkt(0, 500, beacon, len); + + if (!result) + goto error_fail; + + /* Always register for PB-ADV */ + result = pb_adv_reg(acp_prov_open, acp_prov_close, acp_prov_rx, + acp_prov_ack, uuid, prov); + + if (result) + return true; + +error_fail: + acceptor_free(); + return false; +} + +void acceptor_cancel(void *user_data) +{ + acceptor_free(); +} -- 2.14.5