cpw.ljfisher.ssl_client_auth: 6d90a006: Added gnutls implementations of PurplePr...

lucas.fisher at gmail.com lucas.fisher at gmail.com
Sat Apr 16 19:35:59 EDT 2011


----------------------------------------------------------------------
Revision: 6d90a006c807a328d919a2e8bda1f057950039d0
Parent:   785c700532142e03e67bde9af7205df53ad64b64
Author:   lucas.fisher at gmail.com
Date:     04/16/11 16:58:36
Branch:   im.pidgin.cpw.ljfisher.ssl_client_auth
URL: http://d.pidgin.im/viewmtn/revision/info/6d90a006c807a328d919a2e8bda1f057950039d0

Changelog: 

Added gnutls implementations of PurplePrivateKey, PurplePkcs12.
Setup client-side auth using certs/keys from the PurpleSslConnection.

Changes against parent 785c700532142e03e67bde9af7205df53ad64b64

  patched  libpurple/plugins/ssl/ssl-gnutls.c

-------------- next part --------------
============================================================
--- libpurple/plugins/ssl/ssl-gnutls.c	1da6bdaf868c12329feac7b2dd7141f296a984ea
+++ libpurple/plugins/ssl/ssl-gnutls.c	bcc0d140dcaa871a6cfafaec029ff0930b5807be
@@ -22,6 +22,8 @@
 #include "internal.h"
 #include "debug.h"
 #include "certificate.h"
+#include "privatekey.h"
+#include "pkcs12.h"
 #include "plugin.h"
 #include "sslconn.h"
 #include "version.h"
@@ -31,6 +33,7 @@
 
 #include <gnutls/gnutls.h>
 #include <gnutls/x509.h>
+#include <gnutls/pkcs12.h>
 
 typedef struct
 {
@@ -39,10 +42,21 @@ typedef struct
 	guint handshake_timer;
 } PurpleSslGnutlsData;
 
+static gboolean
+ssl_gnutls_set_client_auth(gnutls_certificate_client_credentials cred,
+				PurpleCertificate * pcrt,
+				PurplePrivateKey * pkey);
+
 #define PURPLE_SSL_GNUTLS_DATA(gsc) ((PurpleSslGnutlsData *)gsc->private_data)
 
 static gnutls_certificate_client_credentials xcred = NULL;
 
+/* The GNUTLS get client credentials callback does not support user supplied
+   data so we have to maintain that outselves. Annoying. 
+   The key is a gnutls_session_t pointer and the value is a PurpleSslConnection pointer.
+*/
+GHashTable *sslConnTable;
+
 #ifdef HAVE_GNUTLS_PRIORITY_FUNCS
 /* Priority strings.  The default one is, well, the default (and is always
  * set).  The hash table is of the form hostname => priority (both
@@ -178,17 +192,22 @@ ssl_gnutls_init_gnutls(void)
 	/* TODO: I can likely remove this */
 	gnutls_certificate_set_x509_trust_file(xcred, "ca.pem",
 		GNUTLS_X509_FMT_PEM);
