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