about summary refs log tree commit diff
path: root/src/libnm-core-impl
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-core-impl')
-rw-r--r--src/libnm-core-impl/meson.build30
-rw-r--r--src/libnm-core-impl/nm-connection.c61
-rw-r--r--src/libnm-core-impl/nm-crypto-gnutls.c415
-rw-r--r--src/libnm-core-impl/nm-crypto-impl.h49
-rw-r--r--src/libnm-core-impl/nm-crypto-nss.c548
-rw-r--r--src/libnm-core-impl/nm-crypto.c1044
-rw-r--r--src/libnm-core-impl/nm-crypto.h96
-rw-r--r--src/libnm-core-impl/nm-errors.c14
-rw-r--r--src/libnm-core-impl/nm-keyfile.c759
-rw-r--r--src/libnm-core-impl/nm-meta-setting-base-impl.c62
-rw-r--r--src/libnm-core-impl/nm-setting-8021x.c513
-rw-r--r--src/libnm-core-impl/nm-setting-connection.c105
-rw-r--r--src/libnm-core-impl/nm-setting-ip-config.c6
-rw-r--r--src/libnm-core-impl/nm-setting-ip4-config.c3
-rw-r--r--src/libnm-core-impl/nm-setting-ip6-config.c8
-rw-r--r--src/libnm-core-impl/nm-setting-match.c244
-rw-r--r--src/libnm-core-impl/nm-setting-private.h45
-rw-r--r--src/libnm-core-impl/nm-setting-proxy.c18
-rw-r--r--src/libnm-core-impl/nm-setting.c89
-rw-r--r--src/libnm-core-impl/nm-utils.c100
-rw-r--r--src/libnm-core-impl/tests/test-crypto.c32
-rw-r--r--src/libnm-core-impl/tests/test-general-enums.h10
-rw-r--r--src/libnm-core-impl/tests/test-general.c2
-rw-r--r--src/libnm-core-impl/tests/test-keyfile.c109
-rw-r--r--src/libnm-core-impl/tests/test-setting.c17
25 files changed, 1244 insertions, 3135 deletions
diff --git a/src/libnm-core-impl/meson.build b/src/libnm-core-impl/meson.build
index 2e1f1751..83c29085 100644
--- a/src/libnm-core-impl/meson.build
+++ b/src/libnm-core-impl/meson.build
@@ -2,35 +2,6 @@
 
 libnm_core_impl_inc = include_directories('.')
 
