summary refs log tree commit diff
path: root/libnm-core
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2015-07-14 19:38:58 +0200
committerMichael Biebl <biebl@debian.org>2015-07-14 19:38:58 +0200
commit50a58f0fabd8a34c1b6108a107e08abe3c1ccd24 (patch)
tree6790165f39daee79e2b6c6617483320613493367 /libnm-core
parentf408e27bccfacf347605a8d98649975a68f38a17 (diff)
Imported Upstream version 1.0.4 upstream/1.0.4
Diffstat (limited to 'libnm-core')
-rw-r--r--libnm-core/Makefile.in61
-rw-r--r--libnm-core/Makefile.libnm-core5
-rw-r--r--libnm-core/nm-core-enum-types.c22
-rw-r--r--libnm-core/nm-core-enum-types.h2
-rw-r--r--libnm-core/nm-core-internal.h23
-rw-r--r--libnm-core/nm-dbus-interface.h4
-rw-r--r--libnm-core/nm-keyfile-internal.h171
-rw-r--r--libnm-core/nm-keyfile-reader.c1659
-rw-r--r--libnm-core/nm-keyfile-utils.c342
-rw-r--r--libnm-core/nm-keyfile-utils.h79
-rw-r--r--libnm-core/nm-keyfile-writer.c751
-rw-r--r--libnm-core/nm-setting-8021x.c214
-rw-r--r--libnm-core/nm-setting-8021x.h2
-rw-r--r--libnm-core/nm-setting-connection.c65
-rw-r--r--libnm-core/nm-setting-connection.h23
-rw-r--r--libnm-core/nm-setting-ip6-config.c10
-rw-r--r--libnm-core/nm-setting-private.h2
-rw-r--r--libnm-core/nm-setting-wireless-security.c12
-rw-r--r--libnm-core/nm-setting-wireless.c4
-rw-r--r--libnm-core/nm-setting.c18
-rw-r--r--libnm-core/nm-utils-private.h6
-rw-r--r--libnm-core/nm-utils.c286
-rw-r--r--libnm-core/nm-version.h14
-rw-r--r--libnm-core/nm-version.h.in12
-rw-r--r--libnm-core/tests/Makefile.am35
-rw-r--r--libnm-core/tests/Makefile.in539
-rw-r--r--libnm-core/tests/certs/test-ca-cert.pem27
-rw-r--r--libnm-core/tests/certs/test-key-and-cert.pem118
-rw-r--r--libnm-core/tests/test-general.c414
-rw-r--r--libnm-core/tests/test-keyfile.c534
-rw-r--r--libnm-core/tests/test-secrets.c42
-rw-r--r--libnm-core/tests/test-setting-8021x.c6
-rw-r--r--libnm-core/tests/test-settings-defaults.c25
33 files changed, 5241 insertions, 286 deletions
diff --git a/libnm-core/Makefile.in b/libnm-core/Makefile.in
index 7ceb3cfa..6f502e38 100644
--- a/libnm-core/Makefile.in
+++ b/libnm-core/Makefile.in
@@ -132,14 +132,15 @@ libnm_core_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
 	$(am__DEPENDENCIES_3)
 am__libnm_core_la_SOURCES_DIST = $(core_build)/nm-core-enum-types.c \
 	$(core)/crypto.c $(core)/nm-connection.c $(core)/nm-errors.c \
-	$(core)/nm-property-compare.c $(core)/nm-setting-8021x.c \
-	$(core)/nm-setting-adsl.c $(core)/nm-setting-bluetooth.c \
-	$(core)/nm-setting-bond.c $(core)/nm-setting-bridge-port.c \
-	$(core)/nm-setting-bridge.c $(core)/nm-setting-cdma.c \
-	$(core)/nm-setting-connection.c $(core)/nm-setting-dcb.c \
-	$(core)/nm-setting-generic.c $(core)/nm-setting-gsm.c \
-	$(core)/nm-setting-infiniband.c $(core)/nm-setting-ip-config.c \
-	$(core)/nm-setting-ip4-config.c \
+	$(core)/nm-keyfile-reader.c $(core)/nm-keyfile-utils.c \
+	$(core)/nm-keyfile-writer.c $(core)/nm-property-compare.c \
+	$(core)/nm-setting-8021x.c $(core)/nm-setting-adsl.c \
+	$(core)/nm-setting-bluetooth.c $(core)/nm-setting-bond.c \
+	$(core)/nm-setting-bridge-port.c $(core)/nm-setting-bridge.c \
+	$(core)/nm-setting-cdma.c $(core)/nm-setting-connection.c \
+	$(core)/nm-setting-dcb.c $(core)/nm-setting-generic.c \
+	$(core)/nm-setting-gsm.c $(core)/nm-setting-infiniband.c \
+	$(core)/nm-setting-ip-config.c $(core)/nm-setting-ip4-config.c \
 	$(core)/nm-setting-ip6-config.c $(core)/nm-setting-olpc-mesh.c \
 	$(core)/nm-setting-ppp.c $(core)/nm-setting-pppoe.c \
 	$(core)/nm-setting-serial.c $(core)/nm-setting-team-port.c \
@@ -150,14 +151,17 @@ am__libnm_core_la_SOURCES_DIST = $(core_build)/nm-core-enum-types.c \
 	$(core)/nm-setting-wireless.c $(core)/nm-setting.c \
 	$(core)/nm-simple-connection.c $(core)/nm-utils.c \
 	$(core)/crypto.h $(core)/nm-connection-private.h \
-	$(core)/nm-core-internal.h $(core)/nm-property-compare.h \
+	$(core)/nm-core-internal.h $(core)/nm-keyfile-internal.h \
+	$(core)/nm-keyfile-utils.h $(core)/nm-property-compare.h \
 	$(core)/nm-setting-private.h $(core)/nm-utils-private.h \
 	crypto_gnutls.c crypto_nss.c
 am__objects_1 = nm-core-enum-types.lo crypto.lo nm-connection.lo \
-	nm-errors.lo nm-property-compare.lo nm-setting-8021x.lo \
-	nm-setting-adsl.lo nm-setting-bluetooth.lo nm-setting-bond.lo \
-	nm-setting-bridge-port.lo nm-setting-bridge.lo \
-	nm-setting-cdma.lo nm-setting-connection.lo nm-setting-dcb.lo \
+	nm-errors.lo nm-keyfile-reader.lo nm-keyfile-utils.lo \
+	nm-keyfile-writer.lo nm-property-compare.lo \
+	nm-setting-8021x.lo nm-setting-adsl.lo nm-setting-bluetooth.lo \
+	nm-setting-bond.lo nm-setting-bridge-port.lo \
+	nm-setting-bridge.lo nm-setting-cdma.lo \
+	nm-setting-connection.lo nm-setting-dcb.lo \
 	nm-setting-generic.lo nm-setting-gsm.lo \
 	nm-setting-infiniband.lo nm-setting-ip-config.lo \
 	nm-setting-ip4-config.lo nm-setting-ip6-config.lo \
@@ -315,6 +319,7 @@ ACLOCAL = @ACLOCAL@
 ALL_LINGUAS = @ALL_LINGUAS@
 AMTAR = @AMTAR@
 AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AM_TESTS_FD_REDIRECT = @AM_TESTS_FD_REDIRECT@
 AR = @AR@
 AUTOCONF = @AUTOCONF@
 AUTOHEADER = @AUTOHEADER@
@@ -428,6 +433,7 @@ LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
 LIPO = @LIPO@
 LN_S = @LN_S@
+LOG_DRIVER = @LOG_DRIVER@
 LTLIBICONV = @LTLIBICONV@
 LTLIBINTL = @LTLIBINTL@
 LTLIBOBJS = @LTLIBOBJS@
@@ -622,6 +628,8 @@ libnm_core_private_headers = \
 	$(core)/crypto.h			\
 	$(core)/nm-connection-private.h		\
 	$(core)/nm-core-internal.h		\
+	$(core)/nm-keyfile-internal.h       \
+	$(core)/nm-keyfile-utils.h          \
 	$(core)/nm-property-compare.h		\
 	$(core)/nm-setting-private.h		\
 	$(core)/nm-utils-private.h
@@ -631,6 +639,9 @@ libnm_core_sources = \
 	$(core)/crypto.c			\
 	$(core)/nm-connection.c			\
 	$(core)/nm-errors.c			\
+	$(core)/nm-keyfile-reader.c         \
+	$(core)/nm-keyfile-utils.c          \
+	$(core)/nm-keyfile-writer.c         \
 	$(core)/nm-property-compare.c		\
 	$(core)/nm-setting-8021x.c		\
 	$(core)/nm-setting-adsl.c		\
@@ -742,6 +753,9 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-connection.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-core-enum-types.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-errors.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-keyfile-reader.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-keyfile-utils.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-keyfile-writer.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-property-compare.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-setting-8021x.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-setting-adsl.Plo@am__quote@
@@ -823,6 +837,27 @@ nm-errors.lo: $(core)/nm-errors.c
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
 @am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nm-errors.lo `test -f '$(core)/nm-errors.c' || echo '$(srcdir)/'`$(core)/nm-errors.c
 
+nm-keyfile-reader.lo: $(core)/nm-keyfile-reader.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nm-keyfile-reader.lo -MD -MP -MF $(DEPDIR)/nm-keyfile-reader.Tpo -c -o nm-keyfile-reader.lo `test -f '$(core)/nm-keyfile-reader.c' || echo '$(srcdir)/'`$(core)/nm-keyfile-reader.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/nm-keyfile-reader.Tpo $(DEPDIR)/nm-keyfile-reader.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$(core)/nm-keyfile-reader.c' object='nm-keyfile-reader.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nm-keyfile-reader.lo `test -f '$(core)/nm-keyfile-reader.c' || echo '$(srcdir)/'`$(core)/nm-keyfile-reader.c
+
+nm-keyfile-utils.lo: $(core)/nm-keyfile-utils.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nm-keyfile-utils.lo -MD -MP -MF $(DEPDIR)/nm-keyfile-utils.Tpo -c -o nm-keyfile-utils.lo `test -f '$(core)/nm-keyfile-utils.c' || echo '$(srcdir)/'`$(core)/nm-keyfile-utils.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/nm-keyfile-utils.Tpo $(DEPDIR)/nm-keyfile-utils.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$(core)/nm-keyfile-utils.c' object='nm-keyfile-utils.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nm-keyfile-utils.lo `test -f '$(core)/nm-keyfile-utils.c' || echo '$(srcdir)/'`$(core)/nm-keyfile-utils.c
+
+nm-keyfile-writer.lo: $(core)/nm-keyfile-writer.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nm-keyfile-writer.lo -MD -MP -MF $(DEPDIR)/nm-keyfile-writer.Tpo -c -o nm-keyfile-writer.lo `test -f '$(core)/nm-keyfile-writer.c' || echo '$(srcdir)/'`$(core)/nm-keyfile-writer.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/nm-keyfile-writer.Tpo $(DEPDIR)/nm-keyfile-writer.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$(core)/nm-keyfile-writer.c' object='nm-keyfile-writer.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nm-keyfile-writer.lo `test -f '$(core)/nm-keyfile-writer.c' || echo '$(srcdir)/'`$(core)/nm-keyfile-writer.c
+
 nm-property-compare.lo: $(core)/nm-property-compare.c
 @am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nm-property-compare.lo -MD -MP -MF $(DEPDIR)/nm-property-compare.Tpo -c -o nm-property-compare.lo `test -f '$(core)/nm-property-compare.c' || echo '$(srcdir)/'`$(core)/nm-property-compare.c
 @am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/nm-property-compare.Tpo $(DEPDIR)/nm-property-compare.Plo
diff --git a/libnm-core/Makefile.libnm-core b/libnm-core/Makefile.libnm-core
index fc6d779b..09a80c71 100644
--- a/libnm-core/Makefile.libnm-core
+++ b/libnm-core/Makefile.libnm-core
@@ -48,6 +48,8 @@ libnm_core_private_headers =			\
 	$(core)/crypto.h			\
 	$(core)/nm-connection-private.h		\
 	$(core)/nm-core-internal.h		\
+	$(core)/nm-keyfile-internal.h       \
+	$(core)/nm-keyfile-utils.h          \
 	$(core)/nm-property-compare.h		\
 	$(core)/nm-setting-private.h		\
 	$(core)/nm-utils-private.h
@@ -57,6 +59,9 @@ libnm_core_sources =				\
 	$(core)/crypto.c			\
 	$(core)/nm-connection.c			\
 	$(core)/nm-errors.c			\
+	$(core)/nm-keyfile-reader.c         \
+	$(core)/nm-keyfile-utils.c          \
+	$(core)/nm-keyfile-writer.c         \
 	$(core)/nm-property-compare.c		\
 	$(core)/nm-setting-8021x.c		\
 	$(core)/nm-setting-adsl.c		\
diff --git a/libnm-core/nm-core-enum-types.c b/libnm-core/nm-core-enum-types.c
index e4cf177c..cfb264cf 100644
--- a/libnm-core/nm-core-enum-types.c
+++ b/libnm-core/nm-core-enum-types.c
@@ -401,6 +401,8 @@ nm_device_state_reason_get_type (void)
         { NM_DEVICE_STATE_REASON_MODEM_AVAILABLE, "NM_DEVICE_STATE_REASON_MODEM_AVAILABLE", "modem-available" },
         { NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT, "NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT", "sim-pin-incorrect" },
         { NM_DEVICE_STATE_REASON_NEW_ACTIVATION, "NM_DEVICE_STATE_REASON_NEW_ACTIVATION", "new-activation" },
+        { NM_DEVICE_STATE_REASON_PARENT_CHANGED, "NM_DEVICE_STATE_REASON_PARENT_CHANGED", "parent-changed" },
+        { NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED, "NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED", "parent-managed-changed" },
         { 0, NULL, NULL }
       };
       GType g_define_type_id =
@@ -716,6 +718,26 @@ nm_setting_802_1x_ck_scheme_get_type (void)
   return g_define_type_id__volatile;
 }
 GType
