| // 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 <config.h> |
| #endif |
| |
| #define _GNU_SOURCE |
| #include <fcntl.h> |
| #include <dirent.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| |
| #include <sys/stat.h> |
| |
| #include <ell/ell.h> |
| |
| #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); |
| } |