[PATCH] station: only log station_autoconnect_start if autoconnecting
by James Prestwood
This debug print was before any checks which could bail out prior to
autoconnect starting. This was confusing because debug logs would
contain multiple "station_autoconnect_start()" prints making you think
autoconnect was started several times.
---
src/station.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/station.c b/src/station.c
index 8e2cabd0..6dc0074c 100644
--- a/src/station.c
+++ b/src/station.c
@@ -248,8 +248,6 @@ static int station_autoconnect_next(struct station *station)
static void station_autoconnect_start(struct station *station)
{
- l_debug("");
-
if (!station->autoconnect_can_start)
return;
@@ -265,6 +263,8 @@ static void station_autoconnect_start(struct station *station)
if (L_WARN_ON(station->autoconnect_list))
l_queue_destroy(station->autoconnect_list, NULL);
+ l_debug("");
+
station->autoconnect_list = l_queue_new();
station_network_foreach(station, network_add_foreach, station);
station_autoconnect_next(station);
--
2.34.1
6 months, 1 week
[PATCH v6 1/6] storage: implement network profile encryption
by James Prestwood
Some users don't like the idea of storing network credentials in plaintext
on the file system. As far as IWD goes the credential directory permissions
should be set up such that nobody but the owner/root have access but
nevertheless they are still plaintext.
For added security these credentials can be encrypted using a secret key.
In this patch the origination of the key does not matter, but in this
patch series IWD will support a systemd provided key.
The encryption itself will operate on the entire [Security] group as well
as all embedded groups. Once encrypted the [Security] group will be replaced
with two key/values:
EncryptedSalt - A random string of bytes used for the encryption
EncryptedSecurity - A string of bytes containing the encrypted [Security]
group, as well as all embedded groups.
After the profile has been encrypted these values should not be modified.
Note that any values added to [Security] after encryption has no effect.
Once the profile is encrypted there is no way to modify [Security] without
manually decrypting first, or just removing it entirely which effectively
treated a 'new' profile.
The encryption/decryption is done using AES-SIV with a salt value and the
network SSID as the IV.
Once a key is set any profiles opened will automatically be encrypted and
re-written to disk. Modules using network_storage_open will be provided
the decrypted profile, and will be unaware it was ever encrypted in the
first place. Similarly when network_storage_sync is called the profile
will by automatically encrypted and written to disk without the caller
needing to do anything special.
A few private storage.c helpers were added to serve several purposes:
__storage_set_encryption_key():
This sets the encryption key direct from systemd then uses extract and expand
to create a new fixed length key to do encryption/decryption.
__storage_decrypt():
Low level API to decrypt an l_settings object using a previously set key and
the SSID/name for the network. This returns a 'changed' out parameter signifying
that the settings need to be encrypted and re-written to disk. The purpose of
exposing this is for a standalone decryption tool which does not re-write any
settings.
__storage_open():
Wrapper around __storage_decrypt() that handles re-writing a new profile to
disk. This was exposed in order to support hotspot profiles.
__storage_encrypt():
Encrypts an l_settings object and returns the full profile as data
---
Makefile.am | 3 +-
src/storage.c | 279 ++++++++++++++++++++++++++++++++++++++++++++++++--
src/storage.h | 8 ++
3 files changed, 278 insertions(+), 12 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 89c053a6..35938d22 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -381,7 +381,8 @@ tools_hwsim_SOURCES = tools/hwsim.c src/mpdu.h \
src/nl80211util.h src/nl80211util.c \
src/storage.h src/storage.c \
src/common.h src/common.c \
- src/band.h src/band.c
+ src/band.h src/band.c \
+ src/crypto.h src/crypto.c
tools_hwsim_LDADD = $(ell_ldadd)
if DBUS_POLICY
diff --git a/src/storage.c b/src/storage.c
index 4b89c615..f5c89604 100644
--- a/src/storage.c
+++ b/src/storage.c
@@ -41,9 +41,11 @@
#include <sys/stat.h>
#include <ell/ell.h>
+#include "ell/useful.h"
#include "src/common.h"
#include "src/storage.h"
+#include "src/crypto.h"
#define STORAGE_DIR_MODE (S_IRUSR | S_IWUSR | S_IXUSR)
#define STORAGE_FILE_MODE (S_IRUSR | S_IWUSR)
@@ -52,6 +54,8 @@
static char *storage_path = NULL;
static char *storage_hotspot_path = NULL;
+static uint8_t system_key[32];
+static bool system_key_set = false;
static int create_dirs(const char *filename)
{
@@ -347,24 +351,254 @@ const char *storage_network_ssid_from_path(const char *path,
return buf;
}
+/* Groups requiring encryption (if enabled) */
+static char *encrypt_groups[] = {
+ "Security",
+ NULL
+};
+
+static bool encrypt_group(const char *group)
+{
+ char **g;
+
+ for (g = encrypt_groups; *g; g++) {
+ if (!strcmp(*g, group))
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Encrypt needed groups of 'settings' without modifying the object. Returns
+ * the entire settings object as data, with encrypted groups as a bytestring
+ * set as the value to [Security].EncryptedSecurity. This also includes any
+ * embedded groups.
+ *
+ * Note: If encryption is not enabled or there is no Security group this is
+ * effectively l_settings_to_data.
+ */
+char *__storage_encrypt(const struct l_settings *settings, const char *ssid,
+ size_t *out_len)
+{
+ struct iovec ad[2];
+ uint8_t salt[32];
+ size_t len;
+ _auto_(l_settings_free) struct l_settings *to_encrypt = NULL;
+ _auto_(l_settings_free) struct l_settings *original = NULL;
+ _auto_(l_free) char *plaintext = NULL;
+ _auto_(l_free) uint8_t *enc = NULL;
+ _auto_(l_strv_free) char **groups = NULL;
+ char **i;
+
+ if (!system_key_set || !l_settings_has_group(settings, "Security"))
+ return l_settings_to_data(settings, out_len);
+
+ /*
+ * Make two copies of the settings: One will contain only data to be
+ * encrypted (to_encrypt), the other will contain data to be left
+ * unencrypted (original). At the end any encrypted data will be set
+ * into 'original' as EncryptedSecurity.
+ */
+ to_encrypt = l_settings_clone(settings);
+ original = l_settings_clone(settings);
+
+ groups = l_settings_get_groups(to_encrypt);
+ for (i = groups; *i; i++) {
+ if (encrypt_group(*i))
+ l_settings_remove_group(original, *i);
+ else
+ l_settings_remove_group(to_encrypt, *i);
+ }
+
+ l_settings_remove_embedded_groups(original);
+
+ plaintext = l_settings_to_data(to_encrypt, &len);
+ if (!plaintext)
+ return NULL;
+
+ l_getrandom(salt, 32);
+
+ ad[0].iov_base = (void *) salt;
+ ad[0].iov_len = 32;
+ ad[1].iov_base = (void *) ssid;
+ ad[1].iov_len = strlen(ssid);
+
+ enc = l_malloc(len + 16);
+
+ if (!aes_siv_encrypt(system_key, sizeof(system_key), plaintext, len,
+ ad, 2, enc)) {
+ l_error("Could not encrypt [Security] group");
+ return NULL;
+ }
+
+ l_settings_set_bytes(original, "Security", "EncryptedSalt", salt, 32);
+ l_settings_set_bytes(original, "Security", "EncryptedSecurity",
+ enc, len + 16);
+
+ return l_settings_to_data(original, out_len);
+}
+
+/*
+ * Decrypt data in [Security].EncryptedSecurity. This data also includes
+ * embedded groups potentially. Once decrypted the data is put back into the
+ * object.
+ *
+ * Note: if encryption is not enabled or there is no Security group settings
+ * is not modified.
+ */
+int __storage_decrypt(struct l_settings *settings, const char *ssid,
+ bool *changed)
+{
+ _auto_(l_settings_free) struct l_settings *security = NULL;
+ _auto_(l_free) uint8_t *encrypted = NULL;
+ _auto_(l_free) uint8_t *decrypted = NULL;
+ _auto_(l_free) uint8_t *salt = NULL;
+ _auto_(l_strv_free) char **embedded = NULL;
+ _auto_(l_strv_free) char **groups = NULL;
+ char **i;
+ size_t elen, slen;
+ struct iovec ad[2];
+
+ if (!system_key_set)
+ goto done;
+
+ if (!l_settings_has_group(settings, "Security"))
+ goto done;
+
+ encrypted = l_settings_get_bytes(settings, "Security",
+ "EncryptedSecurity", &elen);
+ salt = l_settings_get_bytes(settings, "Security",
+ "EncryptedSalt", &slen);
+
+ /*
+ * Either profile has never been loaded after enabling encryption or is
+ * missing Encrypted{Salt,Security} values. If either are missing this
+ * profile is corrupted and must be fixed.
+ */
+ if (!(encrypted && salt)) {
+ /* Profile corrupted */
+ if (encrypted || salt) {
+ l_warn("Profile %s is corrupted reconfigure manually",
+ ssid);
+ return -EBADMSG;
+ }
+
+ if (changed)
+ *changed = true;
+
+ return 0;
+ }
+
+ decrypted = l_malloc(elen - 16 + 1);
+
+ ad[0].iov_base = (void *)salt;
+ ad[0].iov_len = slen;
+ ad[1].iov_base = (void *)ssid;
+ ad[1].iov_len = strlen(ssid);
+
+ if (!aes_siv_decrypt(system_key, sizeof(system_key), encrypted, elen,
+ ad, 2, decrypted)) {
+ l_error("Could not decrypt %s profile, did the secret change?",
+ ssid);
+ return -ENOKEY;
+ }
+
+ decrypted[elen - 16] = '\0';
+
+ /*
+ * Remove any groups that are marked as encrypted (plus embedded),
+ * and copy the decrypted groups back into settings.
+ */
+ groups = l_settings_get_groups(settings);
+ for (i = groups; *i; i++) {
+ if (encrypt_group(*i))
+ l_settings_remove_group(settings, *i);
+ }
+
+ l_settings_remove_embedded_groups(settings);
+
+ /*
+ * Load decrypted data into existing settings. This is not how the API
+ * is indended to be used (since this could result in duplicate groups)
+ * but since the Security group was just removed and EncryptedSecurity
+ * should only contain a Security group its safe to use it this way.
+ */
+ if (!l_settings_load_from_data(settings, (const char *) decrypted,
+ elen - 16)) {
+ l_error("Could not load decrypted security group");
+ return -EBADMSG;
+ }
+
+done:
+ if (changed)
+ *changed = false;
+
+ return 0;
+}
+
+/*
+ * Direct open() by path. 'name' is used for decryption and depends on the
+ * module calling. For regular storage operations 'name' is the SSID. For
+ * hotspot 'name' is the consortium name (since the SSID is not always the
+ * same).
+ */
+bool __storage_open(struct l_settings *settings, const char *path,
+ enum security type, const char *name)
+{
+ bool changed;
+ _auto_(l_free) char *encrypted = NULL;
+ size_t elen;
+
+ if (type == SECURITY_NONE)
+ return true;
+
+ if (__storage_decrypt(settings, name, &changed) < 0)
+ return false;
+
+ if (!changed)
+ return true;
+
+ /* Profile never encrypted before. Encrypt and write to disk */
+ encrypted = __storage_encrypt(settings, name, &elen);
+ if (!encrypted) {
+ l_error("Could not encrypt new profile %s", name);
+ return false;
+ }
+
+ if (write_file(encrypted, elen, false, "%s", path) < 0) {
+ l_error("Failed to write out encrypted profile");
+ return false;
+ }
+
+ l_debug("Encrypted a new profile %s", path);
+
+ return true;
+}
+
struct l_settings *storage_network_open(enum security type, const char *ssid)
{
struct l_settings *settings;
- char *path;
+ _auto_(l_free) char *path = NULL;
if (ssid == NULL)
return NULL;
path = storage_get_network_file_path(type, ssid);
+
settings = l_settings_new();
- if (!l_settings_load_from_file(settings, path)) {
- l_settings_free(settings);
- settings = NULL;
- }
+ if (!l_settings_load_from_file(settings, path))
+ goto error;
+
+ if (!__storage_open(settings, path, type, ssid))
+ goto error;
- l_free(path);
return settings;
+
+error:
+ l_settings_free(settings);
+ return NULL;
}
int storage_network_touch(enum security type, const char *ssid)
@@ -388,15 +622,19 @@ int storage_network_touch(enum security type, const char *ssid)
void storage_network_sync(enum security type, const char *ssid,
struct l_settings *settings)
{
- char *data;
+ _auto_(l_free) char *data = NULL;
+ _auto_(l_free) char *path = NULL;
size_t length = 0;
- char *path;
path = storage_get_network_file_path(type, ssid);
- data = l_settings_to_data(settings, &length);
+ data = __storage_encrypt(settings, ssid, &length);
+
+ if (!data) {
+ l_error("Unable to sync profile %s", ssid);
+ return;
+ }
+
write_file(data, length, true, "%s", path);
- l_free(data);
- l_free(path);
}
int storage_network_remove(enum security type, const char *ssid)
@@ -463,3 +701,22 @@ bool storage_is_file(const char *filename)
return false;
}
+
+/*
+ * Initialize a systemd encryption key for encrypting/decrypting credentials.
+ * This uses the 'extract and expand' concept from RFC 5869 to derive a new,
+ * fixed length, key.
+ */
+void __storage_set_encryption_key(const uint8_t *key, size_t key_len)
+{
+ uint8_t tmp[32];
+
+ if (!hkdf_extract(L_CHECKSUM_SHA256, NULL, 0, 1, tmp, key, key_len))
+ return;
+
+ if (!hkdf_expand(L_CHECKSUM_SHA256, tmp, sizeof(tmp), "IWD System Key",
+ system_key, sizeof(system_key)))
+ return;
+
+ system_key_set = true;
+}
diff --git a/src/storage.h b/src/storage.h
index e1ec2cd4..14d81f0a 100644
--- a/src/storage.h
+++ b/src/storage.h
@@ -50,3 +50,11 @@ int storage_network_remove(enum security type, const char *ssid);
struct l_settings *storage_known_frequencies_load(void);
void storage_known_frequencies_sync(struct l_settings *known_freqs);
+
+void __storage_set_encryption_key(const uint8_t *key, size_t key_len);
+int __storage_decrypt(struct l_settings *settings, const char *ssid,
+ bool *changed);
+char *__storage_encrypt(const struct l_settings *settings, const char *ssid,
+ size_t *out_len);
+bool __storage_open(struct l_settings *settings, const char *path,
+ enum security type, const char *name);
--
2.34.1
6 months, 1 week
[PATCH] netdev: fail connection if the link goes down
by James Prestwood
In certain rare cases IWD gets a link down event before nl80211 ever sends
a disconnect event. Netdev notifies station of the link down which causes
station to be freed, but netdev remains in the same state. Then later the
disconnect event arrives and netdev still thinks its connected, calls into
(the now freed) station object and causes a crash.
To fix this netdev_connect_failed() is now called on any link down events
which will reset the netdev object to a proper state.
src/netdev.c:netdev_link_notify() event 16 on ifindex 16
src/netdev.c:netdev_mlme_notify() MLME notification Del Station(20)
src/netdev.c:netdev_link_notify() event 16 on ifindex 16
src/netdev.c:netdev_mlme_notify() MLME notification Deauthenticate(39)
src/netdev.c:netdev_deauthenticate_event()
src/netdev.c:netdev_link_notify() event 16 on ifindex 16
src/station.c:station_free()
src/netconfig.c:netconfig_destroy()
src/resolve.c:resolve_systemd_revert() ifindex: 16
src/station.c:station_roam_state_clear() 16
src/netdev.c:netdev_mlme_notify() MLME notification Disconnect(48)
src/netdev.c:netdev_disconnect_event()
Received Deauthentication event, reason: 3, from_ap: false
0 0x472fa4 in station_disconnect_event src/station.c:2916
1 0x472fa4 in station_netdev_event src/station.c:2954
2 0x43a262 in netdev_disconnect_event src/netdev.c:1213
3 0x43a262 in netdev_mlme_notify src/netdev.c:5471
4 0x6706eb in process_multicast ell/genl.c:1029
5 0x6706eb in received_data ell/genl.c:1096
6 0x65e630 in io_callback ell/io.c:120
7 0x65a94e in l_main_iterate ell/main.c:478
8 0x65b0b3 in l_main_run ell/main.c:525
9 0x65b0b3 in l_main_run ell/main.c:507
10 0x65b5cc in l_main_run_with_signal ell/main.c:647
11 0x4124d7 in main src/main.c:532
---
src/netdev.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/netdev.c b/src/netdev.c
index bac6860c..02ee9920 100644
--- a/src/netdev.c
+++ b/src/netdev.c
@@ -6199,6 +6199,10 @@ static void netdev_newlink_notify(const struct ifinfomsg *ifi, int bytes)
new_up = netdev_get_is_up(netdev);
+ if (!new_up)
+ netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
+ MMPDU_STATUS_CODE_UNSPECIFIED);
+
/*
* If mac_change_cmd_id is set we are in the process of changing the
* MAC address and this event is a result of powering down/up. In this
--
2.31.1
6 months, 1 week
[PATCH] scan: remove periodic scans from queue on abort
by James Prestwood
The periodic scan code was refactored to make normal scans and
periodic scans consistent by keeping both in the same queue. But
that change left out the abort path where periodic scans were not
actually removed from the queue.
This fixes a rare crash when a periodic scan has been triggered and
the device goes down. This path never removes the request from the
queue but still frees it. Then when the scan context is removed the
stale request is freed again.
0 0x4bb65b in scan_request_cancel src/scan.c:202
1 0x64313c in l_queue_clear ell/queue.c:107
2 0x643348 in l_queue_destroy ell/queue.c:82
3 0x4bbfb7 in scan_context_free src/scan.c:209
4 0x4c9a78 in scan_wdev_remove src/scan.c:2115
5 0x42fecd in netdev_free src/netdev.c:965
6 0x445827 in netdev_destroy src/netdev.c:6507
7 0x52beb9 in manager_config_notify src/manager.c:765
8 0x67084b in process_multicast ell/genl.c:1029
9 0x67084b in received_data ell/genl.c:1096
10 0x65e790 in io_callback ell/io.c:120
11 0x65aaae in l_main_iterate ell/main.c:478
12 0x65b213 in l_main_run ell/main.c:525
13 0x65b213 in l_main_run ell/main.c:507
14 0x65b72c in l_main_run_with_signal ell/main.c:647
15 0x4124e7 in main src/main.c:532
---
src/scan.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/scan.c b/src/scan.c
index 99813952..1ab6420b 100644
--- a/src/scan.c
+++ b/src/scan.c
@@ -1993,9 +1993,10 @@ static void scan_notify(struct l_genl_msg *msg, void *user_data)
sr->triggered = false;
/* If periodic scan, don't report the abort */
- if (sr->periodic)
+ if (sr->periodic) {
+ l_queue_remove(sc->requests, sr);
wiphy_radio_work_done(sc->wiphy, sr->work.id);
- else
+ } else
scan_finished(sc, -ECANCELED, NULL, NULL, sr);
} else if (wiphy_radio_work_is_running(sc->wiphy,
sr->work.id)) {
--
2.31.1
6 months, 1 week
[PATCH v3 1/7] storage: implement network profile encryption
by James Prestwood
Some users don't like the idea of storing network credentials in plaintext
on the file system. As far as IWD goes the credential directory permissions
should be set up such that nobody but the owner/root have access but
nevertheless they are still plaintext.
For added security these credentials can be encrypted using a secret key.
In this patch the origination of the key does not matter, but in this
patch series IWD will support a systemd provided key.
The encryption itself will operate on the entire [Security] group as well
as all embedded groups. Once encrypted the [Security] group will be replaced
with two key/values:
EncryptedSalt - A random string of bytes used for the encryption
EncryptedSecurity - A string of bytes containing the encrypted [Security]
group, as well as all embedded groups.
After the profile has been encrypted these values should not be modified.
Note that any values added to [Security] after encryption has no effect.
Once the profile is encrypted there is no way to modify [Security] without
manually decrypting first, or just removing it entirely which effectively
treated a 'new' profile.
The encryption/decryption is done using AES-SIV with a salt value and the
network SSID as the IV.
Once a key is set any profiles opened will automatically be encrypted and
re-written to disk. Modules using network_storage_open will be provided
the decrypted profile, and will be unaware it was ever encrypted in the
first place. Similarly when network_storage_sync is called the profile
will by automatically encrypted and written to disk without the caller
needing to do anything special.
A few private storage.c helpers were added to serve several purposes:
__storage_set_encryption_key():
This sets the encryption key direct from systemd then uses extract and expand
to create a new fixed length key to do encryption/decryption.
__storage_decrypt():
Low level API to decrypt an l_settings object using a previously set key and
the SSID/name for the network. This returns a 'changed' out parameter signifying
that the settings need to be encrypted and re-written to disk. The purpose of
exposing this is for a standalone decryption tool which does not re-write any
settings.
__storage_open():
Wrapper around __storage_decrypt() that handles re-writing a new profile to
disk. This was exposed in order to support hotspot profiles.
__storage_encrypt():
Encrypts an l_settings object and returns the full profile as data
---
Makefile.am | 3 +-
src/storage.c | 268 +++++++++++++++++++++++++++++++++++++++++++++++---
src/storage.h | 8 ++
3 files changed, 267 insertions(+), 12 deletions(-)
v3:
* Created and exposed __storage_open for use in hotspot.
* Use extract-and-expand APIs to create encryption key
diff --git a/Makefile.am b/Makefile.am
index 89c053a6..35938d22 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -381,7 +381,8 @@ tools_hwsim_SOURCES = tools/hwsim.c src/mpdu.h \
src/nl80211util.h src/nl80211util.c \
src/storage.h src/storage.c \
src/common.h src/common.c \
- src/band.h src/band.c
+ src/band.h src/band.c \
+ src/crypto.h src/crypto.c
tools_hwsim_LDADD = $(ell_ldadd)
if DBUS_POLICY
diff --git a/src/storage.c b/src/storage.c
index 4b89c615..026236d0 100644
--- a/src/storage.c
+++ b/src/storage.c
@@ -41,9 +41,11 @@
#include <sys/stat.h>
#include <ell/ell.h>
+#include "ell/useful.h"
#include "src/common.h"
#include "src/storage.h"
+#include "src/crypto.h"
#define STORAGE_DIR_MODE (S_IRUSR | S_IWUSR | S_IXUSR)
#define STORAGE_FILE_MODE (S_IRUSR | S_IWUSR)
@@ -52,6 +54,8 @@
static char *storage_path = NULL;
static char *storage_hotspot_path = NULL;
+static uint8_t system_key[32];
+static bool system_key_set = false;
static int create_dirs(const char *filename)
{
@@ -347,24 +351,243 @@ const char *storage_network_ssid_from_path(const char *path,
return buf;
}
+/* Groups requiring encryption (if enabled) */
+static char *encrypt_groups[] = {
+ "Security",
+ NULL
+};
+
+static bool encrypt_group(const char *group)
+{
+ char **g;
+
+ for (g = encrypt_groups; *g; g++) {
+ if (!strcmp(*g, group))
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Encrypt needed groups of 'settings' without modifying the object. Returns
+ * the entire settings object as data, with encrypted groups as a bytestring
+ * set as the value to [Security].EncryptedSecurity. This also includes any
+ * embedded groups.
+ *
+ * Note: If encryption is not enabled or there is no Security group this is
+ * effectively l_settings_to_data.
+ */
+char *__storage_encrypt(struct l_settings *settings, const char *ssid,
+ size_t *out_len)
+{
+ struct iovec ad[2];
+ uint8_t salt[32];
+ size_t len;
+ _auto_(l_free) struct l_settings *to_encrypt = NULL;
+ _auto_(l_free) struct l_settings *original = NULL;
+ _auto_(l_free) char *plaintext = NULL;
+ _auto_(l_free) uint8_t *enc = NULL;
+ _auto_(l_strv_free) char **groups = NULL;
+ char **i;
+
+ if (!system_key_set || !l_settings_has_group(settings, "Security"))
+ return l_settings_to_data(settings, out_len);
+
+ /*
+ * Make two copies of the settings: One will contain only data to be
+ * encrypted (to_encrypt), the other will contain data to be left
+ * unencrypted (original). At the end any encrypted data will be set
+ * into 'original' as EncryptedSecurity.
+ */
+ to_encrypt = l_settings_clone(settings);
+ original = l_settings_clone(settings);
+
+ groups = l_settings_get_groups(to_encrypt);
+ for (i = groups; *i; i++) {
+ if (encrypt_group(*i))
+ l_settings_remove_group(original, *i);
+ else
+ l_settings_remove_group(to_encrypt, *i);
+ }
+
+ l_settings_remove_embedded_groups(original);
+
+ plaintext = l_settings_to_data(to_encrypt, &len);
+ if (!plaintext)
+ return NULL;
+
+ l_getrandom(salt, 32);
+
+ ad[0].iov_base = (void *) salt;
+ ad[0].iov_len = 32;
+ ad[1].iov_base = (void *) ssid;
+ ad[1].iov_len = strlen(ssid);
+
+ enc = l_malloc(len + 16);
+
+ if (!aes_siv_encrypt(system_key, sizeof(system_key), plaintext, len,
+ ad, 2, enc)) {
+ l_error("Could not encrypt [Security] group");
+ return NULL;
+ }
+
+ l_settings_set_bytes(original, "Security", "EncryptedSalt", salt, 32);
+ l_settings_set_bytes(original, "Security", "EncryptedSecurity",
+ enc, len + 16);
+
+ return l_settings_to_data(original, out_len);
+}
+
+/*
+ * Decrypt data in [Security].EncryptedSecurity. This data also includes
+ * embedded groups potentially. Once decrypted the data is put back into the
+ * object.
+ *
+ * Note: if encryption is not enabled or there is no Security group settings
+ * is not modified.
+ */
+int __storage_decrypt(struct l_settings *settings, const char *ssid,
+ bool *changed)
+{
+ _auto_(l_settings_free) struct l_settings *security = NULL;
+ _auto_(l_free) uint8_t *encrypted = NULL;
+ _auto_(l_free) uint8_t *decrypted = NULL;
+ _auto_(l_free) uint8_t *salt = NULL;
+ _auto_(l_strv_free) char **embedded = NULL;
+ size_t elen, slen;
+ struct iovec ad[2];
+
+ if (!system_key_set)
+ goto done;
+
+ if (!l_settings_has_group(settings, "Security"))
+ goto done;
+
+ encrypted = l_settings_get_bytes(settings, "Security",
+ "EncryptedSecurity", &elen);
+ salt = l_settings_get_bytes(settings, "Security",
+ "EncryptedSalt", &slen);
+
+ /*
+ * Either profile has never been loaded after enabling encryption or is
+ * missing Encrypted{Salt,Security} values. If either are missing this
+ * profile is corrupted and must be fixed.
+ */
+ if (!(encrypted && salt)) {
+ /* Profile corrupted */
+ if (encrypted || salt) {
+ l_warn("Profile %s is corrupted reconfigure manually",
+ ssid);
+ return -EBADMSG;
+ }
+
+ if (changed)
+ *changed = true;
+
+ return 0;
+ }
+
+ decrypted = l_malloc(elen - 16 + 1);
+
+ ad[0].iov_base = (void *)salt;
+ ad[0].iov_len = slen;
+ ad[1].iov_base = (void *)ssid;
+ ad[1].iov_len = strlen(ssid);
+
+ if (!aes_siv_decrypt(system_key, sizeof(system_key), encrypted, elen,
+ ad, 2, decrypted)) {
+ l_error("Could not decrypt %s profile, did the secret change?",
+ ssid);
+ return -ENOKEY;
+ }
+
+ decrypted[elen - 16] = '\0';
+
+ /* Remove encrypted [Security], and copy the decrypted one */
+ l_settings_remove_group(settings, "Security");
+
+ /*
+ * Load decrypted data into existing settings. This is not how the API
+ * is indended to be used (since this could result in duplicate groups)
+ * but since the Security group was just removed and EncryptedSecurity
+ * should only contain a Security group its safe to use it this way.
+ */
+ if (!l_settings_load_from_data(settings, (const char *) decrypted,
+ elen - 16)) {
+ l_error("Could not load decrypted security group");
+ return -EBADMSG;
+ }
+
+done:
+ if (changed)
+ *changed = false;
+
+ return 0;
+}
+
+/*
+ * Direct open() by path. 'name' is used for decryption and depends on the
+ * module calling. For regular storage operations 'name' is the SSID. For
+ * hotspot 'name' is the consortium name (since the SSID is not always the
+ * same).
+ */
+bool __storage_open(struct l_settings *settings, const char *path,
+ enum security type, const char *name)
+{
+ bool changed;
+ _auto_(l_free) char *encrypted = NULL;
+ size_t elen;
+
+ if (type == SECURITY_NONE)
+ return true;
+
+ if (__storage_decrypt(settings, name, &changed) < 0)
+ return false;
+
+ if (!changed)
+ return true;
+
+ /* Profile never encrypted before. Encrypt and write to disk */
+ encrypted = __storage_encrypt(settings, name, &elen);
+ if (!encrypted) {
+ l_error("Could not encrypt new profile %s", name);
+ return false;
+ }
+
+ if (write_file(encrypted, elen, false, "%s", path) < 0) {
+ l_error("Failed to write out encrypted profile");
+ return false;
+ }
+
+ l_debug("Encrypted a new profile %s", path);
+
+ return true;
+}
+
struct l_settings *storage_network_open(enum security type, const char *ssid)
{
struct l_settings *settings;
- char *path;
+ _auto_(l_free) char *path = NULL;
if (ssid == NULL)
return NULL;
path = storage_get_network_file_path(type, ssid);
+
settings = l_settings_new();
- if (!l_settings_load_from_file(settings, path)) {
- l_settings_free(settings);
- settings = NULL;
- }
+ if (!l_settings_load_from_file(settings, path))
+ goto error;
+
+ if (!__storage_open(settings, path, type, ssid))
+ goto error;
- l_free(path);
return settings;
+
+error:
+ l_settings_free(settings);
+ return NULL;
}
int storage_network_touch(enum security type, const char *ssid)
@@ -388,15 +611,19 @@ int storage_network_touch(enum security type, const char *ssid)
void storage_network_sync(enum security type, const char *ssid,
struct l_settings *settings)
{
- char *data;
+ _auto_(l_free) char *data = NULL;
+ _auto_(l_free) char *path = NULL;
size_t length = 0;
- char *path;
path = storage_get_network_file_path(type, ssid);
- data = l_settings_to_data(settings, &length);
+ data = __storage_encrypt(settings, ssid, &length);
+
+ if (!data) {
+ l_error("Unable to sync profile %s", ssid);
+ return;
+ }
+
write_file(data, length, true, "%s", path);
- l_free(data);
- l_free(path);
}
int storage_network_remove(enum security type, const char *ssid)
@@ -463,3 +690,22 @@ bool storage_is_file(const char *filename)
return false;
}
+
+/*
+ * Initialize a systemd encryption key for encrypting/decrypting credentials.
+ * This uses the 'extract and expand' concept from RFC 5869 to derive a new,
+ * fixed length, key.
+ */
+void __storage_set_encryption_key(const uint8_t *key, size_t key_len)
+{
+ uint8_t tmp[32];
+
+ if (!hkdf_extract(L_CHECKSUM_SHA256, NULL, 0, 1, tmp, key, key_len))
+ return;
+
+ if (!hkdf_expand(L_CHECKSUM_SHA256, tmp, sizeof(tmp), "IWD System Key",
+ system_key, sizeof(system_key)))
+ return;
+
+ system_key_set = true;
+}
diff --git a/src/storage.h b/src/storage.h
index e1ec2cd4..93f23c6e 100644
--- a/src/storage.h
+++ b/src/storage.h
@@ -50,3 +50,11 @@ int storage_network_remove(enum security type, const char *ssid);
struct l_settings *storage_known_frequencies_load(void);
void storage_known_frequencies_sync(struct l_settings *known_freqs);
+
+void __storage_set_encryption_key(const uint8_t *key, size_t key_len);
+int __storage_decrypt(struct l_settings *settings, const char *ssid,
+ bool *changed);
+char *__storage_encrypt(struct l_settings *settings, const char *ssid,
+ size_t *out_len);
+bool __storage_open(struct l_settings *settings, const char *path,
+ enum security type, const char *name);
--
2.31.1
6 months, 1 week
[PATCH] netdev: fix crash from not cancelling netdev_get_oci
by James Prestwood
If netdev_connect_failed is called before netdev_get_oci_cb() the
netdev's handshake will be destroyed and ultimately crash when the
callback is called.
This patch moves the cancelation into netdev_connect_free rather than
netdev_free.
++++++++ backtrace ++++++++
0 0x7f4e1787d320 in /lib64/libc.so.6
1 0x42634c in handshake_state_set_chandef() at src/handshake.c:1057
2 0x40a11b in netdev_get_oci_cb() at src/netdev.c:2387
3 0x483d7b in process_unicast() at ell/genl.c:986
4 0x480d3c in io_callback() at ell/io.c:120
5 0x48004d in l_main_iterate() at ell/main.c:472 (discriminator 2)
6 0x4800fc in l_main_run() at ell/main.c:521
7 0x48032c in l_main_run_with_signal() at ell/main.c:649
8 0x403e95 in main() at src/main.c:532
9 0x7f4e17867b75 in /lib64/libc.so.6
+++++++++++++++++++++++++++
---
src/netdev.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/netdev.c b/src/netdev.c
index cda0c7fc..bac6860c 100644
--- a/src/netdev.c
+++ b/src/netdev.c
@@ -838,6 +838,11 @@ static void netdev_connect_free(struct netdev *netdev)
netdev->disconnect_cmd_id = 0;
}
+ if (netdev->get_oci_cmd_id) {
+ l_genl_family_cancel(nl80211, netdev->get_oci_cmd_id);
+ netdev->get_oci_cmd_id = 0;
+ }
+
if (netdev->ft_ds_list) {
l_queue_destroy(netdev->ft_ds_list, netdev_ft_ds_entry_free);
netdev->ft_ds_list = NULL;
@@ -949,11 +954,6 @@ static void netdev_free(void *data)
netdev->get_station_cmd_id = 0;
}
- if (netdev->get_oci_cmd_id) {
- l_genl_family_cancel(nl80211, netdev->get_oci_cmd_id);
- netdev->get_oci_cmd_id = 0;
- }
-
if (netdev->fw_roam_bss)
scan_bss_free(netdev->fw_roam_bss);
--
2.31.1
6 months, 1 week
[PATCH v5 1/6] storage: implement network profile encryption
by James Prestwood
Some users don't like the idea of storing network credentials in plaintext
on the file system. As far as IWD goes the credential directory permissions
should be set up such that nobody but the owner/root have access but
nevertheless they are still plaintext.
For added security these credentials can be encrypted using a secret key.
In this patch the origination of the key does not matter, but in this
patch series IWD will support a systemd provided key.
The encryption itself will operate on the entire [Security] group as well
as all embedded groups. Once encrypted the [Security] group will be replaced
with two key/values:
EncryptedSalt - A random string of bytes used for the encryption
EncryptedSecurity - A string of bytes containing the encrypted [Security]
group, as well as all embedded groups.
After the profile has been encrypted these values should not be modified.
Note that any values added to [Security] after encryption has no effect.
Once the profile is encrypted there is no way to modify [Security] without
manually decrypting first, or just removing it entirely which effectively
treated a 'new' profile.
The encryption/decryption is done using AES-SIV with a salt value and the
network SSID as the IV.
Once a key is set any profiles opened will automatically be encrypted and
re-written to disk. Modules using network_storage_open will be provided
the decrypted profile, and will be unaware it was ever encrypted in the
first place. Similarly when network_storage_sync is called the profile
will by automatically encrypted and written to disk without the caller
needing to do anything special.
A few private storage.c helpers were added to serve several purposes:
__storage_set_encryption_key():
This sets the encryption key direct from systemd then uses extract and expand
to create a new fixed length key to do encryption/decryption.
__storage_decrypt():
Low level API to decrypt an l_settings object using a previously set key and
the SSID/name for the network. This returns a 'changed' out parameter signifying
that the settings need to be encrypted and re-written to disk. The purpose of
exposing this is for a standalone decryption tool which does not re-write any
settings.
__storage_open():
Wrapper around __storage_decrypt() that handles re-writing a new profile to
disk. This was exposed in order to support hotspot profiles.
__storage_encrypt():
Encrypts an l_settings object and returns the full profile as data
---
Makefile.am | 3 +-
src/storage.c | 279 ++++++++++++++++++++++++++++++++++++++++++++++++--
src/storage.h | 8 ++
3 files changed, 278 insertions(+), 12 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 89c053a6..35938d22 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -381,7 +381,8 @@ tools_hwsim_SOURCES = tools/hwsim.c src/mpdu.h \
src/nl80211util.h src/nl80211util.c \
src/storage.h src/storage.c \
src/common.h src/common.c \
- src/band.h src/band.c
+ src/band.h src/band.c \
+ src/crypto.h src/crypto.c
tools_hwsim_LDADD = $(ell_ldadd)
if DBUS_POLICY
diff --git a/src/storage.c b/src/storage.c
index 4b89c615..f5c89604 100644
--- a/src/storage.c
+++ b/src/storage.c
@@ -41,9 +41,11 @@
#include <sys/stat.h>
#include <ell/ell.h>
+#include "ell/useful.h"
#include "src/common.h"
#include "src/storage.h"
+#include "src/crypto.h"
#define STORAGE_DIR_MODE (S_IRUSR | S_IWUSR | S_IXUSR)
#define STORAGE_FILE_MODE (S_IRUSR | S_IWUSR)
@@ -52,6 +54,8 @@
static char *storage_path = NULL;
static char *storage_hotspot_path = NULL;
+static uint8_t system_key[32];
+static bool system_key_set = false;
static int create_dirs(const char *filename)
{
@@ -347,24 +351,254 @@ const char *storage_network_ssid_from_path(const char *path,
return buf;
}
+/* Groups requiring encryption (if enabled) */
+static char *encrypt_groups[] = {
+ "Security",
+ NULL
+};
+
+static bool encrypt_group(const char *group)
+{
+ char **g;
+
+ for (g = encrypt_groups; *g; g++) {
+ if (!strcmp(*g, group))
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Encrypt needed groups of 'settings' without modifying the object. Returns
+ * the entire settings object as data, with encrypted groups as a bytestring
+ * set as the value to [Security].EncryptedSecurity. This also includes any
+ * embedded groups.
+ *
+ * Note: If encryption is not enabled or there is no Security group this is
+ * effectively l_settings_to_data.
+ */
+char *__storage_encrypt(const struct l_settings *settings, const char *ssid,
+ size_t *out_len)
+{
+ struct iovec ad[2];
+ uint8_t salt[32];
+ size_t len;
+ _auto_(l_settings_free) struct l_settings *to_encrypt = NULL;
+ _auto_(l_settings_free) struct l_settings *original = NULL;
+ _auto_(l_free) char *plaintext = NULL;
+ _auto_(l_free) uint8_t *enc = NULL;
+ _auto_(l_strv_free) char **groups = NULL;
+ char **i;
+
+ if (!system_key_set || !l_settings_has_group(settings, "Security"))
+ return l_settings_to_data(settings, out_len);
+
+ /*
+ * Make two copies of the settings: One will contain only data to be
+ * encrypted (to_encrypt), the other will contain data to be left
+ * unencrypted (original). At the end any encrypted data will be set
+ * into 'original' as EncryptedSecurity.
+ */
+ to_encrypt = l_settings_clone(settings);
+ original = l_settings_clone(settings);
+
+ groups = l_settings_get_groups(to_encrypt);
+ for (i = groups; *i; i++) {
+ if (encrypt_group(*i))
+ l_settings_remove_group(original, *i);
+ else
+ l_settings_remove_group(to_encrypt, *i);
+ }
+
+ l_settings_remove_embedded_groups(original);
+
+ plaintext = l_settings_to_data(to_encrypt, &len);
+ if (!plaintext)
+ return NULL;
+
+ l_getrandom(salt, 32);
+
+ ad[0].iov_base = (void *) salt;
+ ad[0].iov_len = 32;
+ ad[1].iov_base = (void *) ssid;
+ ad[1].iov_len = strlen(ssid);
+
+ enc = l_malloc(len + 16);
+
+ if (!aes_siv_encrypt(system_key, sizeof(system_key), plaintext, len,
+ ad, 2, enc)) {
+ l_error("Could not encrypt [Security] group");
+ return NULL;
+ }
+
+ l_settings_set_bytes(original, "Security", "EncryptedSalt", salt, 32);
+ l_settings_set_bytes(original, "Security", "EncryptedSecurity",
+ enc, len + 16);
+
+ return l_settings_to_data(original, out_len);
+}
+
+/*
+ * Decrypt data in [Security].EncryptedSecurity. This data also includes
+ * embedded groups potentially. Once decrypted the data is put back into the
+ * object.
+ *
+ * Note: if encryption is not enabled or there is no Security group settings
+ * is not modified.
+ */
+int __storage_decrypt(struct l_settings *settings, const char *ssid,
+ bool *changed)
+{
+ _auto_(l_settings_free) struct l_settings *security = NULL;
+ _auto_(l_free) uint8_t *encrypted = NULL;
+ _auto_(l_free) uint8_t *decrypted = NULL;
+ _auto_(l_free) uint8_t *salt = NULL;
+ _auto_(l_strv_free) char **embedded = NULL;
+ _auto_(l_strv_free) char **groups = NULL;
+ char **i;
+ size_t elen, slen;
+ struct iovec ad[2];
+
+ if (!system_key_set)
+ goto done;
+
+ if (!l_settings_has_group(settings, "Security"))
+ goto done;
+
+ encrypted = l_settings_get_bytes(settings, "Security",
+ "EncryptedSecurity", &elen);
+ salt = l_settings_get_bytes(settings, "Security",
+ "EncryptedSalt", &slen);
+
+ /*
+ * Either profile has never been loaded after enabling encryption or is
+ * missing Encrypted{Salt,Security} values. If either are missing this
+ * profile is corrupted and must be fixed.
+ */
+ if (!(encrypted && salt)) {
+ /* Profile corrupted */
+ if (encrypted || salt) {
+ l_warn("Profile %s is corrupted reconfigure manually",
+ ssid);
+ return -EBADMSG;
+ }
+
+ if (changed)
+ *changed = true;
+
+ return 0;
+ }
+
+ decrypted = l_malloc(elen - 16 + 1);
+
+ ad[0].iov_base = (void *)salt;
+ ad[0].iov_len = slen;
+ ad[1].iov_base = (void *)ssid;
+ ad[1].iov_len = strlen(ssid);
+
+ if (!aes_siv_decrypt(system_key, sizeof(system_key), encrypted, elen,
+ ad, 2, decrypted)) {
+ l_error("Could not decrypt %s profile, did the secret change?",
+ ssid);
+ return -ENOKEY;
+ }
+
+ decrypted[elen - 16] = '\0';
+
+ /*
+ * Remove any groups that are marked as encrypted (plus embedded),
+ * and copy the decrypted groups back into settings.
+ */
+ groups = l_settings_get_groups(settings);
+ for (i = groups; *i; i++) {
+ if (encrypt_group(*i))
+ l_settings_remove_group(settings, *i);
+ }
+
+ l_settings_remove_embedded_groups(settings);
+
+ /*
+ * Load decrypted data into existing settings. This is not how the API
+ * is indended to be used (since this could result in duplicate groups)
+ * but since the Security group was just removed and EncryptedSecurity
+ * should only contain a Security group its safe to use it this way.
+ */
+ if (!l_settings_load_from_data(settings, (const char *) decrypted,
+ elen - 16)) {
+ l_error("Could not load decrypted security group");
+ return -EBADMSG;
+ }
+
+done:
+ if (changed)
+ *changed = false;
+
+ return 0;
+}
+
+/*
+ * Direct open() by path. 'name' is used for decryption and depends on the
+ * module calling. For regular storage operations 'name' is the SSID. For
+ * hotspot 'name' is the consortium name (since the SSID is not always the
+ * same).
+ */
+bool __storage_open(struct l_settings *settings, const char *path,
+ enum security type, const char *name)
+{
+ bool changed;
+ _auto_(l_free) char *encrypted = NULL;
+ size_t elen;
+
+ if (type == SECURITY_NONE)
+ return true;
+
+ if (__storage_decrypt(settings, name, &changed) < 0)
+ return false;
+
+ if (!changed)
+ return true;
+
+ /* Profile never encrypted before. Encrypt and write to disk */
+ encrypted = __storage_encrypt(settings, name, &elen);
+ if (!encrypted) {
+ l_error("Could not encrypt new profile %s", name);
+ return false;
+ }
+
+ if (write_file(encrypted, elen, false, "%s", path) < 0) {
+ l_error("Failed to write out encrypted profile");
+ return false;
+ }
+
+ l_debug("Encrypted a new profile %s", path);
+
+ return true;
+}
+
struct l_settings *storage_network_open(enum security type, const char *ssid)
{
struct l_settings *settings;
- char *path;
+ _auto_(l_free) char *path = NULL;
if (ssid == NULL)
return NULL;
path = storage_get_network_file_path(type, ssid);
+
settings = l_settings_new();
- if (!l_settings_load_from_file(settings, path)) {
- l_settings_free(settings);
- settings = NULL;
- }
+ if (!l_settings_load_from_file(settings, path))
+ goto error;
+
+ if (!__storage_open(settings, path, type, ssid))
+ goto error;
- l_free(path);
return settings;
+
+error:
+ l_settings_free(settings);
+ return NULL;
}
int storage_network_touch(enum security type, const char *ssid)
@@ -388,15 +622,19 @@ int storage_network_touch(enum security type, const char *ssid)
void storage_network_sync(enum security type, const char *ssid,
struct l_settings *settings)
{
- char *data;
+ _auto_(l_free) char *data = NULL;
+ _auto_(l_free) char *path = NULL;
size_t length = 0;
- char *path;
path = storage_get_network_file_path(type, ssid);
- data = l_settings_to_data(settings, &length);
+ data = __storage_encrypt(settings, ssid, &length);
+
+ if (!data) {
+ l_error("Unable to sync profile %s", ssid);
+ return;
+ }
+
write_file(data, length, true, "%s", path);
- l_free(data);
- l_free(path);
}
int storage_network_remove(enum security type, const char *ssid)
@@ -463,3 +701,22 @@ bool storage_is_file(const char *filename)
return false;
}
+
+/*
+ * Initialize a systemd encryption key for encrypting/decrypting credentials.
+ * This uses the 'extract and expand' concept from RFC 5869 to derive a new,
+ * fixed length, key.
+ */
+void __storage_set_encryption_key(const uint8_t *key, size_t key_len)
+{
+ uint8_t tmp[32];
+
+ if (!hkdf_extract(L_CHECKSUM_SHA256, NULL, 0, 1, tmp, key, key_len))
+ return;
+
+ if (!hkdf_expand(L_CHECKSUM_SHA256, tmp, sizeof(tmp), "IWD System Key",
+ system_key, sizeof(system_key)))
+ return;
+
+ system_key_set = true;
+}
diff --git a/src/storage.h b/src/storage.h
index e1ec2cd4..14d81f0a 100644
--- a/src/storage.h
+++ b/src/storage.h
@@ -50,3 +50,11 @@ int storage_network_remove(enum security type, const char *ssid);
struct l_settings *storage_known_frequencies_load(void);
void storage_known_frequencies_sync(struct l_settings *known_freqs);
+
+void __storage_set_encryption_key(const uint8_t *key, size_t key_len);
+int __storage_decrypt(struct l_settings *settings, const char *ssid,
+ bool *changed);
+char *__storage_encrypt(const struct l_settings *settings, const char *ssid,
+ size_t *out_len);
+bool __storage_open(struct l_settings *settings, const char *path,
+ enum security type, const char *name);
--
2.31.1
6 months, 2 weeks
[PATCH v4 1/7] storage: implement network profile encryption
by James Prestwood
Some users don't like the idea of storing network credentials in plaintext
on the file system. As far as IWD goes the credential directory permissions
should be set up such that nobody but the owner/root have access but
nevertheless they are still plaintext.
For added security these credentials can be encrypted using a secret key.
In this patch the origination of the key does not matter, but in this
patch series IWD will support a systemd provided key.
The encryption itself will operate on the entire [Security] group as well
as all embedded groups. Once encrypted the [Security] group will be replaced
with two key/values:
EncryptedSalt - A random string of bytes used for the encryption
EncryptedSecurity - A string of bytes containing the encrypted [Security]
group, as well as all embedded groups.
After the profile has been encrypted these values should not be modified.
Note that any values added to [Security] after encryption has no effect.
Once the profile is encrypted there is no way to modify [Security] without
manually decrypting first, or just removing it entirely which effectively
treated a 'new' profile.
The encryption/decryption is done using AES-SIV with a salt value and the
network SSID as the IV.
Once a key is set any profiles opened will automatically be encrypted and
re-written to disk. Modules using network_storage_open will be provided
the decrypted profile, and will be unaware it was ever encrypted in the
first place. Similarly when network_storage_sync is called the profile
will by automatically encrypted and written to disk without the caller
needing to do anything special.
A few private storage.c helpers were added to serve several purposes:
__storage_set_encryption_key():
This sets the encryption key direct from systemd then uses extract and expand
to create a new fixed length key to do encryption/decryption.
__storage_decrypt():
Low level API to decrypt an l_settings object using a previously set key and
the SSID/name for the network. This returns a 'changed' out parameter signifying
that the settings need to be encrypted and re-written to disk. The purpose of
exposing this is for a standalone decryption tool which does not re-write any
settings.
__storage_open():
Wrapper around __storage_decrypt() that handles re-writing a new profile to
disk. This was exposed in order to support hotspot profiles.
__storage_encrypt():
Encrypts an l_settings object and returns the full profile as data
---
Makefile.am | 3 +-
src/storage.c | 279 ++++++++++++++++++++++++++++++++++++++++++++++++--
src/storage.h | 8 ++
3 files changed, 278 insertions(+), 12 deletions(-)
v4:
* Made settings const in __storage_encrypt
* Remove all encryptable settings before loading decrypted ones
diff --git a/Makefile.am b/Makefile.am
index 89c053a6..35938d22 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -381,7 +381,8 @@ tools_hwsim_SOURCES = tools/hwsim.c src/mpdu.h \
src/nl80211util.h src/nl80211util.c \
src/storage.h src/storage.c \
src/common.h src/common.c \
- src/band.h src/band.c
+ src/band.h src/band.c \
+ src/crypto.h src/crypto.c
tools_hwsim_LDADD = $(ell_ldadd)
if DBUS_POLICY
diff --git a/src/storage.c b/src/storage.c
index 4b89c615..f5c89604 100644
--- a/src/storage.c
+++ b/src/storage.c
@@ -41,9 +41,11 @@
#include <sys/stat.h>
#include <ell/ell.h>
+#include "ell/useful.h"
#include "src/common.h"
#include "src/storage.h"
+#include "src/crypto.h"
#define STORAGE_DIR_MODE (S_IRUSR | S_IWUSR | S_IXUSR)
#define STORAGE_FILE_MODE (S_IRUSR | S_IWUSR)
@@ -52,6 +54,8 @@
static char *storage_path = NULL;
static char *storage_hotspot_path = NULL;
+static uint8_t system_key[32];
+static bool system_key_set = false;
static int create_dirs(const char *filename)
{
@@ -347,24 +351,254 @@ const char *storage_network_ssid_from_path(const char *path,
return buf;
}
+/* Groups requiring encryption (if enabled) */
+static char *encrypt_groups[] = {
+ "Security",
+ NULL
+};
+
+static bool encrypt_group(const char *group)
+{
+ char **g;
+
+ for (g = encrypt_groups; *g; g++) {
+ if (!strcmp(*g, group))
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Encrypt needed groups of 'settings' without modifying the object. Returns
+ * the entire settings object as data, with encrypted groups as a bytestring
+ * set as the value to [Security].EncryptedSecurity. This also includes any
+ * embedded groups.
+ *
+ * Note: If encryption is not enabled or there is no Security group this is
+ * effectively l_settings_to_data.
+ */
+char *__storage_encrypt(const struct l_settings *settings, const char *ssid,
+ size_t *out_len)
+{
+ struct iovec ad[2];
+ uint8_t salt[32];
+ size_t len;
+ _auto_(l_settings_free) struct l_settings *to_encrypt = NULL;
+ _auto_(l_settings_free) struct l_settings *original = NULL;
+ _auto_(l_free) char *plaintext = NULL;
+ _auto_(l_free) uint8_t *enc = NULL;
+ _auto_(l_strv_free) char **groups = NULL;
+ char **i;
+
+ if (!system_key_set || !l_settings_has_group(settings, "Security"))
+ return l_settings_to_data(settings, out_len);
+
+ /*
+ * Make two copies of the settings: One will contain only data to be
+ * encrypted (to_encrypt), the other will contain data to be left
+ * unencrypted (original). At the end any encrypted data will be set
+ * into 'original' as EncryptedSecurity.
+ */
+ to_encrypt = l_settings_clone(settings);
+ original = l_settings_clone(settings);
+
+ groups = l_settings_get_groups(to_encrypt);
+ for (i = groups; *i; i++) {
+ if (encrypt_group(*i))
+ l_settings_remove_group(original, *i);
+ else
+ l_settings_remove_group(to_encrypt, *i);
+ }
+
+ l_settings_remove_embedded_groups(original);
+
+ plaintext = l_settings_to_data(to_encrypt, &len);
+ if (!plaintext)
+ return NULL;
+
+ l_getrandom(salt, 32);
+
+ ad[0].iov_base = (void *) salt;
+ ad[0].iov_len = 32;
+ ad[1].iov_base = (void *) ssid;
+ ad[1].iov_len = strlen(ssid);
+
+ enc = l_malloc(len + 16);
+
+ if (!aes_siv_encrypt(system_key, sizeof(system_key), plaintext, len,
+ ad, 2, enc)) {
+ l_error("Could not encrypt [Security] group");
+ return NULL;
+ }
+
+ l_settings_set_bytes(original, "Security", "EncryptedSalt", salt, 32);
+ l_settings_set_bytes(original, "Security", "EncryptedSecurity",
+ enc, len + 16);
+
+ return l_settings_to_data(original, out_len);
+}
+
+/*
+ * Decrypt data in [Security].EncryptedSecurity. This data also includes
+ * embedded groups potentially. Once decrypted the data is put back into the
+ * object.
+ *
+ * Note: if encryption is not enabled or there is no Security group settings
+ * is not modified.
+ */
+int __storage_decrypt(struct l_settings *settings, const char *ssid,
+ bool *changed)
+{
+ _auto_(l_settings_free) struct l_settings *security = NULL;
+ _auto_(l_free) uint8_t *encrypted = NULL;
+ _auto_(l_free) uint8_t *decrypted = NULL;
+ _auto_(l_free) uint8_t *salt = NULL;
+ _auto_(l_strv_free) char **embedded = NULL;
+ _auto_(l_strv_free) char **groups = NULL;
+ char **i;
+ size_t elen, slen;
+ struct iovec ad[2];
+
+ if (!system_key_set)
+ goto done;
+
+ if (!l_settings_has_group(settings, "Security"))
+ goto done;
+
+ encrypted = l_settings_get_bytes(settings, "Security",
+ "EncryptedSecurity", &elen);
+ salt = l_settings_get_bytes(settings, "Security",
+ "EncryptedSalt", &slen);
+
+ /*
+ * Either profile has never been loaded after enabling encryption or is
+ * missing Encrypted{Salt,Security} values. If either are missing this
+ * profile is corrupted and must be fixed.
+ */
+ if (!(encrypted && salt)) {
+ /* Profile corrupted */
+ if (encrypted || salt) {
+ l_warn("Profile %s is corrupted reconfigure manually",
+ ssid);
+ return -EBADMSG;
+ }
+
+ if (changed)
+ *changed = true;
+
+ return 0;
+ }
+
+ decrypted = l_malloc(elen - 16 + 1);
+
+ ad[0].iov_base = (void *)salt;
+ ad[0].iov_len = slen;
+ ad[1].iov_base = (void *)ssid;
+ ad[1].iov_len = strlen(ssid);
+
+ if (!aes_siv_decrypt(system_key, sizeof(system_key), encrypted, elen,
+ ad, 2, decrypted)) {
+ l_error("Could not decrypt %s profile, did the secret change?",
+ ssid);
+ return -ENOKEY;
+ }
+
+ decrypted[elen - 16] = '\0';
+
+ /*
+ * Remove any groups that are marked as encrypted (plus embedded),
+ * and copy the decrypted groups back into settings.
+ */
+ groups = l_settings_get_groups(settings);
+ for (i = groups; *i; i++) {
+ if (encrypt_group(*i))
+ l_settings_remove_group(settings, *i);
+ }
+
+ l_settings_remove_embedded_groups(settings);
+
+ /*
+ * Load decrypted data into existing settings. This is not how the API
+ * is indended to be used (since this could result in duplicate groups)
+ * but since the Security group was just removed and EncryptedSecurity
+ * should only contain a Security group its safe to use it this way.
+ */
+ if (!l_settings_load_from_data(settings, (const char *) decrypted,
+ elen - 16)) {
+ l_error("Could not load decrypted security group");
+ return -EBADMSG;
+ }
+
+done:
+ if (changed)
+ *changed = false;
+
+ return 0;
+}
+
+/*
+ * Direct open() by path. 'name' is used for decryption and depends on the
+ * module calling. For regular storage operations 'name' is the SSID. For
+ * hotspot 'name' is the consortium name (since the SSID is not always the
+ * same).
+ */
+bool __storage_open(struct l_settings *settings, const char *path,
+ enum security type, const char *name)
+{
+ bool changed;
+ _auto_(l_free) char *encrypted = NULL;
+ size_t elen;
+
+ if (type == SECURITY_NONE)
+ return true;
+
+ if (__storage_decrypt(settings, name, &changed) < 0)
+ return false;
+
+ if (!changed)
+ return true;
+
+ /* Profile never encrypted before. Encrypt and write to disk */
+ encrypted = __storage_encrypt(settings, name, &elen);
+ if (!encrypted) {
+ l_error("Could not encrypt new profile %s", name);
+ return false;
+ }
+
+ if (write_file(encrypted, elen, false, "%s", path) < 0) {
+ l_error("Failed to write out encrypted profile");
+ return false;
+ }
+
+ l_debug("Encrypted a new profile %s", path);
+
+ return true;
+}
+
struct l_settings *storage_network_open(enum security type, const char *ssid)
{
struct l_settings *settings;
- char *path;
+ _auto_(l_free) char *path = NULL;
if (ssid == NULL)
return NULL;
path = storage_get_network_file_path(type, ssid);
+
settings = l_settings_new();
- if (!l_settings_load_from_file(settings, path)) {
- l_settings_free(settings);
- settings = NULL;
- }
+ if (!l_settings_load_from_file(settings, path))
+ goto error;
+
+ if (!__storage_open(settings, path, type, ssid))
+ goto error;
- l_free(path);
return settings;
+
+error:
+ l_settings_free(settings);
+ return NULL;
}
int storage_network_touch(enum security type, const char *ssid)
@@ -388,15 +622,19 @@ int storage_network_touch(enum security type, const char *ssid)
void storage_network_sync(enum security type, const char *ssid,
struct l_settings *settings)
{
- char *data;
+ _auto_(l_free) char *data = NULL;
+ _auto_(l_free) char *path = NULL;
size_t length = 0;
- char *path;
path = storage_get_network_file_path(type, ssid);
- data = l_settings_to_data(settings, &length);
+ data = __storage_encrypt(settings, ssid, &length);
+
+ if (!data) {
+ l_error("Unable to sync profile %s", ssid);
+ return;
+ }
+
write_file(data, length, true, "%s", path);
- l_free(data);
- l_free(path);
}
int storage_network_remove(enum security type, const char *ssid)
@@ -463,3 +701,22 @@ bool storage_is_file(const char *filename)
return false;
}
+
+/*
+ * Initialize a systemd encryption key for encrypting/decrypting credentials.
+ * This uses the 'extract and expand' concept from RFC 5869 to derive a new,
+ * fixed length, key.
+ */
+void __storage_set_encryption_key(const uint8_t *key, size_t key_len)
+{
+ uint8_t tmp[32];
+
+ if (!hkdf_extract(L_CHECKSUM_SHA256, NULL, 0, 1, tmp, key, key_len))
+ return;
+
+ if (!hkdf_expand(L_CHECKSUM_SHA256, tmp, sizeof(tmp), "IWD System Key",
+ system_key, sizeof(system_key)))
+ return;
+
+ system_key_set = true;
+}
diff --git a/src/storage.h b/src/storage.h
index e1ec2cd4..14d81f0a 100644
--- a/src/storage.h
+++ b/src/storage.h
@@ -50,3 +50,11 @@ int storage_network_remove(enum security type, const char *ssid);
struct l_settings *storage_known_frequencies_load(void);
void storage_known_frequencies_sync(struct l_settings *known_freqs);
+
+void __storage_set_encryption_key(const uint8_t *key, size_t key_len);
+int __storage_decrypt(struct l_settings *settings, const char *ssid,
+ bool *changed);
+char *__storage_encrypt(const struct l_settings *settings, const char *ssid,
+ size_t *out_len);
+bool __storage_open(struct l_settings *settings, const char *path,
+ enum security type, const char *name);
--
2.31.1
6 months, 2 weeks
iwd 1.21 stuck on connecting state
by 54d22547-afc0-4701-b1fc-5a2a2e411646@simplelogin.co
Hi,
I've recently started noticing on my laptop that iwd gets stuck on 'connecting' state when I boot a **second time** right after poweroff. The fix is quite easy. Just disconnect and connect again from iwctl.
I recall having a similar issue not long ago. Maybe a regression was introduced. For the record, router and connection settings are the same as before.
Arch Linux with kernel 5.16-zen1.
Thanks!
6 months, 2 weeks
[PATCH] auto-t: only wait for wpa_s to get the DPP configuration
by James Prestwood
As far as wpa_supplicant goes we don't need to test if it can actually
connect to the network, just that it receives the DPP configuration.
---
autotests/testDPP/connection_test.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/autotests/testDPP/connection_test.py b/autotests/testDPP/connection_test.py
index 38d44b25..a451d584 100644
--- a/autotests/testDPP/connection_test.py
+++ b/autotests/testDPP/connection_test.py
@@ -80,7 +80,7 @@ class Test(unittest.TestCase):
self.wpas.dpp_enrollee_start(uri)
- self.hapd.wait_for_event('AP-STA-CONNECTED 42:00:00:00:00:00')
+ self.wpas.wait_for_event('DPP-CONF-RECEIVED')
def setUp(self):
self.wd = IWD(True)
--
2.31.1
6 months, 2 weeks