[PATCH] Add support for OpenSSL's libcrypto as crypto backend



For legal compliance and certification reasons it can be useful to use
a specific cryptographic backend in a product with NetworkManager.

This patch adds support for using OpenSSL in addition to gnutls and
nss. I have tested it using the supplied test suite in
libnm-util/tests/test-crypto.c

Since the patch doesn't affect the functionality if the default
cryptographic backend is used, it should be low risk to apply.

Signed-off-by: Thomas Horsten <thomas horsten citrix com>
---
 configure.ac                |   12 +-
 libnm-util/Makefile.am      |   11 ++
 libnm-util/crypto_openssl.c |  346 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 366 insertions(+), 3 deletions(-)
 create mode 100644 libnm-util/crypto_openssl.c



diff --git a/configure.ac b/configure.ac
index f5872c1..d611cdc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -235,10 +235,11 @@ PKG_CHECK_MODULES(POLKIT, polkit-gobject-1)
 
 AC_SUBST(POLKIT_CFLAGS)
 
-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])
   AC_DEFINE(HAVE_NSS, 1, [Define if you have NSS])
@@ -256,14 +257,19 @@ elif test x"$ac_crypto" = xgnutls; then
     AC_SUBST(LIBGCRYPT_LIBS)
     with_gnutls=yes
   fi
+elif test x"$ac_crypto" = xopenssl; then
+  PKG_CHECK_MODULES(OPENSSL, [libcrypto >= 0.9.8])
+  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 key operations])
+  AC_MSG_ERROR([Please choose either 'nss', 'gnutls', or 'openssl' for certificate and key operations])
 fi
 AM_CONDITIONAL(WITH_NSS, test x"$with_nss" != xno)
 AM_CONDITIONAL(WITH_GNUTLS, test x"$with_gnutls" != xno)
+AM_CONDITIONAL(WITH_OPENSSL, test x"$with_openssl" != xno)
 
 # Shouldn't ever trigger this, but just in case...
-if test x"$ac_nss" = xno -a x"$ac_gnutls" = xno; then
+if test x"$ac_nss" = xno -a x"$ac_gnutls" = xno -a x"$ac_openssl" = xno; then
   AC_MSG_ERROR([Could not find required development headers and libraries for '$ac_crypto'])
 fi
 
diff --git a/libnm-util/Makefile.am b/libnm-util/Makefile.am
index 8f6a0cc..baa3e13 100644
--- a/libnm-util/Makefile.am
+++ b/libnm-util/Makefile.am
@@ -73,6 +73,12 @@ libnm_util_la_CPPFLAGS += $(NSS_CFLAGS)
 libnm_util_la_LIBADD += $(NSS_LIBS)
 endif
 
+if WITH_OPENSSL
+libnm_util_la_SOURCES += crypto_openssl.c
+libnm_util_la_CPPFLAGS += $(OPENSSL_CFLAGS)
+libnm_util_la_LIBADD += $(OPENSSL_LIBS)
+endif
+
 libnm_util_includedir=$(includedir)/NetworkManager
 
 
@@ -102,6 +108,11 @@ libtest_crypto_la_CPPFLAGS += $(NSS_CFLAGS)
 libtest_crypto_la_LIBADD += $(NSS_LIBS)
 endif
 
+if WITH_OPENSSL
+libtest_crypto_la_SOURCES += crypto_openssl.c
+libtest_crypto_la_CPPFLAGS += $(OPENSSL_CFLAGS)
+libtest_crypto_la_LIBADD += $(OPENSSL_LIBS)
+endif
 
 
 pkgconfigdir = $(libdir)/pkgconfig