-if crypto_nss_dep.found()
-  libnm_crypto_nss = static_library(
-    'nm-crypto-nss',
-    sources: 'nm-crypto-nss.c',
-    dependencies: [
-      libnm_core_public_dep,
-      crypto_nss_dep,
-    ],
-  )
-endif
-
-if crypto_gnutls_dep.found()
-  libnm_crypto_gnutls = static_library(
-    'nm-crypto-gnutls',
-    sources: 'nm-crypto-gnutls.c',
-    dependencies: [
-      libnm_core_public_dep,
-      crypto_gnutls_dep,
-    ],
-  )
-endif
-
-if crypto == 'nss'
-  libnm_crypto = libnm_crypto_nss
-else
-  assert(crypto == 'gnutls', 'Unexpected setting "crypto=' + crypto + '"')
-  libnm_crypto = libnm_crypto_gnutls
-endif
-
 libnm_core_settings_sources = files(
   'nm-setting-6lowpan.c',
   'nm-setting-8021x.c',
@@ -89,7 +60,6 @@ libnm_core_settings_sources = files(
 
 libnm_core_impl_sources = files(
   'nm-connection.c',
-  'nm-crypto.c',
   'nm-dbus-utils.c',
   'nm-errors.c',
   'nm-keyfile-utils.c',
diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c
index a9b130a7..27cea2cb 100644
--- a/src/libnm-core-impl/nm-connection.c
+++ b/src/libnm-core-impl/nm-connection.c
@@ -1642,6 +1642,66 @@ _normalize_gsm_auto_config(NMConnection *self)
 }
 
 static gboolean
+_normalize_802_1x_empty_strings(NMConnection *self)
+{
+    NMSetting8021x *s_8021x;
+    gboolean        changed = FALSE;
+
+    s_8021x = _connection_get_setting_by_meta_type(NM_CONNECTION_GET_PRIVATE(self),
+                                                   NM_META_SETTING_TYPE_802_1X);
+    if (!s_8021x)
+        return FALSE;
+
+#define _norm_8021x(s_8021x, getter, prop_name, p_changed)       \
+    G_STMT_START                                                 \
+    {                                                            \
+        NMSetting8021x *_s_8021x   = (s_8021x);                  \
+        gboolean       *_p_changed = (p_changed);                \
+        const char     *_v;                                      \
+                                                                 \
+        _v = getter(_s_8021x);                                   \
+        if (_v && _v[0] == '\0') {                               \
+            g_object_set(_s_8021x, "" prop_name "", NULL, NULL); \
+            *(_p_changed) = TRUE;                                \
+        }                                                        \
+    }                                                            \
+    G_STMT_END
+
+    _norm_8021x(s_8021x, nm_setting_802_1x_get_identity, NM_SETTING_802_1X_IDENTITY, &changed);
+    _norm_8021x(s_8021x,
+                nm_setting_802_1x_get_anonymous_identity,
+                NM_SETTING_802_1X_ANONYMOUS_IDENTITY,
+                &changed);
+    _norm_8021x(s_8021x, nm_setting_802_1x_get_pac_file, NM_SETTING_802_1X_PAC_FILE, &changed);
+    _norm_8021x(s_8021x,
+                nm_setting_802_1x_get_subject_match,
+                NM_SETTING_802_1X_SUBJECT_MATCH,
+                &changed);
+    _norm_8021x(s_8021x,
+                nm_setting_802_1x_get_phase2_subject_match,
+                NM_SETTING_802_1X_PHASE2_SUBJECT_MATCH,
+                &changed);
+    _norm_8021x(s_8021x,
+                nm_setting_802_1x_get_domain_suffix_match,
+                NM_SETTING_802_1X_DOMAIN_SUFFIX_MATCH,
+                &changed);
+    _norm_8021x(s_8021x,
+                nm_setting_802_1x_get_phase2_domain_suffix_match,
+                NM_SETTING_802_1X_PHASE2_DOMAIN_SUFFIX_MATCH,
+                &changed);
+    _norm_8021x(s_8021x,
+                nm_setting_802_1x_get_domain_match,
+                NM_SETTING_802_1X_DOMAIN_MATCH,
+                &changed);
+    _norm_8021x(s_8021x,
+                nm_setting_802_1x_get_phase2_domain_match,
+                NM_SETTING_802_1X_PHASE2_DOMAIN_MATCH,
+                &changed);
+
+    return changed;
+}
+
+static gboolean
 _normalize_required_settings(NMConnection *self)
 {
     NMSettingBluetooth *s_bt = nm_connection_get_setting_bluetooth(self);
@@ -1952,6 +2012,7 @@ _connection_normalize(NMConnection *connection,
     was_modified |= _normalize_bridge_vlan_order(connection);
     was_modified |= _normalize_bridge_port_vlan_order(connection);
     was_modified |= _normalize_gsm_auto_config(connection);
+    was_modified |= _normalize_802_1x_empty_strings(connection);
 
     was_modified = !!was_modified;
 
diff --git a/src/libnm-core-impl/nm-crypto-gnutls.c b/src/libnm-core-impl/nm-crypto-gnutls.c
deleted file mode 100644
index d9e59136..00000000
--- a/src/libnm-core-impl/nm-crypto-gnutls.c
+++ /dev/null
@@ -1,415 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Dan Williams <dcbw@redhat.com>
- * Copyright (C) 2007 - 2015 Red Hat, Inc.
- */
-
-#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
-
-#include "nm-crypto-impl.h"
-
-#include <gnutls/gnutls.h>
-#include <gnutls/crypto.h>
-#include <gnutls/x509.h>
-#include <gnutls/pkcs12.h>
-
-#include "libnm-glib-aux/nm-secret-utils.h"
-#include "nm-errors.h"
-
-/*****************************************************************************/
-
-static gboolean
-_get_cipher_info(NMCryptoCipherType cipher, int *out_cipher_mech, guint8 *out_real_iv_len)
-{
-    static const int cipher_mechs[] = {
-        [NM_CRYPTO_CIPHER_DES_EDE3_CBC] = GNUTLS_CIPHER_3DES_CBC,
-        [NM_CRYPTO_CIPHER_DES_CBC]      = GNUTLS_CIPHER_DES_CBC,
-        [NM_CRYPTO_CIPHER_AES_128_CBC]  = GNUTLS_CIPHER_AES_128_CBC,
-        [NM_CRYPTO_CIPHER_AES_192_CBC]  = GNUTLS_CIPHER_AES_192_CBC,
-        [NM_CRYPTO_CIPHER_AES_256_CBC]  = GNUTLS_CIPHER_AES_256_CBC,
-    };
-
-    g_return_val_if_fail(_NM_INT_NOT_NEGATIVE(cipher)
-                             && (gsize) cipher < G_N_ELEMENTS(cipher_mechs),
-                         FALSE);
-
-    if (cipher_mechs[cipher] == 0)
-        return FALSE;
-
-    NM_SET_OUT(out_cipher_mech, cipher_mechs[cipher]);
-    NM_SET_OUT(out_real_iv_len, nm_crypto_cipher_get_info(cipher)->real_iv_len);
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-gboolean
-_nm_crypto_init(GError **error)
-{
-    static gboolean initialized = FALSE;
-
-    if (initialized)
-        return TRUE;
-
-    if (gnutls_global_init() != 0) {
-        gnutls_global_deinit();
-        g_set_error_literal(error,
-                            NM_CRYPTO_ERROR,
-                            NM_CRYPTO_ERROR_FAILED,
-                            _("Failed to initialize the crypto engine."));
-        return FALSE;
-    }
-
-    initialized = TRUE;
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-guint8 *
-_nmtst_crypto_decrypt(NMCryptoCipherType cipher,
-                      const guint8      *data,
-                      gsize              data_len,
-                      const guint8      *iv,
-                      gsize              iv_len,
-                      const guint8      *key,
-                      gsize              key_len,
-                      gsize             *out_len,
-                      GError           **error)
-{
-    gnutls_cipher_hd_t                   ctx;
-    gnutls_datum_t                       key_dt, iv_dt;
-    int                                  err;
-    int                                  cipher_mech;
-    nm_auto_clear_secret_ptr NMSecretPtr output = {0};
-    guint8                               pad_i, pad_len;
-    guint8                               real_iv_len;
-
-    if (!_get_cipher_info(cipher, &cipher_mech, &real_iv_len)) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_UNKNOWN_CIPHER,
-                    _("Unsupported key cipher for decryption"));
-        return NULL;
-    }
-
-    if (!_nm_crypto_init(error))
-        return NULL;
-
-    if (iv_len < real_iv_len) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("Invalid IV length (must be at least %u)."),
-                    (guint) real_iv_len);
-        return NULL;
-    }
-
-    output.len = data_len;
-    output.bin = g_malloc(data_len);
-
-    key_dt.data = (unsigned char *) key;
-    key_dt.size = key_len;
-    iv_dt.data  = (unsigned char *) iv;
-    iv_dt.size  = iv_len;
-
-    err = gnutls_cipher_init(&ctx, cipher_mech, &key_dt, &iv_dt);
-    if (err < 0) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_DECRYPTION_FAILED,
-                    _("Failed to initialize the decryption cipher context: %s (%s)"),
-                    gnutls_strerror_name(err),
-                    gnutls_strerror(err));
-        return NULL;
-    }
-
-    err = gnutls_cipher_decrypt2(ctx, data, data_len, output.bin, output.len);
-
-    gnutls_cipher_deinit(ctx);
-
-    if (err < 0) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_DECRYPTION_FAILED,
-                    _("Failed to decrypt the private key: %s (%s)"),
-                    gnutls_strerror_name(err),
-                    gnutls_strerror(err));
-        return NULL;
-    }
-
-    pad_len = output.len > 0 ? output.bin[output.len - 1] : 0;
-
-    /* Check if the padding at the end of the decrypted data is valid */
-    if (pad_len == 0 || pad_len > real_iv_len) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_DECRYPTION_FAILED,
-                    _("Failed to decrypt the private key: unexpected padding length."));
-        return NULL;
-    }
-
-    /* Validate tail padding; last byte is the padding size, and all pad bytes
-     * should contain the padding size.
-     */
-    for (pad_i = 1; pad_i <= pad_len; ++pad_i) {
-        if (output.bin[data_len - pad_i] != pad_len) {
-            g_set_error(error,
-                        NM_CRYPTO_ERROR,
-                        NM_CRYPTO_ERROR_DECRYPTION_FAILED,
-                        _("Failed to decrypt the private key."));
-            return NULL;
-        }
-    }
-
-    *out_len = output.len - pad_len;
-    return g_steal_pointer(&output.bin);
-}
-
-guint8 *
-_nmtst_crypto_encrypt(NMCryptoCipherType cipher,
-                      const guint8      *data,
-                      gsize              data_len,
-                      const guint8      *iv,
-                      gsize              iv_len,
-                      const guint8      *key,
-                      gsize              key_len,
-                      gsize             *out_len,
-                      GError           **error)
-{
-    gnutls_cipher_hd_t                   ctx;
-    gnutls_datum_t                       key_dt, iv_dt;
-    int                                  err;
-    int                                  cipher_mech;
-    nm_auto_clear_secret_ptr NMSecretPtr output     = {0};
-    nm_auto_clear_secret_ptr NMSecretPtr padded_buf = {0};
-    gsize                                i, pad_len;
-
-    nm_assert(iv_len);
-
-    if (cipher == NM_CRYPTO_CIPHER_DES_CBC || !_get_cipher_info(cipher, &cipher_mech, NULL)) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_UNKNOWN_CIPHER,
-                    _("Unsupported key cipher for encryption"));
-        return NULL;
-    }
-
-    if (!_nm_crypto_init(error))
-        return NULL;
-
-    key_dt.data = (unsigned char *) key;
-    key_dt.size = key_len;
-    iv_dt.data  = (unsigned char *) iv;
-    iv_dt.size  = iv_len;
-
-    err = gnutls_cipher_init(&ctx, cipher_mech, &key_dt, &iv_dt);
-    if (err < 0) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_ENCRYPTION_FAILED,
-                    _("Failed to initialize the encryption cipher context: %s (%s)"),
-                    gnutls_strerror_name(err),
-                    gnutls_strerror(err));
-        return NULL;
-    }
-
-    /* If data_len % ivlen == 0, then we add another complete block
-     * onto the end so that the decrypter knows there's padding.
-     */
-    pad_len = iv_len - (data_len % iv_len);
-
-    padded_buf.len = data_len + pad_len;
-    padded_buf.bin = g_malloc(padded_buf.len);
-    memcpy(padded_buf.bin, data, data_len);
-    for (i = 0; i < pad_len; i++)
-        padded_buf.bin[data_len + i] = (guint8) (pad_len & 0xFF);
-
-    output.len = padded_buf.len;
-    output.bin = g_malloc(output.len);
-
-    err = gnutls_cipher_encrypt2(ctx, padded_buf.bin, padded_buf.len, output.bin, output.len);
-
-    gnutls_cipher_deinit(ctx);
-
-    if (err < 0) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_ENCRYPTION_FAILED,
-                    _("Failed to encrypt the data: %s (%s)"),
-                    gnutls_strerror_name(err),
-                    gnutls_strerror(err));
-        return NULL;
-    }
-
-    *out_len = output.len;
-    return g_steal_pointer(&output.bin);
-}
-
-gboolean
-_nm_crypto_verify_x509(const guint8 *data, gsize len, GError **error)
-{
-    gnutls_x509_crt_t der;
-    gnutls_datum_t    dt;
-    int               err;
-
-    if (!_nm_crypto_init(error))
-        return FALSE;
-
-    err = gnutls_x509_crt_init(&der);
-    if (err < 0) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("Error initializing certificate data: %s"),
-                    gnutls_strerror(err));
-        return FALSE;
-    }
-
-    /* Try DER first */
-    dt.data = (unsigned char *) data;
-    dt.size = len;
-    err     = gnutls_x509_crt_import(der, &dt, GNUTLS_X509_FMT_DER);
-    if (err == GNUTLS_E_SUCCESS) {
-        gnutls_x509_crt_deinit(der);
-        return TRUE;
-    }
-
-    /* And PEM next */
-    err = gnutls_x509_crt_import(der, &dt, GNUTLS_X509_FMT_PEM);
-    gnutls_x509_crt_deinit(der);
-    if (err == GNUTLS_E_SUCCESS)
-        return TRUE;
-
-    g_set_error(error,
-                NM_CRYPTO_ERROR,
-                NM_CRYPTO_ERROR_INVALID_DATA,
-                _("Couldn't decode certificate: %s"),
-                gnutls_strerror(err));
-    return FALSE;
-}
-
-gboolean
-_nm_crypto_verify_pkcs12(const guint8 *data, gsize data_len, const char *password, GError **error)
-{
-    gnutls_pkcs12_t p12;
-    gnutls_datum_t  dt;
-    int             err;
-
-    g_return_val_if_fail(data != NULL, FALSE);
-
-    if (!_nm_crypto_init(error))
-        return FALSE;
-
-    dt.data = (unsigned char *) data;
-    dt.size = data_len;
-
-    err = gnutls_pkcs12_init(&p12);
-    if (err < 0) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_FAILED,
-                    _("Couldn't initialize PKCS#12 decoder: %s"),
-                    gnutls_strerror(err));
-        return FALSE;
-    }
-
-    /* DER first */
-    err = gnutls_pkcs12_import(p12, &dt, GNUTLS_X509_FMT_DER, 0);
-    if (err < 0) {
-        /* PEM next */
-        err = gnutls_pkcs12_import(p12, &dt, GNUTLS_X509_FMT_PEM, 0);
-        if (err < 0) {
-            g_set_error(error,
-                        NM_CRYPTO_ERROR,
-                        NM_CRYPTO_ERROR_INVALID_DATA,
-                        _("Couldn't decode PKCS#12 file: %s"),
-                        gnutls_strerror(err));
-            gnutls_pkcs12_deinit(p12);
-            return FALSE;
-        }
-    }
-
-    err = gnutls_pkcs12_verify_mac(p12, password);
-
-    gnutls_pkcs12_deinit(p12);
-
-    if (err != GNUTLS_E_SUCCESS) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_DECRYPTION_FAILED,
-                    _("Couldn't verify PKCS#12 file: %s"),
-                    gnutls_strerror(err));
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-gboolean
-_nm_crypto_verify_pkcs8(const guint8 *data,
-                        gsize         data_len,
-                        gboolean      is_encrypted,
-                        const char   *password,
-                        GError      **error)
-{
-    gnutls_x509_privkey_t p8;
-    gnutls_datum_t        dt;
-    int                   err;
-
-    g_return_val_if_fail(data != NULL, FALSE);
-
-    if (!_nm_crypto_init(error))
-        return FALSE;
-
-    err = gnutls_x509_privkey_init(&p8);
-    if (err < 0) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_FAILED,
-                    _("Couldn't initialize PKCS#8 decoder: %s"),
-                    gnutls_strerror(err));
-        return FALSE;
-    }
-
-    dt.data = (unsigned char *) data;
-    dt.size = data_len;
-
-    err = gnutls_x509_privkey_import_pkcs8(p8,
-                                           &dt,
-                                           GNUTLS_X509_FMT_DER,
-                                           is_encrypted ? password : NULL,
-                                           is_encrypted ? 0 : GNUTLS_PKCS_PLAIN);
-
-    gnutls_x509_privkey_deinit(p8);
-
-    if (err < 0) {
-        if (err == GNUTLS_E_UNKNOWN_CIPHER_TYPE) {
-            /* HACK: gnutls < 3.5.4 doesn't support all the cipher types that openssl
-             * can use with PKCS#8, so if we encounter one, we have to assume
-             * the given password works.  gnutls needs to unsuckify, apparently.
-             * Specifically, by default openssl uses pbeWithMD5AndDES-CBC
-             * which gnutls does not support.
-             */
-        } else {
-            g_set_error(error,
-                        NM_CRYPTO_ERROR,
-                        NM_CRYPTO_ERROR_INVALID_DATA,
-                        _("Couldn't decode PKCS#8 file: %s"),
-                        gnutls_strerror(err));
-            return FALSE;
-        }
-    }
-
-    return TRUE;
-}
-
-gboolean
-_nm_crypto_randomize(void *buffer, gsize buffer_len, GError **error)
-{
-    if (!_nm_crypto_init(error))
-        return FALSE;
-
-    gnutls_rnd(GNUTLS_RND_RANDOM, buffer, buffer_len);
-    return TRUE;
-}
diff --git a/src/libnm-core-impl/nm-crypto-impl.h b/src/libnm-core-impl/nm-crypto-impl.h
deleted file mode 100644
index 61c3f7f8..00000000
--- a/src/libnm-core-impl/nm-crypto-impl.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Dan Williams <dcbw@redhat.com>
- * Copyright (C) 2007 - 2018 Red Hat, Inc.
- */
-
-#ifndef __NM_CRYPTO_IMPL_H__
-#define __NM_CRYPTO_IMPL_H__
-
-#include "nm-crypto.h"
-
-gboolean _nm_crypto_init(GError **error);
-
-gboolean _nm_crypto_randomize(void *buffer, gsize buffer_len, GError **error);
-
-gboolean _nm_crypto_verify_x509(const guint8 *data, gsize len, GError **error);
-
-gboolean
-_nm_crypto_verify_pkcs12(const guint8 *data, gsize data_len, const char *password, GError **error);
-
-gboolean _nm_crypto_verify_pkcs8(const guint8 *data,
-                                 gsize         data_len,
-                                 gboolean      is_encrypted,
-                                 const char   *password,
-                                 GError      **error);
-
-/*****************************************************************************/
-
-guint8 *_nmtst_crypto_encrypt(NMCryptoCipherType cipher,
-                              const guint8      *data,
-                              gsize              data_len,
-                              const guint8      *iv,
-                              gsize              iv_len,
-                              const guint8      *key,
-                              gsize              key_len,
-                              gsize             *out_len,
-                              GError           **error);
-
-guint8 *_nmtst_crypto_decrypt(NMCryptoCipherType cipher,
-                              const guint8      *data,
-                              gsize              data_len,
-                              const guint8      *iv,
-                              gsize              iv_len,
-                              const guint8      *key,
-                              gsize              key_len,
-                              gsize             *out_len,
-                              GError           **error);
-
-#endif /* __NM_CRYPTO_IMPL_H__ */
diff --git a/src/libnm-core-impl/nm-crypto-nss.c b/src/libnm-core-impl/nm-crypto-nss.c
deleted file mode 100644
index c27bf09e..00000000
--- a/src/libnm-core-impl/nm-crypto-nss.c
+++ /dev/null
@@ -1,548 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Dan Williams <dcbw@redhat.com>
- * Copyright (C) 2007 - 2009 Red Hat, Inc.
- */
-
-#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
-
-#include "nm-crypto-impl.h"
-
-NM_PRAGMA_WARNING_DISABLE("-Wstrict-prototypes")
-#include <prinit.h>
-#include <nss.h>
-#include <pk11pub.h>
-#include <pkcs11t.h>
-#include <cert.h>
-#include <prerror.h>
-#include <p12.h>
-#include <ciferfam.h>
-#include <p12plcy.h>
-NM_PRAGMA_WARNING_REENABLE
-
-#include "libnm-glib-aux/nm-secret-utils.h"
-#include "nm-errors.h"
-
-/*****************************************************************************/
-
-static gboolean
-_get_cipher_info(NMCryptoCipherType cipher,
-                 CK_MECHANISM_TYPE *out_cipher_mech,
-                 guint8            *out_real_iv_len)
-{
-    static const CK_MECHANISM_TYPE cipher_mechs[] = {
-        [NM_CRYPTO_CIPHER_DES_EDE3_CBC] = CKM_DES3_CBC_PAD,
-        [NM_CRYPTO_CIPHER_DES_CBC]      = CKM_DES_CBC_PAD,
-        [NM_CRYPTO_CIPHER_AES_128_CBC]  = CKM_AES_CBC_PAD,
-        [NM_CRYPTO_CIPHER_AES_192_CBC]  = CKM_AES_CBC_PAD,
-        [NM_CRYPTO_CIPHER_AES_256_CBC]  = CKM_AES_CBC_PAD,
-    };
-
-    g_return_val_if_fail(_NM_INT_NOT_NEGATIVE(cipher)
-                             && (gsize) cipher < G_N_ELEMENTS(cipher_mechs),
-                         FALSE);
-
-    if (!cipher_mechs[cipher])
-        return FALSE;
-
-    NM_SET_OUT(out_cipher_mech, cipher_mechs[cipher]);
-    NM_SET_OUT(out_real_iv_len, nm_crypto_cipher_get_info(cipher)->real_iv_len);
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-gboolean
-_nm_crypto_init(GError **error)
-{
-    static gboolean initialized = FALSE;
-    SECStatus       ret;
-
-    if (initialized)
-        return TRUE;
-
-    PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 1);
-    ret = NSS_NoDB_Init(NULL);
-    if (ret != SECSuccess) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_FAILED,
-                    _("Failed to initialize the crypto engine: %d."),
-                    PR_GetError());
-        PR_Cleanup();
-        return FALSE;
-    }
-
-    SEC_PKCS12EnableCipher(PKCS12_RC4_40, 1);
-    SEC_PKCS12EnableCipher(PKCS12_RC4_128, 1);
-    SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_40, 1);
-    SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_128, 1);
-    SEC_PKCS12EnableCipher(PKCS12_DES_56, 1);
-    SEC_PKCS12EnableCipher(PKCS12_DES_EDE3_168, 1);
-    SEC_PKCS12SetPreferredCipher(PKCS12_DES_EDE3_168, 1);
-
-    initialized = TRUE;
-    return TRUE;
-}
-
-guint8 *
-_nmtst_crypto_decrypt(NMCryptoCipherType cipher,
-                      const guint8      *data,
-                      gsize              data_len,
-                      const guint8      *iv,
-                      gsize              iv_len,
-                      const guint8      *key,
-                      gsize              key_len,
-                      gsize             *out_len,
-                      GError           **error)
-{
-    CK_MECHANISM_TYPE                    cipher_mech;
-    PK11SlotInfo                        *slot = NULL;
-    SECItem                              key_item;
-    PK11SymKey                          *sym_key   = NULL;
-    SECItem                             *sec_param = NULL;
-    PK11Context                         *ctx       = NULL;
-    nm_auto_clear_secret_ptr NMSecretPtr output    = {0};
-    SECStatus                            s;
-    gboolean                             success       = FALSE;
-    int                                  decrypted_len = 0;
-    unsigned                             extra         = 0;
-    unsigned                             pad_len       = 0;
-    guint32                              i;
-    guint8                               real_iv_len;
-
-    if (!_get_cipher_info(cipher, &cipher_mech, &real_iv_len)) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_UNKNOWN_CIPHER,
-                    _("Unsupported key cipher for decryption"));
-        return NULL;
-    }
-
-    if (iv_len < real_iv_len) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("Invalid IV length (must be at least %u)."),
-                    (guint) real_iv_len);
-        return NULL;
-    }
-
-    if (!_nm_crypto_init(error))
-        return NULL;
-
-    slot = PK11_GetBestSlot(cipher_mech, NULL);
-    if (!slot) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_FAILED,
-                    _("Failed to initialize the decryption cipher slot."));
-        goto out;
-    }
-
-    key_item.data = (unsigned char *) key;
-    key_item.len  = key_len;
-    sym_key = PK11_ImportSymKey(slot, cipher_mech, PK11_OriginUnwrap, CKA_DECRYPT, &key_item, NULL);
-    if (!sym_key) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_DECRYPTION_FAILED,
-                    _("Failed to set symmetric key for decryption."));
-        goto out;
-    }
-
-    key_item.data = (unsigned char *) iv;
-    key_item.len  = real_iv_len;
-    sec_param     = PK11_ParamFromIV(cipher_mech, &key_item);
-    if (!sec_param) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_DECRYPTION_FAILED,
-                    _("Failed to set IV for decryption."));
-        goto out;
-    }
-
-    ctx = PK11_CreateContextBySymKey(cipher_mech, CKA_DECRYPT, sym_key, sec_param);
-    if (!ctx) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_DECRYPTION_FAILED,
-                    _("Failed to initialize the decryption context."));
-        goto out;
-    }
-
-    output.len = data_len;
-    output.bin = g_malloc(data_len);
-
-    s = PK11_CipherOp(ctx,
-                      (unsigned char *) output.bin,
-                      &decrypted_len,
-                      output.len,
-                      data,
-                      data_len);
-    if (s != SECSuccess) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_DECRYPTION_FAILED,
-                    _("Failed to decrypt the private key: %d."),
-                    PORT_GetError());
-        goto out;
-    }
-
-    if (decrypted_len > data_len) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_DECRYPTION_FAILED,
-                    _("Failed to decrypt the private key: decrypted data too large."));
-        goto out;
-    }
-
-    s = PK11_DigestFinal(ctx,
-                         (unsigned char *) &output.bin[decrypted_len],
-                         &extra,
-                         data_len - decrypted_len);
-    if (s != SECSuccess) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_DECRYPTION_FAILED,
-                    _("Failed to finalize decryption of the private key: %d."),
-                    PORT_GetError());
-        goto out;
-    }
-
-    decrypted_len += extra;
-    pad_len = data_len - decrypted_len;
-
-    /* Check if the padding at the end of the decrypted data is valid */
-    if (pad_len == 0 || pad_len > real_iv_len) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_DECRYPTION_FAILED,
-                    _("Failed to decrypt the private key: unexpected padding length."));
-        goto out;
-    }
-
-    /* Validate tail padding; last byte is the padding size, and all pad bytes
-     * should contain the padding size.
-     */
-    for (i = pad_len; i > 0; i--) {
-        if (output.bin[data_len - i] != pad_len) {
-            g_set_error(error,
-                        NM_CRYPTO_ERROR,
-                        NM_CRYPTO_ERROR_DECRYPTION_FAILED,
-                        _("Failed to decrypt the private key."));
-            goto out;
-        }
-    }
-
-    success = TRUE;
-
-out:
-    if (ctx)
-        PK11_DestroyContext(ctx, PR_TRUE);
-    if (sym_key)
-        PK11_FreeSymKey(sym_key);
-    if (sec_param)
-        SECITEM_FreeItem(sec_param, PR_TRUE);
-    if (slot)
-        PK11_FreeSlot(slot);
-
-    if (!success)
-        return NULL;
-
-    if (decrypted_len < output.len)
-        nm_explicit_bzero(&output.bin[decrypted_len], output.len - decrypted_len);
-    *out_len = decrypted_len;
-    return g_steal_pointer(&output.bin);
-}
-
-guint8 *
-_nmtst_crypto_encrypt(NMCryptoCipherType cipher,
-                      const guint8      *data,
-                      gsize              data_len,
-                      const guint8      *iv,
-                      gsize              iv_len,
-                      const guint8      *key,
-                      gsize              key_len,
-                      gsize             *out_len,
-                      GError           **error)
-{
-    SECStatus                            ret;
-    CK_MECHANISM_TYPE                    cipher_mech = CKM_DES3_CBC_PAD;
-    PK11SlotInfo                        *slot        = NULL;
-    SECItem                              key_item = {.data = (unsigned char *) key, .len = key_len};
-    SECItem                              iv_item  = {.data = (unsigned char *) iv, .len = iv_len};
-    PK11SymKey                          *sym_key  = NULL;
-    SECItem                             *sec_param     = NULL;
-    PK11Context                         *ctx           = NULL;
-    nm_auto_clear_secret_ptr NMSecretPtr padded_buf    = {0};
-    nm_auto_clear_secret_ptr NMSecretPtr output        = {0};
-    int                                  encrypted_len = 0, i;
-    gboolean                             success       = FALSE;
-    gsize                                pad_len;
-
-    if (cipher == NM_CRYPTO_CIPHER_DES_CBC || !_get_cipher_info(cipher, &cipher_mech, NULL)) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_UNKNOWN_CIPHER,
-                    _("Unsupported key cipher for encryption"));
-        return NULL;
-    }
-
-    if (!_nm_crypto_init(error))
-        return NULL;
-
-    slot = PK11_GetBestSlot(cipher_mech, NULL);
-    if (!slot) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_FAILED,
-                    _("Failed to initialize the encryption cipher slot."));
-        return NULL;
-    }
-
-    sym_key = PK11_ImportSymKey(slot, cipher_mech, PK11_OriginUnwrap, CKA_ENCRYPT, &key_item, NULL);
-    if (!sym_key) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_ENCRYPTION_FAILED,
-                    _("Failed to set symmetric key for encryption."));
-        goto out;
-    }
-
-    sec_param = PK11_ParamFromIV(cipher_mech, &iv_item);
-    if (!sec_param) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_ENCRYPTION_FAILED,
-                    _("Failed to set IV for encryption."));
-        goto out;
-    }
-
-    ctx = PK11_CreateContextBySymKey(cipher_mech, CKA_ENCRYPT, sym_key, sec_param);
-    if (!ctx) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_ENCRYPTION_FAILED,
-                    _("Failed to initialize the encryption context."));
-        goto out;
-    }
-
-    /* If data->len % ivlen == 0, then we add another complete block
-     * onto the end so that the decrypter knows there's padding.
-     */
-    pad_len = iv_len - (data_len % iv_len);
-
-    padded_buf.len = data_len + pad_len;
-    padded_buf.bin = g_malloc(padded_buf.len);
-
-    memcpy(padded_buf.bin, data, data_len);
-    for (i = 0; i < pad_len; i++)
-        padded_buf.bin[data_len + i] = (guint8) (pad_len & 0xFF);
-
-    output.len = padded_buf.len;
-    output.bin = g_malloc(output.len);
-
-    ret =
-        PK11_CipherOp(ctx, output.bin, &encrypted_len, output.len, padded_buf.bin, padded_buf.len);
-    if (ret != SECSuccess) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_ENCRYPTION_FAILED,
-                    _("Failed to encrypt: %d."),
-                    PORT_GetError());
-        goto out;
-    }
-
-    if (encrypted_len != output.len) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_ENCRYPTION_FAILED,
-                    _("Unexpected amount of data after encrypting."));
-        goto out;
-    }
-
-    success = TRUE;
-
-out:
-    if (ctx)
-        PK11_DestroyContext(ctx, PR_TRUE);
-    if (sec_param)
-        SECITEM_FreeItem(sec_param, PR_TRUE);
-    if (sym_key)
-        PK11_FreeSymKey(sym_key);
-    if (slot)
-        PK11_FreeSlot(slot);
-
-    if (!success)
-        return NULL;
-
-    *out_len = output.len;
-    return g_steal_pointer(&output.bin);
-}
-
-gboolean
-_nm_crypto_verify_x509(const guint8 *data, gsize len, GError **error)
-{
-    CERTCertificate *cert;
-
-    if (!_nm_crypto_init(error))
-        return FALSE;
-
-    /* Try DER/PEM first */
-    cert = CERT_DecodeCertFromPackage((char *) data, len);
-    if (!cert) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("Couldn't decode certificate: %d"),
-                    PORT_GetError());
-        return FALSE;
-    }
-
-    CERT_DestroyCertificate(cert);
-    return TRUE;
-}
-
-gboolean
-_nm_crypto_verify_pkcs12(const guint8 *data, gsize data_len, const char *password, GError **error)
-{
-    SEC_PKCS12DecoderContext *p12ctx = NULL;
-    SECItem                   pw     = {0};
-    PK11SlotInfo             *slot   = NULL;
-    SECStatus                 s;
-    gboolean                  success = FALSE;
-
-    g_return_val_if_fail(!error || !*error, FALSE);
-
-    if (!_nm_crypto_init(error))
-        return FALSE;
-
-    /* PKCS#12 passwords are apparently UCS2 BIG ENDIAN, and NSS doesn't do
-     * any conversions for us.
-     */
-    if (password && *password) {
-        nm_auto_clear_secret_ptr NMSecretPtr ucs2_password = {0};
-
-        if (g_utf8_validate(password, -1, NULL)) {
-            long ucs2_chars;
-
-            ucs2_password.bin =
-                (guint8 *) g_utf8_to_utf16(password, strlen(password), NULL, &ucs2_chars, NULL);
-
-            /* cannot fail, because password is valid UTF-8*/
-            nm_assert(ucs2_password.bin && ucs2_chars > 0);
-
-            ucs2_password.len = ucs2_chars * 2;
-        }
-
-        if (!ucs2_password.bin || ucs2_password.len == 0) {
-            g_set_error(error,
-                        NM_CRYPTO_ERROR,
-                        NM_CRYPTO_ERROR_INVALID_PASSWORD,
-                        _("Password must be UTF-8"));
-            return FALSE;
-        }
-
-        pw.data = PORT_ZAlloc(ucs2_password.len + 2);
-        memcpy(pw.data, ucs2_password.bin, ucs2_password.len);
-        pw.len = ucs2_password.len + 2;
-
-#if __BYTE_ORDER == __LITTLE_ENDIAN
-        {
-            guint16 *p, *p_end;
-
-            p_end = (guint16 *) &(((guint8 *) pw.data)[ucs2_password.len]);
-            for (p = (guint16 *) pw.data; p < p_end; p++)
-                *p = GUINT16_SWAP_LE_BE(*p);
-        }
-#endif
-    }
-
-    slot = PK11_GetInternalKeySlot();
-    if (!slot) {
-        g_set_error(error, NM_CRYPTO_ERROR, NM_CRYPTO_ERROR_FAILED, _("Couldn't initialize slot"));
-        goto out;
-    }
-
-    p12ctx = SEC_PKCS12DecoderStart(&pw, slot, NULL, NULL, NULL, NULL, NULL, NULL);
-    if (!p12ctx) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_FAILED,
-                    _("Couldn't initialize PKCS#12 decoder: %d"),
-                    PORT_GetError());
-        goto out;
-    }
-
-    s = SEC_PKCS12DecoderUpdate(p12ctx, (guint8 *) data, data_len);
-    if (s != SECSuccess) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("Couldn't decode PKCS#12 file: %d"),
-                    PORT_GetError());
-        goto out;
-    }
-
-    s = SEC_PKCS12DecoderVerify(p12ctx);
-    if (s != SECSuccess) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_DECRYPTION_FAILED,
-                    _("Couldn't verify PKCS#12 file: %d"),
-                    PORT_GetError());
-        goto out;
-    }
-
-    success = TRUE;
-
-out:
-    if (p12ctx)
-        SEC_PKCS12DecoderFinish(p12ctx);
-    if (slot)
-        PK11_FreeSlot(slot);
-
-    if (pw.data)
-        SECITEM_ZfreeItem(&pw, PR_FALSE);
-
-    return success;
-}
-
-gboolean
-_nm_crypto_verify_pkcs8(const guint8 *data,
-                        gsize         data_len,
-                        gboolean      is_encrypted,
-                        const char   *password,
-                        GError      **error)
-{
-    g_return_val_if_fail(data != NULL, FALSE);
-
-    if (!_nm_crypto_init(error))
-        return FALSE;
-
-    /* NSS apparently doesn't do PKCS#8 natively, but you have to put the
-     * PKCS#8 key into a PKCS#12 file and import that??  So until we figure
-     * all that out, we can only assume the password is valid.
-     */
-    return TRUE;
-}
-
-gboolean
-_nm_crypto_randomize(void *buffer, gsize buffer_len, GError **error)
-{
-    SECStatus s;
-
-    if (!_nm_crypto_init(error))
-        return FALSE;
-
-    s = PK11_GenerateRandom(buffer, buffer_len);
-    if (s != SECSuccess) {
-        g_set_error_literal(error,
-                            NM_CRYPTO_ERROR,
-                            NM_CRYPTO_ERROR_FAILED,
-                            _("Could not generate random data."));
-        return FALSE;
-    }
-    return TRUE;
-}
diff --git a/src/libnm-core-impl/nm-crypto.c b/src/libnm-core-impl/nm-crypto.c
deleted file mode 100644
index 78f793bd..00000000
--- a/src/libnm-core-impl/nm-crypto.c
+++ /dev/null
@@ -1,1044 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Dan Williams <dcbw@redhat.com>
- * Copyright (C) 2007 - 2018 Red Hat, Inc.
- */
-
-#include "libnm-core-impl/nm-default-libnm-core.h"
-
-#include "nm-crypto.h"
-
-#include <strings.h>
-#include <unistd.h>
-#include <stdlib.h>
-
-#include "libnm-glib-aux/nm-secret-utils.h"
-#include "libnm-glib-aux/nm-io-utils.h"
-
-#include "nm-crypto-impl.h"
-#include "nm-utils.h"
-#include "nm-errors.h"
-
-#define PEM_RSA_KEY_BEGIN "-----BEGIN RSA PRIVATE KEY-----"
-#define PEM_RSA_KEY_END   "-----END RSA PRIVATE KEY-----"
-
-#define PEM_DSA_KEY_BEGIN "-----BEGIN DSA PRIVATE KEY-----"
-#define PEM_DSA_KEY_END   "-----END DSA PRIVATE KEY-----"
-
-#define PEM_CERT_BEGIN "-----BEGIN CERTIFICATE-----"
-#define PEM_CERT_END   "-----END CERTIFICATE-----"
-
-#define PEM_PKCS8_ENC_KEY_BEGIN "-----BEGIN ENCRYPTED PRIVATE KEY-----"
-#define PEM_PKCS8_ENC_KEY_END   "-----END ENCRYPTED PRIVATE KEY-----"
-
-#define PEM_PKCS8_DEC_KEY_BEGIN "-----BEGIN PRIVATE KEY-----"
-#define PEM_PKCS8_DEC_KEY_END   "-----END PRIVATE KEY-----"
-
-#define PEM_TPM2_WRAPPED_KEY_BEGIN "-----BEGIN TSS2 PRIVATE KEY-----"
-#define PEM_TPM2_WRAPPED_KEY_END   "-----END TSS2 PRIVATE KEY-----"
-
-#define PEM_TPM2_OLD_WRAPPED_KEY_BEGIN "-----BEGIN TSS2 KEY BLOB-----"
-#define PEM_TPM2_OLD_WRAPPED_KEY_END   "-----END TSS2 KEY BLOB-----"
-
-/*****************************************************************************/
-
-static const NMCryptoCipherInfo cipher_infos[] = {
-#define _CI(_cipher, _name, _digest_len, _real_iv_len) \
-    [(_cipher) -1] = {.cipher      = _cipher,          \
-                      .name        = ""_name           \
-                                     "",               \
-                      .digest_len  = _digest_len,      \
-                      .real_iv_len = _real_iv_len}
-    _CI(NM_CRYPTO_CIPHER_DES_EDE3_CBC, "DES-EDE3-CBC", 24, 8),
-    _CI(NM_CRYPTO_CIPHER_DES_CBC, "DES-CBC", 8, 8),
-    _CI(NM_CRYPTO_CIPHER_AES_128_CBC, "AES-128-CBC", 16, 16),
-    _CI(NM_CRYPTO_CIPHER_AES_192_CBC, "AES-192-CBC", 24, 16),
-    _CI(NM_CRYPTO_CIPHER_AES_256_CBC, "AES-256-CBC", 32, 16),
-};
-
-const NMCryptoCipherInfo *
-nm_crypto_cipher_get_info(NMCryptoCipherType cipher)
-{
-    g_return_val_if_fail(cipher > NM_CRYPTO_CIPHER_UNKNOWN
-                             && (gsize) cipher < G_N_ELEMENTS(cipher_infos) + 1,
-                         NULL);
-
-#if NM_MORE_ASSERTS > 10
-    {
-        int i, j;
-
-        for (i = 0; i < (int) G_N_ELEMENTS(cipher_infos); i++) {
-            const NMCryptoCipherInfo *info = &cipher_infos[i];
-
-            nm_assert(info->cipher == (NMCryptoCipherType) (i + 1));
-            nm_assert(info->name && info->name[0]);
-            for (j = 0; j < i; j++)
-                nm_assert(g_ascii_strcasecmp(info->name, cipher_infos[j].name) != 0);
-        }
-    }
-#endif
-
-    return &cipher_infos[cipher - 1];
-}
-
-const NMCryptoCipherInfo *
-nm_crypto_cipher_get_info_by_name(const char *cipher_name, gssize p_len)
-{
-    int i;
-
-    nm_assert(nm_crypto_cipher_get_info(NM_CRYPTO_CIPHER_DES_CBC)->cipher
-              == NM_CRYPTO_CIPHER_DES_CBC);
-
-    if (p_len < 0) {
-        if (!cipher_name)
-            return FALSE;
-        p_len = strlen(cipher_name);
-    }
-
-    for (i = 0; i < (int) G_N_ELEMENTS(cipher_infos); i++) {
-        const NMCryptoCipherInfo *info = &cipher_infos[i];
-
-        if ((gsize) p_len == strlen(info->name)
-            && g_ascii_strncasecmp(info->name, cipher_name, p_len) == 0)
-            return info;
-    }
-    return NULL;
-}
-
-/*****************************************************************************/
-
-static gboolean
-find_tag(const char *tag, const guint8 *data, gsize data_len, gsize start_at, gsize *out_pos)
-{
-    const guint8 *p;
-    gsize         taglen;
-
-    nm_assert(out_pos);
-    nm_assert(start_at <= data_len);
-
-    taglen = strlen(tag);
-
-    p = memmem(&data[start_at], data_len - start_at, tag, taglen);
-    if (!p)
-        return FALSE;
-
-    *out_pos = p - data;
-
-    nm_assert(memcmp(&data[*out_pos], tag, taglen) == 0);
-
-    return TRUE;
-}
-
-#define DEK_INFO_TAG  "DEK-Info: "
-#define PROC_TYPE_TAG "Proc-Type: "
-
-static char *
-_extract_line(const guint8 **p, const guint8 *p_end)
-{
-    const guint8 *x, *x0;
-
-    nm_assert(p);
-    nm_assert(p_end);
-    nm_assert(*p);
-    nm_assert(*p < p_end);
-
-    x = x0 = *p;
-    while (TRUE) {
-        if (x == p_end) {
-            *p = p_end;
-            break;
-        }
-        if (*x == '\0') {
-            /* the data contains embedded NUL. This is the end. */
-            *p = p_end;
-            break;
-        }
-        if (*x == '\n') {
-            *p = x + 1;
-            break;
-        }
-        x++;
-    }
-
-    if (x == x0)
-        return NULL;
-    return g_strndup((char *) x0, x - x0);
-}
-
-static gboolean
-parse_old_openssl_key_file(const guint8       *data,
-                           gsize               data_len,
-                           NMSecretPtr        *out_parsed,
-                           NMCryptoKeyType    *out_key_type,
-                           NMCryptoCipherType *out_cipher,
-                           char              **out_iv,
-                           GError            **error)
-{
-    gsize                                start = 0, end = 0;
-    nm_auto_free_secret char            *str = NULL;
-    char                                *str_p;
-    gsize                                str_len;
-    int                                  enc_tags = 0;
-    NMCryptoKeyType                      key_type;
-    nm_auto_clear_secret_ptr NMSecretPtr parsed = {0};
-    nm_auto_free_secret char            *iv     = NULL;
-    NMCryptoCipherType                   cipher = NM_CRYPTO_CIPHER_UNKNOWN;
-    const char                          *start_tag;
-    const char                          *end_tag;
-    const guint8                        *data_start, *data_end;
-
-    nm_assert(!out_parsed || (out_parsed->len == 0 && !out_parsed->bin));
-    nm_assert(!out_iv || !*out_iv);
-
-    NM_SET_OUT(out_key_type, NM_CRYPTO_KEY_TYPE_UNKNOWN);
-    NM_SET_OUT(out_cipher, NM_CRYPTO_CIPHER_UNKNOWN);
-
-    if (find_tag(PEM_RSA_KEY_BEGIN, data, data_len, 0, &start)) {
-        key_type  = NM_CRYPTO_KEY_TYPE_RSA;
-        start_tag = PEM_RSA_KEY_BEGIN;
-        end_tag   = PEM_RSA_KEY_END;
-    } else if (find_tag(PEM_DSA_KEY_BEGIN, data, data_len, 0, &start)) {
-        key_type  = NM_CRYPTO_KEY_TYPE_DSA;
-        start_tag = PEM_DSA_KEY_BEGIN;
-        end_tag   = PEM_DSA_KEY_END;
-    } else {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("PEM key file had no start tag"));
-        return FALSE;
-    }
-
-    start += strlen(start_tag);
-    if (!find_tag(end_tag, data, data_len, start, &end)) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("PEM key file had no end tag '%s'."),
-                    end_tag);
-        return FALSE;
-    }
-
-    str_len = end - start + 1;
-    str     = g_new(char, str_len);
-    str[0]  = '\0';
-    str_p   = str;
-
-    data_start = &data[start];
-    data_end   = &data[end];
-
-    while (data_start < data_end) {
-        nm_auto_free_secret char *line = NULL;
-        char                     *p;
-
-        line = _extract_line(&data_start, data_end);
-        if (!line)
-            continue;
-
-        p = nm_secret_strchomp(nm_str_skip_leading_spaces(line));
-
-        if (!strncmp(p, PROC_TYPE_TAG, strlen(PROC_TYPE_TAG))) {
-            if (enc_tags++ != 0 || str_p != str) {
-                g_set_error(error,
-                            NM_CRYPTO_ERROR,
-                            NM_CRYPTO_ERROR_INVALID_DATA,
-                            _("Malformed PEM file: Proc-Type was not first tag."));
-                return FALSE;
-            }
-
-            p += strlen(PROC_TYPE_TAG);
-            if (strcmp(p, "4,ENCRYPTED")) {
-                g_set_error(error,
-                            NM_CRYPTO_ERROR,
-                            NM_CRYPTO_ERROR_INVALID_DATA,
-                            _("Malformed PEM file: unknown Proc-Type tag '%s'."),
-                            p);
-                return FALSE;
-            }
-        } else if (!strncmp(p, DEK_INFO_TAG, strlen(DEK_INFO_TAG))) {
-            const NMCryptoCipherInfo *cipher_info;
-            char                     *comma;
-            gsize                     p_len;
-
-            if (enc_tags++ != 1 || str_p != str) {
-                g_set_error(error,
-                            NM_CRYPTO_ERROR,
-                            NM_CRYPTO_ERROR_INVALID_DATA,
-                            _("Malformed PEM file: DEK-Info was not the second tag."));
-                return FALSE;
-            }
-
-            p += strlen(DEK_INFO_TAG);
-
-            /* Grab the IV first */
-            comma = strchr(p, ',');
-            if (!comma || (*(comma + 1) == '\0')) {
-                g_set_error(error,
-                            NM_CRYPTO_ERROR,
-                            NM_CRYPTO_ERROR_INVALID_DATA,
-                            _("Malformed PEM file: no IV found in DEK-Info tag."));
-                return FALSE;
-            }
-            p_len = comma - p;
-            comma++;
-            if (!g_ascii_isxdigit(*comma)) {
-                g_set_error(error,
-                            NM_CRYPTO_ERROR,
-                            NM_CRYPTO_ERROR_INVALID_DATA,
-                            _("Malformed PEM file: invalid format of IV in DEK-Info tag."));
-                return FALSE;
-            }
-            nm_free_secret(iv);
-            iv = g_strdup(comma);
-
-            /* Get the private key cipher */
-            cipher_info = nm_crypto_cipher_get_info_by_name(p, p_len);
-            if (!cipher_info) {
-                g_set_error(error,
-                            NM_CRYPTO_ERROR,
-                            NM_CRYPTO_ERROR_INVALID_DATA,
-                            _("Malformed PEM file: unknown private key cipher '%s'."),
-                            p);
-                return FALSE;
-            }
-            cipher = cipher_info->cipher;
-        } else {
-            if (enc_tags == 1) {
-                g_set_error(error,
-                            NM_CRYPTO_ERROR,
-                            NM_CRYPTO_ERROR_INVALID_DATA,
-                            "Malformed PEM file: both Proc-Type and DEK-Info tags are required.");
-                return FALSE;
-            }
-            nm_strbuf_append_str(&str_p, &str_len, p);
-            nm_assert(str_len > 0);
-        }
-    }
-
-    parsed.bin = (guint8 *) g_base64_decode(str, &parsed.len);
-    if (!parsed.bin || parsed.len == 0) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("Could not decode private key."));
-        nm_secret_ptr_clear(&parsed);
-        return FALSE;
-    }
-
-    NM_SET_OUT(out_key_type, key_type);
-    NM_SET_OUT(out_iv, g_steal_pointer(&iv));
-    NM_SET_OUT(out_cipher, cipher);
-    nm_secret_ptr_move(out_parsed, &parsed);
-    return TRUE;
-}
-
-static gboolean
-parse_pkcs8_key_file(const guint8 *data,
-                     gsize         data_len,
-                     NMSecretPtr  *parsed,
-                     gboolean     *out_encrypted,
-                     GError      **error)
-{
-    gsize                     start = 0, end = 0;
-    const char               *start_tag = NULL, *end_tag = NULL;
-    gboolean                  encrypted  = FALSE;
-    nm_auto_free_secret char *der_base64 = NULL;
-
-    nm_assert(parsed);
-    nm_assert(!parsed->bin);
-    nm_assert(parsed->len == 0);
-    nm_assert(out_encrypted);
-
-    /* Try encrypted first, decrypted next */
-    if (find_tag(PEM_PKCS8_ENC_KEY_BEGIN, data, data_len, 0, &start)) {
-        start_tag = PEM_PKCS8_ENC_KEY_BEGIN;
-        end_tag   = PEM_PKCS8_ENC_KEY_END;
-        encrypted = TRUE;
-    } else if (find_tag(PEM_PKCS8_DEC_KEY_BEGIN, data, data_len, 0, &start)) {
-        start_tag = PEM_PKCS8_DEC_KEY_BEGIN;
-        end_tag   = PEM_PKCS8_DEC_KEY_END;
-        encrypted = FALSE;
-    } else {
-        g_set_error_literal(error,
-                            NM_CRYPTO_ERROR,
-                            NM_CRYPTO_ERROR_INVALID_DATA,
-                            _("Failed to find expected PKCS#8 start tag."));
-        return FALSE;
-    }
-
-    start += strlen(start_tag);
-    if (!find_tag(end_tag, data, data_len, start, &end)) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("Failed to find expected PKCS#8 end tag '%s'."),
-                    end_tag);
-        return FALSE;
-    }
-
-    /* g_base64_decode() wants a NULL-terminated string */
-    der_base64 = g_strndup((char *) &data[start], end - start);
-
-    parsed->bin = (guint8 *) g_base64_decode(der_base64, &parsed->len);
-    if (!parsed->bin || parsed->len == 0) {
-        g_set_error_literal(error,
-                            NM_CRYPTO_ERROR,
-                            NM_CRYPTO_ERROR_INVALID_DATA,
-                            _("Failed to decode PKCS#8 private key."));
-        nm_secret_ptr_clear(parsed);
-        return FALSE;
-    }
-
-    *out_encrypted = encrypted;
-    return TRUE;
-}
-
-static gboolean
-parse_tpm2_wrapped_key_file(const guint8 *data,
-                            gsize         data_len,
-                            gboolean     *out_encrypted,
-                            GError      **error)
-{
-    gsize       start = 0, end = 0;
-    const char *start_tag = NULL, *end_tag = NULL;
-
-    nm_assert(out_encrypted);
-
-    if (find_tag(PEM_TPM2_WRAPPED_KEY_BEGIN, data, data_len, 0, &start)) {
-        start_tag = PEM_TPM2_WRAPPED_KEY_BEGIN;
-        end_tag   = PEM_TPM2_WRAPPED_KEY_END;
-    } else if (find_tag(PEM_TPM2_OLD_WRAPPED_KEY_BEGIN, data, data_len, 0, &start)) {
-        start_tag = PEM_TPM2_OLD_WRAPPED_KEY_BEGIN;
-        end_tag   = PEM_TPM2_OLD_WRAPPED_KEY_END;
-    } else {
-        g_set_error_literal(error,
-                            NM_CRYPTO_ERROR,
-                            NM_CRYPTO_ERROR_INVALID_DATA,
-                            _("Failed to find expected TSS start tag."));
-        return FALSE;
-    }
-
-    start += strlen(start_tag);
-    if (!find_tag(end_tag, data, data_len, start, &end)) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("Failed to find expected TSS end tag '%s'."),
-                    end_tag);
-        return FALSE;
-    }
-
-    *out_encrypted = FALSE;
-    return TRUE;
-}
-
-static gboolean
-file_read_contents(const char *filename, NMSecretPtr *out_contents, GError **error)
-{
-    nm_assert(out_contents);
-    nm_assert(out_contents->len == 0);
-    nm_assert(!out_contents->str);
-
-    return nm_utils_file_get_contents(-1,
-                                      filename,
-                                      100 * 1024 * 1024,
-                                      NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET,
-                                      &out_contents->str,
-                                      &out_contents->len,
-                                      NULL,
-                                      error);
-}
-
-GBytes *
-nm_crypto_read_file(const char *filename, GError **error)
-{
-    nm_auto_clear_secret_ptr NMSecretPtr contents = {0};
-
-    g_return_val_if_fail(filename, NULL);
-
-    if (!file_read_contents(filename, &contents, error))
-        return NULL;
-    return nm_secret_copy_to_gbytes(contents.bin, contents.len);
-}
-
-/*
- * Convert a hex string into bytes.
- */
-static guint8 *
-_nmtst_convert_iv(const char *src, gsize *out_len, GError **error)
-{
-    gsize           i, num;
-    gs_free guint8 *c = NULL;
-    int             c0, c1;
-
-    nm_assert(src);
-
-    num = strlen(src);
-    if (num == 0 || (num % 2) != 0) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("IV must be an even number of bytes in length."));
-        return NULL;
-    }
-
-    num /= 2;
-    c = g_malloc(num + 1);
-
-    /* defensively add trailing NUL. This function returns binary data,
-     * do not assume it's NUL terminated. */
-    c[num] = '\0';
-
-    for (i = 0; i < num; i++) {
-        if (((c0 = nm_utils_hexchar_to_int(*(src++))) < 0)
-            || ((c1 = nm_utils_hexchar_to_int(*(src++))) < 0)) {
-            g_set_error(error,
-                        NM_CRYPTO_ERROR,
-                        NM_CRYPTO_ERROR_INVALID_DATA,
-                        _("IV contains non-hexadecimal digits."));
-            nm_explicit_bzero(c, i);
-            return FALSE;
-        }
-
-        c[i] = (c0 << 4) + c1;
-    }
-    *out_len = num;
-    return g_steal_pointer(&c);
-}
-
-guint8 *
-nmtst_crypto_make_des_aes_key(NMCryptoCipherType cipher,
-                              const guint8      *salt,
-                              gsize              salt_len,
-                              const char        *password,
-                              gsize             *out_len,
-                              GError           **error)
-{
-    guint8                   *key;
-    const NMCryptoCipherInfo *cipher_info;
-
-    g_return_val_if_fail(salt != NULL, NULL);
-    g_return_val_if_fail(salt_len >= 8, NULL);
-    g_return_val_if_fail(password != NULL, NULL);
-    g_return_val_if_fail(out_len != NULL, NULL);
-
-    *out_len = 0;
-
-    cipher_info = nm_crypto_cipher_get_info(cipher);
-
-    g_return_val_if_fail(cipher_info, NULL);
-
-    if (password[0] == '\0')
-        return NULL;
-
-    key = g_malloc(cipher_info->digest_len);
-
-    nm_crypto_md5_hash(salt,
-                       8,
-                       (guint8 *) password,
-                       strlen(password),
-                       key,
-                       cipher_info->digest_len);
-
-    *out_len = cipher_info->digest_len;
-    return key;
-}
-
-static gboolean
-_nmtst_decrypt_key(NMCryptoCipherType cipher,
-                   const guint8      *data,
-                   gsize              data_len,
-                   const char        *iv,
-                   const char        *password,
-                   NMSecretPtr       *parsed,
-                   GError           **error)
-{
-    nm_auto_clear_secret_ptr NMSecretPtr bin_iv = {0};
-    nm_auto_clear_secret_ptr NMSecretPtr key    = {0};
-
-    nm_assert(password);
-    nm_assert(cipher != NM_CRYPTO_CIPHER_UNKNOWN);
-    nm_assert(iv);
-    nm_assert(parsed);
-    nm_assert(!parsed->bin);
-    nm_assert(parsed->len == 0);
-
-    bin_iv.bin = _nmtst_convert_iv(iv, &bin_iv.len, error);
-    if (!bin_iv.bin)
-        return FALSE;
-
-    if (bin_iv.len < 8) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("IV must contain at least 8 characters"));
-        return FALSE;
-    }
-
-    /* Convert the password and IV into a DES or AES key */
-    key.bin =
-        nmtst_crypto_make_des_aes_key(cipher, bin_iv.bin, bin_iv.len, password, &key.len, error);
-    if (!key.bin || !key.len)
-        return FALSE;
-
-    parsed->bin = _nmtst_crypto_decrypt(cipher,
-                                        data,
-                                        data_len,
-                                        bin_iv.bin,
-                                        bin_iv.len,
-                                        key.bin,
-                                        key.len,
-                                        &parsed->len,
-                                        error);
-    if (!parsed->bin || parsed->len == 0) {
-        nm_secret_ptr_clear(parsed);
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-GBytes *
-nmtst_crypto_decrypt_openssl_private_key_data(const guint8    *data,
-                                              gsize            data_len,
-                                              const char      *password,
-                                              NMCryptoKeyType *out_key_type,
-                                              GError         **error)
-{
-    NMCryptoKeyType                      key_type = NM_CRYPTO_KEY_TYPE_UNKNOWN;
-    nm_auto_clear_secret_ptr NMSecretPtr parsed   = {0};
-    nm_auto_free_secret char            *iv       = NULL;
-    NMCryptoCipherType                   cipher   = NM_CRYPTO_CIPHER_UNKNOWN;
-
-    g_return_val_if_fail(data != NULL, NULL);
-
-    NM_SET_OUT(out_key_type, NM_CRYPTO_KEY_TYPE_UNKNOWN);
-
-    if (!_nm_crypto_init(error))
-        return NULL;
-
-    if (!parse_old_openssl_key_file(data, data_len, &parsed, &key_type, &cipher, &iv, NULL)) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("Unable to determine private key type."));
-        return NULL;
-    }
-
-    NM_SET_OUT(out_key_type, key_type);
-
-    if (password) {
-        nm_auto_clear_secret_ptr NMSecretPtr parsed2 = {0};
-
-        if (cipher == NM_CRYPTO_CIPHER_UNKNOWN || !iv) {
-            g_set_error(error,
-                        NM_CRYPTO_ERROR,
-                        NM_CRYPTO_ERROR_INVALID_PASSWORD,
-                        _("Password provided, but key was not encrypted."));
-            return NULL;
-        }
-
-        if (!_nmtst_decrypt_key(cipher, parsed.bin, parsed.len, iv, password, &parsed2, error))
-            return NULL;
-
-        return nm_secret_copy_to_gbytes(parsed2.bin, parsed2.len);
-    }
-
-    if (cipher != NM_CRYPTO_CIPHER_UNKNOWN || iv)
-        return NULL;
-
-    return nm_secret_copy_to_gbytes(parsed.bin, parsed.len);
-}
-
-GBytes *
-nmtst_crypto_decrypt_openssl_private_key(const char      *file,
-                                         const char      *password,
-                                         NMCryptoKeyType *out_key_type,
-                                         GError         **error)
-{
-    nm_auto_clear_secret_ptr NMSecretPtr contents = {0};
-
-    if (!_nm_crypto_init(error))
-        return NULL;
-
-    if (!file_read_contents(file, &contents, error))
-        return NULL;
-
-    return nmtst_crypto_decrypt_openssl_private_key_data(contents.bin,
-                                                         contents.len,
-                                                         password,
-                                                         out_key_type,
-                                                         error);
-}
-
-static gboolean
-extract_pem_cert_data(const guint8 *contents,
-                      gsize         contents_len,
-                      NMSecretPtr  *out_cert,
-                      GError      **error)
-{
-    gsize                     start      = 0;
-    gsize                     end        = 0;
-    nm_auto_free_secret char *der_base64 = NULL;
-
-    nm_assert(contents);
-    nm_assert(out_cert);
-    nm_assert(out_cert->len == 0);
-    nm_assert(!out_cert->ptr);
-
-    if (!find_tag(PEM_CERT_BEGIN, contents, contents_len, 0, &start)) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("PEM certificate had no start tag '%s'."),
-                    PEM_CERT_BEGIN);
-        return FALSE;
-    }
-
-    start += strlen(PEM_CERT_BEGIN);
-    if (!find_tag(PEM_CERT_END, contents, contents_len, start, &end)) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("PEM certificate had no end tag '%s'."),
-                    PEM_CERT_END);
-        return FALSE;
-    }
-
-    /* g_base64_decode() wants a NULL-terminated string */
-    der_base64 = g_strndup((const char *) &contents[start], end - start);
-
-    out_cert->bin = (guint8 *) g_base64_decode(der_base64, &out_cert->len);
-    if (!out_cert->bin || !out_cert->len) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("Failed to decode certificate."));
-        nm_secret_ptr_clear(out_cert);
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-gboolean
-nm_crypto_load_and_verify_certificate(const char         *file,
-                                      NMCryptoFileFormat *out_file_format,
-                                      GBytes            **out_certificate,
-                                      GError            **error)
-{
-    nm_auto_clear_secret_ptr NMSecretPtr contents = {0};
-
-    g_return_val_if_fail(file, FALSE);
-    nm_assert(!error || !*error);
-
-    if (!_nm_crypto_init(error))
-        goto out;
-
-    if (!file_read_contents(file, &contents, error))
-        goto out;
-
-    if (contents.len == 0) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("Certificate file is empty"));
-        goto out;
-    }
-
-    /* Check for PKCS#12 */
-    if (nm_crypto_is_pkcs12_data(contents.bin, contents.len, NULL)) {
-        NM_SET_OUT(out_file_format, NM_CRYPTO_FILE_FORMAT_PKCS12);
-        NM_SET_OUT(out_certificate, nm_secret_copy_to_gbytes(contents.bin, contents.len));
-        return TRUE;
-    }
-
-    /* Check for plain DER format */
-    if (contents.len > 2 && contents.bin[0] == 0x30 && contents.bin[1] == 0x82) {
-        if (_nm_crypto_verify_x509(contents.bin, contents.len, NULL)) {
-            NM_SET_OUT(out_file_format, NM_CRYPTO_FILE_FORMAT_X509);
-            NM_SET_OUT(out_certificate, nm_secret_copy_to_gbytes(contents.bin, contents.len));
-            return TRUE;
-        }
-    } else {
-        nm_auto_clear_secret_ptr NMSecretPtr pem_cert = {0};
-
-        if (extract_pem_cert_data(contents.bin, contents.len, &pem_cert, NULL)) {
-            if (_nm_crypto_verify_x509(pem_cert.bin, pem_cert.len, NULL)) {
-                NM_SET_OUT(out_file_format, NM_CRYPTO_FILE_FORMAT_X509);
-                NM_SET_OUT(out_certificate, nm_secret_copy_to_gbytes(contents.bin, contents.len));
-                return TRUE;
-            }
-        }
-    }
-
-    g_set_error(error,
-                NM_CRYPTO_ERROR,
-                NM_CRYPTO_ERROR_INVALID_DATA,
-                _("Failed to recognize certificate"));
-
-out:
-    NM_SET_OUT(out_file_format, NM_CRYPTO_FILE_FORMAT_UNKNOWN);
-    NM_SET_OUT(out_certificate, NULL);
-    return FALSE;
-}
-
-gboolean
-nm_crypto_is_pkcs12_data(const guint8 *data, gsize data_len, GError **error)
-{
-    GError  *local = NULL;
-    gboolean success;
-
-    if (!data_len) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("Certificate file is empty"));
-        return FALSE;
-    }
-
-    g_return_val_if_fail(data != NULL, FALSE);
-
-    if (!_nm_crypto_init(error))
-        return FALSE;
-
-    success = _nm_crypto_verify_pkcs12(data, data_len, NULL, &local);
-    if (success == FALSE) {
-        /* If the error was just a decryption error, then it's pkcs#12 */
-        if (local) {
-            if (g_error_matches(local, NM_CRYPTO_ERROR, NM_CRYPTO_ERROR_DECRYPTION_FAILED)) {
-                success = TRUE;
-                g_error_free(local);
-            } else
-                g_propagate_error(error, local);
-        }
-    }
-    return success;
-}
-
-gboolean
-nm_crypto_is_pkcs12_file(const char *file, GError **error)
-{
-    nm_auto_clear_secret_ptr NMSecretPtr contents = {0};
-
-    g_return_val_if_fail(file != NULL, FALSE);
-
-    if (!_nm_crypto_init(error))
-        return FALSE;
-
-    if (!file_read_contents(file, &contents, error))
-        return FALSE;
-
-    return nm_crypto_is_pkcs12_data(contents.bin, contents.len, error);
-}
-
-/* Verifies that a private key can be read, and if a password is given, that
- * the private key can be decrypted with that password.
- */
-NMCryptoFileFormat
-nm_crypto_verify_private_key_data(const guint8 *data,
-                                  gsize         data_len,
-                                  const char   *password,
-                                  gboolean     *out_is_encrypted,
-                                  GError      **error)
-{
-    NMCryptoFileFormat format       = NM_CRYPTO_FILE_FORMAT_UNKNOWN;
-    gboolean           is_encrypted = FALSE;
-
-    g_return_val_if_fail(data != NULL, NM_CRYPTO_FILE_FORMAT_UNKNOWN);
-    g_return_val_if_fail(out_is_encrypted == NULL || *out_is_encrypted == FALSE,
-                         NM_CRYPTO_FILE_FORMAT_UNKNOWN);
-
-    if (!_nm_crypto_init(error))
-        return NM_CRYPTO_FILE_FORMAT_UNKNOWN;
-
-    /* Check for PKCS#12 first */
-    if (nm_crypto_is_pkcs12_data(data, data_len, NULL)) {
-        is_encrypted = TRUE;
-        if (!password || _nm_crypto_verify_pkcs12(data, data_len, password, error))
-            format = NM_CRYPTO_FILE_FORMAT_PKCS12;
-    } else {
-        nm_auto_clear_secret_ptr NMSecretPtr parsed = {0};
-
-        /* Maybe it's PKCS#8 */
-        if (parse_pkcs8_key_file(data, data_len, &parsed, &is_encrypted, NULL)) {
-            if (!password
-                || _nm_crypto_verify_pkcs8(parsed.bin, parsed.len, is_encrypted, password, error))
-                format = NM_CRYPTO_FILE_FORMAT_RAW_KEY;
-        } else if (parse_tpm2_wrapped_key_file(data, data_len, &is_encrypted, NULL)) {
-            format = NM_CRYPTO_FILE_FORMAT_RAW_KEY;
-        } else {
-            NMCryptoCipherType        cipher;
-            nm_auto_free_secret char *iv = NULL;
-
-            /* Or it's old-style OpenSSL */
-            if (parse_old_openssl_key_file(data, data_len, NULL, NULL, &cipher, &iv, NULL)) {
-                format       = NM_CRYPTO_FILE_FORMAT_RAW_KEY;
-                is_encrypted = (cipher != NM_CRYPTO_CIPHER_UNKNOWN && iv);
-            }
-        }
-    }
-
-    if (format == NM_CRYPTO_FILE_FORMAT_UNKNOWN && error && !*error) {
-        g_set_error(error,
-                    NM_CRYPTO_ERROR,
-                    NM_CRYPTO_ERROR_INVALID_DATA,
-                    _("not a valid private key"));
-    }
-
-    if (out_is_encrypted)
-        *out_is_encrypted = is_encrypted;
-    return format;
-}
-
-NMCryptoFileFormat
-nm_crypto_verify_private_key(const char *filename,
-                             const char *password,
-                             gboolean   *out_is_encrypted,
-                             GError    **error)
-{
-    nm_auto_clear_secret_ptr NMSecretPtr contents = {0};
-
-    g_return_val_if_fail(filename != NULL, NM_CRYPTO_FILE_FORMAT_UNKNOWN);
-
-    if (!_nm_crypto_init(error))
-        return NM_CRYPTO_FILE_FORMAT_UNKNOWN;
-
-    if (!file_read_contents(filename, &contents, error))
-        return NM_CRYPTO_FILE_FORMAT_UNKNOWN;
-
-    return nm_crypto_verify_private_key_data(contents.bin,
-                                             contents.len,
-                                             password,
-                                             out_is_encrypted,
-                                             error);
-}
-
-gboolean
-nm_crypto_randomize(void *buffer, gsize buffer_len, GError **error)
-{
-    return _nm_crypto_randomize(buffer, buffer_len, error);
-}
-
-/**
- * nmtst_crypto_rsa_key_encrypt:
- * @data: (array length=len): RSA private key data to be encrypted
- * @len: length of @data
- * @in_password: (allow-none): existing password to use, if any
- * @out_password: (out) (allow-none): if @in_password was %NULL, a random
- *  password will be generated and returned in this argument
- * @error: detailed error information on return, if an error occurred
- *
- * Encrypts the given RSA private key data with the given password (or generates
- * a password if no password was given) and converts the data to PEM format
- * suitable for writing to a file. It uses Triple DES cipher for the encryption.
- *
- * Returns: (transfer full): on success, PEM-formatted data suitable for writing
- * to a PEM-formatted certificate/private key file.
- **/
-GBytes *
-nmtst_crypto_rsa_key_encrypt(const guint8 *data,
-                             gsize         len,
-                             const char   *in_password,
-                             char        **out_password,
-                             GError      **error)
-{
-    guint8                               salt[8];
-    nm_auto_clear_secret_ptr NMSecretPtr key          = {0};
-    nm_auto_clear_secret_ptr NMSecretPtr enc          = {0};
-    gs_unref_ptrarray GPtrArray         *pem          = NULL;
-    nm_auto_free_secret char            *tmp_password = NULL;
-    nm_auto_free_secret char            *enc_base64   = NULL;
-    gsize                                enc_base64_len;
-    const char                          *p;
-    gsize                                ret_len, ret_idx;
-    guint                                i;
-    NMSecretBuf                         *ret;
-
-    g_return_val_if_fail(data, NULL);
-    g_return_val_if_fail(len > 0, NULL);
-    g_return_val_if_fail(!out_password || !*out_password, NULL);
-
-    /* Make the password if needed */
-    if (!in_password) {
-        nm_auto_clear_static_secret_ptr NMSecretPtr pw_buf = NM_SECRET_PTR_STATIC(32);
-
-        if (!nm_crypto_randomize(pw_buf.bin, pw_buf.len, error))
-            return NULL;
-        tmp_password = nm_utils_bin2hexstr(pw_buf.bin, pw_buf.len, -1);
-        in_password  = tmp_password;
-    }
-
-    if (!nm_crypto_randomize(salt, sizeof(salt), error))
-        return NULL;
-
-    key.bin = nmtst_crypto_make_des_aes_key(NM_CRYPTO_CIPHER_DES_EDE3_CBC,
-                                            salt,
-                                            sizeof(salt),
-                                            in_password,
-                                            &key.len,
-                                            NULL);
-    if (!key.bin)
-        g_return_val_if_reached(NULL);
-
-    enc.bin = _nmtst_crypto_encrypt(NM_CRYPTO_CIPHER_DES_EDE3_CBC,
-                                    data,
-                                    len,
-                                    salt,
-                                    sizeof(salt),
-                                    key.bin,
-                                    key.len,
-                                    &enc.len,
-                                    error);
-    if (!enc.bin)
-        return NULL;
-
-    /* What follows is not the most efficient way to construct the pem
-     * file line-by-line. At least, it makes sure, that the data will be cleared
-     * again and not left around in memory.
-     *
-     * If this would not be test code, we should improve the implementation
-     * to avoid some of the copying. */
-    pem = g_ptr_array_new_with_free_func((GDestroyNotify) nm_free_secret);
-
-    g_ptr_array_add(pem, g_strdup("-----BEGIN RSA PRIVATE KEY-----\n"));
-    g_ptr_array_add(pem, g_strdup("Proc-Type: 4,ENCRYPTED\n"));
-
-    /* Convert the salt to a hex string */
-    g_ptr_array_add(
-        pem,
-        g_strdup_printf("DEK-Info: %s,",
-                        nm_crypto_cipher_get_info(NM_CRYPTO_CIPHER_DES_EDE3_CBC)->name));
-    g_ptr_array_add(pem, nm_utils_bin2hexstr(salt, sizeof(salt), sizeof(salt) * 2));
-    g_ptr_array_add(pem, g_strdup("\n\n"));
-
-    /* Convert the encrypted key to a base64 string */
-    enc_base64     = g_base64_encode((const guchar *) enc.bin, enc.len);
-    enc_base64_len = strlen(enc_base64);
-    for (p = enc_base64; (p - enc_base64) < (ptrdiff_t) enc_base64_len; p += 64) {
-        g_ptr_array_add(pem, g_strndup(p, 64));
-        g_ptr_array_add(pem, g_strdup("\n"));
-    }
-
-    g_ptr_array_add(pem, g_strdup("-----END RSA PRIVATE KEY-----\n"));
-
-    ret_len = 0;
-    for (i = 0; i < pem->len; i++)
-        ret_len += strlen(pem->pdata[i]);
-
-    ret     = nm_secret_buf_new(ret_len + 1);
-    ret_idx = 0;
-    for (i = 0; i < pem->len; i++) {
-        const char *line   = pem->pdata[i];
-        gsize       line_l = strlen(line);
-
-        memcpy(&ret->bin[ret_idx], line, line_l);
-        ret_idx += line_l;
-        nm_assert(ret_idx <= ret_len);
-    }
-    nm_assert(ret_idx == ret_len);
-    ret->bin[ret_len] = '\0';
-
-    NM_SET_OUT(out_password, g_strdup(tmp_password));
-    return nm_secret_buf_to_gbytes_take(ret, ret_len);
-}
diff --git a/src/libnm-core-impl/nm-crypto.h b/src/libnm-core-impl/nm-crypto.h
deleted file mode 100644
index a740c43c..00000000
--- a/src/libnm-core-impl/nm-crypto.h
+++ /dev/null
@@ -1,96 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Dan Williams <dcbw@redhat.com>
- * Copyright (C) 2007 - 2014 Red Hat, Inc.
- */
-
-#ifndef __NM_CRYPTO_H__
-#define __NM_CRYPTO_H__
-
-typedef enum {
-    NM_CRYPTO_CIPHER_UNKNOWN,
-    NM_CRYPTO_CIPHER_DES_EDE3_CBC,
-    NM_CRYPTO_CIPHER_DES_CBC,
-    NM_CRYPTO_CIPHER_AES_128_CBC,
-    NM_CRYPTO_CIPHER_AES_192_CBC,
-    NM_CRYPTO_CIPHER_AES_256_CBC,
-} NMCryptoCipherType;
-
-typedef struct {
-    const char        *name;
-    NMCryptoCipherType cipher;
-    guint8             digest_len;
-    guint8             real_iv_len;
-} NMCryptoCipherInfo;
-
-const NMCryptoCipherInfo *nm_crypto_cipher_get_info(NMCryptoCipherType cipher);
-const NMCryptoCipherInfo *nm_crypto_cipher_get_info_by_name(const char *cipher_name, gssize p_len);
-
-typedef enum {
-    NM_CRYPTO_KEY_TYPE_UNKNOWN = 0,
-    NM_CRYPTO_KEY_TYPE_RSA,
-    NM_CRYPTO_KEY_TYPE_DSA
-} NMCryptoKeyType;
-
-typedef enum {
-    NM_CRYPTO_FILE_FORMAT_UNKNOWN = 0,
-    NM_CRYPTO_FILE_FORMAT_X509,
-    NM_CRYPTO_FILE_FORMAT_RAW_KEY,
-    NM_CRYPTO_FILE_FORMAT_PKCS12
-} NMCryptoFileFormat;
-
-/*****************************************************************************/
-
-GBytes *nm_crypto_read_file(const char *filename, GError **error);
-
-gboolean nm_crypto_load_and_verify_certificate(const char         *file,
-                                               NMCryptoFileFormat *out_file_format,
-                                               GBytes            **out_certificat,
-                                               GError            **error);
-
-gboolean nm_crypto_is_pkcs12_file(const char *file, GError **error);
-
-gboolean nm_crypto_is_pkcs12_data(const guint8 *data, gsize len, GError **error);
-
-NMCryptoFileFormat nm_crypto_verify_private_key_data(const guint8 *data,
-                                                     gsize         data_len,
-                                                     const char   *password,
-                                                     gboolean     *out_is_encrypted,
-                                                     GError      **error);
-
-NMCryptoFileFormat nm_crypto_verify_private_key(const char *file,
-                                                const char *password,
-                                                gboolean   *out_is_encrypted,
-                                                GError    **error);
-
-gboolean nm_crypto_randomize(void *buffer, gsize buffer_len, GError **error);
-
-/*****************************************************************************/
-
-GBytes *nmtst_crypto_decrypt_openssl_private_key_data(const guint8    *data,
-                                                      gsize            data_len,
-                                                      const char      *password,
-                                                      NMCryptoKeyType *out_key_type,
-                                                      GError         **error);
-
-GBytes *nmtst_crypto_decrypt_openssl_private_key(const char      *file,
-                                                 const char      *password,
-                                                 NMCryptoKeyType *out_key_type,
-                                                 GError         **error);
-
-GBytes *nmtst_crypto_rsa_key_encrypt(const guint8 *data,
-                                     gsize         len,
-                                     const char   *in_password,
-                                     char        **out_password,
-                                     GError      **error);
-
-guint8 *nmtst_crypto_make_des_aes_key(NMCryptoCipherType cipher,
-                                      const guint8      *salt,
-                                      gsize              salt_len,
-                                      const char        *password,
-                                      gsize             *out_len,
-                                      GError           **error);
-
-/*****************************************************************************/
-
-#endif /* __NM_CRYPTO_H__ */
diff --git a/src/libnm-core-impl/nm-errors.c b/src/libnm-core-impl/nm-errors.c
index ea4ca5e9..fad854bc 100644
--- a/src/libnm-core-impl/nm-errors.c
+++ b/src/libnm-core-impl/nm-errors.c
@@ -12,12 +12,24 @@
 
 NM_CACHED_QUARK_FCN("nm-agent-manager-error-quark", nm_agent_manager_error_quark);
 NM_CACHED_QUARK_FCN("nm-connection-error-quark", nm_connection_error_quark);
