--- configure.ac | 16 ++- libnm-core/Makefile.am | 6 + libnm-core/crypto_openssl.c | 324 ++++++++++++++++++++++++++++++++++++++++++++ po/POTFILES.in | 1 + 4 files changed, 340 insertions(+), 7 deletions(-) create mode 100644 libnm-core/crypto_openssl.c
diff --git a/configure.ac b/configure.ac index 7b4ca9a..83ae5a0 100644 --- a/configure.ac +++ b/configure.ac @@ -594,10 +594,12 @@ else fi AC_SUBST(NM_MODIFY_SYSTEM_POLICY) -AC_ARG_WITH(crypto, AS_HELP_STRING([--with-crypto=nss|gnutls], [Cryptography library to use for certificate and key operations]),ac_crypto=$withval, ac_crypto=nss) +AC_ARG_WITH(crypto, AS_HELP_STRING([--with-crypto=nss|gnutls|openssl], + [Cryptography library to use for certificate and key operations]), ac_crypto=$withval, ac_crypto=nss) with_nss=no with_gnutls=no +with_openssl=no if test x"$ac_crypto" = xnss; then PKG_CHECK_MODULES(NSS, [nss >= 3.11]) @@ -613,16 +615,16 @@ elif test x"$ac_crypto" = xgnutls; then PKG_CHECK_MODULES(GNUTLS, [gnutls >= 2.12]) AC_DEFINE(HAVE_GNUTLS, 1, [Define if you have libgnutls]) with_gnutls=yes +elif test x"$ac_crypto" = xopenssl; then + PKG_CHECK_MODULES(OPENSSL, [libcrypto >= 1.0.1a]) + AC_DEFINE(HAVE_OPENSSL, 1, [Define if you have OpenSSL]) + with_openssl=yes else - AC_MSG_ERROR([Please choose either 'nss' or 'gnutls' for certificate and crypto operations]) + AC_MSG_ERROR([Please choose either 'nss', 'gnutls', 'openssl' for certificate and crypto operations]) fi AM_CONDITIONAL(WITH_NSS, test x"$with_nss" != xno) AM_CONDITIONAL(WITH_GNUTLS, test x"$with_gnutls" != xno) - -# Shouldn't ever trigger this, but just in case... -if test x"$ac_nss" = xno -a x"$ac_gnutls" = xno; then - AC_MSG_ERROR([Could not find required development headers and libraries for '$ac_crypto']) -fi +AM_CONDITIONAL(WITH_OPENSSL, test x"$with_openssl" != xno) GLIB_MAKEFILE='$(top_srcdir)/Makefile.glib' AC_SUBST(GLIB_MAKEFILE) diff --git a/libnm-core/Makefile.am b/libnm-core/Makefile.am index 55ed2f6..23fba5b 100644 --- a/libnm-core/Makefile.am +++ b/libnm-core/Makefile.am @@ -47,5 +47,11 @@ libnm_core_la_SOURCES += crypto_nss.c libnm_core_la_LIBADD += $(NSS_LIBS) endif +if WITH_OPENSSL +AM_CPPFLAGS += $(OPENSSL_CFLAGS) +libnm_core_la_SOURCES += crypto_openssl.c +libnm_core_la_LIBADD += $(OPENSSL_LIBS) +endif + BUILT_SOURCES = $(GLIB_GENERATED) CLEANFILES = $(BUILT_SOURCES) diff --git a/libnm-core/crypto_openssl.c b/libnm-core/crypto_openssl.c new file mode 100644 index 0000000..2952abb --- /dev/null +++ b/libnm-core/crypto_openssl.c @@ -0,0 +1,324 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ + +/* + * Joel Holdsworth <joel.holdswo...@vcatechnology.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * Copyright 2015 VCA Technology Ltd. + */ + +#include "config.h" + +#include <openssl/engine.h> +#include <openssl/evp.h> +#include <openssl/pkcs12.h> +#include <openssl/rand.h> +#include <openssl/ssl.h> + +#include "nm-default.h" +#include "crypto.h" +#include "nm-errors.h" + +static gboolean initialized = FALSE; + +gboolean +crypto_init (GError **error) +{ + 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 +crypto_randomize (void *buffer, gsize buffer_len, GError **error) +{ + RAND_bytes(buffer, buffer_len); + buffer_len = (buffer_len > 16) ? 16 : buffer_len; + return TRUE; +} diff --git a/po/POTFILES.in b/po/POTFILES.in index 8bffd70..759e222 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -47,6 +47,7 @@ clients/tui/nmtui.c libnm-core/crypto.c libnm-core/crypto_gnutls.c libnm-core/crypto_nss.c +libnm-core/crypto_openssl.c libnm-core/nm-connection.c libnm-core/nm-dbus-utils.c libnm-core/nm-keyfile-reader.c -- 1.9.1 _______________________________________________ networkmanager-list mailing list networkmanager-list@gnome.org https://mail.gnome.org/mailman/listinfo/networkmanager-list