summary refs log tree commit diff
path: root/src/core/supplicant/nm-supplicant-config.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/supplicant/nm-supplicant-config.c')
-rw-r--r--src/core/supplicant/nm-supplicant-config.c1698
1 files changed, 1698 insertions, 0 deletions
diff --git a/src/core/supplicant/nm-supplicant-config.c b/src/core/supplicant/nm-supplicant-config.c
new file mode 100644
index 00000000..eab494b0
--- /dev/null
+++ b/src/core/supplicant/nm-supplicant-config.c
@@ -0,0 +1,1698 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2006 - 2012 Red Hat, Inc.
+ * Copyright (C) 2007 - 2008 Novell, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-supplicant-config.h"
+
+#include <stdlib.h>
+
+#include "nm-glib-aux/nm-str-buf.h"
+#include "nm-core-internal.h"
+#include "nm-supplicant-settings-verify.h"
+#include "nm-setting.h"
+#include "nm-libnm-core-intern/nm-auth-subject.h"
+#include "NetworkManagerUtils.h"
+#include "nm-utils.h"
+#include "nm-setting-ip4-config.h"
+
+typedef struct {
+    char *         value;
+    guint32        len;
+    NMSupplOptType type;
+} ConfigOption;
+
+/*****************************************************************************/
+
+typedef struct {
+    GHashTable *   config;
+    GHashTable *   blobs;
+    NMSupplCapMask capabilities;
+    guint32        ap_scan;
+    bool           fast_required : 1;
+    bool           dispose_has_run : 1;
+    bool           ap_isolation : 1;
+} NMSupplicantConfigPrivate;
+
+struct _NMSupplicantConfig {
+    GObject                   parent;
+    NMSupplicantConfigPrivate _priv;
+};
+
+struct _NMSupplicantConfigClass {
+    GObjectClass parent;
+};
+
+G_DEFINE_TYPE(NMSupplicantConfig, nm_supplicant_config, G_TYPE_OBJECT)
+
+#define NM_SUPPLICANT_CONFIG_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMSupplicantConfig, NM_IS_SUPPLICANT_CONFIG)
+
+/*****************************************************************************/
+
+static gboolean
+_get_capability(NMSupplicantConfigPrivate *priv, NMSupplCapType type)
+{
+    return NM_SUPPL_CAP_MASK_GET(priv->capabilities, type) == NM_TERNARY_TRUE;
+}
+
+NMSupplicantConfig *
+nm_supplicant_config_new(NMSupplCapMask capabilities)
+{
+    NMSupplicantConfigPrivate *priv;
+    NMSupplicantConfig *       self;
+
+    self = g_object_new(NM_TYPE_SUPPLICANT_CONFIG, NULL);
+    priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
+
+    priv->capabilities = capabilities;
+
+    return self;
+}
+
+static void
+config_option_free(ConfigOption *opt)
+{
+    g_free(opt->value);
+    g_slice_free(ConfigOption, opt);
+}
+
+static void
+nm_supplicant_config_init(NMSupplicantConfig *self)
+{
+    NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
+
+    priv->config = g_hash_table_new_full(nm_str_hash,
+                                         g_str_equal,
+                                         g_free,
+                                         (GDestroyNotify) config_option_free);
+
+    priv->ap_scan         = 1;
+    priv->dispose_has_run = FALSE;
+}
+
+static gboolean
+nm_supplicant_config_add_option_with_type(NMSupplicantConfig *self,
+                                          const char *        key,
+                                          const char *        value,
+                                          gint32              len,
+                                          NMSupplOptType      opt_type,
+                                          const char *        display_value,
+                                          GError **           error)
+{
+    NMSupplicantConfigPrivate *priv;
+    ConfigOption *             old_opt;
+    ConfigOption *             opt;
+    NMSupplOptType             type;
+
+    g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE);
+    g_return_val_if_fail(key != NULL, FALSE);
+    g_return_val_if_fail(value != NULL, FALSE);
+    nm_assert(!error || !*error);
+
+    priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
+
+    if (len < 0)
+        len = strlen(value);
+
+    if (opt_type != NM_SUPPL_OPT_TYPE_INVALID)
+        type = opt_type;
+    else {
+        type = nm_supplicant_settings_verify_setting(key, value, len);
+        if (type == NM_SUPPL_OPT_TYPE_INVALID) {
+            gs_free char *str_free = NULL;
+            const char *  str;
+
+            str = nm_utils_buf_utf8safe_escape(value,
+                                               len,
+                                               NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
+                                               &str_free);
+
+            str = nm_strquote_a(255, str);
+
+            g_set_error(error,
+                        NM_SUPPLICANT_ERROR,
+                        NM_SUPPLICANT_ERROR_CONFIG,
+                        "key '%s' and/or value %s invalid",
+                        key,
+                        display_value ?: str);
+            return FALSE;
+        }
+    }
+
+    old_opt = (ConfigOption *) g_hash_table_lookup(priv->config, key);
+    if (old_opt) {
+        g_set_error(error,
+                    NM_SUPPLICANT_ERROR,
+                    NM_SUPPLICANT_ERROR_CONFIG,
+                    "key '%s' already configured",
+                    key);
+        return FALSE;
+    }
+
+    opt        = g_slice_new0(ConfigOption);
+    opt->value = g_malloc(len + 1);
+    memcpy(opt->value, value, len);
+    opt->value[len] = '\0';
+
+    opt->len  = len;
+    opt->type = type;
+
+    {
+        char buf[255];
+        memset(&buf[0], 0, sizeof(buf));
+        memcpy(&buf[0], opt->value, opt->len > 254 ? 254 : opt->len);
+        nm_log_info(LOGD_SUPPLICANT,
+                    "Config: added '%s' value '%s'",
+                    key,
+                    display_value ?: &buf[0]);
+    }
+
+    g_hash_table_insert(priv->config, g_strdup(key), opt);
+
+    return TRUE;
+}
+
+static gboolean
+nm_supplicant_config_add_option(NMSupplicantConfig *self,
+                                const char *        key,
+                                const char *        value,
+                                gint32              len,
+                                const char *        display_value,
+                                GError **           error)
+{
+    return nm_supplicant_config_add_option_with_type(self,
+                                                     key,
+                                                     value,
+                                                     len,
+                                                     NM_SUPPL_OPT_TYPE_INVALID,
+                                                     display_value,
+                                                     error);
+}
+
+static gboolean
+nm_supplicant_config_add_blob(NMSupplicantConfig *self,
+                              const char *        key,
+                              GBytes *            value,
+                              const char *        blobid,
+                              GError **           error)
+{
+    NMSupplicantConfigPrivate *priv;
+    ConfigOption *             old_opt;
+    ConfigOption *             opt;
+    NMSupplOptType             type;
+    const guint8 *             data;
+    gsize                      data_len;
+
+    g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE);
+    g_return_val_if_fail(key != NULL, FALSE);
+    g_return_val_if_fail(value != NULL, FALSE);
+    g_return_val_if_fail(blobid != NULL, FALSE);
+
+    data = g_bytes_get_data(value, &data_len);
+    g_return_val_if_fail(data_len > 0, FALSE);
+
+    priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
+
+    type = nm_supplicant_settings_verify_setting(key, (const char *) data, data_len);
+    if (type == NM_SUPPL_OPT_TYPE_INVALID) {
+        g_set_error(error,
+                    NM_SUPPLICANT_ERROR,
+                    NM_SUPPLICANT_ERROR_CONFIG,
+                    "key '%s' and/or its contained value is invalid",
+                    key);
+        return FALSE;
+    }
+
+    old_opt = (ConfigOption *) g_hash_table_lookup(priv->config, key);
+    if (old_opt) {
+        g_set_error(error,
+                    NM_SUPPLICANT_ERROR,
+                    NM_SUPPLICANT_ERROR_CONFIG,
+                    "key '%s' already configured",
+                    key);
+        return FALSE;
+    }
+
+    opt        = g_slice_new0(ConfigOption);
+    opt->value = g_strdup_printf("blob://%s", blobid);
+    opt->len   = strlen(opt->value);
+    opt->type  = type;
+
+    nm_log_info(LOGD_SUPPLICANT, "Config: added '%s' value '%s'", key, opt->value);
+
+    g_hash_table_insert(priv->config, g_strdup(key), opt);
+    if (!priv->blobs) {
+        priv->blobs =
+            g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, (GDestroyNotify) g_bytes_unref);
+    }
+    g_hash_table_insert(priv->blobs, g_strdup(blobid), g_bytes_ref(value));
+
+    return TRUE;
+}
+
+static gboolean
+nm_supplicant_config_add_blob_for_connection(NMSupplicantConfig *self,
+                                             GBytes *            field,
+                                             const char *        name,
+                                             const char *        con_uid,
+                                             GError **           error)
+{
+    if (field && g_bytes_get_size(field)) {
+        gs_free char *uid = NULL;
+        char *        p;
+
+        uid = g_strdup_printf("%s-%s", con_uid, name);
+        for (p = uid; *p; p++) {
+            if (*p == '/')
+                *p = '-';
+        }
+        if (!nm_supplicant_config_add_blob(self, name, field, uid, error))
+            return FALSE;
+    }
+    return TRUE;
+}
+
+static void
+nm_supplicant_config_finalize(GObject *object)
+{
+    NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(object);
+
+    g_hash_table_destroy(priv->config);
+    nm_clear_pointer(&priv->blobs, g_hash_table_destroy);
+
+    G_OBJECT_CLASS(nm_supplicant_config_parent_class)->finalize(object);
+}
+
+static void
+nm_supplicant_config_class_init(NMSupplicantConfigClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+    object_class->finalize = nm_supplicant_config_finalize;
+}
+
+guint32
+nm_supplicant_config_get_ap_scan(NMSupplicantConfig *self)
+{
+    g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), 1);
+
+    return NM_SUPPLICANT_CONFIG_GET_PRIVATE(self)->ap_scan;
+}
+
+gboolean
+nm_supplicant_config_fast_required(NMSupplicantConfig *self)
+{
+    g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE);
+
+    return NM_SUPPLICANT_CONFIG_GET_PRIVATE(self)->fast_required;
+}
+
+GVariant *
+nm_supplicant_config_to_variant(NMSupplicantConfig *self)
+{
+    NMSupplicantConfigPrivate *priv;
+    GVariantBuilder            builder;
+    GHashTableIter             iter;
+    ConfigOption *             option;
+    const char *               key;
+
+    g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), NULL);
+
+    priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
+
+    g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
+
+    g_hash_table_iter_init(&iter, priv->config);
+    while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &option)) {
+        switch (option->type) {
+        case NM_SUPPL_OPT_TYPE_INT:
+            g_variant_builder_add(&builder, "{sv}", key, g_variant_new_int32(atoi(option->value)));
+            break;
+        case NM_SUPPL_OPT_TYPE_BYTES:
+        case NM_SUPPL_OPT_TYPE_UTF8:
+            g_variant_builder_add(
+                &builder,
+                "{sv}",
+                key,
+                g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, option->value, option->len, 1));
+            break;
+        case NM_SUPPL_OPT_TYPE_KEYWORD:
+        case NM_SUPPL_OPT_TYPE_STRING:
+            g_variant_builder_add(&builder, "{sv}", key, g_variant_new_string(option->value));
+            break;
+        default:
+            break;
+        }
+    }
+
+    return g_variant_builder_end(&builder);
+}
+
+GHashTable *
+nm_supplicant_config_get_blobs(NMSupplicantConfig *self)
+{
+    g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), NULL);
+
+    return NM_SUPPLICANT_CONFIG_GET_PRIVATE(self)->blobs;
+}
+
+static const char *
+wifi_freqs_to_string(gboolean bg_band)
+{
+    static const char *str_2ghz = NULL;
+    static const char *str_5ghz = NULL;
+    const char **      f_p;
+    const char *       f;
+
+    f_p = bg_band ? &str_2ghz : &str_5ghz;
+
+again:
+    f = g_atomic_pointer_get(f_p);
+
+    if (G_UNLIKELY(!f)) {
+        nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT(400, FALSE);
+        const guint *            freqs;
+        int                      i;
+
+        freqs = bg_band ? nm_utils_wifi_2ghz_freqs() : nm_utils_wifi_5ghz_freqs();
+        for (i = 0; freqs[i]; i++) {
+            if (i > 0)
+                nm_str_buf_append_c(&strbuf, ' ');
+            nm_str_buf_append_printf(&strbuf, "%u", freqs[i]);
+        }
+
+        f = g_strdup(nm_str_buf_get_str(&strbuf));
+
+        if (!g_atomic_pointer_compare_and_exchange(f_p, NULL, f)) {
+            g_free((char *) f);
+            goto again;
+        }
+    }
+
+    return f;
+}
+
+gboolean
+nm_supplicant_config_add_setting_macsec(NMSupplicantConfig *self,
+                                        NMSettingMacsec *   setting,
+                                        GError **           error)
+{
+    const char *value;
+    char        buf[32];
+    int         port;
+
+    g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE);
+    g_return_val_if_fail(setting != NULL, FALSE);
+    g_return_val_if_fail(!error || !*error, FALSE);
+
+    if (!nm_supplicant_config_add_option(self, "macsec_policy", "1", -1, NULL, error))
+        return FALSE;
+
+    value = nm_setting_macsec_get_encrypt(setting) ? "0" : "1";
+    if (!nm_supplicant_config_add_option(self, "macsec_integ_only", value, -1, NULL, error))
+        return FALSE;
+
+    port = nm_setting_macsec_get_port(setting);
+    if (port > 0 && port < 65534) {
+        snprintf(buf, sizeof(buf), "%d", port);
+        if (!nm_supplicant_config_add_option(self, "macsec_port", buf, -1, NULL, error))
+            return FALSE;
+    }
+
+    if (nm_setting_macsec_get_mode(setting) == NM_SETTING_MACSEC_MODE_PSK) {
+        guint8 buffer_cak[NM_SETTING_MACSEC_MKA_CAK_LENGTH / 2];
+        guint8 buffer_ckn[NM_SETTING_MACSEC_MKA_CKN_LENGTH / 2];
+
+        if (!nm_supplicant_config_add_option(self, "key_mgmt", "NONE", -1, NULL, error))
+            return FALSE;
+
+        value = nm_setting_macsec_get_mka_cak(setting);
+        if (!value || !nm_utils_hexstr2bin_buf(value, FALSE, FALSE, NULL, buffer_cak)) {
+            g_set_error_literal(error,
+                                NM_SUPPLICANT_ERROR,
+                                NM_SUPPLICANT_ERROR_CONFIG,
+                                value ? "invalid MKA CAK" : "missing MKA CAK");
+            return FALSE;
+        }
+        if (!nm_supplicant_config_add_option(self,
+                                             "mka_cak",
+                                             (char *) buffer_cak,
+                                             sizeof(buffer_cak),
+                                             "<hidden>",
+                                             error))
+            return FALSE;
+
+        value = nm_setting_macsec_get_mka_ckn(setting);
+        if (!value || !nm_utils_hexstr2bin_buf(value, FALSE, FALSE, NULL, buffer_ckn)) {
+            g_set_error_literal(error,
+                                NM_SUPPLICANT_ERROR,
+                                NM_SUPPLICANT_ERROR_CONFIG,
+                                value ? "invalid MKA CKN" : "missing MKA CKN");
+            return FALSE;
+        }
+        if (!nm_supplicant_config_add_option(self,
+                                             "mka_ckn",
+                                             (char *) buffer_ckn,
+                                             sizeof(buffer_ckn),
+                                             value,
+                                             error))
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+gboolean
+nm_supplicant_config_add_setting_wireless(NMSupplicantConfig *self,
+                                          NMSettingWireless * setting,
+                                          guint32             fixed_freq,
+                                          GError **           error)
+{
+    NMSupplicantConfigPrivate *priv;
+    gboolean                   is_adhoc, is_ap, is_mesh;
+    const char *               mode, *band;
+    guint32                    channel;
+    GBytes *                   ssid;
+    const char *               bssid;
+
+    g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE);
+    g_return_val_if_fail(setting != NULL, FALSE);
+    g_return_val_if_fail(!error || !*error, FALSE);
+
+    priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
+
+    mode     = nm_setting_wireless_get_mode(setting);
+    is_adhoc = nm_streq0(mode, "adhoc");
+    is_ap    = nm_streq0(mode, "ap");
+    is_mesh  = nm_streq0(mode, "mesh");
+    if (is_adhoc || is_ap)
+        priv->ap_scan = 2;
+    else
+        priv->ap_scan = 1;
+
+    ssid = nm_setting_wireless_get_ssid(setting);
+    if (!nm_supplicant_config_add_option(self,
+                                         "ssid",
+                                         (char *) g_bytes_get_data(ssid, NULL),
+                                         g_bytes_get_size(ssid),
+                                         NULL,
+                                         error))
+        return FALSE;
+
+    if (is_adhoc) {
+        if (!nm_supplicant_config_add_option(self, "mode", "1", -1, NULL, error))
+            return FALSE;
+    }
+
+    if (is_ap) {
+        if (!nm_supplicant_config_add_option(self, "mode", "2", -1, NULL, error))
+            return FALSE;
+
+        if (nm_setting_wireless_get_hidden(setting)
+            && !nm_supplicant_config_add_option(self,
+                                                "ignore_broadcast_ssid",
+                                                "1",
+                                                -1,
+                                                NULL,
+                                                error))
+            return FALSE;
+    }
+
+    if (is_mesh) {
+        if (!nm_supplicant_config_add_option(self, "mode", "5", -1, NULL, error))
+            return FALSE;
+    }
+
+    if ((is_adhoc || is_ap || is_mesh) && fixed_freq) {
+        gs_free char *str_freq = NULL;
+
+        str_freq = g_strdup_printf("%u", fixed_freq);
+        if (!nm_supplicant_config_add_option(self, "frequency", str_freq, -1, NULL, error))
+            return FALSE;
+    }
+
+    /* Except for Ad-Hoc, Hotspot and Mesh, request that the driver probe for the
+     * specific SSID we want to associate with.
+     */
+    if (!(is_adhoc || is_ap || is_mesh)) {
+        if (!nm_supplicant_config_add_option(self, "scan_ssid", "1", -1, NULL, error))
+            return FALSE;
+    }
+
+    bssid = nm_setting_wireless_get_bssid(setting);
+    if (bssid) {
+        if (!nm_supplicant_config_add_option(self, "bssid", bssid, strlen(bssid), NULL, error))
+            return FALSE;
+    }
+
+    band    = nm_setting_wireless_get_band(setting);
+    channel = nm_setting_wireless_get_channel(setting);
+    if (band) {
+        if (channel) {
+            guint32       freq;
+            gs_free char *str_freq = NULL;
+
+            freq     = nm_utils_wifi_channel_to_freq(channel, band);
+            str_freq = g_strdup_printf("%u", freq);
+            if (!nm_supplicant_config_add_option(self, "freq_list", str_freq, -1, NULL, error))
+                return FALSE;
+        } else {
+            const char *freqs = NULL;
+
+            if (nm_streq(band, "a"))
+                freqs = wifi_freqs_to_string(FALSE);
+            else if (nm_streq(band, "bg"))
+                freqs = wifi_freqs_to_string(TRUE);
+
+            if (freqs
+                && !nm_supplicant_config_add_option(self,
+                                                    "freq_list",
+                                                    freqs,
+                                                    strlen(freqs),
+                                                    NULL,
+                                                    error))
+                return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+gboolean
+nm_supplicant_config_add_bgscan(NMSupplicantConfig *self, NMConnection *connection, GError **error)
+{
+    NMSettingWireless *        s_wifi;
+    NMSettingWirelessSecurity *s_wsec;
+    const char *               bgscan;
+
+    s_wifi = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wifi);
+
+    /* Don't scan when a shared connection (either AP or Ad-Hoc) is active;
+     * it will disrupt connected clients.
+     */
+    if (NM_IN_STRSET(nm_setting_wireless_get_mode(s_wifi),
+                     NM_SETTING_WIRELESS_MODE_AP,
+                     NM_SETTING_WIRELESS_MODE_ADHOC))
+        return TRUE;
+
+    /* Don't scan when the connection is locked to a specific AP, since
+     * intra-ESS roaming (which requires periodic scanning) isn't being
+     * used due to the specific AP lock. (bgo #513820)
+     */
+    if (nm_setting_wireless_get_bssid(s_wifi))
+        return TRUE;
+
+    /* Default to a very long bgscan interval when signal is OK on the assumption
+     * that either (a) there aren't multiple APs and we don't need roaming, or
+     * (b) since EAP/802.1x isn't used and thus there are fewer steps to fail
+     * during a roam, we can wait longer before scanning for roam candidates.
+     */
+    bgscan = "simple:30:-70:86400";
+
+    /* If using WPA Enterprise, Dynamic WEP or we have seen more than one AP use
+     * a shorter bgscan interval on the assumption that this is a multi-AP ESS
+     * in which we want more reliable roaming between APs. Thus trigger scans
+     * when the signal is still somewhat OK so we have an up-to-date roam
+     * candidate list when the signal gets bad.
+     */
+    if (nm_setting_wireless_get_num_seen_bssids(s_wifi) > 1
+        || ((s_wsec = nm_connection_get_setting_wireless_security(connection))
+            && NM_IN_STRSET(nm_setting_wireless_security_get_key_mgmt(s_wsec),
+                            "ieee8021x",
+                            "wpa-eap",
+                            "wpa-eap-suite-b-192")))
+        bgscan = "simple:30:-65:300";
+
+    return nm_supplicant_config_add_option(self, "bgscan", bgscan, -1, FALSE, error);
+}
+
+static gboolean
+add_string_val(NMSupplicantConfig *self,
+               const char *        field,
+               const char *        name,
+               gboolean            ucase,
+               const char *        display_value,
+               GError **           error)
+{
+    if (field) {
+        gs_free char *value = NULL;
+
+        if (ucase) {
+            value = g_ascii_strup(field, -1);
+            field = value;
+        }
+        return nm_supplicant_config_add_option(self,
+                                               name,
+                                               field,
+                                               strlen(field),
+                                               display_value,
+                                               error);
+    }
+    return TRUE;
+}
+
+#define ADD_STRING_LIST_VAL(self,                                                         \
+                            setting,                                                      \
+                            setting_name,                                                 \
+                            field,                                                        \
+                            field_plural,                                                 \
+                            name,                                                         \
+                            separator,                                                    \
+                            ucase,                                                        \
+                            display_value,                                                \
+                            error)                                                        \
+    ({                                                                                    \
+        typeof(*(setting)) *_setting = (setting);                                         \
+        gboolean            _success = TRUE;                                              \
+                                                                                          \
+        if (nm_setting_##setting_name##_get_num_##field_plural(_setting)) {               \
+            const char _separator = (separator);                                          \
+            GString *  _str       = g_string_new(NULL);                                   \
+            guint      _k, _n;                                                            \
+                                                                                          \
+            _n = nm_setting_##setting_name##_get_num_##field_plural(_setting);            \
+            for (_k = 0; _k < _n; _k++) {                                                 \
+                const char *item = nm_setting_##setting_name##_get_##field(_setting, _k); \
+                                                                                          \
+                if (!_str->len) {                                                         \
+                    g_string_append(_str, item);                                          \
+                } else {                                                                  \
+                    g_string_append_c(_str, _separator);                                  \
+                    g_string_append(_str, item);                                          \
+                }                                                                         \
+            }                                                                             \
+            if ((ucase))                                                                  \
+                g_string_ascii_up(_str);                                                  \
+            if (_str->len) {                                                              \
+                if (!nm_supplicant_config_add_option((self),                              \
+                                                     (name),                              \
+                                                     _str->str,                           \
+                                                     -1,                                  \
+                                                     (display_value),                     \
+                                                     (error)))                            \
+                    _success = FALSE;                                                     \
+            }                                                                             \
+            g_string_free(_str, TRUE);                                                    \
+        }                                                                                 \
+        _success;                                                                         \
+    })
+
+static void
+wep128_passphrase_hash(const char *input, gsize input_len, guint8 *digest /* 13 bytes */)
+{
+    nm_auto_free_checksum GChecksum *sum = NULL;
+    guint8                           md5[NM_UTILS_CHECKSUM_LENGTH_MD5];
+    guint8                           data[64];
+    int                              i;
+
+    nm_assert(input);
+    nm_assert(input_len);
+    nm_assert(digest);
+
+    /* Get at least 64 bytes by repeating the passphrase into the buffer */
+    for (i = 0; i < sizeof(data); i++)
+        data[i] = input[i % input_len];
+
+    sum = g_checksum_new(G_CHECKSUM_MD5);
+    g_checksum_update(sum, data, sizeof(data));
+    nm_utils_checksum_get_digest(sum, md5);
+
+    /* WEP104 keys are 13 bytes in length (26 hex characters) */
+    memcpy(digest, md5, 13);
+}
+
+static gboolean
+add_wep_key(NMSupplicantConfig *self,
+            const char *        key,
+            const char *        name,
+            NMWepKeyType        wep_type,
+            GError **           error)
+{
+    gsize key_len;
+
+    if (!key || (key_len = strlen(key)) == 0)
+        return TRUE;
+
+    if (wep_type == NM_WEP_KEY_TYPE_UNKNOWN) {
+        if (nm_utils_wep_key_valid(key, NM_WEP_KEY_TYPE_KEY))
+            wep_type = NM_WEP_KEY_TYPE_KEY;
+        else if (nm_utils_wep_key_valid(key, NM_WEP_KEY_TYPE_PASSPHRASE))
+            wep_type = NM_WEP_KEY_TYPE_PASSPHRASE;
+    }
+
+    if ((wep_type == NM_WEP_KEY_TYPE_UNKNOWN) || (wep_type == NM_WEP_KEY_TYPE_KEY)) {
+        if ((key_len == 10) || (key_len == 26)) {
+            guint8 buffer[26 / 2];
+
+            if (!nm_utils_hexstr2bin_full(key,
+                                          FALSE,
+                                          FALSE,
+                                          FALSE,
+                                          NULL,
+                                          key_len / 2,
+                                          buffer,
+                                          sizeof(buffer),
+                                          NULL)) {
+                g_set_error(error,
+                            NM_SUPPLICANT_ERROR,
+                            NM_SUPPLICANT_ERROR_CONFIG,
+                            "cannot add wep-key %s to supplicant config because key is not hex",
+                            name);
+                return FALSE;
+            }
+            if (!nm_supplicant_config_add_option(self,
+                                                 name,
+                                                 (char *) buffer,
+                                                 key_len / 2,
+                                                 "<hidden>",
+                                                 error))
+                return FALSE;
+        } else if ((key_len == 5) || (key_len == 13)) {
+            if (!nm_supplicant_config_add_option(self, name, key, key_len, "<hidden>", error))
+                return FALSE;
+        } else {
+            g_set_error(
+                error,
+                NM_SUPPLICANT_ERROR,
+                NM_SUPPLICANT_ERROR_CONFIG,
+                "Cannot add wep-key %s to supplicant config because key-length %u is invalid",
+                name,
+                (guint) key_len);
+            return FALSE;
+        }
+    } else if (wep_type == NM_WEP_KEY_TYPE_PASSPHRASE) {
+        guint8 digest[13];
+
+        wep128_passphrase_hash(key, key_len, digest);
+        if (!nm_supplicant_config_add_option(self,
+                                             name,
+                                             (const char *) digest,
+                                             sizeof(digest),
+                                             "<hidden>",
+                                             error))
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+gboolean
+nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig *          self,
+                                                   NMSettingWirelessSecurity *   setting,
+                                                   NMSetting8021x *              setting_8021x,
+                                                   const char *                  con_uuid,
+                                                   guint32                       mtu,
+                                                   NMSettingWirelessSecurityPmf  pmf,
+                                                   NMSettingWirelessSecurityFils fils,
+                                                   GError **                     error)
+{
+    NMSupplicantConfigPrivate *priv             = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
+    nm_auto_free_gstring GString *key_mgmt_conf = NULL;
+    const char *                  key_mgmt, *auth_alg;
+    const char *                  psk;
+    gboolean                      set_pmf;
+
+    g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE);
+    g_return_val_if_fail(setting != NULL, FALSE);
+    g_return_val_if_fail(con_uuid != NULL, FALSE);
+    g_return_val_if_fail(!error || !*error, FALSE);
+
+    /* Check if we actually support FILS */
+    if (!_get_capability(priv, NM_SUPPL_CAP_TYPE_FILS)) {
+        if (fils == NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED) {
+            g_set_error_literal(error,
+                                NM_SUPPLICANT_ERROR,
+                                NM_SUPPLICANT_ERROR_CONFIG,
+                                "Supplicant does not support FILS");
+            return FALSE;
+        } else if (fils == NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL)
+            fils = NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE;
+    }
+
+    key_mgmt      = nm_setting_wireless_security_get_key_mgmt(setting);
+    key_mgmt_conf = g_string_new(key_mgmt);
+    if (nm_streq(key_mgmt, "wpa-psk")) {
+        if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF))
+            g_string_append(key_mgmt_conf, " wpa-psk-sha256");
+        if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FT))
+            g_string_append(key_mgmt_conf, " ft-psk");
+    } else if (nm_streq(key_mgmt, "wpa-eap")) {
+        if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF)) {
+            g_string_append(key_mgmt_conf, " wpa-eap-sha256");
+
+            if (_get_capability(priv, NM_SUPPL_CAP_TYPE_SUITEB192)
+                && pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED)
+                g_string_append(key_mgmt_conf, " wpa-eap-suite-b-192");
+        }
+        if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FT))
+            g_string_append(key_mgmt_conf, " ft-eap");
+        if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FT)
+            && _get_capability(priv, NM_SUPPL_CAP_TYPE_SHA384))
+            g_string_append(key_mgmt_conf, " ft-eap-sha384");
+        switch (fils) {
+        case NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED:
+            g_string_truncate(key_mgmt_conf, 0);
+            if (!_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF))
+                g_string_assign(key_mgmt_conf, "fils-sha256 fils-sha384");
+            /* fall-through */
+        case NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL:
+            if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF))
+                g_string_append(key_mgmt_conf, " fils-sha256 fils-sha384");
+            if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF)
+                && _get_capability(priv, NM_SUPPL_CAP_TYPE_FT))
+                g_string_append(key_mgmt_conf, " ft-fils-sha256");
+            if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF)
+                && _get_capability(priv, NM_SUPPL_CAP_TYPE_FT)
+                && _get_capability(priv, NM_SUPPL_CAP_TYPE_SHA384))
+                g_string_append(key_mgmt_conf, " ft-fils-sha384");
+            break;
+        default:
+            break;
+        }
+    } else if (nm_streq(key_mgmt, "sae")) {
+        if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FT))
+            g_string_append(key_mgmt_conf, " ft-sae");
+    } else if (nm_streq(key_mgmt, "wpa-eap-suite-b-192")) {
+        pmf = NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED;
+        if (!nm_supplicant_config_add_option(self, "pairwise", "GCMP-256", -1, NULL, error)
+            || !nm_supplicant_config_add_option(self, "group", "GCMP-256", -1, NULL, error))
+            return FALSE;
+    }
+
+    if (!add_string_val(self, key_mgmt_conf->str, "key_mgmt", TRUE, NULL, error))
+        return FALSE;
+
+    auth_alg = nm_setting_wireless_security_get_auth_alg(setting);
+    if (!add_string_val(self, auth_alg, "auth_alg", TRUE, NULL, error))
+        return FALSE;
+
+    psk = nm_setting_wireless_security_get_psk(setting);
+    if (psk) {
+        size_t psk_len = strlen(psk);
+
+        if (psk_len >= 8 && psk_len <= 63) {
+            /* Use NM_SUPPL_OPT_TYPE_STRING here so that it gets pushed to the
+             * supplicant as a string, and therefore gets quoted,
+             * and therefore the supplicant will interpret it as a
+             * passphrase and not a hex key.
+             */
+            if (!nm_supplicant_config_add_option_with_type(self,
+                                                           "psk",
+                                                           psk,
+                                                           -1,
+                                                           NM_SUPPL_OPT_TYPE_STRING,
+                                                           "<hidden>",
+                                                           error))
+                return FALSE;
+        } else if (nm_streq(key_mgmt, "sae")) {
+            /* If the SAE password doesn't comply with WPA-PSK limitation,
+             * we need to call it "sae_password" instead of "psk".
+             */
+            if (!nm_supplicant_config_add_option_with_type(self,
+                                                           "sae_password",
+                                                           psk,
+                                                           -1,
+                                                           NM_SUPPL_OPT_TYPE_STRING,
+                                                           "<hidden>",
+                                                           error))
+                return FALSE;
+        } else if (psk_len == 64) {
+            guint8 buffer[32];
+
+            /* Hex PSK */
+            if (!nm_utils_hexstr2bin_buf(psk, FALSE, FALSE, NULL, buffer)) {
+                g_set_error(error,
+                            NM_SUPPLICANT_ERROR,
+                            NM_SUPPLICANT_ERROR_CONFIG,
+                            "Cannot add psk to supplicant config due to invalid hex");
+                return FALSE;
+            }
+            if (!nm_supplicant_config_add_option(self,
+                                                 "psk",
+                                                 (char *) buffer,
+                                                 sizeof(buffer),
+                                                 "<hidden>",
+                                                 error))
+                return FALSE;
+        } else {
+            g_set_error(error,
+                        NM_SUPPLICANT_ERROR,
+                        NM_SUPPLICANT_ERROR_CONFIG,
+                        "Cannot add psk to supplicant config due to invalid PSK length %u (not "
+                        "between 8 and 63 characters)",
+                        (guint) psk_len);
+            return FALSE;
+        }
+    }
+
+    /* Don't try to enable PMF on non-WPA/SAE/OWE networks */
+    if (!NM_IN_STRSET(key_mgmt, "wpa-eap", "wpa-eap-suite-b-192", "wpa-psk", "sae", "owe"))
+        pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
+
+    /* Check if we actually support PMF */
+    set_pmf = TRUE;
+    if (!_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF)) {
+        if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) {
+            g_set_error_literal(error,
+                                NM_SUPPLICANT_ERROR,
+                                NM_SUPPLICANT_ERROR_CONFIG,
+                                "Supplicant does not support PMF");
+            return FALSE;
+        }
+        set_pmf = FALSE;
+    }
+
+    /* Only WPA-specific things when using WPA */
+    if (NM_IN_STRSET(key_mgmt, "wpa-psk", "wpa-eap", "sae", "owe")) {
+        if (!ADD_STRING_LIST_VAL(self,
+                                 setting,
+                                 wireless_security,
+                                 proto,
+                                 protos,
+                                 "proto",
+                                 ' ',
+                                 TRUE,
+                                 NULL,
+                                 error))
+            return FALSE;
+        if (!ADD_STRING_LIST_VAL(self,
+                                 setting,
+                                 wireless_security,
+                                 pairwise,
+                                 pairwise,
+                                 "pairwise",
+                                 ' ',
+                                 TRUE,
+                                 NULL,
+                                 error))
+            return FALSE;
+        if (!ADD_STRING_LIST_VAL(self,
+                                 setting,
+                                 wireless_security,
+                                 group,
+                                 groups,
+                                 "group",
+                                 ' ',
+                                 TRUE,
+                                 NULL,
+                                 error))
+            return FALSE;
+
+        if (set_pmf
+            && NM_IN_SET(pmf,
+                         NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE,
+                         NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED)) {
+            if (!nm_supplicant_config_add_option(
+                    self,
+                    "ieee80211w",
+                    pmf == NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE ? "0" : "2",
+                    -1,
+                    NULL,
+                    error))
+                return FALSE;
+        }
+    }
+
+    /* WEP keys if required */
+    if (nm_streq(key_mgmt, "none")) {
+        NMWepKeyType wep_type = nm_setting_wireless_security_get_wep_key_type(setting);
+        const char * wep0     = nm_setting_wireless_security_get_wep_key(setting, 0);
+        const char * wep1     = nm_setting_wireless_security_get_wep_key(setting, 1);
+        const char * wep2     = nm_setting_wireless_security_get_wep_key(setting, 2);
+        const char * wep3     = nm_setting_wireless_security_get_wep_key(setting, 3);
+
+        if (!add_wep_key(self, wep0, "wep_key0", wep_type, error))
+            return FALSE;
+        if (!add_wep_key(self, wep1, "wep_key1", wep_type, error))
+            return FALSE;
+        if (!add_wep_key(self, wep2, "wep_key2", wep_type, error))
+            return FALSE;
+        if (!add_wep_key(self, wep3, "wep_key3", wep_type, error))
+            return FALSE;
+
+        if (wep0 || wep1 || wep2 || wep3) {
+            gs_free char *value = NULL;
+
+            value = g_strdup_printf("%d", nm_setting_wireless_security_get_wep_tx_keyidx(setting));
+            if (!nm_supplicant_config_add_option(self, "wep_tx_keyidx", value, -1, NULL, error))
+                return FALSE;
+        }
+    }
+
+    if (nm_streq0(auth_alg, "leap")) {
+        /* LEAP */
+        if (nm_streq(key_mgmt, "ieee8021x")) {
+            const char *tmp;
+
+            tmp = nm_setting_wireless_security_get_leap_username(setting);
+            if (!add_string_val(self, tmp, "identity", FALSE, NULL, error))
+                return FALSE;
+
+            tmp = nm_setting_wireless_security_get_leap_password(setting);
+            if (!add_string_val(self, tmp, "password", FALSE, "<hidden>", error))
+                return FALSE;
+
+            if (!add_string_val(self, "leap", "eap", TRUE, NULL, error))
+                return FALSE;
+        } else {
+            g_set_error(error,
+                        NM_SUPPLICANT_ERROR,
+                        NM_SUPPLICANT_ERROR_CONFIG,
+                        "Invalid key-mgmt \"%s\" for leap",
+                        key_mgmt);
+            return FALSE;
+        }
+    } else {
+        /* 802.1x for Dynamic WEP and WPA-Enterprise */
+        if (NM_IN_STRSET(key_mgmt, "ieee8021x", "wpa-eap", "wpa-eap-suite-b-192")) {
+            if (!setting_8021x) {
+                g_set_error(error,
+                            NM_SUPPLICANT_ERROR,
+                            NM_SUPPLICANT_ERROR_CONFIG,
+                            "Cannot set key-mgmt %s with missing 8021x setting",
+                            key_mgmt);
+                return FALSE;
+            }
+            if (!nm_supplicant_config_add_setting_8021x(self,
+                                                        setting_8021x,
+                                                        con_uuid,
+                                                        mtu,
+                                                        FALSE,
+                                                        error))
+                return FALSE;
+        }
+
+        if (NM_IN_STRSET(key_mgmt, "wpa-eap", "wpa-eap-suite-b-192")) {
+            /* When using WPA-Enterprise, we want to use Proactive Key Caching (also
+             * called Opportunistic Key Caching) to avoid full EAP exchanges when
+             * roaming between access points in the same mobility group.
+             */
+            if (!nm_supplicant_config_add_option(self,
+                                                 "proactive_key_caching",
+                                                 "1",
+                                                 -1,
+                                                 NULL,
+                                                 error))
+                return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static gboolean
+add_pkcs11_uri_with_pin(NMSupplicantConfig *       self,
+                        const char *               name,
+                        const char *               uri,
+                        const char *               pin,
+                        const NMSettingSecretFlags pin_flags,
+                        GError **                  error)
+{
+    gs_strfreev char **split     = NULL;
+    gs_free char *     tmp       = NULL;
+    gs_free char *     tmp_log   = NULL;
+    gs_free char *     pin_qattr = NULL;
+    char *             escaped   = NULL;
+
+    if (uri == NULL)
+        return TRUE;
+
+    /* We ignore the attributes -- RFC 7512 suggests that some of them
+     * might be unsafe and we want to be on the safe side. Also, we're
+     * installing our attributes, so this makes things a bit easier for us. */
+    split = g_strsplit(uri, "&", 2);
+    if (split[1])
+        nm_log_info(LOGD_SUPPLICANT, "URI attributes ignored");
+
+    /* Fill in the PIN if required. */
+    if (pin) {
+        escaped   = g_uri_escape_string(pin, NULL, TRUE);
+        pin_qattr = g_strdup_printf("pin-value=%s", escaped);
+        g_free(escaped);
+    } else if (!(pin_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) {
+        /* Include an empty PIN to indicate the login is still needed.
+         * Probably a token that has a PIN path and the actual PIN will
+         * be entered using a protected path. */
+        pin_qattr = g_strdup("pin-value=");
+    }
+
+    tmp = g_strdup_printf("%s%s%s", split[0], (pin_qattr ? "?" : ""), (pin_qattr ?: ""));
+
+    tmp_log = g_strdup_printf("%s%s%s",
+                              split[0],
+                              (pin_qattr ? "?" : ""),
+                              (pin_qattr ? "pin-value=<hidden>" : ""));
+
+    return add_string_val(self, tmp, name, FALSE, tmp_log, error);
+}
+
+gboolean
+nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
+                                       NMSetting8021x *    setting,
+                                       const char *        con_uuid,
+                                       guint32             mtu,
+                                       gboolean            wired,
+                                       GError **           error)
+{
+    NMSupplicantConfigPrivate *priv;
+    char *                     tmp;
+    const char *               peapver, *value, *path;
+    gboolean                   added;
+    GString *                  phase1, *phase2;
+    GBytes *                   bytes;
+    gboolean                   fast = FALSE;
+    guint32                    i, num_eap;
+    gboolean                   fast_provisoning_allowed = FALSE;
+    const char *               ca_path_override = NULL, *ca_cert_override = NULL;
+    guint32                    frag, hdrs;
+    gs_free char *             frag_str = NULL;
+    NMSetting8021xAuthFlags    phase1_auth_flags;
+    nm_auto_free_gstring GString *eap_str = NULL;
+
+    g_return_val_if_fail(NM_IS_SUPPLICANT_CONFIG(self), FALSE);
+    g_return_val_if_fail(setting != NULL, FALSE);
+    g_return_val_if_fail(con_uuid != NULL, FALSE);
+
+    priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
+
+    value = nm_setting_802_1x_get_password(setting);
+    if (value) {
+        if (!add_string_val(self, value, "password", FALSE, "<hidden>", error))
+            return FALSE;
+    } else {
+        bytes = nm_setting_802_1x_get_password_raw(setting);
+        if (bytes) {
+            if (!nm_supplicant_config_add_option(self,
+                                                 "password",
+                                                 (const char *) g_bytes_get_data(bytes, NULL),
+                                                 g_bytes_get_size(bytes),
+                                                 "<hidden>",
+                                                 error))
+                return FALSE;
+        }
+    }
+    value = nm_setting_802_1x_get_pin(setting);
+    if (!add_string_val(self, value, "pin", FALSE, "<hidden>", error))
+        return FALSE;
+
+    if (wired) {
+        if (!add_string_val(self, "IEEE8021X", "key_mgmt", FALSE, NULL, error))
+            return FALSE;
+        /* Wired 802.1x must always use eapol_flags=0 */
+        if (!add_string_val(self, "0", "eapol_flags", FALSE, NULL, error))
+            return FALSE;
+        priv->ap_scan = 0;
+    }
+
+    /* Build the "eap" option string while we check for EAP methods needing
+     * special handling: PEAP + GTC, FAST, external */
+    eap_str = g_string_new(NULL);
+    num_eap = nm_setting_802_1x_get_num_eap_methods(setting);
+    for (i = 0; i < num_eap; i++) {
+        const char *method = nm_setting_802_1x_get_eap_method(setting, i);
+
+        if (nm_streq(method, "fast")) {
+            fast                = TRUE;
+            priv->fast_required = TRUE;
+        }
+
+        if (nm_streq(method, "external")) {
+            if (num_eap == 1) {
+                g_set_error(error,
+                            NM_SUPPLICANT_ERROR,
+                            NM_SUPPLICANT_ERROR_CONFIG,
+                            "Connection settings managed externally to NM, connection"
+                            " cannot be used with wpa_supplicant");
+                return FALSE;
+            }
+            continue;
+        }
+
+        if (eap_str->len)
+            g_string_append_c(eap_str, ' ');
+        g_string_append(eap_str, method);
+    }
+
+    g_string_ascii_up(eap_str);
+    if (eap_str->len
+        && !nm_supplicant_config_add_option(self, "eap", eap_str->str, -1, NULL, error))
+        return FALSE;
+
+    /* Adjust the fragment size according to MTU, but do not set it higher than 1280-14
+     * for better compatibility */
+    hdrs = 14; /* EAPOL + EAP-TLS */
+    frag = 1280 - hdrs;
+    if (mtu > hdrs)
+        frag = CLAMP(mtu - hdrs, 100, frag);
+    frag_str = g_strdup_printf("%u", frag);
+
+    if (!nm_supplicant_config_add_option(self, "fragment_size", frag_str, -1, NULL, error))
+        return FALSE;
+
+    phase1  = g_string_new(NULL);
+    peapver = nm_setting_802_1x_get_phase1_peapver(setting);
+    if (peapver) {
+        if (nm_streq(peapver, "0"))
+            g_string_append(phase1, "peapver=0");
+        else if (nm_streq(peapver, "1"))
+            g_string_append(phase1, "peapver=1");
+    }
+
+    if (nm_setting_802_1x_get_phase1_peaplabel(setting)) {
+        if (phase1->len)
+            g_string_append_c(phase1, ' ');
+        g_string_append_printf(phase1,
+                               "peaplabel=%s",
+                               nm_setting_802_1x_get_phase1_peaplabel(setting));
+    }
+
+    value = nm_setting_802_1x_get_phase1_fast_provisioning(setting);
+    if (value) {
+        if (phase1->len)
+            g_string_append_c(phase1, ' ');
+        g_string_append_printf(phase1, "fast_provisioning=%s", value);
+
+        if (!nm_streq(value, "0"))
+            fast_provisoning_allowed = TRUE;
+    }
+
+    phase1_auth_flags = nm_setting_802_1x_get_phase1_auth_flags(setting);
+    if (NM_FLAGS_HAS(phase1_auth_flags, NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_0_DISABLE))
+        g_string_append_printf(phase1, "%stls_disable_tlsv1_0=1", (phase1->len ? " " : ""));
+    if (NM_FLAGS_HAS(phase1_auth_flags, NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_1_DISABLE))
+        g_string_append_printf(phase1, "%stls_disable_tlsv1_1=1", (phase1->len ? " " : ""));
+    if (NM_FLAGS_HAS(phase1_auth_flags, NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_2_DISABLE))
+        g_string_append_printf(phase1, "%stls_disable_tlsv1_2=1", (phase1->len ? " " : ""));
+
+    if (phase1->len) {
+        if (!add_string_val(self, phase1->str, "phase1", FALSE, NULL, error)) {
+            g_string_free(phase1, TRUE);
+            return FALSE;
+        }
+    }
+    g_string_free(phase1, TRUE);
+
+    phase2 = g_string_new(NULL);
+    if (nm_setting_802_1x_get_phase2_auth(setting) && !fast_provisoning_allowed) {
+        tmp = g_ascii_strup(nm_setting_802_1x_get_phase2_auth(setting), -1);
+        g_string_append_printf(phase2, "auth=%s", tmp);
+        g_free(tmp);
+    }
+
+    if (nm_setting_802_1x_get_phase2_autheap(setting)) {
+        if (phase2->len)
+            g_string_append_c(phase2, ' ');
+        tmp = g_ascii_strup(nm_setting_802_1x_get_phase2_autheap(setting), -1);
+        g_string_append_printf(phase2, "autheap=%s", tmp);
+        g_free(tmp);
+    }
+
+    if (phase2->len) {
+        if (!add_string_val(self, phase2->str, "phase2", FALSE, NULL, error)) {
+            g_string_free(phase2, TRUE);
+            return FALSE;
+        }
+    }
+    g_string_free(phase2, TRUE);
+
+    /* PAC file */
+    path = nm_setting_802_1x_get_pac_file(setting);
+    if (path) {
+        if (!add_string_val(self, path, "pac_file", FALSE, NULL, error))
+            return FALSE;
+    } else {
+        /* PAC file is not specified.
+         * If provisioning is allowed, use an blob format.
+         */
+        if (fast_provisoning_allowed) {
+            gs_free char *blob_name = NULL;
+
+            blob_name = g_strdup_printf("blob://pac-blob-%s", con_uuid);
+            if (!add_string_val(self, blob_name, "pac_file", FALSE, NULL, error))
+                return FALSE;
+        } else {
+            /* This is only error for EAP-FAST; don't disturb other methods. */
+            if (fast) {
+                g_set_error(error,
+                            NM_SUPPLICANT_ERROR,
+                            NM_SUPPLICANT_ERROR_CONFIG,
+                            "EAP-FAST error: no PAC file provided and "
+                            "automatic PAC provisioning is disabled");
+                return FALSE;
+            }
+        }
+    }
+
+    /* If user wants to use system CA certs, either populate ca_path (if the path
+     * is a directory) or ca_cert (the path is a file name) */
+    if (nm_setting_802_1x_get_system_ca_certs(setting)) {
+        if (g_file_test(SYSTEM_CA_PATH, G_FILE_TEST_IS_DIR))
+            ca_path_override = SYSTEM_CA_PATH;
+        else
+            ca_cert_override = SYSTEM_CA_PATH;
+    }
+
+    /* CA path */
+    path = nm_setting_802_1x_get_ca_path(setting);
+    path = ca_path_override ?: path;
+    if (path) {
+        if (!add_string_val(self, path, "ca_path", FALSE, NULL, error))
+            return FALSE;
+    }
+
+    /* Phase2 CA path */
+    path = nm_setting_802_1x_get_phase2_ca_path(setting);
+    path = ca_path_override ?: path;
+    if (path) {
+        if (!add_string_val(self, path, "ca_path2", FALSE, NULL, error))
+            return FALSE;
+    }
+
+    /* CA certificate */
+    if (ca_cert_override) {
+        if (!add_string_val(self, ca_cert_override, "ca_cert", FALSE, NULL, error))
+            return FALSE;
+    } else {
+        switch (nm_setting_802_1x_get_ca_cert_scheme(setting)) {
+        case NM_SETTING_802_1X_CK_SCHEME_BLOB:
+            bytes = nm_setting_802_1x_get_ca_cert_blob(setting);
+            if (!nm_supplicant_config_add_blob_for_connection(self,
+                                                              bytes,
+                                                              "ca_cert",
+                                                              con_uuid,
+                                                              error))
+                return FALSE;
+            break;
+        case NM_SETTING_802_1X_CK_SCHEME_PATH:
+            path = nm_setting_802_1x_get_ca_cert_path(setting);
+            if (!add_string_val(self, path, "ca_cert", FALSE, NULL, error))
+                return FALSE;
+            break;
+        case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
+            if (!add_pkcs11_uri_with_pin(self,
+                                         "ca_cert",
+                                         nm_setting_802_1x_get_ca_cert_uri(setting),
+                                         nm_setting_802_1x_get_ca_cert_password(setting),
+                                         nm_setting_802_1x_get_ca_cert_password_flags(setting),
+                                         error)) {
+                return FALSE;
+            }
+            break;
+        default:
+            break;
+        }
+    }
+
+    /* Phase 2 CA certificate */
+    if (ca_cert_override) {
+        if (!add_string_val(self, ca_cert_override, "ca_cert2", FALSE, NULL, error))
+            return FALSE;
+    } else {
+        switch (nm_setting_802_1x_get_phase2_ca_cert_scheme(setting)) {
+        case NM_SETTING_802_1X_CK_SCHEME_BLOB:
+            bytes = nm_setting_802_1x_get_phase2_ca_cert_blob(setting);
+            if (!nm_supplicant_config_add_blob_for_connection(self,
+                                                              bytes,
+                                                              "ca_cert2",
+                                                              con_uuid,
+                                                              error))
+                return FALSE;
+            break;
+        case NM_SETTING_802_1X_CK_SCHEME_PATH:
+            path = nm_setting_802_1x_get_phase2_ca_cert_path(setting);
+            if (!add_string_val(self, path, "ca_cert2", FALSE, NULL, error))
+                return FALSE;
+            break;
+        case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
+            if (!add_pkcs11_uri_with_pin(
+                    self,
+                    "ca_cert2",
+                    nm_setting_802_1x_get_phase2_ca_cert_uri(setting),
+                    nm_setting_802_1x_get_phase2_ca_cert_password(setting),
+                    nm_setting_802_1x_get_phase2_ca_cert_password_flags(setting),
+                    error)) {
+                return FALSE;
+            }
+            break;
+        default:
+            break;
+        }
+    }
+
+    /* Subject match */
+    value = nm_setting_802_1x_get_subject_match(setting);
+    if (!add_string_val(self, value, "subject_match", FALSE, NULL, error))
+        return FALSE;
+    value = nm_setting_802_1x_get_phase2_subject_match(setting);
+    if (!add_string_val(self, value, "subject_match2", FALSE, NULL, error))
+        return FALSE;
+
+    /* altSubjectName match */
+    if (!ADD_STRING_LIST_VAL(self,
+                             setting,
+                             802_1x,
+                             altsubject_match,
+                             altsubject_matches,
+                             "altsubject_match",
+                             ';',
+                             FALSE,
+                             NULL,
+                             error))
+        return FALSE;
+    if (!ADD_STRING_LIST_VAL(self,
+                             setting,
+                             802_1x,
+                             phase2_altsubject_match,
+                             phase2_altsubject_matches,
+                             "altsubject_match2",
+                             ';',
+                             FALSE,
+                             NULL,
+                             error))
+        return FALSE;
+
+    /* Domain suffix match */
+    value = nm_setting_802_1x_get_domain_suffix_match(setting);
+    if (!add_string_val(self, value, "domain_suffix_match", FALSE, NULL, error))
+        return FALSE;
+    value = nm_setting_802_1x_get_phase2_domain_suffix_match(setting);
+    if (!add_string_val(self, value, "domain_suffix_match2", FALSE, NULL, error))
+        return FALSE;
+
+    /* domain match */
+    value = nm_setting_802_1x_get_domain_match(setting);
+    if (!add_string_val(self, value, "domain_match", FALSE, NULL, error))
+        return FALSE;
+    value = nm_setting_802_1x_get_phase2_domain_match(setting);
+    if (!add_string_val(self, value, "domain_match2", FALSE, NULL, error))
+        return FALSE;
+
+    /* Private key */
+    added = FALSE;
+    switch (nm_setting_802_1x_get_private_key_scheme(setting)) {
+    case NM_SETTING_802_1X_CK_SCHEME_BLOB:
+        bytes = nm_setting_802_1x_get_private_key_blob(setting);
+        if (!nm_supplicant_config_add_blob_for_connection(self,
+                                                          bytes,
+                                                          "private_key",
+                                                          con_uuid,
+                                                          error))
+            return FALSE;
+        added = TRUE;
+        break;
+    case NM_SETTING_802_1X_CK_SCHEME_PATH:
+        path = nm_setting_802_1x_get_private_key_path(setting);
+        if (!add_string_val(self, path, "private_key", FALSE, NULL, error))
+            return FALSE;
+        added = TRUE;
+        break;
+    case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
+        if (!add_pkcs11_uri_with_pin(self,
+                                     "private_key",
+                                     nm_setting_802_1x_get_private_key_uri(setting),
+                                     nm_setting_802_1x_get_private_key_password(setting),
+                                     nm_setting_802_1x_get_private_key_password_flags(setting),
+                                     error)) {
+            return FALSE;
+        }
+        added = TRUE;
+        break;
+    default:
+        break;
+    }
+
+    if (added) {
+        NMSetting8021xCKFormat format;
+        NMSetting8021xCKScheme scheme;
+
+        format = nm_setting_802_1x_get_private_key_format(setting);
+        scheme = nm_setting_802_1x_get_private_key_scheme(setting);
+
+        if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH
+            || format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
+            /* Only add the private key password for PKCS#12 blobs and
+             * all path schemes, since in both of these cases the private key
+             * isn't decrypted at all.
+             */
+            value = nm_setting_802_1x_get_private_key_password(setting);
+            if (!add_string_val(self, value, "private_key_passwd", FALSE, "<hidden>", error))
+                return FALSE;
+        }
+
+        if (format != NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
+            /* Only add the client cert if the private key is not PKCS#12, as
+             * wpa_supplicant configuration directs us to do.
+             */
+            switch (nm_setting_802_1x_get_client_cert_scheme(setting)) {
+            case NM_SETTING_802_1X_CK_SCHEME_BLOB:
+                bytes = nm_setting_802_1x_get_client_cert_blob(setting);
+                if (!nm_supplicant_config_add_blob_for_connection(self,
+                                                                  bytes,
+                                                                  "client_cert",
+                                                                  con_uuid,
+                                                                  error))
+                    return FALSE;
+                break;
+            case NM_SETTING_802_1X_CK_SCHEME_PATH:
+                path = nm_setting_802_1x_get_client_cert_path(setting);
+                if (!add_string_val(self, path, "client_cert", FALSE, NULL, error))
+                    return FALSE;
+                break;
+            case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
+                if (!add_pkcs11_uri_with_pin(
+                        self,
+                        "client_cert",
+                        nm_setting_802_1x_get_client_cert_uri(setting),
+                        nm_setting_802_1x_get_client_cert_password(setting),
+                        nm_setting_802_1x_get_client_cert_password_flags(setting),
+                        error)) {
+                    return FALSE;
+                }
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+    /* Phase 2 private key */
+    added = FALSE;
+    switch (nm_setting_802_1x_get_phase2_private_key_scheme(setting)) {
+    case NM_SETTING_802_1X_CK_SCHEME_BLOB:
+        bytes = nm_setting_802_1x_get_phase2_private_key_blob(setting);
+        if (!nm_supplicant_config_add_blob_for_connection(self,
+                                                          bytes,
+                                                          "private_key2",
+                                                          con_uuid,
+                                                          error))
+            return FALSE;
+        added = TRUE;
+        break;
+    case NM_SETTING_802_1X_CK_SCHEME_PATH:
+        path = nm_setting_802_1x_get_phase2_private_key_path(setting);
+        if (!add_string_val(self, path, "private_key2", FALSE, NULL, error))
+            return FALSE;
+        added = TRUE;
+        break;
+    case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
+        if (!add_pkcs11_uri_with_pin(
+                self,
+                "private_key2",
+                nm_setting_802_1x_get_phase2_private_key_uri(setting),
+                nm_setting_802_1x_get_phase2_private_key_password(setting),
+                nm_setting_802_1x_get_phase2_private_key_password_flags(setting),
+                error)) {
+            return FALSE;
+        }
+        added = TRUE;
+        break;
+    default:
+        break;
+    }
+
+    if (added) {
+        NMSetting8021xCKFormat format;
+        NMSetting8021xCKScheme scheme;
+
+        format = nm_setting_802_1x_get_phase2_private_key_format(setting);
+        scheme = nm_setting_802_1x_get_phase2_private_key_scheme(setting);
+
+        if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH
+            || format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
+            /* Only add the private key password for PKCS#12 blobs and
+             * all path schemes, since in both of these cases the private key
+             * isn't decrypted at all.
+             */
+            value = nm_setting_802_1x_get_phase2_private_key_password(setting);
+            if (!add_string_val(self, value, "private_key2_passwd", FALSE, "<hidden>", error))
+                return FALSE;
+        }
+
+        if (format != NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
+            /* Only add the client cert if the private key is not PKCS#12, as
+             * wpa_supplicant configuration directs us to do.
+             */
+            switch (nm_setting_802_1x_get_phase2_client_cert_scheme(setting)) {
+            case NM_SETTING_802_1X_CK_SCHEME_BLOB:
+                bytes = nm_setting_802_1x_get_phase2_client_cert_blob(setting);
+                if (!nm_supplicant_config_add_blob_for_connection(self,
+                                                                  bytes,
+                                                                  "client_cert2",
+                                                                  con_uuid,
+                                                                  error))
+                    return FALSE;
+                break;
+            case NM_SETTING_802_1X_CK_SCHEME_PATH:
+                path = nm_setting_802_1x_get_phase2_client_cert_path(setting);
+                if (!add_string_val(self, path, "client_cert2", FALSE, NULL, error))
+                    return FALSE;
+                break;
+            case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
+                if (!add_pkcs11_uri_with_pin(
+                        self,
+                        "client_cert2",
+                        nm_setting_802_1x_get_phase2_client_cert_uri(setting),
+                        nm_setting_802_1x_get_phase2_client_cert_password(setting),
+                        nm_setting_802_1x_get_phase2_client_cert_password_flags(setting),
+                        error)) {
+                    return FALSE;
+                }
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+    value = nm_setting_802_1x_get_identity(setting);
+    if (!add_string_val(self, value, "identity", FALSE, NULL, error))
+        return FALSE;
+    value = nm_setting_802_1x_get_anonymous_identity(setting);
+    if (!add_string_val(self, value, "anonymous_identity", FALSE, NULL, error))
+        return FALSE;
+
+    return TRUE;
+}
+
+gboolean
+nm_supplicant_config_add_no_security(NMSupplicantConfig *self, GError **error)
+{
+    return nm_supplicant_config_add_option(self, "key_mgmt", "NONE", -1, NULL, error);
+}
+
+gboolean
+nm_supplicant_config_get_ap_isolation(NMSupplicantConfig *self)
+{
+    return self->_priv.ap_isolation;
+}
+
+void
+nm_supplicant_config_set_ap_isolation(NMSupplicantConfig *self, gboolean ap_isolation)
+{
+    self->_priv.ap_isolation = ap_isolation;
+}