-NM_CACHED_QUARK_FCN("nm-crypto-error-quark", nm_crypto_error_quark);
 NM_CACHED_QUARK_FCN("nm-device-error-quark", nm_device_error_quark);
 NM_CACHED_QUARK_FCN("nm-secret-agent-error-quark", nm_secret_agent_error_quark);
 NM_CACHED_QUARK_FCN("nm-settings-error-quark", nm_settings_error_quark);
 NM_CACHED_QUARK_FCN("nm-vpn-plugin-error-quark", nm_vpn_plugin_error_quark);
 
+GQuark
+nm_crypto_error_quark(void)
+{
+    G_STATIC_ASSERT(NM_CRYPTO_ERROR_FAILED == _NM_CRYPTO_ERROR_FAILED);
+    G_STATIC_ASSERT(NM_CRYPTO_ERROR_INVALID_DATA == _NM_CRYPTO_ERROR_INVALID_DATA);
+    G_STATIC_ASSERT(NM_CRYPTO_ERROR_INVALID_PASSWORD == _NM_CRYPTO_ERROR_INVALID_PASSWORD);
+    G_STATIC_ASSERT(NM_CRYPTO_ERROR_UNKNOWN_CIPHER == _NM_CRYPTO_ERROR_UNKNOWN_CIPHER);
+    G_STATIC_ASSERT(NM_CRYPTO_ERROR_DECRYPTION_FAILED == _NM_CRYPTO_ERROR_DECRYPTION_FAILED);
+    G_STATIC_ASSERT(NM_CRYPTO_ERROR_ENCRYPTION_FAILED == _NM_CRYPTO_ERROR_ENCRYPTION_FAILED);
+
+    return _nm_crypto_error_quark();
+}
+
 static void
 register_error_domain(GQuark domain, const char *interface, GType enum_type)
 {
diff --git a/src/libnm-core-impl/nm-keyfile.c b/src/libnm-core-impl/nm-keyfile.c
index dce53ae2..00fb8a33 100644
--- a/src/libnm-core-impl/nm-keyfile.c
+++ b/src/libnm-core-impl/nm-keyfile.c
@@ -115,12 +115,14 @@ _key_file_handler_data_init_write(NMKeyfileHandlerData *handler_data,
                                 &info->error);
 }
 
