diff options
author | Thomas Haller <thaller@redhat.com> | 2022-11-03 11:04:23 +0100 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2022-11-03 11:09:08 +0100 |
commit | 432f39feaa3b5cf363c0899ac80d87a04f14dc7d (patch) | |
tree | 4be6d0684003e429343541344341ef5013246e86 | |
parent | dc26e65928740bd20ab01aee422ee9401e0fe11c (diff) | |
download | NetworkManager-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.am | 23 | ||||
-rw-r--r-- | configure.ac | 16 | ||||
-rw-r--r-- | po/POTFILES.in | 1 | ||||
-rw-r--r-- | src/libnm-crypto/nm-crypto-openssl.c | 320 |
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; +} |