cpw.ljfisher.ssl_client_auth: 80891a14: Added certificate_id, certificate, key, ...

lucas.fisher at gmail.com lucas.fisher at gmail.com
Sat Apr 16 19:36:07 EDT 2011


----------------------------------------------------------------------
Revision: 80891a14397d32668e7e2862d6473b448fa517e9
Parent:   7ec20a6c9614718b62c8f9a5ebee0e27d589c247
Author:   lucas.fisher at gmail.com
Date:     04/16/11 16:54:18
Branch:   im.pidgin.cpw.ljfisher.ssl_client_auth
URL: http://d.pidgin.im/viewmtn/revision/info/80891a14397d32668e7e2862d6473b448fa517e9

Changelog: 

Added certificate_id, certificate, key, fields to PurpleSslConnection.
Added private key related errors to the ssl errors.
Added new ssl connection api that takes a certificate_id of the client certificate to use for client-side auth, grabs the client's credentials from appropriate pools, and prompts for passwords to decrypt client's keys.

Changes against parent 7ec20a6c9614718b62c8f9a5ebee0e27d589c247

  patched  libpurple/sslconn.c
  patched  libpurple/sslconn.h

-------------- next part --------------
============================================================
--- libpurple/sslconn.c	31b8e25e3ce31929d3f6941674a74187fbf20dee
+++ libpurple/sslconn.c	1f28d55412c4cf37177b7a6378645ea5ad42a13e
@@ -32,6 +32,14 @@
 #include "request.h"
 #include "sslconn.h"
 
+/** To carry around connection and account references 
+ *  when doing client-side auth callbacks
+ */
+typedef struct {
+	PurpleSslConnection *gsc;
+	PurpleAccount *account;
+} ssl_connect_cb_data;
+
 static gboolean _ssl_initialized = FALSE;
 static PurpleSslOps *_ssl_ops = NULL;
 
@@ -71,7 +79,21 @@ purple_ssl_is_supported(void)
 #endif
 }
 
+/**
+ * Destroy an allocated PurpleSslConnection. This frees any allocated memory,
+ * but does not close any connection. Use purple_ssl_close() instead.
+ */
 static void