-_nm_printf(5, 6) static void _handle_warn(KeyfileReaderInfo    *info,
-                                          const char           *kf_key,
-                                          const char           *cur_property,
-                                          NMKeyfileWarnSeverity severity,
-                                          const char           *fmt,
-                                          ...)
+/*****************************************************************************/
+
+_nm_printf(5, 6) static void _read_handle_warn(KeyfileReaderInfo    *info,
+                                               const char           *kf_key,
+                                               const char           *cur_property,
+                                               NMKeyfileWarnSeverity severity,
+                                               const char           *fmt,
+                                               ...)
 {
     NMKeyfileHandlerData handler_data;
 
@@ -148,16 +150,75 @@ _nm_printf(5, 6) static void _handle_warn(KeyfileReaderInfo    *info,
     g_free(handler_data.warn.message);
 }
 
-#define handle_warn(arg_info, arg_kf_key, arg_property_name, arg_severity, ...)                  \
-    ({                                                                                           \
-        KeyfileReaderInfo *_info = (arg_info);                                                   \
-                                                                                                 \
-        nm_assert(!_info->error);                                                                \
-                                                                                                 \
-        if (_info->read_handler) {                                                               \
-            _handle_warn(_info, (arg_kf_key), (arg_property_name), (arg_severity), __VA_ARGS__); \
-        }                                                                                        \
-        _info->error == NULL;                                                                    \
+#define read_handle_warn(arg_info, arg_kf_key, arg_property_name, arg_severity, ...) \
+    ({                                                                               \
+        KeyfileReaderInfo *_info = (arg_info);                                       \
+                                                                                     \
+        nm_assert(!_info->error);                                                    \
+                                                                                     \
+        if (_info->read_handler) {                                                   \
+            _read_handle_warn(_info,                                                 \
+                              (arg_kf_key),                                          \
+                              (arg_property_name),                                   \
+                              (arg_severity),                                        \
+                              __VA_ARGS__);                                          \
+        }                                                                            \
+        _info->error == NULL;                                                        \
+    })
+
+/*****************************************************************************/
+
+_nm_printf(6, 7) static void _write_handle_warn(KeyfileWriterInfo    *info,
+                                                NMSetting            *setting,
+                                                const char           *kf_key,
+                                                const char           *cur_property,
+                                                NMKeyfileWarnSeverity severity,
+                                                const char           *fmt,
+                                                ...)
+{
+    NMKeyfileHandlerData handler_data;
+
+    _key_file_handler_data_init_write(&handler_data,
+                                      NM_KEYFILE_HANDLER_TYPE_WARN,
+                                      info,
+                                      nm_setting_get_name(setting),
+                                      cur_property,
+                                      setting,
+                                      kf_key);
+    handler_data.warn = (NMKeyfileHandlerDataWarn){
+        .severity = severity,
+        .message  = NULL,
+        .fmt      = fmt,
+    };
+
+    va_start(handler_data.warn.ap, fmt);
+
+    info->write_handler(info->connection,
+                        info->keyfile,
+                        NM_KEYFILE_HANDLER_TYPE_WARN,
+                        &handler_data,
+                        info->user_data);
+
+    va_end(handler_data.warn.ap);
+
+    g_free(handler_data.warn.message);
+}
+
+#define write_handle_warn(arg_info, arg_setting, arg_kf_key, arg_property_name, arg_severity, ...) \
+    ({                                                                                             \
+        KeyfileWriterInfo *_info = (arg_info);                                                     \
+                                                                                                   \
+        nm_assert(!_info->error);                                                                  \
+                                                                                                   \
+        if (_info->write_handler) {                                                                \
+            _write_handle_warn(_info,                                                              \
+                               (arg_setting),                                                      \
+                               (arg_kf_key),                                                       \
+                               (arg_property_name),                                                \
+                               (arg_severity),                                                     \
+                               __VA_ARGS__);                                                       \
+        }                                                                                          \
+        _info->error == NULL;                                                                      \
     })
 
 /*****************************************************************************/
@@ -264,11 +325,11 @@ get_one_int(KeyfileReaderInfo *info,
 
     if (!str || !str[0]) {
         if (info) {
-            handle_warn(info,
-                        kf_key,
-                        property_name,
-                        NM_KEYFILE_WARN_SEVERITY_WARN,
-                        _("ignoring missing number"));
+            read_handle_warn(info,
+                             kf_key,
+                             property_name,
+                             NM_KEYFILE_WARN_SEVERITY_WARN,
+                             _("ignoring missing number"));
         }
         return FALSE;
     }
@@ -276,12 +337,12 @@ get_one_int(KeyfileReaderInfo *info,
     tmp = _nm_utils_ascii_str_to_int64(str, 10, 0, max_val, -1);
     if (tmp == -1) {
         if (info) {
-            handle_warn(info,
-                        kf_key,
-                        property_name,
-                        NM_KEYFILE_WARN_SEVERITY_WARN,
-                        _("ignoring invalid number '%s'"),
-                        str);
+            read_handle_warn(info,
+                             kf_key,
+                             property_name,
+                             NM_KEYFILE_WARN_SEVERITY_WARN,
+                             _("ignoring invalid number '%s'"),
+                             str);
         }
         return FALSE;
     }
@@ -305,13 +366,13 @@ build_address(KeyfileReaderInfo *info,
 
     addr = nm_ip_address_new(family, address_str, plen, &error);
     if (!addr) {
-        handle_warn(info,
-                    kf_key,
-                    property_name,
-                    NM_KEYFILE_WARN_SEVERITY_WARN,
-                    _("ignoring invalid %s address: %s"),
-                    family == AF_INET ? "IPv4" : "IPv6",
-                    error->message);
+        read_handle_warn(info,
+                         kf_key,
+                         property_name,
+                         NM_KEYFILE_WARN_SEVERITY_WARN,
+                         _("ignoring invalid %s address: %s"),
+                         family == AF_INET ? "IPv4" : "IPv6",
+                         error->message);
         g_error_free(error);
     }
 
@@ -351,13 +412,13 @@ build_route(KeyfileReaderInfo *info,
                 metric      = u32;
                 gateway_str = NULL;
             } else {
-                handle_warn(info,
-                            kf_key,
-                            property_name,
-                            NM_KEYFILE_WARN_SEVERITY_WARN,
-                            _("ignoring invalid gateway '%s' for %s route"),
-                            gateway_str,
-                            family == AF_INET ? "IPv4" : "IPv6");
+                read_handle_warn(info,
+                                 kf_key,
+                                 property_name,
+                                 NM_KEYFILE_WARN_SEVERITY_WARN,
+                                 _("ignoring invalid gateway '%s' for %s route"),
+                                 gateway_str,
+                                 family == AF_INET ? "IPv4" : "IPv6");
                 return NULL;
             }
         }
@@ -373,13 +434,13 @@ build_route(KeyfileReaderInfo *info,
 
     route = nm_ip_route_new(family, dest_str, plen, gateway_str, metric, &error);
     if (!route) {
-        handle_warn(info,
-                    kf_key,
-                    property_name,
-                    NM_KEYFILE_WARN_SEVERITY_WARN,
-                    _("ignoring invalid %s route: %s"),
-                    family == AF_INET ? "IPv4" : "IPv6",
-                    error->message);
+        read_handle_warn(info,
+                         kf_key,
+                         property_name,
+                         NM_KEYFILE_WARN_SEVERITY_WARN,
+                         _("ignoring invalid %s route: %s"),
+                         family == AF_INET ? "IPv4" : "IPv6",
+                         error->message);
         g_error_free(error);
     }
 
@@ -551,15 +612,15 @@ read_one_ip_address_or_route(KeyfileReaderInfo *info,
     /* get address field */
     address_str = read_field(&current, &err_str, IP_ADDRESS_CHARS, DELIMITERS);
     if (err_str) {
-        handle_warn(info,
-                    kf_key,
-                    property_name,
-                    NM_KEYFILE_WARN_SEVERITY_WARN,
-                    _("unexpected character '%c' for address %s: '%s' (position %td)"),
-                    *err_str,
-                    kf_key,
-                    VALUE_ORIG(),
-                    err_str - current);
+        read_handle_warn(info,
+                         kf_key,
+                         property_name,
+                         NM_KEYFILE_WARN_SEVERITY_WARN,
+                         _("unexpected character '%c' for address %s: '%s' (position %td)"),
+                         *err_str,
+                         kf_key,
+                         VALUE_ORIG(),
+                         err_str - current);
         return NULL;
     }
     /* get prefix length field (skippable) */
@@ -567,30 +628,31 @@ read_one_ip_address_or_route(KeyfileReaderInfo *info,
     /* get gateway field */
     gateway_str = read_field(&current, &err_str, IP_ADDRESS_CHARS, DELIMITERS);
     if (err_str) {
-        handle_warn(info,
-                    kf_key,
-                    property_name,
-                    NM_KEYFILE_WARN_SEVERITY_WARN,
-                    _("unexpected character '%c' for %s: '%s' (position %td)"),
-                    *err_str,
-                    kf_key,
-                    VALUE_ORIG(),
-                    err_str - current);
+        read_handle_warn(info,
+                         kf_key,
+                         property_name,
+                         NM_KEYFILE_WARN_SEVERITY_WARN,
+                         _("unexpected character '%c' for %s: '%s' (position %td)"),
+                         *err_str,
+                         kf_key,
+                         VALUE_ORIG(),
+                         err_str - current);
         return NULL;
     }
     /* for routes, get metric */
     if (route) {
         metric_str = read_field(&current, &err_str, DIGITS, DELIMITERS);
         if (err_str) {
-            handle_warn(info,
-                        kf_key,
-                        property_name,
-                        NM_KEYFILE_WARN_SEVERITY_WARN,
-                        _("unexpected character '%c' in prefix length for %s: '%s' (position %td)"),
-                        *err_str,
-                        kf_key,
-                        VALUE_ORIG(),
-                        err_str - current);
+            read_handle_warn(
+                info,
+                kf_key,
+                property_name,
+                NM_KEYFILE_WARN_SEVERITY_WARN,
+                _("unexpected character '%c' in prefix length for %s: '%s' (position %td)"),
+                *err_str,
+                kf_key,
+                VALUE_ORIG(),
+                err_str - current);
             return NULL;
         }
     } else
@@ -599,23 +661,23 @@ read_one_ip_address_or_route(KeyfileReaderInfo *info,
         /* there is still some data */
         if (*current) {
             /* another field follows */
-            handle_warn(info,
-                        kf_key,
-                        property_name,
-                        NM_KEYFILE_WARN_SEVERITY_WARN,
-                        _("garbage at the end of value %s: '%s'"),
-                        kf_key,
-                        VALUE_ORIG());
-            return NULL;
-        } else {
-            /* semicolon at the end of input */
-            if (!handle_warn(info,
+            read_handle_warn(info,
                              kf_key,
                              property_name,
-                             NM_KEYFILE_WARN_SEVERITY_INFO,
-                             _("deprecated semicolon at the end of value %s: '%s'"),
+                             NM_KEYFILE_WARN_SEVERITY_WARN,
+                             _("garbage at the end of value %s: '%s'"),
                              kf_key,
-                             VALUE_ORIG()))
+                             VALUE_ORIG());
+            return NULL;
+        } else {
+            /* semicolon at the end of input */
+            if (!read_handle_warn(info,
+                                  kf_key,
+                                  property_name,
+                                  NM_KEYFILE_WARN_SEVERITY_INFO,
+                                  _("deprecated semicolon at the end of value %s: '%s'"),
+                                  kf_key,
+                                  VALUE_ORIG()))
                 return NULL;
         }
     }
@@ -628,26 +690,26 @@ read_one_ip_address_or_route(KeyfileReaderInfo *info,
         if (!get_one_int(info, kf_key, property_name, plen_str, ipv6 ? 128 : 32, &plen)) {
             plen = DEFAULT_PREFIX(route, ipv6);
             if (info->error
-                || !handle_warn(info,
-                                kf_key,
-                                property_name,
-                                NM_KEYFILE_WARN_SEVERITY_WARN,
-                                _("invalid prefix length for %s '%s', defaulting to %d"),
-                                kf_key,
-                                VALUE_ORIG(),
-                                plen))
+                || !read_handle_warn(info,
+                                     kf_key,
+                                     property_name,
+                                     NM_KEYFILE_WARN_SEVERITY_WARN,
+                                     _("invalid prefix length for %s '%s', defaulting to %d"),
+                                     kf_key,
+                                     VALUE_ORIG(),
+                                     plen))
                 return NULL;
         }
     } else {
         plen = DEFAULT_PREFIX(route, ipv6);
-        if (!handle_warn(info,
-                         kf_key,
-                         property_name,
-                         NM_KEYFILE_WARN_SEVERITY_WARN,
-                         _("missing prefix length for %s '%s', defaulting to %d"),
-                         kf_key,
-                         VALUE_ORIG(),
-                         plen))
+        if (!read_handle_warn(info,
+                              kf_key,
+                              property_name,
+                              NM_KEYFILE_WARN_SEVERITY_WARN,
+                              _("missing prefix length for %s '%s', defaulting to %d"),
+                              kf_key,
+                              VALUE_ORIG(),
+                              plen))
             return NULL;
     }
 
@@ -983,13 +1045,13 @@ ip_routing_rule_parser_full(KeyfileReaderInfo        *info,
             NULL,
             &local);
         if (!rule) {
-            if (!handle_warn(info,
-                             build_list[i_build_list].s_key,
-                             property_info->name,
-                             NM_KEYFILE_WARN_SEVERITY_WARN,
-                             _("invalid value for \"%s\": %s"),
-                             build_list[i_build_list].s_key,
-                             local->message))
+            if (!read_handle_warn(info,
+                                  build_list[i_build_list].s_key,
+                                  property_info->name,
+                                  NM_KEYFILE_WARN_SEVERITY_WARN,
+                                  _("invalid value for \"%s\": %s"),
+                                  build_list[i_build_list].s_key,
+                                  local->message))
                 return;
             continue;
         }
@@ -1061,13 +1123,13 @@ ip_dns_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
         NMIPAddr addr;
 
         if (inet_pton(addr_family, list[i], &addr) <= 0) {
-            if (!handle_warn(info,
-                             key,
-                             key,
-                             NM_KEYFILE_WARN_SEVERITY_WARN,
-                             _("ignoring invalid DNS server IPv%c address '%s'"),
-                             nm_utils_addr_family_to_char(addr_family),
-                             list[i])) {
+            if (!read_handle_warn(info,
+                                  key,
+                                  key,
+                                  NM_KEYFILE_WARN_SEVERITY_WARN,
+                                  _("ignoring invalid DNS server IPv%c address '%s'"),
+                                  nm_utils_addr_family_to_char(addr_family),
+                                  list[i])) {
                 do {
                     nm_clear_g_free(&list[i]);
                 } while (++i < length);
@@ -1098,13 +1160,13 @@ ip6_addr_gen_mode_parser(KeyfileReaderInfo *info, NMSetting *setting, const char
                                     s,
                                     (int *) &addr_gen_mode,
                                     NULL)) {
-            handle_warn(info,
-                        key,
-                        key,
-                        NM_KEYFILE_WARN_SEVERITY_WARN,
-                        _("invalid option '%s', use one of [%s]"),
-                        s,
-                        "eui64,stable-privacy");
+            read_handle_warn(info,
+                             key,
+                             key,
+                             NM_KEYFILE_WARN_SEVERITY_WARN,
+                             _("invalid option '%s', use one of [%s]"),
+                             s,
+                             "eui64,stable-privacy");
             return;
         }
     } else
@@ -1160,7 +1222,11 @@ mac_address_parser(KeyfileReaderInfo *info,
             goto good_addr_bin;
     }
 
-    handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("ignoring invalid MAC address"));
+    read_handle_warn(info,
+                     key,
+                     key,
+                     NM_KEYFILE_WARN_SEVERITY_WARN,
+                     _("ignoring invalid MAC address"));
     return;
 
 good_addr_bin:
@@ -1232,14 +1298,14 @@ read_hash_of_string(KeyfileReaderInfo *info,
                     gs_free_error GError *error = NULL;
 
                     if (!_nm_setting_bond_validate_option(name, value, &error)) {
-                        if (!handle_warn(info,
-                                         kf_key,
-                                         name,
-                                         NM_KEYFILE_WARN_SEVERITY_WARN,
-                                         _("ignoring invalid bond option %s%s%s = %s%s%s: %s"),
-                                         NM_PRINT_FMT_QUOTE_STRING(name),
-                                         NM_PRINT_FMT_QUOTE_STRING(value),
-                                         error->message))
+                        if (!read_handle_warn(info,
+                                              kf_key,
+                                              name,
+                                              NM_KEYFILE_WARN_SEVERITY_WARN,
+                                              _("ignoring invalid bond option %s%s%s = %s%s%s: %s"),
+                                              NM_PRINT_FMT_QUOTE_STRING(name),
+                                              NM_PRINT_FMT_QUOTE_STRING(value),
+                                              error->message))
                             return;
                     } else
                         nm_setting_bond_add_option(NM_SETTING_BOND(setting), name, value);
@@ -1426,7 +1492,7 @@ ssid_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
 
     bytes = get_bytes(info, setting_name, key, FALSE, TRUE);
     if (!bytes) {
-        handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("ignoring invalid SSID"));
+        read_handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("ignoring invalid SSID"));
         return;
     }
     g_object_set(setting, key, bytes, NULL);
@@ -1440,11 +1506,11 @@ password_raw_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key
 
     bytes = get_bytes(info, setting_name, key, FALSE, TRUE);
     if (!bytes) {
-        handle_warn(info,
-                    key,
-                    key,
-                    NM_KEYFILE_WARN_SEVERITY_WARN,
-                    _("ignoring invalid raw password"));
+        read_handle_warn(info,
+                         key,
+                         key,
+                         NM_KEYFILE_WARN_SEVERITY_WARN,
+                         _("ignoring invalid raw password"));
         return;
     }
     g_object_set(setting, key, bytes, NULL);
@@ -1585,7 +1651,11 @@ cert_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
         bin = g_bytes_get_data(bytes, &bin_len);
     if (bin_len == 0) {
         if (!info->error) {
-            handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid key/cert value"));
+            read_handle_warn(info,
+                             key,
+                             key,
+                             NM_KEYFILE_WARN_SEVERITY_WARN,
+                             _("invalid key/cert value"));
         }
         return;
     }
@@ -1596,12 +1666,12 @@ cert_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
 
         if (nm_setting_802_1x_check_cert_scheme(bin, bin_len, NULL)
             != NM_SETTING_802_1X_CK_SCHEME_PATH) {
-            handle_warn(info,
-                        key,
-                        key,
-                        NM_KEYFILE_WARN_SEVERITY_WARN,
-                        _("invalid key/cert value path \"%s\""),
-                        bin);
+            read_handle_warn(info,
+                             key,
+                             key,
+                             NM_KEYFILE_WARN_SEVERITY_WARN,
+                             _("invalid key/cert value path \"%s\""),
+                             bin);
             return;
         }
 
@@ -1621,12 +1691,12 @@ cert_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
          * then by invoking a callback (and possibly keyfile settings plugin would
          * collect the file names to be checked and check them later). */
         if (!g_file_test(path2, G_FILE_TEST_EXISTS)) {
-            handle_warn(info,
-                        key,
-                        key,
-                        NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE,
-                        _("certificate or key file '%s' does not exist"),
-                        path2);
+            read_handle_warn(info,
+                             key,
+                             key,
+                             NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE,
+                             _("certificate or key file '%s' does not exist"),
+                             path2);
         }
         return;
     }
@@ -1634,12 +1704,12 @@ cert_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
     if (HAS_SCHEME_PREFIX(bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_PKCS11)) {
         if (nm_setting_802_1x_check_cert_scheme(bin, bin_len, NULL)
             != NM_SETTING_802_1X_CK_SCHEME_PKCS11) {
-            handle_warn(info,
-                        key,
-                        key,
-                        NM_KEYFILE_WARN_SEVERITY_WARN,
-                        _("invalid PKCS#11 URI \"%s\""),
-                        bin);
+            read_handle_warn(info,
+                             key,
+                             key,
+                             NM_KEYFILE_WARN_SEVERITY_WARN,
+                             _("invalid PKCS#11 URI \"%s\""),
+                             bin);
             return;
         }
 
@@ -1680,11 +1750,11 @@ cert_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
             bin_decoded = g_base64_decode(cdata, &bin_decoded_len);
 
         if (bin_decoded_len == 0) {
-            handle_warn(info,
-                        key,
-                        key,
-                        NM_KEYFILE_WARN_SEVERITY_WARN,
-                        _("invalid key/cert value data:;base64, is not base64"));
+            read_handle_warn(info,
+                             key,
+                             key,
+                             NM_KEYFILE_WARN_SEVERITY_WARN,
+                             _("invalid key/cert value data:;base64, is not base64"));
             return;
         }
 
@@ -1693,11 +1763,11 @@ cert_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
             /* 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. */
-            handle_warn(info,
-                        key,
-                        key,
-                        NM_KEYFILE_WARN_SEVERITY_WARN,
-                        _("invalid key/cert value data:;base64,file://"));
+            read_handle_warn(info,
+                             key,
+                             key,
+                             NM_KEYFILE_WARN_SEVERITY_WARN,
+                             _("invalid key/cert value data:;base64,file://"));
             return;
         }
 
@@ -1718,12 +1788,12 @@ cert_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
 
         /* Warn if the certificate didn't exist */
         if (!path_exists) {
-            handle_warn(info,
-                        key,
-                        key,
-                        NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE,
-                        _("certificate or key file '%s' does not exist"),
-                        path);
+            read_handle_warn(info,
+                             key,
+                             key,
+                             NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE,
+                             _("certificate or key file '%s' does not exist"),
+                             path);
         }
         return;
     }
@@ -1734,11 +1804,11 @@ cert_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
          * 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,
-                    key,
-                    NM_KEYFILE_WARN_SEVERITY_WARN,
-                    _("invalid key/cert value is not a valid blob"));
+        read_handle_warn(info,
+                         key,
+                         key,
+                         NM_KEYFILE_WARN_SEVERITY_WARN,
+                         _("invalid key/cert value is not a valid blob"));
         return;
     }
 
@@ -1836,12 +1906,12 @@ parity_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
         goto parity_good;
     }
 
-    handle_warn(info,
-                key,
-                key,
-                NM_KEYFILE_WARN_SEVERITY_WARN,
-                _("invalid parity value '%s'"),
-                tmp_str ?: "");
+    read_handle_warn(info,
+                     key,
+                     key,
+                     NM_KEYFILE_WARN_SEVERITY_WARN,
+                     _("invalid parity value '%s'"),
+                     tmp_str ?: "");
     return;
 
 parity_good:
@@ -1858,12 +1928,12 @@ out_err:
         /* ignore such errors. The key is not present. */
         return;
     }
-    handle_warn(info,
-                key,
-                key,
-                NM_KEYFILE_WARN_SEVERITY_WARN,
-                _("invalid setting: %s"),
-                err->message);
+    read_handle_warn(info,
+                     key,
+                     key,
+                     NM_KEYFILE_WARN_SEVERITY_WARN,
+                     _("invalid setting: %s"),
+                     err->message);
 }
 
 static void
@@ -1878,12 +1948,12 @@ team_config_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
     g_object_set(G_OBJECT(setting), key, conf, NULL);
 
     if (conf && !nm_setting_verify(setting, NULL, &error)) {
-        handle_warn(info,
-                    key,
-                    key,
-                    NM_KEYFILE_WARN_SEVERITY_WARN,
-                    _("ignoring invalid team configuration: %s"),
-                    error->message);
+        read_handle_warn(info,
+                         key,
+                         key,
+                         NM_KEYFILE_WARN_SEVERITY_WARN,
+                         _("ignoring invalid team configuration: %s"),
+                         error->message);
         g_object_set(G_OBJECT(setting), key, NULL, NULL);
     }
 }
