summary refs log tree commit diff
path: root/src/supplicant
diff options
context:
space:
mode:
Diffstat (limited to 'src/supplicant')
-rw-r--r--src/supplicant/nm-supplicant-config.c1246
-rw-r--r--src/supplicant/nm-supplicant-config.h79
-rw-r--r--src/supplicant/nm-supplicant-interface.c1681
-rw-r--r--src/supplicant/nm-supplicant-interface.h122
-rw-r--r--src/supplicant/nm-supplicant-manager.c413
-rw-r--r--src/supplicant/nm-supplicant-manager.h45
-rw-r--r--src/supplicant/nm-supplicant-settings-verify.c281
-rw-r--r--src/supplicant/nm-supplicant-settings-verify.h38
-rw-r--r--src/supplicant/nm-supplicant-types.h58
-rw-r--r--src/supplicant/tests/certs/test-ca-cert.pem27
-rw-r--r--src/supplicant/tests/certs/test-cert.p12bin0 -> 4092 bytes
-rw-r--r--src/supplicant/tests/test-supplicant-config.c614
12 files changed, 4604 insertions, 0 deletions
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c
new file mode 100644
index 00000000..8f766d7c
--- /dev/null
+++ b/src/supplicant/nm-supplicant-config.c
@@ -0,0 +1,1246 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2006 - 2012 Red Hat, Inc.
+ * Copyright (C) 2007 - 2008 Novell, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-supplicant-config.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "nm-supplicant-settings-verify.h"
+#include "nm-setting.h"
+#include "NetworkManagerUtils.h"
+#include "nm-utils.h"
+
+typedef struct {
+	char *value;
+	guint32 len;
+	OptType type;
+} ConfigOption;
+
+/*****************************************************************************/
+
+typedef struct {
+	GHashTable *config;
+	GHashTable *blobs;
+	guint32    ap_scan;
+	gboolean   fast_required;
+	gboolean   dispose_has_run;
+} 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)
+
+/*****************************************************************************/
+
+NMSupplicantConfig *
+nm_supplicant_config_new (void)
+{
+	return g_object_new (NM_TYPE_SUPPLICANT_CONFIG, NULL);
+}
+
+static void
+config_option_free (ConfigOption *opt)
+{
+	g_free (opt->value);
+	g_slice_free (ConfigOption, opt);
+}
+
+static void
+blob_free (GByteArray *array)
+{
+	g_byte_array_free (array, TRUE);
+}
+
+static void
+nm_supplicant_config_init (NMSupplicantConfig * self)
+{
+	NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self);
+
+	priv->config = g_hash_table_new_full (g_str_hash, g_str_equal,
+	                                      (GDestroyNotify) g_free,
+	                                      (GDestroyNotify) config_option_free);
+
+	priv->blobs = g_hash_table_new_full (g_str_hash, g_str_equal,
+	                                     (GDestroyNotify) g_free,
+	                                     (GDestroyNotify) blob_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,
+                                           OptType opt_type,
+                                           const char *hidden,
+                                           GError **error)
+{
+	NMSupplicantConfigPrivate *priv;
+	ConfigOption *old_opt;
+	ConfigOption *opt;
+	OptType 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 != TYPE_INVALID)
+		type = opt_type;
+	else {
+		type = nm_supplicant_settings_verify_setting (key, value, len);
+		if (type == TYPE_INVALID) {
+			char buf[255];
+			memset (&buf[0], 0, sizeof (buf));
+			memcpy (&buf[0], value, len > 254 ? 254 : len);
+			g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
+			             "key '%s' and/or value '%s' invalid", key, hidden ? hidden : buf);
+			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 ? 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, 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;
+	OptType type;
+	GByteArray *blob;
+	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 == 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;
+	}
+
+	blob = g_byte_array_sized_new (data_len);
+	g_byte_array_append (blob, data, data_len);
+
+	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);
+	g_hash_table_insert (priv->blobs, g_strdup (blobid), blob);
+
+	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 ((NMSupplicantConfig *) object);
+
+	g_hash_table_destroy (priv->config);
+	g_hash_table_destroy (priv->blobs);
+
+	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 TYPE_INT:
+			g_variant_builder_add (&builder, "{sv}", key, g_variant_new_int32 (atoi (option->value)));
+			break;
+		case TYPE_BYTES:
+		case 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 TYPE_KEYWORD:
+		case 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)
+{
+	NMSupplicantConfigPrivate *priv;
+	gs_unref_bytes GBytes *bytes = NULL;
+	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);
+
+	priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self);
+
+	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) {
+		if (!nm_supplicant_config_add_option (self, "key_mgmt", "NONE", -1, NULL, error))
+			return FALSE;
+
+		/* CAK */
+		value = nm_setting_macsec_get_mka_cak (setting);
+		if (!value) {
+			g_set_error_literal (error,
+			                     NM_SUPPLICANT_ERROR,
+			                     NM_SUPPLICANT_ERROR_CONFIG,
+			                     "missing MKA CAK");
+			return FALSE;
+		}
+
+		bytes = nm_utils_hexstr2bin (value);
+		if (!nm_supplicant_config_add_option (self,
+		                                      "mka_cak",
+		                                      g_bytes_get_data (bytes, NULL),
+		                                      g_bytes_get_size (bytes),
+		                                      "<hidden>",
+		                                      error))
+			return FALSE;
+
+		/* CKN */
+		value = nm_setting_macsec_get_mka_ckn (setting);
+		if (!value) {
+			g_set_error_literal (error,
+			                     NM_SUPPLICANT_ERROR,
+			                     NM_SUPPLICANT_ERROR_CONFIG,
+			                     "missing MKA CKN");
+			return FALSE;
+		}
+
+		bytes = nm_utils_hexstr2bin (value);
+		if (!nm_supplicant_config_add_option (self,
+		                                      "mka_ckn",
+		                                      g_bytes_get_data (bytes, NULL),
+		                                      g_bytes_get_size (bytes),
+		                                      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;
+	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;
+	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 ((is_adhoc || is_ap) && 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 and Hotspot, request that the driver probe for the
+	 * specific SSID we want to associate with.
+	 */
+	if (!(is_adhoc || is_ap)) {
+		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;
+}
+
+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,
+                        size_t input_len,
+                        guint8 *out_digest,
+                        size_t *out_digest_len)
+{
+	GChecksum *sum;
+	guint8 data[64];
+	int i;
+
+	g_return_if_fail (out_digest != NULL);
+	g_return_if_fail (out_digest_len != NULL);
+	g_return_if_fail (*out_digest_len >= 16);
+
+	/* 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_assert (sum);
+	g_checksum_update (sum, data, sizeof (data));
+	g_checksum_get_digest (sum, out_digest, out_digest_len);
+	g_checksum_free (sum);
+
+	g_assert (*out_digest_len == 16);
+	/* WEP104 keys are 13 bytes in length (26 hex characters) */
+	*out_digest_len = 13;
+}
+
+static gboolean
+add_wep_key (NMSupplicantConfig *self,
+             const char *key,
+             const char *name,
+             NMWepKeyType wep_type,
+             GError **error)
+{
+	size_t key_len = key ? strlen (key) : 0;
+
+	if (!key || !key_len)
+		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)) {
+			gs_unref_bytes GBytes *bytes = NULL;
+
+			bytes = nm_utils_hexstr2bin (key);
+			if (!bytes) {
+				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,
+			                                      g_bytes_get_data (bytes, NULL),
+			                                      g_bytes_get_size (bytes),
+			                                      "<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[16];
+		size_t digest_len = sizeof (digest);
+
+		wep128_passphrase_hash (key, key_len, digest, &digest_len);
+		if (!nm_supplicant_config_add_option (self, name, (const char *) digest, digest_len, "<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,
+                                                    GError **error)
+{
+	const char *key_mgmt, *auth_alg;
+	const char *psk;
+
+	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);
+
+	key_mgmt = nm_setting_wireless_security_get_key_mgmt (setting);
+	if (!add_string_val (self, key_mgmt, "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 == 64) {
+			gs_unref_bytes GBytes *bytes = NULL;
+
+			/* Hex PSK */
+			bytes = nm_utils_hexstr2bin (psk);
+			if (!bytes) {
+				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",
+			                                      g_bytes_get_data (bytes, NULL),
+			                                      g_bytes_get_size (bytes),
+			                                      "<hidden>",
+			                                      error))
+				return FALSE;
+		} else if (psk_len >= 8 && psk_len <= 63) {
+			/* Use 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, TYPE_STRING, "<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;
+		}
+	}
+
+	/* Only WPA-specific things when using WPA */
+	if (   !strcmp (key_mgmt, "wpa-none")
+	    || !strcmp (key_mgmt, "wpa-psk")
+	    || !strcmp (key_mgmt, "wpa-eap")) {
+		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;
+	}
+
+	/* 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")) {
+			/* If using WPA Enterprise, enable optimized background scanning
+			 * to ensure roaming within an ESS works well.
+			 */
+			if (!nm_supplicant_config_add_option (self, "bgscan", "simple:30:-65:300", -1, NULL, error))
+				return FALSE;
+
+			/* 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;
+}
+
+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;
+
+	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;
+	}
+
+	if (!ADD_STRING_LIST_VAL (self, setting, 802_1x, eap_method, eap_methods, "eap", ' ', TRUE, NULL, error))
+		return FALSE;
+
+	/* Check EAP method for special handling: PEAP + GTC, FAST */
+	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 (method && (strcasecmp (method, "fast") == 0)) {
+			fast = TRUE;
+			priv->fast_required = TRUE;
+		}
+	}
+
+	/* 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;
+	}
+
+	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 ? 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 ? 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:
+			path = nm_setting_802_1x_get_ca_cert_uri (setting);
+			if (!add_string_val (self, path, "ca_cert", FALSE, NULL, 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:
+			path = nm_setting_802_1x_get_phase2_ca_cert_uri (setting);
+			if (!add_string_val (self, path, "ca_cert2", FALSE, NULL, 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;
+
+	/* 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:
+		path = nm_setting_802_1x_get_private_key_uri (setting);
+		if (!add_string_val (self, path, "private_key", FALSE, NULL, 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:
+				path = nm_setting_802_1x_get_client_cert_uri (setting);
+				if (!add_string_val (self, path, "client_cert", FALSE, NULL, 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:
+		path = nm_setting_802_1x_get_phase2_private_key_uri (setting);
+		if (!add_string_val (self, path, "private_key2", FALSE, NULL, 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:
+				path = nm_setting_802_1x_get_phase2_client_cert_uri (setting);
+				if (!add_string_val (self, path, "client_cert2", FALSE, NULL, 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);
+}
+
diff --git a/src/supplicant/nm-supplicant-config.h b/src/supplicant/nm-supplicant-config.h
new file mode 100644
index 00000000..40fca61b
--- /dev/null
+++ b/src/supplicant/nm-supplicant-config.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2006 - 2012 Red Hat, Inc.
+ * Copyright (C) 2007 - 2008 Novell, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_SUPPLICANT_CONFIG_H__
+#define __NETWORKMANAGER_SUPPLICANT_CONFIG_H__
+
+#include <nm-setting-macsec.h>
+#include <nm-setting-wireless.h>
+#include <nm-setting-wireless-security.h>
+#include <nm-setting-8021x.h>
+
+#include "nm-supplicant-types.h"
+
+#define NM_TYPE_SUPPLICANT_CONFIG            (nm_supplicant_config_get_type ())
+#define NM_SUPPLICANT_CONFIG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SUPPLICANT_CONFIG, NMSupplicantConfig))
+#define NM_SUPPLICANT_CONFIG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_SUPPLICANT_CONFIG, NMSupplicantConfigClass))
+#define NM_IS_SUPPLICANT_CONFIG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_SUPPLICANT_CONFIG))
+#define NM_IS_SUPPLICANT_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_SUPPLICANT_CONFIG))
+#define NM_SUPPLICANT_CONFIG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_SUPPLICANT_CONFIG, NMSupplicantConfigClass))
+
+typedef struct _NMSupplicantConfigClass NMSupplicantConfigClass;
+
+GType nm_supplicant_config_get_type (void);
+
+NMSupplicantConfig *nm_supplicant_config_new (void);
+
+guint32 nm_supplicant_config_get_ap_scan (NMSupplicantConfig *self);
+
+gboolean nm_supplicant_config_fast_required (NMSupplicantConfig *self);
+
+GVariant *nm_supplicant_config_to_variant (NMSupplicantConfig *self);
+
+GHashTable *nm_supplicant_config_get_blobs (NMSupplicantConfig *self);
+
+gboolean nm_supplicant_config_add_setting_wireless (NMSupplicantConfig *self,
+                                                    NMSettingWireless *setting,
+                                                    guint32 fixed_freq,
+                                                    GError **error);
+
+gboolean nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
+                                                             NMSettingWirelessSecurity *setting,
+                                                             NMSetting8021x *setting_8021x,
+                                                             const char *con_uuid,
+                                                             guint32 mtu,
+                                                             GError **error);
+
+gboolean nm_supplicant_config_add_no_security (NMSupplicantConfig *self,
+                                               GError **error);
+
+gboolean nm_supplicant_config_add_setting_8021x (NMSupplicantConfig *self,
+                                                 NMSetting8021x *setting,
+                                                 const char *con_uuid,
+                                                 guint32 mtu,
+                                                 gboolean wired,
+                                                 GError **error);
+
+gboolean nm_supplicant_config_add_setting_macsec (NMSupplicantConfig *self,
+                                                  NMSettingMacsec *setting,
+                                                  GError **error);
+
+#endif /* __NETWORKMANAGER_SUPPLICANT_CONFIG_H__ */
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
new file mode 100644
index 00000000..6fc85535
--- /dev/null
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -0,0 +1,1681 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2006 - 2012 Red Hat, Inc.
+ * Copyright (C) 2006 - 2008 Novell, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-supplicant-interface.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "NetworkManagerUtils.h"
+#include "nm-supplicant-config.h"
+#include "nm-core-internal.h"
+#include "nm-dbus-compat.h"
+
+#define WPAS_DBUS_IFACE_INTERFACE   WPAS_DBUS_INTERFACE ".Interface"
+#define WPAS_DBUS_IFACE_BSS         WPAS_DBUS_INTERFACE ".BSS"
+#define WPAS_DBUS_IFACE_NETWORK	    WPAS_DBUS_INTERFACE ".Network"
+#define WPAS_ERROR_INVALID_IFACE    WPAS_DBUS_INTERFACE ".InvalidInterface"
+#define WPAS_ERROR_EXISTS_ERROR     WPAS_DBUS_INTERFACE ".InterfaceExists"
+
+/*****************************************************************************/
+
+enum {
+	STATE,               /* change in the interface's state */
+	REMOVED,             /* interface was removed by the supplicant */
+	NEW_BSS,             /* interface saw a new access point from a scan */
+	BSS_UPDATED,         /* a BSS property changed */
+	BSS_REMOVED,         /* supplicant removed BSS from its scan list */
+	SCAN_DONE,           /* wifi scan is complete */
+	CONNECTION_ERROR,    /* an error occurred during a connection request */
+	CREDENTIALS_REQUEST, /* 802.1x identity or password requested */
+	LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL] = { 0 };
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMSupplicantInterface,
+	PROP_IFACE,
+	PROP_SCANNING,
+	PROP_CURRENT_BSS,
+	PROP_DRIVER,
+	PROP_FAST_SUPPORTED,
+	PROP_AP_SUPPORT,
+);
+
+typedef struct {
+	char *         dev;
+	NMSupplicantDriver driver;
+	bool           fast_supported;
+	gboolean       has_credreq;  /* Whether querying 802.1x credentials is supported */
+	NMSupplicantFeature ap_support;   /* Lightweight AP mode support */
+	NMSupplicantFeature mac_randomization_support;
+	guint32        max_scan_ssids;
+	guint32        ready_count;
+
+	char *         object_path;
+	guint32        state;
+	int            disconnect_reason;
+
+	gboolean       scanning;
+
+	GDBusProxy *   wpas_proxy;
+	GCancellable * init_cancellable;
+	GDBusProxy *   iface_proxy;
+	GCancellable * other_cancellable;
+	GCancellable * assoc_cancellable;
+	char *         net_path;
+	guint32        blobs_left;
+	GHashTable *   bss_proxies;
+	char *         current_bss;
+
+	gint32         last_scan; /* timestamp as returned by nm_utils_get_monotonic_timestamp_s() */
+
+	NMSupplicantConfig *cfg;
+} NMSupplicantInterfacePrivate;
+
+struct _NMSupplicantInterface {
+	GObject parent;
+	NMSupplicantInterfacePrivate _priv;
+};
+
+struct _NMSupplicantInterfaceClass {
+	GObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMSupplicantInterface, nm_supplicant_interface, G_TYPE_OBJECT)
+
+#define NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSupplicantInterface, NM_IS_SUPPLICANT_INTERFACE)
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN           LOGD_SUPPLICANT
+#define _NMLOG_PREFIX_NAME      "sup-iface"
+#define _NMLOG(level, ...) \
+    G_STMT_START { \
+         char _sbuf[64]; \
+         \
+         nm_log ((level), _NMLOG_DOMAIN, \
+                 "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+                 _NMLOG_PREFIX_NAME, \
+                 ((self) \
+                      ? nm_sprintf_buf (_sbuf, \
+                                        "[%p,%s]", \
+                                        (self), \
+                                        NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->dev) \
+                      : "") \
+                 _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+    } G_STMT_END
+
+/*****************************************************************************/
+
+static void
+emit_error_helper (NMSupplicantInterface *self, GError *error)
+{
+	char *name = NULL;
+
+	if (g_dbus_error_is_remote_error (error))
+		name = g_dbus_error_get_remote_error (error);
+
+	g_signal_emit (self, signals[CONNECTION_ERROR], 0, name, error->message);
+	g_free (name);
+}
+
+static void
+bss_props_changed_cb (GDBusProxy *proxy,
+                      GVariant *changed_properties,
+                      char **invalidated_properties,
+                      gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (priv->scanning)
+		priv->last_scan = nm_utils_get_monotonic_timestamp_s ();
+
+	g_signal_emit (self, signals[BSS_UPDATED], 0,
+	               g_dbus_proxy_get_object_path (proxy),
+	               changed_properties);
+}
+
+static GVariant *
+_get_bss_proxy_properties (NMSupplicantInterface *self, GDBusProxy *proxy)
+{
+	gs_strfreev char **properties = NULL;
+	GVariantBuilder builder;
+	char **iter;
+
+	iter = properties = g_dbus_proxy_get_cached_property_names (proxy);
+	if (!iter)
+		return NULL;
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+	while (*iter) {
+		GVariant *copy = g_dbus_proxy_get_cached_property (proxy, *iter);
+
+		g_variant_builder_add (&builder, "{sv}", *iter++, copy);
+		g_variant_unref (copy);
+	}
+
+	return g_variant_builder_end (&builder);
+}
+
+#define BSS_PROXY_INITED "bss-proxy-inited"
+
+static void
+on_bss_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *props = NULL;
+
+	if (!g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error)) {
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			self = NM_SUPPLICANT_INTERFACE (user_data);
+			_LOGD ("failed to acquire BSS proxy: (%s)", error->message);
+			g_hash_table_remove (NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->bss_proxies,
+			                     g_dbus_proxy_get_object_path (proxy));
+		}
+		return;
+	}
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	props = _get_bss_proxy_properties (self, proxy);
+	if (!props)
+		return;
+
+	g_object_set_data (G_OBJECT (proxy), BSS_PROXY_INITED, GUINT_TO_POINTER (TRUE));
+
+	g_signal_emit (self, signals[NEW_BSS], 0,
+	               g_dbus_proxy_get_object_path (proxy),
+	               g_variant_ref_sink (props));
+}
+
+static void
+handle_new_bss (NMSupplicantInterface *self, const char *object_path)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	GDBusProxy *bss_proxy;
+
+	g_return_if_fail (object_path != NULL);
+
+	if (g_hash_table_lookup (priv->bss_proxies, object_path))
+		return;
+
+	bss_proxy = g_object_new (G_TYPE_DBUS_PROXY,
+	                          "g-bus-type", G_BUS_TYPE_SYSTEM,
+	                          "g-flags", G_DBUS_PROXY_FLAGS_NONE,
+	                          "g-name", WPAS_DBUS_SERVICE,
+	                          "g-object-path", object_path,
+	                          "g-interface-name", WPAS_DBUS_IFACE_BSS,
+	                          NULL);
+	g_hash_table_insert (priv->bss_proxies,
+	                     (char *) g_dbus_proxy_get_object_path (bss_proxy),
+	                     bss_proxy);
+	g_signal_connect (bss_proxy, "g-properties-changed", G_CALLBACK (bss_props_changed_cb), self);
+	g_async_initable_init_async (G_ASYNC_INITABLE (bss_proxy),
+	                             G_PRIORITY_DEFAULT,
+	                             priv->other_cancellable,
+	                             (GAsyncReadyCallback) on_bss_proxy_acquired,
+	                             self);
+}
+
+static void
+set_state (NMSupplicantInterface *self, guint32 new_state)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	guint32 old_state = priv->state;
+
+	g_return_if_fail (new_state < NM_SUPPLICANT_INTERFACE_STATE_LAST);
+
+	if (new_state == priv->state)
+		return;
+
+	/* DOWN is a terminal state */
+	g_return_if_fail (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+
+	/* Cannot regress to READY, STARTING, or INIT from higher states */
+	if (priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY)
+		g_return_if_fail (new_state > NM_SUPPLICANT_INTERFACE_STATE_READY);
+
+	if (new_state == NM_SUPPLICANT_INTERFACE_STATE_READY) {
+		if (priv->other_cancellable) {
+			g_warn_if_fail (priv->other_cancellable == NULL);
+			g_cancellable_cancel (priv->other_cancellable);
+			g_clear_object (&priv->other_cancellable);
+		}
+		priv->other_cancellable = g_cancellable_new ();
+	} else if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) {
+		if (priv->init_cancellable)
+			g_cancellable_cancel (priv->init_cancellable);
+		g_clear_object (&priv->init_cancellable);
+
+		if (priv->other_cancellable)
+			g_cancellable_cancel (priv->other_cancellable);
+		g_clear_object (&priv->other_cancellable);
+
+		if (priv->iface_proxy)
+			g_signal_handlers_disconnect_by_data (priv->iface_proxy, self);
+	}
+
+	priv->state = new_state;
+
+	if (   priv->state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING
+	    || old_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
+		priv->last_scan = nm_utils_get_monotonic_timestamp_s ();
+
+	/* Disconnect reason is no longer relevant when not in the DISCONNECTED state */
+	if (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED)
+		priv->disconnect_reason = 0;
+
+	g_signal_emit (self, signals[STATE], 0,
+	               priv->state,
+	               old_state,
+	               priv->disconnect_reason);
+}
+
+static int
+wpas_state_string_to_enum (const char *str_state)
+{
+	if (!strcmp (str_state, "interface_disabled"))
+		return NM_SUPPLICANT_INTERFACE_STATE_DISABLED;
+	else if (!strcmp (str_state, "disconnected"))
+		return NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED;
+	else if (!strcmp (str_state, "inactive"))
+		return NM_SUPPLICANT_INTERFACE_STATE_INACTIVE;
+	else if (!strcmp (str_state, "scanning"))
+		return NM_SUPPLICANT_INTERFACE_STATE_SCANNING;
+	else if (!strcmp (str_state, "authenticating"))
+		return NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING;
+	else if (!strcmp (str_state, "associating"))
+		return NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING;
+	else if (!strcmp (str_state, "associated"))
+		return NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED;
+	else if (!strcmp (str_state, "4way_handshake"))
+		return NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE;
+	else if (!strcmp (str_state, "group_handshake"))
+		return NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE;
+	else if (!strcmp (str_state, "completed"))
+		return NM_SUPPLICANT_INTERFACE_STATE_COMPLETED;
+
+	return -1;
+}
+
+static void
+set_state_from_string (NMSupplicantInterface *self, const char *new_state)
+{
+	int state;
+
+	state = wpas_state_string_to_enum (new_state);
+	if (state == -1) {
+		_LOGW ("unknown supplicant state '%s'", new_state);
+		return;
+	}
+	set_state (self, (guint32) state);
+}
+
+static void
+set_scanning (NMSupplicantInterface *self, gboolean new_scanning)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (priv->scanning != new_scanning) {
+		priv->scanning = new_scanning;
+
+		/* Cache time of last scan completion */
+		if (priv->scanning == FALSE)
+			priv->last_scan = nm_utils_get_monotonic_timestamp_s ();
+
+		_notify (self, PROP_SCANNING);
+	}
+}
+
+gboolean
+nm_supplicant_interface_get_scanning (NMSupplicantInterface *self)
+{
+	NMSupplicantInterfacePrivate *priv;
+
+	g_return_val_if_fail (self != NULL, FALSE);
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	if (priv->scanning)
+		return TRUE;
+	if (priv->state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
+		return TRUE;
+	return FALSE;
+}
+
+const char *
+nm_supplicant_interface_get_current_bss (NMSupplicantInterface *self)
+{
+	NMSupplicantInterfacePrivate *priv;
+
+	g_return_val_if_fail (self != NULL, FALSE);
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	return priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY ? priv->current_bss : NULL;
+}
+
+gint32
+nm_supplicant_interface_get_last_scan_time (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->last_scan;
+}
+
+#define MATCH_PROPERTY(p, n, v, t) (!strcmp (p, n) && g_variant_is_of_type (v, t))
+
+static void
+parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	gboolean have_active = FALSE, have_ssid = FALSE;
+	gint32 max_scan_ssids = -1;
+	const char **array;
+
+	g_return_if_fail (capabilities && g_variant_is_of_type (capabilities, G_VARIANT_TYPE_VARDICT));
+
+	if (   g_variant_lookup (capabilities, "Scan", "^a&s", &array)
+	    && array) {
+		if (g_strv_contains (array, "active"))
+			have_active = TRUE;
+		if (g_strv_contains (array, "ssid"))
+			have_ssid = TRUE;
+		g_free (array);
+	}
+
+	if (g_variant_lookup (capabilities, "MaxScanSSID", "i", &max_scan_ssids)) {
+		/* We need active scan and SSID probe capabilities to care about MaxScanSSIDs */
+		if (max_scan_ssids > 0 && have_active && have_ssid) {
+			/* wpa_supplicant's WPAS_MAX_SCAN_SSIDS value is 16, but for speed
+			 * and to ensure we don't disclose too many SSIDs from the hidden
+			 * list, we'll limit to 5.
+			 */
+			priv->max_scan_ssids = CLAMP (max_scan_ssids, 0, 5);
+			_LOGI ("supports %d scan SSIDs", priv->max_scan_ssids);
+		}
+	}
+}
+
+static void
+iface_check_ready (NMSupplicantInterface *self)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (priv->ready_count && priv->state < NM_SUPPLICANT_INTERFACE_STATE_READY) {
+		priv->ready_count--;
+		if (priv->ready_count == 0)
+			set_state (self, NM_SUPPLICANT_INTERFACE_STATE_READY);
+	}
+}
+
+gboolean
+nm_supplicant_interface_credentials_reply (NMSupplicantInterface *self,
+                                           const char *field,
+                                           const char *value,
+                                           GError **error)
+{
+	NMSupplicantInterfacePrivate *priv;
+	gs_unref_variant GVariant *reply = NULL;
+
+	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), FALSE);
+	g_return_val_if_fail (field != NULL, FALSE);
+	g_return_val_if_fail (value != NULL, FALSE);
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	g_return_val_if_fail (priv->has_credreq == TRUE, FALSE);
+
+	/* Need a network block object path */
+	g_return_val_if_fail (priv->net_path, FALSE);
+	reply = g_dbus_proxy_call_sync (priv->iface_proxy,
+	                                "NetworkReply",
+	                                g_variant_new ("(oss)",
+	                                               priv->net_path,
+	                                               field,
+	                                               value),
+	                                G_DBUS_CALL_FLAGS_NONE,
+	                                5000,
+	                                NULL,
+	                                error);
+	if (error && *error)
+		g_dbus_error_strip_remote_error (*error);
+
+	return !!reply;
+}
+
+static void
+iface_check_netreply_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
+
+	/* We know NetworkReply is supported if the NetworkReply method returned
+	 * successfully (which is unexpected since we sent a bogus network
+	 * object path) or if we got an "InvalidArgs" (which indicates NetworkReply
+	 * is supported).  We know it's not supported if we get an
+	 * "UnknownMethod" error.
+	 */
+
+	variant = g_dbus_proxy_call_finish (proxy, result, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (variant || _nm_dbus_error_has_name (error, "fi.w1.wpa_supplicant1.InvalidArgs"))
+		priv->has_credreq = TRUE;
+
+	_LOGD ("supplicant %s network credentials requests",
+	       priv->has_credreq ? "supports" : "does not support");
+
+	iface_check_ready (self);
+}
+
+NMSupplicantFeature
+nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->ap_support;
+}
+
+void
+nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
+                                        NMSupplicantFeature ap_support)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	/* Use the best indicator of support between the supplicant global
+	 * Capabilities property and the interface's introspection data.
+	 */
+	if (ap_support > priv->ap_support)
+		priv->ap_support = ap_support;
+}
+
+static void
+set_preassoc_scan_mac_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
+
+	variant = _nm_dbus_proxy_call_finish (proxy, result,
+	                                      G_VARIANT_TYPE ("()"),
+	                                      &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	if (error)
+		_LOGW ("failed to enable scan MAC address randomization (%s)", error->message);
+	iface_check_ready (self);
+}
+
+static void
+iface_introspect_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
+	const char *data;
+
+	variant = _nm_dbus_proxy_call_finish (proxy, result,
+	                                      G_VARIANT_TYPE ("(s)"),
+	                                      &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (variant) {
+		g_variant_get (variant, "(&s)", &data);
+
+		/* The ProbeRequest method only exists if AP mode has been enabled */
+		if (strstr (data, "ProbeRequest"))
+			priv->ap_support = NM_SUPPLICANT_FEATURE_YES;
+
+		if (strstr (data, "PreassocMacAddr")) {
+			priv->mac_randomization_support = NM_SUPPLICANT_FEATURE_YES;
+
+			/* Turn on MAC randomization during scans by default */
+			priv->ready_count++;
+			g_dbus_proxy_call (priv->iface_proxy,
+			                   DBUS_INTERFACE_PROPERTIES ".Set",
+			                   g_variant_new ("(ssv)",
+			                                  WPAS_DBUS_IFACE_INTERFACE,
+			                                  "PreassocMacAddr",
+			                                  g_variant_new_string ("0")),
+			                   G_DBUS_CALL_FLAGS_NONE,
+			                   -1,
+			                   priv->init_cancellable,
+			                   (GAsyncReadyCallback) set_preassoc_scan_mac_cb,
+			                   self);
+		}
+	}
+
+	iface_check_ready (self);
+}
+
+static void
+wpas_iface_scan_done (GDBusProxy *proxy,
+                      gboolean success,
+                      gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	GVariant *props;
+	GHashTableIter iter;
+	char *bss_path;
+	GDBusProxy *bss_proxy;
+
+	/* Cache last scan completed time */
+	priv->last_scan = nm_utils_get_monotonic_timestamp_s ();
+
+	g_signal_emit (self, signals[SCAN_DONE], 0, success);
+
+	/* Emit NEW_BSS so that wifi device has the APs (in case it removed them) */
+	g_hash_table_iter_init (&iter, priv->bss_proxies);
+	while (g_hash_table_iter_next (&iter, (gpointer) &bss_path, (gpointer) &bss_proxy)) {
+		if (g_object_get_data (G_OBJECT (bss_proxy), BSS_PROXY_INITED)) {
+			props = _get_bss_proxy_properties (self, bss_proxy);
+			if (props) {
+				g_signal_emit (self, signals[NEW_BSS], 0,
+				               bss_path,
+				               g_variant_ref_sink (props));
+				g_variant_unref (props);
+			}
+		}
+	}
+}
+
+static void
+wpas_iface_bss_added (GDBusProxy *proxy,
+                      const char *path,
+                      GVariant *props,
+                      gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (priv->scanning)
+		priv->last_scan = nm_utils_get_monotonic_timestamp_s ();
+
+	handle_new_bss (self, path);
+}
+
+static void
+wpas_iface_bss_removed (GDBusProxy *proxy,
+                        const char *path,
+                        gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	g_signal_emit (self, signals[BSS_REMOVED], 0, path);
+	g_hash_table_remove (priv->bss_proxies, path);
+}
+
+static void
+wpas_iface_network_request (GDBusProxy *proxy,
+                            const char *path,
+                            const char *field,
+                            const char *message,
+                            gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (priv->has_credreq && priv->net_path && !g_strcmp0 (path, priv->net_path))
+		g_signal_emit (self, signals[CREDENTIALS_REQUEST], 0, field, message);
+}
+
+static void
+props_changed_cb (GDBusProxy *proxy,
+                  GVariant *changed_properties,
+                  GStrv invalidated_properties,
+                  gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	const char *s, **array, **iter;
+	gboolean b = FALSE;
+	gint32 i32;
+	GVariant *v;
+
+	g_object_freeze_notify (G_OBJECT (self));
+
+	if (g_variant_lookup (changed_properties, "Scanning", "b", &b))
+		set_scanning (self, b);
+
+	if (   g_variant_lookup (changed_properties, "State", "&s", &s)
+	    && priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY) {
+		/* Only transition to actual wpa_supplicant interface states (ie,
+		 * anything > READY) after the NMSupplicantInterface has had a
+		 * chance to initialize, which is signalled by entering the READY
+		 * state.
+		 */
+		set_state_from_string (self, s);
+	}
+
+	if (g_variant_lookup (changed_properties, "BSSs", "^a&o", &array)) {
+		iter = array;
+		while (*iter)
+			handle_new_bss (self, *iter++);
+		g_free (array);
+	}
+
+	if (g_variant_lookup (changed_properties, "CurrentBSS", "&o", &s)) {
+		if (strcmp (s, "/") == 0)
+			s = NULL;
+		if (g_strcmp0 (s, priv->current_bss) != 0) {
+			g_free (priv->current_bss);
+			priv->current_bss = g_strdup (s);
+			_notify (self, PROP_CURRENT_BSS);
+		}
+	}
+
+	v = g_variant_lookup_value (changed_properties, "Capabilities", G_VARIANT_TYPE_VARDICT);
+	if (v) {
+		parse_capabilities (self, v);
+		g_variant_unref (v);
+	}
+
+	if (g_variant_lookup (changed_properties, "DisconnectReason", "i", &i32)) {
+		/* Disconnect reason is currently only given for deauthentication events,
+		 * not disassociation; currently they are IEEE 802.11 "reason codes",
+		 * defined by (IEEE 802.11-2007, 7.3.1.7, Table 7-22).  Any locally caused
+		 * deauthentication will be negative, while authentications caused by the
+		 * AP will be positive.
+		 */
+		priv->disconnect_reason = i32;
+		if (priv->disconnect_reason != 0)
+			_LOGW ("connection disconnected (reason %d)", priv->disconnect_reason);
+	}
+
+	g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_free_error GError *error = NULL;
+
+	if (!g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error)) {
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			self = NM_SUPPLICANT_INTERFACE (user_data);
+			_LOGW ("failed to acquire wpa_supplicant interface proxy: (%s)", error->message);
+			set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+		}
+		return;
+	}
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	_nm_dbus_signal_connect (priv->iface_proxy, "ScanDone", G_VARIANT_TYPE ("(b)"),
+	                         G_CALLBACK (wpas_iface_scan_done), self);
+	_nm_dbus_signal_connect (priv->iface_proxy, "BSSAdded", G_VARIANT_TYPE ("(oa{sv})"),
+	                         G_CALLBACK (wpas_iface_bss_added), self);
+	_nm_dbus_signal_connect (priv->iface_proxy, "BSSRemoved", G_VARIANT_TYPE ("(o)"),
+	                         G_CALLBACK (wpas_iface_bss_removed), self);
+	_nm_dbus_signal_connect (priv->iface_proxy, "NetworkRequest", G_VARIANT_TYPE ("(oss)"),
+	                         G_CALLBACK (wpas_iface_network_request), self);
+
+	/* Scan result aging parameters */
+	g_dbus_proxy_call (priv->iface_proxy,
+	                   "org.freedesktop.DBus.Properties.Set",
+	                   g_variant_new ("(ssv)",
+	                                  WPAS_DBUS_IFACE_INTERFACE,
+	                                  "BSSExpireAge",
+	                                  g_variant_new_uint32 (250)),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->init_cancellable,
+	                   NULL,
+	                   NULL);
+	g_dbus_proxy_call (priv->iface_proxy,
+	                   "org.freedesktop.DBus.Properties.Set",
+	                   g_variant_new ("(ssv)",
+	                                  WPAS_DBUS_IFACE_INTERFACE,
+	                                  "BSSExpireCount",
+	                                  g_variant_new_uint32 (2)),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->init_cancellable,
+	                   NULL,
+	                   NULL);
+
+	/* Check whether NetworkReply and AP mode are supported */
+	priv->ready_count = 1;
+	g_dbus_proxy_call (priv->iface_proxy,
+	                   "NetworkReply",
+	                   g_variant_new ("(oss)",
+	                                  "/fff",
+	                                  "foobar",
+	                                  "foobar"),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->init_cancellable,
+	                   (GAsyncReadyCallback) iface_check_netreply_cb,
+	                   self);
+
+	if (priv->ap_support == NM_SUPPLICANT_FEATURE_UNKNOWN ||
+	    priv->mac_randomization_support == NM_SUPPLICANT_FEATURE_UNKNOWN) {
+		/* If the global supplicant capabilities property is not present, we can
+		 * fall back to checking whether the ProbeRequest method is supported.  If
+		 * neither of these works we have no way of determining if AP mode is
+		 * supported or not.  hostap 1.0 and earlier don't support either of these.
+		 */
+		priv->ready_count++;
+		g_dbus_proxy_call (priv->iface_proxy,
+		                   DBUS_INTERFACE_INTROSPECTABLE ".Introspect",
+		                   NULL,
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   priv->init_cancellable,
+		                   (GAsyncReadyCallback) iface_introspect_cb,
+		                   self);
+	}
+}
+
+static void
+interface_add_done (NMSupplicantInterface *self, const char *path)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	_LOGD ("interface added to supplicant");
+
+	priv->object_path = g_strdup (path);
+	priv->iface_proxy = g_object_new (G_TYPE_DBUS_PROXY,
+	                                  "g-bus-type", G_BUS_TYPE_SYSTEM,
+	                                  "g-flags", G_DBUS_PROXY_FLAGS_NONE,
+	                                  "g-name", WPAS_DBUS_SERVICE,
+	                                  "g-object-path", priv->object_path,
+	                                  "g-interface-name", WPAS_DBUS_IFACE_INTERFACE,
+	                                  NULL);
+	g_signal_connect (priv->iface_proxy, "g-properties-changed", G_CALLBACK (props_changed_cb), self);
+	g_async_initable_init_async (G_ASYNC_INITABLE (priv->iface_proxy),
+	                             G_PRIORITY_DEFAULT,
+	                             priv->init_cancellable,
+	                             (GAsyncReadyCallback) on_iface_proxy_acquired,
+	                             self);
+}
+
+static void
+interface_get_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
+	const char *path;
+
+	variant = _nm_dbus_proxy_call_finish (proxy, result,
+	                                      G_VARIANT_TYPE ("(o)"),
+	                                      &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (variant) {
+		g_variant_get (variant, "(&o)", &path);
+		interface_add_done (self, path);
+	} else {
+		g_dbus_error_strip_remote_error (error);
+		_LOGE ("error getting interface: %s", error->message);
+		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+	}
+}
+
+static void
+interface_add_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *variant = NULL;
+	const char *path;
+
+	variant = _nm_dbus_proxy_call_finish (proxy, result,
+	                                      G_VARIANT_TYPE ("(o)"),
+	                                      &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (variant) {
+		g_variant_get (variant, "(&o)", &path);
+		interface_add_done (self, path);
+	} else if (_nm_dbus_error_has_name (error, WPAS_ERROR_EXISTS_ERROR)) {
+		/* Interface already added, just get its object path */
+		g_dbus_proxy_call (priv->wpas_proxy,
+		                   "GetInterface",
+		                   g_variant_new ("(s)", priv->dev),
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   priv->init_cancellable,
+		                   (GAsyncReadyCallback) interface_get_cb,
+		                   self);
+	} else if (   g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
+	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_EXEC_FAILED)
+	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FORK_FAILED)
+	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FAILED)
+	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_TIMEOUT)
+	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NO_REPLY)
+	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_TIMED_OUT)
+	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
+		/* Supplicant wasn't running and could not be launched via service
+		 * activation.  Wait for it to start by moving back to the INIT
+		 * state.
+		 */
+		g_dbus_error_strip_remote_error (error);
+		_LOGD ("failed to activate supplicant: %s", error->message);
+		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_INIT);
+	} else {
+		g_dbus_error_strip_remote_error (error);
+		_LOGE ("error adding interface: %s", error->message);
+		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+	}
+}
+
+#if HAVE_WEXT
+#define DEFAULT_WIFI_DRIVER "nl80211,wext"
+#else
+#define DEFAULT_WIFI_DRIVER "nl80211"
+#endif
+
+static void
+on_wpas_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusProxy *wpas_proxy;
+	GVariantBuilder props;
+	const char *driver_name = NULL;
+
+	wpas_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (!wpas_proxy) {
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			self = NM_SUPPLICANT_INTERFACE (user_data);
+			_LOGW ("failed to acquire wpa_supplicant proxy: (%s)", error->message);
+			set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+		}
+		return;
+	}
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	priv->wpas_proxy = wpas_proxy;
+
+	/* Try to add the interface to the supplicant.  If the supplicant isn't
+	 * running, this will start it via D-Bus activation and return the response
+	 * when the supplicant has started.
+	 */
+
+	switch (priv->driver) {
+	case NM_SUPPLICANT_DRIVER_WIRELESS:
+		driver_name = DEFAULT_WIFI_DRIVER;
+		break;
+	case NM_SUPPLICANT_DRIVER_WIRED:
+		driver_name = "wired";
+		break;
+	case NM_SUPPLICANT_DRIVER_MACSEC:
+		driver_name = "macsec_linux";
+		break;
+	}
+
+	g_return_if_fail (driver_name);
+
+	g_variant_builder_init (&props, G_VARIANT_TYPE_VARDICT);
+	g_variant_builder_add (&props, "{sv}",
+	                       "Driver",
+	                       g_variant_new_string (driver_name));
+	g_variant_builder_add (&props, "{sv}",
+	                       "Ifname",
+	                       g_variant_new_string (priv->dev));
+
+	g_dbus_proxy_call (priv->wpas_proxy,
+	                   "CreateInterface",
+	                   g_variant_new ("(a{sv})", &props),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->init_cancellable,
+	                   (GAsyncReadyCallback) interface_add_cb,
+	                   self);
+}
+
+static void
+interface_add (NMSupplicantInterface *self)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	/* Can only start the interface from INIT state */
+	g_return_if_fail (priv->state == NM_SUPPLICANT_INTERFACE_STATE_INIT);
+
+	_LOGD ("adding interface to supplicant");
+
+	/* Move to starting to prevent double-calls of interface_add() */
+	set_state (self, NM_SUPPLICANT_INTERFACE_STATE_STARTING);
+
+	g_warn_if_fail (priv->init_cancellable == NULL);
+	g_clear_object (&priv->init_cancellable);
+	priv->init_cancellable = g_cancellable_new ();
+
+	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+	                            G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+	                          NULL,
+	                          WPAS_DBUS_SERVICE,
+	                          WPAS_DBUS_PATH,
+	                          WPAS_DBUS_INTERFACE,
+	                          priv->init_cancellable,
+	                          (GAsyncReadyCallback) on_wpas_proxy_acquired,
+	                          self);
+}
+
+void
+nm_supplicant_interface_set_supplicant_available (NMSupplicantInterface *self,
+                                                  gboolean available)
+{
+	NMSupplicantInterfacePrivate *priv;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (available) {
+		/* This can happen if the supplicant couldn't be activated but
+		 * for some reason was started after the activation failure.
+		 */
+		if (priv->state == NM_SUPPLICANT_INTERFACE_STATE_INIT)
+			interface_add (self);
+	} else {
+		/* The supplicant stopped; so we must tear down the interface */
+		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+	}
+}
+
+static void
+log_result_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	gs_unref_variant GVariant *reply = NULL;
+	gs_free_error GError *error = NULL;
+
+	reply = g_dbus_proxy_call_finish (proxy, result, &error);
+	if (   !reply
+	    && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)
+	    && !strstr (error->message, "fi.w1.wpa_supplicant1.NotConnected")) {
+		g_dbus_error_strip_remote_error (error);
+		nm_log_warn (_NMLOG_DOMAIN, "%s: failed to %s: %s",
+		             _NMLOG_PREFIX_NAME, (const char *) user_data, error->message);
+	}
+}
+
+void
+nm_supplicant_interface_disconnect (NMSupplicantInterface * self)
+{
+	NMSupplicantInterfacePrivate *priv;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	/* Cancel all pending calls related to a prior connection attempt */
+	if (priv->assoc_cancellable) {
+		g_cancellable_cancel (priv->assoc_cancellable);
+		g_clear_object (&priv->assoc_cancellable);
+	}
+
+	/* Don't do anything if there is no connection to the supplicant yet. */
+	if (!priv->iface_proxy)
+		return;
+
+	/* Disconnect from the current AP */
+	if (   (priv->state >= NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
+	    && (priv->state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)) {
+		g_dbus_proxy_call (priv->iface_proxy,
+		                   "Disconnect",
+		                   NULL,
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   NULL,
+		                   (GAsyncReadyCallback) log_result_cb,
+		                   "disconnect");
+	}
+
+	/* Remove any network that was added by NetworkManager */
+	if (priv->net_path) {
+		g_dbus_proxy_call (priv->iface_proxy,
+		                   "RemoveNetwork",
+		                   g_variant_new ("(o)", priv->net_path),
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   priv->other_cancellable,
+		                   (GAsyncReadyCallback) log_result_cb,
+		                   "remove network");
+		g_free (priv->net_path);
+		priv->net_path = NULL;
+	}
+}
+
+static void
+select_network_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	gs_unref_variant GVariant *reply = NULL;
+	gs_free_error GError *error = NULL;
+
+	reply = g_dbus_proxy_call_finish (proxy, result, &error);
+	if (   !reply
+	    && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		self = NM_SUPPLICANT_INTERFACE (user_data);
+		g_dbus_error_strip_remote_error (error);
+		_LOGW ("couldn't select network config: %s", error->message);
+		emit_error_helper (self, error);
+	}
+}
+
+static void
+call_select_network (NMSupplicantInterface *self)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	/* We only select the network after all blobs (if any) have been set */
+	if (priv->blobs_left == 0) {
+		g_dbus_proxy_call (priv->iface_proxy,
+		                   "SelectNetwork",
+		                   g_variant_new ("(o)", priv->net_path),
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   priv->assoc_cancellable,
+		                   (GAsyncReadyCallback) select_network_cb,
+		                   self);
+	}
+}
+
+static void
+add_blob_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_unref_variant GVariant *reply = NULL;
+	gs_free_error GError *error = NULL;
+
+	reply = g_dbus_proxy_call_finish (proxy, result, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	priv->blobs_left--;
+	if (reply)
+		call_select_network (self);
+	else {
+		g_dbus_error_strip_remote_error (error);
+		_LOGW ("couldn't set network certificates: %s", error->message);
+		emit_error_helper (self, error);
+	}
+}
+
+static void
+add_network_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_unref_variant GVariant *reply = NULL;
+	gs_free_error GError *error = NULL;
+	GHashTable *blobs;
+	GHashTableIter iter;
+	const char *blob_name;
+	GByteArray *blob_data;
+
+	reply = _nm_dbus_proxy_call_finish (proxy, result,
+	                                    G_VARIANT_TYPE ("(o)"),
+	                                    &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	g_free (priv->net_path);
+	priv->net_path = NULL;
+
+	if (error) {
+		g_dbus_error_strip_remote_error (error);
+		_LOGW ("adding network to supplicant failed: %s", error->message);
+		emit_error_helper (self, error);
+		return;
+	}
+
+	g_variant_get (reply, "(o)", &priv->net_path);
+
+	/* Send blobs first; otherwise jump to selecting the network */
+	blobs = nm_supplicant_config_get_blobs (priv->cfg);
+	priv->blobs_left = g_hash_table_size (blobs);
+
+	g_hash_table_iter_init (&iter, blobs);
+	while (g_hash_table_iter_next (&iter, (gpointer) &blob_name, (gpointer) &blob_data)) {
+		g_dbus_proxy_call (priv->iface_proxy,
+		                   "AddBlob",
+		                   g_variant_new ("(s@ay)",
+		                                  blob_name,
+		                                  g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+		                                                             blob_data->data, blob_data->len, 1)),
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   priv->assoc_cancellable,
+		                   (GAsyncReadyCallback) add_blob_cb,
+		                   self);
+	}
+
+	call_select_network (self);
+}
+
+static void
+add_network (NMSupplicantInterface *self)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	g_dbus_proxy_call (priv->iface_proxy,
+	                   "AddNetwork",
+	                   g_variant_new ("(@a{sv})", nm_supplicant_config_to_variant (priv->cfg)),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->assoc_cancellable,
+	                   (GAsyncReadyCallback) add_network_cb,
+	                   self);
+}
+
+static void
+set_mac_randomization_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_unref_variant GVariant *reply = NULL;
+	gs_free_error GError *error = NULL;
+
+	reply = g_dbus_proxy_call_finish (proxy, result, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (!reply) {
+		g_dbus_error_strip_remote_error (error);
+		_LOGW ("couldn't send MAC randomization mode to the supplicant interface: %s",
+		       error->message);
+		emit_error_helper (self, error);
+		return;
+	}
+
+	_LOGT ("config: set MAC randomization to 0");
+	add_network (self);
+}
+
+static void
+set_ap_scan_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_unref_variant GVariant *reply = NULL;
+	gs_free_error GError *error = NULL;
+
+	reply = g_dbus_proxy_call_finish (proxy, result, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (!reply) {
+		g_dbus_error_strip_remote_error (error);
+		_LOGW ("couldn't send AP scan mode to the supplicant interface: %s",
+		       error->message);
+		emit_error_helper (self, error);
+		return;
+	}
+
+	_LOGI ("config: set interface ap_scan to %d",
+	       nm_supplicant_config_get_ap_scan (priv->cfg));
+
+	if (priv->mac_randomization_support == NM_SUPPLICANT_FEATURE_YES) {
+		/* Enable/disable association MAC address randomization */
+		g_dbus_proxy_call (priv->iface_proxy,
+		                   DBUS_INTERFACE_PROPERTIES ".Set",
+		                   g_variant_new ("(ssv)",
+		                                  WPAS_DBUS_IFACE_INTERFACE,
+		                                  "MacAddr",
+		                                  g_variant_new_string ("0")),
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   priv->assoc_cancellable,
+		                   (GAsyncReadyCallback) set_mac_randomization_cb,
+		                   self);
+	} else
+		add_network (self);
+}
+
+gboolean
+nm_supplicant_interface_set_config (NMSupplicantInterface *self,
+                                    NMSupplicantConfig *cfg,
+                                    GError **error)
+{
+	NMSupplicantInterfacePrivate *priv;
+
+	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), FALSE);
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	nm_supplicant_interface_disconnect (self);
+
+	/* Make sure the supplicant supports EAP-FAST before trying to send
+	 * it an EAP-FAST configuration.
+	 */
+	if (nm_supplicant_config_fast_required (cfg) && !priv->fast_supported) {
+		g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
+		             "EAP-FAST is not supported by the supplicant");
+		return FALSE;
+	}
+
+	g_clear_object (&priv->cfg);
+	if (cfg) {
+		priv->assoc_cancellable = g_cancellable_new ();
+		priv->cfg = g_object_ref (cfg);
+		g_dbus_proxy_call (priv->iface_proxy,
+		                   DBUS_INTERFACE_PROPERTIES ".Set",
+		                   g_variant_new ("(ssv)",
+		                                  WPAS_DBUS_IFACE_INTERFACE,
+		                                  "ApScan",
+		                                  g_variant_new_uint32 (nm_supplicant_config_get_ap_scan (priv->cfg))),
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   priv->assoc_cancellable,
+		                   (GAsyncReadyCallback) set_ap_scan_cb,
+		                   self);
+	}
+	return TRUE;
+}
+
+static void
+scan_request_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	gs_unref_variant GVariant *reply = NULL;
+	gs_free_error GError *error = NULL;
+
+	reply = g_dbus_proxy_call_finish (proxy, result, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+
+	if (error) {
+		if (_nm_dbus_error_has_name (error, "fi.w1.wpa_supplicant1.Interface.ScanError"))
+			_LOGD ("could not get scan request result: %s", error->message);
+		else {
+			g_dbus_error_strip_remote_error (error);
+			_LOGW ("could not get scan request result: %s", error->message);
+		}
+	}
+	g_signal_emit (self, signals[SCAN_DONE], 0, error ? FALSE : TRUE);
+}
+
+gboolean
+nm_supplicant_interface_request_scan (NMSupplicantInterface *self, const GPtrArray *ssids)
+{
+	NMSupplicantInterfacePrivate *priv;
+	GVariantBuilder builder;
+	guint i;
+
+	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), FALSE);
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	/* Scan parameters */
+	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+	g_variant_builder_add (&builder, "{sv}", "Type", g_variant_new_string ("active"));
+	if (ssids) {
+		GVariantBuilder ssids_builder;
+
+		g_variant_builder_init (&ssids_builder, G_VARIANT_TYPE_BYTESTRING_ARRAY);
+		for (i = 0; i < ssids->len; i++) {
+			GByteArray *ssid = g_ptr_array_index (ssids, i);
+			g_variant_builder_add (&ssids_builder, "@ay",
+			                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+			                                                  ssid->data, ssid->len, 1));
+		}
+		g_variant_builder_add (&builder, "{sv}", "SSIDs", g_variant_builder_end (&ssids_builder));
+	}
+
+	g_dbus_proxy_call (priv->iface_proxy,
+	                   "Scan",
+	                   g_variant_new ("(a{sv})", &builder),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->other_cancellable,
+	                   (GAsyncReadyCallback) scan_request_cb,
+	                   self);
+	return TRUE;
+}
+
+guint32
+nm_supplicant_interface_get_state (NMSupplicantInterface * self)
+{
+	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->state;
+}
+
+const char *
+nm_supplicant_interface_state_to_string (guint32 state)
+{
+	switch (state) {
+	case NM_SUPPLICANT_INTERFACE_STATE_INIT:
+		return "init";
+	case NM_SUPPLICANT_INTERFACE_STATE_STARTING:
+		return "starting";
+	case NM_SUPPLICANT_INTERFACE_STATE_READY:
+		return "ready";
+	case NM_SUPPLICANT_INTERFACE_STATE_DISABLED:
+		return "disabled";
+	case NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED:
+		return "disconnected";
+	case NM_SUPPLICANT_INTERFACE_STATE_INACTIVE:
+		return "inactive";
+	case NM_SUPPLICANT_INTERFACE_STATE_SCANNING:
+		return "scanning";
+	case NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING:
+		return "authenticating";
+	case NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING:
+		return "associating";
+	case NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED:
+		return "associated";
+	case NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE:
+		return "4-way handshake";
+	case NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE:
+		return "group handshake";
+	case NM_SUPPLICANT_INTERFACE_STATE_COMPLETED:
+		return "completed";
+	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
+		return "down";
+	default:
+		break;
+	}
+	return "unknown";
+}
+
+const char *
+nm_supplicant_interface_get_object_path (NMSupplicantInterface *self)
+{
+	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), NULL);
+
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->object_path;
+}
+
+const char *
+nm_supplicant_interface_get_ifname (NMSupplicantInterface *self)
+{
+	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), NULL);
+
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->dev;
+}
+
+guint
+nm_supplicant_interface_get_max_scan_ssids (NMSupplicantInterface *self)
+{
+	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), 0);
+
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->max_scan_ssids;
+}
+
+/*****************************************************************************/
+
+NMSupplicantInterface *
+nm_supplicant_interface_new (const char *ifname,
+                             NMSupplicantDriver driver,
+                             gboolean fast_supported,
+                             NMSupplicantFeature ap_support)
+{
+	g_return_val_if_fail (ifname != NULL, NULL);
+
+	return g_object_new (NM_TYPE_SUPPLICANT_INTERFACE,
+	                     NM_SUPPLICANT_INTERFACE_IFACE, ifname,
+	                     NM_SUPPLICANT_INTERFACE_DRIVER, (guint) driver,
+	                     NM_SUPPLICANT_INTERFACE_FAST_SUPPORTED, fast_supported,
+	                     NM_SUPPLICANT_INTERFACE_AP_SUPPORT, (int) ap_support,
+	                     NULL);
+}
+
+static void
+nm_supplicant_interface_init (NMSupplicantInterface * self)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	priv->state = NM_SUPPLICANT_INTERFACE_STATE_INIT;
+	priv->bss_proxies = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
+}
+
+static void
+set_property (GObject *object,
+              guint prop_id,
+              const GValue *value,
+              GParamSpec *pspec)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE ((NMSupplicantInterface *) object);
+
+	switch (prop_id) {
+	case PROP_IFACE:
+		/* construct-only */
+		priv->dev = g_value_dup_string (value);
+		g_return_if_fail (priv->dev);
+		break;
+	case PROP_DRIVER:
+		/* construct-only */
+		priv->driver = g_value_get_uint (value);
+		break;
+	case PROP_FAST_SUPPORTED:
+		/* construct-only */
+		priv->fast_supported = g_value_get_boolean (value);
+		break;
+	case PROP_AP_SUPPORT:
+		/* construct-only */
+		priv->ap_support = g_value_get_int (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+get_property (GObject *object,
+              guint prop_id,
+              GValue *value,
+              GParamSpec *pspec)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE ((NMSupplicantInterface *) object);
+
+	switch (prop_id) {
+	case PROP_SCANNING:
+		g_value_set_boolean (value, priv->scanning);
+		break;
+	case PROP_CURRENT_BSS:
+		g_value_set_string (value, priv->current_bss);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+dispose (GObject *object)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE ((NMSupplicantInterface *) object);
+
+	if (priv->iface_proxy)
+		g_signal_handlers_disconnect_by_data (priv->iface_proxy, object);
+	g_clear_object (&priv->iface_proxy);
+
+	nm_clear_g_cancellable (&priv->init_cancellable);
+	nm_clear_g_cancellable (&priv->other_cancellable);
+	nm_clear_g_cancellable (&priv->assoc_cancellable);
+
+	g_clear_object (&priv->wpas_proxy);
+	g_clear_pointer (&priv->bss_proxies, (GDestroyNotify) g_hash_table_destroy);
+
+	g_clear_pointer (&priv->net_path, g_free);
+	g_clear_pointer (&priv->dev, g_free);
+	g_clear_pointer (&priv->object_path, g_free);
+	g_clear_pointer (&priv->current_bss, g_free);
+
+	g_clear_object (&priv->cfg);
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (nm_supplicant_interface_parent_class)->dispose (object);
+}
+
+static void
+nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->dispose = dispose;
+	object_class->set_property = set_property;
+	object_class->get_property = get_property;
+
+	obj_properties[PROP_SCANNING] =
+	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_SCANNING, "", "",
+	                          FALSE,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_CURRENT_BSS] =
+	    g_param_spec_string (NM_SUPPLICANT_INTERFACE_CURRENT_BSS, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_IFACE] =
+	    g_param_spec_string (NM_SUPPLICANT_INTERFACE_IFACE, "", "",
+	                         NULL,
+	                         G_PARAM_WRITABLE |
+	                         G_PARAM_CONSTRUCT_ONLY |
+	                         G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_DRIVER] =
+	    g_param_spec_uint (NM_SUPPLICANT_INTERFACE_DRIVER, "", "",
+	                       0, G_MAXUINT, NM_SUPPLICANT_DRIVER_WIRELESS,
+	                       G_PARAM_WRITABLE |
+	                       G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_FAST_SUPPORTED] =
+	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_FAST_SUPPORTED, "", "",
+	                          TRUE,
+	                          G_PARAM_WRITABLE |
+	                          G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_AP_SUPPORT] =
+	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_AP_SUPPORT, "", "",
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      NM_SUPPLICANT_FEATURE_YES,
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      G_PARAM_WRITABLE |
+	                      G_PARAM_CONSTRUCT_ONLY |
+	                      G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+	signals[STATE] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_STATE,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INT);
+
+	signals[REMOVED] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_REMOVED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 0);
+
+	signals[NEW_BSS] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_NEW_BSS,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VARIANT);
+
+	signals[BSS_UPDATED] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_BSS_UPDATED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VARIANT);
+
+	signals[BSS_REMOVED] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_BSS_REMOVED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 1, G_TYPE_STRING);
+
+	signals[SCAN_DONE] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_SCAN_DONE,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+
+	signals[CONNECTION_ERROR] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_CONNECTION_ERROR,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
+
+	signals[CREDENTIALS_REQUEST] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_CREDENTIALS_REQUEST,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
+}
+
diff --git a/src/supplicant/nm-supplicant-interface.h b/src/supplicant/nm-supplicant-interface.h
new file mode 100644
index 00000000..5ab66d5d
--- /dev/null
+++ b/src/supplicant/nm-supplicant-interface.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2006 - 2010 Red Hat, Inc.
+ * Copyright (C) 2007 - 2008 Novell, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_SUPPLICANT_INTERFACE_H__
+#define __NETWORKMANAGER_SUPPLICANT_INTERFACE_H__
+
+#include "nm-supplicant-types.h"
+
+/*
+ * Supplicant interface states
+ *   A mix of wpa_supplicant interface states and internal states.
+ */
+enum {
+	NM_SUPPLICANT_INTERFACE_STATE_INIT = 0,
+	NM_SUPPLICANT_INTERFACE_STATE_STARTING,
+	NM_SUPPLICANT_INTERFACE_STATE_READY,
+	NM_SUPPLICANT_INTERFACE_STATE_DISABLED,
+	NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED,
+	NM_SUPPLICANT_INTERFACE_STATE_INACTIVE,
+	NM_SUPPLICANT_INTERFACE_STATE_SCANNING,
+	NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING,
+	NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING,
+	NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED,
+	NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE,
+	NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE,
+	NM_SUPPLICANT_INTERFACE_STATE_COMPLETED,
+	NM_SUPPLICANT_INTERFACE_STATE_DOWN,
+	NM_SUPPLICANT_INTERFACE_STATE_LAST
+};
+
+#define NM_TYPE_SUPPLICANT_INTERFACE            (nm_supplicant_interface_get_type ())
+#define NM_SUPPLICANT_INTERFACE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SUPPLICANT_INTERFACE, NMSupplicantInterface))
+#define NM_SUPPLICANT_INTERFACE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_SUPPLICANT_INTERFACE, NMSupplicantInterfaceClass))
+#define NM_IS_SUPPLICANT_INTERFACE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_SUPPLICANT_INTERFACE))
+#define NM_IS_SUPPLICANT_INTERFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_SUPPLICANT_INTERFACE))
+#define NM_SUPPLICANT_INTERFACE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_SUPPLICANT_INTERFACE, NMSupplicantInterfaceClass))
+
+/* Properties */
+#define NM_SUPPLICANT_INTERFACE_IFACE            "iface"
+#define NM_SUPPLICANT_INTERFACE_SCANNING         "scanning"
+#define NM_SUPPLICANT_INTERFACE_CURRENT_BSS      "current-bss"
+#define NM_SUPPLICANT_INTERFACE_DRIVER           "driver"
+#define NM_SUPPLICANT_INTERFACE_FAST_SUPPORTED   "fast-supported"
+#define NM_SUPPLICANT_INTERFACE_AP_SUPPORT       "ap-support"
+
+/* Signals */
+#define NM_SUPPLICANT_INTERFACE_STATE            "state"
+#define NM_SUPPLICANT_INTERFACE_REMOVED          "removed"
+#define NM_SUPPLICANT_INTERFACE_NEW_BSS          "new-bss"
+#define NM_SUPPLICANT_INTERFACE_BSS_UPDATED      "bss-updated"
+#define NM_SUPPLICANT_INTERFACE_BSS_REMOVED      "bss-removed"
+#define NM_SUPPLICANT_INTERFACE_SCAN_DONE        "scan-done"
+#define NM_SUPPLICANT_INTERFACE_CONNECTION_ERROR "connection-error"
+#define NM_SUPPLICANT_INTERFACE_CREDENTIALS_REQUEST "credentials-request"
+
+typedef struct _NMSupplicantInterfaceClass NMSupplicantInterfaceClass;
+
+GType nm_supplicant_interface_get_type (void);
+
+NMSupplicantInterface * nm_supplicant_interface_new (const char *ifname,
+                                                     NMSupplicantDriver driver,
+                                                     gboolean fast_supported,
+                                                     NMSupplicantFeature ap_support);
+
+void nm_supplicant_interface_set_supplicant_available (NMSupplicantInterface *self,
+                                                       gboolean available);
+
+gboolean nm_supplicant_interface_set_config (NMSupplicantInterface * iface,
+                                             NMSupplicantConfig * cfg,
+                                             GError **error);
+
+void nm_supplicant_interface_disconnect (NMSupplicantInterface * iface);
+
+const char *nm_supplicant_interface_get_object_path (NMSupplicantInterface * iface);
+
+gboolean nm_supplicant_interface_request_scan (NMSupplicantInterface * self, const GPtrArray *ssids);
+
+guint32 nm_supplicant_interface_get_state (NMSupplicantInterface * self);
+
+const char *nm_supplicant_interface_state_to_string (guint32 state);
+
+gboolean nm_supplicant_interface_get_scanning (NMSupplicantInterface *self);
+
+const char *nm_supplicant_interface_get_current_bss (NMSupplicantInterface *self);
+
+gint32 nm_supplicant_interface_get_last_scan_time (NMSupplicantInterface *self);
+
+const char *nm_supplicant_interface_get_ifname (NMSupplicantInterface *self);
+
+guint nm_supplicant_interface_get_max_scan_ssids (NMSupplicantInterface *self);
+
+gboolean nm_supplicant_interface_get_has_credentials_request (NMSupplicantInterface *self);
+
+gboolean nm_supplicant_interface_credentials_reply (NMSupplicantInterface *self,
+                                                    const char *field,
+                                                    const char *value,
+                                                    GError **error);
+
+NMSupplicantFeature nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self);
+
+void nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
+                                             NMSupplicantFeature apmode);
+
+#endif	/* NM_SUPPLICANT_INTERFACE_H */
diff --git a/src/supplicant/nm-supplicant-manager.c b/src/supplicant/nm-supplicant-manager.c
new file mode 100644
index 00000000..2fbfa391
--- /dev/null
+++ b/src/supplicant/nm-supplicant-manager.c
@@ -0,0 +1,413 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2006 - 2010 Red Hat, Inc.
+ * Copyright (C) 2007 - 2008 Novell, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-supplicant-manager.h"
+
+#include <string.h>
+
+#include "nm-supplicant-interface.h"
+#include "nm-supplicant-types.h"
+#include "nm-core-internal.h"
+
+/*****************************************************************************/
+
+typedef struct {
+	GDBusProxy *     proxy;
+	GCancellable *   cancellable;
+	gboolean         running;
+
+	GSList          *ifaces;
+	gboolean          fast_supported;
+	NMSupplicantFeature ap_support;
+	guint             die_count_reset_id;
+	guint             die_count;
+} NMSupplicantManagerPrivate;
+
+struct _NMSupplicantManager {
+	GObject parent;
+	NMSupplicantManagerPrivate _priv;
+};
+
+struct _NMSupplicantManagerClass {
+	GObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMSupplicantManager, nm_supplicant_manager, G_TYPE_OBJECT)
+
+#define NM_SUPPLICANT_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSupplicantManager, NM_IS_SUPPLICANT_MANAGER)
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_SUPPLICANT
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "supplicant", __VA_ARGS__)
+
+/*****************************************************************************/
+
+G_DEFINE_QUARK (nm-supplicant-error-quark, nm_supplicant_error);
+
+/*****************************************************************************/
+
+static inline gboolean
+die_count_exceeded (guint32 count)
+{
+	return count > 2;
+}
+
+static gboolean
+is_available (NMSupplicantManager *self)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	return    priv->running
+	       && !die_count_exceeded (priv->die_count);
+}
+
+/*****************************************************************************/
+
+static void
+_sup_iface_last_ref (gpointer data,
+                     GObject *object,
+                     gboolean is_last_ref)
+{
+	NMSupplicantManager *self = data;
+	NMSupplicantManagerPrivate *priv;
+	NMSupplicantInterface *sup_iface = (NMSupplicantInterface *) object;
+	const char *op;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_MANAGER (self));
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (sup_iface));
+	g_return_if_fail (is_last_ref);
+
+	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	if (!g_slist_find (priv->ifaces, sup_iface))
+		g_return_if_reached ();
+
+	/* Ask wpa_supplicant to remove this interface */
+	if (   priv->running
+	    && priv->proxy
+	    && (op = nm_supplicant_interface_get_object_path (sup_iface))) {
+		g_dbus_proxy_call (priv->proxy,
+		                   "RemoveInterface",
+		                   g_variant_new ("(o)", op),
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   3000,
+		                   NULL,
+		                   NULL,
+		                   NULL);
+	}
+
+	priv->ifaces = g_slist_remove (priv->ifaces, sup_iface);
+	g_object_remove_toggle_ref ((GObject *) sup_iface, _sup_iface_last_ref, self);
+}
+
+/**
+ * nm_supplicant_manager_create_interface:
+ * @self: the #NMSupplicantManager
+ * @ifname: the interface for which to obtain the supplicant interface
+ * @is_wireless: whether the interface is supposed to be wireless.
+ *
+ * Note: the manager owns a reference to the instance and the only way to
+ *   get the manager to release it, is by dropping all other references
+ *   to the supplicant-interface (or destroying the manager).
+ *
+ * Returns: (transfer full): returns a #NMSupplicantInterface or %NULL.
+ *   Must be unrefed at the end.
+ * */
+NMSupplicantInterface *
+nm_supplicant_manager_create_interface (NMSupplicantManager *self,
+                                        const char *ifname,
+                                        NMSupplicantDriver driver)
+{
+	NMSupplicantManagerPrivate *priv;
+	NMSupplicantInterface *iface;
+	GSList *ifaces;
+
+	g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NULL);
+	g_return_val_if_fail (ifname != NULL, NULL);
+
+	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	_LOGD ("(%s): creating new supplicant interface", ifname);
+
+	/* assert against not requesting duplicate interfaces. */
+	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) {
+		if (g_strcmp0 (nm_supplicant_interface_get_ifname (ifaces->data), ifname) == 0)
+			g_return_val_if_reached (NULL);
+	}
+
+	iface = nm_supplicant_interface_new (ifname,
+	                                     driver,
+	                                     priv->fast_supported,
+	                                     priv->ap_support);
+
+	priv->ifaces = g_slist_prepend (priv->ifaces, iface);
+	g_object_add_toggle_ref ((GObject *) iface, _sup_iface_last_ref, self);
+
+	/* If we're making the supplicant take a time out for a bit, don't
+	 * let the supplicant interface start immediately, just let it hang
+	 * around in INIT state until we're ready to talk to the supplicant
+	 * again.
+	 */
+	if (is_available (self))
+		nm_supplicant_interface_set_supplicant_available (iface, TRUE);
+
+	return iface;
+}
+
+static void
+update_capabilities (NMSupplicantManager *self)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	GSList *ifaces;
+	const char **array;
+	GVariant *value;
+
+	/* The supplicant only advertises global capabilities if the following
+	 * commit has been applied:
+	 *
+	 * commit 1634ac0654eba8d458640a115efc0a6cde3bac4d
+	 * Author: Dan Williams <dcbw@redhat.com>
+	 * Date:   Sat Sep 29 19:06:30 2012 +0300
+	 *
+	 * dbus: Add global capabilities property
+	 */
+	priv->ap_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
+
+	value = g_dbus_proxy_get_cached_property (priv->proxy, "Capabilities");
+	if (value) {
+		if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY)) {
+			array = g_variant_get_strv (value, NULL);
+			priv->ap_support = NM_SUPPLICANT_FEATURE_NO;
+			if (array) {
+				if (g_strv_contains (array, "ap"))
+					priv->ap_support = NM_SUPPLICANT_FEATURE_YES;
+				g_free (array);
+			}
+		}
+		g_variant_unref (value);
+	}
+
+	/* Tell all interfaces about results of the AP check */
+	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next)
+		nm_supplicant_interface_set_ap_support (ifaces->data, priv->ap_support);
+
+	_LOGD ("AP mode is %ssupported",
+	       (priv->ap_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
+	           (priv->ap_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
+
+	/* EAP-FAST */
+	priv->fast_supported = FALSE;
+	value = g_dbus_proxy_get_cached_property (priv->proxy, "EapMethods");
+	if (value) {
+		if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY)) {
+			array = g_variant_get_strv (value, NULL);
+			if (array) {
+				if (g_strv_contains (array, "fast"))
+					priv->fast_supported = TRUE;
+				g_free (array);
+			}
+		}
+		g_variant_unref (value);
+	}
+
+	_LOGD ("EAP-FAST is %ssupported", priv->fast_supported ? "" : "not ");
+}
+
+static void
+availability_changed (NMSupplicantManager *self, gboolean available)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	GSList *ifaces, *iter;
+
+	if (!priv->ifaces)
+		return;
+
+	/* setting the supplicant as unavailable might cause the caller to unref
+	 * the supplicant (and thus remove the instance from the list of interfaces.
+	 * Delay that by taking an additional reference first. */
+	ifaces = g_slist_copy (priv->ifaces);
+	for (iter = ifaces; iter; iter = iter->next)
+		g_object_ref (iter->data);
+	for (iter = ifaces; iter; iter = iter->next)
+		nm_supplicant_interface_set_supplicant_available (iter->data, available);
+	g_slist_free_full (ifaces, g_object_unref);
+}
+
+static void
+set_running (NMSupplicantManager *self, gboolean now_running)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	gboolean old_available = is_available (self);
+	gboolean new_available;
+
+	priv->running = now_running;
+	new_available = is_available (self);
+	if (old_available != new_available)
+		availability_changed (self, new_available);
+}
+
+static void
+set_die_count (NMSupplicantManager *self, guint new_die_count)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	gboolean old_available = is_available (self);
+	gboolean new_available;
+
+	priv->die_count = new_die_count;
+	new_available = is_available (self);
+	if (old_available != new_available)
+		availability_changed (self, new_available);
+}
+
+static gboolean
+wpas_die_count_reset_cb (gpointer user_data)
+{
+	NMSupplicantManager *self = NM_SUPPLICANT_MANAGER (user_data);
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	/* Reset the die count back to zero, which allows use of the supplicant again */
+	priv->die_count_reset_id = 0;
+	set_die_count (self, 0);
+	_LOGI ("wpa_supplicant die count reset");
+	return FALSE;
+}
+
+static void
+name_owner_cb (GDBusProxy *proxy, GParamSpec *pspec, gpointer user_data)
+{
+	NMSupplicantManager *self = NM_SUPPLICANT_MANAGER (user_data);
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	char *owner;
+
+	g_return_if_fail (proxy == priv->proxy);
+
+	owner = g_dbus_proxy_get_name_owner (proxy);
+	_LOGI ("wpa_supplicant %s", owner ? "running" : "stopped");
+
+	if (owner) {
+		set_running (self, TRUE);
+		update_capabilities (self);
+	} else if (priv->running) {
+		/* Reschedule the die count reset timeout.  Every time the supplicant
+		 * dies we wait 10 seconds before resetting the counter.  If the
+		 * supplicant died more than twice before the timer is reset, then
+		 * we don't try to talk to the supplicant for a while.
+		 */
+		if (priv->die_count_reset_id)
+			g_source_remove (priv->die_count_reset_id);
+		priv->die_count_reset_id = g_timeout_add_seconds (10, wpas_die_count_reset_cb, self);
+		set_die_count (self, priv->die_count + 1);
+
+		if (die_count_exceeded (priv->die_count)) {
+			_LOGI ("wpa_supplicant die count %d; ignoring for 10 seconds",
+			       priv->die_count);
+		}
+
+		set_running (self, FALSE);
+
+		priv->fast_supported = FALSE;
+	}
+
+	g_free (owner);
+}
+
+static void
+on_proxy_acquired (GObject *object, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantManager *self;
+	NMSupplicantManagerPrivate *priv;
+	GError *error = NULL;
+	GDBusProxy *proxy;
+
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (!proxy) {
+		_LOGW ("failed to acquire wpa_supplicant proxy: Wi-Fi and 802.1x will not be available (%s)",
+		       error->message);
+		g_clear_error (&error);
+		return;
+	}
+
+	self = NM_SUPPLICANT_MANAGER (user_data);
+	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	priv->proxy = proxy;
+	g_signal_connect (priv->proxy, "notify::g-name-owner", G_CALLBACK (name_owner_cb), self);
+	name_owner_cb (priv->proxy, NULL, self);
+}
+
+/*****************************************************************************/
+
+NM_DEFINE_SINGLETON_GETTER (NMSupplicantManager, nm_supplicant_manager_get, NM_TYPE_SUPPLICANT_MANAGER);
+
+static void
+nm_supplicant_manager_init (NMSupplicantManager *self)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	priv->cancellable = g_cancellable_new ();
+	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+	                          G_DBUS_PROXY_FLAGS_NONE,
+	                          NULL,
+	                          WPAS_DBUS_SERVICE,
+	                          WPAS_DBUS_PATH,
+	                          WPAS_DBUS_INTERFACE,
+	                          priv->cancellable,
+	                          (GAsyncReadyCallback) on_proxy_acquired,
+	                          self);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMSupplicantManager *self = (NMSupplicantManager *) object;
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	GSList *ifaces;
+
+	nm_clear_g_source (&priv->die_count_reset_id);
+
+	if (priv->cancellable) {
+		g_cancellable_cancel (priv->cancellable);
+		g_clear_object (&priv->cancellable);
+	}
+
+	if (priv->ifaces) {
+		for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next)
+			g_object_remove_toggle_ref (ifaces->data, _sup_iface_last_ref, self);
+		g_slist_free (priv->ifaces);
+		priv->ifaces = NULL;
+	}
+
+	g_clear_object (&priv->proxy);
+
+	G_OBJECT_CLASS (nm_supplicant_manager_parent_class)->dispose (object);
+}
+
+static void
+nm_supplicant_manager_class_init (NMSupplicantManagerClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->dispose = dispose;
+}
+
diff --git a/src/supplicant/nm-supplicant-manager.h b/src/supplicant/nm-supplicant-manager.h
new file mode 100644
index 00000000..8928cf20
--- /dev/null
+++ b/src/supplicant/nm-supplicant-manager.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2006 - 2008 Red Hat, Inc.
+ * Copyright (C) 2007 - 2008 Novell, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_SUPPLICANT_MANAGER_H__
+#define __NETWORKMANAGER_SUPPLICANT_MANAGER_H__
+
+#include "nm-supplicant-types.h"
+#include "devices/nm-device.h"
+
+#define NM_TYPE_SUPPLICANT_MANAGER              (nm_supplicant_manager_get_type ())
+#define NM_SUPPLICANT_MANAGER(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SUPPLICANT_MANAGER, NMSupplicantManager))
+#define NM_SUPPLICANT_MANAGER_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_SUPPLICANT_MANAGER, NMSupplicantManagerClass))
+#define NM_IS_SUPPLICANT_MANAGER(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_SUPPLICANT_MANAGER))
+#define NM_IS_SUPPLICANT_MANAGER_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_SUPPLICANT_MANAGER))
+#define NM_SUPPLICANT_MANAGER_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_SUPPLICANT_MANAGER, NMSupplicantManagerClass))
+
+typedef struct _NMSupplicantManagerClass NMSupplicantManagerClass;
+
+GType nm_supplicant_manager_get_type (void);
+
+NMSupplicantManager *nm_supplicant_manager_get (void);
+
+NMSupplicantInterface *nm_supplicant_manager_create_interface (NMSupplicantManager *mgr,
+                                                               const char *ifname,
+                                                               NMSupplicantDriver driver);
+
+#endif /* __NETWORKMANAGER_SUPPLICANT_MANAGER_H__ */
diff --git a/src/supplicant/nm-supplicant-settings-verify.c b/src/supplicant/nm-supplicant-settings-verify.c
new file mode 100644
index 00000000..9e220808
--- /dev/null
+++ b/src/supplicant/nm-supplicant-settings-verify.c
@@ -0,0 +1,281 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2006 - 2012 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-supplicant-settings-verify.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+struct Opt {
+	const char *     key;
+	const OptType    type;
+	const gint32     int_low;  /* Inclusive */
+	const gint32     int_high; /* Inclusive; max length for strings */
+	const gboolean   str_allowed_multiple;
+	const char **    str_allowed;
+};
+
+
+static gboolean validate_type_int     (const struct Opt * opt,
+                                       const char * value,
+                                       const guint32 len);
+
+static gboolean validate_type_bytes   (const struct Opt * opt,
+                                       const char * value,
+                                       const guint32 len);
+
+static gboolean validate_type_utf8    (const struct Opt *opt,
+                                       const char * value,
+                                       const guint32 len);
+
+static gboolean validate_type_keyword (const struct Opt * opt,
+                                       const char * value,
+                                       const guint32 len);
+
+typedef gboolean (*validate_func)(const struct Opt *, const char *, const guint32);
+
+struct validate_entry {
+	const OptType  type;
+	const validate_func func;
+};
+
+static const struct validate_entry validate_table[] = {
+	{ TYPE_INT,     validate_type_int     },
+	{ TYPE_BYTES,   validate_type_bytes   },
+	{ TYPE_UTF8,    validate_type_utf8    },
+	{ TYPE_KEYWORD, validate_type_keyword },
+};
+
+
+const char * pairwise_allowed[] = { "CCMP", "TKIP", "NONE", NULL };
+const char * group_allowed[] =    { "CCMP", "TKIP", "WEP104", "WEP40", NULL };
+const char * proto_allowed[] =    { "WPA", "RSN", NULL };
+const char * key_mgmt_allowed[] = { "WPA-PSK", "WPA-EAP", "IEEE8021X", "WPA-NONE",
+                                    "NONE", NULL };
+const char * auth_alg_allowed[] = { "OPEN", "SHARED", "LEAP", NULL };
+const char * eap_allowed[] =      { "LEAP", "MD5", "TLS", "PEAP", "TTLS", "SIM",
+                                    "PSK", "FAST", "PWD", NULL };
+
+const char * phase1_allowed[] =   {"peapver=0", "peapver=1", "peaplabel=1",
+                                    "peap_outer_success=0", "include_tls_length=1",
+                                    "sim_min_num_chal=3", "fast_provisioning=0",
+                                    "fast_provisioning=1", "fast_provisioning=2",
+                                    "fast_provisioning=3", NULL };
+const char * phase2_allowed[] =   {"auth=PAP", "auth=CHAP", "auth=MSCHAP",
+                                   "auth=MSCHAPV2", "auth=GTC", "auth=OTP",
+                                   "auth=MD5", "auth=TLS", "autheap=MD5",
+                                   "autheap=MSCHAPV2", "autheap=OTP",
+                                   "autheap=GTC", "autheap=TLS", NULL };
+
+static const struct Opt opt_table[] = {
+	{ "ssid",               TYPE_BYTES,   0, 32,FALSE,  NULL },
+	{ "bssid",              TYPE_KEYWORD, 0, 0, FALSE,  NULL },
+	{ "scan_ssid",          TYPE_INT,     0, 1, FALSE,  NULL },
+	{ "mode",               TYPE_INT,     0, 2, FALSE,  NULL },
+	{ "frequency",          TYPE_INT,     2412, 5825, FALSE,  NULL },
+	{ "auth_alg",           TYPE_KEYWORD, 0, 0, FALSE,  auth_alg_allowed },
+	{ "psk",                TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "pairwise",           TYPE_KEYWORD, 0, 0, FALSE,  pairwise_allowed },
+	{ "group",              TYPE_KEYWORD, 0, 0, FALSE,  group_allowed },
+	{ "proto",              TYPE_KEYWORD, 0, 0, FALSE,  proto_allowed },
+	{ "key_mgmt",           TYPE_KEYWORD, 0, 0, FALSE,  key_mgmt_allowed },
+	{ "wep_key0",           TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "wep_key1",           TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "wep_key2",           TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "wep_key3",           TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "wep_tx_keyidx",      TYPE_INT,     0, 3, FALSE,  NULL },
+	{ "eapol_flags",        TYPE_INT,     0, 3, FALSE,  NULL },
+	{ "eap",                TYPE_KEYWORD, 0, 0, FALSE,  eap_allowed },
+	{ "identity",           TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "password",           TYPE_UTF8,    0, 0, FALSE,  NULL },
+	{ "ca_path",            TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "subject_match",      TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "altsubject_match",   TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "domain_suffix_match",TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "ca_cert",            TYPE_BYTES,   0, 65536, FALSE,  NULL },
+	{ "client_cert",        TYPE_BYTES,   0, 65536, FALSE,  NULL },
+	{ "private_key",        TYPE_BYTES,   0, 65536, FALSE,  NULL },
+	{ "private_key_passwd", TYPE_BYTES,   0, 1024, FALSE,  NULL },
+	{ "phase1",             TYPE_KEYWORD, 0, 0, TRUE, phase1_allowed },
+	{ "phase2",             TYPE_KEYWORD, 0, 0, TRUE, phase2_allowed },
+	{ "anonymous_identity", TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "ca_path2",           TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "subject_match2",     TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "altsubject_match2",  TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "domain_suffix_match2", TYPE_BYTES, 0, 0, FALSE,  NULL },
+	{ "ca_cert2",           TYPE_BYTES,   0, 65536, FALSE,  NULL },
+	{ "client_cert2",       TYPE_BYTES,   0, 65536, FALSE,  NULL },
+	{ "private_key2",       TYPE_BYTES,   0, 65536, FALSE,  NULL },
+	{ "private_key2_passwd",TYPE_BYTES,   0, 1024, FALSE,  NULL },
+	{ "pin",                TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "pcsc",               TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "nai",                TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "eappsk",             TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "pac_file",           TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "engine",             TYPE_INT,     0, 1, FALSE,  NULL },
+	{ "engine_id",          TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "key_id",             TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "fragment_size",      TYPE_INT,     1, 2000, FALSE,  NULL },
+	{ "proactive_key_caching", TYPE_INT,  0, 1, FALSE,  NULL },
+	{ "bgscan",             TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "pac_file",           TYPE_BYTES,   0, 1024, FALSE,  NULL },
+	{ "freq_list",          TYPE_KEYWORD, 0, 0, FALSE,  NULL },
+	{ "macsec_policy",      TYPE_INT,     0, 1, FALSE, NULL },
+	{ "macsec_integ_only",  TYPE_INT,     0, 1, FALSE, NULL },
+	{ "mka_cak",            TYPE_BYTES,   0, 65536, FALSE, NULL },
+	{ "mka_ckn",            TYPE_BYTES,   0, 65536, FALSE, NULL },
+	{ "macsec_port",        TYPE_INT,     1, 65534, FALSE, NULL },
+};
+
+
+static gboolean
+validate_type_int (const struct Opt * opt,
+                   const char * value,
+                   const guint32 len)
+{
+	long int intval;
+
+	g_return_val_if_fail (opt != NULL, FALSE);
+	g_return_val_if_fail (value != NULL, FALSE);
+
+	errno = 0;
+	intval = strtol (value, NULL, 10);
+	if (errno != 0)
+		return FALSE;
+
+	/* strtol returns a long, but we are dealing with ints */
+	if (intval > INT_MAX || intval < INT_MIN)
+		return FALSE;
+	if (intval > opt->int_high || intval < opt->int_low)
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+validate_type_bytes (const struct Opt * opt,
+                     const char * value,
+                     const guint32 len)
+{
+	guint32 check_len;
+
+	g_return_val_if_fail (opt != NULL, FALSE);
+	g_return_val_if_fail (value != NULL, FALSE);
+
+	check_len = opt->int_high ? opt->int_high : 255;
+	if (len > check_len)
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+validate_type_utf8 (const struct Opt *opt,
+                    const char * value,
+                    const guint32 len)
+{
+	guint32 check_len;
+
+	g_return_val_if_fail (opt != NULL, FALSE);
+	g_return_val_if_fail (value != NULL, FALSE);
+
+	check_len = opt->int_high ? opt->int_high : 255;
+	/* Note that we deliberately don't validate the UTF-8, because
+	   some "UTF-8" fields, such as 8021x.password, do not actually
+	   have to be valid UTF-8 */
+	if (g_utf8_strlen (value, len) > check_len)
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+validate_type_keyword (const struct Opt * opt,
+                       const char * value,
+                       const guint32 len)
+{
+	char **		allowed;
+	gchar **	candidates = NULL;
+	char **		candidate;
+	gboolean	found = FALSE;
+
+	g_return_val_if_fail (opt != NULL, FALSE);
+	g_return_val_if_fail (value != NULL, FALSE);
+
+	/* Allow everything */
+	if (!opt->str_allowed)
+		return TRUE;
+
+	candidates = g_strsplit (value, " ", 0);
+	if (!candidates)
+		goto out;
+
+	/* validate each space-separated word in 'value' */
+	for (candidate = candidates; *candidate; candidate++) {
+		found = FALSE;
+		for (allowed = (char **) opt->str_allowed; *allowed; allowed++) {
+			if (strcmp (*candidate, *allowed) == 0) {
+				found = TRUE;
+				break;
+			}
+		}
+		if (!found)
+			break;
+	}
+
+out:
+	g_strfreev (candidates);
+	return found;
+}
+
+OptType
+nm_supplicant_settings_verify_setting (const char * key,
+                                       const char * value,
+                                       const guint32 len)
+{
+	OptType type = TYPE_INVALID;
+	int opt_count = sizeof (opt_table) / sizeof (opt_table[0]);
+	int val_count = sizeof (validate_table) / sizeof (validate_table[0]);
+	int i, j;
+
+	g_return_val_if_fail (key != NULL, FALSE);
+	g_return_val_if_fail (value != NULL, FALSE);
+
+	for (i = 0; i < opt_count; i++) {
+		if (strcmp (opt_table[i].key, key) != 0)
+			continue;
+
+		for (j = 0; j < val_count; j++) {
+			if (validate_table[j].type == opt_table[i].type) {
+				if ((*(validate_table[j].func))(&opt_table[i], value, len)) {
+					type = opt_table[i].type;
+					break;
+				}
+			}
+		}
+	}
+
+	return type;
+}
+
diff --git a/src/supplicant/nm-supplicant-settings-verify.h b/src/supplicant/nm-supplicant-settings-verify.h
new file mode 100644
index 00000000..920343ba
--- /dev/null
+++ b/src/supplicant/nm-supplicant-settings-verify.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2006 - 2008 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_SUPPLICANT_SETTINGS_VERIFY_H__
+#define __NETWORKMANAGER_SUPPLICANT_SETTINGS_VERIFY_H__
+
+typedef enum {
+	TYPE_INVALID = 0,
+	TYPE_INT,
+	TYPE_BYTES,
+	TYPE_UTF8,
+	TYPE_KEYWORD,
+	TYPE_STRING
+} OptType;
+
+OptType nm_supplicant_settings_verify_setting (const char * key,
+                                               const char * value,
+                                               const guint32 len);
+
+
+#endif /* __NETWORKMANAGER_SUPPLICANT_SETTINGS_VERIFY_H__ */
diff --git a/src/supplicant/nm-supplicant-types.h b/src/supplicant/nm-supplicant-types.h
new file mode 100644
index 00000000..f75827ec
--- /dev/null
+++ b/src/supplicant/nm-supplicant-types.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2006 - 2008 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_SUPPLICANT_TYPES_H__
+#define __NETWORKMANAGER_SUPPLICANT_TYPES_H__
+
+#define WPAS_DBUS_SERVICE	"fi.w1.wpa_supplicant1"
+#define WPAS_DBUS_PATH		"/fi/w1/wpa_supplicant1"
+#define WPAS_DBUS_INTERFACE	"fi.w1.wpa_supplicant1"
+
+typedef struct _NMSupplicantManager NMSupplicantManager;
+typedef struct _NMSupplicantInterface NMSupplicantInterface;
+typedef struct _NMSupplicantConfig NMSupplicantConfig;
+
+typedef enum {
+	NM_SUPPLICANT_FEATURE_UNKNOWN = 0,  /* Can't detect whether supported or not */
+	NM_SUPPLICANT_FEATURE_NO = 1,       /* Feature definitely not supported */
+	NM_SUPPLICANT_FEATURE_YES = 2,      /* Feature definitely supported */
+} NMSupplicantFeature;
+
+/**
+ * NMSupplicantError:
+ * @NM_SUPPLICANT_ERROR_UNKNOWN: unknown or unclassified error
+ * @NM_SUPPLICANT_ERROR_CONFIG: a failure constructing the
+ *   wpa-supplicant configuration.
+ */
+typedef enum {
+	NM_SUPPLICANT_ERROR_UNKNOWN = 0,                    /*< nick=Unknown >*/
+	NM_SUPPLICANT_ERROR_CONFIG = 1,                     /*< nick=Config >*/
+} NMSupplicantError;
+
+typedef enum {
+	NM_SUPPLICANT_DRIVER_WIRELESS,
+	NM_SUPPLICANT_DRIVER_WIRED,
+	NM_SUPPLICANT_DRIVER_MACSEC,
+} NMSupplicantDriver;
+
+#define NM_SUPPLICANT_ERROR (nm_supplicant_error_quark ())
+GQuark nm_supplicant_error_quark (void);
+
+#endif  /* NM_SUPPLICANT_TYPES_H */
diff --git a/src/supplicant/tests/certs/test-ca-cert.pem b/src/supplicant/tests/certs/test-ca-cert.pem
new file mode 100644
index 00000000..ef1be20d
--- /dev/null
+++ b/src/supplicant/tests/certs/test-ca-cert.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEjzCCA3egAwIBAgIJAOvnZPt59yIZMA0GCSqGSIb3DQEBBQUAMIGLMQswCQYD
+VQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcw
+FQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UE
+AxMEdGVzdDEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wOTAzMTAx
+NTEyMTRaFw0xOTAzMDgxNTEyMTRaMIGLMQswCQYDVQQGEwJVUzESMBAGA1UECBMJ
+QmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcwFQYDVQQKEw5NeSBDb21wYW55
+IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UEAxMEdGVzdDEcMBoGCSqGSIb3
+DQEJARYNdGVzdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAKot9j+/+CX1/gZLgJHIXCRgCItKLGnf7qGbgqB9T2ACBqR0jllKWwDKrcWU
+xjXNIc+GF9Wnv+lX6G0Okn4Zt3/uRNobL+2b/yOF7M3Td3/9W873zdkQQX930YZc
+Rr8uxdRPP5bxiCgtcw632y21sSEbG9mjccAUnV/0jdvfmMNj0i8gN6E0fMBiJ9S3
+FkxX/KFvt9JWE9CtoyL7ki7UIDq+6vj7Gd5N0B3dOa1y+rRHZzKlJPcSXQSEYUS4
+HmKDwiKSVahft8c4tDn7KPi0vex91hlgZVd3usL2E/Vq7o5D9FAZ5kZY0AdFXwdm
+J4lO4Mj7ac7GE4vNERNcXVIX59sCAwEAAaOB8zCB8DAdBgNVHQ4EFgQUuDU3Mr7P
+T3n1e3Sy8hBauoDFahAwgcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDF
+ahChgZGkgY4wgYsxCzAJBgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAO
+BgNVBAcTB05ld2J1cnkxFzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQL
+EwdUZXN0aW5nMQ0wCwYDVQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRl
+c3QuY29tggkA6+dk+3n3IhkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOC
+AQEAVRG4aALIvCXCiKfe7K+iJxjBVRDFPEf7JWA9LGgbFOn6pNvbxonrR+0BETdc
+JV1ET4ct2xsE7QNFIkp9GKRC+6J32zCo8qtLCD5+v436r8TUG2/t2JRMkb9I2XVT
+p7RJoot6M0Ltf8KNQUPYh756xmKZ4USfQUwc58MOSDGY8VWEXJOYij9Pf0e0c52t
+qiCEjXH7uXiS8Pgq9TYm7AkWSOrglYhSa83x0f8mtT8Q15nBESIHZ6o8FAS2bBgn
+B0BkrKRjtBUkuJG3vTox+bYINh2Gxi1JZHWSV1tN5z3hd4VFcKqanW5OgQwToBqp
+3nniskIjbH0xjgZf/nVMyLnjxg==
+-----END CERTIFICATE-----
diff --git a/src/supplicant/tests/certs/test-cert.p12 b/src/supplicant/tests/certs/test-cert.p12
new file mode 100644
index 00000000..ae4a6830
--- /dev/null
+++ b/src/supplicant/tests/certs/test-cert.p12
Binary files differdiff --git a/src/supplicant/tests/test-supplicant-config.c b/src/supplicant/tests/test-supplicant-config.c
new file mode 100644
index 00000000..fd91e921
--- /dev/null
+++ b/src/supplicant/tests/test-supplicant-config.c
@@ -0,0 +1,614 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2008 - 2011 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "nm-core-internal.h"
+
+#include "supplicant/nm-supplicant-config.h"
+#include "supplicant/nm-supplicant-settings-verify.h"
+
+#include "nm-test-utils-core.h"
+
+static gboolean
+validate_opt (const char *detail,
+              GVariant *config,
+              const char *key,
+              OptType val_type,
+              gconstpointer expected,
+              size_t expected_len)
+{
+	char *config_key;
+	GVariant *config_value;
+	gboolean found = FALSE;
+	const guint8 *bytes;
+	gsize len;
+	const char *s;
+	const unsigned char *expected_array = expected;
+	GVariantIter iter;
+
+	g_assert (g_variant_is_of_type (config, G_VARIANT_TYPE_VARDICT));
+
+	g_variant_iter_init (&iter, config);
+	while (g_variant_iter_next (&iter, "{&sv}", (gpointer) &config_key, (gpointer) &config_value)) {
+		if (!strcmp (key, config_key)) {
+			found = TRUE;
+			switch (val_type) {
+			case TYPE_INT:
+				g_assert (g_variant_is_of_type (config_value, G_VARIANT_TYPE_INT32));
+				g_assert_cmpint (g_variant_get_int32 (config_value), ==, GPOINTER_TO_INT (expected));
+				break;
+			case TYPE_BYTES:
+				g_assert (g_variant_is_of_type (config_value, G_VARIANT_TYPE_BYTESTRING));
+				bytes = g_variant_get_fixed_array (config_value, &len, 1);
+				g_assert_cmpint (len, ==, expected_len);
+				g_assert (memcmp (bytes, expected_array, expected_len) == 0);
+				break;
+			case TYPE_KEYWORD:
+			case TYPE_STRING:
+				g_assert (g_variant_is_of_type (config_value, G_VARIANT_TYPE_STRING));
+				if (expected_len == -1)
+					expected_len = strlen ((const char *) expected);
+				s = g_variant_get_string (config_value, NULL);
+				g_assert_cmpint (strlen (s), ==, expected_len);
+				g_assert_cmpstr (s, ==, expected);
+				break;
+			default:
+				g_assert_not_reached ();
+				break;
+			}
+		}
+		g_variant_unref (config_value);
+	}
+
+	return found;
+}
+
+static void
+test_wifi_open (void)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_object NMSupplicantConfig *config = NULL;
+	gs_unref_variant GVariant *config_dict = NULL;
+	NMSettingConnection *s_con;
+	NMSettingWireless *s_wifi;
+	NMSettingIPConfig *s_ip4;
+	char *uuid;
+	gboolean success;
+	GError *error = NULL;
+	GBytes *ssid;
+	const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 };
+	const char *bssid_str = "11:22:33:44:55:66";
+
+	connection = nm_simple_connection_new ();
+
+	/* Connection setting */
+	s_con = (NMSettingConnection *) nm_setting_connection_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_con));
+
+	uuid = nm_utils_uuid_generate ();
+	g_object_set (s_con,
+	              NM_SETTING_CONNECTION_ID, "Test Wifi Open",
+	              NM_SETTING_CONNECTION_UUID, uuid,
+	              NM_SETTING_CONNECTION_AUTOCONNECT, TRUE,
+	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
+	              NULL);
+	g_free (uuid);
+
+	/* Wifi setting */
+	s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wifi));
+
+	ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
+
+	g_object_set (s_wifi,
+	              NM_SETTING_WIRELESS_SSID, ssid,
+	              NM_SETTING_WIRELESS_BSSID, bssid_str,
+	              NM_SETTING_WIRELESS_MODE, "infrastructure",
+	              NM_SETTING_WIRELESS_BAND, "bg",
+	              NULL);
+
+	g_bytes_unref (ssid);
+
+	/* IP4 setting */
+	s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip4));
+
+	g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+	success = nm_connection_verify (connection, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	config = nm_supplicant_config_new ();
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'ssid' value 'Test SSID'*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'scan_ssid' value '1'*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'bssid' value '11:22:33:44:55:66'*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'freq_list' value *");
+	g_assert (nm_supplicant_config_add_setting_wireless (config,
+	                                                     s_wifi,
+	                                                     0,
+	                                                     &error));
+	g_assert_no_error (error);
+	g_test_assert_expected_messages ();
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'key_mgmt' value 'NONE'");
+	g_assert (nm_supplicant_config_add_no_security (config, &error));
+	g_assert_no_error (error);
+	g_test_assert_expected_messages ();
+
+	config_dict = nm_supplicant_config_to_variant (config);
+	g_assert (config_dict);
+
+	validate_opt ("wifi-open", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1), -1);
+	validate_opt ("wifi-open", config_dict, "ssid", TYPE_BYTES, ssid_data, sizeof (ssid_data));
+	validate_opt ("wifi-open", config_dict, "bssid", TYPE_KEYWORD, bssid_str, -1);
+	validate_opt ("wifi-open", config_dict, "key_mgmt", TYPE_KEYWORD, "NONE", -1);
+}
+
+static void
+test_wifi_wep_key (const char *detail,
+                   NMWepKeyType wep_type,
+                   const char *key_data,
+                   const unsigned char *expected,
+                   size_t expected_size)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_object NMSupplicantConfig *config = NULL;
+	gs_unref_variant GVariant *config_dict = NULL;
+	NMSettingConnection *s_con;
+	NMSettingWireless *s_wifi;
+	NMSettingWirelessSecurity *s_wsec;
+	NMSettingIPConfig *s_ip4;
+	char *uuid;
+	gboolean success;
+	GError *error = NULL;
+	GBytes *ssid;
+	const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 };
+	const char *bssid_str = "11:22:33:44:55:66";
+
+	connection = nm_simple_connection_new ();
+
+	/* Connection setting */
+	s_con = (NMSettingConnection *) nm_setting_connection_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_con));
+
+	uuid = nm_utils_uuid_generate ();
+	g_object_set (s_con,
+	              NM_SETTING_CONNECTION_ID, "Test Wifi WEP Key",
+	              NM_SETTING_CONNECTION_UUID, uuid,
+	              NM_SETTING_CONNECTION_AUTOCONNECT, TRUE,
+	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
+	              NULL);
+	g_free (uuid);
+
+	/* Wifi setting */
+	s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wifi));
+
+	ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
+
+	g_object_set (s_wifi,
+	              NM_SETTING_WIRELESS_SSID, ssid,
+	              NM_SETTING_WIRELESS_BSSID, bssid_str,
+	              NM_SETTING_WIRELESS_MODE, "infrastructure",
+	              NM_SETTING_WIRELESS_BAND, "bg",
+	              NULL);
+
+	g_bytes_unref (ssid);
+
+	/* Wifi Security setting */
+	s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wsec));
+
+	g_object_set (s_wsec,
+	              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none",
+	              NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, wep_type,
+	              NULL);
+	nm_setting_wireless_security_set_wep_key (s_wsec, 0, key_data);	
+
+	/* IP4 setting */
+	s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip4));
+
+	g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+	success = nm_connection_verify (connection, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	config = nm_supplicant_config_new ();
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'ssid' value 'Test SSID'*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'scan_ssid' value '1'*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'bssid' value '11:22:33:44:55:66'*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'freq_list' value *");
+	g_assert (nm_supplicant_config_add_setting_wireless (config,
+	                                                     s_wifi,
+	                                                     0,
+	                                                     &error));
+	g_assert_no_error (error);
+	g_test_assert_expected_messages ();
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'key_mgmt' value 'NONE'");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'wep_key0' value *");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'wep_tx_keyidx' value '0'");
+	g_assert (nm_supplicant_config_add_setting_wireless_security (config,
+	                                                              s_wsec,
+	                                                              NULL,
+	                                                              "376aced7-b28c-46be-9a62-fcdf072571da",
+	                                                              1500,
+	                                                              &error));
+	g_assert_no_error (error);
+	g_test_assert_expected_messages ();
+
+	config_dict = nm_supplicant_config_to_variant (config);
+	g_assert (config_dict);
+
+	validate_opt (detail, config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1), -1);
+	validate_opt (detail, config_dict, "ssid", TYPE_BYTES, ssid_data, sizeof (ssid_data));
+	validate_opt (detail, config_dict, "bssid", TYPE_KEYWORD, bssid_str, -1);
+	validate_opt (detail, config_dict, "key_mgmt", TYPE_KEYWORD, "NONE", -1);
+	validate_opt (detail, config_dict, "wep_tx_keyidx", TYPE_INT, GINT_TO_POINTER (0), -1);
+	validate_opt (detail, config_dict, "wep_key0", TYPE_BYTES, expected, expected_size);
+}
+
+static void
+test_wifi_wep (void)
+{
+	const char *key1 = "12345";
+	const unsigned char key1_expected[] = { 0x31, 0x32, 0x33, 0x34, 0x35 };
+	const char *key2 = "ascii test$$$";
+	const unsigned char key2_expected[] = { 0x61, 0x73, 0x63, 0x69, 0x69, 0x20, 0x74, 0x65, 0x73, 0x74, 0x24, 0x24, 0x24 };
+	const char *key3 = "abcdef1234";
+	const unsigned char key3_expected[] = { 0xab, 0xcd, 0xef, 0x12, 0x34 };
+	const char *key4 = "96aec785c6392675f87f592972";
+	const unsigned char key4_expected[] = { 0x96, 0xae, 0xc7, 0x85, 0xc6, 0x39, 0x26, 0x75, 0xf8, 0x7f, 0x59, 0x29, 0x72 };
+	const char *key5 = "r34lly l33t w3p p4ssphr4s3 for t3st1ng";
+	const unsigned char key5_expected[] = { 0xce, 0x68, 0x8b, 0x35, 0xf6, 0x0a, 0x2b, 0xbf, 0xc9, 0x8f, 0xed, 0x10, 0xda };
+
+	test_wifi_wep_key ("wifi-wep-ascii-40", NM_WEP_KEY_TYPE_KEY, key1, key1_expected, sizeof (key1_expected));
+	test_wifi_wep_key ("wifi-wep-ascii-104", NM_WEP_KEY_TYPE_KEY, key2, key2_expected, sizeof (key2_expected));
+	test_wifi_wep_key ("wifi-wep-hex-40", NM_WEP_KEY_TYPE_KEY, key3, key3_expected, sizeof (key3_expected));
+	test_wifi_wep_key ("wifi-wep-hex-104", NM_WEP_KEY_TYPE_KEY, key4, key4_expected, sizeof (key4_expected));
+	test_wifi_wep_key ("wifi-wep-passphrase-104", NM_WEP_KEY_TYPE_PASSPHRASE, key5, key5_expected, sizeof (key5_expected));
+
+	test_wifi_wep_key ("wifi-wep-old-hex-104", NM_WEP_KEY_TYPE_UNKNOWN, key4, key4_expected, sizeof (key4_expected));
+}
+
+static void
+test_wifi_wpa_psk (const char *detail,
+                   OptType key_type,
+                   const char *key_data,
+                   const unsigned char *expected,
+                   size_t expected_size)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_object NMSupplicantConfig *config = NULL;
+	gs_unref_variant GVariant *config_dict = NULL;
+	NMSettingConnection *s_con;
+	NMSettingWireless *s_wifi;
+	NMSettingWirelessSecurity *s_wsec;
+	NMSettingIPConfig *s_ip4;
+	char *uuid;
+	gboolean success;
+	GError *error = NULL;
+	GBytes *ssid;
+	const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 };
+	const char *bssid_str = "11:22:33:44:55:66";
+
+	connection = nm_simple_connection_new ();
+
+	/* Connection setting */
+	s_con = (NMSettingConnection *) nm_setting_connection_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_con));
+
+	uuid = nm_utils_uuid_generate ();
+	g_object_set (s_con,
+	              NM_SETTING_CONNECTION_ID, "Test Wifi WEP Key",
+	              NM_SETTING_CONNECTION_UUID, uuid,
+	              NM_SETTING_CONNECTION_AUTOCONNECT, TRUE,
+	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
+	              NULL);
+	g_free (uuid);
+
+	/* Wifi setting */
+	s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wifi));
+
+	ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
+
+	g_object_set (s_wifi,
+	              NM_SETTING_WIRELESS_SSID, ssid,
+	              NM_SETTING_WIRELESS_BSSID, bssid_str,
+	              NM_SETTING_WIRELESS_MODE, "infrastructure",
+	              NM_SETTING_WIRELESS_BAND, "bg",
+	              NULL);
+
+	g_bytes_unref (ssid);
+
+	/* Wifi Security setting */
+	s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wsec));
+
+	g_object_set (s_wsec,
+	              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk",
+	              NM_SETTING_WIRELESS_SECURITY_PSK, key_data,
+	              NULL);
+
+	nm_setting_wireless_security_add_proto (s_wsec, "wpa");
+	nm_setting_wireless_security_add_proto (s_wsec, "rsn");
+	nm_setting_wireless_security_add_pairwise (s_wsec, "tkip");
+	nm_setting_wireless_security_add_pairwise (s_wsec, "ccmp");
+	nm_setting_wireless_security_add_group (s_wsec, "tkip");
+	nm_setting_wireless_security_add_group (s_wsec, "ccmp");
+
+	/* IP4 setting */
+	s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip4));
+
+	g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+	success = nm_connection_verify (connection, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	config = nm_supplicant_config_new ();
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'ssid' value 'Test SSID'*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'scan_ssid' value '1'*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'bssid' value '11:22:33:44:55:66'*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'freq_list' value *");
+	g_assert (nm_supplicant_config_add_setting_wireless (config,
+	                                                     s_wifi,
+	                                                     0,
+	                                                     &error));
+	g_assert_no_error (error);
+	g_test_assert_expected_messages ();
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'key_mgmt' value 'WPA-PSK'");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'psk' value *");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'proto' value 'WPA RSN'");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'pairwise' value 'TKIP CCMP'");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'group' value 'TKIP CCMP'");
+	g_assert (nm_supplicant_config_add_setting_wireless_security (config,
+	                                                              s_wsec,
+	                                                              NULL,
+	                                                              "376aced7-b28c-46be-9a62-fcdf072571da",
+	                                                              1500,
+	                                                              &error));
+	g_assert_no_error (error);
+	g_test_assert_expected_messages ();
+
+	config_dict = nm_supplicant_config_to_variant (config);
+	g_assert (config_dict);
+
+	validate_opt (detail, config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1), -1);
+	validate_opt (detail, config_dict, "ssid", TYPE_BYTES, ssid_data, sizeof (ssid_data));
+	validate_opt (detail, config_dict, "bssid", TYPE_KEYWORD, bssid_str, -1);
+	validate_opt (detail, config_dict, "key_mgmt", TYPE_KEYWORD, "WPA-PSK", -1);
+	validate_opt (detail, config_dict, "proto", TYPE_KEYWORD, "WPA RSN", -1);
+	validate_opt (detail, config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP", -1);
+	validate_opt (detail, config_dict, "group", TYPE_KEYWORD, "TKIP CCMP", -1);
+	validate_opt (detail, config_dict, "psk", key_type, expected, expected_size);
+}
+
+static void
+test_wifi_wpa_psk_types (void)
+{
+	const char *key1 = "d4721e911461d3cdef9793858e977fcda091779243abb7316c2f11605a160893";
+	const unsigned char key1_expected[] = { 0xd4, 0x72, 0x1e, 0x91, 0x14, 0x61, 0xd3, 0xcd,
+	                                        0xef, 0x97, 0x93, 0x85, 0x8e, 0x97, 0x7f, 0xcd,
+	                                        0xa0, 0x91, 0x77, 0x92, 0x43, 0xab, 0xb7, 0x31,
+	                                        0x6c, 0x2f, 0x11, 0x60, 0x5a, 0x16, 0x08, 0x93 };
+	const char *key2 = "r34lly l33t wp4 p4ssphr4s3 for t3st1ng";
+
+	test_wifi_wpa_psk ("wifi-wpa-psk-hex", TYPE_BYTES, key1, key1_expected, sizeof (key1_expected));
+	test_wifi_wpa_psk ("wifi-wep-psk-passphrase", TYPE_STRING, key2, (gconstpointer) key2, strlen (key2));
+}
+
+static void
+test_wifi_eap (void)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_object NMSupplicantConfig *config = NULL;
+	gs_unref_variant GVariant *config_dict = NULL;
+	NMSettingConnection *s_con;
+	NMSettingWireless *s_wifi;
+	NMSettingWirelessSecurity *s_wsec;
+	NMSetting8021x *s_8021x;
+	NMSettingIPConfig *s_ip4;
+	char *uuid;
+	gboolean success;
+	GError *error = NULL;
+	GBytes *ssid;
+	const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 };
+	const char *bssid_str = "11:22:33:44:55:66";
+	guint32 mtu = 1100;
+
+	connection = nm_simple_connection_new ();
+
+	/* Connection setting */
+	s_con = (NMSettingConnection *) nm_setting_connection_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_con));
+
+	uuid = nm_utils_uuid_generate ();
+	g_object_set (s_con,
+	              NM_SETTING_CONNECTION_ID, "Test Wifi EAP-TLS",
+	              NM_SETTING_CONNECTION_UUID, uuid,
+	              NM_SETTING_CONNECTION_AUTOCONNECT, TRUE,
+	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
+	              NULL);
+	g_free (uuid);
+
+	/* Wifi setting */
+	s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wifi));
+
+	ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
+
+	g_object_set (s_wifi,
+	              NM_SETTING_WIRELESS_SSID, ssid,
+	              NM_SETTING_WIRELESS_BSSID, bssid_str,
+	              NM_SETTING_WIRELESS_MODE, "infrastructure",
+	              NM_SETTING_WIRELESS_BAND, "bg",
+	              NULL);
+
+	g_bytes_unref (ssid);
+
+	/* Wifi Security setting */
+	s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wsec));
+
+	g_object_set (s_wsec,
+	              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap",
+	              NULL);
+
+	nm_setting_wireless_security_add_proto (s_wsec, "wpa");
+	nm_setting_wireless_security_add_proto (s_wsec, "rsn");
+	nm_setting_wireless_security_add_pairwise (s_wsec, "tkip");
+	nm_setting_wireless_security_add_pairwise (s_wsec, "ccmp");
+	nm_setting_wireless_security_add_group (s_wsec, "tkip");
+	nm_setting_wireless_security_add_group (s_wsec, "ccmp");
+
+	/* 802-1X setting */
+	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_8021x));
+	nm_setting_802_1x_add_eap_method (s_8021x, "tls");
+	nm_setting_802_1x_set_client_cert (s_8021x, TEST_CERT_DIR "/test-cert.p12", NM_SETTING_802_1X_CK_SCHEME_PATH, NULL, NULL);
+	nm_setting_802_1x_set_ca_cert (s_8021x, TEST_CERT_DIR "/test-ca-cert.pem", NM_SETTING_802_1X_CK_SCHEME_PATH, NULL, NULL);
+	nm_setting_802_1x_set_private_key (s_8021x, TEST_CERT_DIR "/test-cert.p12", NULL, NM_SETTING_802_1X_CK_SCHEME_PATH, NULL, NULL);
+
+	/* IP4 setting */
+	s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip4));
+
+	g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+	success = nm_connection_verify (connection, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	config = nm_supplicant_config_new ();
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'ssid' value 'Test SSID'*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'scan_ssid' value '1'*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'bssid' value '11:22:33:44:55:66'*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'freq_list' value *");
+	g_assert (nm_supplicant_config_add_setting_wireless (config,
+	                                                     s_wifi,
+	                                                     0,
+	                                                     &error));
+	g_assert_no_error (error);
+	g_test_assert_expected_messages ();
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'key_mgmt' value 'WPA-EAP'");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'proto' value 'WPA RSN'");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'pairwise' value 'TKIP CCMP'");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*added 'group' value 'TKIP CCMP'");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*Config: added 'eap' value 'TLS'");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*Config: added 'fragment_size' value '1086'");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "* Config: added 'ca_cert' value '*/test-ca-cert.pem'");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "* Config: added 'private_key' value '*/test-cert.p12'");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*Config: added 'bgscan' value 'simple:30:-65:300'");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
+	                       "*Config: added 'proactive_key_caching' value '1'");
+	g_assert (nm_supplicant_config_add_setting_wireless_security (config,
+	                                                              s_wsec,
+	                                                              s_8021x,
+	                                                              "d5b488af-9cab-41ed-bad4-97709c58430f",
+	                                                              mtu,
+	                                                              &error));
+	g_assert_no_error (error);
+	g_test_assert_expected_messages ();
+
+	config_dict = nm_supplicant_config_to_variant (config);
+	g_assert (config_dict);
+
+	validate_opt ("wifi-eap", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1), -1);
+	validate_opt ("wifi-eap", config_dict, "ssid", TYPE_BYTES, ssid_data, sizeof (ssid_data));
+	validate_opt ("wifi-eap", config_dict, "bssid", TYPE_KEYWORD, bssid_str, -1);
+	validate_opt ("wifi-eap", config_dict, "key_mgmt", TYPE_KEYWORD, "WPA-EAP", -1);
+	validate_opt ("wifi-eap", config_dict, "eap", TYPE_KEYWORD, "TLS", -1);
+	validate_opt ("wifi-eap", config_dict, "proto", TYPE_KEYWORD, "WPA RSN", -1);
+	validate_opt ("wifi-eap", config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP", -1);
+	validate_opt ("wifi-eap", config_dict, "group", TYPE_KEYWORD, "TKIP CCMP", -1);
+	validate_opt ("wifi-eap", config_dict, "fragment_size", TYPE_INT, GINT_TO_POINTER(mtu-14), -1);
+}
+
+NMTST_DEFINE ();
+
+int main (int argc, char **argv)
+{
+	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
+
+	g_test_add_func ("/supplicant-config/wifi-open", test_wifi_open);
+	g_test_add_func ("/supplicant-config/wifi-wep", test_wifi_wep);
+	g_test_add_func ("/supplicant-config/wifi-wpa-psk-types", test_wifi_wpa_psk_types);
+	g_test_add_func ("/supplicant-config/wifi-eap", test_wifi_eap);
+
+	return g_test_run ();
+}
+