diff --git a/src/iwd.network.rst b/src/iwd.network.rst index 892c6eb8..2b5c3f0e 100644 --- a/src/iwd.network.rst +++ b/src/iwd.network.rst @@ -174,6 +174,14 @@ The group ``[Settings]`` contains general settings. Properly configured Access Points will typically update this setting appropriately via Transition Disable indications. User customization of this value is thus typically not required. + * - ExternallyManaged + - Values: true, **false** + + If enabled, the profile contents are managed by an external + application. IWD will read the profile but will not update it as a + side effect of network operation, for example to persist connection + metadata or security material learned at runtime. Explicit profile + removal through the KnownNetwork Forget method is still allowed. * - UseDefaultEccGroup - Values: true, false diff --git a/src/knownnetworks.c b/src/knownnetworks.c index 886f8145..471638e0 100644 --- a/src/knownnetworks.c +++ b/src/knownnetworks.c @@ -82,6 +82,11 @@ void __network_config_parse(const struct l_settings *settings, config->always_random_addr = b; + if (!l_settings_get_bool(settings, NET_EXTERNALLY_MANAGED, &b)) + b = false; + + config->externally_managed = b; + value = l_settings_get_value(settings, NET_ADDRESS_OVERRIDE); if (value) { if (util_string_to_address(value, new_addr) && @@ -690,6 +695,9 @@ static struct l_dbus_message *known_network_property_set_autoconnect( if (network->config.is_autoconnectable == autoconnect) return l_dbus_message_new_method_return(message); + if (network->config.externally_managed) + return dbus_error_not_available(message); + settings = network->ops->open(network); if (!settings) return dbus_error_failed(message); diff --git a/src/knownnetworks.h b/src/knownnetworks.h index a117ded5..ddf72413 100644 --- a/src/knownnetworks.h +++ b/src/knownnetworks.h @@ -30,6 +30,7 @@ #define NET_TRANSITION_DISABLE SETTINGS, "TransitionDisable" #define NET_TRANSITION_DISABLE_MODES SETTINGS, "DisabledTransitionModes" #define NET_USE_DEFAULT_ECC_GROUP SETTINGS, "UseDefaultEccGroup" +#define NET_EXTERNALLY_MANAGED SETTINGS, "ExternallyManaged" enum security; struct scan_freq_set; @@ -82,6 +83,7 @@ struct network_config { bool have_transition_disable : 1; uint8_t transition_disable; enum known_network_ecc_group ecc_group; + bool externally_managed : 1; }; struct network_info { diff --git a/src/network.c b/src/network.c index a5a2375a..e1115be2 100644 --- a/src/network.c +++ b/src/network.c @@ -109,6 +109,29 @@ static bool network_settings_load(struct network *network) return network->settings != NULL; } +static bool network_settings_are_externally_managed( + struct l_settings *settings) +{ + bool externally_managed; + + if (!settings) + return false; + + if (!l_settings_get_bool(settings, NET_EXTERNALLY_MANAGED, + &externally_managed)) + return false; + + return externally_managed; +} + +static bool network_is_externally_managed(struct network *network) +{ + if (network->info) + return network->info->config.externally_managed; + + return network_settings_are_externally_managed(network->settings); +} + static void network_reset_psk(struct network *network) { if (network->psk) @@ -180,25 +203,27 @@ void network_connected(struct network *network) const char *ssid = network_get_ssid(network); int err; - if (!network->info) { - /* - * This is an open network seen for the first time: - * - * Write a settings file to keep track of the - * last connected time. This will also make iwd autoconnect - * to this network in the future. - */ - if (!network->settings) - network->settings = l_settings_new(); + if (!network_is_externally_managed(network)) { + if (!network->info) { + /* + * This is an open network seen for the first time: + * + * Write a settings file to keep track of the + * last connected time. This will also make iwd + * autoconnect to this network in the future. + */ + if (!network->settings) + network->settings = l_settings_new(); - storage_network_sync(security, ssid, network->settings); - } else { - err = network_info_touch(network->info); - if (err < 0) - l_error("Error %i touching network config", err); + storage_network_sync(security, ssid, network->settings); + } else { + err = network_info_touch(network->info); + if (err < 0) + l_error("Error %i touching network config", err); - /* Syncs frequencies of already known network*/ - known_network_frequency_sync(network->info); + /* Syncs frequencies of already known network*/ + known_network_frequency_sync(network->info); + } } l_queue_foreach_remove(network->secrets, @@ -758,6 +783,9 @@ void network_sync_settings(struct network *network) network->sync_settings = false; + if (network_is_externally_managed(network)) + return; + /* * Re-open the settings from Disk, in case they were updated * since we last opened them. @@ -768,6 +796,11 @@ void network_sync_settings(struct network *network) if (L_WARN_ON(!fs_settings)) return; + if (network_settings_are_externally_managed(fs_settings)) { + l_settings_free(fs_settings); + return; + } + network_settings_save(network, fs_settings); info->ops->sync(info, fs_settings); l_settings_free(fs_settings); diff --git a/src/storage.c b/src/storage.c index 2115a879..7158ce72 100644 --- a/src/storage.c +++ b/src/storage.c @@ -559,10 +559,22 @@ int __storage_decrypt(struct l_settings *settings, const char *ssid, return 0; } +static bool storage_settings_are_externally_managed( + struct l_settings *settings) +{ + bool externally_managed; + + if (!l_settings_get_bool(settings, "Settings", "ExternallyManaged", + &externally_managed)) + return false; + + return externally_managed; +} + /* * Decrypts a network profile (if needed). If profile encryption is enabled * and the profile is unencrypted it will be encrypted and written back to - * the file system automatically. + * the file system automatically, unless [Settings].ExternallyManaged is true. * * 'name' is used for decryption and is used as part of the IV. Callers * should provide a unique identifier here if available. For example, the @@ -581,6 +593,9 @@ bool storage_decrypt(struct l_settings *settings, const char *path, if (!needs_encryption) return true; + if (storage_settings_are_externally_managed(settings)) + return true; + /* Profile never encrypted before. Encrypt and write to disk */ encrypted = __storage_encrypt(settings, name, &elen); if (!encrypted) { diff --git a/unit/test-storage.c b/unit/test-storage.c index 9382efa6..92391bc1 100644 --- a/unit/test-storage.c +++ b/unit/test-storage.c @@ -25,6 +25,9 @@ #endif #include +#include +#include +#include #include @@ -35,6 +38,49 @@ static bool aes_ctr_supported(const void *data) return l_cipher_is_supported(L_CIPHER_AES_CTR); } +static char *create_profile_file(const char *data) +{ + char path[] = "/tmp/iwd-storage-test-XXXXXX"; + size_t len = strlen(data); + int fd; + + fd = mkstemp(path); + assert(fd >= 0); + + assert(write(fd, data, len) == (ssize_t) len); + assert(close(fd) == 0); + + return l_strdup(path); +} + +static char *read_profile_file(const char *path) +{ + char buffer[4096]; + ssize_t len; + + len = read_file(buffer, sizeof(buffer) - 1, "%s", path); + assert(len >= 0); + + buffer[len] = '\0'; + + return l_strdup(buffer); +} + +static struct l_settings *create_unencrypted_psk_settings( + bool externally_managed) +{ + struct l_settings *settings = l_settings_new(); + + if (externally_managed) + l_settings_set_bool(settings, "Settings", + "ExternallyManaged", true); + + l_settings_set_string(settings, "Security", "Passphrase", + "test-password"); + + return settings; +} + static void test_short_encrypted_bytes(const void *data) { struct l_settings *settings = l_settings_new(); @@ -54,13 +100,77 @@ static void test_short_encrypted_bytes(const void *data) storage_exit(); } +static void test_decrypt_encrypts_profile(const void *data) +{ + static const char profile[] = + "[Security]\n" + "Passphrase=test-password\n"; + struct l_settings *settings; + char *path; + char *contents; + + storage_init((const uint8_t *)"abc123", 6); + + settings = create_unencrypted_psk_settings(false); + path = create_profile_file(profile); + + assert(storage_decrypt(settings, path, "mySSID")); + + contents = read_profile_file(path); + assert(strstr(contents, "EncryptedSecurity=")); + assert(!strstr(contents, "Passphrase=test-password")); + + l_free(contents); + unlink(path); + l_free(path); + l_settings_free(settings); + + storage_exit(); +} + +static void test_decrypt_externally_managed_profile(const void *data) +{ + static const char profile[] = + "[Settings]\n" + "ExternallyManaged=true\n" + "\n" + "[Security]\n" + "Passphrase=test-password\n"; + struct l_settings *settings; + char *path; + char *contents; + + storage_init((const uint8_t *)"abc123", 6); + + settings = create_unencrypted_psk_settings(true); + path = create_profile_file(profile); + + assert(storage_decrypt(settings, path, "mySSID")); + + contents = read_profile_file(path); + assert(!strcmp(contents, profile)); + + l_free(contents); + unlink(path); + l_free(path); + l_settings_free(settings); + + storage_exit(); +} + int main(int argc, char *argv[]) { l_test_init(&argc, &argv); l_test_add_func_precheck("/storage/profile encryption", - test_short_encrypted_bytes, - aes_ctr_supported, 0); + test_short_encrypted_bytes, + aes_ctr_supported, 0); + l_test_add_func_precheck("/storage/profile encryption write", + test_decrypt_encrypts_profile, + aes_ctr_supported, 0); + l_test_add_func_precheck("/storage/externally managed profile", + test_decrypt_externally_managed_profile, + aes_ctr_supported, 0); return l_test_run(); }