@@ -1909,12 +1979,12 @@ bridge_vlan_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
         for (iter = strv; *iter; iter++) {
             vlan = nm_bridge_vlan_from_str(*iter, &local);
             if (!vlan) {
-                handle_warn(info,
-                            key,
-                            key,
-                            NM_KEYFILE_WARN_SEVERITY_WARN,
-                            "invalid bridge VLAN: %s",
-                            local->message);
+                read_handle_warn(info,
+                                 key,
+                                 key,
+                                 NM_KEYFILE_WARN_SEVERITY_WARN,
+                                 "invalid bridge VLAN: %s",
+                                 local->message);
                 g_clear_error(&local);
                 continue;
             }
@@ -1961,12 +2031,12 @@ qdisc_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
 
         qdisc = nm_utils_tc_qdisc_from_str(qdisc_str, &err);
         if (!qdisc) {
-            handle_warn(info,
-                        keys[i],
-                        key,
-                        NM_KEYFILE_WARN_SEVERITY_WARN,
-                        _("invalid qdisc: %s"),
-                        err->message);
+            read_handle_warn(info,
+                             keys[i],
+                             key,
+                             NM_KEYFILE_WARN_SEVERITY_WARN,
+                             _("invalid qdisc: %s"),
+                             err->message);
         } else {
             g_ptr_array_add(qdiscs, qdisc);
         }
@@ -2011,12 +2081,12 @@ tfilter_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
 
         tfilter = nm_utils_tc_tfilter_from_str(tfilter_str, &err);
         if (!tfilter) {
-            handle_warn(info,
-                        keys[i],
-                        key,
-                        NM_KEYFILE_WARN_SEVERITY_WARN,
-                        _("invalid tfilter: %s"),
-                        err->message);
+            read_handle_warn(info,
+                             keys[i],
+                             key,
+                             NM_KEYFILE_WARN_SEVERITY_WARN,
+                             _("invalid tfilter: %s"),
+                             err->message);
         } else {
             g_ptr_array_add(tfilters, tfilter);
         }
@@ -2689,16 +2759,10 @@ cert_writer_default(NMConnection                     *connection,
 static void
 cert_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value)
 {
-    const NMSetting8021xSchemeVtable *vtable = NULL;
+    const NMSetting8021xSchemeVtable *vtable;
     const char                       *setting_name;
-    guint                             i;
 
-    for (i = 0; nm_setting_8021x_scheme_vtable[i].setting_key; i++) {
-        if (nm_streq0(nm_setting_8021x_scheme_vtable[i].setting_key, key)) {
-            vtable = &nm_setting_8021x_scheme_vtable[i];
-            break;
-        }
-    }
+    vtable = nm_setting_8021x_scheme_vtable_by_setting_key(key);
     if (!vtable)
         g_return_if_reached();
 
@@ -3181,12 +3245,12 @@ read_one_setting_value(KeyfileReaderInfo        *info,
         && !nm_keyfile_plugin_kf_has_key(keyfile, setting_info->setting_name, key, &err)) {
         /* Key doesn't exist or an error occurred, thus nothing to do. */
         if (err) {
-            if (!handle_warn(info,
-                             key,
-                             key,
-                             NM_KEYFILE_WARN_SEVERITY_WARN,
-                             _("error loading setting value: %s"),
-                             err->message))
+            if (!read_handle_warn(info,
+                                  key,
+                                  key,
+                                  NM_KEYFILE_WARN_SEVERITY_WARN,
+                                  _("error loading setting value: %s"),
+                                  err->message))
                 return;
         }
         return;
@@ -3296,7 +3360,7 @@ read_one_setting_value(KeyfileReaderInfo        *info,
 
             if (val > 255u) {
                 if (!already_warned
-                    && !handle_warn(
+                    && !read_handle_warn(
                         info,
                         key,
                         key,
@@ -3356,12 +3420,12 @@ read_one_setting_value(KeyfileReaderInfo        *info,
         if (nm_keyfile_error_is_not_found(err)) {
             /* ignore such errors. The key is not present. */
         } else {
-            handle_warn(info,
-                        key,
-                        key,
-                        NM_KEYFILE_WARN_SEVERITY_WARN,
-                        _("invalid setting: %s"),
-                        err->message);
+            read_handle_warn(info,
+                             key,
+                             key,
+                             NM_KEYFILE_WARN_SEVERITY_WARN,
+                             _("invalid setting: %s"),
+                             err->message);
         }
     }
 }
@@ -3381,12 +3445,12 @@ _read_setting(KeyfileReaderInfo *info)
 
     type = nm_setting_lookup_type(alias);
     if (!type) {
-        handle_warn(info,
-                    NULL,
-                    NULL,
-                    NM_KEYFILE_WARN_SEVERITY_WARN,
-                    _("invalid setting name '%s'"),
-                    info->group);
+        read_handle_warn(info,
+                         NULL,
+                         NULL,
+                         NM_KEYFILE_WARN_SEVERITY_WARN,
+                         _("invalid setting name '%s'"),
+                         info->group);
         return;
     }
 
@@ -3428,13 +3492,13 @@ _read_setting(KeyfileReaderInfo *info)
                 variant_type =
                     sett_info->detail.gendata_info->get_variant_type(sett_info, key, &local);
                 if (!variant_type) {
-                    if (!handle_warn(info,
-                                     key,
-                                     NULL,
-                                     NM_KEYFILE_WARN_SEVERITY_WARN,
-                                     _("invalid key '%s.%s'"),
-                                     info->group,
-                                     key))
+                    if (!read_handle_warn(info,
+                                          key,
+                                          NULL,
+                                          NM_KEYFILE_WARN_SEVERITY_WARN,
+                                          _("invalid key '%s.%s'"),
+                                          info->group,
+                                          key))
                         break;
                     continue;
                 }
@@ -3444,13 +3508,13 @@ _read_setting(KeyfileReaderInfo *info)
 
                     v = g_key_file_get_boolean(info->keyfile, info->group, key, &local);
                     if (local) {
-                        if (!handle_warn(info,
-                                         key,
-                                         key,
-                                         NM_KEYFILE_WARN_SEVERITY_WARN,
-                                         _("key '%s.%s' is not boolean"),
-                                         info->group,
-                                         key))
+                        if (!read_handle_warn(info,
+                                              key,
+                                              key,
+                                              NM_KEYFILE_WARN_SEVERITY_WARN,
+                                              _("key '%s.%s' is not boolean"),
+                                              info->group,
+                                              key))
                             break;
                         continue;
                     }
@@ -3461,13 +3525,13 @@ _read_setting(KeyfileReaderInfo *info)
                     v = g_key_file_get_uint64(info->keyfile, info->group, key, &local);
 
                     if (local) {
-                        if (!handle_warn(info,
-                                         key,
-                                         key,
-                                         NM_KEYFILE_WARN_SEVERITY_WARN,
-                                         _("key '%s.%s' is not a uint32"),
-                                         info->group,
-                                         key))
+                        if (!read_handle_warn(info,
+                                              key,
+                                              key,
+                                              NM_KEYFILE_WARN_SEVERITY_WARN,
+                                              _("key '%s.%s' is not a uint32"),
+                                              info->group,
+                                              key))
                             break;
                         continue;
                     }
@@ -3518,12 +3582,12 @@ _read_setting_wireguard_peer(KeyfileReaderInfo *info)
         || !nm_streq0(str, cstr)) {
         /* the group name must be identical to the normalized(!) key, so that it
          * is uniquely identified. */
-        handle_warn(info,
-                    NULL,
-                    NM_SETTING_WIREGUARD_PEERS,
-                    NM_KEYFILE_WARN_SEVERITY_WARN,
-                    _("invalid peer public key in section '%s'"),
-                    info->group);
+        read_handle_warn(info,
+                         NULL,
+                         NM_SETTING_WIREGUARD_PEERS,
+                         NM_KEYFILE_WARN_SEVERITY_WARN,
+                         _("invalid peer public key in section '%s'"),
+                         info->group);
         return;
     }
     nm_wireguard_peer_set_public_key(peer, cstr, TRUE);
@@ -3533,13 +3597,13 @@ _read_setting_wireguard_peer(KeyfileReaderInfo *info)
     str = nm_keyfile_plugin_kf_get_string(info->keyfile, info->group, key, NULL);
     if (str) {
         if (!nm_wireguard_peer_set_preshared_key(peer, str, FALSE)) {
-            if (!handle_warn(info,
-                             key,
-                             NM_SETTING_WIREGUARD_PEERS,
-                             NM_KEYFILE_WARN_SEVERITY_WARN,
-                             _("key '%s.%s' is not a valid 256 bit key in base64 encoding"),
-                             info->group,
-                             key))
+            if (!read_handle_warn(info,
+                                  key,
+                                  NM_SETTING_WIREGUARD_PEERS,
+                                  NM_KEYFILE_WARN_SEVERITY_WARN,
+                                  _("key '%s.%s' is not a valid 256 bit key in base64 encoding"),
+                                  info->group,
+                                  key))
                 return;
         }
         nm_clear_g_free(&str);
@@ -3556,13 +3620,13 @@ _read_setting_wireguard_peer(KeyfileReaderInfo *info)
                                          NULL);
     if (errno != ENODATA) {
         if (i64 == -1 || !_nm_setting_secret_flags_valid(i64)) {
-            if (!handle_warn(info,
-                             key,
-                             NM_SETTING_WIREGUARD_PEERS,
-                             NM_KEYFILE_WARN_SEVERITY_WARN,
-                             _("key '%s.%s' is not a valid secret flag"),
-                             info->group,
-                             key))
+            if (!read_handle_warn(info,
+                                  key,
+                                  NM_SETTING_WIREGUARD_PEERS,
+                                  NM_KEYFILE_WARN_SEVERITY_WARN,
+                                  _("key '%s.%s' is not a valid secret flag"),
+                                  info->group,
+                                  key))
                 return;
         } else
             nm_wireguard_peer_set_preshared_key_flags(peer, i64);
@@ -3579,13 +3643,13 @@ _read_setting_wireguard_peer(KeyfileReaderInfo *info)
                                          NULL);
     if (errno != ENODATA) {
         if (i64 == -1) {
-            if (!handle_warn(info,
-                             key,
-                             NM_SETTING_WIREGUARD_PEERS,
-                             NM_KEYFILE_WARN_SEVERITY_WARN,
-                             _("key '%s.%s' is not a integer in range 0 to 2^32"),
-                             info->group,
-                             key))
+            if (!read_handle_warn(info,
+                                  key,
+                                  NM_SETTING_WIREGUARD_PEERS,
+                                  NM_KEYFILE_WARN_SEVERITY_WARN,
+                                  _("key '%s.%s' is not a integer in range 0 to 2^32"),
+                                  info->group,
+                                  key))
                 return;
         } else
             nm_wireguard_peer_set_persistent_keepalive(peer, i64);
@@ -3595,13 +3659,13 @@ _read_setting_wireguard_peer(KeyfileReaderInfo *info)
     str = nm_keyfile_plugin_kf_get_string(info->keyfile, info->group, key, NULL);
     if (str && str[0]) {
         if (!nm_wireguard_peer_set_endpoint(peer, str, FALSE)) {
-            if (!handle_warn(info,
-                             key,
-                             NM_SETTING_WIREGUARD_PEERS,
-                             NM_KEYFILE_WARN_SEVERITY_WARN,
-                             _("key '%s.%s' is not a valid endpoint"),
-                             info->group,
-                             key))
+            if (!read_handle_warn(info,
+                                  key,
+                                  NM_SETTING_WIREGUARD_PEERS,
+                                  NM_KEYFILE_WARN_SEVERITY_WARN,
+                                  _("key '%s.%s' is not a valid endpoint"),
+                                  info->group,
+                                  key))
                 return;
         }
     }
@@ -3621,13 +3685,13 @@ _read_setting_wireguard_peer(KeyfileReaderInfo *info)
             nm_wireguard_peer_append_allowed_ip(peer, sa[i], TRUE);
         }
         if (has_error) {
-            if (!handle_warn(info,
-                             key,
-                             NM_SETTING_WIREGUARD_PEERS,
-                             NM_KEYFILE_WARN_SEVERITY_WARN,
-                             _("key '%s.%s' has invalid allowed-ips"),
-                             info->group,
-                             key))
+            if (!read_handle_warn(info,
+                                  key,
+                                  NM_SETTING_WIREGUARD_PEERS,
+                                  NM_KEYFILE_WARN_SEVERITY_WARN,
+                                  _("key '%s.%s' has invalid allowed-ips"),
+                                  info->group,
+                                  key))
                 return;
         }
     }
@@ -3636,13 +3700,13 @@ _read_setting_wireguard_peer(KeyfileReaderInfo *info)
         return;
 
     if (!nm_wireguard_peer_is_valid(peer, TRUE, TRUE, &error)) {
-        handle_warn(info,
-                    NULL,
-                    NM_SETTING_WIREGUARD_PEERS,
-                    NM_KEYFILE_WARN_SEVERITY_WARN,
-                    _("peer '%s' is invalid: %s"),
-                    info->group,
-                    error->message);
+        read_handle_warn(info,
+                         NULL,
+                         NM_SETTING_WIREGUARD_PEERS,
+                         NM_KEYFILE_WARN_SEVERITY_WARN,
+                         _("peer '%s' is invalid: %s"),
+                         info->group,
+                         error->message);
         return;
     }
 
@@ -3855,29 +3919,24 @@ write_setting_value(KeyfileWriterInfo        *info,
 
     _parse_info_find(setting, key, &setting_info, NULL, &pip);
 
+    if (pip && pip->has_writer_full) {
+        pip->writer_full(info, setting_info, property_info, pip, setting);
+        return;
+    }
+
+    if (pip && pip->writer_skip)
+        return;
+
     if (!pip) {
         if (!setting_info) {
-            /* the setting type is unknown. That is highly unexpected
-             * (and as this is currently only called from NetworkManager
-             * daemon, not possible).
-             *
-             * Still, handle it gracefully, because later keyfile writer will become
-             * public API of libnm, where @setting is (untrusted) user input.
-             *
-             * Gracefully here just means: ignore the setting. */
+            /* the setting type is unknown. Handle this gracefully by
+             * ignoring the setting. */
             return;
         }
         if (!property_info->param_spec)
             return;
         if (nm_streq(key, NM_SETTING_NAME))
             return;
-    } else {
-        if (pip->has_writer_full) {
-            pip->writer_full(info, setting_info, property_info, pip, setting);
-            return;
-        }
-        if (pip->writer_skip)
-            return;
     }
 
     nm_assert(property_info->param_spec);
@@ -4089,8 +4148,10 @@ _write_setting_wireguard(NMSetting *setting, KeyfileWriterInfo *info)
  * @user_data: argument for @handler.
  * @error: the #GError in case writing fails.
  *
- * @connection must verify as a valid profile according to
- * nm_connection_verify().
+ * @connection should verify as a valid profile according to
+ * nm_connection_verify(). If it does not verify, the keyfile may
+ * be incomplete and the parser may not be able to fully recreate
+ * the original profile.
  *
  * Returns: (transfer full): a new #GKeyFile or %NULL on error.
  *
@@ -4104,7 +4165,6 @@ nm_keyfile_write(NMConnection         *connection,
                  GError              **error)
 {
     nm_auto_unref_keyfile GKeyFile *keyfile = NULL;
-    GError                         *local   = NULL;
     KeyfileWriterInfo               info;
     NMSetting                     **settings;
     int                             i;
@@ -4114,28 +4174,6 @@ nm_keyfile_write(NMConnection         *connection,
     g_return_val_if_fail(!error || !*error, NULL);
     g_return_val_if_fail(handler_flags == NM_KEYFILE_HANDLER_FLAGS_NONE, NULL);
 
-    /* Technically, we might not require that a profile is valid in
-     * order to serialize it. Like also nm_keyfile_read() does not
-     * ensure that the read profile validates.
-     *
-     * However, if the profile does not validate, then there might be
-     * unexpected edge cases when we try to serialize it. Edge cases
-     * that might result in dangerous crash.
-     *
-     * So, for now we require valid profiles. */
-    if (!nm_connection_verify(connection, error ? &local : NULL)) {
-        if (error) {
-            g_set_error(error,
-                        NM_CONNECTION_ERROR,
-                        NM_CONNECTION_ERROR_FAILED,
-                        _("the profile is not valid: %s"),
-                        local->message);
-            g_error_free(local);
-        } else
-            nm_assert(!local);
-        return NULL;
-    }
-
     keyfile = g_key_file_new();
 
     info = (KeyfileWriterInfo){
@@ -4189,11 +4227,16 @@ nm_keyfile_write(NMConnection         *connection,
                                               key,
                                               (guint64) g_variant_get_uint32(v));
                     } else {
-                        /* BUG: The variant type is not implemented. Since the connection
-                         * verifies, this can only mean we either wrongly didn't reject
-                         * the connection as invalid, or we didn't properly implement the
-                         * variant type. */
-                        nm_assert_not_reached();
+                        if (!write_handle_warn(&info,
+                                               setting,
+                                               NULL,
+                                               key,
+                                               NM_KEYFILE_WARN_SEVERITY_WARN,
+                                               _("unsupported option \"%s.%s\" of variant type %s"),
+                                               setting_name,
+                                               key,
+                                               g_variant_get_type_string(v)))
+                            goto out_with_info_error;
                         continue;
                     }
                 }
diff --git a/src/libnm-core-impl/nm-meta-setting-base-impl.c b/src/libnm-core-impl/nm-meta-setting-base-impl.c
index cd55779f..69daa76c 100644
--- a/src/libnm-core-impl/nm-meta-setting-base-impl.c
+++ b/src/libnm-core-impl/nm-meta-setting-base-impl.c
@@ -153,6 +153,68 @@ const NMSetting8021xSchemeVtable nm_setting_8021x_scheme_vtable[] = {
 #undef _D
 };
 
+const NMSetting8021xSchemeVtable *
+nm_setting_8021x_scheme_vtable_by_setting_key(const char *key)
+{
+    static const NMSetting8021xSchemeType sorted_index[] = {
+        NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT,
+        NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT,
+        NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT,
+        NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT,
+        NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY,
+        NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY,
+    };
+    int imin, imax;
+
+    nm_assert(key);
+
+    if (NM_MORE_ASSERT_ONCE(5)) {
+        const NMSetting8021xSchemeVtable *vtable_prev = NULL;
+        int                               i, j;
+
+        for (i = 0; i < (int) G_N_ELEMENTS(sorted_index); i++) {
+            const NMSetting8021xSchemeType    t = sorted_index[i];
+            const NMSetting8021xSchemeVtable *vtable;
+
+            nm_assert(_NM_INT_NOT_NEGATIVE(t));
+            nm_assert(t < G_N_ELEMENTS(nm_setting_8021x_scheme_vtable) - 1);
+
+            for (j = 0; j < i; j++)
+                nm_assert(t != sorted_index[j]);
+
+            vtable = &nm_setting_8021x_scheme_vtable[t];
+
+            nm_assert(vtable->scheme_type == t);
+            nm_assert(vtable->setting_key);
+
+            if (vtable_prev)
+                nm_assert(strcmp(vtable_prev->setting_key, vtable->setting_key) < 0);
+            vtable_prev = vtable;
+        }
+    }
+
+    imin = 0;
+    imax = G_N_ELEMENTS(sorted_index) - 1;
+    while (imin <= imax) {
+        const NMSetting8021xSchemeVtable *vtable;
+        const int                         imid = imin + (imax - imin) / 2;
+        int                               cmp;
+
+        vtable = &nm_setting_8021x_scheme_vtable[sorted_index[imid]];
+
+        cmp = strcmp(vtable->setting_key, key);
+        if (cmp == 0)
+            return vtable;
+
+        if (cmp < 0)
+            imin = imid + 1;
+        else
+            imax = imid - 1;
+    }
+
+    return NULL;
+}
+
 /*****************************************************************************/
 
 const NMMetaSettingInfo nm_meta_setting_infos[] = {
diff --git a/src/libnm-core-impl/nm-setting-8021x.c b/src/libnm-core-impl/nm-setting-8021x.c
index 884f8830..41feae57 100644
--- a/src/libnm-core-impl/nm-setting-8021x.c
+++ b/src/libnm-core-impl/nm-setting-8021x.c
@@ -9,8 +9,8 @@
 #include "nm-setting-8021x.h"
 
 #include "libnm-glib-aux/nm-secret-utils.h"
+#include "libnm-crypto/nm-crypto.h"
 #include "nm-utils.h"
-#include "nm-crypto.h"
 #include "nm-utils-private.h"
 #include "nm-setting-private.h"
 #include "nm-core-enum-types.h"
@@ -516,7 +516,7 @@ _cert_impl_set(NMSetting8021x         *setting,
         gs_unref_bytes GBytes *file = NULL;
 
         if (NM_IN_SET(property, PROP_PRIVATE_KEY, PROP_PHASE2_PRIVATE_KEY)) {
-            file = nm_crypto_read_file(value, error);
+            file = nm_utils_read_crypto_file_to_bytes(value, error);
             if (!file)
                 goto err;
             format = nm_crypto_verify_private_key_data(g_bytes_get_data(file, NULL),
@@ -2520,230 +2520,172 @@ need_secrets_sim(NMSetting8021x *self, GPtrArray *secrets, gboolean phase2)
         g_ptr_array_add(secrets, NM_SETTING_802_1X_PIN);
 }
 
-static gboolean
-need_private_key_password(GBytes                *blob,
-                          NMSetting8021xCKScheme scheme,
-                          const char            *path,
-                          const char            *password,
-                          NMSettingSecretFlags   flags)
-{
-    NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN;
-
-    if (flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)
-        return FALSE;
-
-    /* Private key password is required */
-    if (password) {
-        if (path)
-            format = nm_crypto_verify_private_key(path, password, NULL, NULL);
-        else if (blob)
-            format = nm_crypto_verify_private_key_data(g_bytes_get_data(blob, NULL),
-                                                       g_bytes_get_size(blob),
-                                                       password,
-                                                       NULL,
-                                                       NULL);
-        else
-            return FALSE;
-    }
-
-    return (format == NM_CRYPTO_FILE_FORMAT_UNKNOWN);
-}
-
 static void
 need_secrets_tls(NMSetting8021x *self, GPtrArray *secrets, gboolean phase2)
 {
     NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(self);
     NMSetting8021xCKScheme scheme;
-    GBytes                *blob = NULL;
-    const char            *path = NULL;
-
-    if (phase2) {
-        scheme = nm_setting_802_1x_get_phase2_private_key_scheme(self);
-        if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH)
-            path = nm_setting_802_1x_get_phase2_private_key_path(self);
-        else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB)
-            blob = nm_setting_802_1x_get_phase2_private_key_blob(self);
-        else if (scheme != NM_SETTING_802_1X_CK_SCHEME_PKCS11)
-            g_warning("%s: unknown phase2 private key scheme %d", __func__, scheme);
-
-        if (need_private_key_password(blob,
-                                      scheme,
-                                      path,
-                                      priv->phase2_private_key_password,
-                                      priv->phase2_private_key_password_flags))
-            g_ptr_array_add(secrets, NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD);
-
-        scheme = nm_setting_802_1x_get_phase2_ca_cert_scheme(self);
-        if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11
-            && !(priv->phase2_ca_cert_password_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)
-            && !priv->phase2_ca_cert_password)
-            g_ptr_array_add(secrets, NM_SETTING_802_1X_PHASE2_CA_CERT_PASSWORD);
 
-        scheme = nm_setting_802_1x_get_phase2_client_cert_scheme(self);
-        if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11
-            && !(priv->phase2_client_cert_password_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)
-            && !priv->phase2_client_cert_password)
-            g_ptr_array_add(secrets, NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD);
-    } else {
-        scheme = nm_setting_802_1x_get_private_key_scheme(self);
-        if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH)
-            path = nm_setting_802_1x_get_private_key_path(self);
-        else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB)
-            blob = nm_setting_802_1x_get_private_key_blob(self);
-        else if (scheme != NM_SETTING_802_1X_CK_SCHEME_PKCS11)
-            g_warning("%s: unknown private key scheme %d", __func__, scheme);
-
-        if (need_private_key_password(blob,
-                                      scheme,
-                                      path,
-                                      priv->private_key_password,
-                                      priv->private_key_password_flags))
-            g_ptr_array_add(secrets, NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD);
-
-        scheme = nm_setting_802_1x_get_ca_cert_scheme(self);
+    if (!NM_FLAGS_HAS(phase2 ? priv->phase2_private_key_password_flags
+                             : priv->private_key_password_flags,
+                      NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) {
+        NMCryptoFileFormat format       = NM_CRYPTO_FILE_FORMAT_UNKNOWN;
+        gboolean           has_password = FALSE;
+        const char        *password;
+
+        password = phase2 ? priv->phase2_private_key_password : priv->private_key_password;
+
+        /* Check whether the password works. */
+        if (password) {
+            scheme = phase2 ? nm_setting_802_1x_get_phase2_private_key_scheme(self)
+                            : nm_setting_802_1x_get_private_key_scheme(self);
+
+            if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+                const char *path = phase2 ? nm_setting_802_1x_get_phase2_private_key_path(self)
+                                          : nm_setting_802_1x_get_private_key_path(self);
+
+                if (path)
+                    format = nm_crypto_verify_private_key(path, password, NULL, NULL);
+            } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
+                GBytes *blob = phase2 ? nm_setting_802_1x_get_phase2_private_key_blob(self)
+                                      : nm_setting_802_1x_get_private_key_blob(self);
+
+                if (blob)
+                    format = nm_crypto_verify_private_key_data(g_bytes_get_data(blob, NULL),
+                                                               g_bytes_get_size(blob),
+                                                               password,
+                                                               NULL,
+                                                               NULL);
+            } else {
+                /* For PKCS#11 URLS, we assume the password is correct. */
+                has_password = TRUE;
+            }
+        }
+        if (!has_password && format == NM_CRYPTO_FILE_FORMAT_UNKNOWN) {
+            g_ptr_array_add(secrets,
+                            phase2 ? NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD
+                                   : NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD);
+        }
+    }
+
+    if (!NM_FLAGS_HAS(phase2 ? priv->phase2_ca_cert_password_flags : priv->ca_cert_password_flags,
+                      NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) {
+        scheme = phase2 ? nm_setting_802_1x_get_phase2_ca_cert_scheme(self)
+                        : nm_setting_802_1x_get_ca_cert_scheme(self);
         if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11
-            && !(priv->ca_cert_password_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)
-            && !priv->ca_cert_password)
-            g_ptr_array_add(secrets, NM_SETTING_802_1X_CA_CERT_PASSWORD);
+            && !(phase2 ? priv->phase2_ca_cert_password : priv->ca_cert_password)) {
+            g_ptr_array_add(secrets,
+                            phase2 ? NM_SETTING_802_1X_PHASE2_CA_CERT_PASSWORD
+                                   : NM_SETTING_802_1X_CA_CERT_PASSWORD);
+        }
+    }
 
-        scheme = nm_setting_802_1x_get_client_cert_scheme(self);
+    if (!NM_FLAGS_HAS(phase2 ? priv->phase2_client_cert_password_flags
+                             : priv->client_cert_password_flags,
+                      NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) {
+        scheme = phase2 ? nm_setting_802_1x_get_phase2_client_cert_scheme(self)
+                        : nm_setting_802_1x_get_client_cert_scheme(self);
         if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11
-            && !(priv->client_cert_password_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)
-            && !priv->client_cert_password)
-            g_ptr_array_add(secrets, NM_SETTING_802_1X_CLIENT_CERT_PASSWORD);
+            && !(phase2 ? priv->phase2_client_cert_password : priv->client_cert_password)) {
+            g_ptr_array_add(secrets,
+                            phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD
+                                   : NM_SETTING_802_1X_CLIENT_CERT_PASSWORD);
+        }
     }
 }
 
 static gboolean
-verify_tls(NMSetting8021x *self, gboolean phase2, GError **error)
+verify_identity(NMSetting8021x *self, gboolean phase2, GError **error)
 {
     NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(self);
 
-    if (phase2) {
-        if (!priv->phase2_client_cert) {
+    if (nm_str_is_empty(priv->identity)) {
+        if (!priv->identity) {
             g_set_error_literal(error,
                                 NM_CONNECTION_ERROR,
                                 NM_CONNECTION_ERROR_MISSING_PROPERTY,
                                 _("property is missing"));
-            g_prefix_error(error,
-                           "%s.%s: ",
-                           NM_SETTING_802_1X_SETTING_NAME,
-                           NM_SETTING_802_1X_PHASE2_CLIENT_CERT);
-            return FALSE;
-        } else if (!g_bytes_get_size(priv->phase2_client_cert)) {
+        } else {
             g_set_error_literal(error,
                                 NM_CONNECTION_ERROR,
                                 NM_CONNECTION_ERROR_INVALID_PROPERTY,
                                 _("property is empty"));
-            g_prefix_error(error,
-                           "%s.%s: ",
-                           NM_SETTING_802_1X_SETTING_NAME,
-                           NM_SETTING_802_1X_PHASE2_CLIENT_CERT);
-            return FALSE;
         }
+        g_prefix_error(error,
+                       "%s.%s: ",
+                       NM_SETTING_802_1X_SETTING_NAME,
+                       NM_SETTING_802_1X_IDENTITY);
+        return FALSE;
+    }
 
-        /* Private key is required for TLS */
-        if (!priv->phase2_private_key) {
-            g_set_error_literal(error,
-                                NM_CONNECTION_ERROR,
-                                NM_CONNECTION_ERROR_MISSING_PROPERTY,
-                                _("property is missing"));
-            g_prefix_error(error,
-                           "%s.%s: ",
-                           NM_SETTING_802_1X_SETTING_NAME,
-                           NM_SETTING_802_1X_PHASE2_PRIVATE_KEY);
-            return FALSE;
-        } else if (!g_bytes_get_size(priv->phase2_private_key)) {
-            g_set_error_literal(error,
-                                NM_CONNECTION_ERROR,
-                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                                _("property is empty"));
-            g_prefix_error(error,
-                           "%s.%s: ",
-                           NM_SETTING_802_1X_SETTING_NAME,
-                           NM_SETTING_802_1X_PHASE2_PRIVATE_KEY);
-            return FALSE;
-        }
+    return TRUE;
+}
 
-        /* If the private key is PKCS#12, check that it matches the client cert */
-        if (nm_crypto_is_pkcs12_data(g_bytes_get_data(priv->phase2_private_key, NULL),
-                                     g_bytes_get_size(priv->phase2_private_key),
-                                     NULL)) {
-            if (!g_bytes_equal(priv->phase2_private_key, priv->phase2_client_cert)) {
-                g_set_error(error,
+static gboolean
+verify_tls(NMSetting8021x *self, gboolean phase2, GError **error)
+{
+    NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(self);
+    GBytes                *client_cert;
+    GBytes                *private_key;
+    const char            *prop_client_cert;
+    const char            *prop_private_key;
+
+    client_cert = phase2 ? priv->phase2_client_cert : priv->client_cert;
+    private_key = phase2 ? priv->phase2_private_key : priv->private_key;
+    prop_client_cert =
+        phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT : NM_SETTING_802_1X_CLIENT_CERT;
+    prop_private_key =
+        phase2 ? NM_SETTING_802_1X_PHASE2_PRIVATE_KEY : NM_SETTING_802_1X_PRIVATE_KEY;
+
+    if (!client_cert) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_MISSING_PROPERTY,
+                            _("property is missing"));
+        g_prefix_error(error, "%s.%s: ", NM_SETTING_802_1X_SETTING_NAME, prop_client_cert);
+        return FALSE;
+    }
+    if (g_bytes_get_size(client_cert) == 0) {
+        g_set_error_literal(error,
                             NM_CONNECTION_ERROR,
                             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                            _("has to match '%s' property for PKCS#12"),
-                            NM_SETTING_802_1X_PHASE2_PRIVATE_KEY);
-                g_prefix_error(error,
-                               "%s.%s: ",
-                               NM_SETTING_802_1X_SETTING_NAME,
-                               NM_SETTING_802_1X_PHASE2_CLIENT_CERT);
-                return FALSE;
-            }
-        }
-    } else {
-        if (!priv->client_cert) {
-            g_set_error_literal(error,
-                                NM_CONNECTION_ERROR,
-                                NM_CONNECTION_ERROR_MISSING_PROPERTY,
-                                _("property is missing"));
-            g_prefix_error(error,
-                           "%s.%s: ",
-                           NM_SETTING_802_1X_SETTING_NAME,
-                           NM_SETTING_802_1X_CLIENT_CERT);
-            return FALSE;
-        } else if (!g_bytes_get_size(priv->client_cert)) {
-            g_set_error_literal(error,
-                                NM_CONNECTION_ERROR,
-                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                                _("property is empty"));
-            g_prefix_error(error,
-                           "%s.%s: ",
-                           NM_SETTING_802_1X_SETTING_NAME,
-                           NM_SETTING_802_1X_CLIENT_CERT);
-            return FALSE;
-        }
+                            _("property is empty"));
+        g_prefix_error(error, "%s.%s: ", NM_SETTING_802_1X_SETTING_NAME, prop_client_cert);
+        return FALSE;
+    }
 
-        /* Private key is required for TLS */
-        if (!priv->private_key) {
-            g_set_error_literal(error,
-                                NM_CONNECTION_ERROR,
-                                NM_CONNECTION_ERROR_MISSING_PROPERTY,
-                                _("property is missing"));
-            g_prefix_error(error,
-                           "%s.%s: ",
-                           NM_SETTING_802_1X_SETTING_NAME,
-                           NM_SETTING_802_1X_PRIVATE_KEY);
-            return FALSE;
-        } else if (!g_bytes_get_size(priv->private_key)) {
-            g_set_error_literal(error,
-                                NM_CONNECTION_ERROR,
-                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                                _("property is empty"));
-            g_prefix_error(error,
-                           "%s.%s: ",
-                           NM_SETTING_802_1X_SETTING_NAME,
-                           NM_SETTING_802_1X_PRIVATE_KEY);
-            return FALSE;
-        }
+    /* Private key is required for TLS */
+    if (!private_key) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_MISSING_PROPERTY,
+                            _("property is missing"));
+        g_prefix_error(error, "%s.%s: ", NM_SETTING_802_1X_SETTING_NAME, prop_private_key);
+        return FALSE;
+    }
 
-        /* If the private key is PKCS#12, check that it matches the client cert */
-        if (nm_crypto_is_pkcs12_data(g_bytes_get_data(priv->private_key, NULL),
-                                     g_bytes_get_size(priv->private_key),
-                                     NULL)) {
-            if (!g_bytes_equal(priv->private_key, priv->client_cert)) {
-                g_set_error(error,
+    if (g_bytes_get_size(private_key) == 0) {
+        g_set_error_literal(error,
                             NM_CONNECTION_ERROR,
                             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                            _("has to match '%s' property for PKCS#12"),
-                            NM_SETTING_802_1X_PRIVATE_KEY);
-                g_prefix_error(error,
-                               "%s.%s: ",
-                               NM_SETTING_802_1X_SETTING_NAME,
-                               NM_SETTING_802_1X_CLIENT_CERT);
-                return FALSE;
-            }
+                            _("property is empty"));
+        g_prefix_error(error, "%s.%s: ", NM_SETTING_802_1X_SETTING_NAME, prop_private_key);
+        return FALSE;
+    }
+
+    if (_nm_setting_802_1x_cert_get_scheme(private_key, NULL) == NM_SETTING_802_1X_CK_SCHEME_BLOB
+        && nm_crypto_is_pkcs12_data(g_bytes_get_data(private_key, NULL),
+                                    g_bytes_get_size(private_key),
+                                    NULL)) {
+        /* If the private key is PKCS#12, check that it matches the client cert */
+        if (!g_bytes_equal(private_key, client_cert)) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("has to match '%s' property for PKCS#12"),
+                        prop_private_key);
+            g_prefix_error(error, "%s.%s: ", NM_SETTING_802_1X_SETTING_NAME, prop_client_cert);
+            return FALSE;
         }
     }
 
