// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2019 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "mesh/mesh-defs.h" #include "mesh/dbus.h" #include "mesh/node.h" #include "mesh/keyring.h" static const char *dev_key_dir = "/dev_keys"; static const char *app_key_dir = "/app_keys"; static const char *net_key_dir = "/net_keys"; static int open_key_file(struct mesh_node *node, const char *key_dir, uint16_t idx, int flags) { const char *node_path; char fname[PATH_MAX]; int ret; if (!node) return -1; node_path = node_get_storage_dir(node); if (flags & O_CREAT) { ret = snprintf(fname, PATH_MAX, "%s%s", node_path, key_dir); if (ret < 0) return -1; if (mkdir(fname, 0755) != 0 && errno != EEXIST) l_error("Failed to create dir(%d): %s", errno, fname); } ret = snprintf(fname, PATH_MAX, "%s%s/%3.3x", node_path, key_dir, idx); if (ret < 0) return -1; if (flags & O_CREAT) return open(fname, flags, 0600); else return open(fname, flags); } bool keyring_put_net_key(struct mesh_node *node, uint16_t net_idx, struct keyring_net_key *key) { bool result = false; int fd; if (!key) return false; fd = open_key_file(node, net_key_dir, net_idx, O_WRONLY | O_CREAT | O_TRUNC); if (fd < 0) return false; if (write(fd, key, sizeof(*key)) == sizeof(*key)) result = true; close(fd); return result; } bool keyring_put_app_key(struct mesh_node *node, uint16_t app_idx, uint16_t net_idx, struct keyring_app_key *key) { bool result = false; int fd; if (!key) return false; fd = open_key_file(node, app_key_dir, app_idx, O_RDWR); if (fd >= 0) { struct keyring_app_key old_key; if (read(fd, &old_key, sizeof(old_key)) == sizeof(old_key)) { if (old_key.net_idx != net_idx) { close(fd); return false; } } lseek(fd, 0, SEEK_SET); } else fd = open_key_file(node, app_key_dir, app_idx, O_WRONLY | O_CREAT | O_TRUNC); if (fd < 0) return false; if (write(fd, key, sizeof(*key)) == sizeof(*key)) result = true; close(fd); return result; } static void finalize(int dir_fd, const char *fname, uint16_t net_idx) { struct keyring_app_key key; int fd; fd = openat(dir_fd, fname, O_RDWR); if (fd < 0) return; if (read(fd, &key, sizeof(key)) != sizeof(key) || key.net_idx != net_idx) goto done; l_debug("Finalize %s", fname); memcpy(key.old_key, key.new_key, 16); lseek(fd, 0, SEEK_SET); if (write(fd, &key, sizeof(key)) != sizeof(key)) goto done; done: close(fd); } bool keyring_finalize_app_keys(struct mesh_node *node, uint16_t net_idx) { const char *node_path; char key_dir[PATH_MAX]; DIR *dir; int ret, dir_fd; struct dirent *entry; if (!node) return false; node_path = node_get_storage_dir(node); ret = snprintf(key_dir, PATH_MAX, "%s%s", node_path, app_key_dir); if (ret < 0) return false; dir = opendir(key_dir); if (!dir) { if (errno == ENOENT) return true; l_error("Failed to open AppKey storage directory: %s", key_dir); return false; } dir_fd = dirfd(dir); while ((entry = readdir(dir)) != NULL) { /* AppKeys are stored in regular files */ if (entry->d_type == DT_REG) finalize(dir_fd, entry->d_name, net_idx); } closedir(dir); return true; } bool keyring_put_remote_dev_key(struct mesh_node *node, uint16_t unicast, uint8_t count, uint8_t dev_key[16]) { const char *node_path; char key_file[PATH_MAX]; bool result = true; int ret, fd, i; if (!IS_UNICAST_RANGE(unicast, count)) return false; if (!node) return false; node_path = node_get_storage_dir(node); ret = snprintf(key_file, PATH_MAX, "%s%s", node_path, dev_key_dir); if (ret < 0) return false; if (mkdir(key_file, 0755) != 0 && errno != EEXIST) l_error("Failed to create dir(%d): %s", errno, key_file); for (i = 0; i < count; i++) { ret = snprintf(key_file, PATH_MAX, "%s%s/%4.4x", node_path, dev_key_dir, unicast + i); if (ret < 0) return false; l_debug("Put Dev Key %s", key_file); fd = open(key_file, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd >= 0) { if (write(fd, dev_key, 16) != 16) result = false; close(fd); } else result = false; } return result; } static bool get_key(struct mesh_node *node, const char *key_dir, uint16_t key_idx, void *key, ssize_t sz) { bool result = false; int fd; if (!key) return false; fd = open_key_file(node, key_dir, key_idx, O_RDONLY); if (fd >= 0) { if (read(fd, key, sz) == sz) result = true; close(fd); } return result; } bool keyring_get_net_key(struct mesh_node *node, uint16_t net_idx, struct keyring_net_key *key) { return get_key(node, net_key_dir, net_idx, key, sizeof(*key)); } bool keyring_get_app_key(struct mesh_node *node, uint16_t app_idx, struct keyring_app_key *key) { return get_key(node, app_key_dir, app_idx, key, sizeof(*key)); } bool keyring_get_remote_dev_key(struct mesh_node *node, uint16_t unicast, uint8_t dev_key[16]) { const char *node_path; char key_file[PATH_MAX]; bool result = false; int ret, fd; if (!IS_UNICAST(unicast)) return false; if (!node) return false; node_path = node_get_storage_dir(node); ret = snprintf(key_file, PATH_MAX, "%s%s/%4.4x", node_path, dev_key_dir, unicast); if (ret < 0) return false; fd = open(key_file, O_RDONLY); if (fd >= 0) { if (read(fd, dev_key, 16) == 16) result = true; close(fd); } return result; } bool keyring_del_net_key(struct mesh_node *node, uint16_t net_idx) { const char *node_path; char key_file[PATH_MAX]; int ret; if (!node) return false; node_path = node_get_storage_dir(node); ret = snprintf(key_file, PATH_MAX, "%s%s/%3.3x", node_path, net_key_dir, net_idx); if (ret < 0) return false; l_debug("RM Net Key %s", key_file); remove(key_file); /* TODO: See if it is easiest to delete all bound App keys here */ /* TODO: see nftw() */ return true; } bool keyring_del_app_key(struct mesh_node *node, uint16_t app_idx) { const char *node_path; char key_file[PATH_MAX]; int ret; if (!node) return false; node_path = node_get_storage_dir(node); ret = snprintf(key_file, PATH_MAX, "%s%s/%3.3x", node_path, app_key_dir, app_idx); if (ret < 0) return false; l_debug("RM App Key %s", key_file); remove(key_file); return true; } bool keyring_del_remote_dev_key(struct mesh_node *node, uint16_t unicast, uint8_t count) { const char *node_path; char key_file[PATH_MAX]; int ret, i; if (!IS_UNICAST_RANGE(unicast, count)) return false; if (!node) return false; node_path = node_get_storage_dir(node); for (i = 0; i < count; i++) { ret = snprintf(key_file, PATH_MAX, "%s%s/%4.4x", node_path, dev_key_dir, unicast + i); if (ret < 0) return false; l_debug("RM Dev Key %s", key_file); remove(key_file); } return true; } bool keyring_del_remote_dev_key_all(struct mesh_node *node, uint16_t unicast) { uint8_t dev_key[16]; uint8_t test_key[16]; uint8_t cnt = 1; if (!keyring_get_remote_dev_key(node, unicast, dev_key)) return false; while (keyring_get_remote_dev_key(node, unicast + cnt, test_key)) { if (memcmp(dev_key, test_key, sizeof(dev_key))) break; cnt++; } if (cnt > 1) return keyring_del_remote_dev_key(node, unicast + 1, cnt - 1); return true; } static DIR *open_key_dir(const char *node_path, const char *key_dir_name) { char dir_path[PATH_MAX]; DIR *key_dir; int ret; ret = snprintf(dir_path, PATH_MAX, "%s%s", node_path, key_dir_name); if (ret < 0) return NULL; key_dir = opendir(dir_path); if (!key_dir) l_error("Failed to open keyring storage directory: %s", dir_path); return key_dir; } static int open_key_dir_entry(int dir_fd, struct dirent *entry, uint8_t fname_len) { if (entry->d_type != DT_REG) return -1; /* Check the file name length */ if (strlen(entry->d_name) != fname_len) return -1; return openat(dir_fd, entry->d_name, O_RDONLY); } static void append_old_key(struct l_dbus_message_builder *builder, const uint8_t key[16]) { l_dbus_message_builder_enter_dict(builder, "sv"); l_dbus_message_builder_append_basic(builder, 's', "OldKey"); l_dbus_message_builder_enter_variant(builder, "ay"); dbus_append_byte_array(builder, key, 16); l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_leave_dict(builder); } static void build_app_keys_reply(const char *node_path, struct l_dbus_message_builder *builder, uint16_t net_idx, uint8_t phase) { DIR *key_dir; int key_dir_fd; struct dirent *entry; key_dir = open_key_dir(node_path, app_key_dir); if (!key_dir) return; key_dir_fd = dirfd(key_dir); l_dbus_message_builder_enter_dict(builder, "sv"); l_dbus_message_builder_append_basic(builder, 's', "AppKeys"); l_dbus_message_builder_enter_variant(builder, "a(qaya{sv})"); l_dbus_message_builder_enter_array(builder, "(qaya{sv})"); while ((entry = readdir(key_dir)) != NULL) { struct keyring_app_key key; int fd = open_key_dir_entry(key_dir_fd, entry, 3); if (fd < 0) continue; if (read(fd, &key, sizeof(key)) != sizeof(key) || key.net_idx != net_idx) { close(fd); continue; } close(fd); l_dbus_message_builder_enter_struct(builder, "qaya{sv}"); l_dbus_message_builder_append_basic(builder, 'q', &key.app_idx); dbus_append_byte_array(builder, key.new_key, 16); l_dbus_message_builder_enter_array(builder, "{sv}"); if (phase != KEY_REFRESH_PHASE_NONE) append_old_key(builder, key.old_key); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_leave_struct(builder); } l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_leave_dict(builder); closedir(key_dir); } static bool build_net_keys_reply(const char *node_path, struct l_dbus_message_builder *builder) { DIR *key_dir; int key_dir_fd; struct dirent *entry; bool result = false; key_dir = open_key_dir(node_path, net_key_dir); if (!key_dir) return false; key_dir_fd = dirfd(key_dir); l_dbus_message_builder_enter_dict(builder, "sv"); l_dbus_message_builder_append_basic(builder, 's', "NetKeys"); l_dbus_message_builder_enter_variant(builder, "a(qaya{sv})"); l_dbus_message_builder_enter_array(builder, "(qaya{sv})"); while ((entry = readdir(key_dir)) != NULL) { struct keyring_net_key key; int fd = open_key_dir_entry(key_dir_fd, entry, 3); if (fd < 0) continue; if (read(fd, &key, sizeof(key)) != sizeof(key)) { close(fd); goto done; } close(fd); /* * If network key is stuck in phase 3, keyring * write failed and this key info is unreliable. */ if (key.phase == KEY_REFRESH_PHASE_THREE) continue; l_dbus_message_builder_enter_struct(builder, "qaya{sv}"); l_dbus_message_builder_append_basic(builder, 'q', &key.net_idx); dbus_append_byte_array(builder, key.new_key, 16); l_dbus_message_builder_enter_array(builder, "{sv}"); if (key.phase != KEY_REFRESH_PHASE_NONE) { dbus_append_dict_entry_basic(builder, "Phase", "y", &key.phase); append_old_key(builder, key.old_key); } build_app_keys_reply(node_path, builder, key.net_idx, key.phase); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_leave_struct(builder); } l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_leave_dict(builder); result = true; done: closedir(key_dir); return result; } struct dev_key_entry { uint16_t unicast; uint8_t value[16]; }; static bool match_key_value(const void *a, const void *b) { const struct dev_key_entry *key = a; const uint8_t *value = b; return (memcmp(key->value, value, 16) == 0); } static void build_dev_key_entry(void *a, void *b) { struct dev_key_entry *key = a; struct l_dbus_message_builder *builder = b; l_dbus_message_builder_enter_struct(builder, "qay"); l_dbus_message_builder_append_basic(builder, 'q', &key->unicast); dbus_append_byte_array(builder, key->value, 16); l_dbus_message_builder_leave_struct(builder); } static bool build_dev_keys_reply(const char *node_path, struct l_dbus_message_builder *builder) { DIR *key_dir; int key_dir_fd; struct dirent *entry; struct l_queue *keys; bool result = false; key_dir = open_key_dir(node_path, dev_key_dir); /* * There is always at least one device key present for a local node. * Therefore, return false, if the directory does not exist. */ if (!key_dir) return false; key_dir_fd = dirfd(key_dir); keys = l_queue_new(); while ((entry = readdir(key_dir)) != NULL) { uint8_t buf[16]; uint16_t unicast; struct dev_key_entry *key; int fd = open_key_dir_entry(key_dir_fd, entry, 4); if (fd < 0) continue; if (read(fd, buf, 16) != 16) { close(fd); goto done; } close(fd); if (sscanf(entry->d_name, "%04hx", &unicast) != 1) goto done; key = l_queue_find(keys, match_key_value, buf); if (key) { if (key->unicast > unicast) key->unicast = unicast; continue; } key = l_new(struct dev_key_entry, 1); key->unicast = unicast; memcpy(key->value, buf, 16); l_queue_push_tail(keys, key); } l_dbus_message_builder_enter_dict(builder, "sv"); l_dbus_message_builder_append_basic(builder, 's', "DevKeys"); l_dbus_message_builder_enter_variant(builder, "a(qay)"); l_dbus_message_builder_enter_array(builder, "(qay)"); l_queue_foreach(keys, build_dev_key_entry, builder); l_dbus_message_builder_leave_array(builder); l_dbus_message_builder_leave_variant(builder); l_dbus_message_builder_leave_dict(builder); result = true; done: l_queue_destroy(keys, l_free); closedir(key_dir); return result; } bool keyring_build_export_keys_reply(struct mesh_node *node, struct l_dbus_message_builder *builder) { const char *node_path; if (!node) return false; node_path = node_get_storage_dir(node); if (!build_net_keys_reply(node_path, builder)) return false; return build_dev_keys_reply(node_path, builder); }