// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2006 - 2012 Red Hat, Inc.
 * Copyright (C) 2007 - 2008 Novell, Inc.
 */

#include "nm-default.h"

#include "nm-supplicant-config.h"

#include <stdlib.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;
} 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 *hidden,
                                           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,
			             hidden ?: 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, hidden ?: &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 *hidden,
                                 GError **error)
{
	return nm_supplicant_config_add_option_with_type (self, key, value, len, NM_SUPPL_OPT_TYPE_INVALID, hidden, 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 *str;

	str = bg_band ? str_2ghz : str_5ghz;

	if (G_UNLIKELY (str == NULL)) {
		GString *tmp;
		const guint *freqs;
		int i;

		freqs = bg_band ? nm_utils_wifi_2ghz_freqs () : nm_utils_wifi_5ghz_freqs ();
		tmp = g_string_sized_new (bg_band ? 70 : 225);
		for (i = 0; freqs[i]; i++)
			g_string_append_printf (tmp, i == 0 ? "%d" : " %d", freqs[i]);
		str = g_string_free (tmp, FALSE);
		if (bg_band)
			str_2ghz = str;
		else
			str_5ghz = str;
	}
	return str;
}

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),
		                                      NULL,
		                                      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 = (mode && !strcmp (mode, "adhoc")) ? TRUE : FALSE;
	is_ap = (mode && !strcmp (mode, "ap")) ? TRUE : FALSE;
	is_mesh = (mode && !strcmp (mode, "mesh")) ? TRUE : FALSE;
	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 (!strcmp (band, "a"))
				freqs = wifi_freqs_to_string (FALSE);
			else if (!strcmp (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")))
		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 *hidden,
                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), hidden, error);
	}
	return TRUE;
}

#define ADD_STRING_LIST_VAL(self, setting, setting_name, field, field_plural, name, separator, ucase, hidden, 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, (hidden), (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,
			                               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 suplicant 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 suplicant 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_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");
	}

	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-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 (   !strcmp (key_mgmt, "wpa-psk")
	    || !strcmp (key_mgmt, "wpa-eap")
	    || !strcmp (key_mgmt, "sae")
	    || !strcmp (key_mgmt, "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 (!strcmp (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 (auth_alg && !strcmp (auth_alg, "leap")) {
		/* LEAP */
		if (!strcmp (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 (!strcmp (key_mgmt, "ieee8021x") || !strcmp (key_mgmt, "wpa-eap")) {
			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 (!strcmp (key_mgmt, "wpa-eap")) {
			/* 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 (!strcmp (peapver, "0"))
			g_string_append (phase1, "peapver=0");
		else if (!strcmp (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 (strcmp (value, "0") != 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);
}

