blob: 64677a3386e89cef8a20a7522774c4640be81d38 [file] [log] [blame]
/*
* Copyright (c) 2017 Intel Corporation
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdlib.h>
#include <zephyr/bluetooth/mesh.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/sys/iterable_sections.h>
#include "mesh.h"
#include "net.h"
#include "app_keys.h"
#include "rpl.h"
#include "settings.h"
#include "crypto.h"
#include "adv.h"
#include "proxy.h"
#include "friend.h"
#include "foundation.h"
#include "access.h"
#include "common/bt_str.h"
#define LOG_LEVEL CONFIG_BT_MESH_KEYS_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mesh_app_keys);
/* Tracking of what storage changes are pending for App Keys. We track this in
* a separate array here instead of within the respective bt_mesh_app_key
* struct itself, since once a key gets deleted its struct becomes invalid
* and may be reused for other keys.
*/
struct app_key_update {
uint16_t key_idx:12, /* AppKey Index */
valid:1, /* 1 if this entry is valid, 0 if not */
clear:1; /* 1 if key needs clearing, 0 if storing */
};
/* AppKey information for persistent storage. */
struct app_key_val {
uint16_t net_idx;
bool updated;
struct bt_mesh_key val[2];
} __packed;
/** Mesh Application Key. */
struct app_key {
uint16_t net_idx;
uint16_t app_idx;
bool updated;
struct bt_mesh_app_cred {
uint8_t id;
struct bt_mesh_key val;
} keys[2];
};
static struct app_key_update app_key_updates[CONFIG_BT_MESH_APP_KEY_COUNT];
static struct app_key apps[CONFIG_BT_MESH_APP_KEY_COUNT] = {
[0 ... (CONFIG_BT_MESH_APP_KEY_COUNT - 1)] = {
.app_idx = BT_MESH_KEY_UNUSED,
.net_idx = BT_MESH_KEY_UNUSED,
}
};
static struct app_key *app_get(uint16_t app_idx)
{
for (int i = 0; i < ARRAY_SIZE(apps); i++) {
if (apps[i].app_idx == app_idx) {
return &apps[i];
}
}
return NULL;
}
static void clear_app_key(uint16_t app_idx)
{
char path[20];
int err;
snprintk(path, sizeof(path), "bt/mesh/AppKey/%x", app_idx);
err = settings_delete(path);
if (err) {
LOG_ERR("Failed to clear AppKeyIndex 0x%03x", app_idx);
} else {
LOG_DBG("Cleared AppKeyIndex 0x%03x", app_idx);
}
}
static void store_app_key(uint16_t app_idx)
{
const struct app_key *app;
struct app_key_val key;
char path[20];
int err;
snprintk(path, sizeof(path), "bt/mesh/AppKey/%x", app_idx);
app = app_get(app_idx);
if (!app) {
LOG_WRN("ApKeyIndex 0x%03x not found", app_idx);
return;
}
key.net_idx = app->net_idx,
key.updated = app->updated,
memcpy(&key.val[0], &app->keys[0].val, sizeof(struct bt_mesh_key));
memcpy(&key.val[1], &app->keys[1].val, sizeof(struct bt_mesh_key));
err = settings_save_one(path, &key, sizeof(key));
if (err) {
LOG_ERR("Failed to store AppKey %s value", path);
} else {
LOG_DBG("Stored AppKey %s value", path);
}
}
static struct app_key_update *app_key_update_find(uint16_t key_idx,
struct app_key_update **free_slot)
{
struct app_key_update *match;
int i;
match = NULL;
*free_slot = NULL;
for (i = 0; i < ARRAY_SIZE(app_key_updates); i++) {
struct app_key_update *update = &app_key_updates[i];
if (!update->valid) {
*free_slot = update;
continue;
}
if (update->key_idx == key_idx) {
match = update;
}
}
return match;
}
static void update_app_key_settings(uint16_t app_idx, bool store)
{
struct app_key_update *update, *free_slot;
uint8_t clear = store ? 0U : 1U;
LOG_DBG("AppKeyIndex 0x%03x", app_idx);
update = app_key_update_find(app_idx, &free_slot);
if (update) {
update->clear = clear;
bt_mesh_settings_store_schedule(
BT_MESH_SETTINGS_APP_KEYS_PENDING);
return;
}
if (!free_slot) {
if (store) {
store_app_key(app_idx);
} else {
clear_app_key(app_idx);
}
return;
}
free_slot->valid = 1U;
free_slot->key_idx = app_idx;
free_slot->clear = clear;
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_APP_KEYS_PENDING);
}
static void app_key_evt(struct app_key *app, enum bt_mesh_key_evt evt)
{
STRUCT_SECTION_FOREACH(bt_mesh_app_key_cb, cb) {
cb->evt_handler(app->app_idx, app->net_idx, evt);
}
}
static struct app_key *app_key_alloc(uint16_t app_idx)
{
struct app_key *app = NULL;
for (int i = 0; i < ARRAY_SIZE(apps); i++) {
/* Check for already existing app_key */
if (apps[i].app_idx == app_idx) {
return &apps[i];
}
if (!app && apps[i].app_idx == BT_MESH_KEY_UNUSED) {
app = &apps[i];
}
}
return app;
}
static void app_key_del(struct app_key *app)
{
LOG_DBG("AppIdx 0x%03x", app->app_idx);
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
update_app_key_settings(app->app_idx, false);
}
app_key_evt(app, BT_MESH_KEY_DELETED);
app->net_idx = BT_MESH_KEY_UNUSED;
app->app_idx = BT_MESH_KEY_UNUSED;
bt_mesh_key_destroy(&app->keys[0].val);
bt_mesh_key_destroy(&app->keys[1].val);
memset(app->keys, 0, sizeof(app->keys));
}
static void app_key_revoke(struct app_key *app)
{
if (!app->updated) {
return;
}
bt_mesh_key_destroy(&app->keys[0].val);
memcpy(&app->keys[0], &app->keys[1], sizeof(app->keys[0]));
memset(&app->keys[1], 0, sizeof(app->keys[1]));
app->updated = false;
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
update_app_key_settings(app->app_idx, true);
}
app_key_evt(app, BT_MESH_KEY_REVOKED);
}
uint8_t bt_mesh_app_key_add(uint16_t app_idx, uint16_t net_idx,
const uint8_t key[16])
{
struct app_key *app;
LOG_DBG("net_idx 0x%04x app_idx %04x val %s", net_idx, app_idx, bt_hex(key, 16));
if (!bt_mesh_subnet_get(net_idx)) {
return STATUS_INVALID_NETKEY;
}
app = app_key_alloc(app_idx);
if (!app) {
return STATUS_INSUFF_RESOURCES;
}
if (app->app_idx == app_idx) {
if (app->net_idx != net_idx) {
return STATUS_INVALID_NETKEY;
}
if (bt_mesh_key_compare(key, &app->keys[0].val)) {
return STATUS_IDX_ALREADY_STORED;
}
return STATUS_SUCCESS;
}
if (bt_mesh_app_id(key, &app->keys[0].id)) {
return STATUS_CANNOT_SET;
}
LOG_DBG("AppIdx 0x%04x AID 0x%02x", app_idx, app->keys[0].id);
app->net_idx = net_idx;
app->app_idx = app_idx;
app->updated = false;
if (bt_mesh_key_import(BT_MESH_KEY_TYPE_APP, key, &app->keys[0].val)) {
LOG_ERR("Unable to import application key");
return STATUS_CANNOT_SET;
}
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
LOG_DBG("Storing AppKey persistently");
update_app_key_settings(app->app_idx, true);
}
app_key_evt(app, BT_MESH_KEY_ADDED);
return STATUS_SUCCESS;
}
uint8_t bt_mesh_app_key_update(uint16_t app_idx, uint16_t net_idx,
const uint8_t key[16])
{
struct app_key *app;
struct bt_mesh_subnet *sub;
LOG_DBG("net_idx 0x%04x app_idx %04x val %s", net_idx, app_idx, bt_hex(key, 16));
app = app_get(app_idx);
if (!app) {
return STATUS_INVALID_APPKEY;
}
if (net_idx != BT_MESH_KEY_UNUSED && app->net_idx != net_idx) {
return STATUS_INVALID_BINDING;
}
sub = bt_mesh_subnet_get(app->net_idx);
if (!sub) {
return STATUS_INVALID_NETKEY;
}
/* The AppKey Update message shall generate an error when node
* is in normal operation, Phase 2, or Phase 3 or in Phase 1
* when the AppKey Update message on a valid AppKeyIndex when
* the AppKey value is different.
*/
if (sub->kr_phase != BT_MESH_KR_PHASE_1) {
return STATUS_CANNOT_UPDATE;
}
if (app->updated) {
if (bt_mesh_key_compare(key, &app->keys[1].val)) {
return STATUS_IDX_ALREADY_STORED;
}
return STATUS_SUCCESS;
}
if (bt_mesh_app_id(key, &app->keys[1].id)) {
return STATUS_CANNOT_UPDATE;
}
LOG_DBG("app_idx 0x%04x AID 0x%02x", app_idx, app->keys[1].id);
app->updated = true;
if (bt_mesh_key_import(BT_MESH_KEY_TYPE_APP, key, &app->keys[1].val)) {
LOG_ERR("Unable to import application key");
return STATUS_CANNOT_UPDATE;
}
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
LOG_DBG("Storing AppKey persistently");
update_app_key_settings(app->app_idx, true);
}
app_key_evt(app, BT_MESH_KEY_UPDATED);
return STATUS_SUCCESS;
}
uint8_t bt_mesh_app_key_del(uint16_t app_idx, uint16_t net_idx)
{
struct app_key *app;
LOG_DBG("AppIdx 0x%03x", app_idx);
if (net_idx != BT_MESH_KEY_UNUSED && !bt_mesh_subnet_get(net_idx)) {
return STATUS_INVALID_NETKEY;
}
app = app_get(app_idx);
if (!app) {
/* This could be a retry of a previous attempt that had its
* response lost, so pretend that it was a success.
*/
return STATUS_SUCCESS;
}
if (net_idx != BT_MESH_KEY_UNUSED && net_idx != app->net_idx) {
return STATUS_INVALID_BINDING;
}
app_key_del(app);
return STATUS_SUCCESS;
}
static int app_id_set(struct app_key *app, int key_idx, const struct bt_mesh_key *key)
{
uint8_t raw_key[16];
int err;
err = bt_mesh_key_export(raw_key, key);
if (err) {
return err;
}
err = bt_mesh_app_id(raw_key, &app->keys[key_idx].id);
if (err) {
return err;
}
bt_mesh_key_assign(&app->keys[key_idx].val, key);
return 0;
}
int bt_mesh_app_key_set(uint16_t app_idx, uint16_t net_idx,
const struct bt_mesh_key *old_key, const struct bt_mesh_key *new_key)
{
struct app_key *app;
app = app_key_alloc(app_idx);
if (!app) {
return -ENOMEM;
}
if (app->app_idx == app_idx) {
return 0;
}
LOG_DBG("AppIdx 0x%04x AID 0x%02x", app_idx, app->keys[0].id);
if (app_id_set(app, 0, old_key)) {
return -EIO;
}
if (new_key != NULL && app_id_set(app, 1, new_key)) {
return -EIO;
}
app->net_idx = net_idx;
app->app_idx = app_idx;
app->updated = !!new_key;
return 0;
}
bool bt_mesh_app_key_exists(uint16_t app_idx)
{
for (int i = 0; i < ARRAY_SIZE(apps); i++) {
if (apps[i].app_idx == app_idx) {
return true;
}
}
return false;
}
ssize_t bt_mesh_app_keys_get(uint16_t net_idx, uint16_t app_idxs[], size_t max,
off_t skip)
{
size_t count = 0;
for (int i = 0; i < ARRAY_SIZE(apps); i++) {
struct app_key *app = &apps[i];
if (app->app_idx == BT_MESH_KEY_UNUSED) {
continue;
}
if (net_idx != BT_MESH_KEY_ANY && app->net_idx != net_idx) {
continue;
}
if (skip) {
skip--;
continue;
}
if (count >= max) {
return -ENOMEM;
}
app_idxs[count++] = app->app_idx;
}
return count;
}
int bt_mesh_keys_resolve(struct bt_mesh_msg_ctx *ctx,
struct bt_mesh_subnet **sub,
const struct bt_mesh_key **app_key, uint8_t *aid)
{
struct app_key *app = NULL;
if (BT_MESH_IS_DEV_KEY(ctx->app_idx)) {
/* With device keys, the application has to decide which subnet
* to send on.
*/
*sub = bt_mesh_subnet_get(ctx->net_idx);
if (!*sub) {
LOG_WRN("Unknown NetKey 0x%03x", ctx->net_idx);
return -EINVAL;
}
if (ctx->app_idx == BT_MESH_KEY_DEV_REMOTE &&
!bt_mesh_has_addr(ctx->addr)) {
struct bt_mesh_cdb_node *node;
if (!IS_ENABLED(CONFIG_BT_MESH_CDB)) {
LOG_WRN("No DevKey for 0x%04x", ctx->addr);
return -EINVAL;
}
node = bt_mesh_cdb_node_get(ctx->addr);
if (!node) {
LOG_WRN("No DevKey for 0x%04x", ctx->addr);
return -EINVAL;
}
*app_key = &node->dev_key;
} else {
*app_key = &bt_mesh.dev_key;
}
*aid = 0;
return 0;
}
app = app_get(ctx->app_idx);
if (!app) {
LOG_WRN("Unknown AppKey 0x%03x", ctx->app_idx);
return -EINVAL;
}
*sub = bt_mesh_subnet_get(app->net_idx);
if (!*sub) {
LOG_WRN("Unknown NetKey 0x%03x", app->net_idx);
return -EINVAL;
}
if ((*sub)->kr_phase == BT_MESH_KR_PHASE_2 && app->updated) {
*aid = app->keys[1].id;
*app_key = &app->keys[1].val;
} else {
*aid = app->keys[0].id;
*app_key = &app->keys[0].val;
}
return 0;
}
uint16_t bt_mesh_app_key_find(bool dev_key, uint8_t aid,
struct bt_mesh_net_rx *rx,
int (*cb)(struct bt_mesh_net_rx *rx,
const struct bt_mesh_key *key, void *cb_data),
void *cb_data)
{
int err, i;
if (dev_key) {
/* Attempt remote dev key first, as that is only available for
* provisioner devices, which normally don't interact with nodes
* that know their local dev key.
*/
if (IS_ENABLED(CONFIG_BT_MESH_CDB) &&
rx->net_if != BT_MESH_NET_IF_LOCAL) {
struct bt_mesh_cdb_node *node;
node = bt_mesh_cdb_node_get(rx->ctx.addr);
if (node && !cb(rx, &node->dev_key, cb_data)) {
return BT_MESH_KEY_DEV_REMOTE;
}
}
/** Bluetooth Mesh Specification v1.0.1, section 3.4.3:
* The Device key is only valid for unicast addresses.
*/
if (BT_MESH_ADDR_IS_UNICAST(rx->ctx.recv_dst)) {
err = cb(rx, &bt_mesh.dev_key, cb_data);
if (!err) {
return BT_MESH_KEY_DEV_LOCAL;
}
#if defined(CONFIG_BT_MESH_RPR_SRV)
if (atomic_test_bit(bt_mesh.flags, BT_MESH_DEVKEY_CAND)) {
err = cb(rx, &bt_mesh.dev_key_cand, cb_data);
if (!err) {
/* Bluetooth Mesh Specification v1.1.0, section 3.6.4.2:
* If a message is successfully decrypted using the device
* key candidate, the device key candidate should
* permanently replace the original devkey.
*/
bt_mesh_dev_key_cand_activate();
return BT_MESH_KEY_DEV_LOCAL;
}
}
#endif
}
return BT_MESH_KEY_UNUSED;
}
for (i = 0; i < ARRAY_SIZE(apps); i++) {
const struct app_key *app = &apps[i];
const struct bt_mesh_app_cred *cred;
if (app->app_idx == BT_MESH_KEY_UNUSED) {
continue;
}
if (app->net_idx != rx->sub->net_idx) {
continue;
}
if (rx->new_key && app->updated) {
cred = &app->keys[1];
} else {
cred = &app->keys[0];
}
if (cred->id != aid) {
continue;
}
err = cb(rx, &cred->val, cb_data);
if (err) {
continue;
}
return app->app_idx;
}
return BT_MESH_KEY_UNUSED;
}
static void subnet_evt(struct bt_mesh_subnet *sub, enum bt_mesh_key_evt evt)
{
if (evt == BT_MESH_KEY_UPDATED || evt == BT_MESH_KEY_ADDED) {
return;
}
for (int i = 0; i < ARRAY_SIZE(apps); i++) {
struct app_key *app = &apps[i];
if (app->app_idx == BT_MESH_KEY_UNUSED) {
continue;
}
if (app->net_idx != sub->net_idx) {
continue;
}
if (evt == BT_MESH_KEY_DELETED) {
app_key_del(app);
} else if (evt == BT_MESH_KEY_REVOKED) {
app_key_revoke(app);
} else if (evt == BT_MESH_KEY_SWAPPED && app->updated) {
app_key_evt(app, BT_MESH_KEY_SWAPPED);
}
}
}
BT_MESH_SUBNET_CB_DEFINE(app_keys) = {
.evt_handler = subnet_evt,
};
void bt_mesh_app_keys_reset(void)
{
for (int i = 0; i < ARRAY_SIZE(apps); i++) {
struct app_key *app = &apps[i];
if (app->app_idx != BT_MESH_KEY_UNUSED) {
app_key_del(app);
}
}
}
static int app_key_set(const char *name, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
struct app_key_val key;
struct bt_mesh_key val[2];
uint16_t app_idx;
int err;
if (!name) {
LOG_ERR("Insufficient number of arguments");
return -ENOENT;
}
app_idx = strtol(name, NULL, 16);
if (!len_rd) {
return 0;
}
err = bt_mesh_settings_set(read_cb, cb_arg, &key, sizeof(key));
if (err < 0) {
return -EINVAL;
}
/* One extra copying since key.val array is from packed structure
* and might be unaligned.
*/
memcpy(val, key.val, sizeof(key.val));
err = bt_mesh_app_key_set(app_idx, key.net_idx, &val[0],
key.updated ? &val[1] : NULL);
if (err) {
LOG_ERR("Failed to set \'app-key\'");
return err;
}
LOG_DBG("AppKeyIndex 0x%03x recovered from storage", app_idx);
return 0;
}
BT_MESH_SETTINGS_DEFINE(app, "AppKey", app_key_set);
void bt_mesh_app_key_pending_store(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(app_key_updates); i++) {
struct app_key_update *update = &app_key_updates[i];
if (!update->valid) {
continue;
}
update->valid = 0U;
if (update->clear) {
clear_app_key(update->key_idx);
} else {
store_app_key(update->key_idx);
}
}
}