+nm_setting_connection_autoconnect_slaves_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const GEnumValue values[] = {
+        { NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT, "NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT", "default" },
+        { NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_NO, "NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_NO", "no" },
+        { NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES, "NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES", "yes" },
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_enum_register_static (g_intern_static_string ("NMSettingConnectionAutoconnectSlaves"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+GType
 nm_setting_dcb_flags_get_type (void)
 {
   static volatile gsize g_define_type_id__volatile = 0;
diff --git a/libnm-core/nm-core-enum-types.h b/libnm-core/nm-core-enum-types.h
index 162b704e..abc646b2 100644
--- a/libnm-core/nm-core-enum-types.h
+++ b/libnm-core/nm-core-enum-types.h
@@ -61,6 +61,8 @@ GType nm_setting_802_1x_ck_format_get_type (void) G_GNUC_CONST;
 #define NM_TYPE_SETTING_802_1X_CK_FORMAT (nm_setting_802_1x_ck_format_get_type ())
 GType nm_setting_802_1x_ck_scheme_get_type (void) G_GNUC_CONST;
 #define NM_TYPE_SETTING_802_1X_CK_SCHEME (nm_setting_802_1x_ck_scheme_get_type ())
+GType nm_setting_connection_autoconnect_slaves_get_type (void) G_GNUC_CONST;
+#define NM_TYPE_SETTING_CONNECTION_AUTOCONNECT_SLAVES (nm_setting_connection_autoconnect_slaves_get_type ())
 GType nm_setting_dcb_flags_get_type (void) G_GNUC_CONST;
 #define NM_TYPE_SETTING_DCB_FLAGS (nm_setting_dcb_flags_get_type ())
 GType nm_setting_ip6_config_privacy_get_type (void) G_GNUC_CONST;
diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h
index 9f4b33e3..dd5151e0 100644
--- a/libnm-core/nm-core-internal.h
+++ b/libnm-core/nm-core-internal.h
@@ -110,18 +110,39 @@ GPtrArray *_nm_utils_copy_array (const GPtrArray *array,
                                  GDestroyNotify free_func);
 GPtrArray *_nm_utils_copy_object_array (const GPtrArray *array);
 
+gssize _nm_utils_ptrarray_find_first (gpointer *list, gssize len, gconstpointer needle);
+
+gssize _nm_utils_ptrarray_find_binary_search (gpointer *list, gsize len, gpointer needle, GCompareDataFunc cmpfcn, gpointer user_data);
+
 gboolean    _nm_utils_string_in_list   (const char *str,
                                         const char **valid_strings);
 
+gssize      _nm_utils_strv_find_first (char **list, gssize len, const char *needle);
+
+char **_nm_utils_strv_cleanup (char **strv,
+                               gboolean strip_whitespace,
+                               gboolean skip_empty,
+                               gboolean skip_repeated);
+
 char **     _nm_utils_strsplit_set (const char *str,
                                     const char *delimiters,
                                     int max_tokens);
 
+GSList *    _nm_utils_strv_to_slist (char **strv, gboolean deep_copy);
+char **     _nm_utils_slist_to_strv (GSList *slist, gboolean deep_copy);
+
+GPtrArray * _nm_utils_strv_to_ptrarray (char **strv);
+char **     _nm_utils_ptrarray_to_strv (GPtrArray *ptrarray);
+
 #define NM_UTILS_UUID_TYPE_LEGACY            0
 #define NM_UTILS_UUID_TYPE_VARIANT3          1
 
 char *nm_utils_uuid_generate_from_string (const char *s, gssize slen, int uuid_type, gpointer type_args);
 
+#define NM_UTILS_UUID_NS "b425e9fb-7598-44b4-9e3b-5a2e3aaa4905"
+
+char *_nm_utils_uuid_generate_from_strings (const char *string1, ...) G_GNUC_NULL_TERMINATED;
+
 void _nm_dbus_errors_init (void);
 
 extern gboolean _nm_utils_is_manager_process;
@@ -136,4 +157,6 @@ GByteArray *nm_utils_rsa_key_encrypt (const guint8 *data,
 int nm_utils_bond_mode_string_to_int (const char *mode);
 const char *nm_utils_bond_mode_int_to_string (int mode);
 
+gint64 _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback);
+
 #endif
diff --git a/libnm-core/nm-dbus-interface.h b/libnm-core/nm-dbus-interface.h
index 9bab80f3..ccc3b3a9 100644
--- a/libnm-core/nm-dbus-interface.h
+++ b/libnm-core/nm-dbus-interface.h
@@ -467,6 +467,8 @@ typedef enum {
  * @NM_DEVICE_STATE_REASON_MODEM_AVAILABLE: Modem now ready and available
  * @NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT: SIM PIN was incorrect
  * @NM_DEVICE_STATE_REASON_NEW_ACTIVATION: New connection activation was enqueued
+ * @NM_DEVICE_STATE_REASON_PARENT_CHANGED: the device's parent changed
+ * @NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED: the device parent's management changed
  *
  * Device state change reason codes
  *
@@ -534,6 +536,8 @@ typedef enum {
 	NM_DEVICE_STATE_REASON_MODEM_AVAILABLE = 58,
 	NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT = 59,
 	NM_DEVICE_STATE_REASON_NEW_ACTIVATION = 60,
+	NM_DEVICE_STATE_REASON_PARENT_CHANGED = 61,
+	NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED = 62,
 } NMDeviceStateReason;
 
 
diff --git a/libnm-core/nm-keyfile-internal.h b/libnm-core/nm-keyfile-internal.h
new file mode 100644
index 00000000..f4bb0796
--- /dev/null
+++ b/libnm-core/nm-keyfile-internal.h
@@ -0,0 +1,171 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager system settings service - keyfile plugin
+ *
+ * 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 Novell, Inc.
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_KEYFILE_INTERNAL_H__
+#define __NM_KEYFILE_INTERNAL_H__
+
+#include <glib.h>
+#include <sys/types.h>
+
+#include "nm-connection.h"
+#include "nm-setting-8021x.h"
+
+
+/*********************************************************/
+
+#define NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB "data:;base64,"
+#define NM_KEYFILE_CERT_SCHEME_PREFIX_PATH "file://"
+
+char *nm_keyfile_detect_unqualified_path_scheme (const char *base_dir,
+                                                 gconstpointer pdata,
+                                                 gsize data_len,
+                                                 gboolean consider_exists,
+                                                 gboolean *out_exists);
+
+typedef enum {
+	NM_KEYFILE_READ_TYPE_WARN               = 1,
+} NMKeyfileReadType;
+
+/**
+ * NMKeyfileReadHandler:
+ *
+ * Hook to nm_keyfile_read(). The user might fail the reading by setting
+ * @error.
+ *
+ * Returns: should return TRUE, if the reading was handled. Otherwise,
+ * a default action will be performed that depends on the @type.
+ * For %NM_KEYFILE_READ_TYPE_WARN type, the default action is doing nothing.
+ */
+typedef gboolean (*NMKeyfileReadHandler) (GKeyFile *keyfile,
+                                          NMConnection *connection,
+                                          NMKeyfileReadType type,
+                                          void *type_data,
+                                          void *user_data,
+                                          GError **error);
+
+typedef enum {
+	NM_KEYFILE_WARN_SEVERITY_DEBUG                  = 1000,
+	NM_KEYFILE_WARN_SEVERITY_INFO                   = 2000,
+	NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE      = 2901,
+	NM_KEYFILE_WARN_SEVERITY_WARN                   = 3000,
+} NMKeyfileWarnSeverity;
+
+/**
+ * NMKeyfileReadTypeDataWarn:
+ *
+ * this struct is passed as @type_data for the @NMKeyfileReadHandler of
+ * type %NM_KEYFILE_READ_TYPE_WARN.
+ */
+typedef struct {
+	/* might be %NULL, if the warning is not about a group. */
+	const char *group;
+
+	/* might be %NULL, if the warning is not about a setting. */
+	NMSetting *setting;
+
+	/* might be %NULL, if the warning is not about a property. */
+	const char *property_name;
+
+	NMKeyfileWarnSeverity severity;
+	const char *message;
+} NMKeyfileReadTypeDataWarn;
+
+
+NMConnection *nm_keyfile_read (GKeyFile *keyfile,
+                               const char *keyfile_name,
+                               const char *base_dir,
+                               NMKeyfileReadHandler handler,
+                               void *user_data,
+                               GError **error);
+
+/*********************************************************/
+
+typedef enum {
+	NM_KEYFILE_WRITE_TYPE_CERT              = 1,
+} NMKeyfileWriteType;
+
+/**
+ * NMKeyfileWriteHandler:
+ *
+ * This is a hook to tweak the serialization.
+ *
+ * Handler for certain properties or events that are not entirely contained
+ * within the keyfile or that might be serialized differently. The @type and
+ * @type_data arguments tell which kind of argument we have at hand.
+ *
+ * Currently only the type %NM_KEYFILE_WRITE_TYPE_CERT is supported, which provides
+ * @type_data as %NMKeyfileWriteTypeDataCert. However, this handler should be generic enough
+ * to support other types as well.
+ *
+ * This don't have to be only "properties". For example, nm_keyfile_read() uses
+ * a similar handler to push warnings to the caller.
+ *
+ * If the handler raises an error, it should set the @error value. This causes
+ * the an overall failure.
+ *
+ * Returns: whether the issue was handled. If the type was unhandled,
+ * a default action will be performed. This might be raise an error,
+ * do some fallback parsing, or do nothing.
+ */
+typedef gboolean (*NMKeyfileWriteHandler) (NMConnection *connection,
+                                           GKeyFile *keyfile,
+                                           NMKeyfileWriteType type,
+                                           void *type_data,
+                                           void *user_data,
+                                           GError **error);
+
+/**
+ * NMKeyfileWriteTypeDataCert:
+ *
+ * this struct is passed as @type_data for the @NMKeyfileWriteHandler of
+ * type %NM_KEYFILE_WRITE_TYPE_CERT.
+ */
+typedef struct {
+	NMSetting8021x *setting;
+	const char *property_name;
+
+	/* The following functions are helpers that simplify the implementation
+	 * of the handler. */
+	const char *suffix;
+	NMSetting8021xCKScheme (*scheme_func) (NMSetting8021x *setting);
+	NMSetting8021xCKFormat (*format_func) (NMSetting8021x *setting);
+	const char *           (*path_func)   (NMSetting8021x *setting);
+	GBytes *               (*blob_func)   (NMSetting8021x *setting);
+} NMKeyfileWriteTypeDataCert;
+
+
+GKeyFile *nm_keyfile_write (NMConnection *connection,
+                            NMKeyfileWriteHandler handler,
+                            void *user_data,
+                            GError **error);
+
+/*********************************************************/
+
+char *nm_keyfile_plugin_kf_get_string (GKeyFile *kf, const char *group, const char *key, GError **error);
+void nm_keyfile_plugin_kf_set_string (GKeyFile *kf, const char *group, const char *key, const char *value);
+
+void _nm_keyfile_copy (GKeyFile *dst, GKeyFile *src);
+gboolean _nm_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b);
+gboolean _nm_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order);
+gboolean _nm_keyfile_has_values (GKeyFile *keyfile);
+
+
+#endif /* __NM_KEYFILE_INTERNAL_H__ */
diff --git a/libnm-core/nm-keyfile-reader.c b/libnm-core/nm-keyfile-reader.c
new file mode 100644
index 00000000..353804f9
--- /dev/null
+++ b/libnm-core/nm-keyfile-reader.c
@@ -0,0 +1,1659 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager system settings service - keyfile plugin
+ *
+ * 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 - 2009 Novell, Inc.
+ * Copyright (C) 2008 - 2015 Red Hat, Inc.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include "nm-core-internal.h"
+#include "nm-macros-internal.h"
+#include "gsystem-local-alloc.h"
+#include "nm-glib-compat.h"
+#include "nm-keyfile-internal.h"
+#include "nm-keyfile-utils.h"
+#include "nm-setting-private.h"
+
+
+typedef struct {
+	NMConnection *connection;
+	GKeyFile *keyfile;
+	const char *base_dir;
+	NMKeyfileReadHandler handler;
+	void *user_data;
+	GError *error;
+	const char *group;
+	NMSetting *setting;
+} KeyfileReaderInfo;
+
+
+static void
+_handle_warn (KeyfileReaderInfo *info,
+              const char *property_name,
+              NMKeyfileWarnSeverity severity,
+              char *message)
+{
+	NMKeyfileReadTypeDataWarn type_data = {
+		.group = info->group,
+		.setting = info->setting,
+		.property_name = property_name,
+		.severity = severity,
+		.message = message,
+	};
+
+	info->handler (info->keyfile,
+	               info->connection,
+	               NM_KEYFILE_READ_TYPE_WARN,
+	               &type_data,
+	               info->user_data,
+	               &info->error);
+	g_free (message);
+}
+#define handle_warn(arg_info, arg_property_name, arg_severity, ...) \
+	({ \
+		KeyfileReaderInfo *_info = (arg_info); \
+		\
+		if (_info->handler) { \
+			_handle_warn (_info, (arg_property_name), (arg_severity), \
+			              g_strdup_printf (__VA_ARGS__)); \
+		} \
+		_info->error == NULL; \
+	})
+
+/* Some setting properties also contain setting names, such as
+ * NMSettingConnection's 'type' property (which specifies the base type of the
+ * connection, e.g. ethernet or wifi) or 'slave-type' (specifies type of slave
+ * connection, e.g. bond or bridge). This function handles translating those
+ * properties' values to the real setting name if they are an alias.
+ */
+static void
+setting_alias_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	char *s;
+	const char *key_setting_name;
+
+	s = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+	if (s) {
+		key_setting_name = nm_keyfile_plugin_get_setting_name_for_alias (s);
+		g_object_set (G_OBJECT (setting),
+		              key, key_setting_name ? key_setting_name : s,
+		              NULL);
+		g_free (s);
+	}
+}
+
+static void
+read_array_of_uint (GKeyFile *file,
+                    NMSetting *setting,
+                    const char *key)
+{
+	GArray *array = NULL;
+	gsize length;
+	int i;
+	gint *tmp;
+
+	tmp = nm_keyfile_plugin_kf_get_integer_list (file, nm_setting_get_name (setting), key, &length, NULL);
+	array = g_array_sized_new (FALSE, FALSE, sizeof (guint32), length);
+
+	for (i = 0; i < length; i++)
+		g_array_append_val (array, tmp[i]);
+
+	g_object_set (setting, key, array, NULL);
+	g_array_unref (array);
+}
+
+static gboolean
+get_one_int (KeyfileReaderInfo *info, const char *property_name, const char *str, guint32 max_val, guint32 *out)
+{
+	long tmp;
+	char *endptr;
+
+	g_return_val_if_fail (!info == !property_name, FALSE);
+
+	if (!str || !str[0]) {
+		if (property_name)
+			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("ignoring missing number"));
+		return FALSE;
+	}
+
+	errno = 0;
+	tmp = strtol (str, &endptr, 10);
+	if (errno || (tmp < 0) || (tmp > max_val) || *endptr != 0) {
+		if (property_name)
+			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("ignoring invalid number '%s'"),
+			            str);
+		return FALSE;
+	}
+
+	*out = (guint32) tmp;
+	return TRUE;
+}
+
+static gpointer
+build_address (KeyfileReaderInfo *info, int family, const char *address_str, guint32 plen, const char *property_name)
+{
+	NMIPAddress *addr;
+	GError *error = NULL;
+
+	g_return_val_if_fail (address_str, NULL);
+
+	addr = nm_ip_address_new (family, address_str, plen, &error);
+	if (!addr) {
+		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid %s address: %s"),
+		            family == AF_INET ? "IPv4" : "IPv6", error->message);
+		g_error_free (error);
+	}
+
+	return addr;
+}
+
+static gpointer
+build_route (KeyfileReaderInfo *info,
+             const char *property_name,
+             int family,
+             const char *dest_str, guint32 plen,
+             const char *gateway_str, const char *metric_str)
+{
+	NMIPRoute *route;
+	guint32 metric = 0;
+	GError *error = NULL;
+
+	g_return_val_if_fail (plen, NULL);
+	g_return_val_if_fail (dest_str, NULL);
+
+	/* Next hop */
+	if (gateway_str && gateway_str[0]) {
+		if (!nm_utils_ipaddr_valid (family, gateway_str)) {
+			/* Try workaround for routes written by broken keyfile writer.
+			 * Due to bug bgo#719851, an older version of writer would have
+			 * written "a:b:c:d::/plen,metric" if the gateway was ::, instead
+			 * of "a:b:c:d::/plen,,metric" or "a:b:c:d::/plen,::,metric"
+			 * Try workaround by interpreting gateway_str as metric to accept such
+			 * invalid routes. This broken syntax should not be not officially
+			 * supported.
+			 **/
+			if (   family == AF_INET6
+			    && !metric_str
+			    && get_one_int (NULL, NULL, gateway_str, G_MAXUINT32, &metric))
+				gateway_str = NULL;
+			else {
+				if (!info->error) {
+					handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+					             _("ignoring invalid gateway '%s' for %s route"),
+					             gateway_str, family == AF_INET ? "IPv4" : "IPv6");
+				}
+				return NULL;
+			}
+		}
+	} else
+		gateway_str = NULL;
+
+	/* parse metric, default to 0 */
+	if (metric_str) {
+		if (!get_one_int (info, property_name, metric_str, G_MAXUINT32, &metric))
+			return NULL;
+	}
+
+	route = nm_ip_route_new (family, dest_str, plen, gateway_str,
+	                         metric ? (gint64) metric : -1,
+	                         &error);
+	if (!route) {
+		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid %s route: %s"),
+		             family == AF_INET ? "IPv4" : "IPv6",
+		             error->message);
+		g_error_free (error);
+	}
+
+	return route;
+}
+
+/* On success, returns pointer to the zero-terminated field (original @current).
+ * The @current * pointer target is set to point to the rest of the input
+ * or %NULL if there is no more input. Sets error to %NULL for convenience.
+ *
+ * On failure, returns %NULL (unspecified). The @current pointer target is
+ * resets to its original value to allow skipping fields. The @error target
+ * is set to the character that breaks the parsing or %NULL if @current was %NULL.
+ *
+ * When @current target is %NULL, gracefully fail returning %NULL while
+ * leaving the @current target %NULL end setting @error to %NULL;
+ */
+static char *
+read_field (char **current, char **error, const char *characters, const char *delimiters)
+{
+	char *start;
+
+	g_return_val_if_fail (current, NULL);
+	g_return_val_if_fail (error, NULL);
+	g_return_val_if_fail (characters, NULL);
+	g_return_val_if_fail (delimiters, NULL);
+
+	*error = NULL;
+
+	if (!*current) {
+		/* graceful failure, leave '*current' NULL */
+		return NULL;
+	}
+
+	/* fail on empty input */
+	if (!**current)
+		return NULL;
+
+	/* remember beginning of input */
+	start = *current;
+
+	while (**current && strchr (characters, **current))
+		(*current)++;
+	if (**current)
+		if (strchr (delimiters, **current)) {
+			/* success, more data available */
+			*(*current)++ = '\0';
+			return start;
+		} else {
+			/* error, bad character */
+			*error = *current;
+			*current = start;
+			return NULL;
+		}
+	else {
+		/* success, end of input */
+		*current = NULL;
+		return start;
+	}
+}
+
+#define IP_ADDRESS_CHARS "0123456789abcdefABCDEF:.%"
+#define DIGITS "0123456789"
+#define DELIMITERS "/;,"
+
+
+/* The following IPv4 and IPv6 address formats are supported:
+ *
+ * address (DEPRECATED)
+ * address/plen
+ * address/gateway (DEPRECATED)
+ * address/plen,gateway
+ *
+ * The following IPv4 and IPv6 route formats are supported:
+ *
+ * address/plen (NETWORK dev DEVICE)
+ * address/plen,gateway (NETWORK via GATEWAY dev DEVICE)
+ * address/plen,,metric (NETWORK dev DEVICE metric METRIC)
+ * address/plen,gateway,metric (NETWORK via GATEWAY dev DEVICE metric METRIC)
+ *
+ * For backward, forward and sideward compatibility, slash (/),
+ * semicolon (;) and comma (,) are interchangable. The choice of
+ * separator in the above examples is therefore not significant.
+ *
+ * Leaving out the prefix length is discouraged and DEPRECATED. The
+ * default value of IPv6 prefix length was 64 and has not been
+ * changed. The default for IPv4 is now 24, which is the closest
+ * IPv4 equivalent. These defaults may just as well be changed to
+ * match the iproute2 defaults (32 for IPv4 and 128 for IPv6).
+ */
+static gpointer
+read_one_ip_address_or_route (KeyfileReaderInfo *info,
+                              const char *property_name,
+                              const char *setting_name,
+                              const char *key_name,
+                              gboolean ipv6,
+                              gboolean route,
+                              char **out_gateway,
+                              NMSetting *setting)
+{
+	guint32 plen = G_MAXUINT32;
+	gpointer result;
+	char *address_str, *plen_str, *gateway_str, *metric_str, *current, *error;
+	gs_free char *value = NULL, *value_orig = NULL;
+
+#define VALUE_ORIG()   (value_orig ? value_orig : (value_orig = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key_name, NULL)))
+
+	current = value = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key_name, NULL);
+	if (!value)
+		return NULL;
+
+	/* get address field */
+	address_str = read_field (&current, &error, IP_ADDRESS_CHARS, DELIMITERS);
+	if (error) {
+		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("unexpected character '%c' for address %s: '%s' (position %td)"),
+		             *error, key_name, VALUE_ORIG (), error - current);
+		return NULL;
+	}
+	/* get prefix length field (skippable) */
+	plen_str = read_field (&current, &error, DIGITS, DELIMITERS);
+	/* get gateway field */
+	gateway_str = read_field (&current, &error, IP_ADDRESS_CHARS, DELIMITERS);
+	if (error) {
+		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("unexpected character '%c' for %s: '%s' (position %td)"),
+		             *error, key_name, VALUE_ORIG (), error - current);
+		return NULL;
+	}
+	/* for routes, get metric */
+	if (route) {
+		metric_str = read_field (&current, &error, DIGITS, DELIMITERS);
+		if (error) {
+			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("unexpected character '%c' in prefix length for %s: '%s' (position %td)"),
+			             *error, key_name, VALUE_ORIG (), error - current);
+			return NULL;
+		}
+	} else
+		metric_str = NULL;
+	if (current) {
+		/* there is still some data */
+		if (*current) {
+			/* another field follows */
+			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("garbage at the end of value %s: '%s'"),
+			             key_name, VALUE_ORIG ());
+			return NULL;
+		} else {
+			/* semicolon at the end of input */
+			if (!handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_INFO,
+			                  _("deprecated semicolon at the end of value %s: '%s'"),
+			                  key_name, VALUE_ORIG ()))
+				return NULL;
+		}
+	}
+
+#define DEFAULT_PREFIX(for_route, for_ipv6) ( (for_route) ? ( (for_ipv6) ? 128 : 24 ) : ( (for_ipv6) ? 64 : 24 ) )
+
+	/* parse plen, fallback to defaults */
+	if (plen_str) {
+		if (!get_one_int (info, property_name, plen_str, ipv6 ? 128 : 32, &plen)
+		    || (route && plen == 0)) {
+			plen = DEFAULT_PREFIX (route, ipv6);
+			if (   info->error
+			    || !handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                     _("invalid prefix length for %s '%s', defaulting to %d"),
+			                     key_name, VALUE_ORIG (), plen))
+				return NULL;
+		}
+	} else {
+		plen = DEFAULT_PREFIX (route, ipv6);
+		if (!handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+		                  _("missing prefix length for %s '%s', defaulting to %d"),
+		                  key_name, VALUE_ORIG (), plen))
+			return NULL;
+	}
+
+	/* build the appropriate data structure for NetworkManager settings */
+	if (route) {
+		result = build_route (info, property_name,
+		                      ipv6 ? AF_INET6 : AF_INET,
+		                      address_str, plen, gateway_str, metric_str);
+	} else {
+		result = build_address (info, ipv6 ? AF_INET6 : AF_INET,
+		                        address_str, plen, property_name);
+		if (!result)
+			return NULL;
+		if (out_gateway && gateway_str)
+			*out_gateway = g_strdup (gateway_str);
+	}
+
+#undef VALUE_ORIG
+
+	return result;
+}
+
+static void
+ip_address_or_route_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	gboolean ipv6 = !strcmp (setting_name, "ipv6");
+	gboolean routes = !strcmp (key, "routes");
+	static const char *key_names_routes[] = { "route", "routes", NULL };
+	static const char *key_names_addresses[] = { "address", "addresses", NULL };
+	const char **key_names = routes ? key_names_routes : key_names_addresses;
+	char *gateway = NULL;
+	GPtrArray *list;
+	GDestroyNotify free_func;
+	int i;
+
+	if (routes)
+		free_func = (GDestroyNotify) nm_ip_route_unref;
+	else
+		free_func = (GDestroyNotify) nm_ip_address_unref;
+	list = g_ptr_array_new_with_free_func (free_func);
+
+	for (i = -1; i < 1000; i++) {
+		const char **key_basename;
+
+		for (key_basename = key_names; *key_basename; key_basename++) {
+			char *key_name;
+			gpointer item;
+
+			/* -1 means no suffix */
+			if (i >= 0)
+				key_name = g_strdup_printf ("%s%d", *key_basename, i);
+			else
+				key_name = g_strdup (*key_basename);
+
+			item = read_one_ip_address_or_route (info, key, setting_name, key_name, ipv6, routes,
+			                                     gateway ? NULL : &gateway, setting);
+			g_free (key_name);
+
+			if (info->error) {
+				g_ptr_array_unref (list);
+				g_free (gateway);
+				return;
+			}
+			if (item)
+				g_ptr_array_add (list, item);
+
+		}
+	}
+
+	if (list->len >= 1)
+		g_object_set (setting, key, list, NULL);
+
+	if (gateway) {
+		g_object_set (setting, "gateway", gateway, NULL);
+		g_free (gateway);
+	}
+
+	g_ptr_array_unref (list);
+}
+
+static void
+ip4_dns_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GPtrArray *array;
+	gsize length;
+	char **list, **iter;
+	int ret;
+
+	list = nm_keyfile_plugin_kf_get_string_list (info->keyfile, setting_name, key, &length, NULL);
+	if (!list || !g_strv_length (list))
+		return;
+
+	array = g_ptr_array_sized_new (length + 1);
+	for (iter = list; *iter; iter++) {
+		guint32 addr;
+
+		ret = inet_pton (AF_INET, *iter, &addr);
+		if (ret <= 0) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("ignoring invalid DNS server IPv4 address '%s'"),
+			                  *iter)) {
+				g_ptr_array_unref (array);
+				g_strfreev (list);
+				return;
+			}
+			continue;
+		}
+
+		g_ptr_array_add (array, *iter);
+	}
+	g_ptr_array_add (array, NULL);
+
+	g_object_set (setting, key, array->pdata, NULL);
+	g_ptr_array_unref (array);
+	g_strfreev (list);
+}
+
+static void
+ip6_dns_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GPtrArray *array = NULL;
+	gsize length;
+	char **list, **iter;
+	int ret;
+
+	list = nm_keyfile_plugin_kf_get_string_list (info->keyfile, setting_name, key, &length, NULL);
+	if (!list || !g_strv_length (list))
+		return;
+
+	array = g_ptr_array_sized_new (length + 1);
+
+	for (iter = list; *iter; iter++) {
+		struct in6_addr addr;
+
+		ret = inet_pton (AF_INET6, *iter, &addr);
+		if (ret <= 0) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("ignoring invalid DNS server IPv6 address '%s'"),
+			                  *iter)) {
+				g_ptr_array_unref (array);
+				g_strfreev (list);
+				return;
+			}
+			continue;
+		}
+
+		g_ptr_array_add (array, *iter);
+	}
+	g_ptr_array_add (array, NULL);
+
+	g_object_set (setting, key, array->pdata, NULL);
+	g_ptr_array_unref (array);
+	g_strfreev (list);
+}
+
+static void
+mac_address_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key, gsize enforce_length)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	char *tmp_string = NULL, *p, *mac_str;
+	gint *tmp_list;
+	GByteArray *array = NULL;
+	gsize length;
+
+	p = tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+	if (tmp_string && tmp_string[0]) {
+		/* Look for enough ':' characters to signify a MAC address */
+		guint i = 0;
+
+		while (*p) {
+			if (*p == ':')
+				i++;
+			p++;
+		}
+
+		if (enforce_length == 0 || enforce_length == i+1) {
+			/* If we found enough it's probably a string-format MAC address */
+			array = g_byte_array_sized_new (i+1);
+			g_byte_array_set_size (array, i+1);
+			if (!nm_utils_hwaddr_aton (tmp_string, array->data, array->len)) {
+				g_byte_array_unref (array);
+				array = NULL;
+			}
+		}
+	}
+	g_free (tmp_string);
+
+	if (array == NULL) {
+		/* Old format; list of ints */
+		tmp_list = nm_keyfile_plugin_kf_get_integer_list (info->keyfile, setting_name, key, &length, NULL);
+		if (length > 0 && (enforce_length == 0 || enforce_length == length)) {
+			gsize i;
+
+			array = g_byte_array_sized_new (length);
+			for (i = 0; i < length; i++) {
+				int val = tmp_list[i];
+				const guint8 v = (guint8) (val & 0xFF);
+
+				if (val < 0 || val > 255) {
+					handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+					             _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"),
+					             val);
+					g_byte_array_free (array, TRUE);
+					g_free (tmp_list);
+					return;
+				}
+				g_byte_array_append (array, &v, 1);
+			}
+		}
+		g_free (tmp_list);
+	}
+
+	if (!array) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid MAC address"));
+		return;
+	}
+
+	mac_str = nm_utils_hwaddr_ntoa (array->data, array->len);
+	g_object_set (setting, key, mac_str, NULL);
+	g_free (mac_str);
+	g_byte_array_free (array, TRUE);
+}
+
+static void
+mac_address_parser_ETHER (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	mac_address_parser (info, setting, key, ETH_ALEN);
+}
+
+static void
+mac_address_parser_INFINIBAND (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	mac_address_parser (info, setting, key, INFINIBAND_ALEN);
+}
+
+static void
+read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key)
+{
+	char **keys, **iter;
+	char *value;
+	const char *setting_name = nm_setting_get_name (setting);
+
+	keys = nm_keyfile_plugin_kf_get_keys (file, setting_name, NULL, NULL);
+	if (!keys || !*keys)
+		return;
+
+	for (iter = keys; *iter; iter++) {
+		value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL);
+		if (!value)
+			continue;
+
+		if (NM_IS_SETTING_VPN (setting)) {
+			/* Add any item that's not a class property to the data hash */
+			if (!g_object_class_find_property (G_OBJECT_GET_CLASS (setting), *iter))
+				nm_setting_vpn_add_data_item (NM_SETTING_VPN (setting), *iter, value);
+		}
+		if (NM_IS_SETTING_BOND (setting)) {
+			if (strcmp (*iter, "interface-name"))
+				nm_setting_bond_add_option (NM_SETTING_BOND (setting), *iter, value);
+		}
+		g_free (value);
+	}
+	g_strfreev (keys);
+}
+
+static void
+unescape_semicolons (char *str)
+{
+	int i;
+	gsize len = strlen (str);
+
+	for (i = 0; i < len; i++) {
+		if (str[i] == '\\' && str[i+1] == ';') {
+			memmove(str + i, str + i + 1, len - (i + 1));
+			len--;
+		}
+		str[len] = '\0';
+	}
+}
+
+static GBytes *
+get_bytes (KeyfileReaderInfo *info,
+           const char *setting_name,
+           const char *key,
+           gboolean zero_terminate,
+           gboolean unescape_semicolon)
+{
+	GByteArray *array = NULL;
+	char *tmp_string;
+	gint *tmp_list;
+	gsize length;
+	int i;
+
+	if (!nm_keyfile_plugin_kf_has_key (info->keyfile, setting_name, key, NULL))
+		return NULL;
+
+	/* New format: just a string
+	 * Old format: integer list; e.g. 11;25;38;
+	 */
+	tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+	if (tmp_string) {
+		GRegex *regex;
+		GMatchInfo *match_info;
+		const char *pattern = "^[[:space:]]*[[:digit:]]{1,3}[[:space:]]*;([[:space:]]*[[:digit:]]{1,3}[[:space:]]*;)*([[:space:]]*)?$";
+
+		regex = g_regex_new (pattern, 0, 0, NULL);
+		g_regex_match (regex, tmp_string, 0, &match_info);
+		if (!g_match_info_matches (match_info)) {
+			/* Handle as a simple string (ie, new format) */
+			if (unescape_semicolon)
+				unescape_semicolons (tmp_string);
+			length = strlen (tmp_string);
+			if (zero_terminate)
+				length++;
+			array = g_byte_array_sized_new (length);
+			g_byte_array_append (array, (guint8 *) tmp_string, length);
+		}
+		g_match_info_free (match_info);
+		g_regex_unref (regex);
+		g_free (tmp_string);
+	}
+
+	if (!array) {
+		gboolean already_warned = FALSE;
+
+		/* Old format; list of ints */
+		tmp_list = nm_keyfile_plugin_kf_get_integer_list (info->keyfile, setting_name, key, &length, NULL);
+		if (!tmp_list) {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("ignoring invalid binary property"));
+			return NULL;
+		}
+		array = g_byte_array_sized_new (length);
+		for (i = 0; i < length; i++) {
+			int val = tmp_list[i];
+			unsigned char v = (unsigned char) (val & 0xFF);
+
+			if (val < 0 || val > 255) {
+				if (   !already_warned
+				    && !handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+				                     _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"),
+				                     val)) {
+					g_free (tmp_list);
+					g_byte_array_free (array, TRUE);
+					return NULL;
+				}
+				already_warned = TRUE;
+			} else
+				g_byte_array_append (array, (const unsigned char *) &v, sizeof (v));
+		}
+		g_free (tmp_list);
+	}
+
+	if (array->len == 0) {
+		g_byte_array_free (array, TRUE);
+		return NULL;
+	} else
+		return g_byte_array_free_to_bytes (array);
+}
+
+static void
+ssid_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GBytes *bytes;
+
+	bytes = get_bytes (info, setting_name, key, FALSE, TRUE);
+	if (bytes) {
+		g_object_set (setting, key, bytes, NULL);
+		g_bytes_unref (bytes);
+	} else if (!info->error) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid SSID"));
+	}
+}
+
+static void
+password_raw_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GBytes *bytes;
+
+	bytes = get_bytes (info, setting_name, key, FALSE, TRUE);
+	if (bytes) {
+		g_object_set (setting, key, bytes, NULL);
+		g_bytes_unref (bytes);
+	} else if (!info->error) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid raw password"));
+	}
+}
+
+static char *
+get_cert_path (const char *base_dir, const guint8 *cert_path, gsize cert_path_len)
+{
+	const char *base;
+	char *p = NULL, *path, *tmp;
+
+	g_return_val_if_fail (base_dir != NULL, NULL);
+	g_return_val_if_fail (cert_path != NULL, NULL);
+
+	base = path = g_malloc0 (cert_path_len + 1);
+	memcpy (path, cert_path, cert_path_len);
+
+	if (path[0] == '/')
+		return path;
+
+	p = strrchr (path, '/');
+	if (p)
+		base = p + 1;
+
+	tmp = g_build_path ("/", base_dir, base, NULL);
+	g_free (path);
+	return tmp;
+}
+
+static const char *certext[] = { ".pem", ".cert", ".crt", ".cer", ".p12", ".der", ".key" };
+
+static gboolean
+has_cert_ext (const char *path)
+{
+	int i;
+
+	for (i = 0; i < G_N_ELEMENTS (certext); i++) {
+		if (g_str_has_suffix (path, certext[i]))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+handle_as_scheme (KeyfileReaderInfo *info, GBytes *bytes, NMSetting *setting, const char *key)
+{
+	const char *data;
+	gsize data_len, bin_len;
+
+	data = g_bytes_get_data (bytes, &data_len);
+
+	g_return_val_if_fail (data && data_len > 0, FALSE);
+
+	/* to be a scheme, @data must be a zero terminated string, which is counted by @data_len */
+	if (data[data_len - 1] != '\0')
+		return FALSE;
+	data_len--;
+
+	/* It's the PATH scheme, can just set plain data.
+	 * In this case, @data_len includes */
+	if (   data_len >= STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)
+	    && g_str_has_prefix (data, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)) {
+		if (nm_setting_802_1x_check_cert_scheme (data, data_len + 1, NULL) == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+			const char *path = &data[STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)];
+			gs_free char *path_free = NULL;
+
+			if (path[0] != '/') {
+				/* we want to read absolute paths because we use keyfile as exchange
+				 * between different processes which might not have the same cwd. */
+				path = path_free = get_cert_path (info->base_dir, (const guint8 *) path,
+				                                  data_len - STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH));
+			}
+
+			g_object_set (setting, key, bytes, NULL);
+			if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
+				handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE,
+				             _("certificate or key file '%s' does not exist"),
+				             path);
+			}
+		} else {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid key/cert value path \"%s\""), data);
+		}
+		return TRUE;
+	}
+	if (   data_len > STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)
+	    && g_str_has_prefix (data, NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)) {
+		const char *cdata = data + STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB);
+		guchar *bin;
+		GBytes *bytes2;
+		gsize i;
+		gboolean valid_base64;
+
+		data_len -= STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB);
+
+		/* Let's be strict here. We expect valid base64, no funny stuff!!
+		 * We didn't write such invalid data ourselfes and refuse to read it as blob. */
+		if ((valid_base64 = (data_len % 4 == 0))) {
+			for (i = 0; i < data_len; i++) {
+				char c = cdata[i];
+
+				if (!(   (c >= 'a' && c <= 'z')
+				      || (c >= 'A' && c <= 'Z')
+				      || (c >= '0' && c <= '9')
+				      || (c == '+' || c == '/'))) {
+					if (c != '=' || i < data_len - 2)
+						valid_base64 = FALSE;
+					else {
+						for (; i < data_len; i++) {
+							if (cdata[i] != '=')
+								valid_base64 = FALSE;
+						}
+					}
+					break;
+				}
+			}
+		}
+		if (!valid_base64) {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid key/cert value data:;base64, is not base64"));
+			return TRUE;
+		}
+
+		bin = g_base64_decode (cdata, &bin_len);
+
+		g_return_val_if_fail (bin_len > 0, FALSE);
+		if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) {
+			/* The blob probably starts with "file://". Setting the cert data will confuse NMSetting8021x.
+			 * In fact this is a limitation of NMSetting8021x which does not support setting blobs that start
+			 * with file://. Just warn and return TRUE to signal that we ~handled~ the setting. */
+			g_free (bin);
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid key/cert value data:;base64,file://"));
+		} else {
+			bytes2 = g_bytes_new_take (bin, bin_len);
+			g_object_set (setting, key, bytes2, NULL);
+			g_bytes_unref (bytes2);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+char *
+nm_keyfile_detect_unqualified_path_scheme (const char *base_dir,
+                                           gconstpointer pdata,
+                                           gsize data_len,
+                                           gboolean consider_exists,
+                                           gboolean *out_exists)
+{
+	const char *data = pdata;
+	gboolean exists = FALSE;
+	gboolean success = FALSE;
+	gsize validate_len;
+	char *path;
+	GByteArray *tmp;
+
+	g_return_val_if_fail (base_dir && base_dir[0] == '/', NULL);
+
+	if (!pdata)
+		return NULL;
+	if (data_len == -1)
+		data_len = strlen (data);
+	if (data_len > 500 || data_len < 1)
+		return NULL;
+
+	/* If there's a trailing zero tell g_utf8_validate() to validate until the zero */
+	if (data[data_len - 1] == '\0') {
+		/* setting it to -1, would mean we accept data to contain NUL characters before the
+		 * end. Don't accept any NUL in [0 .. data_len-1[ . */
+		validate_len = data_len - 1;
+	} else
+		validate_len = data_len;
+	if (   validate_len == 0
+	    || g_utf8_validate ((const char *) data, validate_len, NULL) == FALSE)
+		 return NULL;
+
+	/* Might be a bare path without the file:// prefix; in that case
+	 * if it's an absolute path, use that, otherwise treat it as a
+	 * relative path to the current directory.
+	 */
+
+	path = get_cert_path (base_dir, (const guint8 *) data, data_len);
+	if (   !memchr (data, '/', data_len)
+	    && !has_cert_ext (path)) {
+		if (!consider_exists)
+			goto out;
+		exists = g_file_test (path, G_FILE_TEST_EXISTS);
+		if (!exists)
+			goto out;
+	} else if (out_exists)
+		exists = g_file_test (path, G_FILE_TEST_EXISTS);
+
+	/* Construct the proper value as required for the PATH scheme */
+	tmp = g_byte_array_sized_new (strlen (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) + strlen (path) + 1);
+	g_byte_array_append (tmp, (const guint8 *) NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, strlen (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH));
+	g_byte_array_append (tmp, (const guint8 *) path, strlen (path) + 1);
+	if (nm_setting_802_1x_check_cert_scheme (tmp->data, tmp->len, NULL) == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+		g_free (path);
+		path = (char *) g_byte_array_free (tmp, FALSE);
+		/* when returning TRUE, we must also be sure that @data_len does not look like
+		 * the deprecated format of list of integers. With this implementation that is the
+		 * case, as long as @consider_exists is FALSE. */
+		success = TRUE;
+	} else
+		g_byte_array_unref (tmp);
+
+out:
+	if (!success) {
+		g_free (path);
+		return NULL;
+	}
+	if (out_exists)
+		*out_exists = exists;
+	return path;
+}
+
+static gboolean
+handle_as_path (KeyfileReaderInfo *info,
+                GBytes *bytes,
+                NMSetting *setting,
+                const char *key)
+{
+	const guint8 *data;
+	gsize data_len;
+	char *path;
+	gboolean exists = FALSE;
+	GBytes *val;
+
+	data = g_bytes_get_data (bytes, &data_len);
+
+	path = nm_keyfile_detect_unqualified_path_scheme (info->base_dir, data, data_len, TRUE, &exists);
+	if (!path)
+		return FALSE;
+
+	/* Construct the proper value as required for the PATH scheme */
+	val = g_bytes_new_take (path, strlen (path) + 1);
+	g_object_set (setting, key, val, NULL);
+
+	/* Warn if the certificate didn't exist */
+	if (!exists) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE,
+		             _("certificate or key file '%s' does not exist"),
+		             path);
+	}
+	g_bytes_unref (val);
+
+	return TRUE;
+}
+
+static void
+cert_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	gs_unref_bytes GBytes *bytes = NULL;
+	gsize bin_len;
+	const char *bin;
+
+	bytes = get_bytes (info, setting_name, key, TRUE, FALSE);
+	if (bytes) {
+		/* Try as a path + scheme (ie, starts with "file://") */
+		if (handle_as_scheme (info, bytes, setting, key))
+			return;
+		if (info->error)
+			return;
+
+		/* If not, it might be a plain path */
+		if (handle_as_path (info, bytes, setting, key))
+			return;
+		if (info->error)
+			return;
+
+		bin = g_bytes_get_data (bytes, &bin_len);
+		if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) {
+			/* The blob probably starts with "file://" but contains invalid characters for a path.
+			 * Setting the cert data will confuse NMSetting8021x.
+			 * In fact, NMSetting8021x does not support setting such binary data, so just warn and
+			 * continue. */
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid key/cert value is not a valid blob"));
+		} else
+			g_object_set (setting, key, bytes, NULL);
+	} else if (!info->error) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("invalid key/cert value"));
+	}
+}
+
+static void
+parity_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	NMSettingSerialParity parity;
+	int int_val;
+	gs_free char *str_val = NULL;
+
+	/* Keyfile traditionally stored this as the ASCII value for 'E', 'o', or 'n'.
+	 * We now accept either that or the (case-insensitive) character itself (but
+	 * still always write it the old way, for backward compatibility).
+	 */
+	int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, NULL);
+	if (!int_val) {
+		str_val = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+		if (str_val) {
+			if (str_val[0] && !str_val[1])
+				int_val = str_val[0];
+			else {
+				/* This will hit the warning below */
+				int_val = 'X';
+			}
+		}
+	}
+
+	if (!int_val)
+		return;
+
+	switch (int_val) {
+	case 'E':
+	case 'e':
+		parity = NM_SETTING_SERIAL_PARITY_EVEN;
+		break;
+	case 'O':
+	case 'o':
+		parity = NM_SETTING_SERIAL_PARITY_ODD;
+		break;
+	case 'N':
+	case 'n':
+		parity = NM_SETTING_SERIAL_PARITY_NONE;
+		break;
+	default:
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("invalid parity value '%s'"),
+		             str_val ? str_val : "");
+		return;
+	}
+
+	g_object_set (setting, key, parity, NULL);
+}
+
+typedef struct {
+	const char *setting_name;
+	const char *key;
+	gboolean check_for_key;
+	void (*parser) (KeyfileReaderInfo *info, NMSetting *setting, const char *key);
+} KeyParser;
+
+/* A table of keys that require further parsing/conversion because they are
+ * stored in a format that can't be automatically read using the key's type.
+ * i.e. IPv4 addresses, which are stored in NetworkManager as guint32, but are
+ * stored in keyfiles as strings, eg "10.1.1.2" or IPv6 addresses stored
+ * in struct in6_addr internally, but as string in keyfiles.
+ */
+static KeyParser key_parsers[] = {
+	{ NM_SETTING_CONNECTION_SETTING_NAME,
+	  NM_SETTING_CONNECTION_TYPE,
+	  TRUE,
+	  setting_alias_parser },
+	{ NM_SETTING_BRIDGE_SETTING_NAME,
+	  NM_SETTING_BRIDGE_MAC_ADDRESS,
+	  TRUE,
+	  mac_address_parser_ETHER },
+	{ NM_SETTING_IP4_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_ADDRESSES,
+	  FALSE,
+	  ip_address_or_route_parser },
+	{ NM_SETTING_IP6_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_ADDRESSES,
+	  FALSE,
+	  ip_address_or_route_parser },
+	{ NM_SETTING_IP4_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_ROUTES,
+	  FALSE,
+	  ip_address_or_route_parser },
+	{ NM_SETTING_IP6_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_ROUTES,
+	  FALSE,
+	  ip_address_or_route_parser },
+	{ NM_SETTING_IP4_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_DNS,
+	  FALSE,
+	  ip4_dns_parser },
+	{ NM_SETTING_IP6_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_DNS,
+	  FALSE,
+	  ip6_dns_parser },
+	{ NM_SETTING_WIRED_SETTING_NAME,
+	  NM_SETTING_WIRED_MAC_ADDRESS,
+	  TRUE,
+	  mac_address_parser_ETHER },
+	{ NM_SETTING_WIRED_SETTING_NAME,
+	  NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
+	  TRUE,
+	  mac_address_parser_ETHER },
+	{ NM_SETTING_WIRELESS_SETTING_NAME,
+	  NM_SETTING_WIRELESS_MAC_ADDRESS,
+	  TRUE,
+	  mac_address_parser_ETHER },
+	{ NM_SETTING_WIRELESS_SETTING_NAME,
+	  NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS,
+	  TRUE,
+	  mac_address_parser_ETHER },
+	{ NM_SETTING_WIRELESS_SETTING_NAME,
+	  NM_SETTING_WIRELESS_BSSID,
+	  TRUE,
+	  mac_address_parser_ETHER },
+	{ NM_SETTING_BLUETOOTH_SETTING_NAME,
+	  NM_SETTING_BLUETOOTH_BDADDR,
+	  TRUE,
+	  mac_address_parser_ETHER },
+	{ NM_SETTING_INFINIBAND_SETTING_NAME,
+	  NM_SETTING_INFINIBAND_MAC_ADDRESS,
+	  TRUE,
+	  mac_address_parser_INFINIBAND },
+	{ NM_SETTING_WIMAX_SETTING_NAME,
+	  NM_SETTING_WIMAX_MAC_ADDRESS,
+	  TRUE,
+	  mac_address_parser_ETHER },
+	{ NM_SETTING_WIRELESS_SETTING_NAME,
+	  NM_SETTING_WIRELESS_SSID,
+	  TRUE,
+	  ssid_parser },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_PASSWORD_RAW,
+	  TRUE,
+	  password_raw_parser },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_CA_CERT,
+	  TRUE,
+	  cert_parser },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_CLIENT_CERT,
+	  TRUE,
+	  cert_parser },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_PRIVATE_KEY,
+	  TRUE,
+	  cert_parser },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_PHASE2_CA_CERT,
+	  TRUE,
+	  cert_parser },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_PHASE2_CLIENT_CERT,
+	  TRUE,
+	  cert_parser },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
+	  TRUE,
+	  cert_parser },
+	{ NM_SETTING_SERIAL_SETTING_NAME,
+	  NM_SETTING_SERIAL_PARITY,
+	  TRUE,
+	  parity_parser },
+	{ NULL, NULL, FALSE }
+};
+
+static void
+read_one_setting_value (NMSetting *setting,
+                        const char *key,
+                        const GValue *value,
+                        GParamFlags flags,
+                        gpointer user_data)
+{
+	KeyfileReaderInfo *info = user_data;
+	GKeyFile *keyfile = info->keyfile;
+	const char *setting_name;
+	int errsv;
+	GType type;
+	gs_free_error GError *err = NULL;
+	gboolean check_for_key = TRUE;
+	KeyParser *parser = &key_parsers[0];
+
+	if (info->error)
+		return;
+
+	/* Property is not writable */
+	if (!(flags & G_PARAM_WRITABLE))
+		return;
+
+	/* Setting name gets picked up from the keyfile's section name instead */
+	if (!strcmp (key, NM_SETTING_NAME))
+		return;
+
+	/* Don't read the NMSettingConnection object's 'read-only' property */
+	if (   NM_IS_SETTING_CONNECTION (setting)
+	    && !strcmp (key, NM_SETTING_CONNECTION_READ_ONLY))
+		return;
+
+	setting_name = nm_setting_get_name (setting);
+
+	/* Look through the list of handlers for non-standard format key values */
+	while (parser->setting_name) {
+		if (!strcmp (parser->setting_name, setting_name) && !strcmp (parser->key, key)) {
+			check_for_key = parser->check_for_key;
+			break;
+		}
+		parser++;
+	}
+
+	/* VPN properties don't have the exact key name */
+	if (NM_IS_SETTING_VPN (setting))
+		check_for_key = FALSE;
+
+	/* Bonding 'options' don't have the exact key name. The options are right under [bond] group. */
+	if (NM_IS_SETTING_BOND (setting))
+		check_for_key = FALSE;
+
+	/* Check for the exact key in the GKeyFile if required.  Most setting
+	 * properties map 1:1 to a key in the GKeyFile, but for those properties
+	 * like IP addresses and routes where more than one value is actually
+	 * encoded by the setting property, this won't be true.
+	 */
+	if (check_for_key && !nm_keyfile_plugin_kf_has_key (keyfile, setting_name, key, &err)) {
+		/* Key doesn't exist or an error ocurred, thus nothing to do. */
+		if (err) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("error loading setting value: %s"),
+			                  err->message))
+				goto out_error;
+		}
+		return;
+	}
+
+	/* If there's a custom parser for this key, handle that before the generic
+	 * parsers below.
+	 */
+	if (parser->setting_name) {
+		(*parser->parser) (info, setting, key);
+		return;
+	}
+
+	type = G_VALUE_TYPE (value);
+
+	if (type == G_TYPE_STRING) {
+		char *str_val;
+
+		str_val = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL);
+		g_object_set (setting, key, str_val, NULL);
+		g_free (str_val);
+	} else if (type == G_TYPE_UINT) {
+		int int_val;
+
+		int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, NULL);
+		if (int_val < 0) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("invalid negative value (%i)"),
+			                  int_val))
+				goto out_error;
+		}
+		g_object_set (setting, key, int_val, NULL);
+	} else if (type == G_TYPE_INT) {
+		int int_val;
+
+		int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, NULL);
+		g_object_set (setting, key, int_val, NULL);
+	} else if (type == G_TYPE_BOOLEAN) {
+		gboolean bool_val;
+
+		bool_val = nm_keyfile_plugin_kf_get_boolean (keyfile, setting_name, key, NULL);
+		g_object_set (setting, key, bool_val, NULL);
+	} else if (type == G_TYPE_CHAR) {
+		int int_val;
+
+		int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, NULL);
+		if (int_val < G_MININT8 || int_val > G_MAXINT8) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("invalid char value (%i)"),
+			                  int_val))
+				goto out_error;
+		}
+
+		g_object_set (setting, key, int_val, NULL);
+	} else if (type == G_TYPE_UINT64) {
+		char *tmp_str;
+		guint64 uint_val;
+
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, NULL);
+		uint_val = g_ascii_strtoull (tmp_str, NULL, 10);
+		g_free (tmp_str);
+		g_object_set (setting, key, uint_val, NULL);
+	} else if (type == G_TYPE_INT64) {
+		gs_free char *tmp_str = NULL;
+		gint64 int_val;
+
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, NULL);
+		int_val = _nm_utils_ascii_str_to_int64 (tmp_str, 10, G_MININT64, G_MAXINT64, 0);
+		errsv = errno;
+		if (errsv) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("invalid int64 value (%s)"),
+			                  tmp_str))
+				goto out_error;
+		} else
+			g_object_set (setting, key, int_val, NULL);
+	} else if (type == G_TYPE_BYTES) {
+		gint *tmp;
+		GByteArray *array;
+		GBytes *bytes;
+		gsize length;
+		int i;
+		gboolean already_warned = FALSE;
+
+		tmp = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_name, key, &length, NULL);
+
+		array = g_byte_array_sized_new (length);
+		for (i = 0; i < length; i++) {
+			int val = tmp[i];
+			unsigned char v = (unsigned char) (val & 0xFF);
+
+			if (val < 0 || val > 255) {
+				if (   !already_warned
+				    && !handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+				                     _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"),
+				                     val)) {
+					g_byte_array_unref (array);
+					g_free (tmp);
+					goto out_error;
+				}
+				already_warned = TRUE;
+			} else
+				g_byte_array_append (array, (const unsigned char *) &v, sizeof (v));
+		}
+
+		bytes = g_byte_array_free_to_bytes (array);
+		g_object_set (setting, key, bytes, NULL);
+		g_bytes_unref (bytes);
+		g_free (tmp);
+	} else if (type == G_TYPE_STRV) {
+		gchar **sa;
+		gsize length;
+
+		sa = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_name, key, &length, NULL);
+		g_object_set (setting, key, sa, NULL);
+		g_strfreev (sa);
+	} else if (type == G_TYPE_HASH_TABLE) {
+		read_hash_of_string (keyfile, setting, key);
+	} else if (type == G_TYPE_ARRAY) {
+		read_array_of_uint (keyfile, setting, key);
+	} else if (G_VALUE_HOLDS_FLAGS (value)) {
+		guint64 uint_val;
+
+		/* Flags are guint but GKeyFile has no uint reader, just uint64 */
+		uint_val = nm_keyfile_plugin_kf_get_uint64 (keyfile, setting_name, key, &err);
+		if (!err) {
+			if (uint_val <= G_MAXUINT)
+				g_object_set (setting, key, (guint) uint_val, NULL);
+			else {
+				if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+				                  _("too large FLAGS property '%s' (%llu)"),
+				                  G_VALUE_TYPE_NAME (value), (long long unsigned) uint_val))
+					goto out_error;
+			}
+		}
+	} else if (G_VALUE_HOLDS_ENUM (value)) {
+		gint int_val;
+
+		int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, &err);
+		if (!err)
+			g_object_set (setting, key, (gint) int_val, NULL);
+	} else {
+		if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		                 _("unhandled setting property type '%s'"),
+		                 G_VALUE_TYPE_NAME (value)))
+			goto out_error;
+	}
+out_error:
+	return;
+}
+
+static NMSetting *
+read_setting (KeyfileReaderInfo *info)
+{
+	const char *alias;
+	GType type;
+
+	alias = nm_keyfile_plugin_get_setting_name_for_alias (info->group);
+	if (!alias)
+		alias = info->group;
+
+	type = nm_setting_lookup_type (alias);
+	if (type) {
+		NMSetting *setting = g_object_new (type, NULL);
+
+		info->setting = setting;
+		nm_setting_enumerate_values (setting, read_one_setting_value, info);
+		info->setting = NULL;
+		if (!info->error)
+			return setting;
+
+		g_object_unref (setting);
+	} else {
+		handle_warn (info, NULL, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("invalid setting name '%s'"), info->group);
+	}
+
+	return NULL;
+}
+
+static void
+read_vpn_secrets (KeyfileReaderInfo *info, NMSettingVpn *s_vpn)
+{
+	char **keys, **iter;
+
+	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, VPN_SECRETS_GROUP, NULL, NULL);
+	for (iter = keys; *iter; iter++) {
+		char *secret;
+
+		secret = nm_keyfile_plugin_kf_get_string (info->keyfile, VPN_SECRETS_GROUP, *iter, NULL);
+		if (secret) {
+			nm_setting_vpn_add_secret (s_vpn, *iter, secret);
+			g_free (secret);
+		}
+	}
+	g_strfreev (keys);
+}
+
+/**
+ * nm_keyfile_read:
+ * @keyfile: the keyfile from which to create the connection
+ * @keyfile_name: keyfile allows missing connection id and uuid
+ *   and NetworkManager will create those when reading a connection
+ *   from file. By providing a filename you can reproduce that behavior,
+ *   but of course, it can only recreate the same UUID if you provide the
+ *   same filename as NetworkManager core daemon would.
+ *   @keyfile_name has only a relevance for setting the id or uuid if it
+ *   is missing and as fallback for @base_dir.
+ * @base_dir: when reading certificates from files with relative name,
+ *   the relative path is made absolute using @base_dir.
+ *   If @base_dir is missing, first try to get the pathname from @keyfile_name
+ *   (if it is given as absolute path). As last, fallback to the current path.
+ * @handler: read handler
+ * @user_data: user data for read handler
+ * @error: error
+ *
+ * Tries to create a NMConnection from a keyfile. The resulting keyfile is
+ * not normalized and might not even verify.
+ *
+ * Returns: (transfer full): on success, returns the created connection.
+ */
+NMConnection *
+nm_keyfile_read (GKeyFile *keyfile,
+                 const char *keyfile_name,
+                 const char *base_dir,
+                 NMKeyfileReadHandler handler,
+                 void *user_data,
+                 GError **error)
+{
+	NMConnection *connection = NULL;
+	NMSettingConnection *s_con;
+	NMSetting *setting;
+	gchar **groups;
+	gsize length;
+	int i;
+	gboolean vpn_secrets = FALSE;
+	KeyfileReaderInfo info = { 0 };
+	gs_free char *base_dir_free = NULL;
+
+	g_return_val_if_fail (keyfile, NULL);
+	g_return_val_if_fail (!error || !*error, NULL);
+
+	if (!base_dir) {
+		/* basedir is not given. Prefer it from the keyfile_name */
+		if (keyfile_name && keyfile_name[0] == '/') {
+			base_dir = base_dir_free = g_path_get_dirname (keyfile_name);
+		} else {
+			/* if keyfile is not given or not an absolute path, fallback
+			 * to current working directory. */
+			base_dir = base_dir_free = g_get_current_dir ();
+		}
+	} else
+		g_return_val_if_fail ("/", NULL);
+
+	connection = nm_simple_connection_new ();
+
+	info.connection = connection;
+	info.keyfile = (GKeyFile *) keyfile;
+	info.base_dir = base_dir;
+	info.handler = handler;
+	info.user_data = user_data;
+
+	groups = g_key_file_get_groups (keyfile, &length);
+	if (!groups)
+		length = 0;
+	for (i = 0; i < length; i++) {
+		/* Only read out secrets when needed */
+		if (!strcmp (groups[i], VPN_SECRETS_GROUP)) {
+			vpn_secrets = TRUE;
+			continue;
+		}
+
+		info.group = groups[i];
+		setting = read_setting (&info);
+		info.group = NULL;
+		if (info.error)
+			goto out_error;
+		if (setting)
+			nm_connection_add_setting (connection, setting);
+	}
+	g_strfreev (groups);
+
+	s_con = nm_connection_get_setting_connection (connection);
+	if (!s_con) {
+		s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
+		nm_connection_add_setting (connection, NM_SETTING (s_con));
+	}
+
+	/* Make sure that we have 'id' even if not explictly specified in the keyfile */
+	if (   keyfile_name
+	    && !nm_setting_connection_get_id (s_con)) {
+		char *base_name;
+
+		base_name = g_path_get_basename (keyfile_name);
+		g_object_set (s_con, NM_SETTING_CONNECTION_ID, base_name, NULL);
+		g_free (base_name);
+	}
+
+	/* Make sure that we have 'uuid' even if not explictly specified in the keyfile */
+	if (   keyfile_name
+	    && !nm_setting_connection_get_uuid (s_con)) {
+		char *hashed_uuid;
+
+		hashed_uuid = _nm_utils_uuid_generate_from_strings ("keyfile", keyfile_name, NULL);
+		g_object_set (s_con, NM_SETTING_CONNECTION_UUID, hashed_uuid, NULL);
+		g_free (hashed_uuid);
+	}
+
+	/* Make sure that we have 'interface-name' even if it was specified in the
+	 * "wrong" (ie, deprecated) group.
+	 */
+	if (   !nm_setting_connection_get_interface_name (s_con)
+	    && nm_setting_connection_get_connection_type (s_con)) {
+		char *interface_name;
+
+		interface_name = g_key_file_get_string (keyfile,
+		                                        nm_setting_connection_get_connection_type (s_con),
+		                                        "interface-name",
+		                                        NULL);
+		if (interface_name) {
+			g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, interface_name, NULL);
+			g_free (interface_name);
+		}
+	}
+
+	/* Handle vpn secrets after the 'vpn' setting was read */
+	if (vpn_secrets) {
+		NMSettingVpn *s_vpn;
+
+		s_vpn = nm_connection_get_setting_vpn (connection);
+		if (s_vpn) {
+			read_vpn_secrets (&info, s_vpn);
+			if (info.error)
+				goto out_error;
+		}
+	}
+
+	return connection;
+out_error:
+	g_propagate_error (error, info.error);
+	g_free (connection);
+	return NULL;
+}
diff --git a/libnm-core/nm-keyfile-utils.c b/libnm-core/nm-keyfile-utils.c
new file mode 100644
index 00000000..d04a3d1b
--- /dev/null
+++ b/libnm-core/nm-keyfile-utils.c
@@ -0,0 +1,342 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager system settings service
+ *
+ * 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.
+ *
+ * (C) Copyright 2010 Red Hat, Inc.
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gsystem-local-alloc.h"
+#include "nm-keyfile-utils.h"
+#include "nm-keyfile-internal.h"
+#include "nm-setting-wired.h"
+#include "nm-setting-wireless.h"
+#include "nm-setting-wireless-security.h"
+
+
+typedef struct {
+	const char *setting;
+	const char *alias;
+} SettingAlias;
+
+static const SettingAlias alias_list[] = {
+	{ NM_SETTING_WIRED_SETTING_NAME, "ethernet" },
+	{ NM_SETTING_WIRELESS_SETTING_NAME, "wifi" },
+	{ NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, "wifi-security" },
+};
+
+const char *
+nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name)
+{
+	guint i;
+
+	g_return_val_if_fail (setting_name != NULL, NULL);
+
+	for (i = 0; i < G_N_ELEMENTS (alias_list); i++) {
+		if (strcmp (setting_name, alias_list[i].setting) == 0)
+			return alias_list[i].alias;
+	}
+	return NULL;
+}
+
+const char *
+nm_keyfile_plugin_get_setting_name_for_alias (const char *alias)
+{
+	guint i;
+
+	g_return_val_if_fail (alias != NULL, NULL);
+
+	for (i = 0; i < G_N_ELEMENTS (alias_list); i++) {
+		if (strcmp (alias, alias_list[i].alias) == 0)
+			return alias_list[i].setting;
+	}
+	return NULL;
+}
+
+/**********************************************************************/
+
+/* List helpers */
+#define DEFINE_KF_LIST_WRAPPER(stype, get_ctype, set_ctype) \
+get_ctype \
+nm_keyfile_plugin_kf_get_##stype##_list (GKeyFile *kf, \
+                                         const char *group, \
+                                         const char *key, \
+                                         gsize *out_length, \
+                                         GError **error) \
+{ \
+	get_ctype list; \
+	const char *alias; \
+	GError *local = NULL; \
+ \
+	list = g_key_file_get_##stype##_list (kf, group, key, out_length, &local); \
+	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \
+		alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
+		if (alias) { \
+			g_clear_error (&local); \
+			list = g_key_file_get_##stype##_list (kf, alias, key, out_length, &local); \
+		} \
+	} \
+	if (local) \
+		g_propagate_error (error, local); \
+	return list; \
+} \
+ \
+void \
+nm_keyfile_plugin_kf_set_##stype##_list (GKeyFile *kf, \
+                                         const char *group, \
+                                         const char *key, \
+                                         set_ctype list[], \
+                                         gsize length) \
+{ \
+	const char *alias; \
+ \
+	alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
+	g_key_file_set_##stype##_list (kf, alias ? alias : group, key, list, length); \
+}
+
+DEFINE_KF_LIST_WRAPPER(integer, gint*, gint);
+DEFINE_KF_LIST_WRAPPER(string, gchar **, const gchar* const);
+
+/* Single value helpers */
+#define DEFINE_KF_WRAPPER(stype, get_ctype, set_ctype) \
+get_ctype \
+nm_keyfile_plugin_kf_get_##stype (GKeyFile *kf, \
+                                  const char *group, \
+                                  const char *key, \
+                                  GError **error) \
+{ \
+	get_ctype val; \
+	const char *alias; \
+	GError *local = NULL; \
+ \
+	val = g_key_file_get_##stype (kf, group, key, &local); \
+	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \
+		alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
+		if (alias) { \
+			g_clear_error (&local); \
+			val = g_key_file_get_##stype (kf, alias, key, &local); \
+		} \
+	} \
+	if (local) \
+		g_propagate_error (error, local); \
+	return val; \
+} \
+ \
+void \
+nm_keyfile_plugin_kf_set_##stype (GKeyFile *kf, \
+                                  const char *group, \
+                                  const char *key, \
+                                  set_ctype value) \
+{ \
+	const char *alias; \
+ \
+	alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
+	g_key_file_set_##stype (kf, alias ? alias : group, key, value); \
+}
+
+DEFINE_KF_WRAPPER(string, gchar*, const gchar*);
+DEFINE_KF_WRAPPER(integer, gint, gint);
+DEFINE_KF_WRAPPER(uint64, guint64, guint64);
+DEFINE_KF_WRAPPER(boolean, gboolean, gboolean);
+DEFINE_KF_WRAPPER(value, gchar*, const gchar*);
+
+
+gchar **
+nm_keyfile_plugin_kf_get_keys (GKeyFile *kf,
+                               const char *group,
+                               gsize *out_length,
+                               GError **error)
+{
+	gchar **keys;
+	const char *alias;
+	GError *local = NULL;
+
+	keys = g_key_file_get_keys (kf, group, out_length, &local);
+	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
+		alias = nm_keyfile_plugin_get_alias_for_setting_name (group);
+		if (alias) {
+			g_clear_error (&local);
+			keys = g_key_file_get_keys (kf, alias, out_length, &local);
+		}
+	}
+	if (local)
+		g_propagate_error (error, local);
+	return keys;
+}
+
+gboolean
+nm_keyfile_plugin_kf_has_key (GKeyFile *kf,
+                              const char *group,
+                              const char *key,
+                              GError **error)
+{
+	gboolean has;
+	const char *alias;
+	GError *local = NULL;
+
+	has = g_key_file_has_key (kf, group, key, &local);
+	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
+		alias = nm_keyfile_plugin_get_alias_for_setting_name (group);
+		if (alias) {
+			g_clear_error (&local);
+			has = g_key_file_has_key (kf, alias, key, &local);
+		}
+	}
+	if (local)
+		g_propagate_error (error, local);
+	return has;
+}
+
+/************************************************************************/
+
+void
+_nm_keyfile_copy (GKeyFile *dst, GKeyFile *src)
+{
+	gs_strfreev char **groups = NULL;
+	guint g, k;
+
+	groups = g_key_file_get_groups (src, NULL);
+	for (g = 0; groups && groups[g]; g++) {
+		const char *group = groups[g];
+		gs_strfreev char **keys = NULL;
+
+		keys = g_key_file_get_keys (src, group, NULL, NULL);
+		if (!keys)
+			continue;
+
+		for (k = 0; keys[k]; k++) {
+			const char *key = keys[k];
+			gs_free char *value = NULL;
+
+			value = g_key_file_get_value (src, group, key, NULL);
+			if (value)
+				g_key_file_set_value (dst, group, key, value);
+			else
+				g_key_file_remove_key (dst, group, key, NULL);
+		}
+	}
+}
+
+/************************************************************************/
+
+gboolean
+_nm_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b)
+{
+	gs_strfreev char **groups = NULL;
+	guint i, j;
+
+	if (kf_a == kf_b)
+		return TRUE;
+	if (!kf_a || !kf_b)
+		return FALSE;
+
+	groups = g_key_file_get_groups (kf_a, NULL);
+	for (i = 0; groups && groups[i]; i++) {
+		gs_strfreev char **keys = NULL;
+
+		keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL);
+		if (!keys)
+			continue;
+
+		for (j = 0; keys[j]; j++) {
+			gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL);
+			gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL);
+
+			if (g_strcmp0 (key_a, key_b) != 0)
+				return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+
+static gboolean
+_nm_keyfile_equals_ordered (GKeyFile *kf_a, GKeyFile *kf_b)
+{
+	gs_strfreev char **groups = NULL;
+	gs_strfreev char **groups_b = NULL;
+	guint i, j;
+
+	if (kf_a == kf_b)
+		return TRUE;
+	if (!kf_a || !kf_b)
+		return FALSE;
+
+	groups = g_key_file_get_groups (kf_a, NULL);
+	groups_b = g_key_file_get_groups (kf_b, NULL);
+	if (!groups && !groups_b)
+		return TRUE;
+	if (!groups || !groups_b)
+		return FALSE;
+	for (i = 0; groups[i] && groups_b[i] && !strcmp (groups[i], groups_b[i]); i++)
+		;
+	if (groups[i] || groups_b[i])
+		return FALSE;
+
+	for (i = 0; groups[i]; i++) {
+		gs_strfreev char **keys = NULL;
+		gs_strfreev char **keys_b = NULL;
+
+		keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL);
+		keys_b = g_key_file_get_keys (kf_b, groups[i], NULL, NULL);
+
+		if ((!keys) != (!keys_b))
+			return FALSE;
+		if (!keys)
+			continue;
+
+		for (j = 0; keys[j] && keys_b[j] && !strcmp (keys[j], keys_b[j]); j++)
+			;
+		if (keys[j] || keys_b[j])
+			return FALSE;
+
+		for (j = 0; keys[j]; j++) {
+			gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL);
+			gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL);
+
+			if (g_strcmp0 (key_a, key_b) != 0)
+				return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+gboolean
+_nm_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order)
+{
+	if (!consider_order) {
+		return    _nm_keyfile_a_contains_all_in_b (kf_a, kf_b)
+		       && _nm_keyfile_a_contains_all_in_b (kf_b, kf_a);
+	} else {
+		return _nm_keyfile_equals_ordered (kf_a, kf_b);
+	}
+}
+
+gboolean
+_nm_keyfile_has_values (GKeyFile *keyfile)
+{
+	gs_strfreev char **groups = NULL;
+
+	g_return_val_if_fail (keyfile, FALSE);
+
+	groups = g_key_file_get_groups (keyfile, NULL);
+	return groups && groups[0];
+}
diff --git a/libnm-core/nm-keyfile-utils.h b/libnm-core/nm-keyfile-utils.h
new file mode 100644
index 00000000..fd4334d1
--- /dev/null
+++ b/libnm-core/nm-keyfile-utils.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager system settings service
+ *
+ * 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.
+ *
+ * (C) Copyright 2010-2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_KEYFILE_UTILS_H__
+#define __NM_KEYFILE_UTILS_H__
+
+#include <glib.h>
+
+#define VPN_SECRETS_GROUP "vpn-secrets"
+
+const char *nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name);
+
+const char *nm_keyfile_plugin_get_setting_name_for_alias (const char *alias);
+
+/*********************************************************/
+
+/* List helpers */
+#define DEFINE_KF_LIST_WRAPPER_PROTO(stype, get_ctype, set_ctype) \
+get_ctype nm_keyfile_plugin_kf_get_##stype##_list (GKeyFile *kf, \
+                                                   const char *group, \
+                                                   const char *key, \
+                                                   gsize *out_length, \
+                                                   GError **error); \
+\
+void nm_keyfile_plugin_kf_set_##stype##_list  (GKeyFile *kf, \
+                                               const char *group, \
+                                               const char *key, \
+                                               set_ctype list[], \
+                                               gsize length);
+DEFINE_KF_LIST_WRAPPER_PROTO(integer, gint*, gint)
+DEFINE_KF_LIST_WRAPPER_PROTO(string, gchar**, const gchar* const)
+
+/* Single-value helpers */
+#define DEFINE_KF_WRAPPER_PROTO(stype, get_ctype, set_ctype) \
+get_ctype nm_keyfile_plugin_kf_get_##stype (GKeyFile *kf, \
+                                            const char *group, \
+                                            const char *key, \
+                                            GError **error); \
+\
+void nm_keyfile_plugin_kf_set_##stype (GKeyFile *kf, \
+                                       const char *group, \
+                                       const char *key, \
+                                       set_ctype value);
+DEFINE_KF_WRAPPER_PROTO(string, gchar*, const gchar*)
+DEFINE_KF_WRAPPER_PROTO(integer, gint, gint)
+DEFINE_KF_WRAPPER_PROTO(uint64, guint64, guint64)
+DEFINE_KF_WRAPPER_PROTO(boolean, gboolean, gboolean)
+DEFINE_KF_WRAPPER_PROTO(value, gchar*, const gchar*)
+
+/* Misc */
+gchar ** nm_keyfile_plugin_kf_get_keys    (GKeyFile *kf,
+                                           const char *group,
+                                           gsize *out_length,
+                                           GError **error);
+
+gboolean nm_keyfile_plugin_kf_has_key     (GKeyFile *kf,
+                                           const char *group,
+                                           const char *key,
+                                           GError **error);
+
+#endif  /* __NM_KEYFILE_UTILS_H__ */
+
diff --git a/libnm-core/nm-keyfile-writer.c b/libnm-core/nm-keyfile-writer.c
new file mode 100644
index 00000000..ff433b26
--- /dev/null
+++ b/libnm-core/nm-keyfile-writer.c
@@ -0,0 +1,751 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager system settings service - keyfile plugin
+ *
+ * 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 Novell, Inc.
+ * Copyright (C) 2008 - 2015 Red Hat, Inc.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include "nm-setting.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-ip4-config.h"
+#include "nm-setting-ip6-config.h"
+#include "nm-setting-vpn.h"
+#include "nm-setting-wired.h"
+#include "nm-setting-wireless.h"
+#include "nm-setting-ip4-config.h"
+#include "nm-setting-bluetooth.h"
+#include "nm-setting-8021x.h"
+#include "nm-utils.h"
+
+#include "gsystem-local-alloc.h"
+#include "nm-glib-compat.h"
+#include "nm-keyfile-internal.h"
+#include "nm-keyfile-utils.h"
+
+typedef struct {
+	NMConnection *connection;
+	GKeyFile *keyfile;
+	GError *error;
+	NMKeyfileWriteHandler handler;
+	void *user_data;
+} KeyfileWriterInfo;
+
+
+/* Some setting properties also contain setting names, such as
+ * NMSettingConnection's 'type' property (which specifies the base type of the
+ * connection, eg ethernet or wifi) or the 802-11-wireless setting's
+ * 'security' property which specifies whether or not the AP requires
+ * encrpytion.  This function handles translating those properties' values
+ * from the real setting name to the more-readable alias.
+ */
+static void
+setting_alias_writer (KeyfileWriterInfo *info,
+                      NMSetting *setting,
+                      const char *key,
+                      const GValue *value)
+{
+	const char *str, *alias;
+
+	str = g_value_get_string (value);
+	alias = nm_keyfile_plugin_get_alias_for_setting_name (str);
+	nm_keyfile_plugin_kf_set_string (info->keyfile,
+	                                 nm_setting_get_name (setting),
+	                                 key,
+	                                 alias ? alias : str);
+}
+
+static void
+write_array_of_uint (GKeyFile *file,
+                     NMSetting *setting,
+                     const char *key,
+                     const GValue *value)
+{
+	GArray *array;
+	int i;
+	int *tmp_array;
+
+	array = (GArray *) g_value_get_boxed (value);
+	if (!array || !array->len)
+		return;
+
+	tmp_array = g_new (gint, array->len);
+	for (i = 0; i < array->len; i++)
+		tmp_array[i] = g_array_index (array, int, i);
+
+	nm_keyfile_plugin_kf_set_integer_list (file, nm_setting_get_name (setting), key, tmp_array, array->len);
+	g_free (tmp_array);
+}
+
+static void
+dns_writer (KeyfileWriterInfo *info,
+            NMSetting *setting,
+            const char *key,
+            const GValue *value)
+{
+	char **list;
+
+	list = g_value_get_boxed (value);
+	if (list && list[0]) {
+		nm_keyfile_plugin_kf_set_string_list (info->keyfile, nm_setting_get_name (setting), key,
+		                                      (const char **) list, g_strv_length (list));
+	}
+}
+
+static void
+write_ip_values (GKeyFile *file,
+                 const char *setting_name,
+                 GPtrArray *array,
+                 const char *gateway,
+                 gboolean is_route)
+{
+	GString *output;
+	int family, i;
+	const char *addr, *gw;
+	guint32 plen, metric;
+	char key_name[30], *key_name_idx;
+
+	if (!array->len)
+		return;
+
+	family = !strcmp (setting_name, NM_SETTING_IP4_CONFIG_SETTING_NAME) ? AF_INET : AF_INET6;
+
+	strcpy (key_name, is_route ? "route" : "address");
+	key_name_idx = key_name + strlen (key_name);
+
+	output = g_string_sized_new (2*INET_ADDRSTRLEN + 10);
+	for (i = 0; i < array->len; i++) {
+		if (is_route) {
+			NMIPRoute *route = array->pdata[i];
+
+			addr = nm_ip_route_get_dest (route);
+			plen = nm_ip_route_get_prefix (route);
+			gw = nm_ip_route_get_next_hop (route);
+			metric = MAX (0, nm_ip_route_get_metric (route));
+		} else {
+			NMIPAddress *address = array->pdata[i];
+
+			addr = nm_ip_address_get_address (address);
+			plen = nm_ip_address_get_prefix (address);
+			gw = i == 0 ? gateway : NULL;
+			metric = 0;
+		}
+
+		g_string_set_size (output, 0);
+		g_string_append_printf (output, "%s/%u", addr, plen);
+		if (metric || gw) {
+			/* Older versions of the plugin do not support the form
+			 * "a.b.c.d/plen,,metric", so, we always have to write the
+			 * gateway, even if there isn't one.
+			 * The current version supports reading of the above form.
+			 */
+			if (!gw) {
+				if (family == AF_INET)
+					gw = "0.0.0.0";
+				else
+					gw = "::";
+			}
+
+			g_string_append_printf (output, ",%s", gw);
+			if (metric)
+				g_string_append_printf (output, ",%lu", (unsigned long) metric);
+		}
+
+		sprintf (key_name_idx, "%d", i + 1);
+		nm_keyfile_plugin_kf_set_string (file, setting_name, key_name, output->str);
+	}
+	g_string_free (output, TRUE);
+}
+
+static void
+addr_writer (KeyfileWriterInfo *info,
+             NMSetting *setting,
+             const char *key,
+             const GValue *value)
+{
+	GPtrArray *array;
+	const char *setting_name = nm_setting_get_name (setting);
+	const char *gateway = nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (setting));
+
+	array = (GPtrArray *) g_value_get_boxed (value);
+	if (array && array->len)
+		write_ip_values (info->keyfile, setting_name, array, gateway, FALSE);
+}
+
+static void
+ip4_addr_label_writer (KeyfileWriterInfo *info,
+                       NMSetting *setting,
+                       const char *key,
+                       const GValue *value)
+{
+	/* skip */
+}
+
+static void
+gateway_writer (KeyfileWriterInfo *info,
+                NMSetting *setting,
+                const char *key,
+                const GValue *value)
+{
+	/* skip */
+}
+
+static void
+route_writer (KeyfileWriterInfo *info,
+              NMSetting *setting,
+              const char *key,
+              const GValue *value)
+{
+	GPtrArray *array;
+	const char *setting_name = nm_setting_get_name (setting);
+
+	array = (GPtrArray *) g_value_get_boxed (value);
+	if (array && array->len)
+		write_ip_values (info->keyfile, setting_name, array, NULL, TRUE);
+}
+
+static void
+write_hash_of_string (GKeyFile *file,
+                      NMSetting *setting,
+                      const char *key,
+                      const GValue *value)
+{
+	GHashTableIter iter;
+	const char *property = NULL, *data = NULL;
+	const char *group_name = nm_setting_get_name (setting);
+	gboolean vpn_secrets = FALSE;
+
+	/* Write VPN secrets out to a different group to keep them separate */
+	if (NM_IS_SETTING_VPN (setting) && !strcmp (key, NM_SETTING_VPN_SECRETS)) {
+		group_name = VPN_SECRETS_GROUP;
+		vpn_secrets = TRUE;
+	}
+
+	g_hash_table_iter_init (&iter, (GHashTable *) g_value_get_boxed (value));
+	while (g_hash_table_iter_next (&iter, (gpointer *) &property, (gpointer *) &data)) {
+		gboolean write_item = TRUE;
+
+		/* Handle VPN secrets specially; they are nested in the property's hash;
+		 * we don't want to write them if the secret is not saved, not required,
+		 * or owned by a user's secret agent.
+		 */
+		if (vpn_secrets) {
+			NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
+
+			nm_setting_get_secret_flags (setting, property, &secret_flags, NULL);
+			if (secret_flags != NM_SETTING_SECRET_FLAG_NONE)
+				write_item = FALSE;
+		}
+
+		if (write_item)
+			nm_keyfile_plugin_kf_set_string (file, group_name, property, data);
+	}
+}
+
+static void
+ssid_writer (KeyfileWriterInfo *info,
+             NMSetting *setting,
+             const char *key,
+             const GValue *value)
+{
+	GBytes *bytes;
+	const guint8 *ssid_data;
+	gsize ssid_len;
+	const char *setting_name = nm_setting_get_name (setting);
+	gboolean new_format = TRUE;
+	unsigned int semicolons = 0;
+	int i, *tmp_array;
+	char *ssid;
+
+	g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES));
+
+	bytes = g_value_get_boxed (value);
+	if (!bytes)
+		return;
+	ssid_data = g_bytes_get_data (bytes, &ssid_len);
+	if (ssid_len == 0)
+		return;
+
+	/* Check whether each byte is printable.  If not, we have to use an
+	 * integer list, otherwise we can just use a string.
+	 */
+	for (i = 0; i < ssid_len; i++) {
+		char c = ssid_data[i] & 0xFF;
+		if (!g_ascii_isprint (c)) {
+			new_format = FALSE;
+			break;
+		}
+		if (c == ';')
+			semicolons++;
+	}
+
+	if (new_format) {
+		ssid = g_malloc0 (ssid_len + semicolons + 1);
+		if (semicolons == 0)
+			memcpy (ssid, ssid_data, ssid_len);
+		else {
+			/* Escape semicolons with backslashes to make strings
+			 * containing ';', such as '16;17;' unambiguous */
+			int j = 0;
+			for (i = 0; i < ssid_len; i++) {
+				if (ssid_data[i] == ';')
+					ssid[j++] = '\\';
+				ssid[j++] = ssid_data[i];
+			}
+		}
+		nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, ssid);
+		g_free (ssid);
+	} else {
+		tmp_array = g_new (gint, ssid_len);
+		for (i = 0; i < ssid_len; i++)
+			tmp_array[i] = (int) ssid_data[i];
+		nm_keyfile_plugin_kf_set_integer_list (info->keyfile, setting_name, key, tmp_array, ssid_len);
+		g_free (tmp_array);
+	}
+}
+
+static void
+password_raw_writer (KeyfileWriterInfo *info,
+                     NMSetting *setting,
+                     const char *key,
+                     const GValue *value)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GBytes *array;
+	int *tmp_array;
+	gsize i, len;
+	const char *data;
+
+	g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES));
+
+	array = (GBytes *) g_value_get_boxed (value);
+	if (!array)
+		return;
+	data = g_bytes_get_data (array, &len);
+	if (!data || !len)
+		return;
+
+	tmp_array = g_new (gint, len);
+	for (i = 0; i < len; i++)
+		tmp_array[i] = (int) data[i];
+	nm_keyfile_plugin_kf_set_integer_list (info->keyfile, setting_name, key, tmp_array, len);
+	g_free (tmp_array);
+}
+
+typedef struct ObjectType {
+	const char *key;
+	const char *suffix;
+	NMSetting8021xCKScheme (*scheme_func) (NMSetting8021x *setting);
+	NMSetting8021xCKFormat (*format_func) (NMSetting8021x *setting);
+	const char *           (*path_func)   (NMSetting8021x *setting);
+	GBytes *               (*blob_func)   (NMSetting8021x *setting);
+} ObjectType;
+
+static const ObjectType objtypes[10] = {
+	{ NM_SETTING_802_1X_CA_CERT,
+	  "ca-cert",
+	  nm_setting_802_1x_get_ca_cert_scheme,
+	  NULL,
+	  nm_setting_802_1x_get_ca_cert_path,
+	  nm_setting_802_1x_get_ca_cert_blob },
+
+	{ NM_SETTING_802_1X_PHASE2_CA_CERT,
+	  "inner-ca-cert",
+	  nm_setting_802_1x_get_phase2_ca_cert_scheme,
+	  NULL,
+	  nm_setting_802_1x_get_phase2_ca_cert_path,
+	  nm_setting_802_1x_get_phase2_ca_cert_blob },
+
+	{ NM_SETTING_802_1X_CLIENT_CERT,
+	  "client-cert",
+	  nm_setting_802_1x_get_client_cert_scheme,
+	  NULL,
+	  nm_setting_802_1x_get_client_cert_path,
+	  nm_setting_802_1x_get_client_cert_blob },
+
+	{ NM_SETTING_802_1X_PHASE2_CLIENT_CERT,
+	  "inner-client-cert",
+	  nm_setting_802_1x_get_phase2_client_cert_scheme,
+	  NULL,
+	  nm_setting_802_1x_get_phase2_client_cert_path,
+	  nm_setting_802_1x_get_phase2_client_cert_blob },
+
+	{ NM_SETTING_802_1X_PRIVATE_KEY,
+	  "private-key",
+	  nm_setting_802_1x_get_private_key_scheme,
+	  nm_setting_802_1x_get_private_key_format,
+	  nm_setting_802_1x_get_private_key_path,
+	  nm_setting_802_1x_get_private_key_blob },
+
+	{ NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
+	  "inner-private-key",
+	  nm_setting_802_1x_get_phase2_private_key_scheme,
+	  nm_setting_802_1x_get_phase2_private_key_format,
+	  nm_setting_802_1x_get_phase2_private_key_path,
+	  nm_setting_802_1x_get_phase2_private_key_blob },
+
+	{ NULL },
+};
+
+/**************************************************************************/
+
+static void
+cert_writer_default (NMConnection *connection,
+                     GKeyFile *file,
+                     NMKeyfileWriteTypeDataCert *cert_data)
+{
+	const char *setting_name = nm_setting_get_name (NM_SETTING (cert_data->setting));
+	NMSetting8021xCKScheme scheme;
+
+	scheme = cert_data->scheme_func (cert_data->setting);
+	if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+		const char *path;
+		char *path_free = NULL, *tmp;
+		gs_free char *base_dir = NULL;
+
+		path = cert_data->path_func (cert_data->setting);
+		g_assert (path);
+
+		/* If the path is relative, make it an absolute path.
+		 * Relative paths make a keyfile not easily usable in another
+		 * context. */
+		if (path[0] && path[0] != '/') {
+			base_dir = g_get_current_dir ();
+			path = path_free = g_strconcat (base_dir, "/", path, NULL);
+		} else
+			base_dir = g_path_get_dirname (path);
+
+		/* path cannot start with "file://" or "data:;base64,", because it is an absolute path.
+		 * Still, make sure that a prefix-less path will be recognized. This can happen
+		 * for example if the path is longer then 500 chars. */
+		tmp = nm_keyfile_detect_unqualified_path_scheme (base_dir, path, -1, FALSE, NULL);
+		if (tmp)
+			g_clear_pointer (&tmp, g_free);
+		else
+			path = tmp = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL);
+
+		/* Path contains at least a '/', hence it cannot be recognized as the old
+		 * binary format consisting of a list of integers. */
+
+		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->property_name, path);
+		g_free (tmp);
+		g_free (path_free);
+	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
+		GBytes *blob;
+		const guint8 *blob_data;
+		gsize blob_len;
+		char *blob_base64, *val;
+
+		blob = cert_data->blob_func (cert_data->setting);
+		g_assert (blob);
+		blob_data = g_bytes_get_data (blob, &blob_len);
+
+		blob_base64 = g_base64_encode (blob_data, blob_len);
+		val = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB, blob_base64, NULL);
+
+		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->property_name, val);
+		g_free (val);
+		g_free (blob_base64);
+	} else {
+		/* scheme_func() returns UNKNOWN in all other cases. The only valid case
+		 * where a scheme is allowed to be UNKNOWN, is unsetting the value. In this
+		 * case, we don't expect the writer to be called, because the default value
+		 * will not be serialized.
+		 * The only other reason for the scheme to be UNKNOWN is an invalid cert.
+		 * But our connection verifies, so that cannot happen either. */
+		g_return_if_reached ();
+	}
+}
+
+static void
+cert_writer (KeyfileWriterInfo *info,
+             NMSetting *setting,
+             const char *key,
+             const GValue *value)
+{
+	const ObjectType *objtype = NULL;
+	guint i;
+	NMKeyfileWriteTypeDataCert type_data = { 0 };
+
+	for (i = 0; i < G_N_ELEMENTS (objtypes) && objtypes[i].key; i++) {
+		if (g_strcmp0 (objtypes[i].key, key) == 0) {
+			objtype = &objtypes[i];
+			break;
+		}
+	}
+	if (!objtype)
+		g_return_if_reached ();
+
+	type_data.setting = NM_SETTING_802_1X (setting);
+	type_data.property_name = key;
+	type_data.suffix = objtype->suffix;
+	type_data.scheme_func = objtype->scheme_func;
+	type_data.format_func = objtype->format_func;
+	type_data.path_func = objtype->path_func;
+	type_data.blob_func = objtype->blob_func;
+
+	if (info->handler) {
+		if (info->handler (info->connection,
+		                   info->keyfile,
+		                   NM_KEYFILE_WRITE_TYPE_CERT,
+		                   &type_data,
+		                   info->user_data,
+		                   &info->error))
+			return;
+		if (info->error)
+			return;
+	}
+
+	cert_writer_default (info->connection, info->keyfile, &type_data);
+}
+
+/**************************************************************************/
+
+typedef struct {
+	const char *setting_name;
+	const char *key;
+	void (*writer) (KeyfileWriterInfo *info,
+	                NMSetting *setting,
+	                const char *key,
+	                const GValue *value);
+} KeyWriter;
+
+/* A table of keys that require further parsing/conversion because they are
+ * stored in a format that can't be automatically read using the key's type.
+ * i.e. IPv4 addresses, which are stored in NetworkManager as guint32, but are
+ * stored in keyfiles as strings, eg "10.1.1.2" or IPv6 addresses stored 
+ * in struct in6_addr internally, but as string in keyfiles.
+ */
+static KeyWriter key_writers[] = {
+	{ NM_SETTING_CONNECTION_SETTING_NAME,
+	  NM_SETTING_CONNECTION_TYPE,
+	  setting_alias_writer },
+	{ NM_SETTING_IP4_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_ADDRESSES,
+	  addr_writer },
+	{ NM_SETTING_IP4_CONFIG_SETTING_NAME,
+	  "address-labels",
+	  ip4_addr_label_writer },
+	{ NM_SETTING_IP6_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_ADDRESSES,
+	  addr_writer },
+	{ NM_SETTING_IP4_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_GATEWAY,
+	  gateway_writer },
+	{ NM_SETTING_IP6_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_GATEWAY,
+	  gateway_writer },
+	{ NM_SETTING_IP4_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_ROUTES,
+	  route_writer },
+	{ NM_SETTING_IP6_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_ROUTES,
+	  route_writer },
+	{ NM_SETTING_IP4_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_DNS,
+	  dns_writer },
+	{ NM_SETTING_IP6_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_DNS,
+	  dns_writer },
+	{ NM_SETTING_WIRELESS_SETTING_NAME,
+	  NM_SETTING_WIRELESS_SSID,
+	  ssid_writer },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_PASSWORD_RAW,
+	  password_raw_writer },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_CA_CERT,
+	  cert_writer },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_CLIENT_CERT,
+	  cert_writer },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_PRIVATE_KEY,
+	  cert_writer },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_PHASE2_CA_CERT,
+	  cert_writer },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_PHASE2_CLIENT_CERT,
+	  cert_writer },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
+	  cert_writer },
+	{ NULL, NULL, NULL }
+};
+
+static void
+write_setting_value (NMSetting *setting,
+                     const char *key,
+                     const GValue *value,
+                     GParamFlags flag,
+                     gpointer user_data)
+{
+	KeyfileWriterInfo *info = user_data;
+	const char *setting_name;
+	GType type = G_VALUE_TYPE (value);
+	KeyWriter *writer = &key_writers[0];
+	GParamSpec *pspec;
+
+	if (info->error)
+		return;
+
+	/* Setting name gets picked up from the keyfile's section name instead */
+	if (!strcmp (key, NM_SETTING_NAME))
+		return;
+
+	/* Don't write the NMSettingConnection object's 'read-only' property */
+	if (   NM_IS_SETTING_CONNECTION (setting)
+	    && !strcmp (key, NM_SETTING_CONNECTION_READ_ONLY))
+		return;
+
+	setting_name = nm_setting_get_name (setting);
+
+	/* If the value is the default value, remove the item from the keyfile */
+	pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), key);
+	if (pspec) {
+		if (g_param_value_defaults (pspec, (GValue *) value)) {
+			g_key_file_remove_key (info->keyfile, setting_name, key, NULL);
+			return;
+		}
+	}
+
+	/* Don't write secrets that are owned by user secret agents or aren't
+	 * supposed to be saved.  VPN secrets are handled specially though since
+	 * the secret flags there are in a third-level hash in the 'secrets'
+	 * property.
+	 */
+	if (pspec && (pspec->flags & NM_SETTING_PARAM_SECRET) && !NM_IS_SETTING_VPN (setting)) {
+		NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
+
+		if (!nm_setting_get_secret_flags (setting, key, &secret_flags, NULL))
+			g_assert_not_reached ();
+		if (secret_flags != NM_SETTING_SECRET_FLAG_NONE)
+			return;
+	}
+
+	/* Look through the list of handlers for non-standard format key values */
+	while (writer->setting_name) {
+		if (!strcmp (writer->setting_name, setting_name) && !strcmp (writer->key, key)) {
+			(*writer->writer) (info, setting, key, value);
+			return;
+		}
+		writer++;
+	}
+
+	if (type == G_TYPE_STRING) {
+		const char *str;
+
+		str = g_value_get_string (value);
+		if (str)
+			nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, str);
+	} else if (type == G_TYPE_UINT)
+		nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, (int) g_value_get_uint (value));
+	else if (type == G_TYPE_INT)
+		nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, g_value_get_int (value));
+	else if (type == G_TYPE_UINT64) {
+		char *numstr;
+
+		numstr = g_strdup_printf ("%" G_GUINT64_FORMAT, g_value_get_uint64 (value));
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr);
+		g_free (numstr);
+	} else if (type == G_TYPE_INT64) {
+		char *numstr;
+
+		numstr = g_strdup_printf ("%" G_GINT64_FORMAT, g_value_get_int64 (value));
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr);
+		g_free (numstr);
+	} else if (type == G_TYPE_BOOLEAN) {
+		nm_keyfile_plugin_kf_set_boolean (info->keyfile, setting_name, key, g_value_get_boolean (value));
+	} else if (type == G_TYPE_CHAR) {
+		nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, (int) g_value_get_schar (value));
+	} else if (type == G_TYPE_BYTES) {
+		GBytes *bytes;
+		const guint8 *data;
+		gsize len = 0;
+
+		bytes = g_value_get_boxed (value);
+		data = bytes ? g_bytes_get_data (bytes, &len) : NULL;
+
+		if (data != NULL && len > 0) {
+			int *tmp_array;
+			int i;
+
+			tmp_array = g_new (gint, len);
+			for (i = 0; i < len; i++)
+				tmp_array[i] = (int) data[i];
+
+			nm_keyfile_plugin_kf_set_integer_list (info->keyfile, setting_name, key, tmp_array, len);
+			g_free (tmp_array);
+		}
+	} else if (type == G_TYPE_STRV) {
+		char **array;
+
+		array = (char **) g_value_get_boxed (value);
+		nm_keyfile_plugin_kf_set_string_list (info->keyfile, setting_name, key, (const gchar **const) array, g_strv_length (array));
+	} else if (type == G_TYPE_HASH_TABLE) {
+		write_hash_of_string (info->keyfile, setting, key, value);
+	} else if (type == G_TYPE_ARRAY) {
+		write_array_of_uint (info->keyfile, setting, key, value);
+	} else if (G_VALUE_HOLDS_FLAGS (value)) {
+		/* Flags are guint but GKeyFile has no uint reader, just uint64 */
+		nm_keyfile_plugin_kf_set_uint64 (info->keyfile, setting_name, key, (guint64) g_value_get_flags (value));
+	} else if (G_VALUE_HOLDS_ENUM (value))
+		nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, (gint) g_value_get_enum (value));
+	else
+		g_warn_if_reached ();
+}
+
+GKeyFile *
+nm_keyfile_write (NMConnection *connection,
+                  NMKeyfileWriteHandler handler,
+                  void *user_data,
+                  GError **error)
+{
+	KeyfileWriterInfo info = { 0 };
+
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
+	g_return_val_if_fail (!error || !*error, NULL);
+
+	if (!nm_connection_verify (connection, error))
+		return NULL;
+
+	info.connection = connection;
+	info.keyfile = g_key_file_new ();
+	info.error = NULL;
+	info.handler = handler;
+	info.user_data = user_data;
+	nm_connection_for_each_setting_value (connection, write_setting_value, &info);
+
+	if (info.error) {
+		g_propagate_error (error, info.error);
+		g_key_file_unref (info.keyfile);
+		return NULL;
+	}
+	return info.keyfile;
+}
+
diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c
index 41559e0c..d53ae43b 100644
--- a/libnm-core/nm-setting-8021x.c
+++ b/libnm-core/nm-setting-8021x.c
@@ -31,6 +31,8 @@
 #include "nm-utils-private.h"
 #include "nm-setting-private.h"
 #include "nm-core-enum-types.h"