@@ -2755,24 +2697,8 @@ verify_ttls(NMSetting8021x *self, gboolean phase2, GError **error)
 {
     NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(self);
 
-    if (nm_str_is_empty(priv->identity)) {
-        if (!priv->identity) {
-            g_set_error_literal(error,
-                                NM_CONNECTION_ERROR,
-                                NM_CONNECTION_ERROR_MISSING_PROPERTY,
-                                _("property is missing"));
-        } else {
-            g_set_error_literal(error,
-                                NM_CONNECTION_ERROR,
-                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                                _("property is empty"));
-        }
-        g_prefix_error(error,
-                       "%s.%s: ",
-                       NM_SETTING_802_1X_SETTING_NAME,
-                       NM_SETTING_802_1X_IDENTITY);
+    if (!verify_identity(self, phase2, error))
         return FALSE;
-    }
 
     if ((!priv->phase2_auth && !priv->phase2_autheap)
         || (priv->phase2_auth && priv->phase2_autheap)) {
@@ -2792,33 +2718,6 @@ verify_ttls(NMSetting8021x *self, gboolean phase2, GError **error)
     return TRUE;
 }
 
-static gboolean
-verify_identity(NMSetting8021x *self, gboolean phase2, GError **error)
-{
-    NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(self);
-
-    if (nm_str_is_empty(priv->identity)) {
-        if (!priv->identity) {
-            g_set_error_literal(error,
-                                NM_CONNECTION_ERROR,
-                                NM_CONNECTION_ERROR_MISSING_PROPERTY,
-                                _("property is missing"));
-        } else {
-            g_set_error_literal(error,
-                                NM_CONNECTION_ERROR,
-                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                                _("property is empty"));
-        }
-        g_prefix_error(error,
-                       "%s.%s: ",
-                       NM_SETTING_802_1X_SETTING_NAME,
-                       NM_SETTING_802_1X_IDENTITY);
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
 static void
 need_secrets_phase2(NMSetting8021x *self, GPtrArray *secrets, gboolean phase2)
 {
@@ -2833,17 +2732,14 @@ need_secrets_phase2(NMSetting8021x *self, GPtrArray *secrets, gboolean phase2)
     if (!method)
         method = priv->phase2_autheap;
 
-    if (!method) {
-        g_warning("Couldn't find EAP method.");
-        g_assert_not_reached();
-        return;
-    }
+    if (!method)
+        g_return_if_reached();
 
     /* Ask the configured phase2 method if it needs secrets */
     for (i = 0; eap_methods_table[i].method; i++) {
-        if (eap_methods_table[i].ns_func == NULL)
+        if (!eap_methods_table[i].ns_func)
             continue;
-        if (!strcmp(eap_methods_table[i].method, method)) {
+        if (nm_streq(eap_methods_table[i].method, method)) {
             (*eap_methods_table[i].ns_func)(self, secrets, TRUE);
             break;
         }
@@ -2929,11 +2825,18 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     }
 
     if (!NM_IN_STRSET(priv->phase1_peapver, NULL, "0", "1")) {
-        g_set_error(error,
-                    NM_CONNECTION_ERROR,
-                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                    _("'%s' is not a valid value for the property"),
-                    priv->phase1_peapver);
+        if (priv->phase1_peapver) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("'%s' is not a valid value for the property"),
+                        priv->phase1_peapver);
+        } else {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("property is empty"));
+        }
         g_prefix_error(error,
                        "%s.%s: ",
                        NM_SETTING_802_1X_SETTING_NAME,
@@ -2942,11 +2845,18 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     }
 
     if (!NM_IN_STRSET(priv->phase1_peaplabel, NULL, "0", "1")) {
-        g_set_error(error,
-                    NM_CONNECTION_ERROR,
-                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                    _("'%s' is not a valid value for the property"),
-                    priv->phase1_peaplabel);
+        if (priv->phase1_peaplabel) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("'%s' is not a valid value for the property"),
+                        priv->phase1_peaplabel);
+        } else {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("property is empty"));
+        }
         g_prefix_error(error,
                        "%s.%s: ",
                        NM_SETTING_802_1X_SETTING_NAME,
@@ -2955,11 +2865,18 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     }
 
     if (!NM_IN_STRSET(priv->phase1_fast_provisioning, NULL, "0", "1", "2", "3")) {
-        g_set_error(error,
-                    NM_CONNECTION_ERROR,
-                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                    _("'%s' is not a valid value for the property"),
-                    priv->phase1_fast_provisioning);
+        if (priv->phase1_fast_provisioning) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("'%s' is not a valid value for the property"),
+                        priv->phase1_fast_provisioning);
+        } else {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("property is empty"));
+        }
         g_prefix_error(error,
                        "%s.%s: ",
                        NM_SETTING_802_1X_SETTING_NAME,
@@ -2989,11 +2906,18 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
                       "otp",
                       "md5",
                       "tls")) {
-        g_set_error(error,
-                    NM_CONNECTION_ERROR,
-                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                    _("'%s' is not a valid value for the property"),
-                    priv->phase2_auth);
+        if (priv->phase2_auth) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("'%s' is not a valid value for the property"),
+                        priv->phase2_auth);
+        } else {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("property is empty"));
+        }
         g_prefix_error(error,
                        "%s.%s: ",
                        NM_SETTING_802_1X_SETTING_NAME,
@@ -3002,11 +2926,18 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     }
 
     if (!NM_IN_STRSET(priv->phase2_autheap, NULL, "md5", "mschapv2", "otp", "gtc", "tls")) {
-        g_set_error(error,
-                    NM_CONNECTION_ERROR,
-                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                    _("'%s' is not a valid value for the property"),
-                    priv->phase2_autheap);
+        if (priv->phase2_autheap) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("'%s' is not a valid value for the property"),
+                        priv->phase2_autheap);
+        } else {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("property is empty"));
+        }
         g_prefix_error(error,
                        "%s.%s: ",
                        NM_SETTING_802_1X_SETTING_NAME,
@@ -3049,6 +2980,50 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
                                error))
         return FALSE;
 
+        /* normalizable warnings from here on. */
+
+#define _check_strempty_and_return(priv, prop_name, field, error)                               \
+    G_STMT_START                                                                                \
+    {                                                                                           \
+        NMSetting8021xPrivate *_priv  = (priv);                                                 \
+        GError               **_error = (error);                                                \
+                                                                                                \
+        if (_priv->field && _priv->field[0] == '\0') {                                          \
+            g_set_error(_error,                                                                 \
+                        NM_CONNECTION_ERROR,                                                    \
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,                                   \
+                        _("property is empty"));                                                \
+            g_prefix_error(_error, "%s.%s: ", NM_SETTING_802_1X_SETTING_NAME, "" prop_name ""); \
+            return NM_SETTING_VERIFY_NORMALIZABLE;                                              \
+        }                                                                                       \
+    }                                                                                           \
+    G_STMT_END
+
+    _check_strempty_and_return(priv, NM_SETTING_802_1X_IDENTITY, identity, error);
+    _check_strempty_and_return(priv,
+                               NM_SETTING_802_1X_ANONYMOUS_IDENTITY,
+                               anonymous_identity,
+                               error);
+    _check_strempty_and_return(priv, NM_SETTING_802_1X_PAC_FILE, pac_file, error);
+    _check_strempty_and_return(priv, NM_SETTING_802_1X_SUBJECT_MATCH, subject_match, error);
+    _check_strempty_and_return(priv,
+                               NM_SETTING_802_1X_PHASE2_SUBJECT_MATCH,
+                               phase2_subject_match,
+                               error);
+    _check_strempty_and_return(priv,
+                               NM_SETTING_802_1X_DOMAIN_SUFFIX_MATCH,
+                               domain_suffix_match,
+                               error);
+    _check_strempty_and_return(priv,
+                               NM_SETTING_802_1X_PHASE2_DOMAIN_SUFFIX_MATCH,
+                               phase2_domain_suffix_match,
+                               error);
+    _check_strempty_and_return(priv, NM_SETTING_802_1X_DOMAIN_MATCH, domain_match, error);
+    _check_strempty_and_return(priv,
+                               NM_SETTING_802_1X_PHASE2_DOMAIN_MATCH,
+                               phase2_domain_match,
+                               error);
+
     return TRUE;
 }
 
diff --git a/src/libnm-core-impl/nm-setting-connection.c b/src/libnm-core-impl/nm-setting-connection.c
index 73611004..0d7d7cd5 100644
--- a/src/libnm-core-impl/nm-setting-connection.c
+++ b/src/libnm-core-impl/nm-setting-connection.c
@@ -71,32 +71,32 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMSettingConnection,
                              PROP_MUD_URL, );
 
 typedef struct {
-    GArray *permissions;
-    GArray *secondaries;
-    char   *id;
-    char   *uuid;
-    char   *stable_id;
-    char   *interface_name;
-    char   *type;
-    char   *master;
-    char   *slave_type;
-    char   *zone;
-    char   *mud_url;
-    guint64 timestamp;
-    int     autoconnect_slaves;
-    int     metered;
-    gint32  autoconnect_priority;
-    gint32  autoconnect_retries;
-    gint32  multi_connect;
-    gint32  auth_retries;
-    gint32  mdns;
-    gint32  llmnr;
-    gint32  dns_over_tls;
-    gint32  wait_device_timeout;
-    gint32  lldp;
-    guint32 gateway_ping_timeout;
-    bool    autoconnect;
-    bool    read_only;
+    GArray     *permissions;
+    NMValueStrv secondaries;
+    char       *id;
+    char       *uuid;
+    char       *stable_id;
+    char       *interface_name;
+    char       *type;
+    char       *master;
+    char       *slave_type;
+    char       *zone;
+    char       *mud_url;
+    guint64     timestamp;
+    int         autoconnect_slaves;
+    int         metered;
+    gint32      autoconnect_priority;
+    gint32      autoconnect_retries;
+    gint32      multi_connect;
+    gint32      auth_retries;
+    gint32      mdns;
+    gint32      llmnr;
+    gint32      dns_over_tls;
+    gint32      wait_device_timeout;
+    gint32      lldp;
+    guint32     gateway_ping_timeout;
+    bool        autoconnect;
+    bool        read_only;
 } NMSettingConnectionPrivate;
 
 /**
@@ -757,7 +757,7 @@ nm_setting_connection_get_autoconnect_slaves(NMSettingConnection *setting)
 GArray *
 _nm_setting_connection_get_secondaries(NMSettingConnection *setting)
 {
-    return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->secondaries;
+    return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->secondaries.arr;
 }
 
 /**
@@ -771,7 +771,7 @@ nm_setting_connection_get_num_secondaries(NMSettingConnection *setting)
 {
     g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), 0);
 
-    return nm_g_array_len(NM_SETTING_CONNECTION_GET_PRIVATE(setting)->secondaries);
+    return nm_g_array_len(NM_SETTING_CONNECTION_GET_PRIVATE(setting)->secondaries.arr);
 }
 
 /**
@@ -794,14 +794,14 @@ nm_setting_connection_get_secondary(NMSettingConnection *setting, guint32 idx)
 
     priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
 
-    secondaries_len = nm_g_array_len(priv->secondaries);
+    secondaries_len = nm_g_array_len(priv->secondaries.arr);
     if (idx >= secondaries_len) {
         /* access one past the length is OK. */
         g_return_val_if_fail(idx == secondaries_len, NULL);
         return NULL;
     }
 
-    return nm_strvarray_get_idx(priv->secondaries, idx);
+    return nm_strvarray_get_idx(priv->secondaries.arr, idx);
 }
 
 /**
@@ -841,10 +841,10 @@ nm_setting_connection_add_secondary(NMSettingConnection *setting, const char *se
 
     priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
 
-    if (nm_strvarray_find_first(priv->secondaries, sec_uuid) >= 0)
+    if (nm_strvarray_find_first(priv->secondaries.arr, sec_uuid) >= 0)
         return FALSE;
 
-    nm_strvarray_add(nm_strvarray_ensure(&priv->secondaries), sec_uuid);
+    nm_strvarray_add(nm_strvarray_ensure(&priv->secondaries.arr), sec_uuid);
     _notify(setting, PROP_SECONDARIES);
     return TRUE;
 }
@@ -865,9 +865,9 @@ nm_setting_connection_remove_secondary(NMSettingConnection *setting, guint32 idx
 
     priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
 
-    g_return_if_fail(idx < nm_g_array_len(priv->secondaries));
+    g_return_if_fail(idx < nm_g_array_len(priv->secondaries.arr));
 
-    g_array_remove_index(priv->secondaries, idx);
+    g_array_remove_index(priv->secondaries.arr, idx);
     _notify(setting, PROP_SECONDARIES);
 }
 
@@ -890,7 +890,7 @@ nm_setting_connection_remove_secondary_by_value(NMSettingConnection *setting, co
 
     priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
 
-    if (nm_strvarray_remove_first(priv->secondaries, sec_uuid)) {
+    if (nm_strvarray_remove_first(priv->secondaries.arr, sec_uuid)) {
         _notify(setting, PROP_SECONDARIES);
         return TRUE;
     }
@@ -1524,7 +1524,7 @@ after_interface_name:
         return NM_SETTING_VERIFY_NORMALIZABLE;
     }
 
-    if (!_nm_setting_connection_verify_secondaries(priv->secondaries, error))
+    if (!_nm_setting_connection_verify_secondaries(priv->secondaries.arr, error))
         return NM_SETTING_VERIFY_NORMALIZABLE;
 
     return TRUE;
@@ -1637,9 +1637,6 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
     case PROP_TIMESTAMP:
         g_value_set_uint64(value, nm_setting_connection_get_timestamp(setting));
         break;
-    case PROP_SECONDARIES:
-        g_value_take_boxed(value, nm_strvarray_get_strv_non_empty_dup(priv->secondaries, NULL));
-        break;
     default:
         _nm_setting_property_get_property_direct(object, prop_id, value, pspec);
         break;
@@ -1675,9 +1672,6 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
     case PROP_TIMESTAMP:
         priv->timestamp = g_value_get_uint64(value);
         break;
-    case PROP_SECONDARIES:
-        nm_strvarray_set_strv(&priv->secondaries, g_value_get_boxed(value));
-        break;
     default:
         _nm_setting_property_set_property_direct(object, prop_id, value, pspec);
         break;
@@ -1709,7 +1703,7 @@ finalize(GObject *object)
     NMSettingConnectionPrivate *priv = NM_SETTING_CONNECTION_GET_PRIVATE(object);
 
     nm_clear_pointer(&priv->permissions, g_array_unref);
-    nm_clear_pointer(&priv->secondaries, g_array_unref);
+    nm_clear_pointer(&priv->secondaries.arr, g_array_unref);
 
     G_OBJECT_CLASS(nm_setting_connection_parent_class)->finalize(object);
 }
@@ -1808,7 +1802,8 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
      * is commonly also included, so that different systems end up generating
      * different IDs. Or with ipv6.addr-gen-mode=stable-privacy, also the device's
      * name is included, so that different interfaces yield different addresses.
-     * The per-host key is the identity of your machine and stored in /var/lib/NetworkManager/secret-key.
+     * The per-host key is the identity of your machine and stored in /var/lib/NetworkManager/secret_key.
+     * See NetworkManager(8) manual about the secret-key and the host identity.
      *
      * The '$' character is treated special to perform dynamic substitutions
      * at runtime. Currently, supported are "${CONNECTION}", "${DEVICE}", "${MAC}",
@@ -1958,6 +1953,15 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
      * Note that autoconnect is not implemented for VPN profiles. See
      * #NMSettingConnection:secondaries as an alternative to automatically
      * connect VPN profiles.
+     *
+     * If multiple profiles are ready to autoconnect on the same device,
+     * the one with the better "connection.autoconnect-priority" is chosen. If
+     * the priorities are equal, then the most recently connected profile is activated.
+     * If the profiles were not connected earlier or their
+     * "connection.timestamp" is identical, the choice is undefined.
+     *
+     * Depending on "connection.multi-connect", a profile can (auto)connect only
+     * once at a time or multiple times.
      **/
     /* ---ifcfg-rh---
      * property: autoconnect
@@ -2230,12 +2234,13 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
      *   together with this connection.
      * ---end---
      */