+purple_ssl_destroy(PurpleSslConnection *gsc)
+{
+	purple_certificate_destroy(gsc->certificate);
+	purple_privatekey_destroy(gsc->key);
+	g_free(gsc->host);
+	g_free(gsc->certificate_id);
+	g_free(gsc);
+}
+
+static void
 purple_ssl_connect_cb(gpointer data, gint source, const gchar *error_message)
 {
 	PurpleSslConnection *gsc;
@@ -95,6 +117,128 @@ purple_ssl_connect_cb(gpointer data, gin
 	ops->connectfunc(gsc);
 }
 
+/**
+ * Called when user enters a password for the private key password request. 
+ * We check if the private key was found, and then connect the the host.
+ * For use with purple_ssl_connect_with_ssl_cn_auth().
+ */
+static void
+purple_ssl_connect_with_ssl_cn_auth_cb(PurplePrivateKey *key, void *data)
+{
+	PurpleSslConnection *gsc = ((ssl_connect_cb_data*)data)->gsc;
+	PurpleAccount *account = ((ssl_connect_cb_data*)data)->account;
+
+	/* If key is null either it wasn't found or the password was bad.
+	 * We can't tell which as of now.
+	 */
+	if (NULL == key) {
+		purple_debug_error("sslconn", "Failed to get private key from pool.\n");
+		if (NULL !=  gsc->error_cb)
+			gsc->error_cb(gsc, PURPLE_SSL_PRIVATEKEY_NOT_FOUND, gsc->connect_cb_data);
+		purple_ssl_destroy(gsc);
+		return;
+	}
+
+	gsc->key = key;
+
+	gsc->connect_data = purple_proxy_connect(NULL, 
+				account,
+				gsc->host, gsc->port,
+				purple_ssl_connect_cb,
+				gsc);
+
+	g_free(data);
+
+	if (gsc->connect_data == NULL)
+	{
+		if (NULL !=  gsc->error_cb)
+			gsc->error_cb(gsc, PURPLE_SSL_CONNECT_FAILED, gsc->connect_cb_data);
+		purple_ssl_destroy(gsc);
+		return;
+	}
+}
+
+/**
+ * Called when the user cancels the request for the priavte key password.
+ * Common to all ssl connection types that use client authentication.
+ */
+static void
+purple_ssl_connect_cancel_cb(void *data)
+{
+	PurpleSslConnection *gsc = ((ssl_connect_cb_data*)data)->gsc;
+	purple_debug_error("sslconn", "User canceled password request for private key.\n");
+	if (gsc->error_cb != NULL)
+		gsc->error_cb(gsc, PURPLE_SSL_PRIVATEKEY_CANCELED, gsc->connect_cb_data);
+	purple_ssl_destroy(gsc);
+	g_free(data);
+}
+
+/**
+ * Get user's credentials for client-side SSL authentication. To retrieve
+ * the user's private key uses a user input request. ok_cb() is called if
+ * that request succeeds, otherwise purple_ssl_connect_cancel_cb() is
+ * called. Both the certificate and private key are retrieved from the
+ * x509:user pool.
+ *
+ * @param account Needed for the later call to purple_proxy_connect()
+ * @param gsc  The SSL connection state.
+ * @param ok_cb Called when user acks the password request.
+ *
+ * @returns TRUE if we find everything and FALSE otherwise.
+ * 
+ * TODO: Error handling is not great. Caller doesn't know what failed
+ *  other than something. Maybe we should always use the error_cb
+ *  instead of just returning.
+ */
+static gboolean
+purple_ssl_get_credentials(PurpleAccount *account, PurpleSslConnection *gsc, 
+		GCallback ok_cb)
+{
+	PurpleCertificatePool *crt_pool = NULL;
+	PurplePrivateKeyPool *key_pool = NULL;
+	ssl_connect_cb_data *data = NULL;
+
+	g_return_val_if_fail(gsc, FALSE);
+	g_return_val_if_fail(gsc->certificate_id, FALSE);
+	g_return_val_if_fail(ok_cb, FALSE);
+
+	crt_pool = purple_certificate_find_pool("x509", "user");
+	if (NULL == crt_pool) {
+		purple_debug_error("sslconn",
+				   "Failed to find certificate pool x509:user.\n");
+		return FALSE;
+	}
+
+	key_pool = purple_privatekey_find_pool("x509", "user");
+	if (NULL == key_pool) {
+		purple_debug_error("sslconn",
+				   "Failed to find private key pool x509:user.\n");
+		return FALSE;
+	}
+
+	gsc->certificate = purple_certificate_pool_retrieve(crt_pool, gsc->certificate_id);
+	if (NULL == gsc->certificate) {
+		purple_debug_error("sslconn",
+				   "Failed to find certificate with id '%s' in pool x509:user.\n",
+				   gsc->certificate_id);
+		return FALSE;
+	}
+
+	data = g_new0(ssl_connect_cb_data, 1);
+	if (NULL == data)
+		return FALSE;
+
+	data->gsc = gsc;
+	data->account = account;
+
+	purple_privatekey_pool_retrieve_request(key_pool, gsc->certificate_id,
+		ok_cb,
+		G_CALLBACK(purple_ssl_connect_cancel_cb),
+		(void*)data);
+
+	return TRUE;
+}
+
 PurpleSslConnection *
 purple_ssl_connect(PurpleAccount *account, const char *host, int port,
 				 PurpleSslInputFunction func, PurpleSslErrorFunction error_func,
@@ -109,6 +253,18 @@ purple_ssl_connect_with_ssl_cn(PurpleAcc
 				 PurpleSslInputFunction func, PurpleSslErrorFunction error_func,
 				 const char *ssl_cn, void *data)
 {
+	return purple_ssl_connect_with_ssl_cn_auth(account, host, port,
+				    func, error_func, ssl_cn, NULL, data);
+}
+
+PurpleSslConnection *
+purple_ssl_connect_with_ssl_cn_auth(PurpleAccount *account, const char *host, int port,
+				    PurpleSslInputFunction func,
+				    PurpleSslErrorFunction error_func,
+				    const char *ssl_cn,
+				    const char* certificate_id,
+				    void *data)
+{
 	PurpleSslConnection *gsc;
 
 	g_return_val_if_fail(host != NULL,            NULL);
@@ -130,21 +286,30 @@ purple_ssl_connect_with_ssl_cn(PurpleAcc
 	gsc->connect_cb_data = data;
 	gsc->connect_cb      = func;
 	gsc->error_cb        = error_func;
+	gsc->certificate_id  = certificate_id ? g_strdup(certificate_id) : NULL;
 
 	/* TODO: Move this elsewhere */
 	gsc->verifier = purple_certificate_find_verifier("x509","tls_cached");
 
-	gsc->connect_data = purple_proxy_connect(NULL, account, host, port, purple_ssl_connect_cb, gsc);
+	if (NULL != certificate_id) {
+		/* Caller requested client-side auth, so we need to get the credentials. */
+		if (!purple_ssl_get_credentials(account, gsc, G_CALLBACK(purple_ssl_connect_with_ssl_cn_auth_cb))) {
+			purple_debug_error("sslconn", "Could not retrieve client SSL credentials.\n");
+			purple_ssl_destroy(gsc);
+			return NULL;
+		}
+	}
+	else {
+		gsc->connect_data = purple_proxy_connect(NULL, account, host, port, purple_ssl_connect_cb, gsc);
 
-	if (gsc->connect_data == NULL)
-	{
-		g_free(gsc->host);
-		g_free(gsc);
-
-		return NULL;
+		if (gsc->connect_data == NULL)
+		{
+			purple_ssl_destroy(gsc);
+			return NULL;
+		}
 	}
 
-	return (PurpleSslConnection *)gsc;
+	return gsc;
 }
 
 static void
@@ -178,6 +343,12 @@ purple_ssl_strerror(PurpleSslErrorType e
 			return _("SSL Handshake Failed");
 		case PURPLE_SSL_CERTIFICATE_INVALID:
 			return _("SSL peer presented an invalid certificate");
+		case PURPLE_SSL_PRIVATEKEY_NOT_FOUND:
+			return _("Private key was not found or had invalid password.");
+		case PURPLE_SSL_PRIVATEKEY_CANCELED:
+			return _("Request for private key password was canceled.");
+		case PURPLE_SSL_PRIVATEKEY_BAD_PASSWORD:
+			return _("Invalid password for private key.");
 		default:
 			purple_debug_warning("sslconn", "Unknown SSL error code %d\n", error);
 			return _("Unknown SSL error");
@@ -200,11 +371,50 @@ purple_ssl_connect_with_host_fd(PurpleAc
                       const char *host,
                       void *data)
 {
+
+	return purple_ssl_connect_with_host_fd_auth(
+			account, fd,
+                	func, error_func, host,
+			NULL, data);
+}
+
+static void
+purple_ssl_connect_with_host_fd_auth_cb(PurplePrivateKey *key, void *data)
+{
+	PurpleSslOps *ops;
+	PurpleSslConnection *gsc = ((ssl_connect_cb_data*)data)->gsc;
+
+	/* If key is null either it wasn't found or the password was bad.
+	 * We can't tell which as of now.
+	 */
+	if (NULL == key) {
+		purple_debug_error("sslconn", "Failed to get private key from pool.\n");
+		if (NULL !=  gsc->error_cb)
+			gsc->error_cb(gsc, PURPLE_SSL_PRIVATEKEY_NOT_FOUND, gsc->connect_cb_data);
+		purple_ssl_destroy(gsc);
+		return;
+	}
+
+	gsc->key = key;	
+	g_free(data);
+
+	ops = purple_ssl_get_ops();
+	ops->connectfunc(gsc);
+}
+
+PurpleSslConnection *
+purple_ssl_connect_with_host_fd_auth(PurpleAccount *account, int fd,
+		PurpleSslInputFunction func,
+		PurpleSslErrorFunction error_func,
+		const char *host,
+		const char* certificate_id,
+		void *data)
+{
 	PurpleSslConnection *gsc;
 	PurpleSslOps *ops;
 
-	g_return_val_if_fail(fd != -1,                NULL);
-	g_return_val_if_fail(func != NULL,            NULL);
+	g_return_val_if_fail(fd != -1,                  NULL);
+	g_return_val_if_fail(func != NULL,              NULL);
 	g_return_val_if_fail(purple_ssl_is_supported(), NULL);
 
 	if (!_ssl_initialized)
@@ -219,15 +429,25 @@ purple_ssl_connect_with_host_fd(PurpleAc
 	gsc->connect_cb      = func;
 	gsc->error_cb        = error_func;
 	gsc->fd              = fd;
-    if(host)
-        gsc->host            = g_strdup(host);
+        gsc->host            = host ? g_strdup(host) : NULL;
+	gsc->certificate_id  = certificate_id ? g_strdup(certificate_id) : NULL;
 
 	/* TODO: Move this elsewhere */
 	gsc->verifier = purple_certificate_find_verifier("x509","tls_cached");
 
 
-	ops = purple_ssl_get_ops();
-	ops->connectfunc(gsc);
+	if (NULL != certificate_id) {
+		/* Caller requested client-side auth, so we need to get the credentials. */
+		if (!purple_ssl_get_credentials(account, gsc, G_CALLBACK(purple_ssl_connect_with_host_fd_auth_cb))) {
+			purple_debug_error("sslconn", "Could not retrieve client SSL credentials.\n");
+			purple_ssl_destroy(gsc);
+			return NULL;
+		}
+	}
+	else {
+		ops = purple_ssl_get_ops();
+		ops->connectfunc(gsc);
+	}
 
 	return (PurpleSslConnection *)gsc;
 }
@@ -254,8 +474,7 @@ purple_ssl_close(PurpleSslConnection *gs
 	if (gsc->fd >= 0)
 		close(gsc->fd);
 
-	g_free(gsc->host);
-	g_free(gsc);
+	purple_ssl_destroy(gsc);
 }
 
 size_t
@@ -295,6 +514,29 @@ purple_ssl_get_peer_certificates(PurpleS
 	return (ops->get_peer_certificates)(gsc);
 }
 
+const char*
+purple_ssl_get_client_certificate_id(PurpleSslConnection *gsc)
+{
+	g_return_val_if_fail(gsc != NULL, NULL);
+
+	return gsc->certificate_id;
+}
+
+/*
+gboolean
+purple_ssl_set_client_auth(PurpleSslConnection *gsc, PurpleCertificate *crt, PurplePrivateKey *key)
+{
+	PurpleSslOps *ops;
+
+	g_return_val_if_fail(gsc != NULL, FALSE);
+	g_return_val_if_fail(crt != NULL, FALSE);
+	g_return_val_if_fail(key != NULL, FALSE);
+
+	ops = purple_ssl_get_ops();
+	return (ops->set_client_auth)(gsc, crt, key);
+}
+*/
+
 void
 purple_ssl_set_ops(PurpleSslOps *ops)
 {
============================================================
--- libpurple/sslconn.h	93719cf2c503556e445e9512b31e1a89ad8069de
+++ libpurple/sslconn.h	041f161cfdca9e5b9c5c09d97c8faf2e5d2033a9
@@ -31,10 +31,30 @@ typedef enum
 {
 	PURPLE_SSL_HANDSHAKE_FAILED = 1,
 	PURPLE_SSL_CONNECT_FAILED = 2,
-	PURPLE_SSL_CERTIFICATE_INVALID = 3
+	PURPLE_SSL_CERTIFICATE_INVALID = 3,
+
+	/** 
+	 * When trying to authenticate with client certificates
+	 * the user's private key could not be found.
+	 */
+	PURPLE_SSL_PRIVATEKEY_NOT_FOUND = 4,
+
+	/**
+	 * When trying to authenticate with client certificates
+	 * the user cancelled the request for the private key password.
+	 */
+	PURPLE_SSL_PRIVATEKEY_CANCELED = 5,
+
+	/**
+	 * When trying to authenticate with client certificates
+	 * the user entered an invalid password for the private key.
+	 */
+	PURPLE_SSL_PRIVATEKEY_BAD_PASSWORD = 6
+
 } PurpleSslErrorType;
 
 #include "certificate.h"
+#include "privatekey.h"
 #include "proxy.h"
 
 #define PURPLE_SSL_DEFAULT_PORT 443
@@ -46,6 +66,8 @@ typedef void (*PurpleSslErrorFunction)(P
 									 PurpleInputCondition);
 typedef void (*PurpleSslErrorFunction)(PurpleSslConnection *, PurpleSslErrorType,
 									 gpointer);
+/* TODO: Do we need better parameters here for algorithms, complete certificate chain, etc? */
+typedef gboolean (*PurpleSslGetCredentialsFunction)(PurpleSslConnection*, PurpleCertificate **crt, PurplePrivateKey **key);
 
 struct _PurpleSslConnection
 {
@@ -77,6 +99,18 @@ struct _PurpleSslConnection
 
 	/** Verifier to use in authenticating the peer */
 	PurpleCertificateVerifier *verifier;
+
+	/** Id of certificate to use for client-side authentication */
+	char* certificate_id;
+
+	/** Certificate to use for client authentication. Must match certificate_id */
+	PurpleCertificate *certificate;
+
+	/** Private key to use for client authentication. Must match certificate_id */
+	PurplePrivateKey *key;
+
+	/** Callback function to get credentials. */
+	PurpleSslGetCredentialsFunction get_credentials_cb;
 };
 
 /**
@@ -211,6 +245,37 @@ PurpleSslConnection *purple_ssl_connect_
 									const char *ssl_host,
 									void *data);
 
+/**
+ * Makes a SSL connection to the specified host and port, using the separate
+ * name to verify with the certificate. Client-side authentication may be
+ * enabled by setting the cred_func which will retrieve a PurpleCertificate
+ * and PurplePrivateKey used to authenticate the client to the server.
+ * The caller should keep track of the
+ * returned value and use it to cancel the connection, if needed.
+ *
+ * @param account    The account making the connection.
+ * @param host       The destination host.
+ * @param port       The destination port.
+ * @param func       The SSL input handler function.
+ * @param error_func The SSL error handler function.  This function
+ *                   should <strong>NOT</strong> call purple_ssl_close().  In
+ *                   the event of an error the #PurpleSslConnection will be
+ *                   destroyed for you.
+ * @param certificate_id  Id of the certificate and private key for client-side 
+ *                        authentication. NULL for no auth.
+ * @param ssl_host   The hostname of the other peer (to verify the CN)
+ * @param data       User-defined data.
+ *
+ * @return The SSL connection handle.
+ * @since ?.?.?
+ */
+PurpleSslConnection *
+purple_ssl_connect_with_ssl_cn_auth(PurpleAccount *account, const char *host, int port,
+				    PurpleSslInputFunction func, PurpleSslErrorFunction error_func,
+				    const char *ssl_cn,
+				    const char* certificate_id,
+				    void *data);
+
 #if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_SSLCONN_C_)
 /**
  * Makes a SSL connection using an already open file descriptor.
@@ -252,6 +317,34 @@ PurpleSslConnection *purple_ssl_connect_
                                            void *data);
 
 /**
+ * Makes a SSL connection using an already open file descriptor.
+ * Client-side authentication may be
+ * enabled by setting the cred_func which will retrieve a PurpleCertificate
+ * and PurplePrivateKey used to authenticate the client to the server.
+ *
+ * @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 certificate_id  Id of the certificate and private key for client-side 
+ *                        authentication. NULL for no auth.
+ * @param host       The hostname of the other peer (to verify the CN)
+ * @param data       User-defined data.
+ *
+ * @return The SSL connection handle.
+ *
+ * @since ?.?.?
+ *
+ * TODO: Do we really need to pass certificate_id here? Should we put it in account?
+ */
+PurpleSslConnection *
+purple_ssl_connect_with_host_fd_auth(PurpleAccount *account, int fd,
+		PurpleSslInputFunction func,
+		PurpleSslErrorFunction error_func,
+		const char *host,
+		const char* certificate_id,
+		void *data);
+/**
  * Adds an input watcher for the specified SSL connection.
  * Once the SSL handshake is complete, use this to watch for actual data across it.
  *
@@ -303,6 +396,14 @@ GList * purple_ssl_get_peer_certificates
  */
 GList * purple_ssl_get_peer_certificates(PurpleSslConnection *gsc);
 
+/**
+ * Get the id the of certificate to use for client-side SSL/TLS authentication.
+ *
+ * @param gsc  The SSL connection handle
+ *
+ * @return .
+ */
+const char* purple_ssl_get_client_certificate_id(PurpleSslConnection *gsc);
 /*@}*/
 
 /**************************************************************************/


More information about the Commits mailing list