+#include "nm-macros-internal.h"
+#include "gsystem-local-alloc.h"
 
 /**
  * SECTION:nm-setting-8021x
@@ -60,8 +62,6 @@
  *       ISBN: 978-1587051548
  **/
 
-#define SCHEME_PATH "file://"
-
 G_DEFINE_TYPE_WITH_CODE (NMSetting8021x, nm_setting_802_1x, NM_TYPE_SETTING,
                          _nm_register_setting (802_1X, 2))
 NM_SETTING_REGISTER_TYPE (NM_TYPE_SETTING_802_1X)
@@ -400,25 +400,120 @@ nm_setting_802_1x_get_system_ca_certs (NMSetting8021x *setting)
 }
 
 static NMSetting8021xCKScheme
-get_cert_scheme (GBytes *bytes)
+get_cert_scheme (GBytes *bytes, GError **error)
 {
-	gconstpointer data;
+	const char *data;
 	gsize length;
 
-	if (!bytes)
+	if (!bytes) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("data missing"));
 		return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN;
+	}
 
 	data = g_bytes_get_data (bytes, &length);
-	if (!length)
+	return nm_setting_802_1x_check_cert_scheme (data, length, error);
+}
+
+/**
+ * nm_setting_802_1x_check_cert_scheme:
+ * @pdata: (allow-none): the data pointer
+ * @length: the length of the data
+ * @error: (allow-none): (out): validation reason
+ *
+ * Determines and verifies the blob type.
+ * When setting certificate properties of NMSetting8021x
+ * the blob must be not UNKNOWN (or NULL).
+ *
+ * Returns: the scheme of the blob or %NM_SETTING_802_1X_CK_SCHEME_UNKNOWN.
+ * For NULL it also returns NM_SETTING_802_1X_CK_SCHEME_UNKNOWN.
+ **/
+NMSetting8021xCKScheme
+nm_setting_802_1x_check_cert_scheme (gconstpointer pdata, gsize length, GError **error)
+{
+	const char *data = pdata;
+
+	g_return_val_if_fail (!length || data, NM_SETTING_802_1X_CK_SCHEME_UNKNOWN);
+
+	if (!length || !data) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("binary data missing"));
 		return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN;
