summaryrefslogtreecommitdiff
path: root/src/libnm-crypto
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2022-03-18 18:12:54 +0100
committerThomas Haller <thaller@redhat.com>2022-03-29 11:56:04 +0200
commit901787e06fe35bbcd8dfbb622a9e4c9b97a37698 (patch)
tree42e92b351f66675d671d1f7d197e2eee95cdf8db /src/libnm-crypto
parent3a97604a27aceb58d8edfeee7da9e56b50387a2b (diff)
downloadNetworkManager-901787e06fe35bbcd8dfbb622a9e4c9b97a37698.tar.gz
build: move nm-crypto to separate directory "src/libnm-crypto"
libnm-core currently has a dependency on crypto libraries (either "gnutls", "nss" or "null"). We need this huge dependency for few cases. Move the crypto code to a separate static library"src/libnm-crypto/libnm-crypto.la". The reasoning is that it becomes clearer where we have this dependency, to use it more consciously, and to be better see how it's used. We clearly need the crypto functionality in libnm. But do we also need it in the daemon? Could we ever link the daemon without crypto libraries? The goal of splitting the crypto part out, to better understand the crypto dependency.
Diffstat (limited to 'src/libnm-crypto')
-rw-r--r--src/libnm-crypto/README.md7
-rw-r--r--src/libnm-crypto/meson.build69
-rw-r--r--src/libnm-crypto/nm-crypto-gnutls.c414
-rw-r--r--src/libnm-crypto/nm-crypto-impl.h50
-rw-r--r--src/libnm-crypto/nm-crypto-nss.c551
-rw-r--r--src/libnm-crypto/nm-crypto-null.c103
-rw-r--r--src/libnm-crypto/nm-crypto.c1044
-rw-r--r--src/libnm-crypto/nm-crypto.h96
8 files changed, 2334 insertions, 0 deletions
diff --git a/src/libnm-crypto/README.md b/src/libnm-crypto/README.md
new file mode 100644
index 0000000000..5e83eb0188
--- /dev/null
+++ b/src/libnm-crypto/README.md
@@ -0,0 +1,7 @@
+libnm-crypto
+============
+
+libnm-core has a dependency on crypto code (either backed by
+"gnutls", "nss" or the "null" dummy implementation).
+
+libnm-core gets then statically linked into the daemon and into libnm.so.
diff --git a/src/libnm-crypto/meson.build b/src/libnm-crypto/meson.build
new file mode 100644
index 0000000000..9134c929f1
--- /dev/null
+++ b/src/libnm-crypto/meson.build
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+if crypto_nss_dep.found()
+ libnm_crypto_nss = static_library(
+ 'nm-crypto-nss',
+ sources: 'nm-crypto-nss.c',
+ include_directories: [
+ top_inc,
+ src_inc,
+ ],
+ dependencies: [
+ glib_dep,
+ crypto_nss_dep,
+ ],
+ )
+endif
+
+if crypto_gnutls_dep.found()
+ libnm_crypto_gnutls = static_library(
+ 'nm-crypto-gnutls',
+ sources: 'nm-crypto-gnutls.c',
+ include_directories: [
+ top_inc,
+ src_inc,
+ ],
+ dependencies: [
+ glib_dep,
+ crypto_gnutls_dep,
+ ],
+ )
+endif
+
+libnm_crypto_null = static_library(
+ 'nm-crypto-null',
+ sources: 'nm-crypto-null.c',
+ include_directories: [
+ top_inc,
+ src_inc,
+ ],
+ dependencies: [
+ glib_dep,
+ ],
+)
+
+if crypto == 'nss'
+ libnm_crypto_impl = libnm_crypto_nss
+elif crypto == 'gnutls'
+ libnm_crypto_impl = libnm_crypto_gnutls
+else
+ assert(crypto == 'null', 'Unexpected setting "crypto=' + crypto + '"')
+ libnm_crypto_impl = libnm_crypto_null
+endif
+
+libnm_crypto = static_library(
+ 'nm-crypto',
+ sources: [
+ 'nm-crypto.c',
+ ],
+ include_directories: [
+ top_inc,
+ src_inc,
+ ],
+ link_with: [
+ libnm_crypto_impl,
+ ],
+ dependencies: [
+ glib_dep,
+ ],
+)
diff --git a/src/libnm-crypto/nm-crypto-gnutls.c b/src/libnm-crypto/nm-crypto-gnutls.c
new file mode 100644
index 0000000000..7352a38e60
--- /dev/null
+++ b/src/libnm-crypto/nm-crypto-gnutls.c
@@ -0,0 +1,414 @@
+/* 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"
+
+/*****************************************************************************/
+
+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-crypto/nm-crypto-impl.h b/src/libnm-crypto/nm-crypto-impl.h
new file mode 100644
index 0000000000..0b7c14dc08
--- /dev/null
+++ b/src/libnm-crypto/nm-crypto-impl.h
@@ -0,0 +1,50 @@
+/* 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"
+#include "libnm-base/nm-base.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-crypto/nm-crypto-nss.c b/src/libnm-crypto/nm-crypto-nss.c
new file mode 100644
index 0000000000..cd5966c42a
--- /dev/null
+++ b/src/libnm-crypto/nm-crypto-nss.c
@@ -0,0 +1,551 @@
+/* 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 "libnm-base/nm-base.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-crypto/nm-crypto-null.c b/src/libnm-crypto/nm-crypto-null.c
new file mode 100644
index 0000000000..6f6c7f2897
--- /dev/null
+++ b/src/libnm-crypto/nm-crypto-null.c
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Christian Eggers <ceggers@arri.de>
+ * Copyright (C) 2020 - 2022 ARRI Lighting
+ */
+
+#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
+
+#include "nm-crypto-impl.h"
+
+#include "libnm-glib-aux/nm-secret-utils.h"
+
+/*****************************************************************************/
+
+gboolean
+_nm_crypto_init(GError **error)
+{
+ g_set_error(error,
+ _NM_CRYPTO_ERROR,
+ _NM_CRYPTO_ERROR_FAILED,
+ _("Compiled without crypto support."));
+ return FALSE;
+}
+
+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)
+{
+ g_set_error(error,
+ _NM_CRYPTO_ERROR,
+ _NM_CRYPTO_ERROR_FAILED,
+ _("Compiled without crypto support."));
+ return NULL;
+}
+
+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)
+{
+ g_set_error(error,
+ _NM_CRYPTO_ERROR,
+ _NM_CRYPTO_ERROR_FAILED,
+ _("Compiled without crypto support."));
+ return NULL;
+}
+
+gboolean
+_nm_crypto_verify_x509(const guint8 *data, gsize len, GError **error)
+{
+ g_set_error(error,
+ _NM_CRYPTO_ERROR,
+ _NM_CRYPTO_ERROR_FAILED,
+ _("Compiled without crypto support."));
+ return FALSE;
+}
+
+gboolean
+_nm_crypto_verify_pkcs12(const guint8 *data, gsize data_len, const char *password, GError **error)
+{
+ g_set_error(error,
+ _NM_CRYPTO_ERROR,
+ _NM_CRYPTO_ERROR_FAILED,
+ _("Compiled without crypto support."));
+ return FALSE;
+}
+
+gboolean
+_nm_crypto_verify_pkcs8(const guint8 *data,
+ gsize data_len,
+ gboolean is_encrypted,
+ const char *password,
+ GError **error)
+{
+ g_set_error(error,
+ _NM_CRYPTO_ERROR,
+ _NM_CRYPTO_ERROR_FAILED,
+ _("Compiled without crypto support."));
+ return FALSE;
+}
+
+gboolean
+_nm_crypto_randomize(void *buffer, gsize buffer_len, GError **error)
+{
+ g_set_error(error,
+ _NM_CRYPTO_ERROR,
+ _NM_CRYPTO_ERROR_FAILED,
+ _("Compiled without crypto support."));
+ return FALSE;
+}
diff --git a/src/libnm-crypto/nm-crypto.c b/src/libnm-crypto/nm-crypto.c
new file mode 100644
index 0000000000..56f297e605
--- /dev/null
+++ b/src/libnm-crypto/nm-crypto.c
@@ -0,0 +1,1044 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Dan Williams <dcbw@redhat.com>
+ * Copyright (C) 2007 - 2018 Red Hat, Inc.
+ */
+
+#include "libnm-glib-aux/nm-default-glib-i18n-lib.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"
+
+/*****************************************************************************/
+
+#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-crypto/nm-crypto.h b/src/libnm-crypto/nm-crypto.h
new file mode 100644
index 0000000000..a740c43c5b
--- /dev/null
+++ b/src/libnm-crypto/nm-crypto.h
@@ -0,0 +1,96 @@
+/* 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__ */