tools/mesh-cfgclient: Export configuration database

This adds main menu command "export-db".
When the command is invoked, JSON configuration object is
cloned and trimmed of extraneous properties.
Information about netkeys, appkeys and device keys are obtained
from bluetooth-meshd by calling ExportKeys() method.
The obtained key values are recorded in the export JSON object.
This commit is contained in:
Inga Stotland 2021-09-22 20:26:03 -07:00 committed by Brian Gix
parent 7514aa8d9c
commit 91c9329a05
3 changed files with 393 additions and 0 deletions

View File

@ -17,6 +17,7 @@
#include <ctype.h>
#include <dbus/dbus.h>
#include <errno.h>
#include <libgen.h>
#include <stdio.h>
#include <time.h>
@ -51,6 +52,7 @@
#define MAX_CRPL_SIZE 0x7fff
#define DEFAULT_CFG_FILE "config_db.json"
#define DEFAULT_EXPORT_FILE "export_db.json"
struct meshcfg_el {
const char *path;
@ -835,6 +837,197 @@ static void cmd_scan_unprov(int argc, char *argv[])
}
static uint8_t *parse_key(struct l_dbus_message_iter *iter, uint16_t id,
const char *name)
{
uint8_t *val;
uint32_t len;
if (!l_dbus_message_iter_get_fixed_array(iter, &val, &len)
|| len != 16) {
bt_shell_printf("Failed to parse %s %4.4x\n", name, id);
return NULL;
}
return val;
}
static bool parse_app_keys(struct l_dbus_message_iter *iter, uint16_t net_idx,
void *user_data)
{
struct l_dbus_message_iter app_keys, app_key, opts;
uint16_t app_idx;
if (!l_dbus_message_iter_get_variant(iter, "a(qaya{sv})", &app_keys))
return false;
while (l_dbus_message_iter_next_entry(&app_keys, &app_idx, &app_key,
&opts)) {
struct l_dbus_message_iter var;
uint8_t *val, *old_val = NULL;
const char *key;
val = parse_key(&app_key, app_idx, "AppKey");
if (!val)
return false;
while (l_dbus_message_iter_next_entry(&opts, &key, &var)) {
if (!strcmp(key, "OldKey")) {
if (!l_dbus_message_iter_get_variant(&var, "ay",
&app_key))
return false;
old_val = parse_key(&app_key, app_idx,
"old NetKey");
if (!old_val)
return false;
}
}
mesh_db_set_app_key(user_data, net_idx, app_idx, val, old_val);
}
return true;
}
static bool parse_net_keys(struct l_dbus_message_iter *iter, void *user_data)
{
struct l_dbus_message_iter net_keys, net_key, opts;
uint16_t idx;
if (!l_dbus_message_iter_get_variant(iter, "a(qaya{sv})", &net_keys))
return false;
while (l_dbus_message_iter_next_entry(&net_keys, &idx, &net_key,
&opts)) {
struct l_dbus_message_iter var;
uint8_t *val, *old_val = NULL;
uint8_t phase = KEY_REFRESH_PHASE_NONE;
const char *key;
val = parse_key(&net_key, idx, "NetKey");
if (!val)
return false;
while (l_dbus_message_iter_next_entry(&opts, &key, &var)) {
if (!strcmp(key, "AppKeys")) {
if (!parse_app_keys(&var, idx, user_data))
return false;
} else if (!strcmp(key, "Phase")) {
if (!l_dbus_message_iter_get_variant(&var, "y",
&phase))
return false;
} else if (!strcmp(key, "OldKey")) {
if (!l_dbus_message_iter_get_variant(&var, "ay",
&net_key))
return false;
old_val = parse_key(&net_key, idx,
"old NetKey");
if (!old_val)
return false;
}
}
mesh_db_set_net_key(user_data, idx, val, old_val, phase);
}
return true;
}
static bool parse_dev_keys(struct l_dbus_message_iter *iter, void *user_data)
{
struct l_dbus_message_iter keys, dev_key;
uint16_t unicast;
if (!l_dbus_message_iter_get_variant(iter, "a(qay)", &keys))
return false;
while (l_dbus_message_iter_next_entry(&keys, &unicast, &dev_key)) {
uint8_t *data;
data = parse_key(&dev_key, unicast, "Device Key");
if (!data)
return false;
mesh_db_set_device_key(user_data, unicast, data);
}
return true;
}
static void export_keys_reply(struct l_dbus_proxy *proxy,
struct l_dbus_message *msg, void *user_data)
{
struct l_dbus_message_iter iter, var;
char *cfg_dir = NULL, *fname = NULL;
const char *key;
bool is_error = true;
if (l_dbus_message_is_error(msg)) {
const char *name;
l_dbus_message_get_error(msg, &name, NULL);
bt_shell_printf("Failed to export keys: %s", name);
goto done;
}
if (!l_dbus_message_get_arguments(msg, "a{sv}", &iter)) {
bt_shell_printf("Malformed ExportKeys reply");
goto done;
}
while (l_dbus_message_iter_next_entry(&iter, &key, &var)) {
if (!strcmp(key, "NetKeys")) {
if (!parse_net_keys(&var, user_data))
goto done;
} else if (!strcmp(key, "DevKeys")) {
if (!parse_dev_keys(&var, user_data))
goto done;
}
}
is_error = false;
cfg_dir = l_strdup(cfg_fname);
cfg_dir = dirname(cfg_dir);
fname = l_strdup_printf("%s/%s", cfg_dir, DEFAULT_EXPORT_FILE);
done:
if (mesh_db_finish_export(is_error, user_data, fname)) {
if (!is_error)
bt_shell_printf("Config DB is exported to %s\n", fname);
}
l_free(cfg_dir);
l_free(fname);
}
static void cmd_export_db(int argc, char *argv[])
{
void *cfg_export;
if (!local || !local->proxy || !local->mgmt_proxy) {
bt_shell_printf("Node is not attached\n");
return;
}
/* Generate a properly formatted DB from the local config */
cfg_export = mesh_db_prepare_export();
if (!cfg_export) {
bt_shell_printf("Failed to prepare config db\n");
return;
}
/* Export the keys from the daemon */
l_dbus_proxy_method_call(local->mgmt_proxy, "ExportKeys", NULL,
export_keys_reply, cfg_export, NULL);
}
static void cmd_list_unprov(int argc, char *argv[])
{
bt_shell_printf(COLOR_YELLOW "Unprovisioned devices:\n" COLOR_OFF);
@ -1395,6 +1588,8 @@ static const struct bt_shell_menu main_menu = {
"List remote mesh nodes"},
{ "keys", NULL, cmd_keys,
"List available keys"},
{ "export-db", NULL, cmd_export_db,
"Export mesh configuration database"},
{ } },
};