+	}
+
+	/* interpret the blob as PATH if it starts with "file://". */
+	if (   length >= STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)
+	    && !memcmp (data, NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH))) {
+		/* But it must also be NUL terminated, contain at least
+		 * one non-NUL character, and contain only one trailing NUL
+		 * chracter.
+		 * And ensure it's UTF-8 valid too so we can pass it through
+		 * D-Bus and stuff like that. */
+
+		if (data[length - 1] != '\0') {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("file:// URI not NUL terminated"));
+			return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN;
+		}
+		length--;
+
+		if (length <= STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)) {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("file:// URI is empty"));
+			return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN;
+		}
+
+		if (!g_utf8_validate (data + STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH), length - STRLEN (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH), NULL)) {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("file:// URI is not valid UTF-8"));
+			return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN;
+		}
 
-	if (   (length > strlen (SCHEME_PATH))
-	    && !memcmp (data, SCHEME_PATH, strlen (SCHEME_PATH)))
 		return NM_SETTING_802_1X_CK_SCHEME_PATH;
+	}
 
 	return NM_SETTING_802_1X_CK_SCHEME_BLOB;
 }
 
+static GByteArray *
+load_and_verify_certificate (const char *cert_path,
+                             NMSetting8021xCKScheme scheme,
+                             NMCryptoFileFormat *out_file_format,
+                             GError **error)
+{
+	NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN;
+	GByteArray *array;
+
+	array = crypto_load_and_verify_certificate (cert_path, &format, error);
+
+	if (!array || !array->len || format == NM_CRYPTO_FILE_FORMAT_UNKNOWN) {
+		/* the array is empty or the format is already unknown. */
+		format = NM_CRYPTO_FILE_FORMAT_UNKNOWN;
+	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
+		/* If we load the file as blob, we must ensure that the binary data does not
+		 * start with file://. NMSetting8021x cannot represent blobs that start with
+		 * file://.
+		 * If that's the case, coerce the format to UNKNOWN. The callers will take care
+		 * of that and not set the blob. */
+		if (nm_setting_802_1x_check_cert_scheme (array->data, array->len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB)
+			format = NM_CRYPTO_FILE_FORMAT_UNKNOWN;
+	}
+
+	if (out_file_format)
+		*out_file_format = format;
+	return array;
+}
+
 /**
  * nm_setting_802_1x_get_ca_cert_scheme:
  * @setting: the #NMSetting8021x
@@ -434,7 +529,7 @@ nm_setting_802_1x_get_ca_cert_scheme (NMSetting8021x *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NM_SETTING_802_1X_CK_SCHEME_UNKNOWN);
 
-	return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->ca_cert);
+	return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->ca_cert, NULL);
 }
 
 /**
@@ -488,20 +583,23 @@ nm_setting_802_1x_get_ca_cert_path (NMSetting8021x *setting)
 	g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL);
 
 	data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->ca_cert, NULL);
-	return (const char *)data + strlen (SCHEME_PATH);
+	return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH);
 }
 
 static GBytes *
 path_to_scheme_value (const char *path)
 {
 	GByteArray *array;
+	gsize len;
 
-	g_return_val_if_fail (path != NULL, NULL);
+	g_return_val_if_fail (path != NULL && path[0], NULL);
 
-	/* Add the path scheme tag to the front, then the fielname */
-	array = g_byte_array_sized_new (strlen (path) + strlen (SCHEME_PATH) + 1);
-	g_byte_array_append (array, (const guint8 *) SCHEME_PATH, strlen (SCHEME_PATH));
-	g_byte_array_append (array, (const guint8 *) path, strlen (path));
+	len = strlen (path);
+
+	/* Add the path scheme tag to the front, then the filename */
+	array = g_byte_array_sized_new (len + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH) + 1);
+	g_byte_array_append (array, (const guint8 *) NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH));
+	g_byte_array_append (array, (const guint8 *) path, len);
 	g_byte_array_append (array, (const guint8 *) "\0", 1);
 
 	return g_byte_array_free_to_bytes (array);
@@ -558,7 +656,7 @@ nm_setting_802_1x_set_ca_cert (NMSetting8021x *setting,
 		return TRUE;
 	}
 
-	data = crypto_load_and_verify_certificate (cert_path, &format, error);
+	data = load_and_verify_certificate (cert_path, scheme, &format, error);
 	if (data) {
 		/* wpa_supplicant can only use raw x509 CA certs */
 		if (format == NM_CRYPTO_FILE_FORMAT_X509) {
@@ -766,7 +864,7 @@ nm_setting_802_1x_get_client_cert_scheme (NMSetting8021x *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NM_SETTING_802_1X_CK_SCHEME_UNKNOWN);
 
-	return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->client_cert);
+	return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->client_cert, NULL);
 }
 
 /**
@@ -814,7 +912,7 @@ nm_setting_802_1x_get_client_cert_path (NMSetting8021x *setting)
 	g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL);
 
 	data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->client_cert, NULL);
-	return (const char *)data + strlen (SCHEME_PATH);
+	return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH);
 }
 
 /**
@@ -872,7 +970,7 @@ nm_setting_802_1x_set_client_cert (NMSetting8021x *setting,
 		return TRUE;
 	}
 
-	data = crypto_load_and_verify_certificate (cert_path, &format, error);
+	data = load_and_verify_certificate (cert_path, scheme, &format, error);
 	if (data) {
 		gboolean valid = FALSE;
 
@@ -1029,7 +1127,7 @@ nm_setting_802_1x_get_phase2_ca_cert_scheme (NMSetting8021x *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NM_SETTING_802_1X_CK_SCHEME_UNKNOWN);
 
-	return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_ca_cert);
+	return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_ca_cert, NULL);
 }
 
 /**
@@ -1083,7 +1181,7 @@ nm_setting_802_1x_get_phase2_ca_cert_path (NMSetting8021x *setting)
 	g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL);
 
 	data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_ca_cert, NULL);
-	return (const char *)data + strlen (SCHEME_PATH);
+	return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH);
 }
 
 /**
@@ -1137,7 +1235,7 @@ nm_setting_802_1x_set_phase2_ca_cert (NMSetting8021x *setting,
 		return TRUE;
 	}
 
-	data = crypto_load_and_verify_certificate (cert_path, &format, error);
+	data = load_and_verify_certificate (cert_path, scheme, &format, error);
 	if (data) {
 		/* wpa_supplicant can only use raw x509 CA certs */
 		if (format == NM_CRYPTO_FILE_FORMAT_X509) {
@@ -1349,7 +1447,7 @@ nm_setting_802_1x_get_phase2_client_cert_scheme (NMSetting8021x *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NM_SETTING_802_1X_CK_SCHEME_UNKNOWN);
 
-	return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_client_cert);
+	return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_client_cert, NULL);
 }
 
 /**
@@ -1397,7 +1495,7 @@ nm_setting_802_1x_get_phase2_client_cert_path (NMSetting8021x *setting)
 	g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL);
 
 	data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_client_cert, NULL);
-	return (const char *)data + strlen (SCHEME_PATH);
+	return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH);
 }
 
 /**
@@ -1455,7 +1553,7 @@ nm_setting_802_1x_set_phase2_client_cert (NMSetting8021x *setting,
 		return TRUE;
 	}
 
-	data = crypto_load_and_verify_certificate (cert_path, &format, error);
+	data = load_and_verify_certificate (cert_path, scheme, &format, error);
 	if (data) {
 		gboolean valid = FALSE;
 
@@ -1604,7 +1702,7 @@ nm_setting_802_1x_get_private_key_scheme (NMSetting8021x *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NM_SETTING_802_1X_CK_SCHEME_UNKNOWN);
 
-	return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->private_key);
+	return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->private_key, NULL);
 }
 
 /**
@@ -1656,7 +1754,7 @@ nm_setting_802_1x_get_private_key_path (NMSetting8021x *setting)
 	g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL);
 
 	data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->private_key, NULL);
-	return (const char *)data + strlen (SCHEME_PATH);
+	return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH);
 }
 
 static void
@@ -1678,7 +1776,7 @@ file_to_secure_bytes (const char *filename)
 	if (g_file_get_contents (filename, &contents, &length, NULL)) {
 		array = g_byte_array_sized_new (length);
 		g_byte_array_append (array, (guint8 *) contents, length);
-		g_assert (array->len == length);
+		memset (contents, 0, length);
 		g_free (contents);
 		return g_bytes_new_with_free_func (array->data, array->len, free_secure_bytes, array);
 	}
@@ -1790,7 +1888,8 @@ nm_setting_802_1x_set_private_key (NMSetting8021x *setting,
 
 	priv->private_key_password = g_strdup (password);
 	if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
-		/* Shouldn't fail this since we just verified the private key above */
+		/* FIXME: potential race after verifying the private key above */
+		/* FIXME: ensure blob doesn't start with file:// */
 		priv->private_key = file_to_secure_bytes (key_path);
 		g_assert (priv->private_key);
 	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH)