+//	gnutls_certificate_set_x509_simple_pkcs12_file(xcred, "test.p12", GNUTLS_X509_FMT_DER, "abcd");
 }
 
 static gboolean
 ssl_gnutls_init(void)
 {
+	/* Use direct hashing since the key is a pointer. */
+	sslConnTable = g_hash_table_new(NULL, NULL);
+
 	return TRUE;
 }
 
 static void
 ssl_gnutls_uninit(void)
 {
+	g_hash_table_destroy(sslConnTable);
 	gnutls_global_deinit();
 
 	gnutls_certificate_free_credentials(xcred);
@@ -392,6 +411,10 @@ ssl_gnutls_connect(PurpleSslConnection *
 	gsc->private_data = gnutls_data;
 
 	gnutls_init(&gnutls_data->session, GNUTLS_CLIENT);
+
+	/* State for the credentials retrieve function. */
+	g_hash_table_insert(sslConnTable, gnutls_data->session, gsc);
+
 #ifdef HAVE_GNUTLS_PRIORITY_FUNCS
 	{
 		const char *prio_str = NULL;
@@ -416,6 +439,18 @@ ssl_gnutls_connect(PurpleSslConnection *
 	gnutls_certificate_type_set_priority(gnutls_data->session,
 		cert_type_priority);
 
+	purple_debug_info("gnutls", "client cert id: %s cert:%p key:%p\n",
+		gsc->certificate_id, gsc->certificate, gsc->key);
+
+	if (NULL != gsc->certificate_id 
+			&& NULL != gsc->certificate
+			&& NULL != gsc->key) {
+		purple_debug_info("gnutls/handshake",
+			"Authenticating with certificate/key %s\n",
+			gsc->certificate_id);
+		ssl_gnutls_set_client_auth(xcred, gsc->certificate, gsc->key);
+	}
+
 	gnutls_credentials_set(gnutls_data->session, GNUTLS_CRD_CERTIFICATE,
 		xcred);
 
@@ -453,6 +488,9 @@ ssl_gnutls_close(PurpleSslConnection *gs
 	if (gnutls_data->handshake_timer)
 		purple_timeout_remove(gnutls_data->handshake_timer);
 
+	/* Remove state needed for credential retrieve callback. */
+	g_hash_table_remove(sslConnTable, gnutls_data->session);
+
 	gnutls_bye(gnutls_data->session, GNUTLS_SHUT_RDWR);
 
 	gnutls_deinit(gnutls_data->session);
@@ -575,6 +613,7 @@ ssl_gnutls_get_peer_certificates(PurpleS
 	return peer_certs;
 }
 
+ 
 /************************************************************************/
 /* X.509 functionality                                                  */
 /************************************************************************/
@@ -617,7 +656,7 @@ x509_crtdata_delref(x509_crtdata_t *cd)
 /** Helper macro to retrieve the GnuTLS crt_t from a PurpleCertificate */
 #define X509_GET_GNUTLS_DATA(pcrt) ( ((x509_crtdata_t *) (pcrt->data))->crt)
 
-/** Transforms a gnutls_datum containing an X.509 certificate into a Certificate instance under the x509_gnutls scheme
+/** Transforms a gnutls_datum containing an X.509 certificate into a Certificate instance under the x509_gnutls scheme.
  *
  * @param dt   Datum to transform
  * @param mode GnuTLS certificate format specifier (GNUTLS_X509_FMT_PEM for
@@ -1142,6 +1181,8 @@ x509_times (PurpleCertificate *crt, time
 	return success;
 }
 
+
+
 /* X.509 certificate operations provided by this plugin */
 static PurpleCertificateScheme x509_gnutls = {
 	"x509",                          /* Scheme name */
@@ -1165,6 +1206,1031 @@ static PurpleCertificateScheme x509_gnut
 
 };
 
+/**********************************************************
+ * X.509 Private Key operations                           *
+ **********************************************************/
+
+const gchar * KEY_SCHEME_NAME = "x509";
+
+static PurplePrivateKeyScheme x509_key_gnutls;
+
+/** Refcounted GnuTLS private key data instance */
+typedef struct {
+	gint refcount;
+	gnutls_x509_privkey_t key;
+} x509_keydata_t;
+
+/** Helper functions for reference counting */
+static x509_keydata_t *
+x509_keydata_addref(x509_keydata_t *kd)
+{
+	(kd->refcount)++;
+	return kd;
+}
+
+static void
+x509_keydata_delref(x509_keydata_t *kd)
+{
+	(kd->refcount)--;
+
+	if (kd->refcount < 0)
+		g_critical("Refcount of x509_keydata_t is %d, which is less "
+				"than zero!\n", kd->refcount);
+
+	/* If the refcount reaches zero, kill the structure */
+	if (kd->refcount <= 0) {
+		/* Kill the internal data */
+		if (kd->key)
+			gnutls_x509_privkey_deinit( kd->key );
+		/* And kill the struct */
+		g_free( kd );
+	}
+}
+
+/** Helper macro to retrieve the GnuTLS crt_t from a PurplePrivateKey */
+#define X509_GET_GNUTLS_KEYDATA(pkey) ( ((x509_keydata_t *) (pkey->data))->key)
+
+static gboolean
+read_pkcs8_file(const gchar* filename, gnutls_datum_t *dt, gnutls_x509_crt_fmt_t * fmt)
+{
+	gchar *buf = NULL; /* Used to load the raw file data */
+	gsize buf_sz;      /* Size of the above */
+
+	purple_debug_info("gnutls/x509key",
+			  "Attempting to load PKCS8 file from %s\n",
+			  filename);
+
+	/* Next, we'll simply yank the entire contents of the file
+	   into memory */
+	/* TODO: Should I worry about very large files here? */
+	g_return_val_if_fail(
+		g_file_get_contents(filename,
+			    &buf,
+			    &buf_sz,
+			    NULL      /* No error checking for now */
+		),
+		FALSE);
+	
+	*fmt = GNUTLS_X509_FMT_DER;
+	#define PEM_PKCS8_HDR "-----BEGIN ENCRYPTED PRIVATE KEY-----"
+	if (0 == strncmp(buf, PEM_PKCS8_HDR, sizeof(PEM_PKCS8_HDR)-1)) 
+		*fmt = GNUTLS_X509_FMT_PEM;
+
+	dt->data = (unsigned char*) buf;
+	dt->size = buf_sz;
+
+	return TRUE;
+}
+
+static PurplePrivateKey*
+x509_import_key(const gchar * filename, const gchar * password)
+{
+	/* Internal key data structure */
+	x509_keydata_t *keydat;
+	/* New key to return */
+	PurplePrivateKey * key;
+	gnutls_datum_t dt;
+	gnutls_x509_crt_fmt_t fmt;
+	int rv;
+
+	/* Allocate and prepare the internal key data */
+	keydat = g_new0(x509_keydata_t, 1);
+	if (GNUTLS_E_SUCCESS != gnutls_x509_privkey_init(&keydat->key)) {
+		g_free(keydat);
+		return NULL;
+	}
+	keydat->refcount = 0;
+
+	key = g_new0(PurplePrivateKey, 1);
+	if (NULL == key) {
+		gnutls_x509_privkey_deinit(keydat->key);
+		g_free(keydat);
+		return NULL;
+	}
+
+	key->scheme = &x509_key_gnutls;
+	key->data = keydat; 
+
+	if (read_pkcs8_file(filename, &dt, &fmt)) {
+		rv = gnutls_x509_privkey_import_pkcs8(keydat->key, &dt, fmt, password, 0);
+		if (GNUTLS_E_SUCCESS != rv) {
+			purple_debug_error("gnutls/x509key",
+					   "Error importing key from %s: %s\n",
+					   filename, gnutls_strerror(rv));
+			gnutls_x509_privkey_deinit(keydat->key);
+			g_free(keydat);
+			return NULL;
+		}
+	}
+
+	return key;
+}
+
+static gboolean 
+x509_export_key(const gchar *filename, PurplePrivateKey *key, const gchar* password)
+{
+	gnutls_x509_privkey_t key_dat; /* GnuTLS key struct */
+	int ret;
+	gchar * out_buf; /* Data to output */
+	size_t out_size; /* Output size */
+	gboolean success = FALSE;
+	unsigned int flags;
+
+	/* Paranoia paranoia paranoia! */
+	g_return_val_if_fail(filename, FALSE);
+	g_return_val_if_fail(key, FALSE);
+	g_return_val_if_fail(key->scheme == &x509_key_gnutls, FALSE);
+	g_return_val_if_fail(key->data, FALSE);
+
+	key_dat = X509_GET_GNUTLS_KEYDATA(key);
+
+	/* TODO: Check version of gnutls and use AES if possible */
+	flags = GNUTLS_PKCS_USE_PBES2_3DES;
+
+	/* Obtain the output size required */
+	out_size = 0;
+	ret = gnutls_x509_privkey_export_pkcs8(key_dat, GNUTLS_X509_FMT_PEM,
+					       password,
+					       flags,
+	 				       NULL, /* Provide no buffer yet */
+					       &out_size /* Put size here */);
+	purple_debug_error("gnutls/x509key", "querying for size and export pkcs8 returned (%d) %s with size %d\n",
+			ret, gnutls_strerror(ret), out_size);
+	g_return_val_if_fail(ret == GNUTLS_E_SHORT_MEMORY_BUFFER, FALSE);
+
+	/* Now allocate a buffer and *really* export it */
+
+	/* TODO: Again we seem to randomly get a "just not quite big enough" size above. */
+	out_size += 100;
+
+	out_buf = g_new0(gchar, out_size);
+	ret = gnutls_x509_privkey_export_pkcs8(key_dat, GNUTLS_X509_FMT_PEM,
+					       password,
+					       flags,
+					       out_buf, /* Export to our new buffer */
+					       &out_size /* Put size here */);
+
+	if (GNUTLS_E_SUCCESS != ret) {
+		purple_debug_error("gnutls/x509key",
+				   "Failed to export key to buffer:%s\n",
+				   gnutls_strerror(ret));
+		g_free(out_buf);
+		return FALSE;
+	}
+
+	/* Write it out to an actual file */
+	success = purple_util_write_data_to_file_absolute(filename,
+							  out_buf, out_size);
+
+	g_free(out_buf);
+	return success;
+}
+
+static PurplePrivateKey *
+x509_copy_key(PurplePrivateKey *key)
+{
+	x509_keydata_t *keydat;
+	PurplePrivateKey *newkey;
+
+	g_return_val_if_fail(key, NULL);
+	g_return_val_if_fail(key->scheme == &x509_key_gnutls, NULL);
+
+	keydat = (x509_keydata_t *) key->data;
+
+	newkey = g_new0(PurplePrivateKey, 1);
+	newkey->scheme = &x509_key_gnutls;
+	newkey->data = x509_keydata_addref(keydat);
+
+	return newkey;
+}
+
+static void 
+x509_destroy_key(PurplePrivateKey * key)
+{
+	if (NULL == key) return;
+	
+	g_return_if_fail(key->data != NULL);
+	g_return_if_fail(key->scheme != NULL);
+
+	/* Check that the scheme is x509_key_gnutls */
+	if ( key->scheme != &x509_key_gnutls ) {
+		purple_debug_error("gnutls",
+				   "destroy_key attempted on key of wrong scheme (scheme was %s, expected %s)\n",
+				   key->scheme->name,
+				   KEY_SCHEME_NAME);
+		return;
+	}
+
+	/* Use the reference counting system to free (or not) the
+	   underlying data */
+	x509_keydata_delref((x509_keydata_t *)key->data);
+
+	/* Kill the structure itself */
+	g_free(key);
+	return;
+}
+
+static gchar*
+x509_get_unique_key_id(PurplePrivateKey *key)
+{
+	gnutls_x509_privkey_t key_dat; /* GnuTLS key struct */
+	int ret;
+	guchar * out_buf = NULL; /* Data to output */
+	size_t out_size = 0; /* Output size */
+
+	g_return_val_if_fail(key, FALSE);
+	g_return_val_if_fail(key->scheme == &x509_key_gnutls, FALSE);
+	g_return_val_if_fail(key->data, FALSE);
+
+	key_dat = X509_GET_GNUTLS_KEYDATA(key);
+
+	/* Get output size */
+	ret = gnutls_x509_privkey_get_key_id(key_dat, 0, NULL, &out_size);
+
+	g_return_val_if_fail(ret == GNUTLS_E_SHORT_MEMORY_BUFFER, NULL);
+
+	out_buf = g_new0(guchar, out_size);
+
+	ret = gnutls_x509_privkey_get_key_id(key_dat, 0, out_buf, &out_size);
+	if (GNUTLS_E_SUCCESS != ret) {
+		purple_debug_error("gnutls/x509key",
+				   "Failed to get key id: %s\n",
+				   gnutls_strerror(ret));
+		g_free(out_buf);
+		return NULL;
+	}
+
+	return (gchar*)out_buf;
+}
+
+static PurplePrivateKeyScheme x509_key_gnutls = {
+	"x509",                           /* Scheme name */
+	N_("X.509 Private Keys"),         /* User-visibile scheme name */
+	x509_import_key,                  /* Key import */
+	x509_export_key,                  /* Key export */
+        x509_copy_key,                    /* Copy key */
+	x509_destroy_key,                 /* Destroy key */
+	x509_get_unique_key_id,           /* Get key id */
+
+	NULL,
+	NULL,
+	NULL
+};
+
+/**********************************************************
+ *  PKCS12 operations                                     *
+ **********************************************************/
+
+/**
+ * Borrowed from gnutls_x509.c.  This is only exposed via:
+ *  gnutls_certificate_set_x509_simple_pkcs12_mem
+ *  gnutls_certificate_set_x509_simple_pkcs12_file
+ * which operate on a gnutls_credentials object. However, purple
+ * prefers to directly manage its own certificates and (now) keys.
+ * PKCS12 is complex so we should use code that (probably) already
+ * works.
+ *
+ * Adding a keystore abstraction would probably be better. Let each
+ * SSL crypto backend supply its own keystore???
+ */
+
+#define gnutls_assert() purple_debug_info("gnutls/x509", "parse_pkcs12")
+
+static int
+parse_pkcs12 (gnutls_certificate_credentials_t res,
+	      gnutls_pkcs12_t p12,
+	      const char *password,
+	      gnutls_x509_privkey_t * key,
+	      gnutls_x509_crt_t * cert, gnutls_x509_crl_t * crl)
+{
+  gnutls_pkcs12_bag_t bag = NULL;
+  int idx = 0;
+  int ret;
+  size_t cert_id_size = 0;
+  size_t key_id_size = 0;
+  unsigned char cert_id[20];
+  unsigned char key_id[20];
+  int privkey_ok = 0;
+
+  *cert = NULL;
+  *key = NULL;
+  *crl = NULL;
+
+  /* find the first private key */
+  for (;;)
+    {
+      int elements_in_bag;
+      int i;
+
+      ret = gnutls_pkcs12_bag_init (&bag);
+      if (ret < 0)
+	{
+	  bag = NULL;
+	  gnutls_assert ();
+	  goto done;
+	}
+
+      ret = gnutls_pkcs12_get_bag (p12, idx, bag);
+      if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+	break;
+      if (ret < 0)
+	{
+	  gnutls_assert ();
+	  goto done;
+	}
+
+      ret = gnutls_pkcs12_bag_get_type (bag, 0);
+      if (ret < 0)
+	{
+	  gnutls_assert ();
+	  goto done;
+	}
+
+      if (ret == GNUTLS_BAG_ENCRYPTED)
+	{
+	  ret = gnutls_pkcs12_bag_decrypt (bag, password);
+	  if (ret < 0)
+	    {
+	      gnutls_assert ();
+	      goto done;
+	    }
+	}
+
+      elements_in_bag = gnutls_pkcs12_bag_get_count (bag);
+      if (elements_in_bag < 0)
+	{
+	  gnutls_assert ();
+	  goto done;
+	}
+
+      for (i = 0; i < elements_in_bag; i++)
+	{
+	  int type;
+	  gnutls_datum_t data;
+
+	  type = gnutls_pkcs12_bag_get_type (bag, i);
+	  if (type < 0)
+	    {
+	      gnutls_assert ();
+	      goto done;
+	    }
+
+	  ret = gnutls_pkcs12_bag_get_data (bag, i, &data);
+	  if (ret < 0)
+	    {
+	      gnutls_assert ();
+	      goto done;
+	    }
+
+	  switch (type)
+	    {
+	    case GNUTLS_BAG_PKCS8_ENCRYPTED_KEY:
+	    case GNUTLS_BAG_PKCS8_KEY:
+	      if (*key != NULL)	/* too simple to continue */
+		{
+		  gnutls_assert ();
+		  break;
+		}
+
+	      ret = gnutls_x509_privkey_init (key);
+	      if (ret < 0)
+		{
+		  gnutls_assert ();
+		  goto done;
+		}
+
+	      ret = gnutls_x509_privkey_import_pkcs8
+		(*key, &data, GNUTLS_X509_FMT_DER, password,
+		 type == GNUTLS_BAG_PKCS8_KEY ? GNUTLS_PKCS_PLAIN : 0);
+	      if (ret < 0)
+		{
+		  gnutls_assert ();
+		  gnutls_x509_privkey_deinit (*key);
+		  goto done;
+		}
+
+	      key_id_size = sizeof (key_id);
+	      ret =
+		gnutls_x509_privkey_get_key_id (*key, 0, key_id,
+						&key_id_size);
+	      if (ret < 0)
+		{
+		  gnutls_assert ();
+		  gnutls_x509_privkey_deinit (*key);
+		  goto done;
+		}
+
+	      privkey_ok = 1;	/* break */
+	      break;
+	    default:
+	      break;
+	    }
+	}
+
+      idx++;
+      gnutls_pkcs12_bag_deinit (bag);
+
+      if (privkey_ok != 0)	/* private key was found */
+	break;
+    }
+
+  if (privkey_ok == 0)		/* no private key */
+    {
+      gnutls_assert ();
+      return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
+    }
+
+  /* now find the corresponding certificate 
+   */
+  idx = 0;
+  bag = NULL;
+  for (;;)
+    {
+      int elements_in_bag;
+      int i;
+
+      ret = gnutls_pkcs12_bag_init (&bag);
+      if (ret < 0)
+	{
+	  bag = NULL;
+	  gnutls_assert ();
+	  goto done;
+	}
+
+      ret = gnutls_pkcs12_get_bag (p12, idx, bag);
+      if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+	break;
+      if (ret < 0)
+	{
+	  gnutls_assert ();
+	  goto done;
+	}
+
+      ret = gnutls_pkcs12_bag_get_type (bag, 0);
+      if (ret < 0)
+	{
+	  gnutls_assert ();
+	  goto done;
+	}
+
+      if (ret == GNUTLS_BAG_ENCRYPTED)
+	{
+	  ret = gnutls_pkcs12_bag_decrypt (bag, password);
+	  if (ret < 0)
+	    {
+	      gnutls_assert ();
+	      goto done;
+	    }
+	}
+
+      elements_in_bag = gnutls_pkcs12_bag_get_count (bag);
+      if (elements_in_bag < 0)
+	{
+	  gnutls_assert ();
+	  goto done;
+	}
+
+      for (i = 0; i < elements_in_bag; i++)
+	{
+	  int type;
+	  gnutls_datum_t data;
+
+	  type = gnutls_pkcs12_bag_get_type (bag, i);
+	  if (type < 0)
+	    {
+	      gnutls_assert ();
+	      goto done;
+	    }
+
+	  ret = gnutls_pkcs12_bag_get_data (bag, i, &data);
+	  if (ret < 0)
+	    {
+	      gnutls_assert ();
+	      goto done;
+	    }
+
+	  switch (type)
+	    {
+	    case GNUTLS_BAG_CERTIFICATE:
+	      if (*cert != NULL)	/* no need to set it again */
+		{
+		  gnutls_assert ();
+		  break;
+		}
+
+	      ret = gnutls_x509_crt_init (cert);
+	      if (ret < 0)
+		{
+		  gnutls_assert ();
+		  goto done;
+		}
+
+	      ret =
+		gnutls_x509_crt_import (*cert, &data, GNUTLS_X509_FMT_DER);
+	      if (ret < 0)
+		{
+		  gnutls_assert ();
+		  gnutls_x509_crt_deinit (*cert);
+		  goto done;
+		}
+
+	      /* check if the key id match */
+	      cert_id_size = sizeof (cert_id);
+	      ret =
+		gnutls_x509_crt_get_key_id (*cert, 0, cert_id, &cert_id_size);
+	      if (ret < 0)
+		{
+		  gnutls_assert ();
+		  gnutls_x509_crt_deinit (*cert);
+		  goto done;
+		}
+
+	      if (memcmp (cert_id, key_id, cert_id_size) != 0)
+		{		/* they don't match - skip the certificate */
+		  gnutls_x509_crt_deinit (*cert);
+		  *cert = NULL;
+		}
+	      break;
+
+	    case GNUTLS_BAG_CRL:
+	      if (*crl != NULL)
+		{
+		  gnutls_assert ();
+		  break;
+		}
+
+	      ret = gnutls_x509_crl_init (crl);
+	      if (ret < 0)
+		{
+		  gnutls_assert ();
+		  goto done;
+		}
+
+	      ret = gnutls_x509_crl_import (*crl, &data, GNUTLS_X509_FMT_DER);
+	      if (ret < 0)
+		{
+		  gnutls_assert ();
+		  gnutls_x509_crl_deinit (*crl);
+		  goto done;
+		}
+	      break;
+
+	    case GNUTLS_BAG_ENCRYPTED:
+	      /* XXX Bother to recurse one level down?  Unlikely to
+	         use the same password anyway. */
+	    case GNUTLS_BAG_EMPTY:
+	    default:
+	      break;
+	    }
+	}
+
+      idx++;
+      gnutls_pkcs12_bag_deinit (bag);
+    }
+
+  ret = 0;
+
+done:
+  if (bag)
+    gnutls_pkcs12_bag_deinit (bag);
+
+  return ret;
+}
+
+static gboolean
+read_pkcs12_file(const gchar* filename, gnutls_datum_t *dt, gnutls_x509_crt_fmt_t * fmt)
+{
+	gchar *buf = NULL; /* Used to load the raw file data */
+	gsize buf_sz;      /* Size of the above */
+
+	purple_debug_info("gnutls",
+			  "Attempting to load PKCS12 file from %s\n",
+			  filename);
+
+	/* Next, we'll simply yank the entire contents of the file
+	   into memory */
+	/* TODO: Should I worry about very large files here? */
+	g_return_val_if_fail(
+		g_file_get_contents(filename,
+			    &buf,
+			    &buf_sz,
+			    NULL      /* No error checking for now */
+		),
+		FALSE);
+	
+	*fmt = GNUTLS_X509_FMT_DER;
+	#define PEM_PKCS12_HDR "-----BEGIN PKCS12-----"
+	if (0 == strncmp(buf, PEM_PKCS12_HDR, sizeof(PEM_PKCS12_HDR)-1)) 
+		*fmt = GNUTLS_X509_FMT_PEM;
+
+	dt->data = (unsigned char*) buf;
+	dt->size = buf_sz;
+
+	return TRUE;
+}
+
+
+/**
+ * Derived from gnutls_certificate_set_x509_simple_pkcs12_mem in
+ * gnutls_x509.c. Modified to return PurpleCertificate and PurplePrivateKey
+ * objects.
+ */
+static gboolean
+x509_import_pkcs12_from_file(const gchar* filename,
+			     const gchar* password,
+			     PurpleCertificate **crt,
+			     PurplePrivateKey **key)
+{
+	gnutls_pkcs12_t p12;
+	gnutls_certificate_credentials_t res = NULL;
+	gnutls_x509_crl_t crl = NULL;
+	gnutls_datum_t dt;
+	gnutls_x509_crt_fmt_t fmt;
+	x509_crtdata_t *crtdat;
+	x509_keydata_t *keydat;
+
+	int rv;
+
+	if (!read_pkcs12_file(filename, &dt, &fmt)) {
+		purple_debug_error("gnutls",
+			"Failed to load PKCS12 file from %s\n",
+			filename);
+		return FALSE;
+	}
+
+	purple_debug_info("gnutls", "pkcs12 import: file:%s size:%d fmt:%d\n", filename, dt.size, fmt);
+ 
+	rv = gnutls_pkcs12_init (&p12);
+	if (GNUTLS_E_SUCCESS != rv) {
+		purple_debug_error("gnutls/x509",
+			"pkcs12_init error: %s\n", gnutls_strerror(rv));
+		return FALSE;
+	}
+
+	rv = gnutls_pkcs12_import(p12, &dt, fmt, 0);
+	if (GNUTLS_E_SUCCESS != rv) {
+		purple_debug_error("gnutls/x509",
+			"pkcs12_import error: %s\n", gnutls_strerror(rv));
+ 		gnutls_pkcs12_deinit (p12);
+		return FALSE;
+	}
+
+	if (password) {
+		rv = gnutls_pkcs12_verify_mac(p12, (const char*)password);
+		if (GNUTLS_E_SUCCESS != rv) {
+			purple_debug_error("gnutls/x509",
+				"pkcs12_verify_mac error: %s\n", gnutls_strerror(rv));
+			gnutls_pkcs12_deinit (p12);
+			return FALSE;
+		}
+ 	}
+		
+	/* Allocate and prepare the internal key and crt data */
+	crtdat = g_new0(x509_crtdata_t, 1);
+	crtdat->crt = NULL;
+	crtdat->refcount = 0;
+
+	keydat = g_new0(x509_keydata_t, 1);
+	keydat->key = NULL;
+	keydat->refcount = 0;
+
+	rv = parse_pkcs12 (res, p12, password, &(keydat->key), &(crtdat->crt), &crl);
+//	gnutls_pkcs12_deinit (p12);
+	if (GNUTLS_E_SUCCESS != rv) {
+		purple_debug_error("gnutls/x509",
+			"parse_pkcs12 error: %s\n", gnutls_strerror(rv));
+		gnutls_x509_crt_deinit (crtdat->crt);
+		gnutls_x509_privkey_deinit(keydat->key);
+		gnutls_x509_crl_deinit (crl);
+		g_free(crtdat);
+		g_free(keydat);
+		return FALSE;
+	}
+
+	/* Just deinit since we aren't using and 
+	   want to avoid modifying parse_pkcs12() */
+	gnutls_x509_crl_deinit (crl);
+
+	if (NULL == keydat->key || NULL == crtdat->crt) {
+		purple_debug_error("gnutls/x509",
+			"%s get a cert. %s get a key",
+			crtdat->crt ? "Did" : "Did not",
+			keydat->key ? "Did" : "Did not");
+		return FALSE;
+	}
+
+	*crt = g_new0(PurpleCertificate, 1);
+	(*crt)->scheme = &x509_gnutls;
+	(*crt)->data = x509_crtdata_addref(crtdat);
+
+	*key = g_new0(PurplePrivateKey, 1);
+	(*key)->scheme = &x509_key_gnutls;
+	(*key)->data = x509_keydata_addref(keydat);
+
+	/* check if the key and certificate found match */
+#if 0
+	if (key && (ret = _gnutls_check_key_cert_match (res)) < 0) {
+		gnutls_assert ();
+		to done;
+	}
+#endif
+
+	return TRUE;
+}
+
+/** Export PurpleCertificate and PurplePrivateKey to a PKCS12 file.
+ */
+/* Derived from generate_pkcs12() in certtool.c in the gnutls source. */
+static gboolean
+x509_export_pkcs12_to_filename(const gchar* filename, const gchar* password, PurpleCertificate *purple_crt, PurplePrivateKey *purple_key)
+{
+	gnutls_pkcs12_t pkcs12 = NULL;
+	gnutls_x509_crt_t crts[1];
+	gnutls_x509_privkey_t key = NULL;
+	int result;
+	size_t size;
+	gnutls_datum_t data;
+	const char *pass;
+	const char *name;
+	unsigned int flags, i;
+	gnutls_datum_t key_id;
+	unsigned char _key_id[20];
+	int indx;
+	size_t ncrts;
+	gboolean success = FALSE;
+	gnutls_pkcs12_bag_t kbag = NULL;
+	gnutls_pkcs12_bag_t bag = NULL;
+	char *key_buf = NULL;
+	char *out_buf = NULL;
+
+	crts[0] = X509_GET_GNUTLS_DATA(purple_crt);
+	key = X509_GET_GNUTLS_KEYDATA(purple_key);
+	ncrts = 1;
+
+	name = x509_common_name(purple_crt);
+	if (NULL == name) {
+		purple_debug_error("gnutls/pkcs12", "export: can't get common name for cert\n");
+		goto done;
+	}
+
+	result = gnutls_pkcs12_init (&pkcs12);
+	if (result < 0) {
+		purple_debug_error("gnutls/pkcs12", "export: pkcs12_init: %s\n", gnutls_strerror (result));
+		goto done;
+	}
+
+	for (i = 0; i < ncrts; i++)
+	{
+		result = gnutls_pkcs12_bag_init (&bag);
+		if (result < 0) {
+			purple_debug_error("gnutls/pkcs12", "export: bag_init: %s\n", gnutls_strerror (result));
+			goto done;
+		}
+
+		result = gnutls_pkcs12_bag_set_crt (bag, crts[i]);
+		if (result < 0) {
+			purple_debug_error ("gnutls/pkcs12", "export: set_crt[%d]: %s\n", i,
+				 gnutls_strerror (result));
+			goto done;
+		}
+
+		indx = result;
+
+		result = gnutls_pkcs12_bag_set_friendly_name (bag, indx, name);
+		if (result < 0) {
+			purple_debug_error ("gnutls/pkcs12", "bag_set_friendly_name: %s\n",
+				gnutls_strerror (result));
+			goto done;
+		}
+
+		size = sizeof (_key_id);
+		result = gnutls_x509_crt_get_key_id (crts[i], 0, _key_id, &size);
+		if (result < 0) {
+			purple_debug_error("gnutls/pkcs12", "key_id[%d]: %s\n", i,
+				 gnutls_strerror(result));
+			goto done;
+		}
+
+		key_id.data = _key_id;
+		key_id.size = size;
+
+		result = gnutls_pkcs12_bag_set_key_id (bag, indx, &key_id);
+		if (result < 0) {
+			purple_debug_error("gnutls/pkcs12", "bag_set_key_id: %s\n",
+				 gnutls_strerror(result));
+			goto done;
+		}
+
+		/* TODO: Check GNUTLS version and use AES if possible */
+		flags = GNUTLS_PKCS_USE_PBES2_3DES;
+
+		result = gnutls_pkcs12_bag_encrypt (bag, password, flags);
+		if (result < 0) {
+			purple_debug_error("gnutls/pkcs12", "bag_encrypt: %s\n", gnutls_strerror (result));
+			goto done;
+		}
+
+		result = gnutls_pkcs12_set_bag (pkcs12, bag);
+		if (result < 0) {
+			purple_debug_error("gnutls/pkcs12", "set_bag: %s\n", gnutls_strerror (result));
+			goto done;
+		}
+
+		gnutls_pkcs12_bag_deinit(bag);
+		bag = NULL;
+	}
+
+
+	result = gnutls_pkcs12_bag_init (&kbag);
+	if (result < 0) {
+		purple_debug_error("gnutls/pkcs12", "bag_init: %s\n", gnutls_strerror (result));
+		goto done;
+	}
+
+	/* TODO: Check GNUTLS version and use AES if possible */
+	flags = GNUTLS_PKCS_USE_PBES2_3DES;
+
+	size = 0;
+	result = gnutls_x509_privkey_export_pkcs8 (key, GNUTLS_X509_FMT_DER,
+					pass, flags, NULL, &size);
+
+	if (result != GNUTLS_E_SHORT_MEMORY_BUFFER) {
+		purple_debug_error("gnutls/pkcs12", "Can't get pkcs8 memory size.\n");
+		goto done;
+	}
+
+	purple_debug_info("gnutls/pkcs12", "Got pkcs8 export memory size = %d\n", size);
+
+	/* TODO: Above should give us the correct size, but doesn't. In fact, it seems
+	 * everytime I call it with the new buffer it wants something bigger. So we just
+	 * add on extra 100 bytes and hope for the best.
+	 */
+
+	size += 100;
+	key_buf = g_new0(char, size);
+	if (!key_buf) {
+		goto done;
+	}
+
+	result = gnutls_x509_privkey_export_pkcs8 (key, GNUTLS_X509_FMT_DER,
+					password, flags, key_buf, &size);
+	if (result < 0) {
+		purple_debug_error("gnutls/pkcs12", "key_export: size: %d; error: %s\n",
+			size, gnutls_strerror (result));
+		goto done;
+	}
+
+	data.data = (unsigned char*)key_buf;
+	data.size = size;
+	result =
+		gnutls_pkcs12_bag_set_data (kbag,
+				GNUTLS_BAG_PKCS8_ENCRYPTED_KEY, &data);
+	if (result < 0) {
+		purple_debug_error("gnutls/pkcs12", "bag_set_data: %s\n", gnutls_strerror (result));
+		goto done;
+	}
+
+	indx = result;
+
+	result = gnutls_pkcs12_bag_set_friendly_name (kbag, indx, name);
+	if (result < 0) {
+		purple_debug_error("gnutls/pkcs12", "bag_set_friendly_name: %s\n",
+			 gnutls_strerror(result));
+		goto done;
+	}
+
+	size = sizeof (_key_id);
+	result = gnutls_x509_privkey_get_key_id (key, 0, _key_id, &size);
+	if (result < 0) {
+		purple_debug_error("gnutls/pkcs12", "key_id: %s\n", gnutls_strerror (result));
+		goto done;
+	}
+
+	key_id.data = _key_id;
+	key_id.size = size;
+
+	result = gnutls_pkcs12_bag_set_key_id (kbag, indx, &key_id);
+	if (result < 0) {
+		purple_debug_error("gnutls/pkcs12", "bag_set_key_id: %s\n",
+			 gnutls_strerror(result));
+		goto done;
+	}
+
+	result = gnutls_pkcs12_set_bag (pkcs12, kbag);
+	if (result < 0) {
+		purple_debug_error("gnutls/pkcs12", "set_bag: %s\n", gnutls_strerror (result));
+		goto done;
+	}
+
+	result = gnutls_pkcs12_generate_mac (pkcs12, password);
+	if (result < 0) {
+		purple_debug_error("gnutls/pkcs12", "generate_mac: %s\n", gnutls_strerror (result));
+		goto done;
+	}
+
+	size = 0;
+	result = gnutls_pkcs12_export (pkcs12, GNUTLS_X509_FMT_PEM, NULL, &size);
+
+	if (result != GNUTLS_E_SHORT_MEMORY_BUFFER) {
+		purple_debug_error("gnutls/pkcs12", "Can't get pkcs12 memory size.\n");
+		goto done;
+	}
+
+	out_buf = g_new0(char, size);
+	if (NULL == out_buf) {
+		purple_debug_error("gnutls/pkcs12", "output buf allocation failure\n");
+		goto done;
+	}
+
+	result = gnutls_pkcs12_export (pkcs12, GNUTLS_X509_FMT_PEM, out_buf, &size);
+	if (result < 0) {
+		purple_debug_error("gnutls/pkcs12", "pkcs12_export: %s\n", gnutls_strerror (result));
+		goto done;
+	}
+
+	success = purple_util_write_data_to_file_absolute(filename,
+							  out_buf, size);
+
+done:
+	g_free(key_buf);
+	g_free(out_buf);
+	gnutls_pkcs12_bag_deinit(bag);
+	gnutls_pkcs12_bag_deinit(kbag);
+	gnutls_pkcs12_deinit(pkcs12);
+
+	return success;
+}
+
+static gboolean 
+pkcs12_import(const gchar *filename, const gchar *password,
+	      PurpleCertificate **crt, PurplePrivateKey **key)
+{
+	g_return_val_if_fail(filename, FALSE);
+	g_return_val_if_fail(password, FALSE);
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(key, FALSE);
+
+
+	return x509_import_pkcs12_from_file(filename, password, crt, key);
+}
+
+static gboolean 
+pkcs12_export(const gchar *filename, const gchar *password,
+	      PurpleCertificate *crt, PurplePrivateKey *key)
+{
+	g_return_val_if_fail(filename, FALSE);
+	g_return_val_if_fail(password, FALSE);
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(key, FALSE);
+
+	g_return_val_if_fail(key->scheme == &x509_key_gnutls, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+
+	return x509_export_pkcs12_to_filename(filename, password, crt, key);
+}
+
+
+static PurplePkcs12Scheme pkcs12_gnutls = {
+	"pkcs12",                         /* Scheme name */
+	N_("PKCS12"),                     /* User-visible scheme name */
+	pkcs12_import,                    /* PKCS12 import */
+	pkcs12_export,                    /* PKCS12 export */
+
+	NULL,
+	NULL,
+	NULL
+};
+
+/**********************************************************************
+ * Setting the Purple Certificate and Private Key for authentication  *
+ **********************************************************************/
+
+static gboolean
+ssl_gnutls_set_client_auth(gnutls_certificate_client_credentials cred, PurpleCertificate * pcrt, PurplePrivateKey * pkey)
+{
+	gnutls_x509_crt_t cert_list[1];
+	int rv;
+
+	g_return_val_if_fail(pcrt, FALSE);
+	g_return_val_if_fail(pkey, FALSE);
+	g_return_val_if_fail(pcrt->scheme == &x509_gnutls, FALSE);
+	g_return_val_if_fail(pkey->scheme == &x509_key_gnutls, FALSE);
+
+	if (NULL != xcred) {	
+		cert_list[0] = X509_GET_GNUTLS_DATA(pcrt);
+		rv = gnutls_certificate_set_x509_key(cred, cert_list, 1, X509_GET_GNUTLS_KEYDATA(pkey));
+		if (GNUTLS_E_SUCCESS != rv) {
+			purple_debug_error("gnutls/ssl",
+					  "Failed to set add certs to credentials: %s\n",
+					  gnutls_strerror(rv));
+			return FALSE;
+		}
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
 static PurpleSslOps ssl_ops =
 {
 	ssl_gnutls_init,
@@ -1193,6 +2259,8 @@ plugin_load(PurplePlugin *plugin)
 
 	/* Register that we're providing an X.509 CertScheme */
 	purple_certificate_register_scheme( &x509_gnutls );
+	purple_privatekey_register_scheme( &x509_key_gnutls );
+	purple_pkcs12_register_scheme( &pkcs12_gnutls );
 
 	return TRUE;
 }
@@ -1205,6 +2273,8 @@ plugin_unload(PurplePlugin *plugin)
 	}
 
 	purple_certificate_unregister_scheme( &x509_gnutls );
+	purple_privatekey_unregister_scheme( &x509_key_gnutls );
+	purple_pkcs12_unregister_scheme( &pkcs12_gnutls );
 
 	return TRUE;
 }


More information about the Commits mailing list