changeset 18939:f46ec4d6a385

propagate from branch 'im.pidgin.pidgin' (head d2f50519c5ed668dd980277afdc25d71ccb8a852) to branch 'im.pidgin.soc.2007.certmgr' (head ed3c7d774326b4d168945508111f230f60c79c37)
author William Ehlhardt <williamehlhardt@gmail.com>
date Fri, 22 Jun 2007 16:30:30 +0000
parents a573a67c80a4 (current diff) f2ddc4b10d72 (diff)
children 5fa287322a82
files
diffstat 9 files changed, 789 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/Makefile.am	Thu Jun 21 18:08:48 2007 +0000
+++ b/libpurple/Makefile.am	Fri Jun 22 16:30:30 2007 +0000
@@ -36,6 +36,7 @@
 	accountopt.c \
 	blist.c \
 	buddyicon.c \
+	certificate.c \
 	cipher.c \
 	circbuffer.c \
 	cmds.c \
@@ -85,6 +86,7 @@
 	accountopt.h \
 	blist.h \
 	buddyicon.h \
+	certificate.h \
 	cipher.h \
 	circbuffer.h \
 	cmds.h \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/certificate.c	Fri Jun 22 16:30:30 2007 +0000
@@ -0,0 +1,95 @@
+/**
+ * @file certificate.h Public-Key Certificate API
+ * @ingroup core
+ */
+
+/*
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <glib.h>
+
+#include "certificate.h"
+#include "debug.h"
+
+/** List holding pointers to all registered certificate schemes */
+static GList *cert_schemes = NULL;
+
+PurpleCertificateScheme *
+purple_certificate_find_scheme(const gchar *name)
+{
+	PurpleCertificateScheme *scheme = NULL;
+	GList *l;
+
+	g_return_val_if_fail(name, NULL);
+
+	/* Traverse the list of registered schemes and locate the
+	   one whose name matches */
+	for(l = cert_schemes; l; l = l->next) {
+		scheme = (PurpleCertificateScheme *)(l->data);
+
+		/* Name matches? that's our man */
+		if(!g_ascii_strcasecmp(scheme->name, name))
+			return scheme;
+	}
+
+	purple_debug_warning("certificate",
+			     "CertificateScheme %s requested but not found.\n",
+			     name);
+
+	/* TODO: Signalling and such? */
+	
+	return NULL;
+}
+
+gboolean
+purple_certificate_register_scheme(PurpleCertificateScheme *scheme)
+{
+	g_return_val_if_fail(scheme != NULL, FALSE);
+
+	/* Make sure no scheme is registered with the same name */
+	if (purple_certificate_find_scheme(scheme->name) != NULL) {
+		return FALSE;
+	}
+
+	/* Okay, we're golden. Register it. */
+	cert_schemes = g_list_append(cert_schemes, scheme);
+
+	/* TODO: Signalling and such? */
+	return TRUE;
+}
+
+gboolean
+purple_certificate_unregister_scheme(PurpleCertificateScheme *scheme)
+{
+	if (NULL == scheme) {
+		purple_debug_warning("certificate",
+				     "Attempting to unregister NULL scheme");
+	}
+
+	/* TODO: signalling? */
+
+	/* TODO: unregister all CertificatePools for this scheme! */
+	cert_schemes = g_list_remove(cert_schemes, scheme);
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/certificate.h	Fri Jun 22 16:30:30 2007 +0000
@@ -0,0 +1,233 @@
+/**
+ * @file certificate.h Public-Key Certificate API
+ * @ingroup core
+ */
+
+/*
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _PURPLE_CERTIFICATE_H
+#define _PURPLE_CERTIFICATE_H
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+typedef enum
+{
+	PURPLE_CERTIFICATE_INVALID = 0,
+	PURPLE_CERTIFICATE_VALID = 1
+} PurpleCertificateVerificationStatus;
+
+typedef struct _PurpleCertificate PurpleCertificate;
+typedef struct _PurpleCertificateScheme PurpleCertificateScheme;
+typedef struct _PurpleCertificateVerifier PurpleCertificateVerifier;
+typedef struct _PurpleCertificateVerificationRequest PurpleCertificateVerificationRequest;
+
+/** A certificate instance
+ *
+ *  An opaque data structure representing a single certificate under some
+ *  CertificateScheme
+ */
+struct _PurpleCertificate
+{
+	/** Scheme this certificate is under */
+	PurpleCertificateScheme * scheme;
+	/** Opaque pointer to internal data */
+	gpointer data;
+};
+
+/** A certificate type
+ *
+ *  A CertificateScheme must implement all of the fields in the structure,
+ *  and register it using purple_certificate_register_scheme()
+ *
+ *  There may be only ONE CertificateScheme provided for each certificate
+ *  type, as specified by the "name" field.
+ */
+struct _PurpleCertificateScheme
+{
+	/** Name of the certificate type
+	 *  ex: "x509", "pgp", etc.
+	 *  This must be globally unique - you may not register more than one
+	 *  CertificateScheme of the same name at a time.
+	 */
+	gchar * name;
+
+	/** User-friendly name for this type
+	 *  ex: N_("X.509 Certificates")
+	 *  When this is displayed anywhere, it should be i18ned
+	 *  ex: _(scheme->name)
+	 */
+	gchar * fullname;
+
+	/** Imports a certificate from a file
+	 *
+	 *  @param filename   File to import the certificate from
+	 *  @return           Pointer to the newly allocated Certificate struct
+	 *                    or NULL on failure.
+	 */
+	PurpleCertificate * (* import_certificate)(const gchar * filename);
+
+	/** Destroys and frees a Certificate structure
+	 *
+	 *  Destroys a Certificate's internal data structures and calls
+	 *  free(crt)
+	 *
+	 *  @param crt  Certificate instance to be destroyed. It WILL NOT be
+	 *              destroyed if it is not of the correct
+	 *              CertificateScheme. Can be NULL
+	 */
+	void (* destroy_certificate)(PurpleCertificate * crt);
+
+	/**
+	 * Retrieves the certificate public key fingerprint using SHA1
+	 *
+	 * @param crt   Certificate instance
+	 * @return Binary representation of SHA1 hash - must be freed using
+	 *         g_byte_array_free()
+	 */
+	GByteArray * (* get_fingerprint_sha1)(PurpleCertificate *crt);
+
+	/**
+	 * Reads "who the certificate is assigned to"
+	 *
+	 * For SSL X.509 certificates, this is something like
+	 * "gmail.com" or "jabber.org"
+	 *
+	 * @param crt   Certificate instance
+	 * @return Newly allocated string specifying "whose certificate this
+	 *         is"
+	 */
+	gchar * (* get_certificate_subject)(PurpleCertificate *crt);
+
+	/**
+	 * Retrieves a unique certificate identifier
+	 *
+	 * @param crt   Certificate instance
+	 * @return Newly allocated string that can be used to uniquely
+	 *         identify the certificate.
+	 */
+	gchar * (* get_unique_id)(PurpleCertificate *crt);
+
+	/**
+	 * Retrieves a unique identifier for the certificate's issuer
+	 *
+	 * @param crt   Certificate instance
+	 * @return Newly allocated string that can be used to uniquely
+	 *         identify the issuer's certificate.
+	 */
+	gchar * (* get_issuer_unique_id)(PurpleCertificate *crt);
+
+	
+	/* TODO: Fill out this structure */
+};
+
+/** A set of operations used to provide logic for verifying a Certificate's
+ *  authenticity.
+ */
+struct _PurpleCertificateVerifier
+{	
+	/** Scheme this Verifier operates on */
+	PurpleCertificateScheme *scheme;
+
+	/** Internal name used for lookups
+	 *
+	 * Case insensitive
+	 */
+	gchar * name;
+};
+
+/** Structure for a single certificate request
+ *
+ *  Useful for keeping track of the state of a verification that involves
+ *  several steps
+ */
+struct _PurpleCertificateVerificationRequest
+{
+	/** Reference to the verification logic used */
+	PurpleCertificateVerifier *verifier;
+
+	/** Certificate subject's name.
+	 *
+	 * For X.509 certificates, this is the Common Name
+	 */
+	gchar *subject_name;
+	
+	/** List of certificates in the chain to be verified (such as that returned by purple_ssl_get_peer_certificates )
+	 *
+	 * This is most relevant for X.509 certificates used in SSL sessions.
+	 * The list order should be: certificate, issuer, issuer's issuer, etc.
+	 */
+	GList *cert_chain;
+	
+	/** Internal data used by the Verifier code */
+	gpointer *data;
+};
+
+/*****************************************************************************/
+/** @name PurpleCertificate Subsystem API                                    */
+/*****************************************************************************/
+/*@{*/
+
+/** Look up a registered CertificateScheme by name
+ * @param name   The scheme name. Case insensitive.
+ * @return Pointer to the located Scheme, or NULL if it isn't found.
+ */
+PurpleCertificateScheme *
+purple_certificate_find_scheme(const gchar *name);
+
+/** Register a CertificateScheme with libpurple
+ *
+ * No two schemes can be registered with the same name; this function enforces
+ * that.
+ *
+ * @param scheme  Pointer to the scheme to register.
+ * @return TRUE if the scheme was successfully added, otherwise FALSE
+ */
+gboolean
+purple_certificate_register_scheme(PurpleCertificateScheme *scheme);
+
+/** Unregister a CertificateScheme from libpurple
+ *
+ * @param scheme    Scheme to unregister.
+ *                  If the scheme is not registered, this is a no-op.
+ *
+ * @return TRUE if the unregister completed successfully
+ */
+gboolean
+purple_certificate_unregister_scheme(PurpleCertificateScheme *scheme);
+
+/* TODO: ADD STUFF HERE */
+
+/*@}*/
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _PURPLE_CERTIFICATE_H */
--- a/libpurple/plugins/ssl/ssl-gnutls.c	Thu Jun 21 18:08:48 2007 +0000
+++ b/libpurple/plugins/ssl/ssl-gnutls.c	Fri Jun 22 16:30:30 2007 +0000
@@ -21,15 +21,18 @@
  */
 #include "internal.h"
 #include "debug.h"
+#include "certificate.h"
 #include "plugin.h"
 #include "sslconn.h"
 #include "version.h"
+#include "util.h"
 
 #define SSL_GNUTLS_PLUGIN_ID "ssl-gnutls"
 
 #ifdef HAVE_GNUTLS
 
 #include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
 
 typedef struct
 {
@@ -44,9 +47,25 @@
 static void
 ssl_gnutls_init_gnutls(void)
 {
+	/* Configure GnuTLS to use glib memory management */
+	/* I expect that this isn't really necessary, but it may prevent
+	   some bugs */
+	/* TODO: It may be necessary to wrap this allocators for GnuTLS.
+	   If there are strange bugs, perhaps look here (yes, I am a
+	   hypocrite) */
+	gnutls_global_set_mem_functions(
+		(gnutls_alloc_function)   g_malloc0, /* malloc */
+		(gnutls_alloc_function)   g_malloc0, /* secure malloc */
+		NULL,      /* mem_is_secure */
+		(gnutls_realloc_function) g_realloc, /* realloc */
+		(gnutls_free_function)    g_free     /* free */
+		);
+	
 	gnutls_global_init();
 
 	gnutls_certificate_allocate_credentials(&xcred);
+
+	/* TODO: I can likely remove this */
 	gnutls_certificate_set_x509_trust_file(xcred, "ca.pem",
 		GNUTLS_X509_FMT_PEM);
 }
@@ -73,7 +92,7 @@
 	PurpleSslGnutlsData *gnutls_data = PURPLE_SSL_GNUTLS_DATA(gsc);
 	ssize_t ret;
 
-	purple_debug_info("gnutls", "Handshaking\n");
+	purple_debug_info("gnutls", "Handshaking with %s\n", gsc->host);
 	ret = gnutls_handshake(gnutls_data->session);
 
 	if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
@@ -94,6 +113,96 @@
 	} else {
 		purple_debug_info("gnutls", "Handshake complete\n");
 
+		/* TODO: Remove all this debugging babble */
+		/* Now we are cooking with gas! */
+		PurpleSslOps *ops = purple_ssl_get_ops();
+		GList * peers = ops->get_peer_certificates(gsc);
+		
+		PurpleCertificateScheme *x509 =
+			purple_certificate_find_scheme("x509");
+
+		GList * l;
+		for (l=peers; l; l = l->next) {
+			PurpleCertificate *crt = l->data;
+			GByteArray *z =
+				x509->get_fingerprint_sha1(crt);
+			gchar * fpr =
+				purple_base16_encode_chunked(z->data,
+							     z->len);
+
+			purple_debug_info("gnutls/x509",
+					  "Key print: %s\n",
+					  fpr);
+
+			/* Kill the cert! */
+			x509->destroy_certificate(crt);
+			
+			g_free(fpr);
+			g_byte_array_free(z, TRUE);
+		}
+		g_list_free(peers);
+		
+		{
+		  const gnutls_datum_t *cert_list;
+		  unsigned int cert_list_size = 0;
+		  gnutls_session_t session=gnutls_data->session;
+		  
+		  cert_list =
+		    gnutls_certificate_get_peers(session, &cert_list_size);
+		  
+		  purple_debug_info("gnutls",
+				    "Peer provided %d certs\n",
+				    cert_list_size);
+		  int i;
+		  for (i=0; i<cert_list_size; i++)
+		    {
+		      gchar fpr_bin[256];
+		      gsize fpr_bin_sz = sizeof(fpr_bin);
+		      gchar * fpr_asc = NULL;
+		      gchar tbuf[256];
+		      gsize tsz=sizeof(tbuf);
+		      gchar * tasc = NULL;
+		      gnutls_x509_crt_t cert;
+		      
+		      gnutls_x509_crt_init(&cert);
+		      gnutls_x509_crt_import (cert, &cert_list[i],
+					      GNUTLS_X509_FMT_DER);
+		      
+		      gnutls_x509_crt_get_fingerprint(cert, GNUTLS_MAC_SHA,
+						      fpr_bin, &fpr_bin_sz);
+		      
+		      fpr_asc =
+			purple_base16_encode_chunked(fpr_bin,fpr_bin_sz);
+		      
+		      purple_debug_info("gnutls", 
+					"Lvl %d SHA1 fingerprint: %s\n",
+					i, fpr_asc);
+		      
+		      tsz=sizeof(tbuf);
+		      gnutls_x509_crt_get_serial(cert,tbuf,&tsz);
+		      tasc=
+			purple_base16_encode_chunked(tbuf, tsz);
+		      purple_debug_info("gnutls",
+					"Serial: %s\n",
+					tasc);
+		      g_free(tasc);
+
+		      tsz=sizeof(tbuf);
+		      gnutls_x509_crt_get_dn (cert, tbuf, &tsz);
+		      purple_debug_info("gnutls",
+					"Cert DN: %s\n",
+					tbuf);
+		      tsz=sizeof(tbuf);
+		      gnutls_x509_crt_get_issuer_dn (cert, tbuf, &tsz);
+		      purple_debug_info("gnutls",
+					"Cert Issuer DN: %s\n",
+					tbuf);
+
+		      g_free(fpr_asc); fpr_asc = NULL;
+		      gnutls_x509_crt_deinit(cert);
+		    }
+		  
+		}
 		gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
 	}
 
@@ -213,6 +322,275 @@
 	return s;
 }
 