@@ -1941,7 +2040,7 @@ nm_setting_802_1x_get_phase2_private_key_scheme (NMSetting8021x *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NM_SETTING_802_1X_CK_SCHEME_UNKNOWN);
 
-	return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_private_key);
+	return get_cert_scheme (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_private_key, NULL);
 }
 
 /**
@@ -1993,7 +2092,7 @@ nm_setting_802_1x_get_phase2_private_key_path (NMSetting8021x *setting)
 	g_return_val_if_fail (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH, NULL);
 
 	data = g_bytes_get_data (NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_private_key, NULL);
-	return (const char *)data + strlen (SCHEME_PATH);
+	return (const char *)data + strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH);
 }
 
 /**
@@ -2101,7 +2200,8 @@ nm_setting_802_1x_set_phase2_private_key (NMSetting8021x *setting,
 
 	priv->phase2_private_key_password = g_strdup (password);
 	if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
-		/* Shouldn't fail this since we just verified the private key above */
+		/* FIXME: potential race after verifying the private key above */
+		/* FIXME: ensure blob doesn't start with file:// */
 		priv->phase2_private_key = file_to_secure_bytes (key_path);
 		g_assert (priv->phase2_private_key);
 	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH)
@@ -2575,35 +2675,18 @@ need_secrets (NMSetting *setting)
 static gboolean
 verify_cert (GBytes *bytes, const char *prop_name, GError **error)
 {
-	gconstpointer data;
-	gsize length;
-
-	if (!bytes)
-		return TRUE;
+	GError *local = NULL;
 
-	switch (get_cert_scheme (bytes)) {
-	case NM_SETTING_802_1X_CK_SCHEME_BLOB:
+	if (   !bytes
+	    || get_cert_scheme (bytes, &local) != NM_SETTING_802_1X_CK_SCHEME_UNKNOWN)
 		return TRUE;
-	case NM_SETTING_802_1X_CK_SCHEME_PATH:
-		/* For path-based schemes, verify that the path is zero-terminated */
-		data = g_bytes_get_data (bytes, &length);
-		if (((const guchar *)data)[length - 1] == '\0') {
-			/* And ensure it's UTF-8 valid too so we can pass it through
-			 * D-Bus and stuff like that.
-			 */
-			if (g_utf8_validate ((const char *)data + strlen (SCHEME_PATH), -1, NULL))
-				return TRUE;
-		}
-		break;
-	default:
-		break;
-	}
 
-	g_set_error_literal (error,
-	                     NM_CONNECTION_ERROR,
-	                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-	                     _("property is invalid"));
+	g_set_error (error,
+	             NM_CONNECTION_ERROR,
+	             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+	             _("certificate is invalid: %s"), local->message);
 	g_prefix_error (error, "%s.%s: ", NM_SETTING_802_1X_SETTING_NAME, prop_name);
+	g_error_free (local);
 	return FALSE;
 }
 
@@ -2805,7 +2888,7 @@ set_property (GObject *object, guint prop_id,
 	switch (prop_id) {
 	case PROP_EAP:
 		g_slist_free_full (priv->eap, g_free);
-		priv->eap = _nm_utils_strv_to_slist (g_value_get_boxed (value));
+		priv->eap = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE);
 		break;
 	case PROP_IDENTITY:
 		g_free (priv->identity);
@@ -2839,7 +2922,7 @@ set_property (GObject *object, guint prop_id,
 		break;
 	case PROP_ALTSUBJECT_MATCHES:
 		g_slist_free_full (priv->altsubject_matches, g_free);
-		priv->altsubject_matches = _nm_utils_strv_to_slist (g_value_get_boxed (value));
+		priv->altsubject_matches = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE);
 		break;
 	case PROP_CLIENT_CERT:
 		if (priv->client_cert)
@@ -2891,7 +2974,7 @@ set_property (GObject *object, guint prop_id,
 		break;
 	case PROP_PHASE2_ALTSUBJECT_MATCHES:
 		g_slist_free_full (priv->phase2_altsubject_matches, g_free);
-		priv->phase2_altsubject_matches = _nm_utils_strv_to_slist (g_value_get_boxed (value));
+		priv->phase2_altsubject_matches = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE);
 		break;
 	case PROP_PHASE2_CLIENT_CERT:
 		if (priv->phase2_client_cert)
@@ -2977,7 +3060,7 @@ get_property (GObject *object, guint prop_id,
 
 	switch (prop_id) {
 	case PROP_EAP:
-		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->eap));
+		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->eap, TRUE));
 		break;
 	case PROP_IDENTITY:
 		g_value_set_string (value, priv->identity);
@@ -2998,7 +3081,7 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_string (value, priv->subject_match);
 		break;
 	case PROP_ALTSUBJECT_MATCHES:
-		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->altsubject_matches));
+		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->altsubject_matches, TRUE));
 		break;
 	case PROP_CLIENT_CERT:
 		g_value_set_boxed (value, priv->client_cert);
@@ -3028,7 +3111,7 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_string (value, priv->phase2_subject_match);
 		break;
 	case PROP_PHASE2_ALTSUBJECT_MATCHES:
-		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->phase2_altsubject_matches));
+		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->phase2_altsubject_matches, TRUE));
 		break;
 	case PROP_PHASE2_CLIENT_CERT:
 		g_value_set_boxed (value, priv->phase2_client_cert);
@@ -3848,7 +3931,10 @@ nm_setting_802_1x_class_init (NMSetting8021xClass *setting_class)
 	 * specified at configure time with the --system-ca-path switch.  The
 	 * certificates in this directory are added to the verification chain in
 	 * addition to any certificates specified by the #NMSetting8021x:ca-cert and
-	 * #NMSetting8021x:phase2-ca-cert properties.
+	 * #NMSetting8021x:phase2-ca-cert properties. If the path provided with
+	 * --system-ca-path is rather a file name (bundle of trusted CA certificates),
+	 * it overrides #NMSetting8021x:ca-cert and #NMSetting8021x:phase2-ca-cert
+	 * properties instead (sets ca_cert/ca_cert2 options for wpa_supplicant).
 	 **/
 	/* ---ifcfg-rh---
 	 * property: system-ca-certs
diff --git a/libnm-core/nm-setting-8021x.h b/libnm-core/nm-setting-8021x.h
index 43885daa..3099fbd1 100644
--- a/libnm-core/nm-setting-8021x.h
+++ b/libnm-core/nm-setting-8021x.h
@@ -31,6 +31,8 @@
 
 G_BEGIN_DECLS
 
+#define NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH "file://"
+
 /**
  * NMSetting8021xCKFormat:
  * @NM_SETTING_802_1X_CK_FORMAT_UNKNOWN: unknown file format
diff --git a/libnm-core/nm-setting-connection.c b/libnm-core/nm-setting-connection.c
index 7bc927bc..07b3401d 100644
--- a/libnm-core/nm-setting-connection.c
+++ b/libnm-core/nm-setting-connection.c
@@ -27,6 +27,7 @@
 
 #include "nm-utils.h"
 #include "nm-utils-private.h"
+#include "nm-core-enum-types.h"
 #include "nm-setting-connection.h"
 #include "nm-connection-private.h"
 #include "nm-setting-bond.h"
@@ -66,6 +67,7 @@ typedef struct {
 	char *type;
 	char *master;
 	char *slave_type;
+	NMSettingConnectionAutoconnectSlaves autoconnect_slaves;
 	GSList *permissions; /* list of Permission structs */
 	gboolean autoconnect;
 	gint autoconnect_priority;
@@ -90,6 +92,7 @@ enum {
 	PROP_ZONE,
 	PROP_MASTER,
 	PROP_SLAVE_TYPE,
+	PROP_AUTOCONNECT_SLAVES,
 	PROP_SECONDARIES,
 	PROP_GATEWAY_PING_TIMEOUT,
 
@@ -603,6 +606,25 @@ nm_setting_connection_is_slave_type (NMSettingConnection *setting,
 }
 
 /**
+ * nm_setting_connection_get_autoconnect_slaves:
+ * @setting: the #NMSettingConnection
+ *
+ * Returns the #NMSettingConnection:autoconnect-slaves property of the connection.
+ *
+ * Returns: whether slaves of the connection should be activated together
+ *          with the connection.
+ *
+ * Since: 1.0.4
+ **/
+NMSettingConnectionAutoconnectSlaves
+nm_setting_connection_get_autoconnect_slaves (NMSettingConnection *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_CONNECTION (setting), NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT);
+
+	return NM_SETTING_CONNECTION_GET_PRIVATE (setting)->autoconnect_slaves;
+}
+
+/**
  * nm_setting_connection_get_num_secondaries:
  * @setting: the #NMSettingConnection
  *
@@ -1122,9 +1144,12 @@ set_property (GObject *object, guint prop_id,
 		g_free (priv->slave_type);
 		priv->slave_type = g_value_dup_string (value);
 		break;
+	case PROP_AUTOCONNECT_SLAVES:
+		priv->autoconnect_slaves = g_value_get_enum (value);
+		break;
 	case PROP_SECONDARIES:
 		g_slist_free_full (priv->secondaries, g_free);
-		priv->secondaries = _nm_utils_strv_to_slist (g_value_get_boxed (value));
+		priv->secondaries = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE);
 		break;
 	case PROP_GATEWAY_PING_TIMEOUT:
 		priv->gateway_ping_timeout = g_value_get_uint (value);
@@ -1193,8 +1218,11 @@ get_property (GObject *object, guint prop_id,
 	case PROP_SLAVE_TYPE:
 		g_value_set_string (value, nm_setting_connection_get_slave_type (setting));
 		break;
+	case PROP_AUTOCONNECT_SLAVES:
+		g_value_set_enum (value, nm_setting_connection_get_autoconnect_slaves (setting));
+		break;
 	case PROP_SECONDARIES:
-		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->secondaries));
+		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->secondaries, TRUE));
 		break;
 	case PROP_GATEWAY_PING_TIMEOUT:
 		g_value_set_uint (value, priv->gateway_ping_timeout);
@@ -1524,6 +1552,37 @@ nm_setting_connection_class_init (NMSettingConnectionClass *setting_class)
 		                      G_PARAM_STATIC_STRINGS));
 
 	/**
+	 * NMSettingConnection:autoconnect-slaves:
+	 *
+	 * Whether or not slaves of this connection should be automatically brought up
+	 * when NetworkManager activates this connection. This only has a real effect
+	 * for master connections.
+	 * The permitted values are: 0: leave slave connections untouched,
+	 * 1: activate all the slave connections with this connection, -1: default.
+	 * If -1 (default) is set, global connection.autoconnect-slaves is read to
+	 * determine the real value. If it is default as well, this fallbacks to 0.
+	 *
+	 * Since: 1.0.4
+	 **/
+	/* ---ifcfg-rh---
+	 * property: autoconnect-slaves
+	 * variable: AUTOCONNECT-SLAVES(+)
+	 * default: missing variable means global default
+	 * description: Whether slaves of this connection should be auto-connected
+	 *   when this connection is activated.
+	 * ---end---
+	 */
+	g_object_class_install_property
+		(object_class, PROP_AUTOCONNECT_SLAVES,
+		 g_param_spec_enum (NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES, "", "",
+		                    NM_TYPE_SETTING_CONNECTION_AUTOCONNECT_SLAVES,
+		                    NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT,
+		                    G_PARAM_READWRITE |
+		                    G_PARAM_CONSTRUCT |
+		                    NM_SETTING_PARAM_FUZZY_IGNORE |
+		                    G_PARAM_STATIC_STRINGS));
+
+	/**
 	 * NMSettingConnection:secondaries:
 	 *
 	 * List of connection UUIDs that should be activated when the base
@@ -1563,7 +1622,7 @@ nm_setting_connection_class_init (NMSettingConnectionClass *setting_class)
 	g_object_class_install_property
 		(object_class, PROP_GATEWAY_PING_TIMEOUT,
 		 g_param_spec_uint (NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT, "", "",
-		                    0, 30, 0,
+		                    0, 600, 0,
 		                    G_PARAM_READWRITE |
 		                    G_PARAM_CONSTRUCT |
 		                    G_PARAM_STATIC_STRINGS));
diff --git a/libnm-core/nm-setting-connection.h b/libnm-core/nm-setting-connection.h
index d1ad0fe1..ff89d200 100644
--- a/libnm-core/nm-setting-connection.h
+++ b/libnm-core/nm-setting-connection.h
@@ -56,9 +56,30 @@ G_BEGIN_DECLS
 #define NM_SETTING_CONNECTION_ZONE           "zone"
 #define NM_SETTING_CONNECTION_MASTER         "master"
 #define NM_SETTING_CONNECTION_SLAVE_TYPE     "slave-type"
+#define NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES "autoconnect-slaves"
 #define NM_SETTING_CONNECTION_SECONDARIES    "secondaries"
 #define NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT "gateway-ping-timeout"
 
+/* Types for property values */
+/**
+ * NMSettingConnectionAutoconnectSlaves:
+ * @NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT: default value
+ * @NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_NO: slaves are not brought up when
+ *   master is activated
+ * @NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES: slaves are brought up when
+ *   master is activated
+ *
+ * #NMSettingConnectionAutoconnectSlaves values indicate whether slave connections
+ * should be activated when master is activated.
+ */
+NM_AVAILABLE_IN_1_0_4
+typedef enum {
+	NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT = -1,
+	NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_NO = 0,
+	NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES = 1,
+} NMSettingConnectionAutoconnectSlaves;
+
+
 /**
  * NMSettingConnection:
  *
@@ -111,6 +132,8 @@ const char *nm_setting_connection_get_master           (NMSettingConnection *set
 gboolean    nm_setting_connection_is_slave_type        (NMSettingConnection *setting,
                                                         const char *type);
 const char *nm_setting_connection_get_slave_type       (NMSettingConnection *setting);
+NM_AVAILABLE_IN_1_0_4
+NMSettingConnectionAutoconnectSlaves nm_setting_connection_get_autoconnect_slaves (NMSettingConnection *setting);
 
 guint32     nm_setting_connection_get_num_secondaries  (NMSettingConnection *setting);
 const char *nm_setting_connection_get_secondary        (NMSettingConnection *setting, guint32 idx);
diff --git a/libnm-core/nm-setting-ip6-config.c b/libnm-core/nm-setting-ip6-config.c
index 29ca5515..7e3ae4d4 100644
--- a/libnm-core/nm-setting-ip6-config.c
+++ b/libnm-core/nm-setting-ip6-config.c
@@ -504,9 +504,15 @@ nm_setting_ip6_config_class_init (NMSettingIP6ConfigClass *ip6_class)
 	 * enabled, it makes the kernel generate a temporary IPv6 address in
 	 * addition to the public one generated from MAC address via modified
 	 * EUI-64.  This enhances privacy, but could cause problems in some
-	 * applications, on the other hand.  The permitted values are: 0: disabled,
-	 * 1: enabled (prefer public address), 2: enabled (prefer temporary
+	 * applications, on the other hand.  The permitted values are: -1: unknown,
+	 * 0: disabled, 1: enabled (prefer public address), 2: enabled (prefer temporary
 	 * addresses).
+	 *
+	 * Having a per-connection setting set to "-1" (unknown) means fallback to
+	 * global configuration "ipv6.ip6-privacy".
+	 *
+	 * If also global configuration is unspecified or set to "-1", fallback to read
+	 * "/proc/sys/net/ipv6/conf/default/use_tempaddr".
 	 **/
 	/* ---ifcfg-rh---
 	 * property: ip6-privacy
diff --git a/libnm-core/nm-setting-private.h b/libnm-core/nm-setting-private.h
index 2d34d509..955608fa 100644
--- a/libnm-core/nm-setting-private.h
+++ b/libnm-core/nm-setting-private.h
@@ -155,4 +155,6 @@ gboolean _nm_setting_use_legacy_property (NMSetting *setting,
 
 GPtrArray  *_nm_setting_need_secrets (NMSetting *setting);
 
+NMSetting8021xCKScheme nm_setting_802_1x_check_cert_scheme (gconstpointer pdata, gsize length, GError **error);
+
 #endif  /* NM_SETTING_PRIVATE_H */
diff --git a/libnm-core/nm-setting-wireless-security.c b/libnm-core/nm-setting-wireless-security.c
index a56a8e97..6df5b461 100644
--- a/libnm-core/nm-setting-wireless-security.c
+++ b/libnm-core/nm-setting-wireless-security.c
@@ -1132,15 +1132,15 @@ set_property (GObject *object, guint prop_id,
 		break;
 	case PROP_PROTO:
 		g_slist_free_full (priv->proto, g_free);
-		priv->proto = _nm_utils_strv_to_slist (g_value_get_boxed (value));
+		priv->proto = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE);
 		break;
 	case PROP_PAIRWISE:
 		g_slist_free_full (priv->pairwise, g_free);
-		priv->pairwise = _nm_utils_strv_to_slist (g_value_get_boxed (value));
+		priv->pairwise = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE);
 		break;
 	case PROP_GROUP:
 		g_slist_free_full (priv->group, g_free);
-		priv->group = _nm_utils_strv_to_slist (g_value_get_boxed (value));
+		priv->group = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE);
 		break;
 	case PROP_LEAP_USERNAME:
 		g_free (priv->leap_username);
@@ -1206,13 +1206,13 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_string (value, priv->auth_alg);
 		break;
 	case PROP_PROTO:
-		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->proto));
+		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->proto, TRUE));
 		break;
 	case PROP_PAIRWISE:
-		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->pairwise));
+		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->pairwise, TRUE));
 		break;
 	case PROP_GROUP:
-		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->group));
+		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->group, TRUE));
 		break;
 	case PROP_LEAP_USERNAME:
 		g_value_set_string (value, priv->leap_username);
diff --git a/libnm-core/nm-setting-wireless.c b/libnm-core/nm-setting-wireless.c
index f8d22c2d..ce3cf9a7 100644
--- a/libnm-core/nm-setting-wireless.c
+++ b/libnm-core/nm-setting-wireless.c
@@ -911,7 +911,7 @@ set_property (GObject *object, guint prop_id,
 		break;
 	case PROP_SEEN_BSSIDS:
 		g_slist_free_full (priv->seen_bssids, g_free);
-		priv->seen_bssids = _nm_utils_strv_to_slist (g_value_get_boxed (value));
+		priv->seen_bssids = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE);
 		break;
 	case PROP_HIDDEN:
 		priv->hidden = g_value_get_boolean (value);
@@ -964,7 +964,7 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_uint (value, nm_setting_wireless_get_mtu (setting));
 		break;
 	case PROP_SEEN_BSSIDS:
-		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->seen_bssids));
+		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->seen_bssids, TRUE));
 		break;
 	case PROP_HIDDEN:
 		g_value_set_boolean (value, nm_setting_wireless_get_hidden (setting));
diff --git a/libnm-core/nm-setting.c b/libnm-core/nm-setting.c
index b78d9376..e1e81b5d 100644
--- a/libnm-core/nm-setting.c
+++ b/libnm-core/nm-setting.c
@@ -91,7 +91,7 @@ _nm_gtype_hash (gconstpointer v)
 	return *((const GType *) v);
 }
 
-static void __attribute__((constructor))
+static void
 _ensure_registered (void)
 {
 	if (G_UNLIKELY (registered_settings == NULL)) {
@@ -103,6 +103,12 @@ _ensure_registered (void)
 	}
 }
 
