pidgin/ljfisher-ssl-client-auth
Clone
propagate from branch 'im.pidgin.pidgin' (head 45883bf3facb2c404189a366868eb301717a2d68)
to branch 'im.pidgin.cpw.ljfisher.ssl_client_auth' (head 049a590b0b75e8ae24f4a47af68779e1b45a6169)
--- a/libpurple/Makefile.am Sat Aug 13 21:57:35 2011 +0000
+++ b/libpurple/Makefile.am Sun Aug 14 18:20:04 2011 +0000
@@ -65,11 +65,13 @@
@@ -129,11 +131,13 @@
--- a/libpurple/account.c Sat Aug 13 21:57:35 2011 +0000
+++ b/libpurple/account.c Sun Aug 14 18:20:04 2011 +0000
@@ -383,6 +383,14 @@
xmlnode_insert_data(child, tmp, -1);
+ if ((tmp = purple_account_get_certificate_id(account)) != NULL) + child = xmlnode_new_child(node, "certificateid"); + xmlnode_insert_data(child, tmp, -1); if ((tmp = purple_account_get_alias(account)) != NULL)
child = xmlnode_new_child(node, "alias");
@@ -887,6 +895,17 @@
+ /* Read the certificate id */ + child = xmlnode_get_child(node, "certificateid"); + purple_debug_info("account", "crt id field from accounts.xml: %p\n", child); + if ((child != NULL) && ((data = xmlnode_get_data(child)) != NULL)) + purple_account_set_certificate_id(ret, data); child = xmlnode_get_child(node, "alias");
if ((child != NULL) && ((data = xmlnode_get_data(child)) != NULL))
@@ -985,6 +1004,7 @@
_purple_buddy_icons_account_loaded_cb();
@@ -1652,6 +1672,24 @@
schedule_accounts_save();
+purple_account_set_certificate_id(PurpleAccount *account, const char *id) + purple_debug_info("account", "Set certificate_id = %s\n", id); + g_return_if_fail(account != NULL); + if (g_strcmp0(id, "None")) { + g_free(account->certificate_id); + account->certificate_id = g_strdup(id); + schedule_accounts_save(); purple_account_set_alias(PurpleAccount *account, const char *alias)
@@ -2163,7 +2201,15 @@
return account->password;
+purple_account_get_certificate_id(const PurpleAccount *account) + g_return_val_if_fail(account != NULL, NULL); + return account->certificate_id; purple_account_get_alias(const PurpleAccount *account)
@@ -2526,6 +2572,18 @@
+purple_account_get_options(PurplePluginProtocolInfo *prpl_info) + g_return_val_if_fail(prpl_info != NULL, NULL); + if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_account_options)) + return prpl_info->get_account_options(); purple_account_add_buddy(PurpleAccount *account, PurpleBuddy *buddy)
--- a/libpurple/account.h Sat Aug 13 21:57:35 2011 +0000
+++ b/libpurple/account.h Sun Aug 14 18:20:04 2011 +0000
@@ -129,6 +129,7 @@
char *alias; /**< How you appear to yourself. */
char *password; /**< The account password. */
char *user_info; /**< User information. */
+//XXX char *certificate_id; /**< User certificate id. */ char *buddy_icon_path; /**< The buddy icon's non-cached path. */
@@ -353,6 +354,14 @@
void purple_account_set_password(PurpleAccount *account, const char *password);
+ * Sets id of the certificate to use for authentication with this account. + * @param account The account. + * @param id The user's certificate id. +// XXX void purple_account_set_certificate_id(PurpleAccount *account, const char* id); * Sets the account's alias.
* @param account The account.
@@ -655,6 +664,15 @@
const char *purple_account_get_password(const PurpleAccount *account);
+ * Returns the id of the certificate to use for client-side PKI authentcation. + * @param account The account. + * @return The certificate id. +// XXX const char *purple_account_get_certificate_id(const PurpleAccount *account); * Returns the account's alias.
* @param account The account.
@@ -1139,6 +1157,17 @@
void purple_accounts_restore_current_statuses(void);
+ * Get account options. Prior to version 3.0 this was accessed + * via PurplePluginInfo.protocol_options. + * @param account The account + * @returns List of PurpleAccountOption +GList* purple_account_get_options(PurplePluginProtocolInfo *prpl_info); --- a/libpurple/certificate.c Sat Aug 13 21:57:35 2011 +0000
+++ b/libpurple/certificate.c Sun Aug 14 18:20:04 2011 +0000
@@ -1272,6 +1272,177 @@
+/***** User's certificates and keys *****/ +/* This code is just a duplication of x509_tls_peers. We should +static PurpleCertificatePool x509_user; + /* Set up key cache here if it isn't already done */ + poolpath = purple_certificate_pool_mkpath(&x509_user, NULL); + ret = purple_build_dir(poolpath, 0700); /* Make it this user only */ + purple_debug_info("certificate/user", + "Could not create %s. Certificates will not be cached.\n", +x509_user_cert_in_pool(const gchar *id) + g_return_val_if_fail(id, FALSE); + keypath = purple_certificate_pool_mkpath(&x509_user, id); + ret = g_file_test(keypath, G_FILE_TEST_IS_REGULAR); +static PurpleCertificate * +x509_user_get_cert(const gchar *id) + PurpleCertificateScheme *x509; + PurpleCertificate *crt; + g_return_val_if_fail(id, NULL); + /* Is it in the pool? */ + if ( !x509_user_cert_in_pool(id) ) { + /* Look up the X.509 scheme */ + x509 = purple_certificate_find_scheme("x509"); + g_return_val_if_fail(x509, NULL); + /* Okay, now find and load that key */ + keypath = purple_certificate_pool_mkpath(&x509_user, id); + crt = purple_certificate_import(x509, keypath); +x509_user_put_cert(const gchar *id, PurpleCertificate *crt) + g_return_val_if_fail(crt, FALSE); + g_return_val_if_fail(crt->scheme, FALSE); + /* Make sure that this is some kind of X.509 certificate */ + /* TODO: Perhaps just check crt->scheme->name instead? */ + g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_user.scheme_name), FALSE); + /* Work out the filename and export */ + keypath = purple_certificate_pool_mkpath(&x509_user, id); + ret = purple_certificate_export(keypath, crt); +x509_user_delete_cert(const gchar *id) + g_return_val_if_fail(id, FALSE); + /* Is the id even in the pool? */ + if (!x509_user_cert_in_pool(id)) { + purple_debug_warning("certificate/tls_peers", + "Id %s wasn't in the pool\n", + /* OK, so work out the keypath and delete the thing */ + keypath = purple_certificate_pool_mkpath(&x509_user, id); + if ( unlink(keypath) != 0 ) { + purple_debug_error("certificate/tls_peers", + "Unlink of %s failed!\n", +x509_user_get_idlist(void) + /* Get a handle on the pool directory */ + poolpath = purple_certificate_pool_mkpath(&x509_user, NULL); + dir = g_dir_open(poolpath, + NULL); /* Not interested in what the error is */ + g_return_val_if_fail(dir, NULL); + /* Traverse the directory listing and create an idlist */ + while ( (entry = g_dir_read_name(dir)) != NULL ) { + /* Unescape the filename */ + const char *unescaped = purple_unescape_filename(entry); + /* Copy the entry name into our list (GLib owns the original + idlist = g_list_prepend(idlist, g_strdup(unescaped)); + /* Release the directory */ +static PurpleCertificatePool x509_user = { + "x509", /* Scheme name */ + "user", /* Pool name */ + N_("SSL Peers Cache"), /* User-friendly name */ + NULL, /* Internal data */ + x509_user_init, /* init */ + NULL, /* uninit not required */ + x509_user_cert_in_pool, /* Certificate exists? */ + x509_user_get_cert, /* Cert retriever */ + x509_user_put_cert, /* Cert writer */ + x509_user_delete_cert, /* Cert remover */ + x509_user_get_idlist, /* idlist retriever */ /***** A Verifier that uses the tls_peers cache and the CA pool to validate certificates *****/
static PurpleCertificateVerifier x509_tls_cached;
@@ -1848,6 +2019,7 @@
purple_certificate_register_verifier(&x509_singleuse);
purple_certificate_register_pool(&x509_ca);
purple_certificate_register_pool(&x509_tls_peers);
+ purple_certificate_register_pool(&x509_user); purple_certificate_register_verifier(&x509_tls_cached);
--- a/libpurple/connection.c Sat Aug 13 21:57:35 2011 +0000
+++ b/libpurple/connection.c Sun Aug 14 18:20:04 2011 +0000
@@ -610,6 +610,12 @@
/* TODO: maybe PURPLE_SSL_* should be more specific? */
reason = PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR;
+ case PURPLE_SSL_PRIVATEKEY_NOT_FOUND: + case PURPLE_SSL_PRIVATEKEY_CANCELED: + case PURPLE_SSL_PRIVATEKEY_BAD_PASSWORD: + /* TODO: Create specific PurpleConnectionError codes */ + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; reason = PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR;
--- a/libpurple/core.c Sat Aug 13 21:57:35 2011 +0000
+++ b/libpurple/core.c Sun Aug 14 18:20:04 2011 +0000
@@ -26,6 +26,8 @@
#include "conversation.h"
@@ -161,6 +163,8 @@
purple_savedstatuses_init();
purple_certificate_init();
+ purple_privatekey_init(); purple_conversations_init();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/pkcs12.c Sun Aug 14 18:20:04 2011 +0000
@@ -0,0 +1,209 @@
+ * @file pkcs12.c PKCS12 API + * 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 + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA +/** List holding pointers to all registered private key schemes */ +static GList *pkcs12_schemes = NULL; +purple_pkcs12_import(PurplePkcs12Scheme *scheme, const gchar *filename, const gchar *password, + PurpleCertificate **crt, PurplePrivateKey **key) + g_return_val_if_fail(scheme, FALSE); + 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 (scheme->import_pkcs12)(filename, password, crt, key); +purple_pkcs12_export(PurplePkcs12Scheme *scheme, const gchar *filename, const gchar *password, + PurpleCertificate *crt, PurplePrivateKey *key) + g_return_val_if_fail(scheme, FALSE); + 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 (scheme->export_pkcs12)(filename, password, crt, key); +purple_pkcs12_import_to_pool(PurplePkcs12Scheme *scheme, const gchar *filename, const gchar *password, + PurpleCertificatePool *certpool, PurplePrivateKeyPool *keypool) + g_return_val_if_fail(scheme, FALSE); + g_return_val_if_fail(filename, FALSE); + g_return_val_if_fail(password, FALSE); + g_return_val_if_fail(certpool, FALSE); + g_return_val_if_fail(keypool, FALSE); +purple_pkcs12_request_password(void* handle, const char* filename, GCallback ok_cb, + GCallback cancel_cb, void *user_data) + PurpleRequestFieldGroup *group; + PurpleRequestField *field; + PurpleRequestFields *fields; + /* Close any previous password request windows */ + purple_request_close_with_handle((void*)filename); + /* TODO: Should display only filename and not whole path */ + primary = g_strdup_printf(_("Enter password for %s"), filename); + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + field = purple_request_field_string_new("password", _("Enter Password"), NULL, FALSE); + purple_request_field_string_set_masked(field, TRUE); + purple_request_field_set_required(field, TRUE); + purple_request_field_group_add_field(group, field); + field = purple_request_field_bool_new("remember", _("Save password"), FALSE); + purple_request_field_group_add_field(group, field); + purple_request_fields(handle, /* handle */ + _("Enter Password"), /* title */ + _("OK"), ok_cb, /* ok text and callback */ + _("Cancel"), cancel_cb, /* cancel text and callback */ + NULL, NULL, NULL, /* account, who, conv */ + user_data); /* callback data */ + g_free(primary); /* TODO: not right */ +/****************************************************************************/ +/****************************************************************************/ +purple_pkcs12_init(void) +purple_pkcs12_uninit(void) +purple_pkcs12_get_handle(void) +purple_pkcs12_find_scheme(const gchar *name) + PurplePkcs12Scheme *scheme = NULL; + g_return_val_if_fail(name, NULL); + /* Traverse the list of registered schemes and locate the + one whose name matches */ + for(l = pkcs12_schemes; l; l = l->next) { + scheme = (PurplePkcs12Scheme *)(l->data); + /* Name matches? that's our man */ + if(!g_ascii_strcasecmp(scheme->name, name)) + purple_debug_warning("pkcs12", + "Pkcs12Scheme %s requested but not found.\n", + /* TODO: Signalling and such? */ +purple_pkcs12_get_schemes(void) +purple_pkcs12_register_scheme(PurplePkcs12Scheme *scheme) + g_return_val_if_fail(scheme != NULL, FALSE); + /* Make sure no scheme is registered with the same name */ + if (purple_pkcs12_find_scheme(scheme->name) != NULL) { + /* Okay, we're golden. Register it. */ + pkcs12_schemes = g_list_prepend(pkcs12_schemes, scheme); + /* TODO: Signalling and such? */ + purple_debug_info("pkcs12", + "Pkcs12Scheme %s registered\n", +purple_pkcs12_unregister_scheme(PurplePkcs12Scheme *scheme) + purple_debug_warning("pkcs12", + "Attempting to unregister NULL scheme\n"); + pkcs12_schemes = g_list_remove(pkcs12_schemes, scheme); + purple_debug_info("pkcs12", + "Pkcs12Scheme %s unregistered\n", --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/pkcs12.h Sun Aug 14 18:20:04 2011 +0000
@@ -0,0 +1,226 @@
+ * @file pkcs12.h PKCS12 API + * 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 + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA +#ifndef _PURPLE_PKCS12_H +#define _PURPLE_PKCS12_H +#include <certificate.h> +#endif /* __cplusplus */ +typedef struct _PurplePkcs12Scheme PurplePkcs12Scheme; +/** PKCS12 import/export + * A Pkcs12Scheme must implement all of the fields in the structure, + * and register it using purple_pkcs12_register_scheme() +struct _PurplePkcs12Scheme + /** Name of the pkcs12 scheme + * This must be globally unique - you may not register more than one + * Pkcs12Scheme of the same name at a time. + /** User-friendly name for this type + * ex: N_("X.509 PKCS12") + * When this is displayed anywhere, it should be i18ned + * ex: _(scheme->fullname) + * Imports PurpleCertificates and PurplePrivateKeys from a PKCS12 file + * @param filename File path to import from + * @param password Password protecting the PKCS12 file + * @param crt Certificate in the PKCS12 file. Must be free'd by caller. + * @param key Private key in the PKCS12 file. Must be free'd by caller. + * @return TRUE if at least one certificate and key were imported, and FALSE on failure + gboolean (*import_pkcs12)(const gchar *filename, const gchar *password, + PurpleCertificate **crt, PurplePrivateKey **key); + * Exports PurpleCertificates and PurplePrivateKey to a file + * @param filename File to export the key to + * @param password Password to protect the PKCS12 file + * @param crt Certificate to export + * @param key Key to export + * @return TRUE if the export succeeded, otherwise FALSE + gboolean (*export_pkcs12)(const gchar *filename, const gchar *password, + PurpleCertificate *crt, PurplePrivateKey *key); + void (*_purple_reserved1)(void); + void (*_purple_reserved2)(void); + void (*_purple_reserved3)(void); +/*****************************************************************************/ +/** @name PKCS12 Functions */ +/*****************************************************************************/ + * Imports PurpleCertificates and PurplePrivateKeys from a PKCS12 file + * @param scheme Scheme to import under + * @param filename File path to import from + * @param password Password protecting the PKCS12 file + * @param crt Certificate in the PKCS12 file. Must be free'd by caller. + * @param key Private key in the PKCS12 file. Must be free'd by caller. + * @return TRUE if at least one certificate and key were imported, and FALSE on failure +purple_pkcs12_import(PurplePkcs12Scheme *scheme, const gchar *filename, const gchar *password, + PurpleCertificate **crt, PurplePrivateKey **key); + * Exports PurpleCertificates and PurplePrivateKey to a file + * @param filename File to export the key to + * @param password Password to protect the PKCS12 file + * @param crt Certificate to export + * @param key Key to export + * @return TRUE if the export succeeded, otherwise FALSE +purple_pkcs12_export(PurplePkcs12Scheme *scheme, const gchar *filename, const gchar *password, + PurpleCertificate *crt, PurplePrivateKey *key); + * Imports certificates and key into given certificate and private key pools. + * @param scheme Scheme to import under + * @param filename File path to import from + * @param password Password protecting the PKCS12 file + * @param certpool CertificatePool to import certificates into + * @param keypool PrivateKeyPool to import keys into + * @return TRUE if as least one certificate and key were imported, and FALSE on failure +purple_pkcs12_import_to_pool(PurplePkcs12Scheme *scheme, const gchar *filename, const gchar *password, + PurpleCertificatePool *certpool, PurplePrivateKeyPool *keypool); + * Request the password used to encrypt the pkcs12 file. + * @param filename File name of the pkcs12 file. + * @param ok_cb Called when the user acknowledges the request. + * @param cancel_cb Called when the user cancels the request. + * @param user_data Opaque data pointer. +purple_pkcs12_request_password(void* handle, const char* filename, GCallback ok_cb, + GCallback cancel_cb, void *user_data); +/*****************************************************************************/ +/** @name PKCS12 Subsystem API */ +/*****************************************************************************/ + * Initialize the PKCS12 system +purple_pkcs12_init(void); + * Un-initialize the pkcs12 system +purple_pkcs12_uninit(void); + * Get the pkcs12 subsystem handle for signalling purposes +purple_pkcs12_get_handle(void); +/** Look up a registered PKCS12 by name + * @param name The scheme name. Case insensitive. + * @return Pointer to the located Scheme, or NULL if it isn't found. +purple_pkcs12_find_scheme(const gchar *name); + * Get all registered Pkcs12Schemes + * @return GList pointing to all registered Pkcs12Schemes . This value + * is owned by libpurple +purple_pkcs12_get_schemes(void); +/** Register a Pkcs12Scheme with libpurple + * No two schemes can be registered with the same name; this function enforces + * @param scheme Pointer to the scheme to register. + * @return TRUE if the scheme was successfully added, otherwise FALSE +purple_pkcs12_register_scheme(PurplePkcs12Scheme *scheme); +/** Unregister a Pkcs12Scheme 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 +purple_pkcs12_unregister_scheme(PurplePkcs12Scheme *scheme); +#endif /* __cplusplus */ +#endif /* _PURPLE_PKCS12_H */ --- a/libpurple/plugins/ssl/ssl-gnutls.c Sat Aug 13 21:57:35 2011 +0000
+++ b/libpurple/plugins/ssl/ssl-gnutls.c Sun Aug 14 18:20:04 2011 +0000
@@ -22,6 +22,8 @@
@@ -31,6 +33,7 @@
#include <gnutls/gnutls.h>
+#include <gnutls/pkcs12.h> @@ -39,10 +42,21 @@
+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 @@
/* TODO: I can likely remove this */
gnutls_certificate_set_x509_trust_file(xcred, "ca.pem",
+// gnutls_certificate_set_x509_simple_pkcs12_file(xcred, "test.p12", GNUTLS_X509_FMT_DER, "abcd"); + /* Use direct hashing since the key is a pointer. */ + sslConnTable = g_hash_table_new(NULL, NULL); + g_hash_table_destroy(sslConnTable); gnutls_certificate_free_credentials(xcred);
@@ -392,6 +411,10 @@
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 @@
gnutls_certificate_type_set_priority(gnutls_data->session,
+ 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 + purple_debug_info("gnutls/handshake", + "Authenticating with certificate/key %s\n", + ssl_gnutls_set_client_auth(xcred, gsc->certificate, gsc->key); gnutls_credentials_set(gnutls_data->session, GNUTLS_CRD_CERTIFICATE,
@@ -453,6 +488,9 @@
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 @@
/************************************************************************/
/* X.509 functionality */
/************************************************************************/
@@ -617,7 +656,7 @@
/** 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 @@
/* X.509 certificate operations provided by this plugin */
static PurpleCertificateScheme x509_gnutls = {
"x509", /* Scheme name */
@@ -1165,6 +1206,1031 @@
+/********************************************************** + * X.509 Private Key operations * + **********************************************************/ +const gchar * KEY_SCHEME_NAME = "x509"; +static PurplePrivateKeyScheme x509_key_gnutls; +/** Refcounted GnuTLS private key data instance */ + gnutls_x509_privkey_t key; +/** Helper functions for reference counting */ +x509_keydata_addref(x509_keydata_t *kd) +x509_keydata_delref(x509_keydata_t *kd) + 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 */ + gnutls_x509_privkey_deinit( kd->key ); + /* And kill the struct */ +/** Helper macro to retrieve the GnuTLS crt_t from a PurplePrivateKey */ +#define X509_GET_GNUTLS_KEYDATA(pkey) ( ((x509_keydata_t *) (pkey->data))->key) +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", + /* Next, we'll simply yank the entire contents of the file + /* TODO: Should I worry about very large files here? */ + g_file_get_contents(filename, + NULL /* No error checking for now */ + *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; +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_x509_crt_fmt_t fmt; + /* Allocate and prepare the internal key data */ + keydat = g_new0(x509_keydata_t, 1); + if (GNUTLS_E_SUCCESS != gnutls_x509_privkey_init(&keydat->key)) { + key = g_new0(PurplePrivateKey, 1); + gnutls_x509_privkey_deinit(keydat->key); + key->scheme = &x509_key_gnutls; + 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); +x509_export_key(const gchar *filename, PurplePrivateKey *key, const gchar* password) + gnutls_x509_privkey_t key_dat; /* GnuTLS key struct */ + gchar * out_buf; /* Data to output */ + size_t out_size; /* Output size */ + gboolean success = FALSE; + /* 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 */ + ret = gnutls_x509_privkey_export_pkcs8(key_dat, GNUTLS_X509_FMT_PEM, + 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_buf = g_new0(gchar, out_size); + ret = gnutls_x509_privkey_export_pkcs8(key_dat, GNUTLS_X509_FMT_PEM, + 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", + /* Write it out to an actual file */ + success = purple_util_write_data_to_file_absolute(filename, +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); +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", + /* Use the reference counting system to free (or not) the + x509_keydata_delref((x509_keydata_t *)key->data); + /* Kill the structure itself */ +x509_get_unique_key_id(PurplePrivateKey *key) + gnutls_x509_privkey_t key_dat; /* GnuTLS key struct */ + 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); + 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", + 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 */ +/********************************************************** + **********************************************************/ + * 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 + * 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") +parse_pkcs12 (gnutls_certificate_credentials_t res, + gnutls_x509_privkey_t * key, + gnutls_x509_crt_t * cert, gnutls_x509_crl_t * crl) + gnutls_pkcs12_bag_t bag = NULL; + size_t cert_id_size = 0; + size_t key_id_size = 0; + unsigned char cert_id[20]; + unsigned char key_id[20]; + /* find the first private key */ + ret = gnutls_pkcs12_bag_init (&bag); + ret = gnutls_pkcs12_get_bag (p12, idx, bag); + if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) + ret = gnutls_pkcs12_bag_get_type (bag, 0); + if (ret == GNUTLS_BAG_ENCRYPTED) + ret = gnutls_pkcs12_bag_decrypt (bag, password); + elements_in_bag = gnutls_pkcs12_bag_get_count (bag); + if (elements_in_bag < 0) + for (i = 0; i < elements_in_bag; i++) + type = gnutls_pkcs12_bag_get_type (bag, i); + ret = gnutls_pkcs12_bag_get_data (bag, i, &data); + case GNUTLS_BAG_PKCS8_ENCRYPTED_KEY: + case GNUTLS_BAG_PKCS8_KEY: + if (*key != NULL) /* too simple to continue */ + ret = gnutls_x509_privkey_init (key); + ret = gnutls_x509_privkey_import_pkcs8 + (*key, &data, GNUTLS_X509_FMT_DER, password, + type == GNUTLS_BAG_PKCS8_KEY ? GNUTLS_PKCS_PLAIN : 0); + gnutls_x509_privkey_deinit (*key); + key_id_size = sizeof (key_id); + gnutls_x509_privkey_get_key_id (*key, 0, key_id, + gnutls_x509_privkey_deinit (*key); + privkey_ok = 1; /* break */ + gnutls_pkcs12_bag_deinit (bag); + if (privkey_ok != 0) /* private key was found */ + if (privkey_ok == 0) /* no private key */ + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + /* now find the corresponding certificate + ret = gnutls_pkcs12_bag_init (&bag); + ret = gnutls_pkcs12_get_bag (p12, idx, bag); + if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) + ret = gnutls_pkcs12_bag_get_type (bag, 0); + if (ret == GNUTLS_BAG_ENCRYPTED) + ret = gnutls_pkcs12_bag_decrypt (bag, password); + elements_in_bag = gnutls_pkcs12_bag_get_count (bag); + if (elements_in_bag < 0) + for (i = 0; i < elements_in_bag; i++) + type = gnutls_pkcs12_bag_get_type (bag, i); + ret = gnutls_pkcs12_bag_get_data (bag, i, &data); + case GNUTLS_BAG_CERTIFICATE: + if (*cert != NULL) /* no need to set it again */ + ret = gnutls_x509_crt_init (cert); + gnutls_x509_crt_import (*cert, &data, GNUTLS_X509_FMT_DER); + gnutls_x509_crt_deinit (*cert); + /* check if the key id match */ + cert_id_size = sizeof (cert_id); + gnutls_x509_crt_get_key_id (*cert, 0, cert_id, &cert_id_size); + gnutls_x509_crt_deinit (*cert); + if (memcmp (cert_id, key_id, cert_id_size) != 0) + { /* they don't match - skip the certificate */ + gnutls_x509_crt_deinit (*cert); + ret = gnutls_x509_crl_init (crl); + ret = gnutls_x509_crl_import (*crl, &data, GNUTLS_X509_FMT_DER); + gnutls_x509_crl_deinit (*crl); + case GNUTLS_BAG_ENCRYPTED: + /* XXX Bother to recurse one level down? Unlikely to + use the same password anyway. */ + gnutls_pkcs12_bag_deinit (bag); + gnutls_pkcs12_bag_deinit (bag); +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", + /* Next, we'll simply yank the entire contents of the file + /* TODO: Should I worry about very large files here? */ + g_file_get_contents(filename, + NULL /* No error checking for now */ + *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; + * Derived from gnutls_certificate_set_x509_simple_pkcs12_mem in + * gnutls_x509.c. Modified to return PurpleCertificate and PurplePrivateKey +x509_import_pkcs12_from_file(const gchar* filename, + PurpleCertificate **crt, + PurplePrivateKey **key) + gnutls_certificate_credentials_t res = NULL; + gnutls_x509_crl_t crl = NULL; + gnutls_x509_crt_fmt_t fmt; + x509_crtdata_t *crtdat; + x509_keydata_t *keydat; + if (!read_pkcs12_file(filename, &dt, &fmt)) { + purple_debug_error("gnutls", + "Failed to load PKCS12 file from %s\n", + 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)); + 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); + 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); + /* Allocate and prepare the internal key and crt data */ + crtdat = g_new0(x509_crtdata_t, 1); + keydat = g_new0(x509_keydata_t, 1); + 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); + /* 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"); + *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 (key && (ret = _gnutls_check_key_cert_match (res)) < 0) { +/** Export PurpleCertificate and PurplePrivateKey to a PKCS12 file. +/* Derived from generate_pkcs12() in certtool.c in the gnutls source. */ +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; + unsigned char _key_id[20]; + gboolean success = FALSE; + gnutls_pkcs12_bag_t kbag = NULL; + gnutls_pkcs12_bag_t bag = NULL; + crts[0] = X509_GET_GNUTLS_DATA(purple_crt); + key = X509_GET_GNUTLS_KEYDATA(purple_key); + name = x509_common_name(purple_crt); + purple_debug_error("gnutls/pkcs12", "export: can't get common name for cert\n"); + result = gnutls_pkcs12_init (&pkcs12); + purple_debug_error("gnutls/pkcs12", "export: pkcs12_init: %s\n", gnutls_strerror (result)); + for (i = 0; i < ncrts; i++) + result = gnutls_pkcs12_bag_init (&bag); + purple_debug_error("gnutls/pkcs12", "export: bag_init: %s\n", gnutls_strerror (result)); + result = gnutls_pkcs12_bag_set_crt (bag, crts[i]); + purple_debug_error ("gnutls/pkcs12", "export: set_crt[%d]: %s\n", i, + gnutls_strerror (result)); + result = gnutls_pkcs12_bag_set_friendly_name (bag, indx, name); + purple_debug_error ("gnutls/pkcs12", "bag_set_friendly_name: %s\n", + gnutls_strerror (result)); + size = sizeof (_key_id); + result = gnutls_x509_crt_get_key_id (crts[i], 0, _key_id, &size); + purple_debug_error("gnutls/pkcs12", "key_id[%d]: %s\n", i, + gnutls_strerror(result)); + result = gnutls_pkcs12_bag_set_key_id (bag, indx, &key_id); + purple_debug_error("gnutls/pkcs12", "bag_set_key_id: %s\n", + gnutls_strerror(result)); + /* TODO: Check GNUTLS version and use AES if possible */ + flags = GNUTLS_PKCS_USE_PBES2_3DES; + result = gnutls_pkcs12_bag_encrypt (bag, password, flags); + purple_debug_error("gnutls/pkcs12", "bag_encrypt: %s\n", gnutls_strerror (result)); + result = gnutls_pkcs12_set_bag (pkcs12, bag); + purple_debug_error("gnutls/pkcs12", "set_bag: %s\n", gnutls_strerror (result)); + gnutls_pkcs12_bag_deinit(bag); + result = gnutls_pkcs12_bag_init (&kbag); + purple_debug_error("gnutls/pkcs12", "bag_init: %s\n", gnutls_strerror (result)); + /* TODO: Check GNUTLS version and use AES if possible */ + flags = GNUTLS_PKCS_USE_PBES2_3DES; + 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"); + 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. + key_buf = g_new0(char, size); + result = gnutls_x509_privkey_export_pkcs8 (key, GNUTLS_X509_FMT_DER, + password, flags, key_buf, &size); + purple_debug_error("gnutls/pkcs12", "key_export: size: %d; error: %s\n", + size, gnutls_strerror (result)); + data.data = (unsigned char*)key_buf; + gnutls_pkcs12_bag_set_data (kbag, + GNUTLS_BAG_PKCS8_ENCRYPTED_KEY, &data); + purple_debug_error("gnutls/pkcs12", "bag_set_data: %s\n", gnutls_strerror (result)); + result = gnutls_pkcs12_bag_set_friendly_name (kbag, indx, name); + purple_debug_error("gnutls/pkcs12", "bag_set_friendly_name: %s\n", + gnutls_strerror(result)); + size = sizeof (_key_id); + result = gnutls_x509_privkey_get_key_id (key, 0, _key_id, &size); + purple_debug_error("gnutls/pkcs12", "key_id: %s\n", gnutls_strerror (result)); + result = gnutls_pkcs12_bag_set_key_id (kbag, indx, &key_id); + purple_debug_error("gnutls/pkcs12", "bag_set_key_id: %s\n", + gnutls_strerror(result)); + result = gnutls_pkcs12_set_bag (pkcs12, kbag); + purple_debug_error("gnutls/pkcs12", "set_bag: %s\n", gnutls_strerror (result)); + result = gnutls_pkcs12_generate_mac (pkcs12, password); + purple_debug_error("gnutls/pkcs12", "generate_mac: %s\n", gnutls_strerror (result)); + 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"); + out_buf = g_new0(char, size); + purple_debug_error("gnutls/pkcs12", "output buf allocation failure\n"); + result = gnutls_pkcs12_export (pkcs12, GNUTLS_X509_FMT_PEM, out_buf, &size); + purple_debug_error("gnutls/pkcs12", "pkcs12_export: %s\n", gnutls_strerror (result)); + success = purple_util_write_data_to_file_absolute(filename, + gnutls_pkcs12_bag_deinit(bag); + gnutls_pkcs12_bag_deinit(kbag); + gnutls_pkcs12_deinit(pkcs12); +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); +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 */ +/********************************************************************** + * Setting the Purple Certificate and Private Key for authentication * + **********************************************************************/ +ssl_gnutls_set_client_auth(gnutls_certificate_client_credentials cred, PurpleCertificate * pcrt, PurplePrivateKey * pkey) + gnutls_x509_crt_t cert_list[1]; + 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); + 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", static PurpleSslOps ssl_ops =
@@ -1193,6 +2259,8 @@
/* 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 ); @@ -1205,6 +2273,8 @@
purple_certificate_unregister_scheme( &x509_gnutls );
+ purple_privatekey_unregister_scheme( &x509_key_gnutls ); + purple_pkcs12_unregister_scheme( &pkcs12_gnutls ); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/privatekey.c Sun Aug 14 18:20:04 2011 +0000
@@ -0,0 +1,909 @@
+ * @file privatekey.c Private-Key API + * 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 + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA +/** List holding pointers to all registered private key schemes */ +static GList *key_schemes = NULL; +/** List of registered Pools */ +static GList *key_pools = NULL; +/***************************************************************************** + * Purple Private Key API * + *****************************************************************************/ +purple_privatekey_copy(PurplePrivateKey *key) + g_return_val_if_fail(key, NULL); + g_return_val_if_fail(key->scheme, NULL); + g_return_val_if_fail(key->scheme->copy_key, NULL); + return (key->scheme->copy_key)(key); +purple_privatekey_destroy (PurplePrivateKey *key) + PurplePrivateKeyScheme *scheme; + if (NULL == key) return; + (scheme->destroy_key)(key); +purple_privatekey_import(PurplePrivateKeyScheme *scheme, const gchar *filename, + g_return_val_if_fail(scheme, NULL); + g_return_val_if_fail(scheme->import_key, NULL); + g_return_val_if_fail(filename, NULL); + return (scheme->import_key)(filename, password); +purple_privatekey_export(const gchar *filename, PurplePrivateKey *key, + PurplePrivateKeyScheme *scheme; + g_return_val_if_fail(filename, FALSE); + g_return_val_if_fail(key, FALSE); + g_return_val_if_fail(key->scheme, FALSE); + g_return_val_if_fail(scheme->export_key, FALSE); + return (scheme->export_key)(filename, key, password); +purple_privatekey_get_unique_id(PurplePrivateKey *key) + g_return_val_if_fail(key, NULL); + g_return_val_if_fail(key->scheme, NULL); + g_return_val_if_fail(key->scheme->get_unique_id, NULL); + return (key->scheme->get_unique_id)(key); +/***************************************************************************** + * Purple Private Key Pool API * + *****************************************************************************/ +purple_privatekey_pool_mkpath(PurplePrivateKeyPool *pool, const gchar *id) + gchar *esc_scheme_name, *esc_name, *esc_id; + g_return_val_if_fail(pool, NULL); + g_return_val_if_fail(pool->scheme_name, NULL); + g_return_val_if_fail(pool->name, NULL); + /* Escape all the elements for filesystem-friendliness */ + esc_scheme_name = pool ? g_strdup(purple_escape_filename(pool->scheme_name)) : NULL; + esc_name = pool ? g_strdup(purple_escape_filename(pool->name)) : NULL; + esc_id = id ? g_strdup(purple_escape_filename(id)) : NULL; + path = g_build_filename(purple_user_dir(), + "privatekeys", /* TODO: constantize this? */ + g_free(esc_scheme_name); +purple_privatekey_pool_set_password(PurplePrivateKeyPool *pool, const gchar* password) + g_return_val_if_fail(pool, FALSE); + g_return_val_if_fail(password, FALSE); + (pool->set_password)(password); +purple_privatekey_pool_get_password(PurplePrivateKeyPool *pool) + g_returen_val_if_fail(pool, FALSE); + return (pool->get_password)(); +purple_privatekey_pool_usable(PurplePrivateKeyPool *pool) + g_return_val_if_fail(pool, FALSE); + g_return_val_if_fail(pool->scheme_name, FALSE); + /* Check that the pool's scheme is loaded */ + if (purple_privatekey_find_scheme(pool->scheme_name) == NULL) { +PurplePrivateKeyScheme * +purple_privatekey_pool_get_scheme(PurplePrivateKeyPool *pool) + g_return_val_if_fail(pool, NULL); + g_return_val_if_fail(pool->scheme_name, NULL); + return purple_privatekey_find_scheme(pool->scheme_name); +purple_privatekey_pool_contains(PurplePrivateKeyPool *pool, const gchar *id) + g_return_val_if_fail(pool, FALSE); + g_return_val_if_fail(id, FALSE); + g_return_val_if_fail(pool->key_in_pool, FALSE); + return (pool->key_in_pool)(id); +/****************************************************************************** + * Purple Private Key retrieve and store that prompt user for password + PurplePrivateKeyPool *pool; +} privatekey_pool_req_data; +purple_privatekey_pool_request_password( + PurplePrivateKeyPool *pool, + PurpleRequestFieldGroup *group; + PurpleRequestField *field; + PurpleRequestFields *fields; + /* Close any previous password request windows */ + purple_request_close_with_handle(pool); + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + field = purple_request_field_string_new("password", _("Enter Password"), NULL, FALSE); + purple_request_field_string_set_masked(field, TRUE); + purple_request_field_set_required(field, TRUE); + purple_request_field_group_add_field(group, field); + field = purple_request_field_bool_new("remember", _("Save password"), FALSE); + purple_request_field_group_add_field(group, field); + purple_request_fields((void*)id, + _("Cancel"), cancel_cb, +privatekey_pool_req_cancel_cb(privatekey_pool_req_data *data, PurpleRequestFields *fields) + ((PurplePrivateKeyPoolCancelCb)(data->cancel_cb))(data->user_data); +privatekey_pool_req_retrieve_ok_cb(privatekey_pool_req_data *data, PurpleRequestFields *fields) + entry = purple_request_fields_get_string(fields, "password"); +/* remember = purple_request_fields_get_bool(fields, "remember");*/ + /* TODO: can we use any pointer for the handle here? */ + purple_notify_error((void*)data->id, NULL, _("Password is required to access your private keys."), NULL); + purple_account_set_remember_password(account, TRUE); + key = (data->pool->get_key)(data->id, entry); + ((PurplePrivateKeyPoolRetrieveRequestOkCb)data->ok_cb)(key, data->user_data); +void purple_privatekey_pool_retrieve_request( + PurplePrivateKeyPool *pool, const gchar *id, +// PurplePrivateKeyPoolRetrieveRequestOkCb ok_cb, +// PurplePrivateKeyPoolCancelCb cancel_cb, + privatekey_pool_req_data *data; + g_return_if_fail(pool); + g_return_if_fail(ok_cb); + g_return_if_fail(cancel_cb); + data = g_new0(privatekey_pool_req_data, 1); + g_return_if_fail(data); + data->ok_cb = G_CALLBACK(ok_cb); + data->cancel_cb = G_CALLBACK(cancel_cb); + data->user_data = user_data; + msg = g_strdup_printf(_("Enter the password protecting the key named \"%s\""), id); + purple_privatekey_pool_request_password( + G_CALLBACK(privatekey_pool_req_retrieve_ok_cb), + G_CALLBACK(privatekey_pool_req_cancel_cb), + g_free(msg); /* TODO: not sure this is safe */ +privatekey_pool_req_store_ok_cb(privatekey_pool_req_data *data, PurpleRequestFields *fields) + entry = purple_request_fields_get_string(fields, "password"); +/* remember = purple_request_fields_get_bool(fields, "remember");*/ + /* TODO: can we use any pointer for the handle here? */ + purple_notify_error((void*)data->id, NULL, _("Password is required to protect your private keys."), NULL); + purple_account_set_remember_password(account, TRUE); + result = (data->pool->put_key)(data->id, data->key, entry); + ((PurplePrivateKeyPoolStoreRequestOkCb)data->ok_cb)(result, data->user_data); +purple_privatekey_pool_store_request( + PurplePrivateKeyPool *pool, const gchar *id, PurplePrivateKey *key, +// PurplePrivateKeyPoolStoreRequestOkCb ok_cb, +// PurplePrivateKeyPoolCancelCb cancel_cb, + privatekey_pool_req_data *data; + g_return_if_fail(pool); + g_return_if_fail(ok_cb); + g_return_if_fail(cancel_cb); + data = g_new0(privatekey_pool_req_data, 1); + g_return_if_fail(data); + data->ok_cb = G_CALLBACK(ok_cb); + data->cancel_cb = G_CALLBACK(cancel_cb); + data->user_data = user_data; + msg = g_strdup_printf(_("Enter a password to protect the key named \"%s\""), id); + purple_privatekey_pool_request_password( + G_CALLBACK(privatekey_pool_req_store_ok_cb), + G_CALLBACK(privatekey_pool_req_cancel_cb), + g_free(msg); /* TODO: not sure this is safe */ +/****************************************************************************** + * Purple Private Key Pool direct retrieve and store +purple_privatekey_pool_retrieve(PurplePrivateKeyPool *pool, const gchar *id, const gchar *password) + g_return_val_if_fail(pool, NULL); + g_return_val_if_fail(id, NULL); + g_return_val_if_fail(pool->get_key, NULL); + return (pool->get_key)(id, password); +purple_privatekey_pool_store(PurplePrivateKeyPool *pool, const gchar *id, PurplePrivateKey *key, const gchar* password) + g_return_val_if_fail(pool, FALSE); + g_return_val_if_fail(id, FALSE); + g_return_val_if_fail(pool->put_key, FALSE); + /* Whether key->scheme matches find_scheme(pool->scheme_name) is not + relevant... I think... */ + g_ascii_strcasecmp(pool->scheme_name, key->scheme->name) == 0, + ret = (pool->put_key)(id, key, password); + /* Signal that the certificate was stored if success*/ + purple_signal_emit(pool, "privatekey-stored", +purple_privatekey_pool_delete(PurplePrivateKeyPool *pool, const gchar *id) + g_return_val_if_fail(pool, FALSE); + g_return_val_if_fail(id, FALSE); + g_return_val_if_fail(pool->delete_key, FALSE); + ret = (pool->delete_key)(id); + /* Signal that the key was deleted if success */ + purple_signal_emit(pool, "key-deleted", +purple_privatekey_pool_get_idlist(PurplePrivateKeyPool *pool) + g_return_val_if_fail(pool, NULL); + g_return_val_if_fail(pool->get_idlist, NULL); + return (pool->get_idlist)(); +purple_privatekey_pool_destroy_idlist(GList *idlist) + /* Iterate through and free them strings */ + for ( l = idlist; l; l = l->next ) { +/****************************************************************************/ +/****************************************************************************/ +/***** Cache of user's keys *****/ +static PurplePrivateKeyPool x509_user_keys; +typedef struct x509_user_data { +static GList* x509_user_key_paths = NULL; + x509_user_keys.data = g_new0(x509_user_data_t, 1); + /* Set up key cache here if it isn't already done */ + poolpath = purple_privatekey_pool_mkpath(&x509_user_keys, NULL); + ret = purple_build_dir(poolpath, 0700); /* Make it this user only */ + purple_debug_info("x509_user/keys", + "Could not create %s. Keys will not be saved.\n", + if (x509_user_keys.data != NULL) + g_free(x509_user_keys.data); +x509_user_set_password(const gchar *password) + x509_user_data_t *user_data = (x509_user_data_t*)x509_user_keys.data; + user_data->password = g_strdup(password); + purple_debug_info("x509_user/keys", "Setting password\n"); +x509_user_key_in_pool(const gchar *id) + g_return_val_if_fail(id, FALSE); + keypath = purple_privatekey_pool_mkpath(&x509_user_keys, id); + ret = g_file_test(keypath, G_FILE_TEST_IS_REGULAR); +static PurplePrivateKey * +x509_user_get_key(const gchar *id, const gchar* password) + PurplePrivateKeyScheme *x509; + g_return_val_if_fail(id, NULL); + /* Is it in the pool? */ + if ( !x509_user_key_in_pool(id) ) { + /* Look up the X.509 scheme */ + x509 = purple_privatekey_find_scheme("x509"); + g_return_val_if_fail(x509, NULL); + /* Okay, now find and load that key */ + keypath = purple_privatekey_pool_mkpath(&x509_user_keys, id); + key = purple_privatekey_import(x509, keypath, password); +x509_user_put_key(const gchar *id, PurplePrivateKey *key, const gchar* password) + g_return_val_if_fail(key, FALSE); + g_return_val_if_fail(key->scheme, FALSE); + /* Make sure that this is some kind of X.509 certificate */ + /* TODO: Perhaps just check key->scheme->name instead? */ + g_return_val_if_fail(key->scheme == purple_privatekey_find_scheme(x509_user_keys.scheme_name), FALSE); + /* Work out the filename and export */ + keypath = purple_privatekey_pool_mkpath(&x509_user_keys, id); + ret = purple_privatekey_export(keypath, key, password); +x509_user_delete_key(const gchar *id) + g_return_val_if_fail(id, FALSE); + /* Is the id even in the pool? */ + if (!x509_user_key_in_pool(id)) { + purple_debug_warning("x509_user/keys", + "Id %s wasn't in the key pool\n", + /* OK, so work out the keypath and delete the thing */ + keypath = purple_privatekey_pool_mkpath(&x509_user_keys, id); + if ( unlink(keypath) != 0 ) { + purple_debug_error("x509_user/keys", + "Unlink of %s failed!\n", +x509_user_get_idlist(void) + /* Get a handle on the pool directory */ + poolpath = purple_privatekey_pool_mkpath(&x509_user_keys, NULL); + dir = g_dir_open(poolpath, + NULL); /* Not interested in what the error is */ + g_return_val_if_fail(dir, NULL); + /* Traverse the directory listing and create an idlist */ + while ( (entry = g_dir_read_name(dir)) != NULL ) { + /* Unescape the filename */ + const char *unescaped = purple_unescape_filename(entry); + /* Copy the entry name into our list (GLib owns the original + idlist = g_list_prepend(idlist, g_strdup(unescaped)); + /* Release the directory */ +static PurplePrivateKeyPool x509_user_keys = { + "x509", /* Scheme name */ + "user", /* Pool name */ + N_("My Private Keys"), /* User-friendly name */ + NULL, /* Internal data */ + x509_user_init, /* init */ + x509_user_uninit, /* uninit */ + x509_user_key_in_pool, /* Certificate exists? */ + x509_user_get_key, /* Cert retriever */ + x509_user_put_key, /* Cert writer */ + x509_user_delete_key, /* Cert remover */ + x509_user_get_idlist, /* idlist retriever */ +/****************************************************************************/ +/****************************************************************************/ +purple_privatekey_init(void) + /* Register builtins */ + purple_privatekey_register_pool(&x509_user_keys); +purple_privatekey_uninit(void) + /* Unregister all Pools */ + g_list_foreach(key_pools, (GFunc)purple_privatekey_unregister_pool, NULL); +purple_privatekey_get_handle(void) +PurplePrivateKeyScheme * +purple_privatekey_find_scheme(const gchar *name) + PurplePrivateKeyScheme *scheme = NULL; + g_return_val_if_fail(name, NULL); + /* Traverse the list of registered schemes and locate the + one whose name matches */ + for(l = key_schemes; l; l = l->next) { + scheme = (PurplePrivateKeyScheme *)(l->data); + /* Name matches? that's our man */ + if(!g_ascii_strcasecmp(scheme->name, name)) + purple_debug_warning("privatekey", + "PrivateKeyScheme %s requested but not found.\n", + /* TODO: Signalling and such? */ +purple_privatekey_get_schemes(void) +purple_privatekey_register_scheme(PurplePrivateKeyScheme *scheme) + g_return_val_if_fail(scheme != NULL, FALSE); + /* Make sure no scheme is registered with the same name */ + if (purple_privatekey_find_scheme(scheme->name) != NULL) { + /* Okay, we're golden. Register it. */ + key_schemes = g_list_prepend(key_schemes, scheme); + /* TODO: Signalling and such? */ + purple_debug_info("privatekey", + "PrivateKeyScheme %s registered\n", +purple_privatekey_unregister_scheme(PurplePrivateKeyScheme *scheme) + purple_debug_warning("privatekey", + "Attempting to unregister NULL scheme\n"); + /* TODO: signalling? */ + /* TODO: unregister all PrivateKeyPools for this scheme? */ + /* Neither of the above should be necessary, though */ + key_schemes = g_list_remove(key_schemes, scheme); + purple_debug_info("privatekey", + "PrivateKeyScheme %s unregistered\n", +purple_privatekey_find_pool(const gchar *scheme_name, const gchar *pool_name) + PurplePrivateKeyPool *pool = NULL; + g_return_val_if_fail(scheme_name, NULL); + g_return_val_if_fail(pool_name, NULL); + /* Traverse the list of registered pools and locate the + one whose name matches */ + for(l = key_pools; l; l = l->next) { + pool = (PurplePrivateKeyPool *)(l->data); + /* Scheme and name match? */ + if(!g_ascii_strcasecmp(pool->scheme_name, scheme_name) && + !g_ascii_strcasecmp(pool->name, pool_name)) + purple_debug_warning("privatekey", + "PrivateKeyPool %s, %s requested but not found.\n", + scheme_name, pool_name); + /* TODO: Signalling and such? */ +purple_privatekey_get_pools(void) +purple_privatekey_register_pool(PurplePrivateKeyPool *pool) + g_return_val_if_fail(pool, FALSE); + g_return_val_if_fail(pool->scheme_name, FALSE); + g_return_val_if_fail(pool->name, FALSE); + g_return_val_if_fail(pool->fullname, FALSE); + /* Make sure no pools are registered under this name */ + if (purple_privatekey_find_pool(pool->scheme_name, pool->name)) { + /* Initialize the pool if needed */ + success = pool->init(); + /* Register the Pool */ + key_pools = g_list_prepend(key_pools, pool); + /* TODO: Emit a signal that the pool got registered */ + PURPLE_DBUS_REGISTER_POINTER(pool, PurplePrivateKeyPool); + purple_signal_register(pool, /* Signals emitted from pool */ + purple_marshal_VOID__POINTER_POINTER, + NULL, /* No callback return value */ + 2, /* Two non-data arguments */ + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_PRIVATEKEYPOOL), + purple_value_new(PURPLE_TYPE_STRING)); + purple_signal_register(pool, /* Signals emitted from pool */ + purple_marshal_VOID__POINTER_POINTER, + NULL, /* No callback return value */ + 2, /* Two non-data arguments */ + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_PRIVATEKEYPOOL), + purple_value_new(PURPLE_TYPE_STRING)); + purple_debug_info("privatekey", + "PrivateKeyPool %s registered\n", +purple_privatekey_unregister_pool(PurplePrivateKeyPool *pool) + purple_debug_warning("privatekey", + "Attempting to unregister NULL pool\n"); + /* Check that the pool is registered */ + if (!g_list_find(key_pools, pool)) { + purple_debug_warning("privatekey", + "Pool to unregister isn't registered!\n"); + /* Uninit the pool if needed */ + PURPLE_DBUS_UNREGISTER_POINTER(pool); + key_pools = g_list_remove(key_pools, pool); + /* TODO: Signalling? */ + purple_signal_unregister(pool, "privatekey-stored"); + purple_signal_unregister(pool, "privatekey-deleted"); + purple_debug_info("privatekey", + "PrivateKeyPool %s unregistered\n", +/****************************************************************************/ +/* Scheme-specific functions */ +/****************************************************************************/ +purple_privatekey_display_x509(PurplePrivateKey *key) +void purple_privatekey_add_key_search_path(const char *path) + if (g_list_find_custom(x509_user_key_paths, path, (GCompareFunc)strcmp)) + x509_user_key_paths = g_list_append(x509_user_key_paths, g_strdup(path)); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/privatekey.h Sun Aug 14 18:20:04 2011 +0000
@@ -0,0 +1,524 @@
+ * @file privatekey.h Private-Key API + * @see @ref privatekey-signals + * 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 + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA +#ifndef _PURPLE_PRIVATEKEY_H +#define _PURPLE_PRIVATEKEY_H +#endif /* __cplusplus */ +typedef struct _PurplePrivateKey PurplePrivateKey; +typedef struct _PurplePrivateKeyPool PurplePrivateKeyPool; +typedef struct _PurplePrivateKeyScheme PurplePrivateKeyScheme; +/** A private key instance + * An opaque data structure representing a single private key under some +struct _PurplePrivateKey + /** Scheme this private key is under */ + PurplePrivateKeyScheme * scheme; + /** Opaque pointer to internal data */ + * Database for retrieval or storage of PrivateKeys + * More or less a hash table; all lookups and writes are controlled by a string +struct _PurplePrivateKeyPool + /** Scheme this Pool operates for */ + /** Internal name to refer to the pool by */ + /** User-friendly name for this type + * ex: N_("SSL Servers") + * When this is displayed anywhere, it should be i18ned + * ex: _(pool->fullname) + /** Internal pool data */ + * Set up the Pool's internal state + * Upon calling purple_privatekey_register_pool() , this function will + * be called. May be NULL. + * @return TRUE if the initialization succeeded, otherwise FALSE + gboolean (* init)(void); + * Uninit the Pool's internal state + * Will be called by purple_privatekey_unregister_pool() . May be NULL + * Use password to protect the keys on disk on the pool using the password. + //void (* set_password)(const gchar* password); + /** Check for presence of a private key in the pool using unique ID */ + gboolean (* key_in_pool)(const gchar *id); + /** Retrieve a PurplePrivateKey from the pool */ + PurplePrivateKey * (* get_key)(const gchar *id, const gchar* password); + /** Add a private key to the pool. Must overwrite any other + * keys sharing the same ID in the pool. + * @return TRUE if the operation succeeded, otherwise FALSE + gboolean (* put_key)(const gchar *id, PurplePrivateKey *crt, const gchar* password); + /** Delete a key from the pool */ + gboolean (* delete_key)(const gchar *id); + /** Returns a list of IDs stored in the pool */ + GList * (* get_idlist)(void); + void (*_purple_reserved1)(void); + void (*_purple_reserved2)(void); + void (*_purple_reserved3)(void); + void (*_purple_reserved4)(void); + * A PrivateKeyScheme must implement all of the fields in the structure, + * and register it using purple_privatekey_register_scheme() + * There may be only ONE PrivateKeyScheme provided for each certificate + * type, as specified by the "name" field. +struct _PurplePrivateKeyScheme + /** Name of the private key type + * ex: "x509", "pgp", etc. + * This must be globally unique - you may not register more than one + * PrivateKeyScheme of the same name at a time. + /** User-friendly name for this type + * ex: N_("X.509 PrivateKeys") + * When this is displayed anywhere, it should be i18ned + * ex: _(scheme->fullname) + /** Imports a private key from a file + * @param filename File to import the private key from + * @param password Password to decrypt the key file. + * @return Pointer to the newly allocated PrivateKey struct + PurplePrivateKey * (* import_key)(const gchar * filename, const gchar * password); + * Exports a private key to a file + * @param filename File to export the private key to + * @param key PrivateKey to export + * @param password Password t encrypt the key file + * @return TRUE if the export succeeded, otherwise FALSE + * @see purple_privatekey_export() + gboolean (* export_key)(const gchar *filename, PurplePrivateKey *key, const gchar* password); + * Duplicates a private key + * Keys are generally assumed to be read-only, so feel free to + * do any sort of reference-counting magic you want here. If this ever + * changes, please remember to change the magic accordingly. + * @return Reference to the new copy + PurplePrivateKey * (* copy_key)(PurplePrivateKey *key); + /** Destroys and frees a PrviateKey structure + * Destroys a PrivateKey's internal data structures and calls + * @param key PrviateKey instance to be destroyed. It WILL NOT be + * destroyed if it is not of the correct + * PrivateKeyScheme. Can be NULL + void (* destroy_key)(PurplePrivateKey * key); + * Retrieves a unique key identifier + * @param key PrivateKey instance + * @return Newly allocated string that can be used to uniquely + gchar* (* get_unique_id)(PurplePrivateKey *key); + void (*_purple_reserved1)(void); + void (*_purple_reserved2)(void); + void (*_purple_reserved3)(void); +/*****************************************************************************/ +/** @name Private Key Functions */ +/*****************************************************************************/ + * Makes a duplicate of a private key + * @param key Instance to duplicate + * @return Pointer to new instance +purple_privatekey_copy(PurplePrivateKey *key); + * Destroys and free()'s a PrivateKey + * @param key Instance to destroy. May be NULL. +purple_privatekey_destroy (PurplePrivateKey *key); + * Imports a PurplePrivateKey from a file + * @param scheme Scheme to import under + * @param filename File path to import from + * @param password Password to protecting the key on disk + * @return Pointer to a new PurplePrivateKey, or NULL on failure +purple_privatekey_import(PurplePrivateKeyScheme *scheme, const gchar *filename, const gchar *password); + * Exports a PurplePrivateKey to a file + * @param filename File to export the key to + * @param key Key to export + * @param password Password to protect the key on disk + * @return TRUE if the export succeeded, otherwise FALSE +purple_privatekey_export(const gchar *filename, PurplePrivateKey *key, const gchar *password); + * Get a unique identifier for the private key + * @param key PrivateKey instance + * @return String representing the key uniquely. Must be g_free()'ed +purple_privatekey_get_unique_id(PurplePrivateKey *key); +/*****************************************************************************/ +/** @name Private Key Pool Functions */ +/*****************************************************************************/ + * Helper function for generating file paths in ~/.purple/certificates for + * PrivateKeyPools that use them. + * All components will be escaped for filesystem friendliness. + * @param pool PrivateKeyPool to build a path for + * @param id Key to look up a PrivateKey by. May be NULL. + * @return A newly allocated path of the form + * ~/.purple/certificates/scheme_name/pool_name/unique_id +purple_privatekey_pool_mkpath(PurplePrivateKeyPool *pool, const gchar *id); + * Determines whether a pool can be used. + * Checks whether the associated PrivateKeyScheme is loaded. + * @param pool Pool to check + * @return TRUE if the pool can be used, otherwise FALSE +purple_privatekey_pool_usable(PurplePrivateKeyPool *pool); +purple_privatekey_pool_set_password(PurplePrivateKeyPool *pool, const gchar* password); +const gchar* purple_privatekey_pool_get_password(PurplePrivateKeyPool *pool); + * Looks up the scheme the pool operates under + * @param pool Pool to get the scheme of + * @return Pointer to the pool's scheme, or NULL if it isn't loaded. + * @see purple_privatekey_pool_usable() +PurplePrivateKeyScheme * +purple_privatekey_pool_get_scheme(PurplePrivateKeyPool *pool); + * Check for presence of an ID in a pool. + * @param pool Pool to look in + * @param id ID to look for + * @return TRUE if the ID is in the pool, else FALSE +purple_privatekey_pool_contains(PurplePrivateKeyPool *pool, const gchar *id); +typedef void (*PurplePrivateKeyPoolCancelCb)(void* data); +typedef void (*PurplePrivateKeyPoolRetrieveRequestOkCb)(PurplePrivateKey *key, void* data); +typedef void (*PurplePrivateKeyPoolStoreRequestOkCb)(gboolean result, void* data); + * Retrieve a key from a pool and prompt user for the password protecting the key. + * @param pool Pool to get key from + * @param id ID of key to retrieve + * @param ok_cb Called if the user clicks ok in the password prompt. + * The key parameter to the callback is non-null if the key was successfully + * retrieved from the pool and null otherwise. + * @param cancel_cb Called if the user cancels the password dialog. + * @param user_data Pointer to caller defined data structure to be passed to callbacks. +purple_privatekey_pool_retrieve_request( + PurplePrivateKeyPool *pool, const gchar *id, + * Store a key in the given pool and prompt user for a password to protect the key. + * @param pool Pool to store key in + * @param id ID of key to store + * @param key Key to store. + * @param ok_cb Called if the user clicks ok in the password prompt. + * The result parameter to the callback is true if the key was successfully + * stored in the pool and false otherwise. + * @param cancel_cb Called if the user cancels the password dialog. + * @param user_data Pointer to caller defined data structure to be passed to callbacks. +purple_privatekey_pool_store_request( + PurplePrivateKeyPool *pool, const gchar *id, PurplePrivateKey *key, + * Retrieve a key from a pool. + * @param pool Pool to fish in + * @param id ID to look up + * @param password Password used to encrypt key + * @return Retrieved key, or NULL if it wasn't there +purple_privatekey_pool_retrieve(PurplePrivateKeyPool *pool, const gchar *id, const gchar *password); + * Any pre-existing key of the same ID will be overwritten. + * @param pool Pool to add to + * @param id ID to store the key with + * @param key Key to store + * @param password Password to decrypt key + * @return TRUE if the operation succeeded, otherwise FALSE +purple_privatekey_pool_store(PurplePrivateKeyPool *pool, const gchar *id, PurplePrivateKey *key, const gchar *password); + * Remove a key from a pool + * @param pool Pool to remove from + * @param id ID to remove + * @return TRUE if the operation succeeded, otherwise FALSE +purple_privatekey_pool_delete(PurplePrivateKeyPool *pool, const gchar *id); + * Get the list of IDs currently in the pool. + * @param pool Pool to enumerate + * @return GList pointing to newly-allocated id strings. Free using + * purple_privatekey_pool_destroy_idlist() +purple_privatekey_pool_get_idlist(PurplePrivateKeyPool *pool); + * Destroys the result given by purple_privatekey_pool_get_idlist() + * @param idlist ID List to destroy +purple_privatekey_pool_destroy_idlist(GList *idlist); +/*****************************************************************************/ +/** @name PrivateKey Subsystem API */ +/*****************************************************************************/ + * Initialize the private key system +purple_privatekey_init(void); + * Un-initialize the private key system +purple_privatekey_uninit(void); + * Get the PrivateKey subsystem handle for signalling purposes +purple_privatekey_get_handle(void); +/** Look up a registered PrivateKeyScheme by name + * @param name The scheme name. Case insensitive. + * @return Pointer to the located Scheme, or NULL if it isn't found. +PurplePrivateKeyScheme * +purple_privatekey_find_scheme(const gchar *name); + * Get all registered PrivateKeySchemes + * @return GList pointing to all registered PrivateKeySchemes . This value + * is owned by libpurple +purple_privatekey_get_schemes(void); +/** Register a PrivateKeyScheme with libpurple + * No two schemes can be registered with the same name; this function enforces + * @param scheme Pointer to the scheme to register. + * @return TRUE if the scheme was successfully added, otherwise FALSE +purple_privatekey_register_scheme(PurplePrivateKeyScheme *scheme); +/** Unregister a PrivateKeyScheme 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 +purple_privatekey_unregister_scheme(PurplePrivateKeyScheme *scheme); +/** Look up a registered PurplePrivateKeyPool by scheme and name + * @param scheme_name Scheme name. Case insensitive. + * @param pool_name Pool name. Case insensitive. + * @return Pointer to the located Pool, or NULL if it isn't found. +purple_privatekey_find_pool(const gchar *scheme_name, const gchar *pool_name); + * Get the list of registered Pools + * @return GList of all registered PurplePrivateKeyPools. This value + * is owned by libpurple +purple_privatekey_get_pools(void); + * Register a PrivateKeyPool with libpurple and call its init function + * @param pool Pool to register. + * @return TRUE if the register succeeded, otherwise FALSE +purple_privatekey_register_pool(PurplePrivateKeyPool *pool); + * Unregister a PrivateKeyPool with libpurple and call its uninit function + * @param pool Pool to unregister. + * @return TRUE if the unregister succeeded, otherwise FALSE +purple_privatekey_unregister_pool(PurplePrivateKeyPool *pool); + * Add a search path for keys. + * @param path Path to search for keys. +void purple_privatekey_add_key_search_path(const char *path); +#endif /* __cplusplus */ +#endif /* _PURPLE_PRIVATEKEY_H */ --- a/libpurple/protocols/jabber/auth_cyrus.c Sat Aug 13 21:57:35 2011 +0000
+++ b/libpurple/protocols/jabber/auth_cyrus.c Sun Aug 14 18:20:04 2011 +0000
@@ -216,6 +216,7 @@
js->sasl_state = sasl_client_new("xmpp", js->serverFQDN, NULL, NULL, js->sasl_cb, 0, &js->sasl);
if (js->sasl_state==SASL_OK) {
sasl_setprop(js->sasl, SASL_SEC_PROPS, &secprops);
+ sasl_setprop(js->sasl, SASL_AUTH_EXTERNAL, account->username); purple_debug_info("sasl", "Mechs found: %s\n", js->sasl_mechs->str);
js->sasl_state = sasl_client_start(js->sasl, js->sasl_mechs->str, NULL, &clientout, &coutlen, &js->current_mech);
@@ -411,8 +412,8 @@
* Except SASL_NOMECH is supposed to mean "no concordant
* mechanisms"... Easiest just to blacklist it (for now).
- if (!mech_name || !*mech_name ||
- g_str_equal(mech_name, "EXTERNAL")) {
+ if (!mech_name || !*mech_name /*|| + g_str_equal(mech_name, "EXTERNAL")*/) { --- a/libpurple/protocols/jabber/jabber.c Sat Aug 13 21:57:35 2011 +0000
+++ b/libpurple/protocols/jabber/jabber.c Sun Aug 14 18:20:04 2011 +0000
@@ -852,8 +852,14 @@
purple_input_remove(js->gc->inpa);
- js->gsc = purple_ssl_connect_with_host_fd(js->gc->account, js->fd,
- jabber_login_callback_ssl, jabber_ssl_connect_failure, js->certificate_CN, js->gc);
+ js->gsc = purple_ssl_connect_with_host_fd_auth( + jabber_login_callback_ssl, + jabber_ssl_connect_failure, + purple_account_get_string(js->gc->account, "certificate_id", NULL), /* The fd is no longer our concern */
@@ -1050,9 +1056,15 @@
/* if they've got old-ssl mode going, we probably want to ignore SRV lookups */
if (g_str_equal("old_ssl", purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) {
if(purple_ssl_is_supported()) {
- js->gsc = purple_ssl_connect(account, js->certificate_CN,
+ js->gsc = purple_ssl_connect_with_ssl_cn_auth( purple_account_get_int(account, "port", 5223),
- jabber_login_callback_ssl, jabber_ssl_connect_failure, gc);
+ jabber_login_callback_ssl, + jabber_ssl_connect_failure, + purple_account_get_string(account, "certificate_id", NULL), purple_connection_error_reason(gc,
PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
--- a/libpurple/protocols/jabber/jabber.h Sat Aug 13 21:57:35 2011 +0000
+++ b/libpurple/protocols/jabber/jabber.h Sun Aug 14 18:20:04 2011 +0000
@@ -416,6 +416,7 @@
PurpleMediaSessionType type);
PurpleMediaCaps jabber_get_media_caps(PurpleAccount *account, const char *who);
gboolean jabber_can_receive_file(PurpleConnection *gc, const gchar *who);
+GList* jabber_get_account_options(); void jabber_plugin_init(PurplePlugin *plugin);
void jabber_plugin_uninit(PurplePlugin *plugin);
--- a/libpurple/protocols/jabber/libxmpp.c Sat Aug 13 21:57:35 2011 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c Sun Aug 14 18:20:04 2011 +0000
@@ -48,6 +48,7 @@
+#include "certificate.h" static PurplePlugin *my_protocol = NULL;
@@ -131,7 +132,8 @@
NULL, /* set_public_alias */
NULL, /* get_public_alias */
NULL, /* add_buddy_with_invite */
- NULL /* add_buddies_with_invite */
+ NULL, /* add_buddies_with_invite */ + jabber_get_account_options /* get_account_options */ static gboolean load_plugin(PurplePlugin *plugin)
@@ -249,22 +251,30 @@
-init_plugin(PurplePlugin *plugin)
+void destroy_account_options(GList *account_options) - PurpleAccountUserSplit *split;
- PurpleAccountOption *option;
- GList *encryption_values = NULL;
+ for (item = account_options; item != NULL; item = item->next) { + purple_account_option_destroy(item->data); + g_list_free(account_options); - /* Translators: 'domain' is used here in the context of Internet domains, e.g. pidgin.im */
- split = purple_account_user_split_new(_("Domain"), NULL, '@');
- purple_account_user_split_set_reverse(split, FALSE);
- prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
- split = purple_account_user_split_new(_("Resource"), "", '/');
- purple_account_user_split_set_reverse(split, FALSE);
- prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
+GList* jabber_get_account_options() + PurpleAccountOption *option; + GList *encryption_values = NULL; + PurpleCertificatePool *cert_pool = NULL; + GList *certificates = NULL; + /* Destroy the current option list so we can recreated it. + * We could just update the changed field, but this is simpler + destroy_account_options(prpl_info.protocol_options); #define ADD_VALUE(list, desc, v) { \
PurpleKeyValuePair *kvp = g_new0(PurpleKeyValuePair, 1); \
@@ -281,46 +291,90 @@
encryption_values = g_list_reverse(encryption_values);
option = purple_account_option_list_new(_("Connection security"), "connection_security", encryption_values);
- prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+ options = g_list_append(options, option); + ADD_VALUE(certificates, _("None"), "none"); /* hopefully we don't have a cert id of none */ + cert_pool = purple_certificate_find_pool("x509", "user"); + PurpleCertificate *cert = NULL; - option = purple_account_option_bool_new(
- _("Allow plaintext auth over unencrypted streams"),
+ id_list = purple_certificate_pool_get_idlist(cert_pool); + for (item = id_list; item != NULL; item = item->next) { + gchar* id = item->data; + cert = purple_certificate_pool_retrieve(cert_pool, id); + PurpleKeyValuePair *kvp = g_new0(PurpleKeyValuePair, 1); + kvp->key = g_strdup(id); + kvp->value = purple_certificate_get_subject_name(cert); + certificates = g_list_append(certificates, kvp); + purple_debug_info("xmpp/accountopt", "added cert %s to acct opt list\n", id); + purple_debug_warning("xmpp/accountopt", "Failed to find cert for id %s\n", id); + purple_certificate_pool_destroy_idlist(id_list); + option = purple_account_option_list_new(_("Login certificate"), "certificate_id", certificates); + options = g_list_append(options, option); + option = purple_account_option_bool_new(_("Allow plaintext auth over unencrypted streams"), "auth_plain_in_clear", FALSE);
- prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+ options = g_list_append(options, option); option = purple_account_option_int_new(_("Connect port"), "port", 5222);
- prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+ options = g_list_append(options, option); option = purple_account_option_string_new(_("Connect server"),
- prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+ options = g_list_append(options, option); option = purple_account_option_string_new(_("File transfer proxies"),
/* TODO: Is this an acceptable default?
* Also, keep this in sync as they add more servers */
JABBER_DEFAULT_FT_PROXIES);
- prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+ options = g_list_append(options, option); option = purple_account_option_string_new(_("BOSH URL"),
- prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+ options = g_list_append(options, option); /* this should probably be part of global smiley theme settings later on,
option = purple_account_option_bool_new(_("Show Custom Smileys"),
- prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+ options = g_list_append(options, option); + /* Required until we stop using the protocol_options field */ + prpl_info.protocol_options = options; +init_plugin(PurplePlugin *plugin) + PurpleAccountUserSplit *split; + /* Translators: 'domain' is used here in the context of Internet domains, e.g. pidgin.im */ + split = purple_account_user_split_new(_("Domain"), NULL, '@'); + purple_account_user_split_set_reverse(split, FALSE); + prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); + split = purple_account_user_split_new(_("Resource"), "", '/'); + purple_account_user_split_set_reverse(split, FALSE); + prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); + prpl_info.protocol_options = jabber_get_account_options(); --- a/libpurple/prpl.h Sat Aug 13 21:57:35 2011 +0000
+++ b/libpurple/prpl.h Sun Aug 14 18:20:04 2011 +0000
@@ -210,7 +210,6 @@
OPT_PROTO_INVITE_MESSAGE = 0x00000800
@@ -647,6 +646,18 @@
void (*add_buddy_with_invite)(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group, const char *message);
void (*add_buddies_with_invite)(PurpleConnection *pc, GList *buddies, GList *groups, const char *message);
+ * Get account options for this protocol. Allows dynamic generation + * of account options or values. Either this or protocol_options (above) should be + * used. It should update protocol_options field with the new list when called. + * Yes, we could call this updateAccountOptions, but it would be nice to move + * away from just a field to a field getter. + * @returns GList of PurpleAccountOption + GList *(*get_account_options)(); #define PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl, member) \
--- a/libpurple/sslconn.c Sat Aug 13 21:57:35 2011 +0000
+++ b/libpurple/sslconn.c Sun Aug 14 18:20:04 2011 +0000
@@ -32,6 +32,14 @@
+/** To carry around connection and account references + * when doing client-side auth callbacks + PurpleSslConnection *gsc; + PurpleAccount *account; static gboolean _ssl_initialized = FALSE;
static PurpleSslOps *_ssl_ops = NULL;
@@ -71,6 +79,20 @@
+ * Destroy an allocated PurpleSslConnection. This frees any allocated memory, + * but does not close any connection. Use purple_ssl_close() instead. +purple_ssl_destroy(PurpleSslConnection *gsc) + purple_certificate_destroy(gsc->certificate); + purple_privatekey_destroy(gsc->key); + g_free(gsc->certificate_id); purple_ssl_connect_cb(gpointer data, gint source, const gchar *error_message)
@@ -95,6 +117,128 @@
+ * 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(). +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. + 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); + gsc->connect_data = purple_proxy_connect(NULL, + 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); + * Called when the user cancels the request for the priavte key password. + * Common to all ssl connection types that use client authentication. +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); + * 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 + * @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. +purple_ssl_get_credentials(PurpleAccount *account, PurpleSslConnection *gsc, + 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"); + 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"); + 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", + data = g_new0(ssl_connect_cb_data, 1); + data->account = account; + purple_privatekey_pool_retrieve_request(key_pool, gsc->certificate_id, + G_CALLBACK(purple_ssl_connect_cancel_cb), purple_ssl_connect(PurpleAccount *account, const char *host, int port,
PurpleSslInputFunction func, PurpleSslErrorFunction error_func,
@@ -109,6 +253,18 @@
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); +purple_ssl_connect_with_ssl_cn_auth(PurpleAccount *account, const char *host, int port, + PurpleSslInputFunction func, + PurpleSslErrorFunction error_func, + const char* certificate_id, PurpleSslConnection *gsc;
g_return_val_if_fail(host != NULL, NULL);
@@ -130,21 +286,30 @@
gsc->connect_cb_data = data;
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); + gsc->connect_data = purple_proxy_connect(NULL, account, host, port, purple_ssl_connect_cb, gsc); - if (gsc->connect_data == NULL)
+ if (gsc->connect_data == NULL) + purple_ssl_destroy(gsc); - return (PurpleSslConnection *)gsc;
@@ -178,6 +343,12 @@
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."); purple_debug_warning("sslconn", "Unknown SSL error code %d\n", error);
return _("Unknown SSL error");
@@ -200,11 +371,50 @@
+ return purple_ssl_connect_with_host_fd_auth( + func, error_func, host, +purple_ssl_connect_with_host_fd_auth_cb(PurplePrivateKey *key, void *data) + 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. + 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); + ops = purple_ssl_get_ops(); +purple_ssl_connect_with_host_fd_auth(PurpleAccount *account, int fd, + PurpleSslInputFunction func, + PurpleSslErrorFunction error_func, + const char* certificate_id, PurpleSslConnection *gsc;
- 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);
@@ -219,15 +429,25 @@
gsc->error_cb = error_func;
- 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();
+ 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); + ops = purple_ssl_get_ops(); return (PurpleSslConnection *)gsc;
@@ -254,8 +474,7 @@
+ purple_ssl_destroy(gsc); @@ -295,6 +514,29 @@
return (ops->get_peer_certificates)(gsc);
+purple_ssl_get_client_certificate_id(PurpleSslConnection *gsc) + g_return_val_if_fail(gsc != NULL, NULL); + return gsc->certificate_id; +purple_ssl_set_client_auth(PurpleSslConnection *gsc, PurpleCertificate *crt, PurplePrivateKey *key) + 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); purple_ssl_set_ops(PurpleSslOps *ops)
--- a/libpurple/sslconn.h Sat Aug 13 21:57:35 2011 +0000
+++ b/libpurple/sslconn.h Sun Aug 14 18:20:04 2011 +0000
@@ -31,10 +31,30 @@
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 #define PURPLE_SSL_DEFAULT_PORT 443
@@ -46,6 +66,8 @@
typedef void (*PurpleSslErrorFunction)(PurpleSslConnection *, PurpleSslErrorType,
+/* 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 @@
/** Verifier to use in authenticating the peer */
PurpleCertificateVerifier *verifier;
+ /** Id of certificate to use for client-side authentication */ + /** Certificate to use for client authentication. Must match certificate_id */ + PurpleCertificate *certificate; + /** Private key to use for client authentication. Must match certificate_id */ + /** Callback function to get credentials. */ + PurpleSslGetCredentialsFunction get_credentials_cb; @@ -211,6 +245,37 @@
+ * 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 + * @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. +purple_ssl_connect_with_ssl_cn_auth(PurpleAccount *account, const char *host, int port, + PurpleSslInputFunction func, PurpleSslErrorFunction error_func, + const char* certificate_id, #if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_SSLCONN_C_)
* Makes a SSL connection using an already open file descriptor.
@@ -252,6 +317,34 @@
+ * 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. + * TODO: Do we really need to pass certificate_id here? Should we put it in account? +purple_ssl_connect_with_host_fd_auth(PurpleAccount *account, int fd, + PurpleSslInputFunction func, + PurpleSslErrorFunction error_func, + const char* certificate_id, * 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(PurpleSslConnection *gsc);
+ * Get the id the of certificate to use for client-side SSL/TLS authentication. + * @param gsc The SSL connection handle +const char* purple_ssl_get_client_certificate_id(PurpleSslConnection *gsc); /**************************************************************************/
--- a/libpurple/value.h Sat Aug 13 21:57:35 2011 +0000
+++ b/libpurple/value.h Sun Aug 14 18:20:04 2011 +0000
@@ -80,6 +80,7 @@
PURPLE_SUBTYPE_STORED_IMAGE,
PURPLE_SUBTYPE_CERTIFICATEPOOL,
+ PURPLE_SUBTYPE_PRIVATEKEYPOOL, --- a/pidgin/gtkaccount.c Sat Aug 13 21:57:35 2011 +0000
+++ b/pidgin/gtkaccount.c Sun Aug 14 18:20:04 2011 +0000
@@ -782,6 +782,7 @@
ProtocolOptEntry *opt_entry;
+ GList *protocol_options; if (dialog->protocol_frame != NULL) {
gtk_notebook_remove_page (GTK_NOTEBOOK(dialog->notebook), 1);
@@ -795,11 +796,15 @@
dialog->protocol_opt_entries = g_list_delete_link(dialog->protocol_opt_entries, dialog->protocol_opt_entries);
- if (dialog->prpl_info == NULL ||
- dialog->prpl_info->protocol_options == NULL)
+ account = dialog->account; + if (dialog->prpl_info == NULL) - account = dialog->account;
+ protocol_options = purple_account_get_options(dialog->prpl_info); + if (protocol_options == NULL) dialog->protocol_frame = vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
@@ -811,7 +816,7 @@
menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(dialog->protocol_menu));
item = gtk_menu_get_active(GTK_MENU(menu));
- for (l = dialog->prpl_info->protocol_options; l != NULL; l = l->next)
+ for (l = protocol_options; l != NULL; l = l->next) option = (PurpleAccountOption *)l->data;
--- a/pidgin/gtkcertmgr.c Sat Aug 13 21:57:35 2011 +0000
+++ b/pidgin/gtkcertmgr.c Sun Aug 14 18:20:04 2011 +0000
@@ -31,6 +31,7 @@
@@ -532,6 +533,748 @@
/*****************************************************************************
+ * X.509 user certificates management interface * + *****************************************************************************/ + GtkWidget *mgmt_widget; + GtkTreeSelection *listselect; + GtkWidget *importbutton; + GtkWidget *exportbutton; + GtkWidget *deletebutton; + PurpleCertificatePool *user_crts; + PurplePrivateKeyPool *user_keys; + PurplePkcs12Scheme *pkcs12; +user_mgmt_data *um_dat = NULL; + See http://developer.gnome.org/doc/API/2.0/gtk/TreeWidget.html */ +user_mgmt_destroy(GtkWidget *mgmt_widget, gpointer data) + purple_debug_info("certmgr", + "user self-destructs\n"); + purple_signals_disconnect_by_handle(um_dat); + purple_request_close_with_handle(um_dat); + g_free(um_dat); um_dat = NULL; +user_mgmt_repopulate_list(void) + GtkTreeView *listview = um_dat->listview; + PurpleCertificatePool *user_crts; + PurplePrivateKeyPool *user_keys; + GtkListStore *store = GTK_LIST_STORE( + gtk_tree_view_get_model(GTK_TREE_VIEW(listview))); + /* First, delete everything in the list */ + gtk_list_store_clear(store); + /* Locate the "user" pools */ + user_crts = purple_certificate_find_pool("x509", "user"); + user_keys = purple_privatekey_find_pool("x509", "user"); + g_return_if_fail(user_crts); + g_return_if_fail(user_keys); + /* Grab the loaded certificates */ + idlist = purple_certificate_pool_get_idlist(user_crts); + /* Populate the listview */ + for (l = idlist; l; l = l->next) { + if ( ! purple_privatekey_pool_contains(user_keys, l->data)) { + purple_debug_warning("gtkcertmgr/user_mgmt", + "User cert %s is missing it's private key.\n", + gtk_list_store_append(store, &iter); + gtk_list_store_set(GTK_LIST_STORE(store), &iter, + UM_NAME_COLUMN, l->data, + purple_certificate_pool_destroy_idlist(idlist); +user_mgmt_mod_cb(PurpleCertificatePool *pool, const gchar *id, gpointer data) + g_assert (pool == um_dat->user_crts); + user_mgmt_repopulate_list(); +user_mgmt_select_chg_cb(GtkTreeSelection *ignored, gpointer data) + GtkTreeSelection *select = um_dat->listselect; + /* See if things are selected */ + if (gtk_tree_selection_get_selected(select, &model, &iter)) { + /* Enable buttons if something is selected */ + gtk_widget_set_sensitive(GTK_WIDGET(um_dat->exportbutton), TRUE); + gtk_widget_set_sensitive(GTK_WIDGET(um_dat->infobutton), TRUE); + gtk_widget_set_sensitive(GTK_WIDGET(um_dat->deletebutton), TRUE); + /* Otherwise, disable them */ + gtk_widget_set_sensitive(GTK_WIDGET(um_dat->exportbutton), FALSE); + gtk_widget_set_sensitive(GTK_WIDGET(um_dat->infobutton), FALSE); + gtk_widget_set_sensitive(GTK_WIDGET(um_dat->deletebutton), FALSE); +/********************************************************** + * Importing a crt and key from PKCS12 file + PurpleCertificate *crt; +pkcs12_import_data_free(pkcs12_import_data *data) + purple_certificate_destroy(data->crt); + purple_privatekey_destroy(data->key); +pkcs12_import_key_password_ok_cb(gboolean result, pkcs12_import_data *data) + if (!purple_certificate_pool_store(um_dat->user_crts, data->name, data->crt)) { + purple_notify_error(um_dat, NULL, _("Failed to save imported certificate."), NULL); + /* TODO: deleted corresponding key stored in privatekey pool */ + pkcs12_import_data_free(data); +pkcs12_import_key_password_cancel_cb(pkcs12_import_data *data) + pkcs12_import_data_free(data); +pkcs12_import_name_ok_cb(pkcs12_import_data *data, char* name) + data->name = g_strdup(name); + /* TODO: What if we have the same name? */ + purple_privatekey_pool_store_request( + pkcs12_import_key_password_ok_cb, + pkcs12_import_key_password_cancel_cb, +pkcs12_import_name_cancel_cb(pkcs12_import_data *data) + pkcs12_import_data_free(data); +user_mgmt_import_pkcs12(const gchar* filename, const gchar* password) + PurpleCertificateScheme *x509_crts; + PurplePrivateKeyScheme *x509_keys; + PurpleCertificate *crt = NULL; + PurplePrivateKey *key = NULL; + pkcs12_import_data *data; + purple_debug_info("gtkcertmgr/user_mgmt", "Importing pkcs12 file %s with password XXXXXX\n", filename); + /* Load the scheme of our user pool (ought to be x509) */ + x509_crts = purple_certificate_pool_get_scheme(um_dat->user_crts); + g_return_if_fail(x509_crts); + x509_keys = purple_privatekey_pool_get_scheme(um_dat->user_keys); + g_return_if_fail(x509_keys); + /* Now load the certificate/keys from disk */ + result = purple_pkcs12_import(um_dat->pkcs12, filename, password, &crt, &key); + /* Get name to add to pool as */ + /* Make a guess about what the hostname should be */ + default_name = purple_certificate_get_subject_name(crt); + /* TODO: Find a way to make sure that crt & key gets destroyed + if the window gets closed unusually, such as by handle + /* TODO: Display some more information on the certificate? */ + data = g_new0(pkcs12_import_data, 1); + data->name = default_name; + /* TODO: Enable custom cert name dialog */ + purple_request_input(um_dat, + _("Type the name for the imported certificate and key."), + FALSE, /* Not multiline */ + FALSE, /* Not masked? */ + G_CALLBACK(pkcs12_import_name_ok_cb), + G_CALLBACK(pkcs12_import_name_cancel_cb), + NULL, NULL, NULL, /* No account/who/conv*/ + /* TODO: Perhaps find a way to be specific about what just + purple_certificate_destroy(crt); + purple_privatekey_destroy(key); + secondary = g_strdup_printf(_("File %s could not be imported.\nMake sure that the file is readable, in PKCS12 format and you used the correct password.\n"), filename); + purple_notify_message(NULL, PURPLE_NOTIFY_MSG_ERROR, + _("Certificate Import Error"), + _("PKCS12 certificate import failed"), +pkcs12_import_password_ok_cb(char *filename, PurpleRequestFields *fields) + entry = purple_request_fields_get_string(fields, "password"); +/* remember = purple_request_fields_get_bool(fields, "remember");*/ + purple_notify_error(um_dat, NULL, _("A password is required to access a PKCS12 file."), NULL); + purple_account_set_remember_password(account, TRUE); + purple_debug_info("gtkcertmgr/user_mgmt/import_pkcs12", "Got password for pkcs file %s\n", filename); + user_mgmt_import_pkcs12(filename, entry); +pkcs12_import_password_cancel_cb( char* filename) +user_mgmt_import_ok_cb(gpointer data, char *filename) + purple_debug_info("gtkcertmgr/user_mgmt/import_pkcs12", "Importing pkcs12 file %s\n", filename); + filename = g_strdup(filename); /* othewise it goes away after this func returns */ + purple_pkcs12_request_password( + G_CALLBACK(pkcs12_import_password_ok_cb), + G_CALLBACK(pkcs12_import_password_cancel_cb), +user_mgmt_import_cb(GtkWidget *button, gpointer data) + /* TODO: need to tell the user that we want a .PEM file! */ + purple_request_file(um_dat, + _("Select a PKCS12 certificate file"), + FALSE, /* Not a save dialog */ + G_CALLBACK(user_mgmt_import_ok_cb), + NULL, /* Do nothing if cancelled */ + NULL, NULL, NULL, NULL );/* No account,conv,etc. */ +/********************************************************** + * Exporting crt and key to PKCS12 file + PurpleCertificate *crt; +pkcs12_export_data_free(pkcs12_export_data *data) + g_return_if_fail(data); + purple_certificate_destroy(data->crt); + purple_privatekey_destroy(data->key); + g_free(data->filename); +pkcs12_export_password_ok_cb(pkcs12_export_data *data, PurpleRequestFields *fields) + purple_debug_info("certmgr/user_mgmt/pkcs12_export", "pkcs12 password ok cb\n"); + entry = purple_request_fields_get_string(fields, "password"); +/* remember = purple_request_fields_get_bool(fields, "remember");*/ + purple_notify_error(um_dat, NULL, _("A password is required to protect the PKCS12 file."), NULL); + pkcs12_export_data_free(data); + purple_account_set_remember_password(account, TRUE); + purple_debug_info("gtkcertmgr/user_mgmt/export_pkcs12", "Got password for pkcs file %s\n", data->filename); + /* Finally create the pkcs12 file */ + if (!purple_pkcs12_export(um_dat->pkcs12, data->filename, entry, data->crt, data->key)) { + /* TODO: Perhaps find a way to be specific about what just + secondary = g_strdup_printf( + _("Export to file %s failed.\nCheck that you have write " + "permission to the target path\n"), data->filename); + purple_notify_message(NULL, PURPLE_NOTIFY_MSG_ERROR, + _("PKCS12 Export Error"), + _("PKCS12 certificate & key export failed"), + pkcs12_export_data_free(data); +pkcs12_export_password_cancel_cb(pkcs12_export_data* data) + pkcs12_export_data_free(data); +user_mgmt_export_ok_cb(pkcs12_export_data *data, const char *filename) + purple_debug_info("certmgr/user_mgmt/pkcs12_export", "file ok cb\n"); + data->filename = g_strdup(filename); /* it goes away otherwise */ + purple_pkcs12_request_password( + G_CALLBACK(pkcs12_export_password_ok_cb), + G_CALLBACK(pkcs12_export_password_cancel_cb), +user_mgmt_export_cancel_cb(pkcs12_export_data *data, const char *filename) + /* Pressing cancel just frees the duplicated certificate & key*/ + pkcs12_export_data_free(data); +pkcs12_get_key_password_ok_cb(PurplePrivateKey *key, pkcs12_export_data *data) + purple_debug_info("certmgr/user_mgmt/pkcs12_export", "key password ok cb\n"); + purple_debug_error("gtkcertmgr/user_mgmt", + "Id %s was not in the user key pool or bad password\n", + secondary = g_strdup_printf( + _("The private key named \"%s\" was not found or the key's password was incorrect."), + purple_notify_message(NULL, PURPLE_NOTIFY_MSG_ERROR, + _("PKCS12 Export Error"), + _("PKCS12 certificate & key export failed"), + pkcs12_export_data_free(data); + purple_request_file(um_dat, + _("PKCS12 Certificate & Key Export"), + TRUE, /* Is a save dialog */ + G_CALLBACK(user_mgmt_export_ok_cb), + G_CALLBACK(user_mgmt_export_cancel_cb), + NULL, NULL, NULL, /* No account,conv,etc. */ + data); /* Pass the certificate & key on to the callback */ +pkcs12_get_key_password_cancel_cb(pkcs12_export_data *data) + pkcs12_export_data_free(data); +user_mgmt_export_cb(GtkWidget *button, void* stuff) + PurpleCertificate *crt = NULL; + GtkTreeSelection *select = um_dat->listselect; + GtkTreeModel *model = NULL; + pkcs12_export_data *data = NULL; + purple_debug_info("gtkcertmgr/user_mgmt", "1111111111111111\n"); + /* See if things are selected */ + if (!gtk_tree_selection_get_selected(select, &model, &iter)) { + purple_debug_warning("gtkcertmgr/user_mgmt", + "Export clicked with no selection?\n"); + /* Retrieve the selected name */ + gtk_tree_model_get(model, &iter, UM_NAME_COLUMN, &id, -1); + /* Extract the certificate & keys from the pools now to make sure it doesn't + get deleted out from under us */ + crt = purple_certificate_pool_retrieve(um_dat->user_crts, id); + purple_debug_error("gtkcertmgr/user_mgmt", + "Id %s was not in the user cert pool?!\n", + /* stuff we will need in our callbacks */ + data = g_new0(pkcs12_export_data, 1); + purple_privatekey_pool_retrieve_request( + G_CALLBACK(pkcs12_get_key_password_ok_cb), + G_CALLBACK(pkcs12_get_key_password_cancel_cb), +/********************************************************** + * Display certificate and key info +user_mgmt_info_cb(GtkWidget *button, gpointer data) + GtkTreeSelection *select = um_dat->listselect; + PurpleCertificate *crt; + /* See if things are selected */ + if (!gtk_tree_selection_get_selected(select, &model, &iter)) { + purple_debug_warning("gtkcertmgr/user_mgmt", + "Info clicked with no selection?\n"); + /* Retrieve the selected name */ + gtk_tree_model_get(model, &iter, UM_NAME_COLUMN, &id, -1); + /* Now retrieve the certificate */ + crt = purple_certificate_pool_retrieve(um_dat->user_crts, id); + /* Fire the notification */ + purple_certificate_display_x509(crt); + purple_certificate_destroy(crt); +/*********************************************************** + * Delete a certificate and key +user_mgmt_delete_confirm_cb(gchar *id, gint choice) + /* Yes, delete was confirmed */ + /* Now delete the thing */ + if (!purple_certificate_pool_delete(um_dat->user_crts, id)) { + purple_debug_warning("gtkcertmgr/user_mgmt", + "Deletion failed of certificate for id %s\n", + if (!purple_privatekey_pool_delete(um_dat->user_keys, id)) { + purple_debug_warning("gtkcertmgr/user_mgrm", + "Deletion failed of private key for id %s\n", +user_mgmt_delete_cb(GtkWidget *button, gpointer data) + GtkTreeSelection *select = um_dat->listselect; + /* See if things are selected */ + if (gtk_tree_selection_get_selected(select, &model, &iter)) { + /* Retrieve the selected hostname */ + gtk_tree_model_get(model, &iter, UM_NAME_COLUMN, &id, -1); + /* Prompt to confirm deletion */ + primary = g_strdup_printf( + _("Really delete certificate for %s?"), id ); + purple_request_yes_no(um_dat, _("Confirm certificate delete"), + primary, NULL, /* Can this be NULL? */ + 0, /* "yes" is the default action */ + id, /* id ownership passed to callback */ + user_mgmt_delete_confirm_cb, + user_mgmt_delete_confirm_cb ); + purple_debug_warning("gtkcertmgr/user_mgmt", + "Delete clicked with no selection?\n"); +/********************************************************** + * Setup the user certificate & key management tab + /* This block of variables will end up in um_dat */ + GtkTreeSelection *select; + GtkWidget *importbutton; + GtkWidget *exportbutton; + GtkWidget *deletebutton; + /** Element to return to the Certmgr window to put in the Notebook */ + GtkWidget *mgmt_widget; + /* Create a struct to store context information about this window */ + um_dat = g_new0(user_mgmt_data, 1); + um_dat->mgmt_widget = mgmt_widget = + gtk_hbox_new(FALSE, /* Non-homogeneous */ + gtk_container_set_border_width(GTK_CONTAINER(mgmt_widget), + gtk_widget_show(mgmt_widget); + /* Ensure that everything gets cleaned up when the dialog box + g_signal_connect(G_OBJECT(mgmt_widget), "destroy", + G_CALLBACK(user_mgmt_destroy), NULL); + sw = gtk_scrolled_window_new(NULL,NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN); + gtk_box_pack_start(GTK_BOX(mgmt_widget), GTK_WIDGET(sw), + TRUE, TRUE, /* Take up lots of space */ + gtk_widget_show(GTK_WIDGET(sw)); + store = gtk_list_store_new(UM_N_COLUMNS, G_TYPE_STRING); + um_dat->listview = listview = + GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(store))); + g_object_unref(G_OBJECT(store)); + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + /* Set up the display columns */ + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( + "text", UM_NAME_COLUMN, + gtk_tree_view_append_column(GTK_TREE_VIEW(listview), column); + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), + UM_NAME_COLUMN, GTK_SORT_ASCENDING); + /* Get the treeview selector into the struct */ + um_dat->listselect = select = + gtk_tree_view_get_selection(GTK_TREE_VIEW(listview)); + /* Force the selection mode */ + gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE); + /* Use a callback to enable/disable the buttons based on whether + something is selected */ + g_signal_connect(G_OBJECT(select), "changed", + G_CALLBACK(user_mgmt_select_chg_cb), NULL); + gtk_container_add(GTK_CONTAINER(sw), GTK_WIDGET(listview)); + gtk_widget_show(GTK_WIDGET(listview)); + /* Fill the list for the first time */ + user_mgmt_repopulate_list(); + /* Right-hand side controls box */ + bbox = gtk_vbutton_box_new(); + gtk_box_pack_end(GTK_BOX(mgmt_widget), bbox, + FALSE, FALSE, /* Do not take up space */ + gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); + gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_START); + /* TODO: This is the wrong stock button */ + um_dat->importbutton = importbutton = + gtk_button_new_from_stock(GTK_STOCK_ADD); + gtk_box_pack_start(GTK_BOX(bbox), importbutton, FALSE, FALSE, 0); + gtk_widget_show(importbutton); + g_signal_connect(G_OBJECT(importbutton), "clicked", + G_CALLBACK(user_mgmt_import_cb), NULL); + /* TODO: This is the wrong stock button */ + um_dat->exportbutton = exportbutton = + gtk_button_new_from_stock(GTK_STOCK_SAVE); + gtk_box_pack_start(GTK_BOX(bbox), exportbutton, FALSE, FALSE, 0); + gtk_widget_show(exportbutton); + g_signal_connect(G_OBJECT(exportbutton), "clicked", + G_CALLBACK(user_mgmt_export_cb), NULL); + um_dat->infobutton = infobutton = + gtk_button_new_from_stock(PIDGIN_STOCK_INFO); + gtk_box_pack_start(GTK_BOX(bbox), infobutton, FALSE, FALSE, 0); + gtk_widget_show(infobutton); + g_signal_connect(G_OBJECT(infobutton), "clicked", + G_CALLBACK(user_mgmt_info_cb), NULL); + um_dat->deletebutton = deletebutton = + gtk_button_new_from_stock(GTK_STOCK_DELETE); + gtk_box_pack_start(GTK_BOX(bbox), deletebutton, FALSE, FALSE, 0); + gtk_widget_show(deletebutton); + g_signal_connect(G_OBJECT(deletebutton), "clicked", + G_CALLBACK(user_mgmt_delete_cb), NULL); + /* Call the "selection changed" callback, which will probably disable + all the buttons since nothing is selected yet */ + user_mgmt_select_chg_cb(select, NULL); + /* Bind us to the user pool */ + um_dat->user_crts = purple_certificate_find_pool("x509", "user"); + um_dat->user_keys = purple_privatekey_find_pool("x509", "user"); + um_dat->pkcs12 = purple_pkcs12_find_scheme("pkcs12"); + /**** libpurple signals ****/ + /* Respond to certificate add/remove by just reloading everything */ + purple_signal_connect(um_dat->user_crts, "certificate-stored", + um_dat, PURPLE_CALLBACK(user_mgmt_mod_cb), + purple_signal_connect(um_dat->user_crts, "certificate-deleted", + um_dat, PURPLE_CALLBACK(user_mgmt_mod_cb), +const PidginCertificateManager user_mgmt = { + user_mgmt_build, /* Widget creation function */ + N_("Your Certificates") +/***************************************************************************** * GTK+ main certificate manager *
*****************************************************************************/
@@ -631,6 +1374,9 @@
gtk_notebook_append_page(GTK_NOTEBOOK (dlg->notebook),
(tls_peers_mgmt.build)(),
gtk_label_new(_(tls_peers_mgmt.label)) );
+ gtk_notebook_append_page(GTK_NOTEBOOK (dlg->notebook), + gtk_label_new(_(user_mgmt.label)) );