[PATCH] Add support for OpenSSL's libcrypto as crypto backend
- From: Thomas Horsten <thomas horsten citrix com>
- To: networkmanager-list gnome org
- Cc: Thomas Horsten <thomas horsten com>
- Subject: [PATCH] Add support for OpenSSL's libcrypto as crypto backend
- Date: Thu, 8 Apr 2010 19:16:07 +0100
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]