-    obj_properties[PROP_SECONDARIES] = g_param_spec_boxed(
-        NM_SETTING_CONNECTION_SECONDARIES,
-        "",
-        "",
-        G_TYPE_STRV,
-        G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_direct_strv(properties_override,
+                                            obj_properties,
+                                            NM_SETTING_CONNECTION_SECONDARIES,
+                                            PROP_SECONDARIES,
+                                            NM_SETTING_PARAM_FUZZY_IGNORE,
+                                            NMSettingConnectionPrivate,
+                                            secondaries);
 
     /**
      * NMSettingConnection:gateway-ping-timeout:
diff --git a/src/libnm-core-impl/nm-setting-ip-config.c b/src/libnm-core-impl/nm-setting-ip-config.c
index 52aa6515..cffd5b19 100644
--- a/src/libnm-core-impl/nm-setting-ip-config.c
+++ b/src/libnm-core-impl/nm-setting-ip-config.c
@@ -1390,7 +1390,8 @@ _ip_route_attribute_validate(const char           *name,
                        RTN_LOCAL,
                        RTN_BLACKHOLE,
                        RTN_UNREACHABLE,
-                       RTN_PROHIBIT)) {
+                       RTN_PROHIBIT,
+                       RTN_THROW)) {
             g_set_error(error,
                         NM_CONNECTION_ERROR,
                         NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -1494,6 +1495,7 @@ _nm_ip_route_attribute_validate_all(const NMIPRoute *route, GError **error)
     case RTN_BLACKHOLE:
     case RTN_UNREACHABLE:
     case RTN_PROHIBIT:
+    case RTN_THROW:
         if (route->next_hop) {
             g_set_error(error,
                         NM_CONNECTION_ERROR,
@@ -2684,7 +2686,7 @@ nm_ip_routing_rule_validate(const NMIPRoutingRule *self, GError **error)
         g_set_error_literal(error,
                             NM_CONNECTION_ERROR,
                             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                            _("invalid priority"));
+                            _("missing priority"));
         return FALSE;
     }
 
diff --git a/src/libnm-core-impl/nm-setting-ip4-config.c b/src/libnm-core-impl/nm-setting-ip4-config.c
index 5b06739c..bf555168 100644
--- a/src/libnm-core-impl/nm-setting-ip4-config.c
+++ b/src/libnm-core-impl/nm-setting-ip4-config.c
@@ -1006,7 +1006,8 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
      *      </listitem>
      *      <listitem>
      *        <para><literal>"type"</literal> - one of <literal>unicast</literal>, <literal>local</literal>, <literal>blackhole</literal>,
-     *          <literal>unavailable</literal>, <literal>prohibit</literal>. The default is <literal>unicast</literal>.</para>
+     *          <literal>unavailable</literal>, <literal>prohibit</literal>, <literal>throw</literal>.
+     *          The default is <literal>unicast</literal>.</para>
      *      </listitem>
      *      <listitem>
      *        <para><literal>"window"</literal> - an unsigned 32 bit integer.</para>
diff --git a/src/libnm-core-impl/nm-setting-ip6-config.c b/src/libnm-core-impl/nm-setting-ip6-config.c
index f4623b28..f75c14ce 100644
--- a/src/libnm-core-impl/nm-setting-ip6-config.c
+++ b/src/libnm-core-impl/nm-setting-ip6-config.c
@@ -946,8 +946,9 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
      * format: a comma separated list of addresses
      * description: A list of IPv6 addresses and their prefix length. Multiple addresses
      * can be separated by comma. For example "2001:db8:85a3::8a2e:370:7334/64, 2001:db8:85a3::5/64".
-     * The addresses are listed in increasing priority, meaning the last address will
-     * be the primary address.
+     * The addresses are listed in decreasing priority, meaning the first address will
+     * be the primary address. This can make a difference with IPv6 source address selection
+     * (RFC 6724, section 5).
      * ---end---
      */
     _nm_properties_override_gobj(
@@ -1042,7 +1043,8 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
      *      </listitem>
      *      <listitem>
      *        <para><literal>"type"</literal> - one of <literal>unicast</literal>, <literal>local</literal>, <literal>blackhole</literal>,
-     *          <literal>unavailable</literal>, <literal>prohibit</literal>. The default is <literal>unicast</literal>.</para>
+     *          <literal>unavailable</literal>, <literal>prohibit</literal>, <literal>throw</literal>.
+     *          The default is <literal>unicast</literal>.</para>
      *      </listitem>
      *      <listitem>
      *        <para><literal>"window"</literal> - an unsigned 32 bit integer.</para>
diff --git a/src/libnm-core-impl/nm-setting-match.c b/src/libnm-core-impl/nm-setting-match.c
index 0ddffee5..f0e9fdf8 100644
--- a/src/libnm-core-impl/nm-setting-match.c
+++ b/src/libnm-core-impl/nm-setting-match.c
@@ -32,11 +32,11 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMSettingMatch,
  * Since: 1.14
  */
 struct _NMSettingMatch {
-    NMSetting parent;
-    GArray   *interface_name;
-    GArray   *kernel_command_line;
-    GArray   *driver;
-    GArray   *path;
+    NMSetting   parent;
+    NMValueStrv interface_name;
+    NMValueStrv kernel_command_line;
+    NMValueStrv driver;
+    NMValueStrv path;
 };
 
 struct _NMSettingMatchClass {
@@ -60,7 +60,7 @@ nm_setting_match_get_num_interface_names(NMSettingMatch *setting)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), 0);
 
-    return nm_g_array_len(setting->interface_name);
+    return nm_g_array_len(setting->interface_name.arr);
 }
 
 /**
@@ -77,10 +77,11 @@ nm_setting_match_get_interface_name(NMSettingMatch *setting, int idx)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    g_return_val_if_fail(setting->interface_name && idx >= 0 && idx < setting->interface_name->len,
+    g_return_val_if_fail(setting->interface_name.arr && idx >= 0
+                             && idx < setting->interface_name.arr->len,
                          NULL);
 
-    return nm_strvarray_get_idx(setting->interface_name, idx);
+    return nm_strvarray_get_idx(setting->interface_name.arr, idx);
 }
 
 /**
@@ -98,7 +99,7 @@ nm_setting_match_add_interface_name(NMSettingMatch *setting, const char *interfa
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
     g_return_if_fail(interface_name);
 
-    nm_strvarray_add(nm_strvarray_ensure(&setting->interface_name), interface_name);
+    nm_strvarray_add(nm_strvarray_ensure(&setting->interface_name.arr), interface_name);
     _notify(setting, PROP_INTERFACE_NAME);
 }
 
@@ -116,9 +117,10 @@ nm_setting_match_remove_interface_name(NMSettingMatch *setting, int idx)
 {
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
 
-    g_return_if_fail(setting->interface_name && idx >= 0 && idx < setting->interface_name->len);
+    g_return_if_fail(setting->interface_name.arr && idx >= 0
+                     && idx < setting->interface_name.arr->len);
 
-    g_array_remove_index(setting->interface_name, idx);
+    g_array_remove_index(setting->interface_name.arr, idx);
     _notify(setting, PROP_INTERFACE_NAME);
 }
 
@@ -139,7 +141,7 @@ nm_setting_match_remove_interface_name_by_value(NMSettingMatch *setting, const c
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), FALSE);
     g_return_val_if_fail(interface_name, FALSE);
 
-    if (nm_strvarray_remove_first(setting->interface_name, interface_name)) {
+    if (nm_strvarray_remove_first(setting->interface_name.arr, interface_name)) {
         _notify(setting, PROP_INTERFACE_NAME);
         return TRUE;
     }
@@ -160,8 +162,8 @@ nm_setting_match_clear_interface_names(NMSettingMatch *setting)
 {
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
 
-    if (nm_g_array_len(setting->interface_name) != 0) {
-        nm_clear_pointer(&setting->interface_name, g_array_unref);
+    if (nm_g_array_len(setting->interface_name.arr) != 0) {
+        nm_clear_pointer(&setting->interface_name.arr, g_array_unref);
         _notify(setting, PROP_INTERFACE_NAME);
     }
 }
@@ -185,7 +187,7 @@ nm_setting_match_get_interface_names(NMSettingMatch *setting, guint *length)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    return nm_strvarray_get_strv(&setting->interface_name, length);
+    return nm_strvarray_get_strv(&setting->interface_name.arr, length);
 }
 
 /*****************************************************************************/
@@ -203,7 +205,7 @@ nm_setting_match_get_num_kernel_command_lines(NMSettingMatch *setting)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), 0);
 
-    return nm_g_array_len(setting->kernel_command_line);
+    return nm_g_array_len(setting->kernel_command_line.arr);
 }
 
 /**
@@ -220,10 +222,11 @@ nm_setting_match_get_kernel_command_line(NMSettingMatch *setting, guint idx)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    g_return_val_if_fail(setting->kernel_command_line && idx < setting->kernel_command_line->len,
+    g_return_val_if_fail(setting->kernel_command_line.arr
+                             && idx < setting->kernel_command_line.arr->len,
                          NULL);
 
-    return nm_strvarray_get_idx(setting->kernel_command_line, idx);
+    return nm_strvarray_get_idx(setting->kernel_command_line.arr, idx);
 }
 
 /**
@@ -241,7 +244,7 @@ nm_setting_match_add_kernel_command_line(NMSettingMatch *setting, const char *ke
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
     g_return_if_fail(kernel_command_line);
 
-    nm_strvarray_add(nm_strvarray_ensure(&setting->kernel_command_line), kernel_command_line);
+    nm_strvarray_add(nm_strvarray_ensure(&setting->kernel_command_line.arr), kernel_command_line);
     _notify(setting, PROP_KERNEL_COMMAND_LINE);
 }
 
@@ -259,9 +262,10 @@ nm_setting_match_remove_kernel_command_line(NMSettingMatch *setting, guint idx)
 {
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
 
-    g_return_if_fail(setting->kernel_command_line && idx < setting->kernel_command_line->len);
+    g_return_if_fail(setting->kernel_command_line.arr
+                     && idx < setting->kernel_command_line.arr->len);
 
-    g_array_remove_index(setting->kernel_command_line, idx);
+    g_array_remove_index(setting->kernel_command_line.arr, idx);
     _notify(setting, PROP_KERNEL_COMMAND_LINE);
 }
 
@@ -283,7 +287,7 @@ nm_setting_match_remove_kernel_command_line_by_value(NMSettingMatch *setting,
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), FALSE);
     g_return_val_if_fail(kernel_command_line, FALSE);
 
-    if (nm_strvarray_remove_first(setting->kernel_command_line, kernel_command_line)) {
+    if (nm_strvarray_remove_first(setting->kernel_command_line.arr, kernel_command_line)) {
         _notify(setting, PROP_KERNEL_COMMAND_LINE);
         return TRUE;
     }
@@ -304,8 +308,8 @@ nm_setting_match_clear_kernel_command_lines(NMSettingMatch *setting)
 {
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
 
-    if (nm_g_array_len(setting->kernel_command_line) != 0) {
-        nm_clear_pointer(&setting->kernel_command_line, g_array_unref);
+    if (nm_g_array_len(setting->kernel_command_line.arr) != 0) {
+        nm_clear_pointer(&setting->kernel_command_line.arr, g_array_unref);
         _notify(setting, PROP_KERNEL_COMMAND_LINE);
     }
 }
@@ -326,7 +330,7 @@ nm_setting_match_get_kernel_command_lines(NMSettingMatch *setting, guint *length
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    return nm_strvarray_get_strv(&setting->kernel_command_line, length);
+    return nm_strvarray_get_strv(&setting->kernel_command_line.arr, length);
 }
 
 /*****************************************************************************/
@@ -344,7 +348,7 @@ nm_setting_match_get_num_drivers(NMSettingMatch *setting)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), 0);
 
-    return nm_g_array_len(setting->driver);
+    return nm_g_array_len(setting->driver.arr);
 }
 
 /**
@@ -361,9 +365,9 @@ nm_setting_match_get_driver(NMSettingMatch *setting, guint idx)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    g_return_val_if_fail(setting->driver && idx < setting->driver->len, NULL);
+    g_return_val_if_fail(setting->driver.arr && idx < setting->driver.arr->len, NULL);
 
-    return nm_strvarray_get_idx(setting->driver, idx);
+    return nm_strvarray_get_idx(setting->driver.arr, idx);
 }
 
 /**
@@ -381,7 +385,7 @@ nm_setting_match_add_driver(NMSettingMatch *setting, const char *driver)
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
     g_return_if_fail(driver);
 
-    nm_strvarray_add(nm_strvarray_ensure(&setting->driver), driver);
+    nm_strvarray_add(nm_strvarray_ensure(&setting->driver.arr), driver);
     _notify(setting, PROP_DRIVER);
 }
 
@@ -399,9 +403,9 @@ nm_setting_match_remove_driver(NMSettingMatch *setting, guint idx)
 {
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
 
-    g_return_if_fail(setting->driver && idx < setting->driver->len);
+    g_return_if_fail(setting->driver.arr && idx < setting->driver.arr->len);
 
-    g_array_remove_index(setting->driver, idx);
+    g_array_remove_index(setting->driver.arr, idx);
     _notify(setting, PROP_DRIVER);
 }
 
@@ -422,7 +426,7 @@ nm_setting_match_remove_driver_by_value(NMSettingMatch *setting, const char *dri
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), FALSE);
     g_return_val_if_fail(driver, FALSE);
 
-    if (nm_strvarray_remove_first(setting->driver, driver)) {
+    if (nm_strvarray_remove_first(setting->driver.arr, driver)) {
         _notify(setting, PROP_DRIVER);
         return TRUE;
     }
@@ -443,8 +447,8 @@ nm_setting_match_clear_drivers(NMSettingMatch *setting)
 {
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
 
-    if (nm_g_array_len(setting->driver) != 0) {
-        nm_clear_pointer(&setting->driver, g_array_unref);
+    if (nm_g_array_len(setting->driver.arr) != 0) {
+        nm_clear_pointer(&setting->driver.arr, g_array_unref);
         _notify(setting, PROP_DRIVER);
     }
 }
@@ -465,7 +469,7 @@ nm_setting_match_get_drivers(NMSettingMatch *setting, guint *length)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    return nm_strvarray_get_strv(&setting->driver, length);
+    return nm_strvarray_get_strv(&setting->driver.arr, length);
 }
 
 /*****************************************************************************/
@@ -483,7 +487,7 @@ nm_setting_match_get_num_paths(NMSettingMatch *setting)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), 0);
 
-    return nm_g_array_len(setting->path);
+    return nm_g_array_len(setting->path.arr);
 }
 
 /**
@@ -500,9 +504,9 @@ nm_setting_match_get_path(NMSettingMatch *setting, guint idx)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    g_return_val_if_fail(setting->path && idx < setting->path->len, NULL);
+    g_return_val_if_fail(setting->path.arr && idx < setting->path.arr->len, NULL);
 
-    return nm_strvarray_get_idx(setting->path, idx);
+    return nm_strvarray_get_idx(setting->path.arr, idx);
 }
 
 /**
@@ -520,7 +524,7 @@ nm_setting_match_add_path(NMSettingMatch *setting, const char *path)
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
     g_return_if_fail(path);
 
-    nm_strvarray_add(nm_strvarray_ensure(&setting->path), path);
+    nm_strvarray_add(nm_strvarray_ensure(&setting->path.arr), path);
     _notify(setting, PROP_PATH);
 }
 
@@ -538,9 +542,9 @@ nm_setting_match_remove_path(NMSettingMatch *setting, guint idx)
 {
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
 
-    g_return_if_fail(setting->path && idx < setting->path->len);
+    g_return_if_fail(setting->path.arr && idx < setting->path.arr->len);
 
-    g_array_remove_index(setting->path, idx);
+    g_array_remove_index(setting->path.arr, idx);
     _notify(setting, PROP_PATH);
 }
 
@@ -561,7 +565,7 @@ nm_setting_match_remove_path_by_value(NMSettingMatch *setting, const char *path)
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), FALSE);
     g_return_val_if_fail(path, FALSE);
 
-    if (nm_strvarray_remove_first(setting->path, path)) {
+    if (nm_strvarray_remove_first(setting->path.arr, path)) {
         _notify(setting, PROP_PATH);
         return TRUE;
     }
@@ -582,8 +586,8 @@ nm_setting_match_clear_paths(NMSettingMatch *setting)
 {
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
 
-    if (nm_g_array_len(setting->path) != 0) {
-        nm_clear_pointer(&setting->path, g_array_unref);
+    if (nm_g_array_len(setting->path.arr) != 0) {
+        nm_clear_pointer(&setting->path.arr, g_array_unref);
         _notify(setting, PROP_PATH);
     }
 }
@@ -604,58 +608,7 @@ nm_setting_match_get_paths(NMSettingMatch *setting, guint *length)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    return nm_strvarray_get_strv(&setting->path, length);
-}
-
-/*****************************************************************************/
-
-static void
-get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
-{
-    NMSettingMatch *self = NM_SETTING_MATCH(object);
-
-    switch (prop_id) {
-    case PROP_INTERFACE_NAME:
-        g_value_take_boxed(value, nm_strvarray_get_strv_non_empty_dup(self->interface_name, NULL));
-        break;
-    case PROP_KERNEL_COMMAND_LINE:
-        g_value_take_boxed(value,
-                           nm_strvarray_get_strv_non_empty_dup(self->kernel_command_line, NULL));
-        break;
-    case PROP_DRIVER:
-        g_value_take_boxed(value, nm_strvarray_get_strv_non_empty_dup(self->driver, NULL));
-        break;
-    case PROP_PATH:
-        g_value_take_boxed(value, nm_strvarray_get_strv_non_empty_dup(self->path, NULL));
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-        break;
-    }
-}
-
-static void
-set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
-{
-    NMSettingMatch *self = NM_SETTING_MATCH(object);
-
-    switch (prop_id) {
-    case PROP_INTERFACE_NAME:
-        nm_strvarray_set_strv(&self->interface_name, g_value_get_boxed(value));
-        break;
-    case PROP_KERNEL_COMMAND_LINE:
-        nm_strvarray_set_strv(&self->kernel_command_line, g_value_get_boxed(value));
-        break;
-    case PROP_DRIVER:
-        nm_strvarray_set_strv(&self->driver, g_value_get_boxed(value));
-        break;
-    case PROP_PATH:
-        nm_strvarray_set_strv(&self->path, g_value_get_boxed(value));
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-        break;
-    }
+    return nm_strvarray_get_strv(&setting->path.arr, length);
 }
 
 /*****************************************************************************/
@@ -698,9 +651,9 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     NMSettingMatch *self = NM_SETTING_MATCH(setting);
     guint           i;
 
