summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2022-11-03 11:04:23 +0100
committerThomas Haller <thaller@redhat.com>2022-11-03 11:09:08 +0100
commit432f39feaa3b5cf363c0899ac80d87a04f14dc7d (patch)
tree4be6d0684003e429343541344341ef5013246e86
parentdc26e65928740bd20ab01aee422ee9401e0fe11c (diff)
downloadNetworkManager-th/openssl.tar.gz
libnm/crypto: add OpenSSL crypto backendth/openssl
Based-on-patch-by: Joel Holdsworth <joel.holdsworth@vcatechnology.com> https://mail.gnome.org/archives/networkmanager-list/2015-November/msg00062.html
-rw-r--r--Makefile.am23
-rw-r--r--configure.ac16
-rw-r--r--po/POTFILES.in1
-rw-r--r--src/libnm-crypto/nm-crypto-openssl.c320
4 files changed, 356 insertions, 4 deletions
diff --git a/Makefile.am b/Makefile.am
index 8e60a5f5bd..90c021f176 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1548,8 +1548,27 @@ src_libnm_crypto_libnm_crypto_nss_la_LIBADD = \
$(NSS_LIBS)
endif
+if HAVE_CRYPTO_OPENSSL
+if WITH_OPENSSL
+libnm_crypto_lib = src/libnm-crypto/libnm-crypto-openssl.la
+else
+check_ltlibraries += src/libnm-crypto/libnm-crypto-openssl.la
+endif
+
+src_libnm_crypto_libnm_crypto_openssl_la_SOURCES = src/libnm-crypto/nm-crypto-openssl.c
+src_libnm_crypto_libnm_crypto_openssl_la_CPPFLAGS = \
+ $(src_libnm_core_impl_libnm_core_impl_la_CPPFLAGS) \
+ $(OPENSSL_CFLAGS)
+src_libnm_crypto_libnm_crypto_openssl_la_LDFLAGS = \
+ $(src_libnm_core_impl_libnm_core_impl_la_LDFLAGS)
+src_libnm_crypto_libnm_crypto_openssl_la_LIBADD = \
+ $(GLIB_LIBS) \
+ $(OPENSSL_LIBS)
+endif
+
if !WITH_GNUTLS
if !WITH_NSS
+if !WITH_OPENSSL
libnm_crypto_lib = src/libnm-crypto/libnm-crypto-null.la
else
check_ltlibraries += src/libnm-crypto/libnm-crypto-null.la
@@ -1557,6 +1576,9 @@ endif
else
check_ltlibraries += src/libnm-crypto/libnm-crypto-null.la
endif
+else
+check_ltlibraries += src/libnm-crypto/libnm-crypto-null.la
+endif
src_libnm_crypto_libnm_crypto_null_la_SOURCES = src/libnm-crypto/nm-crypto-null.c
src_libnm_crypto_libnm_crypto_null_la_CPPFLAGS = \
@@ -1576,6 +1598,7 @@ EXTRA_DIST += \
src/libnm-crypto/meson.build \
src/libnm-crypto/nm-crypto-gnutls.c \
src/libnm-crypto/nm-crypto-nss.c \
+ src/libnm-crypto/nm-crypto-openssl.c \
$(NULL)
###############################################################################
diff --git a/configure.ac b/configure.ac
index 323ffa469b..31899cb9a5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -707,7 +707,7 @@ fi
AM_CONDITIONAL(WITH_FIREWALLD_ZONE, test "${enable_firewalld_zone}" = "yes")
PKG_CHECK_MODULES(GNUTLS, [gnutls >= 2.12], [have_crypto_gnutls=yes], [have_crypto_gnutls=no])
-PKG_CHECK_MODULES(NSS, [nss], [have_crypto_nss=yes], [have_crypto_nss=yes])
+PKG_CHECK_MODULES(NSS, [nss], [have_crypto_nss=yes], [have_crypto_nss=no])
if test "${have_crypto_nss}" = "yes"; then
# Work around a pkg-config bug (fdo #29801) where exists != usable
FOO=`$PKG_CONFIG --cflags --libs nss`
@@ -715,11 +715,14 @@ if test "${have_crypto_nss}" = "yes"; then
have_crypto_nss=no
fi
fi
+PKG_CHECK_MODULES(OPENSSL, [libcrypto >= 1.0.1a], [have_crypto_openssl=yes], [have_crypto_openssl=no])
+
AM_CONDITIONAL(HAVE_CRYPTO_GNUTLS, test "${have_crypto_gnutls}" = 'yes')
AM_CONDITIONAL(HAVE_CRYPTO_NSS, test "${have_crypto_nss}" = 'yes')
+AM_CONDITIONAL(HAVE_CRYPTO_OPENSSL, test "${have_crypto_openssl}" = 'yes')
AC_ARG_WITH(crypto,
- AS_HELP_STRING([--with-crypto=nss|gnutls|null],
+ AS_HELP_STRING([--with-crypto=nss|gnutls|openssl|null],
[Cryptography library to use for certificate and key operations]),
with_crypto=$withval,
with_crypto=nss)
@@ -731,13 +734,18 @@ elif test "$with_crypto" = 'gnutls'; then
if test "${have_crypto_gnutls}" != "yes"; then
AC_MSG_ERROR([No usable gnutls found for --with-crypto=gnutls])
fi
+elif test "$with_crypto" = 'openssl'; then
+ if test "${have_crypto_openssl}" != "yes"; then
+ AC_MSG_ERROR([No usable openssl found for --with-crypto=openssl])
+ fi
elif test "$with_crypto" = 'null'; then
:
else
- AC_MSG_ERROR([Please choose either 'nss', 'gnutls' or 'null' for certificate and crypto operations])
+ AC_MSG_ERROR([Please choose either 'nss', 'gnutls', 'openssl' or 'null' for certificate and crypto operations])
fi
AM_CONDITIONAL(WITH_NSS, test "$with_crypto" = 'nss')
AM_CONDITIONAL(WITH_GNUTLS, test "$with_crypto" = 'gnutls')
+AM_CONDITIONAL(WITH_OPENSSL, test "$with_crypto" = 'openssl')
GLIB_MAKEFILE='$(top_srcdir)/Makefile.glib'
AC_SUBST(GLIB_MAKEFILE)
@@ -1439,7 +1447,7 @@ echo " valgrind: $with_valgrind $with_valgrind_suppressions"
echo " code coverage: $enable_code_coverage"
echo " LTO: $enable_lto"
echo " linker garbage collection: $enable_ld_gc"
-echo " crypto: $with_crypto (have-gnutls: $have_crypto_gnutls, have-nss: $have_crypto_nss)"
+echo " crypto: $with_crypto (have-gnutls: $have_crypto_gnutls, have-nss: $have_crypto_nss, have-openssl $have_crypto_openssl)"
echo " sanitizers: $sanitizers"
echo " Mozilla Public Suffix List: $with_libpsl"
echo " eBPF: $have_ebpf"
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 82895456b0..c06d3a49da 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -131,6 +131,7 @@ src/libnm-core-impl/nm-vpn-plugin-info.c
src/libnm-crypto/nm-crypto-gnutls.c
src/libnm-crypto/nm-crypto-nss.c
src/libnm-crypto/nm-crypto-null.c
+src/libnm-crypto/nm-crypto-openssl.c
src/libnm-crypto/nm-crypto.c
src/libnm-glib-aux/nm-shared-utils.c
src/libnm-log-core/nm-logging.c
diff --git a/src/libnm-crypto/nm-crypto-openssl.c b/src/libnm-crypto/nm-crypto-openssl.c
new file mode 100644
index 0000000000..22030df5ff
--- /dev/null
+++ b/src/libnm-crypto/nm-crypto-openssl.c
@@ -0,0 +1,320 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Joel Holdsworth <joel.holdsworth@vcatechnology.com>
+ * Copyright (C) 2015 VCA Technology Ltd.
+ */
+
+#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
+
+#include "nm-crypto-impl.h"
+
+#include <openssl/engine.h>
+#include <openssl/evp.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+#include <openssl/ssl.h>
+
+gboolean
+_nm_crypto_init(GError **error)
+{
+ static gboolean initialized = FALSE;
+
+ if (initialized)
+ return TRUE;
+
+ CRYPTO_malloc_init();
+ OpenSSL_add_all_algorithms();
+ ENGINE_load_builtin_engines();
+
+ initialized = TRUE;
+ return TRUE;
+}
+
+static const EVP_CIPHER *
+get_cipher(const char *cipher, GError **error)
+{
+ if (strcmp(cipher, CIPHER_DES_EDE3_CBC) == 0)
+ return EVP_des_ede3_cbc();
+ else if (strcmp(cipher, CIPHER_DES_CBC) == 0)
+ return EVP_des_cbc();
+ else if (strcmp(cipher, CIPHER_AES_CBC) == 0)
+ return EVP_aes_128_cbc();
+ else {
+ g_set_error(error,
+ NM_CRYPTO_ERROR,
+ NM_CRYPTO_ERROR_UNKNOWN_CIPHER,
+ _("Private key cipher '%s' was unknown."),
+ cipher);
+ return NULL;
+ }
+}
+
+char *
+crypto_decrypt(const char *cipher,
+ int key_type,
+ const guint8 *data,
+ gsize data_len,
+ const char *iv,
+ const gsize iv_len,
+ const char *key,
+ const gsize key_len,
+ gsize *out_len,
+ GError **error)
+{
+ const EVP_CIPHER *evp_cipher = NULL;
+ EVP_CIPHER_CTX ctx;
+ char *output = NULL;
+ gboolean success = FALSE;
+ gsize real_iv_len = 0;
+ int initial_len = 0, final_len = 0;
+
+ if (!(evp_cipher = get_cipher(cipher, error)))
+ return NULL;
+
+ real_iv_len = EVP_CIPHER_iv_length(evp_cipher);
+ 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 %zd)."),
+ real_iv_len);
+ return NULL;
+ }
+
+ EVP_CIPHER_CTX_init(&ctx);
+ if (!EVP_DecryptInit_ex(&ctx,
+ evp_cipher,
+ NULL,
+ (const unsigned char *) key,
+ (const unsigned char *) iv)) {
+ g_set_error(error,
+ NM_CRYPTO_ERROR,
+ NM_CRYPTO_ERROR_DECRYPTION_FAILED,
+ _("Failed to initialize the decryption cipher context."));
+ goto out;
+ }
+
+ output = g_malloc0(data_len);
+
+ if (!EVP_DecryptUpdate(&ctx, (unsigned char *) output, &initial_len, data, data_len)) {
+ g_set_error(error,
+ NM_CRYPTO_ERROR,
+ NM_CRYPTO_ERROR_DECRYPTION_FAILED,
+ _("Failed to decrypt the private key."));
+ goto out;
+ }
+
+ /* Finalise decryption, and check the padding */
+ if (!EVP_DecryptFinal_ex(&ctx, (unsigned char *) output + initial_len, &final_len)) {
+ g_set_error(error,
+ NM_CRYPTO_ERROR,
+ NM_CRYPTO_ERROR_DECRYPTION_FAILED,
+ _("Failed to finalize decryption of the private key."));
+ goto out;
+ }
+
+ *out_len = initial_len + final_len;
+ success = TRUE;
+
+out:
+ if (!success && output) {
+ /* Don't expose key material */
+ memset(output, 0, data_len);
+ g_free(output);
+ output = NULL;
+ }
+ EVP_CIPHER_CTX_cleanup(&ctx);
+ return output;
+}
+
+char *
+crypto_encrypt(const char *cipher,
+ const guint8 *data,
+ gsize data_len,
+ const char *iv,
+ const gsize iv_len,
+ const char *key,
+ gsize key_len,
+ gsize *out_len,
+ GError **error)
+{
+ const EVP_CIPHER *evp_cipher = NULL;
+ EVP_CIPHER_CTX ctx;
+ char *output = NULL;
+ gboolean success = FALSE;
+ gsize pad_len, output_len;
+ int initial_len = 0, final_len = 0;
+
+ if (!(evp_cipher = get_cipher(cipher, error)))
+ 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);
+ output_len = data_len + pad_len;
+ output = g_malloc0(output_len);
+
+ EVP_CIPHER_CTX_init(&ctx);
+ if (!EVP_EncryptInit_ex(&ctx,
+ evp_cipher,
+ NULL,
+ (const unsigned char *) key,
+ (const unsigned char *) iv)) {
+ g_set_error(error,
+ NM_CRYPTO_ERROR,
+ NM_CRYPTO_ERROR_DECRYPTION_FAILED,
+ _("Failed to initialize the encryption cipher context."));
+ goto out;
+ }
+
+ if (!EVP_EncryptUpdate(&ctx, (unsigned char *) output, &initial_len, data, data_len)) {
+ g_set_error(error,
+ NM_CRYPTO_ERROR,
+ NM_CRYPTO_ERROR_DECRYPTION_FAILED,
+ _("Failed to encrypt the private key."));
+ goto out;
+ }
+
+ /* Finalise encryption, and add the padding */
+ if (!EVP_EncryptFinal_ex(&ctx, (unsigned char *) output + initial_len, &final_len)) {
+ g_set_error(error,
+ NM_CRYPTO_ERROR,
+ NM_CRYPTO_ERROR_DECRYPTION_FAILED,
+ _("Failed to finalize encryption of the private key."));
+ goto out;
+ }
+
+ *out_len = initial_len + final_len;
+ success = TRUE;
+
+out:
+ if (!success && output) {
+ /* Don't expose key material */
+ memset(output, 0, output_len);
+ g_free(output);
+ output = NULL;
+ }
+ EVP_CIPHER_CTX_cleanup(&ctx);
+ return output;
+}
+
+NMCryptoFileFormat
+crypto_verify_cert(const unsigned char *data, gsize len, GError **error)
+{
+ BIO *in = NULL;
+ X509 *x = NULL;
+
+ /* Try PEM */
+ in = BIO_new_mem_buf((void *) data, len);
+ x = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
+ BIO_free(in);
+ X509_free(x);
+ if (x)
+ return NM_CRYPTO_FILE_FORMAT_X509;
+
+ /* Try DER */
+ in = BIO_new_mem_buf((void *) data, len);
+ x = d2i_X509_bio(in, NULL);
+ BIO_free(in);
+ X509_free(x);
+ if (x)
+ return NM_CRYPTO_FILE_FORMAT_X509;
+
+ g_set_error(error,
+ NM_CRYPTO_ERROR,
+ NM_CRYPTO_ERROR_INVALID_DATA,
+ _("Couldn't decode certificate"));
+ return NM_CRYPTO_FILE_FORMAT_UNKNOWN;
+}
+
+gboolean
+crypto_verify_pkcs12(const guint8 *data, gsize data_len, const char *password, GError **error)
+{
+ BIO *in = NULL;
+ PKCS12 *p12 = NULL;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail(data != NULL, FALSE);
+
+ in = BIO_new_mem_buf((void *) data, data_len);
+ p12 = d2i_PKCS12_bio(in, NULL);
+ BIO_free(in);
+
+ if (!p12) {
+ /* Currently only DER format PKCS12 files are supported. */
+ g_set_error(error,
+ NM_CRYPTO_ERROR,
+ NM_CRYPTO_ERROR_INVALID_DATA,
+ _("Couldn't decode PKCS#12 file"));
+ goto out;
+ }
+
+ if (password) {
+ if (!(success = PKCS12_verify_mac(p12, password, -1)))
+ g_set_error(error,
+ NM_CRYPTO_ERROR,
+ NM_CRYPTO_ERROR_DECRYPTION_FAILED,
+ _("Couldn't verify PKCS#12 file."));
+ } else
+ success = TRUE;
+
+out:
+ if (p12)
+ PKCS12_free(p12);
+ return success;
+}
+
+gboolean
+crypto_verify_pkcs8(const guint8 *data,
+ gsize data_len,
+ gboolean is_encrypted,
+ const char *password,
+ GError **error)
+{
+ BIO *in = NULL;
+ X509_SIG *p8 = NULL;
+ PKCS8_PRIV_KEY_INFO *p8inf = NULL;
+
+ g_return_val_if_fail(data != NULL, FALSE);
+
+ if (is_encrypted) {
+ in = BIO_new_mem_buf((void *) data, data_len);
+ p8 = d2i_PKCS8_bio(in, NULL);
+ BIO_free(in);
+
+ if (p8) {
+ X509_SIG_free(p8);
+ return TRUE;
+ } else {
+ g_set_error(error,
+ NM_CRYPTO_ERROR,
+ NM_CRYPTO_ERROR_INVALID_DATA,
+ _("Couldn't decode PKCS#8 file"));
+ }
+ } else {
+ in = BIO_new_mem_buf((void *) data, data_len);
+ p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(in, NULL);
+ BIO_free(in);
+
+ if (p8inf) {
+ PKCS8_PRIV_KEY_INFO_free(p8inf);
+ return p8inf->broken == 0;
+ } else {
+ g_set_error(error,
+ NM_CRYPTO_ERROR,
+ NM_CRYPTO_ERROR_INVALID_DATA,
+ _("Couldn't decode PKCS#8 file"));
+ }
+ }
+
+ return FALSE;
+}
+
+gboolean
+_nm_crypto_randomize(void *buffer, gsize buffer_len, GError **error)
+{
+ RAND_bytes(buffer, buffer_len);
+ buffer_len = (buffer_len > 16) ? 16 : buffer_len;
+ return TRUE;
+}