+static void __attribute__((constructor))
+_ensure_registered_constructor (void)
+{
+	_ensure_registered ();
+}
+
 #define _ensure_setting_info(self, priv) \
 	G_STMT_START { \
 		NMSettingPrivate *_priv_esi = (priv); \
@@ -1047,13 +1053,15 @@ compare_property (NMSetting *setting,
 	property = nm_setting_class_find_property (NM_SETTING_GET_CLASS (setting), prop_spec->name);
 	g_return_val_if_fail (property != NULL, FALSE);
 
-	value1 = get_property_for_dbus (setting, property, FALSE);
-	value2 = get_property_for_dbus (other, property, FALSE);
+	value1 = get_property_for_dbus (setting, property, TRUE);
+	value2 = get_property_for_dbus (other, property, TRUE);
 
 	cmp = nm_property_compare (value1, value2);
 
-	g_variant_unref (value1);
-	g_variant_unref (value2);
+	if (value1)
+		g_variant_unref (value1);
+	if (value2)
+		g_variant_unref (value2);
 
 	return cmp == 0;
 }
diff --git a/libnm-core/nm-utils-private.h b/libnm-core/nm-utils-private.h
index 3854d679..ffdc6315 100644
--- a/libnm-core/nm-utils-private.h
+++ b/libnm-core/nm-utils-private.h
@@ -40,12 +40,6 @@ GVariant *  _nm_utils_bytes_to_dbus     (const GValue *prop_value);
 void        _nm_utils_bytes_from_dbus   (GVariant *dbus_value,
                                          GValue *prop_value);
 
-GSList *    _nm_utils_strv_to_slist (char **strv);
-char **     _nm_utils_slist_to_strv (GSList *slist);
-
-GPtrArray * _nm_utils_strv_to_ptrarray (char **strv);
-char **     _nm_utils_ptrarray_to_strv (GPtrArray *ptrarray);
-
 char *      _nm_utils_hwaddr_canonical_or_invalid (const char *mac, gssize length);
 
 #endif
diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c
index 5d4f1163..14686ed1 100644
--- a/libnm-core/nm-utils.c
+++ b/libnm-core/nm-utils.c
@@ -22,6 +22,7 @@
 #include "config.h"
 
 #include <string.h>
+#include <errno.h>
 #include <stdlib.h>
 #include <netinet/ether.h>
 #include <arpa/inet.h>
@@ -36,7 +37,7 @@
 #include "nm-setting-private.h"
 #include "crypto.h"
 #include "gsystem-local-alloc.h"
-#include "nm-utils-internal.h"
+#include "nm-macros-internal.h"
 
 #include "nm-setting-bond.h"
 #include "nm-setting-bridge.h"
@@ -433,15 +434,85 @@ nm_utils_same_ssid (const guint8 *ssid1, gsize len1,
 gboolean
 _nm_utils_string_in_list (const char *str, const char **valid_strings)
 {
-	int i;
+	return _nm_utils_strv_find_first ((char **) valid_strings, -1, str) >= 0;
+}
 
-	for (i = 0; valid_strings[i]; i++)
-		if (strcmp (str, valid_strings[i]) == 0)
-			break;
+/**
+ * _nm_utils_strv_find_first:
+ * @list: the strv list to search
+ * @len: the length of the list, or a negative value if @list is %NULL terminated.
+ * @needle: the value to search for. The search is done using strcmp().
+ *
+ * Searches @list for @needle and returns the index of the first match (based
+ * on strcmp()).
+ *
+ * For convenience, @list has type 'char**' instead of 'const char **'.
+ *
+ * Returns: index of first occurrence or -1 if @needle is not found in @list.
+ */
+gssize
+_nm_utils_strv_find_first (char **list, gssize len, const char *needle)
+{
+	gssize i;
+
+	if (len > 0) {
+		g_return_val_if_fail (list, -1);
+
+		if (!needle) {
+			/* if we search a list with known length, %NULL is a valid @needle. */
+			for (i = 0; i < len; i++) {
+				if (!list[i])
+					return i;
+			}
+		} else {
+			for (i = 0; i < len; i++) {
+				if (list[i] && !strcmp (needle, list[i]))
+					return i;
+			}
+		}
+	} else if (len < 0) {
+		g_return_val_if_fail (needle, -1);
 
-	return valid_strings[i] != NULL;
+		if (list) {
+			for (i = 0; list[i]; i++) {
+				if (strcmp (needle, list[i]) == 0)
+					return i;
+			}
+		}
+	}
+	return -1;
 }
 
+char **
+_nm_utils_strv_cleanup (char **strv,
+                        gboolean strip_whitespace,
+                        gboolean skip_empty,
+                        gboolean skip_repeated)
+{
+	guint i, j;
+
+	if (!strv || !*strv)
+		return strv;
+
+	if (strip_whitespace) {
+		for (i = 0; strv[i]; i++)
+			g_strstrip (strv[i]);
+	}
+	if (!skip_empty && !skip_repeated)
+		return strv;
+	j = 0;
+	for (i = 0; strv[i]; i++) {
+		if (   (skip_empty && !*strv[i])
+		    || (skip_repeated && _nm_utils_strv_find_first (strv, j, strv[i]) >= 0))
+			g_free (strv[i]);
+		else
+			strv[j++] = strv[i];
+	}
+	strv[j] = NULL;
+	return strv;
+}
+
+
 gboolean
 _nm_utils_string_slist_validate (GSList *list, const char **valid_values)
 {
@@ -590,6 +661,65 @@ _nm_utils_copy_object_array (const GPtrArray *array)
 	return _nm_utils_copy_array (array, g_object_ref, g_object_unref);
 }
 
+/* have @list of type 'gpointer *' instead of 'gconstpointer *' to
+ * reduce the necessity for annoying const-casts. */
+gssize
+_nm_utils_ptrarray_find_first (gpointer *list, gssize len, gconstpointer needle)
+{
+	gssize i;
+
+	if (len == 0)
+		return -1;
+
+	if (len > 0) {
+		g_return_val_if_fail (list, -1);
+		for (i = 0; i < len; i++) {
+			if (list[i] == needle)
+				return i;
+		}
+	} else {
+		g_return_val_if_fail (needle, -1);
+		for (i = 0; list && list[i]; i++) {
+			if (list[i] == needle)
+				return i;
+		}
+	}
+	return -1;
+}
+
+gssize
+_nm_utils_ptrarray_find_binary_search (gpointer *list, gsize len, gpointer needle, GCompareDataFunc cmpfcn, gpointer user_data)
+{
+	gssize imin, imax, imid;
+	int cmp;
+
+	g_return_val_if_fail (list || !len, ~((gssize) 0));
+	g_return_val_if_fail (cmpfcn, ~((gssize) 0));
+
+	imin = 0;
+	if (len == 0)
+		return ~imin;
+
+	imax = len - 1;
+
+	while (imin <= imax) {
+		imid = imin + (imax - imin) / 2;
+
+		cmp = cmpfcn (list[imid], needle, user_data);
+		if (cmp == 0)
+			return imid;
+
+		if (cmp < 0)
+			imin = imid + 1;
+		else
+			imax = imid - 1;
+	}
+
+	/* return the inverse of @imin. This is a negative number, but
+	 * also is ~imin the position where the value should be inserted. */
+	return ~imin;
+}
+
 GVariant *
 _nm_utils_bytes_to_dbus (const GValue *prop_value)
 {
@@ -625,21 +755,26 @@ _nm_utils_bytes_from_dbus (GVariant *dbus_value,
 }
 
 GSList *
-_nm_utils_strv_to_slist (char **strv)
+_nm_utils_strv_to_slist (char **strv, gboolean deep_copy)
 {
 	int i;
 	GSList *list = NULL;
 
 	if (strv) {
-		for (i = 0; strv[i]; i++)
-			list = g_slist_prepend (list, g_strdup (strv[i]));
+		if (deep_copy) {
+			for (i = 0; strv[i]; i++)
+				list = g_slist_prepend (list, g_strdup (strv[i]));
+		} else {
+			for (i = 0; strv[i]; i++)
+				list = g_slist_prepend (list, strv[i]);
+		}
 	}
 
 	return g_slist_reverse (list);
 }
 
 char **
-_nm_utils_slist_to_strv (GSList *slist)
+_nm_utils_slist_to_strv (GSList *slist, gboolean deep_copy)
 {
 	GSList *iter;
 	char **strv;
@@ -648,8 +783,13 @@ _nm_utils_slist_to_strv (GSList *slist)
 	len = g_slist_length (slist);
 	strv = g_new (char *, len + 1);
 
-	for (i = 0, iter = slist; iter; iter = iter->next, i++)
-		strv[i] = g_strdup (iter->data);
+	if (deep_copy) {
+		for (i = 0, iter = slist; iter; iter = iter->next, i++)
+			strv[i] = g_strdup (iter->data);
+	} else {
+		for (i = 0, iter = slist; iter; iter = iter->next, i++)
+			strv[i] = iter->data;
+	}
 	strv[i] = NULL;
 
 	return strv;
@@ -1942,6 +2082,8 @@ nm_utils_ip_routes_from_variant (GVariant *value,
 	return routes;
 }
 
+/**********************************************************************************************/
+
 /**
  * nm_utils_uuid_generate:
  *
@@ -2015,6 +2157,50 @@ nm_utils_uuid_generate_from_string (const char *s, gssize slen, int uuid_type, g
 }
 
 /**
+ * _nm_utils_uuid_generate_from_strings:
+ * @string1: a variadic list of strings. Must be NULL terminated.
+ *
+ * Returns a variant3 UUID based on the concatenated C strings.
+ * It does not simply concatenate them, but also includes the
+ * terminating '\0' character. For example "a", "b", gives
+ * "a\0b\0".
+ *
+ * This has the advantage, that the following invocations
+ * all give different UUIDs: (NULL), (""), ("",""), ("","a"), ("a",""),
+ * ("aa"), ("aa", ""), ("", "aa"), ...
+ */
+char *
+_nm_utils_uuid_generate_from_strings (const char *string1, ...)
+{
+	GString *str;
+	va_list args;
+	const char *s;
+	char *uuid;
+
+	if (!string1)
+		return nm_utils_uuid_generate_from_string (NULL, 0, NM_UTILS_UUID_TYPE_VARIANT3, NM_UTILS_UUID_NS);
+
+	str = g_string_sized_new (120); /* effectively allocates power of 2 (128)*/
+
+	g_string_append_len (str, string1, strlen (string1) + 1);
+
+	va_start (args, string1);
+	s = va_arg (args, const char *);
+	while (s) {
+		g_string_append_len (str, s, strlen (s) + 1);
+		s = va_arg (args, const char *);
+	}
+	va_end (args);
+
+	uuid = nm_utils_uuid_generate_from_string (str->str, str->len, NM_UTILS_UUID_TYPE_VARIANT3, NM_UTILS_UUID_NS);
+
+	g_string_free (str, TRUE);
+	return uuid;
+}
+
+/**********************************************************************************************/
+
+/**
  * nm_utils_rsa_key_encrypt:
  * @data: (array length=len): RSA private key data to be encrypted
  * @len: length of @data
@@ -3278,3 +3464,79 @@ nm_utils_bond_mode_string_to_int (const char *mode)
 	}
 	return -1;
 }
+
+/**********************************************************************************************/
+
+/* _nm_utils_ascii_str_to_int64:
+ *
+ * A wrapper for g_ascii_strtoll, that checks whether the whole string
+ * can be successfully converted to a number and is within a given
+ * range. On any error, @fallback will be returned and %errno will be set
+ * to a non-zero value. On success, %errno will be set to zero, check %errno
+ * for errors. Any trailing or leading (ascii) white space is ignored and the
+ * functions is locale independent.
+ *
+ * The function is guaranteed to return a value between @min and @max
+ * (inclusive) or @fallback. Also, the parsing is rather strict, it does
+ * not allow for any unrecognized characters, except leading and trailing
+ * white space.
+ **/
+gint64
+_nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback)
+{
+	gint64 v;
+	size_t len;
+	char buf[64], *s, *str_free = NULL;
+
+	if (str) {
+		while (g_ascii_isspace (str[0]))
+			str++;
+	}
+	if (!str || !str[0]) {
+		errno = EINVAL;
+		return fallback;
+	}
+
+	len = strlen (str);
+	if (g_ascii_isspace (str[--len])) {
+		/* backward search the first non-ws character.
+		 * We already know that str[0] is non-ws. */
+		while (g_ascii_isspace (str[--len]))
+			;
+
+		/* str[len] is now the last non-ws character... */
+		len++;
+
+		if (len >= sizeof (buf))
+			s = str_free = g_malloc (len + 1);
+		else
+			s = buf;
+
+		memcpy (s, str, len);
+		s[len] = 0;
+
+		nm_assert (len > 0 && len < strlen (str) && len == strlen (s));
+		nm_assert (!g_ascii_isspace (str[len-1]) && g_ascii_isspace (str[len]));
+		nm_assert (strncmp (str, s, len) == 0);
+
+		str = s;
+	}
+
+	errno = 0;
+	v = g_ascii_strtoll (str, &s, base);
+
+	if (errno != 0)
+		v = fallback;
+	else if (s[0] != 0) {
+		errno = EINVAL;
+		v = fallback;
+	} else if (v > max || v < min) {
+		errno = ERANGE;
+		v = fallback;
+	}
+
+	if (G_UNLIKELY (str_free))
+		g_free (str_free);
+	return v;
+}
+
diff --git a/libnm-core/nm-version.h b/libnm-core/nm-version.h
index 46103544..e36d921e 100644
--- a/libnm-core/nm-version.h
+++ b/libnm-core/nm-version.h
@@ -43,7 +43,7 @@
  * Evaluates to the micro version number of NetworkManager which this source
  * compiled against.
  */
-#define NM_MICRO_VERSION (2)
+#define NM_MICRO_VERSION (4)
 
 /**
  * NM_CHECK_VERSION:
@@ -67,9 +67,11 @@
 #define NM_VERSION_0_9_8  (NM_ENCODE_VERSION (0, 9, 8))
 #define NM_VERSION_0_9_10 (NM_ENCODE_VERSION (0, 9, 10))
 #define NM_VERSION_1_0    (NM_ENCODE_VERSION (1, 0, 0))
+#define NM_VERSION_1_0_2  (NM_ENCODE_VERSION (1, 0, 2))
+#define NM_VERSION_1_0_4  (NM_ENCODE_VERSION (1, 0, 4))
 
-#define NM_VERSION_CUR_STABLE  NM_VERSION_1_0
-#define NM_VERSION_NEXT_STABLE NM_VERSION_1_0
+#define NM_VERSION_CUR_STABLE  NM_VERSION_1_0_2
+#define NM_VERSION_NEXT_STABLE NM_VERSION_1_0_4
 
 #if !defined (NM_VERSION_MIN_REQUIRED) || (NM_VERSION_MIN_REQUIRED == 0)
 # undef NM_VERSION_MIN_REQUIRED
@@ -120,4 +122,10 @@
 # define NM_AVAILABLE_IN_1_0
 #endif
 
+#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_0_4
+# define NM_AVAILABLE_IN_1_0_4          G_UNAVAILABLE(1,0.4)
+#else
+# define NM_AVAILABLE_IN_1_0_4
+#endif
+
 #endif  /* NM_VERSION_H */
diff --git a/libnm-core/nm-version.h.in b/libnm-core/nm-version.h.in
index bbbf993a..9c9219e1 100644
--- a/libnm-core/nm-version.h.in
+++ b/libnm-core/nm-version.h.in
@@ -67,9 +67,11 @@
 #define NM_VERSION_0_9_8  (NM_ENCODE_VERSION (0, 9, 8))
 #define NM_VERSION_0_9_10 (NM_ENCODE_VERSION (0, 9, 10))
 #define NM_VERSION_1_0    (NM_ENCODE_VERSION (1, 0, 0))
+#define NM_VERSION_1_0_2  (NM_ENCODE_VERSION (1, 0, 2))
+#define NM_VERSION_1_0_4  (NM_ENCODE_VERSION (1, 0, 4))
 
-#define NM_VERSION_CUR_STABLE  NM_VERSION_1_0
-#define NM_VERSION_NEXT_STABLE NM_VERSION_1_0
+#define NM_VERSION_CUR_STABLE  NM_VERSION_1_0_2
+#define NM_VERSION_NEXT_STABLE NM_VERSION_1_0_4
 
 #if !defined (NM_VERSION_MIN_REQUIRED) || (NM_VERSION_MIN_REQUIRED == 0)
 # undef NM_VERSION_MIN_REQUIRED
@@ -120,4 +122,10 @@
 # define NM_AVAILABLE_IN_1_0
 #endif
 
+#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_0_4
+# define NM_AVAILABLE_IN_1_0_4          G_UNAVAILABLE(1,0.4)
+#else
+# define NM_AVAILABLE_IN_1_0_4
+#endif
+
 #endif  /* NM_VERSION_H */
diff --git a/libnm-core/tests/Makefile.am b/libnm-core/tests/Makefile.am
index 79aa73db..daa5825b 100644
--- a/libnm-core/tests/Makefile.am
+++ b/libnm-core/tests/Makefile.am
@@ -15,6 +15,7 @@ noinst_PROGRAMS =		\
 	test-compare		\
 	test-crypto		\
 	test-general		\
+	test-keyfile		\
 	test-secrets		\
 	test-setting-8021x	\
 	test-setting-dcb	\
@@ -38,20 +39,22 @@ endif
 #	-name "test-pkcs12" \
 #	-out test-cert.p12
 
-EXTRA_DIST =                              \
-	certs/test_ca_cert.pem            \
-	certs/test_ca_cert.der            \
-	certs/test_key_and_cert.pem       \
-	certs/test-cert.p12               \
-	certs/test2_ca_cert.pem           \
-	certs/test2_key_and_cert.pem      \
-	certs/test2-cert.p12              \
-	certs/ca-no-ending-newline.pem    \
-	certs/test-key-only.pem           \
-	certs/test-key-only-decrypted.der \
-	certs/test-key-only-decrypted.pem \
-	certs/pkcs8-enc-key.pem           \
-	certs/pkcs8-noenc-key.pem         \
-	certs/pkcs8-decrypted.der         \
-	certs/test-aes-key.pem
+EXTRA_DIST =                            \
+	certs/ca-no-ending-newline.pem      \
+	certs/pkcs8-decrypted.der           \
+	certs/pkcs8-enc-key.pem             \
+	certs/pkcs8-noenc-key.pem           \
+	certs/test2_ca_cert.pem             \
+	certs/test2-cert.p12                \
+	certs/test2_key_and_cert.pem        \
+	certs/test-aes-key.pem              \
+	certs/test_ca_cert.der              \
+	certs/test_ca_cert.pem              \
+	certs/test-ca-cert.pem              \
+	certs/test-cert.p12                 \
+	certs/test_key_and_cert.pem         \
+	certs/test-key-and-cert.pem         \
+	certs/test-key-only-decrypted.der   \
+	certs/test-key-only-decrypted.pem   \
+	certs/test-key-only.pem
 
diff --git a/libnm-core/tests/Makefile.in b/libnm-core/tests/Makefile.in
index c2b15c6a..edb6a50e 100644
--- a/libnm-core/tests/Makefile.in
+++ b/libnm-core/tests/Makefile.in
@@ -90,7 +90,7 @@ build_triplet = @build@
 host_triplet = @host@
 @ENABLE_TESTS_TRUE@noinst_PROGRAMS = test-compare$(EXEEXT) \
 @ENABLE_TESTS_TRUE@	test-crypto$(EXEEXT) test-general$(EXEEXT) \
-@ENABLE_TESTS_TRUE@	test-secrets$(EXEEXT) \
+@ENABLE_TESTS_TRUE@	test-keyfile$(EXEEXT) test-secrets$(EXEEXT) \
 @ENABLE_TESTS_TRUE@	test-setting-8021x$(EXEEXT) \
 @ENABLE_TESTS_TRUE@	test-setting-dcb$(EXEEXT) \
 @ENABLE_TESTS_TRUE@	test-settings-defaults$(EXEEXT)
@@ -140,6 +140,12 @@ test_general_LDADD = $(LDADD)
 @ENABLE_TESTS_TRUE@test_general_DEPENDENCIES =  \
 @ENABLE_TESTS_TRUE@	$(top_builddir)/libnm-core/libnm-core.la \
 @ENABLE_TESTS_TRUE@	$(am__DEPENDENCIES_1)
+test_keyfile_SOURCES = test-keyfile.c
+test_keyfile_OBJECTS = test-keyfile.$(OBJEXT)
+test_keyfile_LDADD = $(LDADD)
+@ENABLE_TESTS_TRUE@test_keyfile_DEPENDENCIES =  \
+@ENABLE_TESTS_TRUE@	$(top_builddir)/libnm-core/libnm-core.la \
+@ENABLE_TESTS_TRUE@	$(am__DEPENDENCIES_1)
 test_secrets_SOURCES = test-secrets.c
 test_secrets_OBJECTS = test-secrets.$(OBJEXT)
 test_secrets_LDADD = $(LDADD)
@@ -198,12 +204,12 @@ AM_V_CCLD = $(am__v_CCLD_@AM_V@)
 am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
 am__v_CCLD_0 = @echo "  CCLD    " $@;
 am__v_CCLD_1 = 
-SOURCES = test-compare.c test-crypto.c test-general.c test-secrets.c \
-	test-setting-8021x.c test-setting-dcb.c \
-	test-settings-defaults.c
-DIST_SOURCES = test-compare.c test-crypto.c test-general.c \
+SOURCES = test-compare.c test-crypto.c test-general.c test-keyfile.c \
 	test-secrets.c test-setting-8021x.c test-setting-dcb.c \
 	test-settings-defaults.c
+DIST_SOURCES = test-compare.c test-crypto.c test-general.c \
+	test-keyfile.c test-secrets.c test-setting-8021x.c \
+	test-setting-dcb.c test-settings-defaults.c
 am__can_run_installinfo = \
   case $$AM_UPDATE_INFO_DIR in \
     n|no|NO) false;; \
@@ -250,13 +256,196 @@ am__tty_colors = { \
     std=''; \
   fi; \
 }
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+    $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+    *) f=$$p;; \
+  esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+  srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+  for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+  for p in $$list; do echo "$$p $$p"; done | \
+  sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+  $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+    if (++n[$$2] == $(am__install_max)) \
+      { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+    END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+  sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+  sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+  test -z "$$files" \
+    || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+    || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+         $(am__cd) "$$dir" && rm -f $$files; }; \
+  }
+am__recheck_rx = ^[ 	]*:recheck:[ 	]*
+am__global_test_result_rx = ^[ 	]*:global-test-result:[ 	]*
+am__copy_in_global_log_rx = ^[ 	]*:copy-in-global-log:[ 	]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+  recheck = 1; \
+  while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+    { \
+      if (rc < 0) \
+        { \
+          if ((getline line2 < ($$0 ".log")) < 0) \
+	    recheck = 0; \
+          break; \
+        } \
+      else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+        { \
+          recheck = 0; \
+          break; \
+        } \
+      else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+        { \
+          break; \
+        } \
+    }; \
+  if (recheck) \
+    print $$0; \
+  close ($$0 ".trs"); \
+  close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+  print "fatal: making $@: " msg | "cat >&2"; \
+  exit 1; \
+} \
+function rst_section(header) \
+{ \
+  print header; \
+  len = length(header); \
+  for (i = 1; i <= len; i = i + 1) \
+    printf "="; \
+  printf "\n\n"; \
+} \
+{ \
+  copy_in_global_log = 1; \
+  global_test_result = "RUN"; \
+  while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+    { \
+      if (rc < 0) \
+         fatal("failed to read from " $$0 ".trs"); \
+      if (line ~ /$(am__global_test_result_rx)/) \
+        { \
+          sub("$(am__global_test_result_rx)", "", line); \
+          sub("[ 	]*$$", "", line); \
+          global_test_result = line; \
+        } \
+      else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+        copy_in_global_log = 0; \
+    }; \
+  if (copy_in_global_log) \
+    { \
+      rst_section(global_test_result ": " $$0); \
+      while ((rc = (getline line < ($$0 ".log"))) != 0) \
+      { \
+        if (rc < 0) \
+          fatal("failed to read from " $$0 ".log"); \
+        print line; \
+      }; \
+      printf "\n"; \
+    }; \
+  close ($$0 ".trs"); \
+  close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/   &   /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this.  Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+  --color-tests "$$am__color_tests" \
+  --enable-hard-errors "$$am__enable_hard_errors" \
+  --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test.  Creates the
+# directory for the log if needed.  Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log.  Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT.  Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup);					\
+$(am__vpath_adj_setup) $(am__vpath_adj)			\
+$(am__tty_colors);					\
+srcdir=$(srcdir); export srcdir;			\
+case "$@" in						\
+  */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;;	\
+    *) am__odir=.;; 					\
+esac;							\
+test "x$$am__odir" = x"." || test -d "$$am__odir" 	\
+  || $(MKDIR_P) "$$am__odir" || exit $$?;		\
+if test -f "./$$f"; then dir=./;			\
+elif test -f "$$f"; then dir=;				\
+else dir="$(srcdir)/"; fi;				\
+tst=$$dir$$f; log='$@'; 				\
+if test -n '$(DISABLE_HARD_ERRORS)'; then		\
+  am__enable_hard_errors=no; 				\
+else							\
+  am__enable_hard_errors=yes; 				\
+fi; 							\
+case " $(XFAIL_TESTS) " in				\
+  *[\ \	]$$f[\ \	]* | *[\ \	]$$dir$$f[\ \	]*) \
+    am__expect_failure=yes;;				\
+  *)							\
+    am__expect_failure=no;;				\
+esac; 							\
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed).  The result is saved in the shell variable
+# '$bases'.  This honors runtime overriding of TESTS and TEST_LOGS.  Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+  bases='$(TEST_LOGS)'; \
+  bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+  bases=`echo $$bases`
+RECHECK_LOGS = $(TEST_LOGS)
+AM_RECURSIVE_TARGETS = check recheck
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+  case '$@' in \
+    */*) \
+      case '$*' in \
+        */*) b='$*';; \
+          *) b=`echo '$@' | sed 's/\.log$$//'`; \
+       esac;; \
+    *) \
+      b='$*';; \
+  esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/build-aux/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+	$(TEST_LOG_FLAGS)
 am__DIST_COMMON = $(srcdir)/Makefile.in \
-	$(top_srcdir)/build-aux/depcomp
+	$(top_srcdir)/build-aux/depcomp \
+	$(top_srcdir)/build-aux/test-driver
 DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
 ACLOCAL = @ACLOCAL@
 ALL_LINGUAS = @ALL_LINGUAS@
 AMTAR = @AMTAR@
 AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AM_TESTS_FD_REDIRECT = @AM_TESTS_FD_REDIRECT@
 AR = @AR@
 AUTOCONF = @AUTOCONF@
 AUTOHEADER = @AUTOHEADER@
@@ -370,6 +559,7 @@ LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
 LIPO = @LIPO@
 LN_S = @LN_S@
+LOG_DRIVER = @LOG_DRIVER@
 LTLIBICONV = @LTLIBICONV@
 LTLIBINTL = @LTLIBINTL@
 LTLIBOBJS = @LTLIBOBJS@
@@ -538,26 +728,28 @@ with_valgrind = @with_valgrind@
 #	-name "test-pkcs12" \
 #	-out test-cert.p12
 EXTRA_DIST = \
-	certs/test_ca_cert.pem            \
-	certs/test_ca_cert.der            \
-	certs/test_key_and_cert.pem       \
-	certs/test-cert.p12               \
-	certs/test2_ca_cert.pem           \
-	certs/test2_key_and_cert.pem      \
-	certs/test2-cert.p12              \
-	certs/ca-no-ending-newline.pem    \
-	certs/test-key-only.pem           \
-	certs/test-key-only-decrypted.der \
-	certs/test-key-only-decrypted.pem \
-	certs/pkcs8-enc-key.pem           \
-	certs/pkcs8-noenc-key.pem         \
-	certs/pkcs8-decrypted.der         \
-	certs/test-aes-key.pem
+	certs/ca-no-ending-newline.pem      \
+	certs/pkcs8-decrypted.der           \
+	certs/pkcs8-enc-key.pem             \
+	certs/pkcs8-noenc-key.pem           \
+	certs/test2_ca_cert.pem             \
+	certs/test2-cert.p12                \
+	certs/test2_key_and_cert.pem        \
+	certs/test-aes-key.pem              \
+	certs/test_ca_cert.der              \
+	certs/test_ca_cert.pem              \
+	certs/test-ca-cert.pem              \
+	certs/test-cert.p12                 \
+	certs/test_key_and_cert.pem         \
+	certs/test-key-and-cert.pem         \
+	certs/test-key-only-decrypted.der   \
+	certs/test-key-only-decrypted.pem   \
+	certs/test-key-only.pem
 
 all: all-am
 
 .SUFFIXES:
-.SUFFIXES: .c .lo .o .obj
+.SUFFIXES: .c .lo .log .o .obj .test .test$(EXEEXT) .trs
 $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am  $(am__configure_deps)
 	@for dep in $?; do \
 	  case '$(am__configure_deps)' in \