diff --git a/libnm-util/crypto_openssl.c b/libnm-util/crypto_openssl.c
new file mode 100644
index 0000000..b5670a6
--- /dev/null
+++ b/libnm-util/crypto_openssl.c
@@ -0,0 +1,346 @@
+/* NetworkManager Cryptographic Interface (OpenSSL version)
+ *
+ * Thomas Horsten <thomas horsten citrix 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.
+ *
+ * (C) Copyright 2010 Citrix Systems Ltd.
+ */
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/bio.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+
+#include "crypto.h"
+
+static gboolean initialized = FALSE;
+
+gboolean
+crypto_init (GError **error)
+{
+	if (initialized)
+		return TRUE;
+
+	/* Needed for the PKCS12 stuff to find the algorithms */
+	OpenSSL_add_all_algorithms ();
+	ERR_load_crypto_strings ();
+
+	initialized = TRUE;
+	return TRUE;
+}
+
+void
+crypto_deinit (void)
+{
+	if (initialized) {
+		EVP_cleanup ();
+		initialized = FALSE;
+	}
+}
+
+gboolean
+crypto_md5_hash (const char *salt,
+                 const gsize salt_len,
+                 const char *password,
+                 gsize password_len,
+                 char *buffer,
+                 gsize buflen,
+                 GError **error)
+{
+	EVP_MD_CTX ctx;
+	int nkey = buflen;
+	unsigned int digest_len = 0;
+	int count = 0;
+	unsigned char digest[EVP_MAX_MD_SIZE];
+	char *p = buffer;
+
+	if (salt)
+		g_return_val_if_fail (salt_len >= 8, FALSE);
+
+	g_return_val_if_fail (password != NULL, FALSE);
+	g_return_val_if_fail (password_len > 0, FALSE);
+	g_return_val_if_fail (buffer != NULL, FALSE);
+	g_return_val_if_fail (buflen > 0, FALSE);
+
+	while (nkey > 0) {
+		int i = 0;
+
+		EVP_MD_CTX_init (&ctx);
+		if (!EVP_DigestInit_ex (&ctx, EVP_md5 (), NULL)) {
+			g_set_error (error, NM_CRYPTO_ERROR,
+				     NM_CRYPTO_ERR_MD5_INIT_FAILED,
+				     _("Failed to initialize the MD5 engine: %s."),
+				     ERR_error_string (ERR_get_error (), NULL));
+			return FALSE;
+		}
+
+		if (count++)
+			EVP_DigestUpdate (&ctx, digest, digest_len);
+		EVP_DigestUpdate (&ctx, password, password_len);
+		if (salt)
+			EVP_DigestUpdate (&ctx, salt, 8); /* Only use 8 bytes of salt */
+		EVP_DigestFinal_ex (&ctx, digest, &digest_len);
+		EVP_MD_CTX_cleanup (&ctx);
+
+		while (nkey && (i < digest_len)) {
+			*(p++) = digest[i++];
+			nkey--;
+		}
+	}
+
+	memset (digest, 0, sizeof (digest));
+	EVP_MD_CTX_cleanup (&ctx);
+	return TRUE;
+}
+
+static char *
+crypto_cipher (const char *cipher,
+	       int key_type,
+	       const GByteArray *data,
+	       const char *iv,
+	       const gsize iv_len,
+	       const char *key,
+	       const gsize key_len,
+	       gsize *out_len,
+	       GError **error,
+	       int encrypt)
+{
+	EVP_CIPHER_CTX ctx;
+	const EVP_CIPHER *cipher_mech = NULL;
+	unsigned char *output = NULL;
+	gboolean success = FALSE;
+	int tmp_len;
+
+	if (!strcmp (cipher, CIPHER_DES_EDE3_CBC))
+		cipher_mech = EVP_des_ede3_cbc ();
+	else if (!strcmp (cipher, CIPHER_DES_CBC))
+		cipher_mech = EVP_des_cbc ();
+	if (cipher_mech == NULL) {
+		g_set_error (error, NM_CRYPTO_ERROR,
+			     NM_CRYPTO_ERR_UNKNOWN_CIPHER,
+			     _("Private key cipher '%s' was unknown."),
+			     cipher);
+		return NULL;
+	}
+
+	output = g_malloc0 (data->len + EVP_MAX_BLOCK_LENGTH);
+	if (!output) {
+		g_set_error (error, NM_CRYPTO_ERROR,
+			     NM_CRYPTO_ERR_OUT_OF_MEMORY,
+			     _("Not enough memory for decrypted key buffer."));
+		return NULL;
+	}
+
+	if ( (EVP_CIPHER_key_length (cipher_mech) != key_len) ||
+	     (EVP_CIPHER_iv_length (cipher_mech) != iv_len) ) {
+		/* Note: Fix if we need to support variable key length ciphers,
+		 * none of DES_EDE3_CBC or DES_CBC need that. */
+		g_set_error (error, NM_CRYPTO_ERROR,
+			     NM_CRYPTO_ERR_OUT_OF_MEMORY,
+			     _("Invalid key or IV length for selected cipher."));
+		return NULL;
+	}
+
+	EVP_CIPHER_CTX_init (&ctx);
+	if (!EVP_CipherInit_ex (&ctx, cipher_mech, NULL, (const unsigned char *)key, (const unsigned char *)iv, encrypt)) {
+		g_set_error (error, NM_CRYPTO_ERROR,
+			     NM_CRYPTO_ERR_CIPHER_INIT_FAILED,
+			     _("Failed to initialize the decryption cipher context: %s."),
+			     ERR_error_string (ERR_get_error (), NULL));
+		goto out;
+	}
+
+	if ( (!EVP_CipherUpdate (&ctx, output, (int *)out_len, data->data, data->len)) ||
+	     (!EVP_CipherFinal_ex (&ctx, output + *out_len, &tmp_len)) ) {
+		g_set_error (error, NM_CRYPTO_ERROR,
+			     NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED,
+			     _("Failed to decrypt the private key: %s."),
+			     ERR_error_string (ERR_get_error (), NULL));
+		goto out;
+	}
+	*out_len += tmp_len;
+	output[*out_len] = '\0';
+	success = TRUE;
+
+out:
+	if (!success) {
+		if (output) {
+			/* Don't expose key material */
+			memset (output, 0, data->len + EVP_MAX_BLOCK_LENGTH);
+			g_free (output);
+			output = NULL;
+		}
+	}
+	EVP_CIPHER_CTX_cleanup (&ctx);
+	return (char *)output;
+}
+
+char *
+crypto_encrypt (const char *cipher,
+		const GByteArray *data,
+		const char *iv,
+		const gsize iv_len,
+		const char *key,
+		const gsize key_len,
+		gsize *out_len,
+		GError **error)
+{
+	return crypto_cipher (cipher, 0, data, iv, iv_len, key, key_len, out_len, error, 1);
+}
+
+char *
+crypto_decrypt (const char *cipher,
+		int key_type,
+		GByteArray *data,
+		const char *iv,
+		const gsize iv_len,
+		const char *key,
+		const gsize key_len,
+		gsize *out_len,
+		GError **error)
+{
+	return crypto_cipher (cipher, 0, data, iv, iv_len, key, key_len, out_len, error, 0);
+}
+
+static X509 *
+crypto_load_x509 (const unsigned char *data,
+		  gsize len,
+		  GError **error,
+		  NMCryptoFileFormat *format)
+{
+	const unsigned char *p;
+	X509 *x = NULL;
+	BIO *b = NULL;
+
+	/* Try DER first */
+	p = data;
+	x = d2i_X509 (NULL, &p, len);
+	if (x) {
+		if (format)
+			*format = NM_CRYPTO_FILE_FORMAT_X509;
+		goto out;
+	}
+
+	/* And PEM next */
+	b = BIO_new_mem_buf ((void *)data, len);
+	if (!b) {
+		g_set_error (error, NM_CRYPTO_ERROR,
+			     NM_CRYPTO_ERR_CERT_FORMAT_INVALID,
+			     _("Couldn't decode certificate: %s."),
+			     ERR_error_string( ERR_get_error (), NULL));
+		goto out;
+	}
+	x = PEM_read_bio_X509 (b, NULL, 0, NULL);
+	if (!x) {
+		g_set_error (error, NM_CRYPTO_ERROR,
+			     NM_CRYPTO_ERR_CERT_FORMAT_INVALID,
+			     _("Couldn't decode certificate: %s."),
+			     ERR_error_string( ERR_get_error (), NULL));
+		goto out;
+	}
+	if (format)
+		*format = NM_CRYPTO_FILE_FORMAT_X509;
+
+out:
+	if (b)
+		BIO_free (b);
+	return x;
+}
+
+NMCryptoFileFormat
+crypto_verify_cert (const unsigned char *data,
+                    gsize len,
+                    GError **error)
+{
+	X509 *x = NULL;
+	NMCryptoFileFormat result = NM_CRYPTO_FILE_FORMAT_UNKNOWN;
+
+	x = crypto_load_x509 (data, len, error, &result);
+	if (x)
+		X509_free (x);
+	return result;
+}
+
+gboolean
+crypto_verify_pkcs12 (const GByteArray *data,
+                      const char *password,
+                      GError **error)
+{
+	BIO *b = NULL;
+	PKCS12 *p12 = NULL;
+	EVP_PKEY *pkey = NULL;
+	X509 *cert = NULL;
+	gboolean result = FALSE;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	b = BIO_new_mem_buf (data->data, data->len);
+	if (!b) {
+		g_set_error (error, NM_CRYPTO_ERROR,
+			     NM_CRYPTO_ERR_DECODE_FAILED,
+			     _("Couldn't initialize PKCS#12 decoder: %s"),
+			     ERR_error_string (ERR_get_error (), NULL));
+		goto out;
+	}
+
+	p12 = d2i_PKCS12_bio (b, NULL);
+	if (!p12) {
+		g_set_error (error, NM_CRYPTO_ERROR,
+			     NM_CRYPTO_ERR_DECODE_FAILED,
+			     _("Couldn't decode PKCS#12 file: %s"),
+			     ERR_error_string (ERR_get_error (), NULL));
+		goto out;
+	}
+
+	if (!PKCS12_parse (p12, password, &pkey, &cert, NULL)) {
+		g_set_error (error, NM_CRYPTO_ERROR,
+			     NM_CRYPTO_ERR_CIPHER_DECRYPT_FAILED,
+			     _("Couldn't verify PKCS#12 file: %s"),
+			     ERR_error_string (ERR_get_error (), NULL));
+		goto out;
+	}
+	result = TRUE;
+
+out:
+	if (pkey)
+		EVP_PKEY_free (pkey);
+	if (cert)
+		X509_free (cert);
+	if (p12)
+		PKCS12_free (p12);
+	if (b)
+		BIO_free (b);
+
+	return result;
+}
+
+gboolean
+crypto_randomize (void *buffer, gsize buffer_len, GError **error)
+{
+	if (!RAND_bytes(buffer, buffer_len)) {
+		g_set_error_literal (error, NM_CRYPTO_ERROR,
+		                     NM_CRYPTO_ERR_RANDOMIZE_FAILED,
+		                     _("Could not generate random data."));
+		return FALSE;
+	}
+	return TRUE;
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]