View File

@ -48,6 +48,13 @@ static struct mesh_db *cfg;
static const char *bak_ext = ".bak";
static const char *tmp_ext = ".tmp";
static const char *js_schema = "http://json-schema.org/draft-04/schema#";
static const char *schema_id = "http://www.bluetooth.com/specifications/"
"assigned-numbers/mesh-profile/"
"cdb-schema.json#";
const char *schema_version = "1.0.0";
static bool add_string(json_object *jobj, const char *desc, const char *str)
{
json_object *jstring = json_object_new_string(str);
@ -2412,3 +2419,187 @@ fail:
return false;
}
bool mesh_db_set_device_key(void *expt_cfg, uint16_t unicast, uint8_t key[16])
{
json_object *jnode;
if (!expt_cfg)
return false;
jnode = get_node_by_unicast(expt_cfg, unicast);
if (!jnode)
return false;
return add_u8_16(jnode, "deviceKey", key);
}
bool mesh_db_set_net_key(void *expt_cfg, uint16_t idx, uint8_t key[16],
uint8_t *old_key, uint8_t phase)
{
json_object *jarray, *jkey;
if (!expt_cfg)
return false;
json_object_object_get_ex(expt_cfg, "netKeys", &jarray);
if (!jarray || json_object_get_type(jarray) != json_type_array)
return false;
jkey = get_key_object(jarray, idx);
if (!jkey)
return false;
if (!write_int(jkey, "phase", phase))
return false;
if (!add_u8_16(jkey, "key", key))
return false;
if (old_key && !(!add_u8_16(jkey, "oldKey", old_key)))
return false;
return true;
}
bool mesh_db_set_app_key(void *expt_cfg, uint16_t net_idx, uint16_t app_idx,
uint8_t key[16], uint8_t *old_key)
{
json_object *jarray, *jkey;
if (!expt_cfg)
return false;
json_object_object_get_ex(expt_cfg, "appKeys", &jarray);
if (!jarray || json_object_get_type(jarray) != json_type_array)
return false;
jkey = get_key_object(jarray, app_idx);
if (!jkey)
return false;
if (!add_u8_16(jkey, "key", key))
return false;
if (old_key && !(!add_u8_16(jkey, "oldKey", old_key)))
return false;
return true;
}
void *mesh_db_prepare_export(void)
{
json_object *export = NULL, *jarray;
if (!cfg || !cfg->jcfg)
return false;
if (json_object_deep_copy(cfg->jcfg, &export, NULL) != 0)
return NULL;
/* Delete token */
json_object_object_del(export, "token");
/* Delete IV index */
json_object_object_del(export, "ivIndex");
/* Scenes are not supported. Just add an empty array */
jarray = json_object_new_array();
json_object_object_add(export, "scenes", jarray);
write_bool(export, "partial", false);
return export;
}
bool mesh_db_finish_export(bool is_error, void *expt_cfg, const char *fname)
{
FILE *outfile = NULL;
const char *str, *hdr;
json_object *jhdr = NULL;
bool result = false;
char *pos;
uint32_t sz;
if (!expt_cfg)
return false;
if (is_error) {
json_object_put(expt_cfg);
return true;
}
if (!fname)
goto done;
outfile = fopen(fname, "w");
if (!outfile) {
l_error("Failed to save configuration to %s", fname);
goto done;
}
jhdr = json_object_new_object();
if (!add_string(jhdr, "$schema", js_schema))
goto done;
if (!add_string(jhdr, "id", schema_id))
goto done;
if (!add_string(jhdr, "version", schema_version))
goto done;
hdr = json_object_to_json_string_ext(jhdr, JSON_C_TO_STRING_PRETTY |
JSON_C_TO_STRING_NOSLASHESCAPE);
str = json_object_to_json_string_ext(expt_cfg, JSON_C_TO_STRING_PRETTY |
JSON_C_TO_STRING_NOSLASHESCAPE);
if (!hdr || !str)
goto done;
/*
* Write two strings to the output while stripping closing "}" from the
* header string and opening "{" from the config object.
*/
pos = strrchr(hdr, '}');
if (!pos)
goto done;
*pos = '\0';
pos = strrchr(hdr, '"');
if (!pos)
goto done;
pos[1] = ',';
if (fwrite(hdr, sizeof(char), strlen(hdr), outfile) < strlen(hdr))
goto done;
pos = strchr(str, '{');
if (!pos || pos[1] == '\0')
goto done;
pos++;
sz = strlen(pos);
if (fwrite(pos, sizeof(char), sz, outfile) < sz)
goto done;
result = true;
done:
if (outfile)
fclose(outfile);
json_object_put(expt_cfg);
if (jhdr)
json_object_put(jhdr);
return result;
}

View File

@ -82,3 +82,10 @@ struct l_queue *mesh_db_load_groups(void);
bool mesh_db_add_group(struct mesh_group *grp);
bool mesh_db_add_rejected_addr(uint16_t unicast, uint32_t iv_index);
bool mesh_db_clear_rejected(uint32_t iv_index);
bool mesh_db_set_device_key(void *expt_cfg, uint16_t unicast, uint8_t key[16]);
bool mesh_db_set_net_key(void *expt_cfg, uint16_t idx, uint8_t key[16],
uint8_t *old_key, uint8_t phase);
bool mesh_db_set_app_key(void *expt_cfg, uint16_t net_idx, uint16_t app_idx,
uint8_t key[16], uint8_t *old_key);
void *mesh_db_prepare_export(void);
bool mesh_db_finish_export(bool is_error, void *expt_cfg, const char *fname);