blob: d1b855552ea7206534f19c3c856d2e334ce13029 [file] [log] [blame]
/*
*
* 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;
}