+/* Forward declarations are fun!
+   TODO: This is a stupid place for this */
+static PurpleCertificate *
+x509_import_from_datum(const gnutls_datum_t dt, gnutls_x509_crt_fmt_t mode);
+
+static GList *
+ssl_gnutls_get_peer_certificates(PurpleSslConnection * gsc)
+{
+	PurpleSslGnutlsData *gnutls_data = PURPLE_SSL_GNUTLS_DATA(gsc);
+
+	/* List of Certificate instances to return */
+	GList * peer_certs = NULL;
+
+	/* List of raw certificates as given by GnuTLS */
+	const gnutls_datum_t *cert_list;
+	unsigned int cert_list_size = 0;
+
+	unsigned int i;
+	
+	/* This should never, ever happen. */
+	g_return_val_if_fail( gnutls_certificate_type_get (gnutls_data->session) == GNUTLS_CRT_X509, NULL);
+
+	/* Get the certificate list from GnuTLS */
+	/* TODO: I am _pretty sure_ this doesn't block or do other exciting things */
+	cert_list = gnutls_certificate_get_peers(gnutls_data->session,
+						 &cert_list_size);
+
+	/* Convert each certificate to a Certificate and append it to the list */
+	for (i = 0; i < cert_list_size; i++) {
+		PurpleCertificate * newcrt = x509_import_from_datum(cert_list[i],
+							      GNUTLS_X509_FMT_DER);
+		/* Append is somewhat inefficient on linked lists, but is easy
+		   to read. If someone complains, I'll change it.
+		   TODO: Is anyone complaining? (Maybe elb?) */
+		peer_certs = g_list_append(peer_certs, newcrt);
+	}
+
+	/* cert_list shouldn't need free()-ing */
+	/* TODO: double-check this */
+
+	return peer_certs;
+}
+
+/************************************************************************/
+/* X.509 functionality                                                  */
+/************************************************************************/
+const gchar * SCHEME_NAME = "x509";
+
+static PurpleCertificateScheme x509_gnutls;
+
+/** Transforms a gnutls_datum_t 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
+ *             reading from files, and GNUTLS_X509_FMT_DER for converting
+ *             "over the wire" certs for SSL)
+ *
+ * @return A newly allocated Certificate structure of the x509_gnutls scheme
+ */
+static PurpleCertificate *
+x509_import_from_datum(const gnutls_datum_t dt, gnutls_x509_crt_fmt_t mode)
+{
+	/* Internal certificate data structure */
+	gnutls_x509_crt_t *certdat;
+	/* New certificate to return */
+	PurpleCertificate * crt;
+
+	/* Allocate and prepare the internal certificate data */
+	certdat = g_new(gnutls_x509_crt_t, 1);
+	gnutls_x509_crt_init(certdat);
+
+	/* Perform the actual certificate parse */
+	/* Yes, certdat SHOULD be dereferenced */
+	gnutls_x509_crt_import(*certdat, &dt, mode);
+	
+	/* Allocate the certificate and load it with data */
+	crt = g_new(PurpleCertificate, 1);
+	crt->scheme = &x509_gnutls;
+	crt->data = certdat;
+
+	return crt;
+}
+
+/** Imports a PEM-formatted X.509 certificate from the specified file.
+ * @param filename Filename to import from. Format is PEM
+ *
+ * @return A newly allocated Certificate structure of the x509_gnutls scheme
+ */
+static PurpleCertificate *
+x509_import_from_file(const gchar * filename)
+{
+	PurpleCertificate *crt;  /* Certificate being constructed */
+	gchar *buf;        /* Used to load the raw file data */
+	gsize buf_sz;      /* Size of the above */
+	gnutls_datum_t dt; /* Struct to pass down to GnuTLS */
+
+	purple_debug_info("gnutls",
+			  "Attempting to load X.509 certificate 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? */
+	/* TODO: Error checking */
+	g_file_get_contents(filename,
+			    &buf,
+			    &buf_sz,
+			    NULL      /* No error checking for now */
+		);
+	
+	/* Load the datum struct */
+	dt.data = (unsigned char *) buf;
+	dt.size = buf_sz;
+
+	/* Perform the conversion */
+	crt = x509_import_from_datum(dt,
+				     GNUTLS_X509_FMT_PEM); // files should be in PEM format
+	
+	/* Cleanup */
+	g_free(buf);
+
+	return crt;
+}
+
+/** Frees a Certificate
+ *
+ *  Destroys a Certificate's internal data structures and frees the pointer
+ *  given.
+ *  @param crt  Certificate instance to be destroyed. It WILL NOT be destroyed
+ *              if it is not of the correct CertificateScheme. Can be NULL
+ *
+ */
+static void
+x509_destroy_certificate(PurpleCertificate * crt)
+{
+	/* TODO: Issue a warning here? */
+	if (NULL == crt) return;
+
+	/* Check that the scheme is x509_gnutls */
+	if ( crt->scheme != &x509_gnutls ) {
+		purple_debug_error("gnutls",
+				   "destroy_certificate attempted on certificate of wrong scheme (scheme was %s, expected %s)\n",
+				   crt->scheme->name,
+				   SCHEME_NAME);
+		return;
+	}
+
+	/* TODO: Different error checking? */
+	g_return_if_fail(crt->data != NULL);
+	g_return_if_fail(crt->scheme != NULL);
+
+	/* Destroy the GnuTLS-specific data */
+	gnutls_x509_crt_deinit( *( (gnutls_x509_crt_t *) crt->data ) );
+	g_free(crt->data);
+
+	/* TODO: Reference counting here? */
+
+	/* Kill the structure itself */
+	g_free(crt);
+}
+
+/** Determines whether one certificate has been issued and signed by another
+ *
+ * @param crt       Certificate to check the signature of
+ * @param issuer    Issuer's certificate
+ *
+ * @return TRUE if crt was signed and issued by issuer, otherwise FALSE
+ * @TODO  Modify this function to return a reason for invalidity?
+ */
+static gboolean
+x509_certificate_signed_by(PurpleCertificate * crt,
+			   PurpleCertificate * issuer)
+{
+	gnutls_x509_crt_t crt_dat;
+	gnutls_x509_crt_t issuer_dat;
+	unsigned int verify; /* used to store details from GnuTLS verifier */
+	int ret;
+	
+	/* TODO: Change this error checking? */
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(issuer, FALSE);
+
+	/* Verify that both certs are the correct scheme */
+	g_return_val_if_fail(crt->scheme != &x509_gnutls, FALSE);
+	g_return_val_if_fail(issuer->scheme != &x509_gnutls, FALSE);
+
+	/* TODO: check for more nullness? */
+
+	crt_dat = *((gnutls_x509_crt_t *) crt->data);
+	issuer_dat = *((gnutls_x509_crt_t *) issuer->data);
+
+	/* First, let's check that crt.issuer is actually issuer */
+	ret = gnutls_x509_crt_check_issuer(crt_dat, issuer_dat);
+	if (ret <= 0) {
+
+		if (ret < 0) {
+			purple_debug_error("gnutls/x509",
+					   "GnuTLS error %d while checking certificate issuer match.",
+					   ret);
+		}
+
+		/* The issuer is not correct, or there were errors */
+		return FALSE;
+	}
+	
+	/* Now, check the signature */
+	/* The second argument is a ptr to an array of "trusted" issuer certs,
+	   but we're only using one trusted one */
+	ret = gnutls_x509_crt_verify(crt_dat, &issuer_dat, 1, 0, &verify);
+	
+	if (ret > 0) {
+		/* The certificate is good. */
+		return TRUE;
+	}
+	else if (ret < 0) {
+		purple_debug_error("gnutls/x509",
+				   "Attempted certificate verification caused a GnuTLS error code %d. I will just say the signature is bad, but you should look into this.\n", ret);
+		return FALSE;
+	}
+	else {
+		/* Signature didn't check out, but at least
+		   there were no errors*/
+		return FALSE;
+	} /* if (ret, etc.) */
+
+	/* Control does not reach this point */
+}
+
+static GByteArray *
+x509_sha1sum(PurpleCertificate *crt)
+{
+	size_t hashlen = 20; /* SHA1 hashes are 20 bytes */
+	size_t tmpsz = hashlen; /* Throw-away variable for GnuTLS to stomp on*/
+	gnutls_x509_crt_t crt_dat;
+	GByteArray *hash; /**< Final hash container */
+	guchar hashbuf[hashlen]; /**< Temporary buffer to contain hash */
+
+	g_return_val_if_fail(crt, NULL);
+
+	crt_dat = *( (gnutls_x509_crt_t *) crt->data );
+
+	/* Extract the fingerprint */
+	/* TODO: Errorcheck? */
+	gnutls_x509_crt_get_fingerprint(crt_dat, GNUTLS_MAC_SHA,
+					hashbuf, &tmpsz);
+
+	/* This shouldn't happen */
+	g_return_val_if_fail(tmpsz == hashlen, NULL);
+	
+	/* Okay, now create and fill hash array */
+	hash = g_byte_array_new();
+	g_byte_array_append(hash, hashbuf, hashlen);
+
+	return hash;
+}
+
+/* X.509 certificate operations provided by this plugin */
+/* TODO: Flesh this out! */
+static PurpleCertificateScheme x509_gnutls = {
+	"x509",                          /* Scheme name */
+	N_("X.509 Certificates"),        /* User-visible scheme name */
+	x509_import_from_file,           /* Certificate import function */
+	x509_destroy_certificate,        /* Destroy cert */
+	x509_sha1sum,                    /* SHA1 fingerprint */
+	NULL,                            /* Subject */
+	NULL,                            /* Unique ID */
+	NULL                             /* Issuer Unique ID */
+};
+
 static PurpleSslOps ssl_ops =
 {
 	ssl_gnutls_init,
@@ -221,11 +599,11 @@
 	ssl_gnutls_close,
 	ssl_gnutls_read,
 	ssl_gnutls_write,
+	ssl_gnutls_get_peer_certificates,
 
 	/* padding */
 	NULL,
 	NULL,
-	NULL,
 	NULL
 };
 
@@ -235,6 +613,10 @@
 plugin_load(PurplePlugin *plugin)
 {
 #ifdef HAVE_GNUTLS
+	/* Register that we're providing an X.509 CertScheme */
+	/* @TODO : error checking */
+	purple_certificate_register_scheme( &x509_gnutls );
+	
 	if(!purple_ssl_get_ops()) {
 		purple_ssl_set_ops(&ssl_ops);
 	}
@@ -255,6 +637,8 @@
 	if(purple_ssl_get_ops() == &ssl_ops) {
 		purple_ssl_set_ops(NULL);
 	}
+
+	purple_certificate_unregister_scheme( &x509_gnutls );
 #endif
 
 	return TRUE;
--- a/libpurple/prefs.h	Thu Jun 21 18:08:48 2007 +0000
+++ b/libpurple/prefs.h	Fri Jun 22 16:30:30 2007 +0000
@@ -55,7 +55,9 @@
 #endif
 
 /**************************************************************************/
-/** @name Prefs API                                                       */
+/** @name Prefs API                                                       
+    Preferences are named according to a directory-like structure.        
+    Example: "/plugins/core/potato/is_from_idaho" (probably a boolean)    */
 /**************************************************************************/
 /*@{*/
 
--- a/libpurple/protocols/jabber/jabber.c	Thu Jun 21 18:08:48 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Fri Jun 22 16:30:30 2007 +0000
@@ -508,15 +508,13 @@
 {
 	purple_input_remove(js->gc->inpa);
 	js->gc->inpa = 0;
-	js->gsc = purple_ssl_connect_fd(js->gc->account, js->fd,
-			jabber_login_callback_ssl, jabber_ssl_connect_failure, js->gc);
+	js->gsc = purple_ssl_connect_with_host_fd(js->gc->account, js->fd,
+			jabber_login_callback_ssl, jabber_ssl_connect_failure, js->serverFQDN, js->gc);
 }
 
 static void jabber_login_connect(JabberStream *js, const char *fqdn, const char *host, int port)
 {
-#ifdef HAVE_CYRUS_SASL
 	js->serverFQDN = g_strdup(fqdn);
-#endif
 
 	if (purple_proxy_connect(js->gc, js->gc->account, host,
 			port, jabber_login_callback, js->gc) == NULL)
@@ -1025,9 +1023,9 @@
 		g_string_free(js->sasl_mechs, TRUE);
 	if(js->sasl_cb)
 		g_free(js->sasl_cb);
+#endif
 	if(js->serverFQDN)
 		g_free(js->serverFQDN);
-#endif
 	g_free(js->server_name);
 	g_free(js->gmail_last_time);
 	g_free(js->gmail_last_tid);
--- a/libpurple/protocols/jabber/jabber.h	Thu Jun 21 18:08:48 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Fri Jun 22 16:30:30 2007 +0000
@@ -136,6 +136,8 @@
 	char *gmail_last_time;
 	char *gmail_last_tid;
 
+    char *serverFQDN;
+
 	/* OK, this stays at the end of the struct, so plugins can depend
 	 * on the rest of the stuff being in the right place
 	 */
@@ -150,7 +152,6 @@
 	int sasl_state;
 	int sasl_maxbuf;
 	GString *sasl_mechs;
-	char *serverFQDN;
 
 	gboolean vcard_fetched;
 
--- a/libpurple/sslconn.c	Thu Jun 21 18:08:48 2007 +0000
+++ b/libpurple/sslconn.c	Fri Jun 22 16:30:30 2007 +0000
@@ -154,7 +154,18 @@
 PurpleSslConnection *
 purple_ssl_connect_fd(PurpleAccount *account, int fd,
 					PurpleSslInputFunction func,
-					PurpleSslErrorFunction error_func, void *data)
+					PurpleSslErrorFunction error_func,
+                    void *data)
+{
+    return purple_ssl_connect_with_host_fd(account, fd, func, error_func, NULL, data);
+}
+
+PurpleSslConnection *
+purple_ssl_connect_with_host_fd(PurpleAccount *account, int fd,
+                      PurpleSslInputFunction func,
+                      PurpleSslErrorFunction error_func,
+                      const char *host,
+                      void *data)
 {
 	PurpleSslConnection *gsc;
 	PurpleSslOps *ops;
@@ -175,6 +186,8 @@
 	gsc->connect_cb      = func;
 	gsc->error_cb        = error_func;
 	gsc->fd              = fd;
+    if(host)
+        gsc->host            = g_strdup(host);
 
 	ops = purple_ssl_get_ops();
 	ops->connectfunc(gsc);
@@ -231,6 +244,17 @@
 	return (ops->write)(gsc, data, len);
 }
 
+GList *
+purple_ssl_get_peer_certificates(PurpleSslConnection *gsc)
+{
+	PurpleSslOps *ops;
+
+	g_return_val_if_fail(gsc != NULL, NULL);
+
+	ops = purple_ssl_get_ops();
+	return (ops->get_peer_certificates)(gsc);
+}
+
 void
 purple_ssl_set_ops(PurpleSslOps *ops)
 {
--- a/libpurple/sslconn.h	Thu Jun 21 18:08:48 2007 +0000
+++ b/libpurple/sslconn.h	Fri Jun 22 16:30:30 2007 +0000
@@ -107,8 +107,16 @@
 	* @return	The number of bytes written (may be less than len) or <0 on error
 	*/
 	size_t (*write)(PurpleSslConnection *gsc, const void *data, size_t len);
-
-	void (*_purple_reserved1)(void);
+	/** Obtains the certificate chain provided by the peer
+	 *
+	 * @param gsc   Connection context
+	 * @return      A newly allocated list containing the certificates
+	 *              the peer provided.
+	 * @todo        Decide whether the ordering of certificates in this
+	 *              list can be guaranteed.
+	 */
+	GList * (* get_peer_certificates)(PurpleSslConnection * gsc);
+	
 	void (*_purple_reserved2)(void);
 	void (*_purple_reserved3)(void);
 	void (*_purple_reserved4)(void);
@@ -154,6 +162,7 @@
 
 /**
  * Makes a SSL connection using an already open file descriptor.
+ * DEPRECATED. Use purple_ssl_connect_with_host_fd instead.
  *
  * @param account    The account making the connection.
  * @param fd         The file descriptor.
@@ -166,7 +175,25 @@
 PurpleSslConnection *purple_ssl_connect_fd(PurpleAccount *account, int fd,
 									   PurpleSslInputFunction func,
 									   PurpleSslErrorFunction error_func,
-									   void *data);
+ 									   void *data);
+
+/**
+  * Makes a SSL connection using an already open file descriptor.
+  *
+  * @param account    The account making the connection.
+  * @param fd         The file descriptor.
+  * @param func       The SSL input handler function.
+  * @param error_func The SSL error handler function.
+  * @param host       The hostname of the other peer (to verify the CN)
+  * @param data       User-defined data.
+  *
+  * @return The SSL connection handle.
+  */
+PurpleSslConnection *purple_ssl_connect_with_host_fd(PurpleAccount *account, int fd,
+                                           PurpleSslInputFunction func,
+                                           PurpleSslErrorFunction error_func,
+                                           const char *host,
+                                           void *data);
 
 /**
  * Adds an input watcher for the specified SSL connection.
@@ -208,6 +235,16 @@
  */
 size_t purple_ssl_write(PurpleSslConnection *gsc, const void *buffer, size_t len);
 
+/**
+ * Obtains the peer's presented certificates
+ *
+ * @param gsc    The SSL connection handle
+ *
+ * @return The peer certificate chain, in the order of certificate, issuer,
+ *         issuer's issuer, etc. NULL if no certificates have been provided,
+ */
+GList * purple_ssl_get_peer_certificates(PurpleSslConnection *gsc);
+
 /*@}*/
 
 /**************************************************************************/