@@ -609,6 +801,10 @@ test-general$(EXEEXT): $(test_general_OBJECTS) $(test_general_DEPENDENCIES) $(EX
 	@rm -f test-general$(EXEEXT)
 	$(AM_V_CCLD)$(LINK) $(test_general_OBJECTS) $(test_general_LDADD) $(LIBS)
 
+test-keyfile$(EXEEXT): $(test_keyfile_OBJECTS) $(test_keyfile_DEPENDENCIES) $(EXTRA_test_keyfile_DEPENDENCIES) 
+	@rm -f test-keyfile$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(test_keyfile_OBJECTS) $(test_keyfile_LDADD) $(LIBS)
+
 test-secrets$(EXEEXT): $(test_secrets_OBJECTS) $(test_secrets_DEPENDENCIES) $(EXTRA_test_secrets_DEPENDENCIES) 
 	@rm -f test-secrets$(EXEEXT)
 	$(AM_V_CCLD)$(LINK) $(test_secrets_OBJECTS) $(test_secrets_LDADD) $(LIBS)
@@ -634,6 +830,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-compare.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-crypto.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-general.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-keyfile.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-secrets.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-setting-8021x.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-setting-dcb.Po@am__quote@
@@ -718,98 +915,217 @@ cscopelist-am: $(am__tagged_files)
 distclean-tags:
 	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
 
-check-TESTS: $(TESTS)
-	@failed=0; all=0; xfail=0; xpass=0; skip=0; \
-	srcdir=$(srcdir); export srcdir; \
-	list=' $(TESTS) '; \
-	$(am__tty_colors); \
-	if test -n "$$list"; then \
-	  for tst in $$list; do \
-	    if test -f ./$$tst; then dir=./; \
-	    elif test -f $$tst; then dir=; \
-	    else dir="$(srcdir)/"; fi; \
-	    if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
-	      all=`expr $$all + 1`; \
-	      case " $(XFAIL_TESTS) " in \
-	      *[\ \	]$$tst[\ \	]*) \
-		xpass=`expr $$xpass + 1`; \
-		failed=`expr $$failed + 1`; \
-		col=$$red; res=XPASS; \
-	      ;; \
-	      *) \
-		col=$$grn; res=PASS; \
-	      ;; \
-	      esac; \
-	    elif test $$? -ne 77; then \
-	      all=`expr $$all + 1`; \
-	      case " $(XFAIL_TESTS) " in \
-	      *[\ \	]$$tst[\ \	]*) \
-		xfail=`expr $$xfail + 1`; \
-		col=$$lgn; res=XFAIL; \
-	      ;; \
-	      *) \
-		failed=`expr $$failed + 1`; \
-		col=$$red; res=FAIL; \
-	      ;; \
-	      esac; \
-	    else \
-	      skip=`expr $$skip + 1`; \
-	      col=$$blu; res=SKIP; \
-	    fi; \
-	    echo "$${col}$$res$${std}: $$tst"; \
-	  done; \
-	  if test "$$all" -eq 1; then \
-	    tests="test"; \
-	    All=""; \
-	  else \
-	    tests="tests"; \
-	    All="All "; \
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'.  Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+	rm -f $< $@
+	$(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+	@:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+	@$(am__set_TESTS_bases); \
+	am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+	redo_bases=`for i in $$bases; do \
+	              am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+	            done`; \
+	if test -n "$$redo_bases"; then \
+	  redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+	  redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+	  if $(am__make_dryrun); then :; else \
+	    rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
 	  fi; \
-	  if test "$$failed" -eq 0; then \
-	    if test "$$xfail" -eq 0; then \
-	      banner="$$All$$all $$tests passed"; \
+	fi; \
+	if test -n "$$am__remaking_logs"; then \
+	  echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+	       "recursion detected" >&2; \
+	elif test -n "$$redo_logs"; then \
+	  am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+	fi; \
+	if $(am__make_dryrun); then :; else \
+	  st=0;  \
+	  errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+	  for i in $$redo_bases; do \
+	    test -f $$i.trs && test -r $$i.trs \
+	      || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+	    test -f $$i.log && test -r $$i.log \
+	      || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+	  done; \
+	  test $$st -eq 0 || exit 1; \
+	fi
+	@$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+	ws='[ 	]'; \
+	results=`for b in $$bases; do echo $$b.trs; done`; \
+	test -n "$$results" || results=/dev/null; \
+	all=`  grep "^$$ws*:test-result:"           $$results | wc -l`; \
+	pass=` grep "^$$ws*:test-result:$$ws*PASS"  $$results | wc -l`; \
+	fail=` grep "^$$ws*:test-result:$$ws*FAIL"  $$results | wc -l`; \
+	skip=` grep "^$$ws*:test-result:$$ws*SKIP"  $$results | wc -l`; \
+	xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+	xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+	error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+	if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+	  success=true; \
+	else \
+	  success=false; \
+	fi; \
+	br='==================='; br=$$br$$br$$br$$br; \
+	result_count () \
+	{ \
+	    if test x"$$1" = x"--maybe-color"; then \
+	      maybe_colorize=yes; \
+	    elif test x"$$1" = x"--no-color"; then \
+	      maybe_colorize=no; \
 	    else \
-	      if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
-	      banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
+	      echo "$@: invalid 'result_count' usage" >&2; exit 4; \
 	    fi; \
-	  else \
-	    if test "$$xpass" -eq 0; then \
-	      banner="$$failed of $$all $$tests failed"; \
+	    shift; \
+	    desc=$$1 count=$$2; \
+	    if test $$maybe_colorize = yes && test $$count -gt 0; then \
+	      color_start=$$3 color_end=$$std; \
 	    else \
-	      if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
-	      banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+	      color_start= color_end=; \
 	    fi; \
-	  fi; \
-	  dashes="$$banner"; \
-	  skipped=""; \
-	  if test "$$skip" -ne 0; then \
-	    if test "$$skip" -eq 1; then \
-	      skipped="($$skip test was not run)"; \
-	    else \
-	      skipped="($$skip tests were not run)"; \
-	    fi; \
-	    test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
-	      dashes="$$skipped"; \
-	  fi; \
-	  report=""; \
-	  if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
-	    report="Please report to $(PACKAGE_BUGREPORT)"; \
-	    test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
-	      dashes="$$report"; \
-	  fi; \
-	  dashes=`echo "$$dashes" | sed s/./=/g`; \
-	  if test "$$failed" -eq 0; then \
-	    col="$$grn"; \
-	  else \
-	    col="$$red"; \
-	  fi; \
-	  echo "$${col}$$dashes$${std}"; \
-	  echo "$${col}$$banner$${std}"; \
-	  test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
-	  test -z "$$report" || echo "$${col}$$report$${std}"; \
-	  echo "$${col}$$dashes$${std}"; \
-	  test "$$failed" -eq 0; \
-	else :; fi
+	    echo "$${color_start}# $$desc $$count$${color_end}"; \
+	}; \
+	create_testsuite_report () \
+	{ \
+	  result_count $$1 "TOTAL:" $$all   "$$brg"; \
+	  result_count $$1 "PASS: " $$pass  "$$grn"; \
+	  result_count $$1 "SKIP: " $$skip  "$$blu"; \
+	  result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+	  result_count $$1 "FAIL: " $$fail  "$$red"; \
+	  result_count $$1 "XPASS:" $$xpass "$$red"; \
+	  result_count $$1 "ERROR:" $$error "$$mgn"; \
+	}; \
+	{								\
+	  echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" |	\
+	    $(am__rst_title);						\
+	  create_testsuite_report --no-color;				\
+	  echo;								\
+	  echo ".. contents:: :depth: 2";				\
+	  echo;								\
+	  for b in $$bases; do echo $$b; done				\
+	    | $(am__create_global_log);					\
+	} >$(TEST_SUITE_LOG).tmp || exit 1;				\
+	mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG);			\
+	if $$success; then						\
+	  col="$$grn";							\
+	 else								\
+	  col="$$red";							\
+	  test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG);		\
+	fi;								\
+	echo "$${col}$$br$${std}"; 					\
+	echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}";	\
+	echo "$${col}$$br$${std}"; 					\
+	create_testsuite_report --maybe-color;				\
+	echo "$$col$$br$$std";						\
+	if $$success; then :; else					\
+	  echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}";		\
+	  if test -n "$(PACKAGE_BUGREPORT)"; then			\
+	    echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}";	\
+	  fi;								\
+	  echo "$$col$$br$$std";					\
+	fi;								\
+	$$success || exit 1
+
+check-TESTS:
+	@list='$(RECHECK_LOGS)';           test -z "$$list" || rm -f $$list
+	@list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+	@test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+	@set +e; $(am__set_TESTS_bases); \
+	log_list=`for i in $$bases; do echo $$i.log; done`; \
+	trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+	log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+	$(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+	exit $$?;
+recheck: all 
+	@test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+	@set +e; $(am__set_TESTS_bases); \
+	bases=`for i in $$bases; do echo $$i; done \
+	         | $(am__list_recheck_tests)` || exit 1; \
+	log_list=`for i in $$bases; do echo $$i.log; done`; \
+	log_list=`echo $$log_list`; \
+	$(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+	        am__force_recheck=am--force-recheck \
+	        TEST_LOGS="$$log_list"; \
+	exit $$?
+test-compare.log: test-compare$(EXEEXT)
+	@p='test-compare$(EXEEXT)'; \
+	b='test-compare'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-crypto.log: test-crypto$(EXEEXT)
+	@p='test-crypto$(EXEEXT)'; \
+	b='test-crypto'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-general.log: test-general$(EXEEXT)
+	@p='test-general$(EXEEXT)'; \
+	b='test-general'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-keyfile.log: test-keyfile$(EXEEXT)
+	@p='test-keyfile$(EXEEXT)'; \
+	b='test-keyfile'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-secrets.log: test-secrets$(EXEEXT)
+	@p='test-secrets$(EXEEXT)'; \
+	b='test-secrets'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-setting-8021x.log: test-setting-8021x$(EXEEXT)
+	@p='test-setting-8021x$(EXEEXT)'; \
+	b='test-setting-8021x'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-setting-dcb.log: test-setting-dcb$(EXEEXT)
+	@p='test-setting-dcb$(EXEEXT)'; \
+	b='test-setting-dcb'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+test-settings-defaults.log: test-settings-defaults$(EXEEXT)
+	@p='test-settings-defaults$(EXEEXT)'; \
+	b='test-settings-defaults'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+	@p='$<'; \
+	$(am__set_b); \
+	$(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@	@p='$<'; \
+@am__EXEEXT_TRUE@	$(am__set_b); \
+@am__EXEEXT_TRUE@	$(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@	--log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@	$(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@	"$$tst" $(AM_TESTS_FD_REDIRECT)
 
 distdir: $(DISTFILES)
 	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
@@ -866,6 +1182,9 @@ install-strip:
 	    "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
 	fi
 mostlyclean-generic:
+	-test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+	-test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+	-test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
 
 clean-generic:
 
@@ -961,7 +1280,7 @@ uninstall-am:
 	installcheck-am installdirs maintainer-clean \
 	maintainer-clean-generic mostlyclean mostlyclean-compile \
 	mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
-	tags tags-am uninstall uninstall-am
+	recheck tags tags-am uninstall uninstall-am
 
 .PRECIOUS: Makefile
 
diff --git a/libnm-core/tests/certs/test-ca-cert.pem b/libnm-core/tests/certs/test-ca-cert.pem
new file mode 100644
index 00000000..ef1be20d
--- /dev/null
+++ b/libnm-core/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/libnm-core/tests/certs/test-key-and-cert.pem b/libnm-core/tests/certs/test-key-and-cert.pem
new file mode 100644
index 00000000..dec9aa1b
--- /dev/null
+++ b/libnm-core/tests/certs/test-key-and-cert.pem
@@ -0,0 +1,118 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,4DE0615F23D82107
+
+QPNCO5Dobvz9dDhN32KkZRoEifW+HDm2PCbRQhKDiscGwB6LgypvVjHNsZiFKwzz
+L4R51UqgQeJx7GSGJqE626e9z9J+UNBhop02aOO2X0eSPdvBzr/uJ6Umiyr1xqD7
+zWf7u9l5kXElDJRhK+87GMBewp4Ie9NeXDjhF8hzC5Kiulen4AH3AYnfH3S7DimU
+h8GFMg8inrudrTbcjBhCdPeHG2jCygOxw3InRFz7uaN6LIhOaPQvmvpP4Cc1WRnW
+ZPq9o+eU3fPWPD5t+Op/VzYLvKwgBy/yK1rQXUm6ZMO7MhhRJ94ZCsJv+nVWpJlv
+QyBlxDKxwfkfYbDELdnnDQdHdMbKatLqa0KhSkgpp8LywBtanPz731tyT0r7b3na
+eLdra59lRU7ZQLPEdS3lPZd2O/KQvWf8wbg7MjXS9LxQ7R5HOPu6DNJlwXVZBmmo
+cAfu2q8ubU2IePvWLD1GOrBi6hE9TiGvFJkw+wBK+t72sz3njv9Xm/zlxruaEk5m
+RW/kybU3FP4PtjriBbskz3/VZaaxuRN7OoOYTkmyHmG1ADgcRUV6fea19qqsBlN8
+xb+SRtoH28oT/JVWU5neE2dbNzk5LeVO+w70NNdR5s5xqkBhbGGaJxvXwNP4ltFr
+T06SMh8znOLKwWB00aRtwfU7jOwR3mOleQO4ugIHmau3zp1TqzAHW8XtpuV7qVeI
+ESZOZuf0vW43BtNzgLXt1+r+bmsMsRwhnyomL9M0TUyyBdVYY9GkzTG9pOESheRo
+RSvAZ8qKGUliTpgBcbt2v1+NqkszcHa6FxuvS8YU4uo5/GqsgTxHTNIB232hIrrZ
+EIm6QL9TC5oFXMjy6UNqoCm5Nb8DBJ6aErt7pt7aoktqUW3O3QIzQT3IbZ4nAcTt
+lVF4d7j29I9t7bcC8GOVU1neilguZUss4ghJg9x4zI5UZdR7hZ8fbFT47TyxB+j5
+r0YdmjbjVTaSyaN2JGh1wvb4TzawGNVx/U2EJE16HigOtPfsfQRJ3x+FROKBdVa4
+aIFYXkRBeIPxX6n9pcw0lBCsnXo6/5iTjQSk2VqO3rHO/wyWiEjNczhL33dY2A8W
+GG5ECMO5SqXZHQQzpABqK94dxe3UC8aEESO5NhEqDuV7qQGol0qPKrUA3wb0jb2e
+DrejJ9HS2m1SUDmjpvvmEGy6GN7CRibbKt5rNZdJNNvWArOF5d0F6wkixQLl73oE
+lq5gLQQk9n7ClleKLhlQpBCorxilBbzmSUekkJLi0eaZiBBFWBX9udqnUZloXTgO
+8qwuO8K/GPR9Jy1/UH2Vh1H+wivaqKTVgEb0NotzgzECgTEFKJafl7rUNs1OZRZ3
+VBjevi6+iDpxVFgF71kXfdUC4ph0E1XDl0ja2rrKQGivMkUhWJ57+4EV5+hBkAnt
+G0RV45NwHXLrK2bd8F9PlRk2XHW6mIcFRXsW1DjeBhk/sQjvlO9R01GRSgcXtekJ
+tmX17FWrMrzXHpvy1IC3fk4RVnSjpzQ8O+17YE8/la9wVaeZZzHyYFmMT7VXjIhW
+QozJQ0vJ2jxJRh5GYn3tpJzdaeRfvTBik0pChNdUTnWP+BJ35xoCTs8iwJbmgVZ1
+-----END RSA PRIVATE KEY-----
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+        Signature Algorithm: md5WithRSAEncryption
+        Issuer: C=US, ST=Berkshire, L=Newbury, O=My Company Ltd, OU=Testing, CN=test/emailAddress=test@test.com
+        Validity
+            Not Before: Mar 10 15:13:16 2009 GMT
+            Not After : Mar  8 15:13:16 2019 GMT
+        Subject: C=US, ST=Berkshire, O=My Company Ltd, OU=Testing, CN=test1/emailAddress=test@test.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:cd:34:b1:2e:b0:04:c6:f4:2b:a2:c0:a0:39:7a:
+                    82:ed:96:c4:f7:19:83:91:5c:b4:e7:9c:de:ec:48:
+                    ec:2d:e4:51:08:26:42:ac:d3:98:26:7a:72:f7:49:
+                    c2:9e:66:05:c6:47:29:fe:3b:ac:6b:af:6f:5e:a8:
+                    03:5a:73:33:ba:19:03:00:35:f5:00:bc:a8:be:14:
+                    ce:46:69:e3:6d:ed:34:37:85:55:87:62:b3:b7:c9:
+                    c0:cc:9a:aa:61:05:5b:cd:a2:17:42:d3:e5:6f:1c:
+                    60:8d:c2:15:41:46:f8:12:54:d0:38:57:e1:fd:8d:
+                    44:c8:fb:56:b3:b9:6c:e9:f8:9e:21:11:57:1b:8b:
+                    f9:cf:e3:17:e7:d8:fd:ac:d1:01:c6:92:30:f3:2d:
+                    c9:d6:c1:f0:3d:fd:ca:30:dd:75:74:e7:d1:6b:75:
+                    d8:c5:4d:43:61:fe:f6:ad:7e:4c:63:7c:03:17:a2:
+                    06:8f:d0:8b:69:d3:7a:07:0f:0b:a2:cf:0c:70:38:
+                    ba:cc:55:35:60:84:58:d8:d2:be:1f:ef:76:a9:ba:
+                    ae:6a:dc:08:97:80:de:42:00:b7:d4:ce:9a:b0:36:
+                    2a:c7:6f:45:04:7c:ea:41:19:d8:b9:19:04:1f:11:
+                    a9:22:80:bd:69:08:15:0d:3c:de:cd:7e:88:6c:0f:
+                    a3:43
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                CE:03:7E:EF:E7:DE:C9:87:BF:DE:56:F4:C8:A3:40:F6:C8:6F:05:8C
+            X509v3 Authority Key Identifier: 
+                keyid:B8:35:37:32:BE:CF:4F:79:F5:7B:74:B2:F2:10:5A:BA:80:C5:6A:10
+                DirName:/C=US/ST=Berkshire/L=Newbury/O=My Company Ltd/OU=Testing/CN=test/emailAddress=test@test.com
+                serial:EB:E7:64:FB:79:F7:22:19
+
+    Signature Algorithm: md5WithRSAEncryption
+        7a:20:93:63:40:73:7d:33:01:2e:c0:13:52:a4:a7:e1:4d:82:
+        f4:fb:b2:7b:d0:2b:5a:3f:0e:3c:28:61:71:ab:01:4d:fe:89:
+        b5:cd:2f:97:59:93:53:9d:51:86:48:dd:b9:e4:73:5e:22:0b:
+        12:0d:25:39:76:16:44:06:0c:40:45:21:6b:a6:b1:e0:bf:76:
+        1b:36:f3:1e:41:82:57:d9:59:b7:60:40:43:1c:1d:79:f6:48:
+        32:5c:4e:e2:06:89:96:41:d2:54:1f:4a:6f:f6:78:a5:3c:02:
+        85:21:e2:65:e1:8a:6d:24:19:95:f8:c0:35:ab:bd:ff:3d:f1:
+        fb:50:2d:30:1e:67:a6:7c:50:f9:d5:77:66:77:5a:14:0f:5c:
+        cd:21:09:9b:a3:92:57:19:dd:01:a4:18:c5:f9:70:e4:17:43:
+        8d:b1:e6:61:e9:50:89:83:4f:ce:a4:57:68:58:40:70:ae:71:
+        1c:47:66:d2:30:54:50:ea:3a:87:32:64:3b:18:42:fe:5a:19:
+        07:64:f7:f1:b1:10:07:fd:a7:d2:a7:a8:05:79:5b:25:ba:69:
+        7b:1a:3e:b1:3e:e4:17:17:01:ba:eb:54:ae:83:00:ed:66:62:
+        8d:c0:3e:8a:b4:27:5f:e9:01:ce:20:c3:34:a9:28:c0:6f:c7:
+        3b:65:fe:f9
+-----BEGIN CERTIFICATE-----
+MIIEojCCA4qgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBizELMAkGA1UEBhMCVVMx
+EjAQBgNVBAgTCUJlcmtzaGlyZTEQMA4GA1UEBxMHTmV3YnVyeTEXMBUGA1UEChMO
+TXkgQ29tcGFueSBMdGQxEDAOBgNVBAsTB1Rlc3RpbmcxDTALBgNVBAMTBHRlc3Qx
+HDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wHhcNMDkwMzEwMTUxMzE2WhcN
+MTkwMzA4MTUxMzE2WjB6MQswCQYDVQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJl
+MRcwFQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzEOMAwG
+A1UEAxMFdGVzdDExHDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNNLEusATG9CuiwKA5eoLtlsT3GYOR
+XLTnnN7sSOwt5FEIJkKs05gmenL3ScKeZgXGRyn+O6xrr29eqANaczO6GQMANfUA
+vKi+FM5GaeNt7TQ3hVWHYrO3ycDMmqphBVvNohdC0+VvHGCNwhVBRvgSVNA4V+H9
+jUTI+1azuWzp+J4hEVcbi/nP4xfn2P2s0QHGkjDzLcnWwfA9/cow3XV059FrddjF
+TUNh/vatfkxjfAMXogaP0Itp03oHDwuizwxwOLrMVTVghFjY0r4f73apuq5q3AiX
+gN5CALfUzpqwNirHb0UEfOpBGdi5GQQfEakigL1pCBUNPN7NfohsD6NDAgMBAAGj
+ggEfMIIBGzAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVy
+YXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUzgN+7+feyYe/3lb0yKNA9shvBYww
+gcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDFahChgZGkgY4wgYsxCzAJ
+BgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAOBgNVBAcTB05ld2J1cnkx
+FzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQLEwdUZXN0aW5nMQ0wCwYD
+VQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3QuY29tggkA6+dk+3n3
+IhkwDQYJKoZIhvcNAQEEBQADggEBAHogk2NAc30zAS7AE1Kkp+FNgvT7snvQK1o/
+DjwoYXGrAU3+ibXNL5dZk1OdUYZI3bnkc14iCxINJTl2FkQGDEBFIWumseC/dhs2
+8x5BglfZWbdgQEMcHXn2SDJcTuIGiZZB0lQfSm/2eKU8AoUh4mXhim0kGZX4wDWr
+vf898ftQLTAeZ6Z8UPnVd2Z3WhQPXM0hCZujklcZ3QGkGMX5cOQXQ42x5mHpUImD
+T86kV2hYQHCucRxHZtIwVFDqOocyZDsYQv5aGQdk9/GxEAf9p9KnqAV5WyW6aXsa
+PrE+5BcXAbrrVK6DAO1mYo3APoq0J1/pAc4gwzSpKMBvxztl/vk=
+-----END CERTIFICATE-----
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index 7df4e57a..bd144f38 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -19,6 +19,8 @@
  *
  */
 
+#define NM_GLIB_COMPAT_H_TEST
+
 #include "config.h"
 
 #include <glib.h>
@@ -57,6 +59,7 @@
 #include "nm-setting-wireless.h"
 #include "nm-setting-wireless-security.h"
 #include "nm-simple-connection.h"
+#include "nm-glib-compat.h"
 
 #include "nm-test-utils.h"
 
@@ -1963,6 +1966,7 @@ test_connection_diff_a_only (void)
 			{ NM_SETTING_CONNECTION_ZONE,                 NM_SETTING_DIFF_RESULT_IN_A },
 			{ NM_SETTING_CONNECTION_MASTER,               NM_SETTING_DIFF_RESULT_IN_A },
 			{ NM_SETTING_CONNECTION_SLAVE_TYPE,           NM_SETTING_DIFF_RESULT_IN_A },
+			{ NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES,   NM_SETTING_DIFF_RESULT_IN_A },
 			{ NM_SETTING_CONNECTION_SECONDARIES,          NM_SETTING_DIFF_RESULT_IN_A },
 			{ NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT, NM_SETTING_DIFF_RESULT_IN_A },
 			{ NULL, NM_SETTING_DIFF_RESULT_UNKNOWN }
@@ -4038,6 +4042,410 @@ test_nm_utils_uuid_generate_from_string (void)
 	_test_uuid (NM_UTILS_UUID_TYPE_VARIANT3, "002a0ada-f547-375a-bab5-896a11d1927e", "a\0b", 3, UUID_NS_DNS);
 }
 
+/*******************************************/
+
+static void
+__test_uuid (const char *expected_uuid, const char *str, gssize slen, char *uuid_test)
+{
+	g_assert (uuid_test);
+	g_assert (nm_utils_is_uuid (uuid_test));
+
+	if (strcmp (uuid_test, expected_uuid)) {
+		g_error ("UUID test failed (1): text=%s, len=%lld, expected=%s, uuid_test=%s",
+		         str, (long long) slen, expected_uuid, uuid_test);
+	}
+	g_free (uuid_test);
+
+	uuid_test = nm_utils_uuid_generate_from_string (str, slen, NM_UTILS_UUID_TYPE_VARIANT3, NM_UTILS_UUID_NS);
+
+	g_assert (uuid_test);
+	g_assert (nm_utils_is_uuid (uuid_test));
+
+	if (strcmp (uuid_test, expected_uuid)) {
+		g_error ("UUID test failed (2): text=%s; len=%lld, expected=%s, uuid2=%s",
+		         str, (long long) slen, expected_uuid, uuid_test);
+	}
+	g_free (uuid_test);
+}
+
+#define _test_uuid(expected_uuid, str, strlen, ...) __test_uuid (expected_uuid, str, strlen, _nm_utils_uuid_generate_from_strings(__VA_ARGS__, NULL))
+
+static void
+test_nm_utils_uuid_generate_from_strings (void)
+{
+	_test_uuid ("b07c334a-399b-32de-8d50-58e4e08f98e3", "",         0, NULL);
+	_test_uuid ("b8a426cb-bcb5-30a3-bd8f-6786fea72df9", "\0",       1, "");
+	_test_uuid ("12a4a982-7aae-39e1-951e-41aeb1250959", "a\0",      2, "a");
+	_test_uuid ("69e22c7e-f89f-3a43-b239-1cb52ed8db69", "aa\0",     3, "aa");
+	_test_uuid ("59829fd3-5ad5-3d90-a7b0-4911747e4088", "\0\0",     2, "",   "");
+	_test_uuid ("01ad0e06-6c50-3384-8d86-ddab81421425", "a\0\0",    3, "a",  "");
+	_test_uuid ("e1ed8647-9ed3-3ec8-8c6d-e8204524d71d", "aa\0\0",   4, "aa", "");
+	_test_uuid ("fb1c7cd6-275c-3489-9382-83b900da8af0", "\0a\0",    3, "",   "a");
+	_test_uuid ("5d79494e-c4ba-31a6-80a2-d6016ccd7e17", "a\0a\0",   4, "a",  "a");
+	_test_uuid ("fd698d86-1b60-3ebe-855f-7aada9950a8d", "aa\0a\0",  5, "aa", "a");
+	_test_uuid ("8c573b48-0f01-30ba-bb94-c5f59f4fe517", "\0aa\0",   4, "",   "aa");
+	_test_uuid ("2bdd3d46-eb83-3c53-a41b-a724d04b5544", "a\0aa\0",  5, "a",  "aa");
+	_test_uuid ("13d4b780-07c1-3ba7-b449-81c4844ef039", "aa\0aa\0", 6, "aa", "aa");
+	_test_uuid ("dd265bf7-c05a-3037-9939-b9629858a477", "a\0b\0",   4, "a",  "b");
+}
+
+/******************************************************************************/
+
+static void
+test_nm_utils_ascii_str_to_int64_check (const char *str, guint base, gint64 min,
+                                        gint64 max, gint64 fallback, int exp_errno,
+                                        gint64 exp_val)
+{
+	gint64 v;
+
+	errno = 1;
+	v = _nm_utils_ascii_str_to_int64 (str, base, min, max, fallback);
+	g_assert_cmpint (errno, ==, exp_errno);
+	g_assert_cmpint (v, ==, exp_val);
+}
+
+static void
+test_nm_utils_ascii_str_to_int64_do (const char *str, guint base, gint64 min,
+                                     gint64 max, gint64 fallback, int exp_errno,
+                                     gint64 exp_val)
+{
+	const char *sign = "";
+	const char *val;
+	static const char *whitespaces[] = {
+		"",
+		" ",
+		"\r\n\t",
+		" \r\n\t ",
+		" \r\n\t \t\r\n\t",
+		NULL,
+	};
+	static const char *nulls[] = {
+		"",
+		"0",
+		"00",
+		"0000",
+		"0000000000000000",
+		"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+		"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+		NULL,
+	};
+	const char **ws_pre, **ws_post, **null;
+	guint i;
+
+	if (str == NULL || exp_errno != 0) {
+		test_nm_utils_ascii_str_to_int64_check (str, base, min, max, fallback, exp_errno, exp_val);
+		return;
+	}
+
+	if (strncmp (str, "-", 1) == 0)
+		sign = "-";
+
+	val = str + strlen (sign);
+
+	for (ws_pre = whitespaces; *ws_pre; ws_pre++) {
+		for (ws_post = whitespaces; *ws_post; ws_post++) {
+			for (null = nulls; *null; null++) {
+				for (i = 0; ; i++) {
+					char *s;
+					const char *str_base = "";
+
+					if (base == 16) {
+						if (i == 1)
+							str_base = "0x";
+						else if (i > 1)
+							break;
+					} else if (base == 8) {
+						if (i == 1)
+							str_base = "0";
+						else if (i > 1)
+							break;
+					} else if (base == 0) {
+						if (i > 0)
+							break;
+						/* with base==0, a leading zero would be interpreted as octal. Only test without *null */
+						if ((*null)[0])
+							break;
+					} else {
+						if (i > 0)
+							break;
+					}
+
+					s = g_strdup_printf ("%s%s%s%s%s%s", *ws_pre, sign, str_base, *null, val, *ws_post);
+
+					test_nm_utils_ascii_str_to_int64_check (s, base, min, max, fallback, exp_errno, exp_val);
+					g_free (s);
+				}
+			}
+		}
+	}
+}
+
+static void
+test_nm_utils_ascii_str_to_int64 (void)
+{
+	test_nm_utils_ascii_str_to_int64_do (NULL, 10, 0, 10000, -1, EINVAL, -1);
+	test_nm_utils_ascii_str_to_int64_do ("", 10, 0, 10000, -1, EINVAL, -1);
+	test_nm_utils_ascii_str_to_int64_do ("1x", 10, 0, 10000, -1, EINVAL, -1);
+	test_nm_utils_ascii_str_to_int64_do ("4711", 10, 0, 10000, -1, 0, 4711);
+	test_nm_utils_ascii_str_to_int64_do ("10000", 10, 0, 10000, -1, 0, 10000);
+	test_nm_utils_ascii_str_to_int64_do ("10001", 10, 0, 10000, -1, ERANGE, -1);
+	test_nm_utils_ascii_str_to_int64_do ("FF", 16, 0, 10000, -1, 0, 255);
+	test_nm_utils_ascii_str_to_int64_do ("FF", 10, 0, 10000, -2, EINVAL, -2);
+	test_nm_utils_ascii_str_to_int64_do ("9223372036854775807", 10, 0, G_MAXINT64, -2, 0, G_MAXINT64);
+	test_nm_utils_ascii_str_to_int64_do ("7FFFFFFFFFFFFFFF", 16, 0, G_MAXINT64, -2, 0, G_MAXINT64);
+	test_nm_utils_ascii_str_to_int64_do ("9223372036854775808", 10, 0, G_MAXINT64, -2, ERANGE, -2);
+	test_nm_utils_ascii_str_to_int64_do ("-9223372036854775808", 10, G_MININT64, 0, -2, 0, G_MININT64);
+	test_nm_utils_ascii_str_to_int64_do ("-9223372036854775808", 10, G_MININT64+1, 0, -2, ERANGE, -2);
+	test_nm_utils_ascii_str_to_int64_do ("-9223372036854775809", 10, G_MININT64, 0, -2, ERANGE, -2);
+	test_nm_utils_ascii_str_to_int64_do ("1.0", 10, 1, 1, -1, EINVAL, -1);
+	test_nm_utils_ascii_str_to_int64_do ("1x0", 16, -10, 10, -100, EINVAL, -100);
+	test_nm_utils_ascii_str_to_int64_do ("0", 16, -10, 10, -100, 0, 0);
+	test_nm_utils_ascii_str_to_int64_do ("10001111", 2, -1000, 1000, -100000, 0, 0x8F);
+	test_nm_utils_ascii_str_to_int64_do ("-10001111", 2, -1000, 1000, -100000, 0, -0x8F);
+	test_nm_utils_ascii_str_to_int64_do ("1111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7F);
+	test_nm_utils_ascii_str_to_int64_do ("111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFF);
+	test_nm_utils_ascii_str_to_int64_do ("11111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFF);
+	test_nm_utils_ascii_str_to_int64_do ("111111111111111111111111111111111111111111111111111111111111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFFFFFF);
+	test_nm_utils_ascii_str_to_int64_do ("100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0,  0x4000000000000000);
+	test_nm_utils_ascii_str_to_int64_do ("1000000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, ERANGE, -1);
+	test_nm_utils_ascii_str_to_int64_do ("-100000000000000000000000000000000000000000000000000000000000000", 2, G_MININT64, G_MAXINT64, -1, 0,  -0x4000000000000000);
+	test_nm_utils_ascii_str_to_int64_do ("111111111111111111111111111111111111111111111111111111111111111",  2, G_MININT64, G_MAXINT64, -1, 0, 0x7FFFFFFFFFFFFFFF);
+	test_nm_utils_ascii_str_to_int64_do ("-100000000000000000000000000000000000000000000000000000000000000",  2, G_MININT64, G_MAXINT64, -1, 0,  -0x4000000000000000);
+	test_nm_utils_ascii_str_to_int64_do ("0x70",  10, G_MININT64, G_MAXINT64, -1, EINVAL, -1);
+	test_nm_utils_ascii_str_to_int64_do ("4711",  0, G_MININT64, G_MAXINT64, -1, 0, 4711);
+	test_nm_utils_ascii_str_to_int64_do ("04711",  0, G_MININT64, G_MAXINT64, -1, 0, 04711);
+	test_nm_utils_ascii_str_to_int64_do ("0x4711",  0, G_MININT64, G_MAXINT64, -1, 0, 0x4711);
+	test_nm_utils_ascii_str_to_int64_do ("080",  0, G_MININT64, G_MAXINT64, -1, EINVAL, -1);
+	test_nm_utils_ascii_str_to_int64_do ("070",  0, G_MININT64, G_MAXINT64, -1, 0, 7*8);
+	test_nm_utils_ascii_str_to_int64_do ("0x70",  0, G_MININT64, G_MAXINT64, -1, 0, 0x70);
+}
+
+/******************************************************************************/
+
+enum TEST_IS_POWER_OF_TWP_ENUM_SIGNED {
+	_DUMMY_1 = -1,
+};
+
+enum TEST_IS_POWER_OF_TWP_ENUM_UNSIGNED {
+	_DUMMY_2,
+};
+
+enum TEST_IS_POWER_OF_TWP_ENUM_SIGNED_64 {
+	_DUMMY_3 = (1LL << 40),
+};
+
+enum TEST_IS_POWER_OF_TWP_ENUM_UNSIGNED_64 {
+	_DUMMY_4a = -1,
+	_DUMMY_4b = (1LL << 40),
+};
+
+#define test_nm_utils_is_power_of_two_do(type, x, expect) \
+	G_STMT_START { \
+		typeof (x) x1 = (x); \
+		type x2 = (type) x1; \
+		\
+		if (((typeof (x1)) x2) == x1 && (x2 > 0 || x2 == 0)) { \
+			/* x2 equals @x, and is positive. Compare to @expect */ \
+			g_assert_cmpint (expect, ==, nm_utils_is_power_of_two (x2)); \
+		} else if (!(x2 > 0) && !(x2 == 0)) { \
+			/* a (signed) negative value is always FALSE. */ \
+			g_assert_cmpint (FALSE, ==, nm_utils_is_power_of_two (x2));\
+		} \
+		g_assert_cmpint (expect, ==, nm_utils_is_power_of_two (x1)); \
+	} G_STMT_END
+
+static void
+test_nm_utils_is_power_of_two (void)
+{
+	guint64 xyes, xno;
+	gint i, j;
+	GRand *rand = nmtst_get_rand ();
+	int numbits;
+
+	for (i = -1; i < 64; i++) {
+
+		/* find a (positive) x which is a power of two. */
+		if (i == -1)
+			xyes = 0;
+		else {
+			xyes = (1LL << i);
+			g_assert (xyes != 0);
+		}
+
+		xno = xyes;
+		if (xyes != 0) {
+again:
+			/* Find another @xno, that is not a power of two. Do that,
+			 * by randomly setting bits. */
+			numbits = g_rand_int_range (rand, 1, 65);
+			while (xno != ~((guint64) 0) && numbits > 0) {
+				guint64 v = (1LL << g_rand_int_range (rand, 0, 65));
+
+				if ((xno | v) != xno) {
+					xno |= v;
+					--numbits;
+				}
+			}
+			if (xno == xyes)
+				goto again;
+		}
+
+		for (j = 0; j < 2; j++) {
+			gboolean expect = j == 0;
+			guint64 x = expect ? xyes : xno;
+
+			if (!expect && xno == 0)
+				continue;
+
+			/* check if @x is as @expect, when casted to a certain data type. */
+			test_nm_utils_is_power_of_two_do (gint8, x, expect);
+			test_nm_utils_is_power_of_two_do (guint8, x, expect);
+			test_nm_utils_is_power_of_two_do (gint16, x, expect);
+			test_nm_utils_is_power_of_two_do (guint16, x, expect);
+			test_nm_utils_is_power_of_two_do (gint32, x, expect);
+			test_nm_utils_is_power_of_two_do (guint32, x, expect);
+			test_nm_utils_is_power_of_two_do (gint64, x, expect);
+			test_nm_utils_is_power_of_two_do (guint64, x, expect);
+			test_nm_utils_is_power_of_two_do (char, x, expect);
+			test_nm_utils_is_power_of_two_do (unsigned char, x, expect);
+			test_nm_utils_is_power_of_two_do (signed char, x, expect);
+			test_nm_utils_is_power_of_two_do (enum TEST_IS_POWER_OF_TWP_ENUM_SIGNED, x, expect);
+			test_nm_utils_is_power_of_two_do (enum TEST_IS_POWER_OF_TWP_ENUM_UNSIGNED, x, expect);
+			test_nm_utils_is_power_of_two_do (enum TEST_IS_POWER_OF_TWP_ENUM_SIGNED_64, x, expect);
+			test_nm_utils_is_power_of_two_do (enum TEST_IS_POWER_OF_TWP_ENUM_UNSIGNED_64, x, expect);
+		}
+	}
+}
+
+/******************************************************************************/
+
+static void
+test_g_ptr_array_insert (void)
+{
+	/* this test only makes sense on a recent glib, where we compare our compat
+	 * with the original implementation. */
+#if GLIB_CHECK_VERSION(2, 40, 0)
+	gs_unref_ptrarray GPtrArray *arr1 = g_ptr_array_new ();
+	gs_unref_ptrarray GPtrArray *arr2 = g_ptr_array_new ();
+	GRand *rand = nmtst_get_rand ();
+	guint i;
+
+	for (i = 0; i < 560; i++) {
+		gint32 idx = g_rand_int_range (rand, -1, arr1->len + 1);
+
+		g_ptr_array_insert (arr1, idx, GINT_TO_POINTER (i));
+		_nm_g_ptr_array_insert (arr2, idx, GINT_TO_POINTER (i));
+
+		g_assert_cmpint (arr1->len, ==, arr2->len);
+		g_assert (memcmp (arr1->pdata, arr2->pdata, arr1->len * sizeof (gpointer)) == 0);
+	}
+#endif
+}
+
+/******************************************************************************/
+
+static int
+_test_find_binary_search_cmp (gconstpointer a, gconstpointer b, gpointer dummy)
+{
+	int ia, ib;
+
+	ia = GPOINTER_TO_INT (a);
+	ib = GPOINTER_TO_INT (b);
+
+	if (ia == ib)
+		return 0;
+	if (ia < ib)
+		return -1;
+	return 1;
+}
+
+static void
+_test_find_binary_search_do (const int *array, gsize len)
+{
+	gsize i;
+	gssize idx;
+	gs_free gpointer *parray = g_new (gpointer, len);
+	const int needle = 0;
+	gpointer pneedle = GINT_TO_POINTER (needle);
+	gssize expected_result;
+
+	for (i = 0; i < len; i++)
+		parray[i] = GINT_TO_POINTER (array[i]);
+
+	expected_result = _nm_utils_ptrarray_find_first (parray, len, pneedle);
+
+	idx = _nm_utils_ptrarray_find_binary_search (parray, len, pneedle, _test_find_binary_search_cmp, NULL);
+	if (expected_result >= 0)
+		g_assert_cmpint (expected_result, ==, idx);
+	else {
+		gssize idx2 = ~idx;
+		g_assert_cmpint (idx, <, 0);
+
+		g_assert (idx2 >= 0);
+		g_assert (idx2 <= len);
+		g_assert (idx2 - 1 < 0 || _test_find_binary_search_cmp (parray[idx2 - 1], pneedle, NULL) < 0);
+		g_assert (idx2 >= len || _test_find_binary_search_cmp (parray[idx2], pneedle, NULL) > 0);
+	}
+	for (i = 0; i < len; i++) {
+		int cmp;
+
+		cmp = _test_find_binary_search_cmp (parray[i], pneedle, NULL);
+		if (cmp == 0) {
+			g_assert (pneedle == parray[i]);
+			g_assert (idx >= 0);
+			g_assert (i == idx);
+		} else {
+			g_assert (pneedle != parray[i]);
+			if (cmp < 0) {
+				if (idx < 0)
+					g_assert (i < ~idx);
+				else
+					g_assert (i < idx);
+			} else {
+				if (idx < 0)
+					g_assert (i >= ~idx);
+				else
+					g_assert (i >= idx);
+			}
+		}
+	}
+}
+#define test_find_binary_search_do(...) \
+	G_STMT_START { \
+		const int _array[] = { __VA_ARGS__ } ; \
+		_test_find_binary_search_do (_array, G_N_ELEMENTS (_array)); \
+	} G_STMT_END
+
+static void
+test_nm_utils_ptrarray_find_binary_search (void)
+{
+#define _NOT(idx) (~ ((gssize) (idx)))
+	test_find_binary_search_do (            0);
+	test_find_binary_search_do (        -1, 0);
+	test_find_binary_search_do (    -2, -1, 0);
+	test_find_binary_search_do (-3, -2, -1, 0);
+	test_find_binary_search_do (            0, 1);
+	test_find_binary_search_do (            0, 1, 2);
+	test_find_binary_search_do (        -1, 0, 1, 2);
+	test_find_binary_search_do (    -2, -1, 0, 1, 2);
+	test_find_binary_search_do (-3, -2, -1, 0, 1, 2);
+	test_find_binary_search_do (-3, -2, -1, 0, 1, 2);
+	test_find_binary_search_do (-3, -2, -1, 0, 1, 2, 3);
+	test_find_binary_search_do (-3, -2, -1, 0, 1, 2, 3, 4);
+
+	test_find_binary_search_do (        -1);
+	test_find_binary_search_do (    -2, -1);
+	test_find_binary_search_do (-3, -2, -1);
+	test_find_binary_search_do (            1);
+	test_find_binary_search_do (            1, 2);
+	test_find_binary_search_do (        -1, 1, 2);
+	test_find_binary_search_do (    -2, -1, 1, 2);
+	test_find_binary_search_do (-3, -2, -1, 1, 2);
+	test_find_binary_search_do (-3, -2, -1, 1, 2);
+	test_find_binary_search_do (-3, -2, -1, 1, 2, 3);
+	test_find_binary_search_do (-3, -2, -1, 1, 2, 3, 4);
+}
+
 /******************************************************************************/
 
 NMTST_DEFINE ();
@@ -4137,6 +4545,12 @@ int main (int argc, char **argv)
 
 	g_test_add_func ("/core/general/hexstr2bin", test_hexstr2bin);
 	g_test_add_func ("/core/general/test_nm_utils_uuid_generate_from_string", test_nm_utils_uuid_generate_from_string);
+	g_test_add_func ("/core/general/_nm_utils_uuid_generate_from_strings", test_nm_utils_uuid_generate_from_strings);
+
+	g_test_add_func ("/core/general/_nm_utils_ascii_str_to_int64", test_nm_utils_ascii_str_to_int64);
+	g_test_add_func ("/core/general/nm_utils_is_power_of_two", test_nm_utils_is_power_of_two);
+	g_test_add_func ("/core/general/_glib_compat_g_ptr_array_insert", test_g_ptr_array_insert);
+	g_test_add_func ("/core/general/_nm_utils_ptrarray_find_binary_search", test_nm_utils_ptrarray_find_binary_search);
 
 	return g_test_run ();
 }
diff --git a/libnm-core/tests/test-keyfile.c b/libnm-core/tests/test-keyfile.c
new file mode 100644
index 00000000..99f88ac5
--- /dev/null
+++ b/libnm-core/tests/test-keyfile.c
@@ -0,0 +1,534 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/*
+ *
+ * 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, 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 2015 Red Hat, Inc.
+ *
+ */
+
+#include "config.h"
+
+#include "nm-macros-internal.h"
+#include "nm-keyfile-utils.h"
+#include "nm-keyfile-internal.h"
+
+#include "nm-simple-connection.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-wired.h"
+#include "nm-setting-8021x.h"
+
+#include "nm-test-utils.h"
+
+
+#define TEST_WIRED_TLS_CA_CERT     TEST_CERT_DIR"/test-ca-cert.pem"
+#define TEST_WIRED_TLS_PRIVKEY     TEST_CERT_DIR"/test-key-and-cert.pem"
+
+
+/******************************************************************************/
+
+#define CLEAR(con, keyfile) \
+	G_STMT_START { \
+		NMConnection **_con = (con); \
+		GKeyFile **_keyfile = (keyfile); \
+		\
+		g_clear_object (_con); \
+		g_clear_pointer (_keyfile, g_key_file_unref); \
+	} G_STMT_END
+
+static void
+_assert_gbytes (GBytes *bytes, gconstpointer data, gssize len)
+{
+	g_assert ((data && len > 0) || !len || (data && len == -1));
+
+	if (len == -1)
+		len = strlen (data);
+
+	if (!len)
+		g_assert (!bytes);
+	else {
+		g_assert_cmpint (g_bytes_get_size (bytes), ==, len);
+		g_assert (memcmp (g_bytes_get_data (bytes, NULL), data, len) == 0);
+	}
+}
+
+static GKeyFile *
+_keyfile_load_from_data (const char *str)
+{
+	GError *error = NULL;
+	gboolean success;
+	GKeyFile *keyfile;
+
+	g_assert (str);
+
+	keyfile =  g_key_file_new ();
+	success = g_key_file_load_from_data (keyfile, str, strlen (str), G_KEY_FILE_NONE, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	return keyfile;
+}
+
+static GKeyFile *
+_nm_keyfile_write (NMConnection *connection,
+                   NMKeyfileWriteHandler handler,
+                   void *user_data)
+{
+	GError *error = NULL;
+	GKeyFile *kf;
+
+	g_assert (NM_IS_CONNECTION (connection));
+
+	kf = nm_keyfile_write (connection, handler, user_data, &error);
+	g_assert_no_error (error);
+	g_assert (kf);
+	return kf;
+}
+
+static NMConnection *
+_nm_keyfile_read (GKeyFile *keyfile,
+                  const char *keyfile_name,
+                  const char *base_dir,
+                  NMKeyfileReadHandler read_handler,
+                  void *read_data,
+                  gboolean needs_normalization)
+{
+	GError *error = NULL;
+	NMConnection *con;
+
+	g_assert (keyfile);
+
+	con = nm_keyfile_read (keyfile, keyfile_name, base_dir, read_handler, read_data, &error);
+	g_assert_no_error (error);
+	g_assert (NM_IS_CONNECTION (con));
+	if (needs_normalization)
+		nmtst_assert_connection_verifies_after_normalization (con, 0, 0);
+	else
+		nmtst_assert_connection_verifies_without_normalization (con);
+	return con;
+}
+
+
+static void
+_keyfile_convert (NMConnection **con,
+                  GKeyFile **keyfile,
+                  const char *keyfile_name,
+                  const char *base_dir,
+                  NMKeyfileReadHandler read_handler,
+                  void *read_data,
+                  NMKeyfileWriteHandler write_handler,
+                  void *write_data,
+                  gboolean needs_normalization)
+{
+	NMConnection *c0;
+	GKeyFile *k0;
+	gs_unref_object NMConnection *c0_k1_c2 = NULL, *k0_c1 = NULL, *k0_c1_k2_c3 = NULL;
+	gs_unref_keyfile GKeyFile *k0_c1_k2 = NULL, *c0_k1 = NULL, *c0_k1_c2_k3 = NULL;
+
+	/* convert from @con to @keyfile and check that we can make
+	 * full round trips and obtaining the same result. */
+
+	g_assert (con);
+	g_assert (keyfile);
+	g_assert (*con || *keyfile);
+
+	c0 = *con;
+	k0 = *keyfile;
+
+	if (c0) {
+		c0_k1 = _nm_keyfile_write (c0, write_handler, write_data);
+		c0_k1_c2 = _nm_keyfile_read (c0_k1, keyfile_name, base_dir, read_handler, read_data, FALSE);
+		c0_k1_c2_k3 = _nm_keyfile_write (c0_k1_c2, write_handler, write_data);
+
+		g_assert (_nm_keyfile_equals (c0_k1, c0_k1_c2_k3, TRUE));
+	}
+	if (k0) {
+		NMSetting8021x *s1, *s2;
+
+		k0_c1 = _nm_keyfile_read (k0, keyfile_name, base_dir, read_handler, read_data, needs_normalization);
+		k0_c1_k2 = _nm_keyfile_write (k0_c1, write_handler, write_data);
+		k0_c1_k2_c3 = _nm_keyfile_read (k0_c1_k2, keyfile_name, base_dir, read_handler, read_data, FALSE);
+
+		/* It is a expeced behavior, that if @k0 contains a relative path ca-cert, @k0_c1 will
+		 * contain that path as relative. But @k0_c1_k2 and @k0_c1_k2_c3 will have absolute paths.
+		 * In this case, hack up @k0_c1_k2_c3 to contain the same relative path. */
+		s1 = nm_connection_get_setting_802_1x (k0_c1);
+		s2 = nm_connection_get_setting_802_1x (k0_c1_k2_c3);
+		if (s1 || s2) {
+			g_assert_cmpint (nm_setting_802_1x_get_ca_cert_scheme (s1), ==, nm_setting_802_1x_get_ca_cert_scheme (s2));
+			switch (nm_setting_802_1x_get_ca_cert_scheme (s1)) {
+			case NM_SETTING_802_1X_CK_SCHEME_PATH:
+				{
+					const char *p1 = nm_setting_802_1x_get_ca_cert_path (s1);
+					const char *p2 = nm_setting_802_1x_get_ca_cert_path (s2);
+
+					nmtst_assert_resolve_relative_path_equals (p1, p2);
+					if (strcmp (p1, p2) != 0) {
+						gs_free char *puri = NULL;
+						gs_unref_bytes GBytes *pfile = NULL;
+
+						g_assert (p1[0] != '/' && p2[0] == '/');
+
+						/* one of the paths is a relative path and the other is absolute. This is an
+						 * expected difference.
+						 * Make the paths of s2 identical to s1... */
+						puri = g_strconcat (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, p1, NULL);
+						pfile = g_bytes_new (puri, strlen (puri) + 1);
+						g_object_set (s2, NM_SETTING_802_1X_CA_CERT, pfile, NULL);
+					}
+				}
+				break;
+			case NM_SETTING_802_1X_CK_SCHEME_BLOB: {
+				GBytes *b1, *b2;
+
+				b1 = nm_setting_802_1x_get_ca_cert_blob (s1);
+				b2 = nm_setting_802_1x_get_ca_cert_blob (s2);
+				g_assert_cmpint (g_bytes_get_size (b1), ==, g_bytes_get_size (b2));
+				g_assert (memcmp (g_bytes_get_data (b1, NULL), g_bytes_get_data (b2, NULL), g_bytes_get_size (b1)) == 0);
+				break;
+			}
+			default:
+				break;
+			}
+		}
+
+		nmtst_assert_connection_equals (k0_c1, FALSE, k0_c1_k2_c3, FALSE);
+	}
+
+	if (!k0)
+		*keyfile = g_key_file_ref (c0_k1);
+	else if (!c0)
+		*con = g_object_ref (k0_c1);
+	else {
+		/* finally, if both a keyfile and a connection are given, assert that they are equal
+		 * after a round of conversion. */
+		g_assert (_nm_keyfile_equals (c0_k1, k0_c1_k2, TRUE));
+		nmtst_assert_connection_equals (k0_c1, FALSE, c0_k1_c2, FALSE);
+	}
+}
+
+/******************************************************************************/
+
+static void
+_test_8021x_cert_check (NMConnection *con,
+                        NMSetting8021xCKScheme expected_scheme,
+                        const void *value,
+                        gssize val_len)
+{
+	GKeyFile *keyfile = NULL;
+	NMSetting8021x *s_8021x;
+	gs_free char *kval = NULL;
+
+	_keyfile_convert (&con, &keyfile, NULL, NULL, NULL, NULL, NULL, NULL, FALSE);
+
+	s_8021x = nm_connection_get_setting_802_1x (con);
+
+	g_assert (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == expected_scheme);
+
+	if (expected_scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+		const char *path = nm_setting_802_1x_get_ca_cert_path (s_8021x);
+
+		g_assert_cmpstr (path, ==, value);
+		g_assert (val_len == -1 || strlen (path) == val_len);
+
+		kval = g_key_file_get_string (keyfile, "802-1x", "ca-cert", NULL);
+		g_assert (kval);
+		g_assert_cmpstr (kval, ==, value);
+	} else if (expected_scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
+		GBytes *blob = nm_setting_802_1x_get_ca_cert_blob (s_8021x);
+		gs_free char *file_blob = NULL;
+
+		if (val_len == -1) {
+			gsize l;
+			gboolean success;
+
+			success = g_file_get_contents (value, &file_blob, &l, NULL);
+			g_assert (success);
+
+			value = file_blob;
+			val_len = l;
+		}
+
+		g_assert (blob);
+		g_assert_cmpint (g_bytes_get_size (blob), ==, val_len);
+		g_assert (!memcmp (g_bytes_get_data (blob, NULL), value, val_len));
+
+		kval = g_key_file_get_string (keyfile, "802-1x", "ca-cert", NULL);
+		g_assert (kval);
+		g_assert (g_str_has_prefix (kval, NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB));
+	}
+
+	g_key_file_unref (keyfile);
+}
+
+static void
+_test_8021x_cert_check_blob_full (NMConnection *con, const void *data, gsize len)
+{
+	GBytes *bytes;
+	NMSetting8021x *s_8021x = nm_connection_get_setting_802_1x (con);
+
+	bytes = g_bytes_new (data, len);
+	g_object_set (s_8021x,
+	              NM_SETTING_802_1X_CA_CERT,
+                  bytes,
+                  NULL);
+	_test_8021x_cert_check (con, NM_SETTING_802_1X_CK_SCHEME_BLOB, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes));
+	g_bytes_unref (bytes);
+}
+#define _test_8021x_cert_check_blob(con, data) _test_8021x_cert_check_blob_full(con, data, STRLEN (data))
+
+static void
+test_8021x_cert (void)
+{
+	NMSetting8021x *s_8021x;
+	gs_unref_object NMConnection *con = nmtst_create_minimal_connection ("test-cert", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+	GError *error = NULL;
+	gboolean success;
+	NMSetting8021xCKScheme scheme = NM_SETTING_802_1X_CK_SCHEME_PATH;
+	gs_free char *full_TEST_WIRED_TLS_CA_CERT = nmtst_file_resolve_relative_path (TEST_WIRED_TLS_CA_CERT, NULL);
+	gs_free char *full_TEST_WIRED_TLS_PRIVKEY = nmtst_file_resolve_relative_path (TEST_WIRED_TLS_PRIVKEY, NULL);
+
+	/* test writing/reading of certificates of NMSetting8021x */
+
+	/* create a valid connection with NMSetting8021x */
+	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
+	nm_setting_802_1x_add_eap_method (s_8021x, "tls");
+	g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, "Bill Smith", NULL);
+	success = nm_setting_802_1x_set_ca_cert (s_8021x,
+	                                         full_TEST_WIRED_TLS_CA_CERT,
+	                                         scheme,
+	                                         NULL,
+	                                         &error);
+	g_assert_no_error (error);
+	g_assert (success);
+	success = nm_setting_802_1x_set_client_cert (s_8021x,
+	                                             full_TEST_WIRED_TLS_CA_CERT,
+	                                             scheme,
+	                                             NULL,
+	                                             &error);
+	g_assert_no_error (error);
+	g_assert (success);
+	success = nm_setting_802_1x_set_private_key (s_8021x,
+	                                             full_TEST_WIRED_TLS_PRIVKEY,
+	                                             "test1",
+	                                             scheme,
+	                                             NULL,
+	                                             &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+
+	/* test reseting ca-cert to different values and see whether we can write/read. */
+
+	nm_connection_add_setting (con, NM_SETTING (s_8021x));
+	nmtst_assert_connection_verifies_and_normalizable (con);
+
+
+	_test_8021x_cert_check (con, scheme, full_TEST_WIRED_TLS_CA_CERT, -1);
+
+	scheme = NM_SETTING_802_1X_CK_SCHEME_BLOB;
+	success = nm_setting_802_1x_set_ca_cert (s_8021x,
+	                                         full_TEST_WIRED_TLS_CA_CERT,
+	                                         scheme,
+	                                         NULL,
+	                                         &error);
+	g_assert_no_error (error);
+	g_assert (success);
+	_test_8021x_cert_check (con, scheme, full_TEST_WIRED_TLS_CA_CERT, -1);
+
+	_test_8021x_cert_check_blob (con, "a");
+	_test_8021x_cert_check_blob (con, "\0");
+	_test_8021x_cert_check_blob (con, "10");
+	_test_8021x_cert_check_blob (con, "data:;base64,a");
+	_test_8021x_cert_check_blob_full (con, "data:;base64,a", STRLEN ("data:;base64,a") + 1);
+	_test_8021x_cert_check_blob (con, "data:;base64,file://a");
+	_test_8021x_cert_check_blob (con, "123");
+
+}
+
+/******************************************************************************/
+
+static void
+test_8021x_cert_read (void)
+{
+	GKeyFile *keyfile = NULL;
+	gs_unref_object NMConnection *con = NULL;
+	NMSetting8021x *s_8021x;
+
+	con = nmtst_create_connection_from_keyfile (
+	      "[connection]\n"
+	      "type=ethernet",
+	      "/test_8021x_cert_read/test0", NULL);
+	CLEAR (&con, &keyfile);
+
+
+	keyfile = _keyfile_load_from_data (
+	          "[connection]\n"
+	          "type=ethernet"
+	          );
+	_keyfile_convert (&con, &keyfile, "/test_8021x_cert_read/test1", NULL, NULL, NULL, NULL, NULL, TRUE);
+	CLEAR (&con, &keyfile);
+
+	keyfile = _keyfile_load_from_data (
+	          "[connection]\n"
+	          "type=802-3-ethernet\n"
+
+	          "[802-1x]\n"
+	          "eap=tls;\n"
+	          "identity=Bill Smith\n"
+	          "ca-cert=48;130;2;52;48;130;1;161;2;16;2;173;102;126;78;69;254;94;87;111;60;152;25;94;221;192;48;13;6;9;42;134;72;134;247;13;1;1;2;5;0;48;95;49;11;48;9;6;3;85;4;6;19;2;85;83;49;32;48;30;6;3;85;4;10;19;23;82;83;65;32;68;97;116;97;32;83;101;99;117;114;105;116;121;44;32;73;110;99;46;49;46;48;44;6;3;85;4;11;19;37;83;101;99;117;114;101;32;83;101;114;118;101;114;32;67;101;114;116;105;102;105;99;97;116;105;111;110;32;65;117;116;104;111;114;105;116;121;48;30;23;13;57;52;49;49;48;57;48;48;48;48;48;48;90;23;13;49;48;48;49;48;55;50;51;53;57;53;57;90;48;95;49;11;48;9;6;3;85;4;6;19;2;85;83;49;32;48;30;6;3;85;4;10;19;23;82;83;65;32;68;97;116;97;32;83;101;99;117;114;105;116;121;44;32;73;110;99;46;49;46;48;44;6;3;85;4;11;19;37;83;101;99;117;114;101;32;83;101;114;118;101;114;32;67;101;114;116;105;102;105;99;97;116;105;111;110;32;65;117;116;104;111;114;105;116;121;48;129;155;48;13;6;9;42;134;72;134;247;13;1;1;1;5;0;3;129;137;0;48;129;133;2;126;0;146;206;122;193;174;131;62;90;170;137;131;87;172;37;1;118;12;173;174;142;44;55;206;235;53;120;100;84;3;229;132;64;81;201;191;143;8;226;138;130;8;210;22;134;55;85;233;177;33;2;173;118;104;129;154;5;162;75;201;75;37;102;34;86;108;136;7;143;247;129;89;109;132;7;101;112;19;113;118;62;155;119;76;227;80;137;86;152;72;185;29;167;41;26;19;46;74;17;89;156;30;21;213;73;84;44;115;58;105;130;177;151;57;156;109;112;103;72;229;221;45;214;200;30;123;2;3;1;0;1;48;13;6;9;42;134;72;134;247;13;1;1;2;5;0;3;126;0;101;221;126;225;178;236;176;226;58;224;236;113;70;154;25;17;184;211;199;160;180;3;64;38;2;62;9;156;225;18;179;209;90;246;55;165;183;97;3;182;91;22;105;59;198;68;8;12;136;83;12;107;151;73;199;62;53;220;108;185;187;170;223;92;187;58;47;147;96;182;169;75;77;242;32;247;205;95;127;100;123;142;220;0;92;215;250;119;202;57;22;89;111;14;234;211;181;131;127;77;77;66;86;118;180;201;95;4;248;56;248;235;210;95;117;95;205;123;252;229;142;128;124;252;80;\n"
+	          "client-cert=102;105;108;101;58;47;47;47;104;111;109;101;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0;\n"
+	          "private-key=102;105;108;101;58;47;47;47;104;111;109;101;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0;\n"
+	          "private-key-password=12345testing\n"
+	          );
+	_keyfile_convert (&con, &keyfile, "/test_8021x_cert_read/test2", NULL, NULL, NULL, NULL, NULL, TRUE);
+	CLEAR (&con, &keyfile);
+
+
+	keyfile = _keyfile_load_from_data (
+	          "[connection]\n"
+	          "type=802-3-ethernet\n"
+
+	          "[802-1x]\n"
+	          "eap=tls;\n"
+	          "identity=Bill Smith\n"
+	          /* unqualified strings are only recognized as path up to 500 chars*/
+	          "ca-cert="  "/111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
+	                      "/111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
+	                      "/111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
+	                      "/111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
+	                      "/11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\n"
+	          "client-cert=/222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222221"
+	                      "/222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222221"
+	                      "/222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222221"
+	                      "/222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222221"
+	                      "/222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222\n"
+	          "private-key=file://"
+	                      "/333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333331"
+	                      "/333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333331"
+	                      "/333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333331"
+	                      "/333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333331"
+	                      "/33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333111111\n"
+	          "private-key-password=12345testing\n"
+	          );
+	_keyfile_convert (&con, &keyfile, "/test_8021x_cert_read/test2", NULL, NULL, NULL, NULL, NULL, TRUE);
+	s_8021x = nm_connection_get_setting_802_1x (con);
+
+	g_assert (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH);
+	g_assert (g_str_has_prefix (nm_setting_802_1x_get_ca_cert_path (s_8021x), "/111111111111"));
+	g_assert_cmpint (strlen (nm_setting_802_1x_get_ca_cert_path (s_8021x)), ==, 499);
+
+	g_assert (nm_setting_802_1x_get_client_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB);
+	g_assert (g_str_has_prefix (g_bytes_get_data (nm_setting_802_1x_get_client_cert_blob (s_8021x), NULL), "/2222222222"));
+	g_assert_cmpint (g_bytes_get_size (nm_setting_802_1x_get_client_cert_blob (s_8021x)), ==, 500 + 1 /* keyfile reader adds a trailing NUL */);
+
+	g_assert (nm_setting_802_1x_get_private_key_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH);
+	g_assert (g_str_has_prefix (nm_setting_802_1x_get_private_key_path (s_8021x), "/333333333"));
+	g_assert_cmpint (strlen (nm_setting_802_1x_get_private_key_path (s_8021x)), ==, 505);
+	CLEAR (&con, &keyfile);
+
+
+	keyfile = _keyfile_load_from_data (
+	          "[connection]\n"
+	          "type=802-3-ethernet\n"
+
+	          "[802-1x]\n"
+	          "eap=tls;\n"
+	          "identity=Bill Smith\n"
+	          "ca-cert=/\n"
+	          "client-cert=a.pem\n"
+	          "private-key=data:;base64,aGFsbG8=\n" // hallo
+	          "private-key-password=12345testing\n"
+	          );
+	_keyfile_convert (&con, &keyfile, "/test_8021x_cert_read/test2", NULL, NULL, NULL, NULL, NULL, TRUE);
+	s_8021x = nm_connection_get_setting_802_1x (con);
+
+	g_assert (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH);
+	g_assert_cmpstr (nm_setting_802_1x_get_ca_cert_path (s_8021x), ==, "/");
+
+	g_assert (nm_setting_802_1x_get_client_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH);
+	g_assert_cmpstr (nm_setting_802_1x_get_client_cert_path (s_8021x), ==, "/test_8021x_cert_read/a.pem");
+
+	g_assert (nm_setting_802_1x_get_private_key_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB);
+	_assert_gbytes (nm_setting_802_1x_get_private_key_blob (s_8021x), "hallo", -1);
+	CLEAR (&con, &keyfile);
+
+
+	keyfile = _keyfile_load_from_data (
+	          "[connection]\n"
+	          "type=802-3-ethernet\n"
+
+	          "[802-1x]\n"
+	          "eap=tls;\n"
+	          "identity=Bill Smith\n"
+	          "ca-cert=file://data:;base64,x\n"
+	          "client-cert=abc.der\n"
+	          "private-key=abc.deR\n"
+	          "private-key-password=12345testing\n"
+	          );
+	_keyfile_convert (&con, &keyfile, "/test_8021x_cert_read/test2", NULL, NULL, NULL, NULL, NULL, TRUE);
+	s_8021x = nm_connection_get_setting_802_1x (con);
+
+	g_assert (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH);
+	g_assert_cmpstr (nm_setting_802_1x_get_ca_cert_path (s_8021x), ==, "data:;base64,x");
+
+	g_assert (nm_setting_802_1x_get_client_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH);
+	g_assert_cmpstr (nm_setting_802_1x_get_client_cert_path (s_8021x), ==, "/test_8021x_cert_read/abc.der");
+
+	g_assert (nm_setting_802_1x_get_private_key_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB);
+	_assert_gbytes (nm_setting_802_1x_get_private_key_blob (s_8021x), "abc.deR\0", 8);
+	CLEAR (&con, &keyfile);
+
+
+	keyfile = _keyfile_load_from_data (
+	          "[connection]\n"
+	          "type=802-3-ethernet\n"
+
+	          "[802-1x]\n"
+	          "eap=tls;\n"
+	          "identity=Bill Smith\n"
+	          "ca-cert=104;97;108;108;111;\n" /* "hallo" without trailing NUL */
+	          "client-cert=104;097;108;108;111;0;\n"
+	          "private-key=hallo\n"
+	          "private-key-password=12345testing\n"
+	          );
+	_keyfile_convert (&con, &keyfile, "/test_8021x_cert_read/test2", NULL, NULL, NULL, NULL, NULL, TRUE);
+	s_8021x = nm_connection_get_setting_802_1x (con);
+
+	g_assert (nm_setting_802_1x_get_ca_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB);
+	_assert_gbytes (nm_setting_802_1x_get_ca_cert_blob (s_8021x), "hallo", 5);
+
+	g_assert (nm_setting_802_1x_get_client_cert_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB);
+	_assert_gbytes (nm_setting_802_1x_get_client_cert_blob (s_8021x), "hallo\0", 6);
+
+	g_assert (nm_setting_802_1x_get_private_key_scheme (s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB);
+	_assert_gbytes (nm_setting_802_1x_get_private_key_blob (s_8021x), "hallo\0", 6);
+	CLEAR (&con, &keyfile);
+}
+
+/******************************************************************************/
+
+NMTST_DEFINE ();
+
+int main (int argc, char **argv)
+{
+	nmtst_init (&argc, &argv, TRUE);
+
+	g_test_add_func ("/core/keyfile/test_8021x_cert", test_8021x_cert);
+	g_test_add_func ("/core/keyfile/test_8021x_cert_read", test_8021x_cert_read);
+
+	return g_test_run ();
+}
+
diff --git a/libnm-core/tests/test-secrets.c b/libnm-core/tests/test-secrets.c
index a915ae7d..178fddd5 100644
--- a/libnm-core/tests/test-secrets.c
+++ b/libnm-core/tests/test-secrets.c
@@ -739,33 +739,23 @@ NMTST_DEFINE ();
 int
 main (int argc, char **argv)
 {
-	char *base;
-
-#if !GLIB_CHECK_VERSION (2, 35, 0)
-	g_type_init ();
-#endif
-
 	nmtst_init (&argc, &argv, TRUE);
 
 	/* The tests */
-	test_need_tls_secrets_path ();
-	test_need_tls_secrets_blob ();
-	test_need_tls_phase2_secrets_path ();
-	test_need_tls_phase2_secrets_blob ();
-
-	test_update_secrets_wifi_single_setting ();
-	test_update_secrets_wifi_full_hash ();
-	test_update_secrets_wifi_bad_setting_name ();
-
-	test_update_secrets_whole_connection ();
-	test_update_secrets_whole_connection_empty_hash ();
-	test_update_secrets_whole_connection_bad_setting ();
-	test_update_secrets_whole_connection_empty_base_setting ();
-	test_update_secrets_null_setting_name_with_setting_hash ();
-
-	base = g_path_get_basename (argv[0]);
-	fprintf (stdout, "%s: SUCCESS\n", base);
-	g_free (base);
-	return 0;
+	g_test_add_func ("/libnm/need_tls_secrets_path", test_need_tls_secrets_path);
+	g_test_add_func ("/libnm/need_tls_secrets_blob", test_need_tls_secrets_blob);
+	g_test_add_func ("/libnm/need_tls_phase2_secrets_path", test_need_tls_phase2_secrets_path);
+	g_test_add_func ("/libnm/need_tls_phase2_secrets_blob", test_need_tls_phase2_secrets_blob);
+
+	g_test_add_func ("/libnm/update_secrets_wifi_single_setting", test_update_secrets_wifi_single_setting);
+	g_test_add_func ("/libnm/update_secrets_wifi_full_hash", test_update_secrets_wifi_full_hash);
+	g_test_add_func ("/libnm/update_secrets_wifi_bad_setting_name", test_update_secrets_wifi_bad_setting_name);
+
+	g_test_add_func ("/libnm/update_secrets_whole_connection", test_update_secrets_whole_connection);
+	g_test_add_func ("/libnm/update_secrets_whole_connection_empty_hash", test_update_secrets_whole_connection_empty_hash);
+	g_test_add_func ("/libnm/update_secrets_whole_connection_bad_setting", test_update_secrets_whole_connection_bad_setting);
+	g_test_add_func ("/libnm/update_secrets_whole_connection_empty_base_setting", test_update_secrets_whole_connection_empty_base_setting);
+	g_test_add_func ("/libnm/update_secrets_null_setting_name_with_setting_hash", test_update_secrets_null_setting_name_with_setting_hash);
+
+	return g_test_run ();
 }
-
diff --git a/libnm-core/tests/test-setting-8021x.c b/libnm-core/tests/test-setting-8021x.c
index 992379c7..16f6016e 100644
--- a/libnm-core/tests/test-setting-8021x.c
+++ b/libnm-core/tests/test-setting-8021x.c
@@ -57,15 +57,13 @@ compare_blob_data (const char *test,
 	g_free (contents);
 }
 
-#define SCHEME_PATH "file://"
-
 static void
 check_scheme_path (GBytes *value, const char *path)
 {
 	const guint8 *p = g_bytes_get_data (value, NULL);
 
-	g_assert (memcmp (p, SCHEME_PATH, strlen (SCHEME_PATH)) == 0);
-	p += strlen (SCHEME_PATH);
+	g_assert (memcmp (p, NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)) == 0);
+	p += strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH);
 	g_assert (memcmp (p, path, strlen (path)) == 0);
 	p += strlen (path);
 	g_assert (*p == '\0');
diff --git a/libnm-core/tests/test-settings-defaults.c b/libnm-core/tests/test-settings-defaults.c
index 0e786187..7d7715cb 100644
--- a/libnm-core/tests/test-settings-defaults.c
+++ b/libnm-core/tests/test-settings-defaults.c
@@ -101,15 +101,9 @@ test_defaults (GType type, const char *name)
 	g_object_unref (setting);
 }
 
-int
-main (int argc, char **argv)
+static void
+defaults (void)
 {
-	char *base;
-
-#if !GLIB_CHECK_VERSION (2, 35, 0)
-	g_type_init ();
-#endif
-
 	/* The tests */
 	test_defaults (NM_TYPE_SETTING_CONNECTION, NM_SETTING_CONNECTION_SETTING_NAME);
 	test_defaults (NM_TYPE_SETTING_802_1X, NM_SETTING_802_1X_SETTING_NAME);
@@ -124,10 +118,17 @@ main (int argc, char **argv)
 	test_defaults (NM_TYPE_SETTING_WIRED, NM_SETTING_WIRED_SETTING_NAME);
 	test_defaults (NM_TYPE_SETTING_WIRELESS, NM_SETTING_WIRELESS_SETTING_NAME);
 	test_defaults (NM_TYPE_SETTING_WIRELESS_SECURITY, NM_SETTING_WIRELESS_SECURITY_SETTING_NAME);
+}
+
+NMTST_DEFINE ();
+
+int
+main (int argc, char **argv)
+{
+	nmtst_init (&argc, &argv, TRUE);
+
+	g_test_add_func ("/libnm/defaults", defaults);
 
-	base = g_path_get_basename (argv[0]);
-	fprintf (stdout, "%s: SUCCESS\n", base);
-	g_free (base);
-	return 0;
+	return g_test_run ();
 }