-    if (self->interface_name) {
-        for (i = 0; i < self->interface_name->len; i++) {
-            if (nm_str_is_empty(nm_strvarray_get_idx(self->interface_name, i))) {
+    if (self->interface_name.arr) {
+        for (i = 0; i < self->interface_name.arr->len; i++) {
+            if (nm_str_is_empty(nm_strvarray_get_idx(self->interface_name.arr, i))) {
                 g_set_error(error,
                             NM_CONNECTION_ERROR,
                             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -714,9 +667,9 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         }
     }
 
-    if (self->kernel_command_line) {
-        for (i = 0; i < self->kernel_command_line->len; i++) {
-            if (nm_str_is_empty(nm_strvarray_get_idx(self->kernel_command_line, i))) {
+    if (self->kernel_command_line.arr) {
+        for (i = 0; i < self->kernel_command_line.arr->len; i++) {
+            if (nm_str_is_empty(nm_strvarray_get_idx(self->kernel_command_line.arr, i))) {
                 g_set_error(error,
                             NM_CONNECTION_ERROR,
                             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -730,9 +683,9 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         }
     }
 
-    if (self->driver) {
-        for (i = 0; i < self->driver->len; i++) {
-            if (nm_str_is_empty(nm_strvarray_get_idx(self->driver, i))) {
+    if (self->driver.arr) {
+        for (i = 0; i < self->driver.arr->len; i++) {
+            if (nm_str_is_empty(nm_strvarray_get_idx(self->driver.arr, i))) {
                 g_set_error(error,
                             NM_CONNECTION_ERROR,
                             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -746,9 +699,9 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         }
     }
 
-    if (self->path) {
-        for (i = 0; i < self->path->len; i++) {
-            if (nm_str_is_empty(nm_strvarray_get_idx(self->path, i))) {
+    if (self->path.arr) {
+        for (i = 0; i < self->path.arr->len; i++) {
+            if (nm_str_is_empty(nm_strvarray_get_idx(self->path.arr, i))) {
                 g_set_error(error,
                             NM_CONNECTION_ERROR,
                             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -770,10 +723,10 @@ finalize(GObject *object)
 {
     NMSettingMatch *self = NM_SETTING_MATCH(object);
 
-    nm_clear_pointer(&self->interface_name, g_array_unref);
-    nm_clear_pointer(&self->kernel_command_line, g_array_unref);
-    nm_clear_pointer(&self->driver, g_array_unref);
-    nm_clear_pointer(&self->path, g_array_unref);
+    nm_clear_pointer(&self->interface_name.arr, g_array_unref);
+    nm_clear_pointer(&self->kernel_command_line.arr, g_array_unref);
+    nm_clear_pointer(&self->driver.arr, g_array_unref);
+    nm_clear_pointer(&self->path.arr, g_array_unref);
 
     G_OBJECT_CLASS(nm_setting_match_parent_class)->finalize(object);
 }
@@ -781,11 +734,12 @@ finalize(GObject *object)
 static void
 nm_setting_match_class_init(NMSettingMatchClass *klass)
 {
-    GObjectClass   *object_class  = G_OBJECT_CLASS(klass);
-    NMSettingClass *setting_class = NM_SETTING_CLASS(klass);
+    GObjectClass   *object_class        = G_OBJECT_CLASS(klass);
+    NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
+    GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    object_class->get_property = get_property;
-    object_class->set_property = set_property;
+    object_class->get_property = _nm_setting_property_get_property_direct;
+    object_class->set_property = _nm_setting_property_set_property_direct;
     object_class->finalize     = finalize;
 
     setting_class->verify = verify;
@@ -810,12 +764,13 @@ nm_setting_match_class_init(NMSettingMatchClass *klass)
      *
      * Since: 1.14
      **/
-    obj_properties[PROP_INTERFACE_NAME] = g_param_spec_boxed(
-        NM_SETTING_MATCH_INTERFACE_NAME,
-        "",
-        "",
-        G_TYPE_STRV,
-        NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_direct_strv(properties_override,
+                                            obj_properties,
+                                            NM_SETTING_MATCH_INTERFACE_NAME,
+                                            PROP_INTERFACE_NAME,
+                                            NM_SETTING_PARAM_FUZZY_IGNORE,
+                                            NMSettingMatch,
+                                            interface_name);
 
     /**
      * NMSettingMatch:kernel-command-line
@@ -834,12 +789,13 @@ nm_setting_match_class_init(NMSettingMatchClass *klass)
      *
      * Since: 1.26
      **/
-    obj_properties[PROP_KERNEL_COMMAND_LINE] = g_param_spec_boxed(
-        NM_SETTING_MATCH_KERNEL_COMMAND_LINE,
-        "",
-        "",
-        G_TYPE_STRV,
-        NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_direct_strv(properties_override,
+                                            obj_properties,
+                                            NM_SETTING_MATCH_KERNEL_COMMAND_LINE,
+                                            PROP_KERNEL_COMMAND_LINE,
+                                            NM_SETTING_PARAM_FUZZY_IGNORE,
+                                            NMSettingMatch,
+                                            kernel_command_line);
 
     /**
      * NMSettingMatch:driver
@@ -852,12 +808,13 @@ nm_setting_match_class_init(NMSettingMatchClass *klass)
      *
      * Since: 1.26
      **/
-    obj_properties[PROP_DRIVER] = g_param_spec_boxed(
-        NM_SETTING_MATCH_DRIVER,
-        "",
-        "",
-        G_TYPE_STRV,
-        NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_direct_strv(properties_override,
+                                            obj_properties,
+                                            NM_SETTING_MATCH_DRIVER,
+                                            PROP_DRIVER,
+                                            NM_SETTING_PARAM_FUZZY_IGNORE,
+                                            NMSettingMatch,
+                                            driver);
 
     /**
      * NMSettingMatch:path
@@ -892,14 +849,19 @@ nm_setting_match_class_init(NMSettingMatchClass *klass)
      * example: MATCH_PATH="pci-0000:01:00.0 pci-0000:0c:00.0"
      * ---end---
      */
-    obj_properties[PROP_PATH] = g_param_spec_boxed(NM_SETTING_MATCH_PATH,
-                                                   "",
-                                                   "",
-                                                   G_TYPE_STRV,
-                                                   NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE
-                                                       | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_direct_strv(properties_override,
+                                            obj_properties,
+                                            NM_SETTING_MATCH_PATH,
+                                            PROP_PATH,
+                                            NM_SETTING_PARAM_FUZZY_IGNORE,
+                                            NMSettingMatch,
+                                            path);
 
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
-    _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_MATCH, NULL, NULL, 0);
+    _nm_setting_class_commit(setting_class,
+                             NM_META_SETTING_TYPE_MATCH,
+                             NULL,
+                             properties_override,
+                             0);
 }
diff --git a/src/libnm-core-impl/nm-setting-private.h b/src/libnm-core-impl/nm-setting-private.h
index 7483eaed..38b233e9 100644
--- a/src/libnm-core-impl/nm-setting-private.h
+++ b/src/libnm-core-impl/nm-setting-private.h
@@ -232,6 +232,14 @@ gboolean _nm_setting_clear_secrets(NMSetting                       *setting,
 
 /*****************************************************************************/
 
+/* This holds a property of type NM_VALUE_TYPE_STRV. You probably want
+ * to use nm_strvarray_*() API with this. */
+typedef struct {
+    GArray *arr;
+} NMValueStrv;
+
+/*****************************************************************************/
+
 #define NM_SETTING_PARAM_NONE 0
 
 /* The property of the #NMSetting should be considered during comparisons that
@@ -277,6 +285,7 @@ extern const NMSettInfoPropertType nm_sett_info_propert_type_direct_int64;
 extern const NMSettInfoPropertType nm_sett_info_propert_type_direct_uint64;
 extern const NMSettInfoPropertType nm_sett_info_propert_type_direct_string;
 extern const NMSettInfoPropertType nm_sett_info_propert_type_direct_bytes;
+extern const NMSettInfoPropertType nm_sett_info_propert_type_direct_strv;
 extern const NMSettInfoPropertType nm_sett_info_propert_type_direct_enum;
 extern const NMSettInfoPropertType nm_sett_info_propert_type_direct_flags;
 extern const NMSettInfoPropertType nm_sett_info_propert_type_direct_mac_address;
@@ -774,6 +783,42 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
 
 /*****************************************************************************/
 
+#define _nm_setting_property_define_direct_strv(properties_override,                         \
+                                                obj_properties,                              \
+                                                prop_name,                                   \
+                                                prop_id,                                     \
+                                                param_flags,                                 \
+                                                private_struct_type,                         \
+                                                private_struct_field,                        \
+                                                ... /* extra NMSettInfoProperty fields */)   \
+    G_STMT_START                                                                             \
+    {                                                                                        \
+        GParamSpec *_param_spec;                                                             \
+                                                                                             \
+        G_STATIC_ASSERT(!NM_FLAGS_ANY((param_flags), ~(NM_SETTING_PARAM_FUZZY_IGNORE)));     \
+                                                                                             \
+        _param_spec =                                                                        \
+            g_param_spec_boxed("" prop_name "",                                              \
+                               "",                                                           \
+                               "",                                                           \
+                               G_TYPE_STRV,                                                  \
+                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | (param_flags));  \
+                                                                                             \
+        (obj_properties)[(prop_id)] = _param_spec;                                           \
+                                                                                             \
+        _nm_properties_override_gobj((properties_override),                                  \
+                                     _param_spec,                                            \
+                                     &nm_sett_info_propert_type_direct_strv,                 \
+                                     .direct_offset =                                        \
+                                         NM_STRUCT_OFFSET_ENSURE_TYPE(NMValueStrv,           \
+                                                                      private_struct_type,   \
+                                                                      private_struct_field), \
+                                     __VA_ARGS__);                                           \
+    }                                                                                        \
+    G_STMT_END
+
+/*****************************************************************************/
+
 #define _nm_setting_property_define_direct_enum(properties_override,                             \
                                                 obj_properties,                                  \
                                                 prop_name,                                       \
diff --git a/src/libnm-core-impl/nm-setting-proxy.c b/src/libnm-core-impl/nm-setting-proxy.c
index 4b9a2970..8b91210b 100644
--- a/src/libnm-core-impl/nm-setting-proxy.c
+++ b/src/libnm-core-impl/nm-setting-proxy.c
@@ -114,7 +114,7 @@ nm_setting_proxy_get_pac_url(NMSettingProxy *setting)
  * nm_setting_proxy_get_pac_script:
  * @setting: the #NMSettingProxy
  *
- * Returns: the PAC script
+ * Returns: the PAC script.
  *
  * Since: 1.6
  **/
@@ -315,15 +315,25 @@ nm_setting_proxy_class_init(NMSettingProxyClass *klass)
     /**
      * NMSettingProxy:pac-script:
      *
-     * PAC script for the connection.
+     * PAC script for the connection. This is an UTF-8 encoded javascript code
+     * that defines a FindProxyForURL() function.
      *
      * Since: 1.6
      **/
+    /* ---nmcli---
+     * property: pac-script
+     * description: The PAC script. In the profile this must be an UTF-8 encoded javascript code that defines
+     *   a FindProxyForURL() function.
+     *   When setting the property in nmcli, a filename is accepted too. In that case,
+     *   nmcli will read the content of the file and set the script. The prefixes "file://" and "js://" are
+     *   supported to explicitly differentiate between the two.
+     * ---end---
+     */
     /* ---ifcfg-rh---
      * property: pac-script
      * variable: PAC_SCRIPT(+)
-     * description: Path of the PAC script.
-     * example: PAC_SCRIPT=/home/joe/proxy.pac
+     * description: The PAC script. This is an UTF-8 encoded javascript code that defines a FindProxyForURL() function.
+     * example: PAC_SCRIPT="function FindProxyForURL (url, host) { return 'PROXY proxy.example.com:8080; DIRECT'; }"
      * ---end---
      */
     _nm_setting_property_define_direct_string(properties_override,
diff --git a/src/libnm-core-impl/nm-setting.c b/src/libnm-core-impl/nm-setting.c
index be88effb..35070bae 100644
--- a/src/libnm-core-impl/nm-setting.c
+++ b/src/libnm-core-impl/nm-setting.c
@@ -774,6 +774,13 @@ _nm_setting_property_get_property_direct(GObject    *object,
         g_value_set_boxed(value, *p_val);
         return;
     }
+    case NM_VALUE_TYPE_STRV:
+    {
+        const NMValueStrv *p_val = _nm_setting_get_private_field(setting, sett_info, property_info);
+
+        g_value_take_boxed(value, nm_strvarray_get_strv_non_empty_dup(p_val->arr, NULL));
+        return;
+    }
     default:
         goto out_fail;
     }
@@ -909,6 +916,18 @@ _nm_setting_property_set_property_direct(GObject      *object,
         *p_val = v ? g_bytes_ref(v) : NULL;
         goto out_notify;
     }
+    case NM_VALUE_TYPE_STRV:
+    {
+        NMValueStrv       *p_val = _nm_setting_get_private_field(setting, sett_info, property_info);
+        const char *const *v;
+
+        v = g_value_get_boxed(value);
+        if (nm_strvarray_equal_strv(p_val->arr, v, -1))
+            return;
+
+        nm_strvarray_set_strv(&p_val->arr, v);
+        goto out_notify;
+    }
     default:
         goto out_fail;
     }
@@ -1026,6 +1045,11 @@ _init_direct(NMSetting *setting)
             nm_assert(!(*((const GBytes *const *)
                               _nm_setting_get_private_field(setting, sett_info, property_info))));
             break;
+        case NM_VALUE_TYPE_STRV:
+            nm_assert(!((const NMValueStrv *)
+                            _nm_setting_get_private_field(setting, sett_info, property_info))
+                           ->arr);
+            break;
         default:
             nm_assert_not_reached();
             break;
@@ -1081,6 +1105,13 @@ _finalize_direct(NMSetting *setting)
             nm_clear_pointer(p_val, g_bytes_unref);
             break;
         }
+        case NM_VALUE_TYPE_STRV:
+        {
+            NMValueStrv *p_val = _nm_setting_get_private_field(setting, sett_info, property_info);
+
+            nm_clear_pointer(&p_val->arr, g_array_unref);
+            break;
+        }
         default:
             nm_assert_not_reached();
             break;
@@ -1199,6 +1230,20 @@ _nm_setting_property_to_dbus_fcn_direct(_NM_SETT_INFO_PROP_TO_DBUS_FCN_ARGS _nm_
             return NULL;
         return nm_g_bytes_to_variant_ay(val);
     }
+    case NM_VALUE_TYPE_STRV:
+    {
+        const NMValueStrv *val;
+
+        /* Strv properties have always NULL as default. Setting "including_default" has no defined meaning
+         * (but it could have). */
+        nm_assert(!property_info->to_dbus_including_default);
+
+        val =
+            (const NMValueStrv *) _nm_setting_get_private_field(setting, sett_info, property_info);
+        if (!val->arr)
+            return NULL;
+        return g_variant_new_strv((const char *const *) val->arr->data, val->arr->len);
+    }
     default:
         return nm_assert_unreachable_val(NULL);
     }
@@ -1545,6 +1590,8 @@ _nm_setting_property_from_dbus_fcn_direct(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS
         gs_unref_bytes GBytes *v = NULL;
         GBytes               **p_val;
 
+        nm_assert(!property_info->property_type->from_dbus_direct_allow_transform);
+
         if (!g_variant_is_of_type(value, G_VARIANT_TYPE_BYTESTRING))
             goto out_error_wrong_dbus_type;
 
@@ -1557,6 +1604,28 @@ _nm_setting_property_from_dbus_fcn_direct(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS
         NM_SWAP(p_val, &v);
         goto out_notify;
     }
+    case NM_VALUE_TYPE_STRV:
+    {
+        NMValueStrv         *p_val;
+        gs_free const char **ss = NULL;
+        gsize                ss_len;
+
+        nm_assert(!property_info->property_type->from_dbus_direct_allow_transform);
+
+        if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING_ARRAY))
+            goto out_error_wrong_dbus_type;
+
+        ss = g_variant_get_strv(value, &ss_len);
+        nm_assert(ss_len <= G_MAXUINT);
+
+        p_val = _nm_setting_get_private_field(setting, sett_info, property_info);
+
+        if (nm_strvarray_equal_strv(p_val->arr, ss, ss_len))
+            goto out_unchanged;
+
+        nm_strvarray_set_strv(&p_val->arr, ss);
+        goto out_notify;
+    }
     default:
         break;
     }
@@ -2465,6 +2534,9 @@ _nm_setting_property_compare_fcn_direct(_NM_SETT_INFO_PROP_COMPARE_FCN_ARGS _nm_
         return nm_streq0(*((const char *const *) p_a), *((const char *const *) p_b));
     case NM_VALUE_TYPE_BYTES:
         return nm_g_bytes_equal0(*((const GBytes *const *) p_a), *((const GBytes *const *) p_b));
+    case NM_VALUE_TYPE_STRV:
+        return nm_strvarray_equal(((const NMValueStrv *) p_a)->arr,
+                                  ((const NMValueStrv *) p_b)->arr);
     default:
         return nm_assert_unreachable_val(TRUE);
     }
@@ -3529,8 +3601,15 @@ const NMSettInfoPropertType nm_sett_info_propert_type_direct_bytes =
                                         .compare_fcn   = _nm_setting_property_compare_fcn_direct,
                                         .to_dbus_fcn   = _nm_setting_property_to_dbus_fcn_direct,
                                         .from_dbus_fcn = _nm_setting_property_from_dbus_fcn_direct,
-                                        .from_dbus_is_full                = TRUE,
-                                        .from_dbus_direct_allow_transform = TRUE);
+                                        .from_dbus_is_full = TRUE);
+
+const NMSettInfoPropertType nm_sett_info_propert_type_direct_strv =
+    NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(G_VARIANT_TYPE_STRING_ARRAY,
+                                        .direct_type   = NM_VALUE_TYPE_STRV,
+                                        .compare_fcn   = _nm_setting_property_compare_fcn_direct,
+                                        .to_dbus_fcn   = _nm_setting_property_to_dbus_fcn_direct,
+                                        .from_dbus_fcn = _nm_setting_property_from_dbus_fcn_direct,
+                                        .from_dbus_is_full = TRUE);
 
 const NMSettInfoPropertType nm_sett_info_propert_type_direct_enum =
     NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(G_VARIANT_TYPE_INT32,
@@ -3896,7 +3975,7 @@ nm_setting_option_set(NMSetting *setting, const char *opt_name, GVariant *varian
     g_hash_table_insert(hash, g_strdup(opt_name), g_variant_ref_sink(variant));
 
     if (changed_value)
-        _nm_setting_option_notify(setting, !changed_name);
+        _nm_setting_option_notify(setting, changed_name);
 }
 
 /**
@@ -3933,7 +4012,7 @@ nm_setting_option_set_boolean(NMSetting *setting, const char *opt_name, gboolean
     g_hash_table_insert(hash, g_strdup(opt_name), g_variant_ref_sink(g_variant_new_boolean(value)));
 
     if (changed_value)
-        _nm_setting_option_notify(setting, !changed_name);
+        _nm_setting_option_notify(setting, changed_name);
 }
 
 /**
@@ -3968,7 +4047,7 @@ nm_setting_option_set_uint32(NMSetting *setting, const char *opt_name, guint32 v
     g_hash_table_insert(hash, g_strdup(opt_name), g_variant_ref_sink(g_variant_new_uint32(value)));
 
     if (changed_value)
-        _nm_setting_option_notify(setting, !changed_name);
+        _nm_setting_option_notify(setting, changed_name);
 }
 
 /*****************************************************************************/
diff --git a/src/libnm-core-impl/nm-utils.c b/src/libnm-core-impl/nm-utils.c
index 55b01c15..d5d884f2 100644
--- a/src/libnm-core-impl/nm-utils.c
+++ b/src/libnm-core-impl/nm-utils.c
@@ -27,7 +27,6 @@
 #include "libnm-core-aux-intern/nm-common-macros.h"
 #include "nm-utils-private.h"
 #include "nm-setting-private.h"
-#include "nm-crypto.h"
 #include "nm-setting-bond.h"
 #include "nm-setting-bond-port.h"
 #include "nm-setting-bridge.h"
@@ -3083,94 +3082,6 @@ nm_utils_uuid_generate(void)
 
 /*****************************************************************************/
 
-static gboolean
-file_has_extension(const char *filename, const char *extensions[])
-{
-    const char *ext;
-    gsize       i;
-
-    ext = strrchr(filename, '.');
-    if (!ext)
-        return FALSE;
-
-    for (i = 0; extensions[i]; i++) {
-        if (!g_ascii_strcasecmp(ext, extensions[i]))
-            return TRUE;
-    }
-
-    return FALSE;
-}
-
-/**
- * nm_utils_file_is_certificate:
- * @filename: name of the file to test
- *
- * Tests if @filename has a valid extension for an X.509 certificate file
- * (".cer", ".crt", ".der", or ".pem"), and contains a certificate in a format
- * recognized by NetworkManager.
- *
- * Returns: %TRUE if the file is a certificate, %FALSE if it is not
- **/
-gboolean
-nm_utils_file_is_certificate(const char *filename)
-{
-    const char        *extensions[] = {".der", ".pem", ".crt", ".cer", NULL};
-    NMCryptoFileFormat file_format;
-
-    g_return_val_if_fail(filename != NULL, FALSE);
-
-    if (!file_has_extension(filename, extensions))
-        return FALSE;
-
-    if (!nm_crypto_load_and_verify_certificate(filename, &file_format, NULL, NULL))
-        return FALSE;
-    return file_format = NM_CRYPTO_FILE_FORMAT_X509;
-}
-
-/**
- * nm_utils_file_is_private_key:
- * @filename: name of the file to test
- * @out_encrypted: (out): on return, whether the file is encrypted
- *
- * Tests if @filename has a valid extension for an X.509 private key file
- * (".der", ".key", ".pem", or ".p12"), and contains a private key in a format
- * recognized by NetworkManager.
- *
- * Returns: %TRUE if the file is a private key, %FALSE if it is not
- **/
-gboolean
-nm_utils_file_is_private_key(const char *filename, gboolean *out_encrypted)
-{
-    const char *extensions[] = {".der", ".pem", ".p12", ".key", NULL};
-
-    g_return_val_if_fail(filename != NULL, FALSE);
-
-    NM_SET_OUT(out_encrypted, FALSE);
-    if (!file_has_extension(filename, extensions))
-        return FALSE;
-
-    return nm_crypto_verify_private_key(filename, NULL, out_encrypted, NULL)
-           != NM_CRYPTO_FILE_FORMAT_UNKNOWN;
-}
-
-/**
- * nm_utils_file_is_pkcs12:
- * @filename: name of the file to test
- *
- * Tests if @filename is a PKCS#<!-- -->12 file.
- *
- * Returns: %TRUE if the file is PKCS#<!-- -->12, %FALSE if it is not
- **/
-gboolean
-nm_utils_file_is_pkcs12(const char *filename)
-{
-    g_return_val_if_fail(filename != NULL, FALSE);
-
-    return nm_crypto_is_pkcs12_file(filename, NULL);
-}
-
-/*****************************************************************************/
-
 gboolean
 _nm_utils_check_file(const char               *filename,
                      gint64                    check_owner,
@@ -3795,22 +3706,13 @@ nm_utils_hwaddr_aton(const char *asc, gpointer buffer, gsize length)
 char *
 nm_utils_bin2hexstr(gconstpointer src, gsize len, int final_len)
 {
-    char *result;
     gsize buflen = (len * 2) + 1;
 
     g_return_val_if_fail(src != NULL, NULL);
     g_return_val_if_fail(len > 0 && (buflen - 1) / 2 == len, NULL);
     g_return_val_if_fail(final_len < 0 || (gsize) final_len < buflen, NULL);
 
-    result = g_malloc(buflen);
-
-    nm_utils_bin2hexstr_full(src, len, '\0', FALSE, result);
-
-    /* Cut converted key off at the correct length for this cipher type */
-    if (final_len >= 0 && (gsize) final_len < buflen)
-        result[final_len] = '\0';
-
-    return result;
+    return _nm_utils_bin2hexstr(src, len, final_len);
 }
 
 /**
diff --git a/src/libnm-core-impl/tests/test-crypto.c b/src/libnm-core-impl/tests/test-crypto.c
index 6a6e7fbc..896c3c2e 100644
--- a/src/libnm-core-impl/tests/test-crypto.c
+++ b/src/libnm-core-impl/tests/test-crypto.c
@@ -10,7 +10,7 @@
 #include <stdlib.h>
 #include <stdio.h>
 
-#include "nm-crypto-impl.h"
+#include "libnm-crypto/nm-crypto-impl.h"
 #include "nm-utils.h"
 #include "nm-errors.h"
 #include "libnm-core-intern/nm-core-internal.h"
@@ -92,7 +92,7 @@ test_cert(gconstpointer test_data)
     nmtst_assert_success(success, error);
     g_assert_cmpint(format, ==, NM_CRYPTO_FILE_FORMAT_X509);
 
-    g_assert(nm_utils_file_is_certificate(path));
+    g_assert(nm_crypto_utils_file_is_certificate(path));
 }
 
 static void
@@ -106,7 +106,7 @@ test_load_private_key(const char *path,
     gs_unref_bytes GBytes *array        = NULL;
     GError                *error        = NULL;
 
-    g_assert(nm_utils_file_is_private_key(path, &is_encrypted));
+    g_assert(nm_crypto_utils_file_is_private_key(path, &is_encrypted));
     g_assert(is_encrypted);
 
     array = nmtst_crypto_decrypt_openssl_private_key(path, password, &key_type, &error);
@@ -146,7 +146,7 @@ test_load_pkcs12(const char *path, const char *password, int expected_error)
     gboolean           is_encrypted = FALSE;
     GError            *error        = NULL;
 
-    g_assert(nm_utils_file_is_private_key(path, NULL));
+    g_assert(nm_crypto_utils_file_is_private_key(path, NULL));
 
     format = nm_crypto_verify_private_key(path, password, &is_encrypted, &error);
     if (expected_error != -1) {
@@ -167,7 +167,7 @@ test_load_pkcs12_no_password(const char *path)
     gboolean           is_encrypted = FALSE;
     GError            *error        = NULL;
 
-    g_assert(nm_utils_file_is_private_key(path, NULL));
+    g_assert(nm_crypto_utils_file_is_private_key(path, NULL));
 
     /* We should still get a valid returned crypto file format */
     format = nm_crypto_verify_private_key(path, NULL, &is_encrypted, &error);
@@ -201,7 +201,7 @@ test_load_pkcs8(const char *path, const char *password, int expected_error)
     gboolean           is_encrypted = FALSE;
     GError            *error        = NULL;
 
-    g_assert(nm_utils_file_is_private_key(path, NULL));
+    g_assert(nm_crypto_utils_file_is_private_key(path, NULL));
 
     format = nm_crypto_verify_private_key(path, password, &is_encrypted, &error);
     if (expected_error != -1) {
@@ -285,7 +285,7 @@ test_key_decrypted(gconstpointer test_data)
 
     path = g_build_filename(TEST_CERT_DIR, file, NULL);
 
-    g_assert(nm_utils_file_is_private_key(path, &is_encrypted));
+    g_assert(nm_crypto_utils_file_is_private_key(path, &is_encrypted));
     g_assert(!is_encrypted);
 
     g_free(path);
@@ -399,6 +399,23 @@ test_md5(void)
     }
 }
 
+/*****************************************************************************/
+
+static void
+test_crypto_error(void)
+{
+    G_STATIC_ASSERT(NM_CRYPTO_ERROR_FAILED == _NM_CRYPTO_ERROR_FAILED);
+    G_STATIC_ASSERT(NM_CRYPTO_ERROR_INVALID_DATA == _NM_CRYPTO_ERROR_INVALID_DATA);
+    G_STATIC_ASSERT(NM_CRYPTO_ERROR_INVALID_PASSWORD == _NM_CRYPTO_ERROR_INVALID_PASSWORD);
+    G_STATIC_ASSERT(NM_CRYPTO_ERROR_UNKNOWN_CIPHER == _NM_CRYPTO_ERROR_UNKNOWN_CIPHER);
+    G_STATIC_ASSERT(NM_CRYPTO_ERROR_DECRYPTION_FAILED == _NM_CRYPTO_ERROR_DECRYPTION_FAILED);
+    G_STATIC_ASSERT(NM_CRYPTO_ERROR_ENCRYPTION_FAILED == _NM_CRYPTO_ERROR_ENCRYPTION_FAILED);
+
+    g_assert_cmpint(NM_CRYPTO_ERROR, ==, _NM_CRYPTO_ERROR);
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE();
 
 int
@@ -448,6 +465,7 @@ main(int argc, char **argv)
     g_test_add_data_func("/libnm/crypto/PKCS#8", "pkcs8-enc-key.pem, 1234567890", test_pkcs8);
 
     g_test_add_func("/libnm/crypto/md5", test_md5);
+    g_test_add_func("/libnm/crypto/error", test_crypto_error);
 
     ret = g_test_run();
 
diff --git a/src/libnm-core-impl/tests/test-general-enums.h b/src/libnm-core-impl/tests/test-general-enums.h
index b3163e23..2ac8cf2b 100644
--- a/src/libnm-core-impl/tests/test-general-enums.h
+++ b/src/libnm-core-impl/tests/test-general-enums.h
@@ -25,11 +25,11 @@ typedef enum {
     NM_TEST_GENERAL_META_FLAGS_0x4  = (1 << 4),
 } NMTestGeneralMetaFlags;
 
-typedef enum {                                        /*< flags >*/
-               NM_TEST_GENERAL_COLOR_FLAGS_WHITE = 1, /*< skip >*/
-               NM_TEST_GENERAL_COLOR_FLAGS_BLUE  = 2,
-               NM_TEST_GENERAL_COLOR_FLAGS_RED   = 4,
-               NM_TEST_GENERAL_COLOR_FLAGS_GREEN = 8,
+typedef enum /*< flags >*/ {
+    NM_TEST_GENERAL_COLOR_FLAGS_WHITE = 1, /*< skip >*/
+    NM_TEST_GENERAL_COLOR_FLAGS_BLUE  = 2,
+    NM_TEST_GENERAL_COLOR_FLAGS_RED   = 4,
+    NM_TEST_GENERAL_COLOR_FLAGS_GREEN = 8,
 } NMTestGeneralColorFlags;
 
 #endif /* _NM_TEST_GENERAL_ENUMS_H_ */
diff --git a/src/libnm-core-impl/tests/test-general.c b/src/libnm-core-impl/tests/test-general.c
index d0f85a9f..1856f6ad 100644
--- a/src/libnm-core-impl/tests/test-general.c
+++ b/src/libnm-core-impl/tests/test-general.c
@@ -5060,7 +5060,7 @@ test_setting_connection_changed_signal(void)
     ASSERT_CHANGED(nm_setting_connection_add_secondary(s_con, uuid));
     ASSERT_CHANGED(nm_setting_connection_remove_secondary(s_con, 0));
 
-    NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx < nm_g_array_len(priv->secondaries)));
+    NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx < nm_g_array_len(priv->secondaries.arr)));
     ASSERT_UNCHANGED(nm_setting_connection_remove_secondary(s_con, 1));
     g_test_assert_expected_messages();
 
diff --git a/src/libnm-core-impl/tests/test-keyfile.c b/src/libnm-core-impl/tests/test-keyfile.c
index 9bd13ffc..c163c429 100644
--- a/src/libnm-core-impl/tests/test-keyfile.c
+++ b/src/libnm-core-impl/tests/test-keyfile.c
@@ -5,16 +5,18 @@
 
 #include "libnm-core-impl/nm-default-libnm-core.h"
 
-#include "libnm-glib-aux/nm-json-aux.h"
-#include "libnm-core-intern/nm-keyfile-utils.h"
+#include "libnm-base/nm-ethtool-utils-base.h"
 #include "libnm-core-intern/nm-keyfile-internal.h"
-#include "nm-simple-connection.h"
-#include "nm-setting-connection.h"
-#include "nm-setting-wired.h"
+#include "libnm-core-intern/nm-keyfile-utils.h"
+#include "libnm-glib-aux/nm-json-aux.h"
 #include "nm-setting-8021x.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-ethtool.h"
+#include "nm-setting-proxy.h"
 #include "nm-setting-team.h"
 #include "nm-setting-user.h"
-#include "nm-setting-proxy.h"
+#include "nm-setting-wired.h"
+#include "nm-simple-connection.h"
 
 #include "libnm-glib-aux/nm-test-utils.h"
 
@@ -887,6 +889,100 @@ test_bridge_port_vlans(void)
 
 /*****************************************************************************/
 
+typedef struct {
+    bool  expect;
+    guint n_calls;
+} InvalidOptionWriteData;
+
+static gboolean
+_invalid_option_write_handler(NMConnection         *connection,
+                              GKeyFile             *keyfile,
+                              NMKeyfileHandlerType  handler_type,
+                              NMKeyfileHandlerData *handler_data,
+                              void                 *user_data)
+{
+    InvalidOptionWriteData *data = user_data;
+    const char             *message;
+    NMKeyfileWarnSeverity   severity;
+
+    g_assert(data);
+    g_assert(data->expect);
+
+    g_assert(data->n_calls == 0);
+    data->n_calls++;
+
+    switch (handler_type) {
+    case NM_KEYFILE_HANDLER_TYPE_WARN:
+        nm_keyfile_handler_data_warn_get(handler_data, &message, &severity);
+        g_assert(message && strstr(message, "ethtool.bogus"));
+        break;
+    default:
+        g_assert_not_reached();
+    }
+
+    return TRUE;
+}
+
+static void
+test_invalid_option(void)
+{
+    gs_unref_object NMConnection   *con = NULL;
+    NMSetting                      *s_ethtool;
+    nm_auto_unref_keyfile GKeyFile *kf    = NULL;
+    gs_free_error GError           *error = NULL;
+    InvalidOptionWriteData          data;
+
+    con = nmtst_create_minimal_connection("test invalid option",
+                                          NULL,
+                                          NM_SETTING_WIRED_SETTING_NAME,
+                                          NULL);
+
+    s_ethtool = nm_setting_ethtool_new();
+
+    nm_connection_add_setting(con, s_ethtool);
+
+    nm_setting_option_set_boolean(s_ethtool, NM_ETHTOOL_OPTNAME_PAUSE_RX, TRUE);
+
+    data = (InvalidOptionWriteData){};
+    kf   = nm_keyfile_write(con,
+                          NM_KEYFILE_HANDLER_FLAGS_NONE,
+                          _invalid_option_write_handler,
+                          &data,
+                          nmtst_get_rand_bool() ? &error : NULL);
+    nmtst_assert_success(kf, error);
+    nm_clear_pointer(&kf, g_key_file_unref);
+
+    nmtst_connection_normalize(con);
+
+    nmtst_assert_connection_verifies_without_normalization(con);
+
+    data = (InvalidOptionWriteData){};
+    kf   = nm_keyfile_write(con,
+                          NM_KEYFILE_HANDLER_FLAGS_NONE,
+                          _invalid_option_write_handler,
+                          &data,
+                          nmtst_get_rand_bool() ? &error : NULL);
+    nmtst_assert_success(kf, error);
+    nm_clear_pointer(&kf, g_key_file_unref);
+
+    nm_setting_option_set(s_ethtool, "bogus", g_variant_new_int64(0));
+
+    data = (InvalidOptionWriteData){
+        .expect = TRUE,
+    };
+    kf = nm_keyfile_write(con,
+                          NM_KEYFILE_HANDLER_FLAGS_NONE,
+                          _invalid_option_write_handler,
+                          &data,
+                          nmtst_get_rand_bool() ? &error : NULL);
+    nmtst_assert_success(kf, error);
+    nm_clear_pointer(&kf, g_key_file_unref);
+
+    g_assert_cmpint(data.n_calls, ==, 1);
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE();
 
 int
@@ -904,6 +1000,7 @@ main(int argc, char **argv)
     g_test_add_func("/core/keyfile/test_vpn/1", test_vpn_1);
     g_test_add_func("/core/keyfile/bridge/vlans", test_bridge_vlans);
     g_test_add_func("/core/keyfile/bridge-port/vlans", test_bridge_port_vlans);
+    g_test_add_func("/core/keyfile/invalid-option", test_invalid_option);
 
     return g_test_run();
 }
diff --git a/src/libnm-core-impl/tests/test-setting.c b/src/libnm-core-impl/tests/test-setting.c
index b260ac37..788f218d 100644
--- a/src/libnm-core-impl/tests/test-setting.c
+++ b/src/libnm-core-impl/tests/test-setting.c
@@ -4565,6 +4565,12 @@ test_setting_metadata(void)
                          == _nm_setting_property_to_dbus_fcn_direct);
                 g_assert(sip->param_spec);
                 g_assert(sip->param_spec->value_type == G_TYPE_BYTES);
+            } else if (sip->property_type->direct_type == NM_VALUE_TYPE_STRV) {
+                g_assert(g_variant_type_equal(sip->property_type->dbus_type, "as"));
+                g_assert(sip->property_type->to_dbus_fcn
+                         == _nm_setting_property_to_dbus_fcn_direct);
+                g_assert(sip->param_spec);
+                g_assert(sip->param_spec->value_type == G_TYPE_STRV);
             } else
                 g_assert_not_reached();
 
@@ -4653,7 +4659,12 @@ check_done:;
             }
             if (sip->property_type->from_dbus_fcn == _nm_setting_property_from_dbus_fcn_direct) {
                 /* for the moment, all direct properties allow transformation. */
-                g_assert(sip->property_type->from_dbus_direct_allow_transform);
+                if (NM_IN_SET(sip->property_type->direct_type,
+                              NM_VALUE_TYPE_BYTES,
+                              NM_VALUE_TYPE_STRV))
+                    g_assert(!sip->property_type->from_dbus_direct_allow_transform);
+                else
+                    g_assert(sip->property_type->from_dbus_direct_allow_transform);
             }
 
             if (sip->property_type->from_dbus_fcn == _nm_setting_property_from_dbus_fcn_gprop)
@@ -4763,10 +4774,12 @@ check_done:;
                         g_assert(NM_IS_SETTING_VPN(setting));
                         g_assert_cmpstr(sip->name, ==, NM_SETTING_VPN_SECRETS);
                     } else {
+                        NM_PRAGMA_WARNING_DISABLE_DANGLING_POINTER
                         g_error("secret %s.%s is of unexpected property type %s",
                                 nm_setting_get_name(setting),
                                 sip->name,
                                 g_type_name(sip->param_spec->value_type));
+                        NM_PRAGMA_WARNING_REENABLE
                     }
                 }
             }
@@ -4889,12 +4902,14 @@ check_done:;
 
                 /* the property-types with same content should all be shared. Here we have two that
                  * are the same content, but different instances. Bug. */
+                NM_PRAGMA_WARNING_DISABLE_DANGLING_POINTER
                 g_error("The identical property type for D-Bus type \"%s\" is used by: %s and %s. "
                         "If a NMSettInfoPropertType is identical, it should be shared by creating "
                         "a common instance of the property type",
                         (const char *) pt->dbus_type,
                         _PROP_IDX_OWNER(h_property_types, pt),
                         _PROP_IDX_OWNER(h_property_types, pt_2));
+                NM_PRAGMA_WARNING_REENABLE
             }
         }
     }