pidgin/ljfisher-ssl-client-auth

72326cbd2ad4
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 @@
network.c \
ntlm.c \
notify.c \
+ pkcs12.c \
plugin.c \
pluginpref.c \
pounce.c \
prefs.c \
privacy.c \
+ privatekey.c \
proxy.c \
prpl.c \
request.c \
@@ -129,11 +131,13 @@
network.h \
notify.h \
ntlm.h \
+ pkcs12.h \
plugin.h \
pluginpref.h \
pounce.h \
prefs.h \
privacy.h \
+ privatekey.h \
proxy.h \
prpl.h \
request.h \
--- 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 0
+ /* XXX ljf remove */
+ if ((tmp = purple_account_get_certificate_id(account)) != NULL)
+ {
+ child = xmlnode_new_child(node, "certificateid");
+ xmlnode_insert_data(child, tmp, -1);
+ }
+#endif
if ((tmp = purple_account_get_alias(account)) != NULL)
{
child = xmlnode_new_child(node, "alias");
@@ -887,6 +895,17 @@
g_free(data);
}
+#if 0
+ /* XXX ljf remove */
+ /* 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);
+ g_free(data);
+ }
+#endif
/* Read the alias */
child = xmlnode_get_child(node, "alias");
if ((child != NULL) && ((data = xmlnode_get_data(child)) != NULL))
@@ -985,6 +1004,7 @@
xmlnode_free(node);
_purple_buddy_icons_account_loaded_cb();
+
}
@@ -1652,6 +1672,24 @@
schedule_accounts_save();
}
+#if 0
+/* XXX ljf */
+void
+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")) {
+ id = NULL;
+ }
+
+ g_free(account->certificate_id);
+ account->certificate_id = g_strdup(id);
+
+ schedule_accounts_save();
+}
+#endif
void
purple_account_set_alias(PurpleAccount *account, const char *alias)
{
@@ -2163,7 +2201,15 @@
return account->password;
}
-
+#if 0 /* XXX ljf */
+const char *
+purple_account_get_certificate_id(const PurpleAccount *account)
+{
+ g_return_val_if_fail(account != NULL, NULL);
+
+ return account->certificate_id;
+}
+#endif
const char *
purple_account_get_alias(const PurpleAccount *account)
{
@@ -2526,6 +2572,18 @@
}
}
+GList*
+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();
+ else
+ return NULL;
+}
+
void
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
+ * @since 3.0
+ */
+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 @@
NULL
};
+/***** User's certificates and keys *****/
+/* This code is just a duplication of x509_tls_peers. We should
+ share this code.
+*/
+static PurpleCertificatePool x509_user;
+
+static gboolean
+x509_user_init(void)
+{
+ gchar *poolpath;
+ int ret;
+
+ /* 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 */
+
+ if (ret != 0)
+ purple_debug_info("certificate/user",
+ "Could not create %s. Certificates will not be cached.\n",
+ poolpath);
+
+ g_free(poolpath);
+
+ return TRUE;
+}
+
+static gboolean
+x509_user_cert_in_pool(const gchar *id)
+{
+ gchar *keypath;
+ gboolean ret = FALSE;
+
+ 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);
+
+ g_free(keypath);
+ return ret;
+}
+
+static PurpleCertificate *
+x509_user_get_cert(const gchar *id)
+{
+ PurpleCertificateScheme *x509;
+ PurpleCertificate *crt;
+ gchar *keypath;
+
+ g_return_val_if_fail(id, NULL);
+
+ /* Is it in the pool? */
+ if ( !x509_user_cert_in_pool(id) ) {
+ return NULL;
+ }
+
+ /* 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);
+
+ g_free(keypath);
+
+ return crt;
+}
+
+static gboolean
+x509_user_put_cert(const gchar *id, PurpleCertificate *crt)
+{
+ gboolean ret = FALSE;
+ gchar *keypath;
+
+ 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);
+
+ g_free(keypath);
+ return ret;
+}
+
+static gboolean
+x509_user_delete_cert(const gchar *id)
+{
+ gboolean ret = FALSE;
+ gchar *keypath;
+
+ 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",
+ id);
+ return FALSE;
+ }
+
+ /* 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",
+ keypath);
+ ret = FALSE;
+ } else {
+ ret = TRUE;
+ }
+
+ g_free(keypath);
+ return ret;
+}
+
+static GList *
+x509_user_get_idlist(void)
+{
+ GList *idlist = NULL;
+ GDir *dir;
+ const gchar *entry;
+ gchar *poolpath;
+
+ /* Get a handle on the pool directory */
+ poolpath = purple_certificate_pool_mkpath(&x509_user, NULL);
+ dir = g_dir_open(poolpath,
+ 0, /* No flags */
+ NULL); /* Not interested in what the error is */
+ g_free(poolpath);
+
+ 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
+ string) */
+ idlist = g_list_prepend(idlist, g_strdup(unescaped));
+ }
+
+ /* Release the directory */
+ g_dir_close(dir);
+
+ return idlist;
+}
+
+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 */
+
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
/***** 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;
break;
+ 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;
+ break;
default:
g_assert_not_reached ();
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 "internal.h"
#include "cipher.h"
#include "certificate.h"
+#include "privatekey.h"
+#include "pkcs12.h"
#include "cmds.h"
#include "connection.h"
#include "conversation.h"
@@ -161,6 +163,8 @@
purple_savedstatuses_init();
purple_notify_init();
purple_certificate_init();
+ purple_privatekey_init();
+ purple_pkcs12_init();
purple_conversations_init();
purple_blist_init();
purple_log_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
+ * @ingroup core
+ */
+
+/*
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include "internal.h"
+#include "pkcs12.h"
+#include "dbus-maybe.h"
+#include "debug.h"
+#include "request.h"
+#include "signals.h"
+#include "util.h"
+
+/** List holding pointers to all registered private key schemes */
+static GList *pkcs12_schemes = NULL;
+
+gboolean
+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);
+}
+
+gboolean
+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);
+}
+
+gboolean
+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);
+
+ return FALSE;
+}
+
+void
+purple_pkcs12_request_password(void* handle, const char* filename, GCallback ok_cb,
+ GCallback cancel_cb, void *user_data)
+{
+ gchar *primary;
+ 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 */
+ primary, /* primary */
+ NULL, /* secondary */
+ fields, /* fields */
+ _("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 */
+}
+
+/****************************************************************************/
+/* Subsystem */
+/****************************************************************************/
+void
+purple_pkcs12_init(void)
+{
+}
+
+void
+purple_pkcs12_uninit(void)
+{
+}
+
+gpointer
+purple_pkcs12_get_handle(void)
+{
+ static gint handle;
+ return &handle;
+}
+
+PurplePkcs12Scheme *
+purple_pkcs12_find_scheme(const gchar *name)
+{
+ PurplePkcs12Scheme *scheme = NULL;
+ GList *l;
+
+ g_return_val_if_fail(name, NULL);
+
+ /* Traverse the list of registered schemes and locate the
+ one whose name matches */
+ for(l = pkcs12_schemes; l; l = l->next) {
+ scheme = (PurplePkcs12Scheme *)(l->data);
+
+ /* Name matches? that's our man */
+ if(!g_ascii_strcasecmp(scheme->name, name))
+ return scheme;
+ }
+
+ purple_debug_warning("pkcs12",
+ "Pkcs12Scheme %s requested but not found.\n",
+ name);
+
+ /* TODO: Signalling and such? */
+
+ return NULL;
+}
+
+GList *
+purple_pkcs12_get_schemes(void)
+{
+ return pkcs12_schemes;
+}
+
+gboolean
+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) {
+ return FALSE;
+ }
+
+ /* 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",
+ scheme->name);
+
+ return TRUE;
+}
+
+gboolean
+purple_pkcs12_unregister_scheme(PurplePkcs12Scheme *scheme)
+{
+ if (NULL == scheme) {
+ purple_debug_warning("pkcs12",
+ "Attempting to unregister NULL scheme\n");
+ return FALSE;
+ }
+
+ pkcs12_schemes = g_list_remove(pkcs12_schemes, scheme);
+
+ purple_debug_info("pkcs12",
+ "Pkcs12Scheme %s unregistered\n",
+ scheme->name);
+
+ return TRUE;
+}
--- /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
+ * @ingroup core
+ * @see
+ * @since 2.2.0
+ */
+
+/*
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *a
+ * 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 <time.h>
+
+#include <glib.h>
+
+#include <pkcs12.h>
+#include <certificate.h>
+#include <privatekey.h>
+
+#ifdef __cplusplus
+extern "C" {
+#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
+ * ex: "pkcs12"
+ * This must be globally unique - you may not register more than one
+ * Pkcs12Scheme of the same name at a time.
+ */
+ gchar * name;
+
+ /** User-friendly name for this type
+ * ex: N_("X.509 PKCS12")
+ * When this is displayed anywhere, it should be i18ned
+ * ex: _(scheme->fullname)
+ */
+ gchar * 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
+ */
+gboolean
+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
+ */
+gboolean
+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
+ */
+gboolean
+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.
+ */
+void
+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
+ */
+void
+purple_pkcs12_init(void);
+
+/**
+ * Un-initialize the pkcs12 system
+ */
+void
+purple_pkcs12_uninit(void);
+
+/**
+ * Get the pkcs12 subsystem handle for signalling purposes
+ */
+gpointer
+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.
+ */
+PurplePkcs12Scheme *
+purple_pkcs12_find_scheme(const gchar *name);
+
+/**
+ * Get all registered Pkcs12Schemes
+ *
+ * @return GList pointing to all registered Pkcs12Schemes . This value
+ * is owned by libpurple
+ */
+GList *
+purple_pkcs12_get_schemes(void);
+
+/** Register a Pkcs12Scheme with libpurple
+ *
+ * No two schemes can be registered with the same name; this function enforces
+ * that.
+ *
+ * @param scheme Pointer to the scheme to register.
+ * @return TRUE if the scheme was successfully added, otherwise FALSE
+ */
+gboolean
+purple_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
+ */
+gboolean
+purple_pkcs12_unregister_scheme(PurplePkcs12Scheme *scheme);
+
+/*@}*/
+
+
+#ifdef __cplusplus
+}
+#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 @@
#include "internal.h"
#include "debug.h"
#include "certificate.h"
+#include "privatekey.h"
+#include "pkcs12.h"
#include "plugin.h"
#include "sslconn.h"
#include "version.h"
@@ -31,6 +33,7 @@
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
+#include <gnutls/pkcs12.h>
typedef struct
{
@@ -39,10 +42,21 @@
guint handshake_timer;
} PurpleSslGnutlsData;
+static gboolean
+ssl_gnutls_set_client_auth(gnutls_certificate_client_credentials cred,
+ PurpleCertificate * pcrt,
+ PurplePrivateKey * pkey);
+
#define PURPLE_SSL_GNUTLS_DATA(gsc) ((PurpleSslGnutlsData *)gsc->private_data)
static gnutls_certificate_client_credentials xcred = NULL;
+/* The GNUTLS get client credentials callback does not support user supplied
+ data so we have to maintain that outselves. Annoying.
+ The key is a gnutls_session_t pointer and the value is a PurpleSslConnection pointer.
+*/
+GHashTable *sslConnTable;
+
#ifdef HAVE_GNUTLS_PRIORITY_FUNCS
/* Priority strings. The default one is, well, the default (and is always
* set). The hash table is of the form hostname => priority (both
@@ -178,17 +192,22 @@
/* TODO: I can likely remove this */
gnutls_certificate_set_x509_trust_file(xcred, "ca.pem",
GNUTLS_X509_FMT_PEM);
+// gnutls_certificate_set_x509_simple_pkcs12_file(xcred, "test.p12", GNUTLS_X509_FMT_DER, "abcd");
}
static gboolean
ssl_gnutls_init(void)
{
+ /* Use direct hashing since the key is a pointer. */
+ sslConnTable = g_hash_table_new(NULL, NULL);
+
return TRUE;
}
static void
ssl_gnutls_uninit(void)
{
+ g_hash_table_destroy(sslConnTable);
gnutls_global_deinit();
gnutls_certificate_free_credentials(xcred);
@@ -392,6 +411,10 @@
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,
cert_type_priority);
+ purple_debug_info("gnutls", "client cert id: %s cert:%p key:%p\n",
+ gsc->certificate_id, gsc->certificate, gsc->key);
+
+ if (NULL != gsc->certificate_id
+ && NULL != gsc->certificate
+ && NULL != gsc->key) {
+ purple_debug_info("gnutls/handshake",
+ "Authenticating with certificate/key %s\n",
+ gsc->certificate_id);
+ ssl_gnutls_set_client_auth(xcred, gsc->certificate, gsc->key);
+ }
+
gnutls_credentials_set(gnutls_data->session, GNUTLS_CRD_CERTIFICATE,
xcred);
@@ -453,6 +488,9 @@
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 @@
return peer_certs;
}
+
/************************************************************************/
/* 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 @@
return success;
}
+
+
/* 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 */
+typedef struct {
+ gint refcount;
+ gnutls_x509_privkey_t key;
+} x509_keydata_t;
+
+/** Helper functions for reference counting */
+static x509_keydata_t *
+x509_keydata_addref(x509_keydata_t *kd)
+{
+ (kd->refcount)++;
+ return kd;
+}
+
+static void
+x509_keydata_delref(x509_keydata_t *kd)
+{
+ (kd->refcount)--;
+
+ if (kd->refcount < 0)
+ g_critical("Refcount of x509_keydata_t is %d, which is less "
+ "than zero!\n", kd->refcount);
+
+ /* If the refcount reaches zero, kill the structure */
+ if (kd->refcount <= 0) {
+ /* Kill the internal data */
+ if (kd->key)
+ gnutls_x509_privkey_deinit( kd->key );
+ /* And kill the struct */
+ g_free( kd );
+ }
+}
+
+/** Helper macro to retrieve the GnuTLS crt_t from a PurplePrivateKey */
+#define X509_GET_GNUTLS_KEYDATA(pkey) ( ((x509_keydata_t *) (pkey->data))->key)
+
+static gboolean
+read_pkcs8_file(const gchar* filename, gnutls_datum_t *dt, gnutls_x509_crt_fmt_t * fmt)
+{
+ gchar *buf = NULL; /* Used to load the raw file data */
+ gsize buf_sz; /* Size of the above */
+
+ purple_debug_info("gnutls/x509key",
+ "Attempting to load PKCS8 file from %s\n",
+ filename);
+
+ /* Next, we'll simply yank the entire contents of the file
+ into memory */
+ /* TODO: Should I worry about very large files here? */
+ g_return_val_if_fail(
+ g_file_get_contents(filename,
+ &buf,
+ &buf_sz,
+ NULL /* No error checking for now */
+ ),
+ FALSE);
+
+ *fmt = GNUTLS_X509_FMT_DER;
+ #define PEM_PKCS8_HDR "-----BEGIN ENCRYPTED PRIVATE KEY-----"
+ if (0 == strncmp(buf, PEM_PKCS8_HDR, sizeof(PEM_PKCS8_HDR)-1))
+ *fmt = GNUTLS_X509_FMT_PEM;
+
+ dt->data = (unsigned char*) buf;
+ dt->size = buf_sz;
+
+ return TRUE;
+}
+
+static PurplePrivateKey*
+x509_import_key(const gchar * filename, const gchar * password)
+{
+ /* Internal key data structure */
+ x509_keydata_t *keydat;
+ /* New key to return */
+ PurplePrivateKey * key;
+ gnutls_datum_t dt;
+ gnutls_x509_crt_fmt_t fmt;
+ int rv;
+
+ /* Allocate and prepare the internal key data */
+ keydat = g_new0(x509_keydata_t, 1);
+ if (GNUTLS_E_SUCCESS != gnutls_x509_privkey_init(&keydat->key)) {
+ g_free(keydat);
+ return NULL;
+ }
+ keydat->refcount = 0;
+
+ key = g_new0(PurplePrivateKey, 1);
+ if (NULL == key) {
+ gnutls_x509_privkey_deinit(keydat->key);
+ g_free(keydat);
+ return NULL;
+ }
+
+ key->scheme = &x509_key_gnutls;
+ key->data = keydat;
+
+ if (read_pkcs8_file(filename, &dt, &fmt)) {
+ rv = gnutls_x509_privkey_import_pkcs8(keydat->key, &dt, fmt, password, 0);
+ if (GNUTLS_E_SUCCESS != rv) {
+ purple_debug_error("gnutls/x509key",
+ "Error importing key from %s: %s\n",
+ filename, gnutls_strerror(rv));
+ gnutls_x509_privkey_deinit(keydat->key);
+ g_free(keydat);
+ return NULL;
+ }
+ }
+
+ return key;
+}
+
+static gboolean
+x509_export_key(const gchar *filename, PurplePrivateKey *key, const gchar* password)
+{
+ gnutls_x509_privkey_t key_dat; /* GnuTLS key struct */
+ int ret;
+ gchar * out_buf; /* Data to output */
+ size_t out_size; /* Output size */
+ gboolean success = FALSE;
+ unsigned int flags;
+
+ /* Paranoia paranoia paranoia! */
+ g_return_val_if_fail(filename, FALSE);
+ g_return_val_if_fail(key, FALSE);
+ g_return_val_if_fail(key->scheme == &x509_key_gnutls, FALSE);
+ g_return_val_if_fail(key->data, FALSE);
+
+ key_dat = X509_GET_GNUTLS_KEYDATA(key);
+
+ /* TODO: Check version of gnutls and use AES if possible */
+ flags = GNUTLS_PKCS_USE_PBES2_3DES;
+
+ /* Obtain the output size required */
+ out_size = 0;
+ ret = gnutls_x509_privkey_export_pkcs8(key_dat, GNUTLS_X509_FMT_PEM,
+ password,
+ flags,
+ NULL, /* Provide no buffer yet */
+ &out_size /* Put size here */);
+ purple_debug_error("gnutls/x509key", "querying for size and export pkcs8 returned (%d) %s with size %d\n",
+ ret, gnutls_strerror(ret), out_size);
+ g_return_val_if_fail(ret == GNUTLS_E_SHORT_MEMORY_BUFFER, FALSE);
+
+ /* Now allocate a buffer and *really* export it */
+
+ /* TODO: Again we seem to randomly get a "just not quite big enough" size above. */
+ out_size += 100;
+
+ out_buf = g_new0(gchar, out_size);
+ ret = gnutls_x509_privkey_export_pkcs8(key_dat, GNUTLS_X509_FMT_PEM,
+ password,
+ flags,
+ out_buf, /* Export to our new buffer */
+ &out_size /* Put size here */);
+
+ if (GNUTLS_E_SUCCESS != ret) {
+ purple_debug_error("gnutls/x509key",
+ "Failed to export key to buffer:%s\n",
+ gnutls_strerror(ret));
+ g_free(out_buf);
+ return FALSE;
+ }
+
+ /* Write it out to an actual file */
+ success = purple_util_write_data_to_file_absolute(filename,
+ out_buf, out_size);
+
+ g_free(out_buf);
+ return success;
+}
+
+static PurplePrivateKey *
+x509_copy_key(PurplePrivateKey *key)
+{
+ x509_keydata_t *keydat;
+ PurplePrivateKey *newkey;
+
+ g_return_val_if_fail(key, NULL);
+ g_return_val_if_fail(key->scheme == &x509_key_gnutls, NULL);
+
+ keydat = (x509_keydata_t *) key->data;
+
+ newkey = g_new0(PurplePrivateKey, 1);
+ newkey->scheme = &x509_key_gnutls;
+ newkey->data = x509_keydata_addref(keydat);
+
+ return newkey;
+}
+
+static void
+x509_destroy_key(PurplePrivateKey * key)
+{
+ if (NULL == key) return;
+
+ g_return_if_fail(key->data != NULL);
+ g_return_if_fail(key->scheme != NULL);
+
+ /* Check that the scheme is x509_key_gnutls */
+ if ( key->scheme != &x509_key_gnutls ) {
+ purple_debug_error("gnutls",
+ "destroy_key attempted on key of wrong scheme (scheme was %s, expected %s)\n",
+ key->scheme->name,
+ KEY_SCHEME_NAME);
+ return;
+ }
+
+ /* Use the reference counting system to free (or not) the
+ underlying data */
+ x509_keydata_delref((x509_keydata_t *)key->data);
+
+ /* Kill the structure itself */
+ g_free(key);
+ return;
+}
+
+static gchar*
+x509_get_unique_key_id(PurplePrivateKey *key)
+{
+ gnutls_x509_privkey_t key_dat; /* GnuTLS key struct */
+ int ret;
+ guchar * out_buf = NULL; /* Data to output */
+ size_t out_size = 0; /* Output size */
+
+ g_return_val_if_fail(key, FALSE);
+ g_return_val_if_fail(key->scheme == &x509_key_gnutls, FALSE);
+ g_return_val_if_fail(key->data, FALSE);
+
+ key_dat = X509_GET_GNUTLS_KEYDATA(key);
+
+ /* Get output size */
+ ret = gnutls_x509_privkey_get_key_id(key_dat, 0, NULL, &out_size);
+
+ g_return_val_if_fail(ret == GNUTLS_E_SHORT_MEMORY_BUFFER, NULL);
+
+ out_buf = g_new0(guchar, out_size);
+
+ ret = gnutls_x509_privkey_get_key_id(key_dat, 0, out_buf, &out_size);
+ if (GNUTLS_E_SUCCESS != ret) {
+ purple_debug_error("gnutls/x509key",
+ "Failed to get key id: %s\n",
+ gnutls_strerror(ret));
+ g_free(out_buf);
+ return NULL;
+ }
+
+ return (gchar*)out_buf;
+}
+
+static PurplePrivateKeyScheme x509_key_gnutls = {
+ "x509", /* Scheme name */
+ N_("X.509 Private Keys"), /* User-visibile scheme name */
+ x509_import_key, /* Key import */
+ x509_export_key, /* Key export */
+ x509_copy_key, /* Copy key */
+ x509_destroy_key, /* Destroy key */
+ x509_get_unique_key_id, /* Get key id */
+
+ NULL,
+ NULL,
+ NULL
+};
+
+/**********************************************************
+ * PKCS12 operations *
+ **********************************************************/
+
+/**
+ * Borrowed from gnutls_x509.c. This is only exposed via:
+ * gnutls_certificate_set_x509_simple_pkcs12_mem
+ * gnutls_certificate_set_x509_simple_pkcs12_file
+ * which operate on a gnutls_credentials object. However, purple
+ * prefers to directly manage its own certificates and (now) keys.
+ * PKCS12 is complex so we should use code that (probably) already
+ * works.
+ *
+ * Adding a keystore abstraction would probably be better. Let each
+ * SSL crypto backend supply its own keystore???
+ */
+
+#define gnutls_assert() purple_debug_info("gnutls/x509", "parse_pkcs12")
+
+static int
+parse_pkcs12 (gnutls_certificate_credentials_t res,
+ gnutls_pkcs12_t p12,
+ const char *password,
+ gnutls_x509_privkey_t * key,
+ gnutls_x509_crt_t * cert, gnutls_x509_crl_t * crl)
+{
+ gnutls_pkcs12_bag_t bag = NULL;
+ int idx = 0;
+ int ret;
+ size_t cert_id_size = 0;
+ size_t key_id_size = 0;
+ unsigned char cert_id[20];
+ unsigned char key_id[20];
+ int privkey_ok = 0;
+
+ *cert = NULL;
+ *key = NULL;
+ *crl = NULL;
+
+ /* find the first private key */
+ for (;;)
+ {
+ int elements_in_bag;
+ int i;
+
+ ret = gnutls_pkcs12_bag_init (&bag);
+ if (ret < 0)
+ {
+ bag = NULL;
+ gnutls_assert ();
+ goto done;
+ }
+
+ ret = gnutls_pkcs12_get_bag (p12, idx, bag);
+ if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+ break;
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ goto done;
+ }
+
+ ret = gnutls_pkcs12_bag_get_type (bag, 0);
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ goto done;
+ }
+
+ if (ret == GNUTLS_BAG_ENCRYPTED)
+ {
+ ret = gnutls_pkcs12_bag_decrypt (bag, password);
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ goto done;
+ }
+ }
+
+ elements_in_bag = gnutls_pkcs12_bag_get_count (bag);
+ if (elements_in_bag < 0)
+ {
+ gnutls_assert ();
+ goto done;
+ }
+
+ for (i = 0; i < elements_in_bag; i++)
+ {
+ int type;
+ gnutls_datum_t data;
+
+ type = gnutls_pkcs12_bag_get_type (bag, i);
+ if (type < 0)
+ {
+ gnutls_assert ();
+ goto done;
+ }
+
+ ret = gnutls_pkcs12_bag_get_data (bag, i, &data);
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ goto done;
+ }
+
+ switch (type)
+ {
+ case GNUTLS_BAG_PKCS8_ENCRYPTED_KEY:
+ case GNUTLS_BAG_PKCS8_KEY:
+ if (*key != NULL) /* too simple to continue */
+ {
+ gnutls_assert ();
+ break;
+ }
+
+ ret = gnutls_x509_privkey_init (key);
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ goto done;
+ }
+
+ ret = gnutls_x509_privkey_import_pkcs8
+ (*key, &data, GNUTLS_X509_FMT_DER, password,
+ type == GNUTLS_BAG_PKCS8_KEY ? GNUTLS_PKCS_PLAIN : 0);
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ gnutls_x509_privkey_deinit (*key);
+ goto done;
+ }
+
+ key_id_size = sizeof (key_id);
+ ret =
+ gnutls_x509_privkey_get_key_id (*key, 0, key_id,
+ &key_id_size);
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ gnutls_x509_privkey_deinit (*key);
+ goto done;
+ }
+
+ privkey_ok = 1; /* break */
+ break;
+ default:
+ break;
+ }
+ }
+
+ idx++;
+ gnutls_pkcs12_bag_deinit (bag);
+
+ if (privkey_ok != 0) /* private key was found */
+ break;
+ }
+
+ if (privkey_ok == 0) /* no private key */
+ {
+ gnutls_assert ();
+ return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
+ }
+
+ /* now find the corresponding certificate
+ */
+ idx = 0;
+ bag = NULL;
+ for (;;)
+ {
+ int elements_in_bag;
+ int i;
+
+ ret = gnutls_pkcs12_bag_init (&bag);
+ if (ret < 0)
+ {
+ bag = NULL;
+ gnutls_assert ();
+ goto done;
+ }
+
+ ret = gnutls_pkcs12_get_bag (p12, idx, bag);
+ if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+ break;
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ goto done;
+ }
+
+ ret = gnutls_pkcs12_bag_get_type (bag, 0);
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ goto done;
+ }
+
+ if (ret == GNUTLS_BAG_ENCRYPTED)
+ {
+ ret = gnutls_pkcs12_bag_decrypt (bag, password);
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ goto done;
+ }
+ }
+
+ elements_in_bag = gnutls_pkcs12_bag_get_count (bag);
+ if (elements_in_bag < 0)
+ {
+ gnutls_assert ();
+ goto done;
+ }
+
+ for (i = 0; i < elements_in_bag; i++)
+ {
+ int type;
+ gnutls_datum_t data;
+
+ type = gnutls_pkcs12_bag_get_type (bag, i);
+ if (type < 0)
+ {
+ gnutls_assert ();
+ goto done;
+ }
+
+ ret = gnutls_pkcs12_bag_get_data (bag, i, &data);
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ goto done;
+ }
+
+ switch (type)
+ {
+ case GNUTLS_BAG_CERTIFICATE:
+ if (*cert != NULL) /* no need to set it again */
+ {
+ gnutls_assert ();
+ break;
+ }
+
+ ret = gnutls_x509_crt_init (cert);
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ goto done;
+ }
+
+ ret =
+ gnutls_x509_crt_import (*cert, &data, GNUTLS_X509_FMT_DER);
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ gnutls_x509_crt_deinit (*cert);
+ goto done;
+ }
+
+ /* check if the key id match */
+ cert_id_size = sizeof (cert_id);
+ ret =
+ gnutls_x509_crt_get_key_id (*cert, 0, cert_id, &cert_id_size);
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ gnutls_x509_crt_deinit (*cert);
+ goto done;
+ }
+
+ if (memcmp (cert_id, key_id, cert_id_size) != 0)
+ { /* they don't match - skip the certificate */
+ gnutls_x509_crt_deinit (*cert);
+ *cert = NULL;
+ }
+ break;
+
+ case GNUTLS_BAG_CRL:
+ if (*crl != NULL)
+ {
+ gnutls_assert ();
+ break;
+ }
+
+ ret = gnutls_x509_crl_init (crl);
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ goto done;
+ }
+
+ ret = gnutls_x509_crl_import (*crl, &data, GNUTLS_X509_FMT_DER);
+ if (ret < 0)
+ {
+ gnutls_assert ();
+ gnutls_x509_crl_deinit (*crl);
+ goto done;
+ }
+ break;
+
+ case GNUTLS_BAG_ENCRYPTED:
+ /* XXX Bother to recurse one level down? Unlikely to
+ use the same password anyway. */
+ case GNUTLS_BAG_EMPTY:
+ default:
+ break;
+ }
+ }
+
+ idx++;
+ gnutls_pkcs12_bag_deinit (bag);
+ }
+
+ ret = 0;
+
+done:
+ if (bag)
+ gnutls_pkcs12_bag_deinit (bag);
+
+ return ret;
+}
+
+static gboolean
+read_pkcs12_file(const gchar* filename, gnutls_datum_t *dt, gnutls_x509_crt_fmt_t * fmt)
+{
+ gchar *buf = NULL; /* Used to load the raw file data */
+ gsize buf_sz; /* Size of the above */
+
+ purple_debug_info("gnutls",
+ "Attempting to load PKCS12 file from %s\n",
+ filename);
+
+ /* Next, we'll simply yank the entire contents of the file
+ into memory */
+ /* TODO: Should I worry about very large files here? */
+ g_return_val_if_fail(
+ g_file_get_contents(filename,
+ &buf,
+ &buf_sz,
+ NULL /* No error checking for now */
+ ),
+ FALSE);
+
+ *fmt = GNUTLS_X509_FMT_DER;
+ #define PEM_PKCS12_HDR "-----BEGIN PKCS12-----"
+ if (0 == strncmp(buf, PEM_PKCS12_HDR, sizeof(PEM_PKCS12_HDR)-1))
+ *fmt = GNUTLS_X509_FMT_PEM;
+
+ dt->data = (unsigned char*) buf;
+ dt->size = buf_sz;
+
+ return TRUE;
+}
+
+
+/**
+ * Derived from gnutls_certificate_set_x509_simple_pkcs12_mem in
+ * gnutls_x509.c. Modified to return PurpleCertificate and PurplePrivateKey
+ * objects.
+ */
+static gboolean
+x509_import_pkcs12_from_file(const gchar* filename,
+ const gchar* password,
+ PurpleCertificate **crt,
+ PurplePrivateKey **key)
+{
+ gnutls_pkcs12_t p12;
+ gnutls_certificate_credentials_t res = NULL;
+ gnutls_x509_crl_t crl = NULL;
+ gnutls_datum_t dt;
+ gnutls_x509_crt_fmt_t fmt;
+ x509_crtdata_t *crtdat;
+ x509_keydata_t *keydat;
+
+ int rv;
+
+ if (!read_pkcs12_file(filename, &dt, &fmt)) {
+ purple_debug_error("gnutls",
+ "Failed to load PKCS12 file from %s\n",
+ filename);
+ return FALSE;
+ }
+
+ purple_debug_info("gnutls", "pkcs12 import: file:%s size:%d fmt:%d\n", filename, dt.size, fmt);
+
+ rv = gnutls_pkcs12_init (&p12);
+ if (GNUTLS_E_SUCCESS != rv) {
+ purple_debug_error("gnutls/x509",
+ "pkcs12_init error: %s\n", gnutls_strerror(rv));
+ return FALSE;
+ }
+
+ rv = gnutls_pkcs12_import(p12, &dt, fmt, 0);
+ if (GNUTLS_E_SUCCESS != rv) {
+ purple_debug_error("gnutls/x509",
+ "pkcs12_import error: %s\n", gnutls_strerror(rv));
+ gnutls_pkcs12_deinit (p12);
+ return FALSE;
+ }
+
+ if (password) {
+ rv = gnutls_pkcs12_verify_mac(p12, (const char*)password);
+ if (GNUTLS_E_SUCCESS != rv) {
+ purple_debug_error("gnutls/x509",
+ "pkcs12_verify_mac error: %s\n", gnutls_strerror(rv));
+ gnutls_pkcs12_deinit (p12);
+ return FALSE;
+ }
+ }
+
+ /* Allocate and prepare the internal key and crt data */
+ crtdat = g_new0(x509_crtdata_t, 1);
+ crtdat->crt = NULL;
+ crtdat->refcount = 0;
+
+ keydat = g_new0(x509_keydata_t, 1);
+ keydat->key = NULL;
+ keydat->refcount = 0;
+
+ rv = parse_pkcs12 (res, p12, password, &(keydat->key), &(crtdat->crt), &crl);
+// gnutls_pkcs12_deinit (p12);
+ if (GNUTLS_E_SUCCESS != rv) {
+ purple_debug_error("gnutls/x509",
+ "parse_pkcs12 error: %s\n", gnutls_strerror(rv));
+ gnutls_x509_crt_deinit (crtdat->crt);
+ gnutls_x509_privkey_deinit(keydat->key);
+ gnutls_x509_crl_deinit (crl);
+ g_free(crtdat);
+ g_free(keydat);
+ return FALSE;
+ }
+
+ /* Just deinit since we aren't using and
+ want to avoid modifying parse_pkcs12() */
+ gnutls_x509_crl_deinit (crl);
+
+ if (NULL == keydat->key || NULL == crtdat->crt) {
+ purple_debug_error("gnutls/x509",
+ "%s get a cert. %s get a key",
+ crtdat->crt ? "Did" : "Did not",
+ keydat->key ? "Did" : "Did not");
+ return FALSE;
+ }
+
+ *crt = g_new0(PurpleCertificate, 1);
+ (*crt)->scheme = &x509_gnutls;
+ (*crt)->data = x509_crtdata_addref(crtdat);
+
+ *key = g_new0(PurplePrivateKey, 1);
+ (*key)->scheme = &x509_key_gnutls;
+ (*key)->data = x509_keydata_addref(keydat);
+
+ /* check if the key and certificate found match */
+#if 0
+ if (key && (ret = _gnutls_check_key_cert_match (res)) < 0) {
+ gnutls_assert ();
+ to done;
+ }
+#endif
+
+ return TRUE;
+}
+
+/** Export PurpleCertificate and PurplePrivateKey to a PKCS12 file.
+ */
+/* Derived from generate_pkcs12() in certtool.c in the gnutls source. */
+static gboolean
+x509_export_pkcs12_to_filename(const gchar* filename, const gchar* password, PurpleCertificate *purple_crt, PurplePrivateKey *purple_key)
+{
+ gnutls_pkcs12_t pkcs12 = NULL;
+ gnutls_x509_crt_t crts[1];
+ gnutls_x509_privkey_t key = NULL;
+ int result;
+ size_t size;
+ gnutls_datum_t data;
+ const char *pass;
+ const char *name;
+ unsigned int flags, i;
+ gnutls_datum_t key_id;
+ unsigned char _key_id[20];
+ int indx;
+ size_t ncrts;
+ gboolean success = FALSE;
+ gnutls_pkcs12_bag_t kbag = NULL;
+ gnutls_pkcs12_bag_t bag = NULL;
+ char *key_buf = NULL;
+ char *out_buf = NULL;
+
+ crts[0] = X509_GET_GNUTLS_DATA(purple_crt);
+ key = X509_GET_GNUTLS_KEYDATA(purple_key);
+ ncrts = 1;
+
+ name = x509_common_name(purple_crt);
+ if (NULL == name) {
+ purple_debug_error("gnutls/pkcs12", "export: can't get common name for cert\n");
+ goto done;
+ }
+
+ result = gnutls_pkcs12_init (&pkcs12);
+ if (result < 0) {
+ purple_debug_error("gnutls/pkcs12", "export: pkcs12_init: %s\n", gnutls_strerror (result));
+ goto done;
+ }
+
+ for (i = 0; i < ncrts; i++)
+ {
+ result = gnutls_pkcs12_bag_init (&bag);
+ if (result < 0) {
+ purple_debug_error("gnutls/pkcs12", "export: bag_init: %s\n", gnutls_strerror (result));
+ goto done;
+ }
+
+ result = gnutls_pkcs12_bag_set_crt (bag, crts[i]);
+ if (result < 0) {
+ purple_debug_error ("gnutls/pkcs12", "export: set_crt[%d]: %s\n", i,
+ gnutls_strerror (result));
+ goto done;
+ }
+
+ indx = result;
+
+ result = gnutls_pkcs12_bag_set_friendly_name (bag, indx, name);
+ if (result < 0) {
+ purple_debug_error ("gnutls/pkcs12", "bag_set_friendly_name: %s\n",
+ gnutls_strerror (result));
+ goto done;
+ }
+
+ size = sizeof (_key_id);
+ result = gnutls_x509_crt_get_key_id (crts[i], 0, _key_id, &size);
+ if (result < 0) {
+ purple_debug_error("gnutls/pkcs12", "key_id[%d]: %s\n", i,
+ gnutls_strerror(result));
+ goto done;
+ }
+
+ key_id.data = _key_id;
+ key_id.size = size;
+
+ result = gnutls_pkcs12_bag_set_key_id (bag, indx, &key_id);
+ if (result < 0) {
+ purple_debug_error("gnutls/pkcs12", "bag_set_key_id: %s\n",
+ gnutls_strerror(result));
+ goto done;
+ }
+
+ /* TODO: Check GNUTLS version and use AES if possible */
+ flags = GNUTLS_PKCS_USE_PBES2_3DES;
+
+ result = gnutls_pkcs12_bag_encrypt (bag, password, flags);
+ if (result < 0) {
+ purple_debug_error("gnutls/pkcs12", "bag_encrypt: %s\n", gnutls_strerror (result));
+ goto done;
+ }
+
+ result = gnutls_pkcs12_set_bag (pkcs12, bag);
+ if (result < 0) {
+ purple_debug_error("gnutls/pkcs12", "set_bag: %s\n", gnutls_strerror (result));
+ goto done;
+ }
+
+ gnutls_pkcs12_bag_deinit(bag);
+ bag = NULL;
+ }
+
+
+ result = gnutls_pkcs12_bag_init (&kbag);
+ if (result < 0) {
+ purple_debug_error("gnutls/pkcs12", "bag_init: %s\n", gnutls_strerror (result));
+ goto done;
+ }
+
+ /* TODO: Check GNUTLS version and use AES if possible */
+ flags = GNUTLS_PKCS_USE_PBES2_3DES;
+
+ size = 0;
+ result = gnutls_x509_privkey_export_pkcs8 (key, GNUTLS_X509_FMT_DER,
+ pass, flags, NULL, &size);
+
+ if (result != GNUTLS_E_SHORT_MEMORY_BUFFER) {
+ purple_debug_error("gnutls/pkcs12", "Can't get pkcs8 memory size.\n");
+ goto done;
+ }
+
+ purple_debug_info("gnutls/pkcs12", "Got pkcs8 export memory size = %d\n", size);
+
+ /* TODO: Above should give us the correct size, but doesn't. In fact, it seems
+ * everytime I call it with the new buffer it wants something bigger. So we just
+ * add on extra 100 bytes and hope for the best.
+ */
+
+ size += 100;
+ key_buf = g_new0(char, size);
+ if (!key_buf) {
+ goto done;
+ }
+
+ result = gnutls_x509_privkey_export_pkcs8 (key, GNUTLS_X509_FMT_DER,
+ password, flags, key_buf, &size);
+ if (result < 0) {
+ purple_debug_error("gnutls/pkcs12", "key_export: size: %d; error: %s\n",
+ size, gnutls_strerror (result));
+ goto done;
+ }
+
+ data.data = (unsigned char*)key_buf;
+ data.size = size;
+ result =
+ gnutls_pkcs12_bag_set_data (kbag,
+ GNUTLS_BAG_PKCS8_ENCRYPTED_KEY, &data);
+ if (result < 0) {
+ purple_debug_error("gnutls/pkcs12", "bag_set_data: %s\n", gnutls_strerror (result));
+ goto done;
+ }
+
+ indx = result;
+
+ result = gnutls_pkcs12_bag_set_friendly_name (kbag, indx, name);
+ if (result < 0) {
+ purple_debug_error("gnutls/pkcs12", "bag_set_friendly_name: %s\n",
+ gnutls_strerror(result));
+ goto done;
+ }
+
+ size = sizeof (_key_id);
+ result = gnutls_x509_privkey_get_key_id (key, 0, _key_id, &size);
+ if (result < 0) {
+ purple_debug_error("gnutls/pkcs12", "key_id: %s\n", gnutls_strerror (result));
+ goto done;
+ }
+
+ key_id.data = _key_id;
+ key_id.size = size;
+
+ result = gnutls_pkcs12_bag_set_key_id (kbag, indx, &key_id);
+ if (result < 0) {
+ purple_debug_error("gnutls/pkcs12", "bag_set_key_id: %s\n",
+ gnutls_strerror(result));
+ goto done;
+ }
+
+ result = gnutls_pkcs12_set_bag (pkcs12, kbag);
+ if (result < 0) {
+ purple_debug_error("gnutls/pkcs12", "set_bag: %s\n", gnutls_strerror (result));
+ goto done;
+ }
+
+ result = gnutls_pkcs12_generate_mac (pkcs12, password);
+ if (result < 0) {
+ purple_debug_error("gnutls/pkcs12", "generate_mac: %s\n", gnutls_strerror (result));
+ goto done;
+ }
+
+ size = 0;
+ result = gnutls_pkcs12_export (pkcs12, GNUTLS_X509_FMT_PEM, NULL, &size);
+
+ if (result != GNUTLS_E_SHORT_MEMORY_BUFFER) {
+ purple_debug_error("gnutls/pkcs12", "Can't get pkcs12 memory size.\n");
+ goto done;
+ }
+
+ out_buf = g_new0(char, size);
+ if (NULL == out_buf) {
+ purple_debug_error("gnutls/pkcs12", "output buf allocation failure\n");
+ goto done;
+ }
+
+ result = gnutls_pkcs12_export (pkcs12, GNUTLS_X509_FMT_PEM, out_buf, &size);
+ if (result < 0) {
+ purple_debug_error("gnutls/pkcs12", "pkcs12_export: %s\n", gnutls_strerror (result));
+ goto done;
+ }
+
+ success = purple_util_write_data_to_file_absolute(filename,
+ out_buf, size);
+
+done:
+ g_free(key_buf);
+ g_free(out_buf);
+ gnutls_pkcs12_bag_deinit(bag);
+ gnutls_pkcs12_bag_deinit(kbag);
+ gnutls_pkcs12_deinit(pkcs12);
+
+ return success;
+}
+
+static gboolean
+pkcs12_import(const gchar *filename, const gchar *password,
+ PurpleCertificate **crt, PurplePrivateKey **key)
+{
+ g_return_val_if_fail(filename, FALSE);
+ g_return_val_if_fail(password, FALSE);
+ g_return_val_if_fail(crt, FALSE);
+ g_return_val_if_fail(key, FALSE);
+
+
+ return x509_import_pkcs12_from_file(filename, password, crt, key);
+}
+
+static gboolean
+pkcs12_export(const gchar *filename, const gchar *password,
+ PurpleCertificate *crt, PurplePrivateKey *key)
+{
+ g_return_val_if_fail(filename, FALSE);
+ g_return_val_if_fail(password, FALSE);
+ g_return_val_if_fail(crt, FALSE);
+ g_return_val_if_fail(key, FALSE);
+
+ g_return_val_if_fail(key->scheme == &x509_key_gnutls, FALSE);
+ g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+
+ return x509_export_pkcs12_to_filename(filename, password, crt, key);
+}
+
+
+static PurplePkcs12Scheme pkcs12_gnutls = {
+ "pkcs12", /* Scheme name */
+ N_("PKCS12"), /* User-visible scheme name */
+ pkcs12_import, /* PKCS12 import */
+ pkcs12_export, /* PKCS12 export */
+
+ NULL,
+ NULL,
+ NULL
+};
+
+/**********************************************************************
+ * Setting the Purple Certificate and Private Key for authentication *
+ **********************************************************************/
+
+static gboolean
+ssl_gnutls_set_client_auth(gnutls_certificate_client_credentials cred, PurpleCertificate * pcrt, PurplePrivateKey * pkey)
+{
+ gnutls_x509_crt_t cert_list[1];
+ int rv;
+
+ g_return_val_if_fail(pcrt, FALSE);
+ g_return_val_if_fail(pkey, FALSE);
+ g_return_val_if_fail(pcrt->scheme == &x509_gnutls, FALSE);
+ g_return_val_if_fail(pkey->scheme == &x509_key_gnutls, FALSE);
+
+ if (NULL != xcred) {
+ cert_list[0] = X509_GET_GNUTLS_DATA(pcrt);
+ rv = gnutls_certificate_set_x509_key(cred, cert_list, 1, X509_GET_GNUTLS_KEYDATA(pkey));
+ if (GNUTLS_E_SUCCESS != rv) {
+ purple_debug_error("gnutls/ssl",
+ "Failed to set add certs to credentials: %s\n",
+ gnutls_strerror(rv));
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
static PurpleSslOps ssl_ops =
{
ssl_gnutls_init,
@@ -1193,6 +2259,8 @@
/* Register that we're providing an X.509 CertScheme */
purple_certificate_register_scheme( &x509_gnutls );
+ purple_privatekey_register_scheme( &x509_key_gnutls );
+ purple_pkcs12_register_scheme( &pkcs12_gnutls );
return TRUE;
}
@@ -1205,6 +2273,8 @@
}
purple_certificate_unregister_scheme( &x509_gnutls );
+ purple_privatekey_unregister_scheme( &x509_key_gnutls );
+ purple_pkcs12_unregister_scheme( &pkcs12_gnutls );
return TRUE;
}
--- /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
+ * @ingroup core
+ */
+
+/*
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include "internal.h"
+#include "privatekey.h"
+#include "dbus-maybe.h"
+#include "debug.h"
+#include "request.h"
+#include "signals.h"
+#include "util.h"
+
+/** 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 *
+ *****************************************************************************/
+
+PurplePrivateKey *
+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);
+}
+
+void
+purple_privatekey_destroy (PurplePrivateKey *key)
+{
+ PurplePrivateKeyScheme *scheme;
+
+ if (NULL == key) return;
+
+ scheme = key->scheme;
+
+ (scheme->destroy_key)(key);
+}
+
+PurplePrivateKey *
+purple_privatekey_import(PurplePrivateKeyScheme *scheme, const gchar *filename,
+ const gchar *password)
+{
+ 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);
+}
+
+gboolean
+purple_privatekey_export(const gchar *filename, PurplePrivateKey *key,
+ const gchar *password)
+{
+ PurplePrivateKeyScheme *scheme;
+
+ g_return_val_if_fail(filename, FALSE);
+ g_return_val_if_fail(key, FALSE);
+ g_return_val_if_fail(key->scheme, FALSE);
+
+ scheme = key->scheme;
+ g_return_val_if_fail(scheme->export_key, FALSE);
+
+ return (scheme->export_key)(filename, key, password);
+}
+
+gchar *
+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 *
+ *****************************************************************************/
+
+gchar *
+purple_privatekey_pool_mkpath(PurplePrivateKeyPool *pool, const gchar *id)
+{
+ gchar *path;
+ 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? */
+ esc_scheme_name,
+ esc_name,
+ esc_id,
+ NULL);
+
+ g_free(esc_scheme_name);
+ g_free(esc_name);
+ g_free(esc_id);
+ return path;
+}
+
+#if 0
+gboolean
+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);
+
+ return TRUE;
+}
+
+const gchar*
+purple_privatekey_pool_get_password(PurplePrivateKeyPool *pool)
+{
+ g_returen_val_if_fail(pool, FALSE);
+
+ return (pool->get_password)();
+}
+#endif
+
+gboolean
+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) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+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);
+}
+
+gboolean
+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
+ */
+
+typedef struct {
+ PurplePrivateKeyPool *pool;
+ PurplePrivateKey *key;
+ const gchar* id;
+ GCallback ok_cb;
+ GCallback cancel_cb;
+ void *user_data;
+} privatekey_pool_req_data;
+
+static void
+purple_privatekey_pool_request_password(
+ const gchar* msg,
+ PurplePrivateKeyPool *pool,
+ const gchar* id,
+ 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(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,
+ NULL,
+ msg,
+ NULL,
+ fields,
+ _("OK"), ok_cb,
+ _("Cancel"), cancel_cb,
+ NULL, NULL, NULL,
+ user_data);
+}
+
+
+static void
+privatekey_pool_req_cancel_cb(privatekey_pool_req_data *data, PurpleRequestFields *fields)
+{
+ ((PurplePrivateKeyPoolCancelCb)(data->cancel_cb))(data->user_data);
+ g_free(data);
+}
+
+static void
+privatekey_pool_req_retrieve_ok_cb(privatekey_pool_req_data *data, PurpleRequestFields *fields)
+{
+ const char *entry;
+ gboolean remember;
+ PurplePrivateKey *key;
+
+ entry = purple_request_fields_get_string(fields, "password");
+/* remember = purple_request_fields_get_bool(fields, "remember");*/
+
+ if (!entry || !*entry)
+ {
+ /* 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);
+ return;
+ }
+/*
+ if(remember)
+ purple_account_set_remember_password(account, TRUE);
+*/
+
+ key = (data->pool->get_key)(data->id, entry);
+
+ ((PurplePrivateKeyPoolRetrieveRequestOkCb)data->ok_cb)(key, data->user_data);
+ g_free(data);
+}
+
+void purple_privatekey_pool_retrieve_request(
+ PurplePrivateKeyPool *pool, const gchar *id,
+// PurplePrivateKeyPoolRetrieveRequestOkCb ok_cb,
+// PurplePrivateKeyPoolCancelCb cancel_cb,
+ GCallback ok_cb,
+ GCallback cancel_cb,
+ void* user_data)
+{
+ gchar* msg;
+
+ privatekey_pool_req_data *data;
+
+ g_return_if_fail(pool);
+ g_return_if_fail(id);
+ 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->key = NULL;
+ data->pool = pool;
+ data->id = id;
+ 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(
+ msg,
+ pool,
+ id,
+ G_CALLBACK(privatekey_pool_req_retrieve_ok_cb),
+ G_CALLBACK(privatekey_pool_req_cancel_cb),
+ data);
+ g_free(msg); /* TODO: not sure this is safe */
+}
+
+static void
+privatekey_pool_req_store_ok_cb(privatekey_pool_req_data *data, PurpleRequestFields *fields)
+{
+ const char *entry;
+ gboolean remember;
+ gboolean result;
+
+ entry = purple_request_fields_get_string(fields, "password");
+/* remember = purple_request_fields_get_bool(fields, "remember");*/
+
+ if (!entry || !*entry)
+ {
+ /* 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);
+ return;
+ }
+/*
+ if(remember)
+ 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);
+ g_free(data);
+}
+
+void
+purple_privatekey_pool_store_request(
+ PurplePrivateKeyPool *pool, const gchar *id, PurplePrivateKey *key,
+// PurplePrivateKeyPoolStoreRequestOkCb ok_cb,
+// PurplePrivateKeyPoolCancelCb cancel_cb,
+ GCallback ok_cb,
+ GCallback cancel_cb,
+ void* user_data)
+{
+ gchar* msg;
+ privatekey_pool_req_data *data;
+
+ g_return_if_fail(pool);
+ g_return_if_fail(id);
+ 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->key = key;
+ data->pool = pool;
+ data->id = id;
+ 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(
+ msg,
+ pool,
+ id,
+ G_CALLBACK(privatekey_pool_req_store_ok_cb),
+ G_CALLBACK(privatekey_pool_req_cancel_cb),
+ data);
+ g_free(msg); /* TODO: not sure this is safe */
+}
+
+/******************************************************************************
+ * Purple Private Key Pool direct retrieve and store
+ */
+
+PurplePrivateKey *
+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);
+}
+
+gboolean
+purple_privatekey_pool_store(PurplePrivateKeyPool *pool, const gchar *id, PurplePrivateKey *key, const gchar* password)
+{
+ gboolean ret = FALSE;
+
+ 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_return_val_if_fail(
+ g_ascii_strcasecmp(pool->scheme_name, key->scheme->name) == 0,
+ FALSE);
+
+ ret = (pool->put_key)(id, key, password);
+
+ /* Signal that the certificate was stored if success*/
+ if (ret) {
+ purple_signal_emit(pool, "privatekey-stored",
+ pool, id);
+ }
+
+ return ret;
+}
+
+gboolean
+purple_privatekey_pool_delete(PurplePrivateKeyPool *pool, const gchar *id)
+{
+ gboolean ret = FALSE;
+
+ 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 */
+ if (ret) {
+ purple_signal_emit(pool, "key-deleted",
+ pool, id);
+ }
+
+ return ret;
+}
+
+GList *
+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)();
+}
+
+void
+purple_privatekey_pool_destroy_idlist(GList *idlist)
+{
+ GList *l;
+
+ /* Iterate through and free them strings */
+ for ( l = idlist; l; l = l->next ) {
+ g_free(l->data);
+ }
+
+ g_list_free(idlist);
+}
+
+
+/****************************************************************************/
+/* Builtin Pools */
+/****************************************************************************/
+
+/***** Cache of user's keys *****/
+static PurplePrivateKeyPool x509_user_keys;
+
+typedef struct x509_user_data {
+ gchar *password;
+} x509_user_data_t;
+
+static GList* x509_user_key_paths = NULL;
+
+static gboolean
+x509_user_init(void)
+{
+ gchar *poolpath;
+ int ret;
+
+ 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 */
+
+ if (ret != 0)
+ purple_debug_info("x509_user/keys",
+ "Could not create %s. Keys will not be saved.\n",
+ poolpath);
+
+ g_free(poolpath);
+
+ return TRUE;
+}
+
+static void
+x509_user_uninit(void)
+{
+ if (x509_user_keys.data != NULL)
+ g_free(x509_user_keys.data);
+}
+/*
+static void
+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");
+}
+*/
+static gboolean
+x509_user_key_in_pool(const gchar *id)
+{
+ gchar *keypath;
+ gboolean ret = FALSE;
+
+ 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);
+
+ g_free(keypath);
+ return ret;
+}
+
+static PurplePrivateKey *
+x509_user_get_key(const gchar *id, const gchar* password)
+{
+ PurplePrivateKeyScheme *x509;
+ PurplePrivateKey *key;
+ gchar *keypath;
+
+ g_return_val_if_fail(id, NULL);
+
+ /* Is it in the pool? */
+ if ( !x509_user_key_in_pool(id) ) {
+ return NULL;
+ }
+
+ /* 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);
+
+ g_free(keypath);
+
+ return key;
+}
+
+static gboolean
+x509_user_put_key(const gchar *id, PurplePrivateKey *key, const gchar* password)
+{
+ gboolean ret = FALSE;
+ gchar *keypath;
+
+ 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);
+
+ g_free(keypath);
+ return ret;
+}
+
+static gboolean
+x509_user_delete_key(const gchar *id)
+{
+ gboolean ret = FALSE;
+ gchar *keypath;
+
+ 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",
+ id);
+ return FALSE;
+ }
+
+ /* 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",
+ keypath);
+ ret = FALSE;
+ } else {
+ ret = TRUE;
+ }
+
+ g_free(keypath);
+ return ret;
+}
+
+static GList *
+x509_user_get_idlist(void)
+{
+ GList *idlist = NULL;
+ GDir *dir;
+ const gchar *entry;
+ gchar *poolpath;
+
+ /* Get a handle on the pool directory */
+ poolpath = purple_privatekey_pool_mkpath(&x509_user_keys, NULL);
+ dir = g_dir_open(poolpath,
+ 0, /* No flags */
+ NULL); /* Not interested in what the error is */
+ g_free(poolpath);
+
+ 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
+ string) */
+ idlist = g_list_prepend(idlist, g_strdup(unescaped));
+ }
+
+ /* Release the directory */
+ g_dir_close(dir);
+
+ return idlist;
+}
+
+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 */
+
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+/****************************************************************************/
+/* Subsystem */
+/****************************************************************************/
+void
+purple_privatekey_init(void)
+{
+ /* Register builtins */
+ purple_privatekey_register_pool(&x509_user_keys);
+}
+
+void
+purple_privatekey_uninit(void)
+{
+ /* Unregister all Pools */
+ g_list_foreach(key_pools, (GFunc)purple_privatekey_unregister_pool, NULL);
+}
+
+gpointer
+purple_privatekey_get_handle(void)
+{
+ static gint handle;
+ return &handle;
+}
+
+PurplePrivateKeyScheme *
+purple_privatekey_find_scheme(const gchar *name)
+{
+ PurplePrivateKeyScheme *scheme = NULL;
+ GList *l;
+
+ g_return_val_if_fail(name, NULL);
+
+ /* Traverse the list of registered schemes and locate the
+ one whose name matches */
+ for(l = key_schemes; l; l = l->next) {
+ scheme = (PurplePrivateKeyScheme *)(l->data);
+
+ /* Name matches? that's our man */
+ if(!g_ascii_strcasecmp(scheme->name, name))
+ return scheme;
+ }
+
+ purple_debug_warning("privatekey",
+ "PrivateKeyScheme %s requested but not found.\n",
+ name);
+
+ /* TODO: Signalling and such? */
+
+ return NULL;
+}
+
+GList *
+purple_privatekey_get_schemes(void)
+{
+ return key_schemes;
+}
+
+gboolean
+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) {
+ return FALSE;
+ }
+
+ /* 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",
+ scheme->name);
+
+ return TRUE;
+}
+
+gboolean
+purple_privatekey_unregister_scheme(PurplePrivateKeyScheme *scheme)
+{
+ if (NULL == scheme) {
+ purple_debug_warning("privatekey",
+ "Attempting to unregister NULL scheme\n");
+ return FALSE;
+ }
+
+ /* 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",
+ scheme->name);
+
+
+ return TRUE;
+}
+
+PurplePrivateKeyPool *
+purple_privatekey_find_pool(const gchar *scheme_name, const gchar *pool_name)
+{
+ PurplePrivateKeyPool *pool = NULL;
+ GList *l;
+
+ 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))
+ return pool;
+ }
+
+ purple_debug_warning("privatekey",
+ "PrivateKeyPool %s, %s requested but not found.\n",
+ scheme_name, pool_name);
+
+ /* TODO: Signalling and such? */
+
+ return NULL;
+
+}
+
+GList *
+purple_privatekey_get_pools(void)
+{
+ return key_pools;
+}
+
+gboolean
+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)) {
+ return FALSE;
+ }
+
+ /* Initialize the pool if needed */
+ if (pool->init) {
+ gboolean success;
+
+ success = pool->init();
+ if (!success)
+ return FALSE;
+ }
+
+ /* 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 */
+ "privatekey-stored",
+ 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 */
+ "privatekey-deleted",
+ 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",
+ pool->name);
+
+ return TRUE;
+}
+
+gboolean
+purple_privatekey_unregister_pool(PurplePrivateKeyPool *pool)
+{
+ if (NULL == pool) {
+ purple_debug_warning("privatekey",
+ "Attempting to unregister NULL pool\n");
+ return FALSE;
+ }
+
+ /* Check that the pool is registered */
+ if (!g_list_find(key_pools, pool)) {
+ purple_debug_warning("privatekey",
+ "Pool to unregister isn't registered!\n");
+
+ return FALSE;
+ }
+
+ /* Uninit the pool if needed */
+ PURPLE_DBUS_UNREGISTER_POINTER(pool);
+ if (pool->uninit) {
+ pool->uninit();
+ }
+
+ 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",
+ pool->name);
+ return TRUE;
+}
+
+/****************************************************************************/
+/* Scheme-specific functions */
+/****************************************************************************/
+
+#if 0
+static void
+purple_privatekey_display_x509(PurplePrivateKey *key)
+{
+}
+#endif
+
+void purple_privatekey_add_key_search_path(const char *path)
+{
+ if (g_list_find_custom(x509_user_key_paths, path, (GCompareFunc)strcmp))
+ return;
+ 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
+ * @ingroup core
+ * @see @ref privatekey-signals
+ * @since 2.2.0
+ */
+
+/*
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *a
+ * 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
+
+#include <time.h>
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#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
+ * PrivateKeyScheme
+ */
+struct _PurplePrivateKey
+{
+ /** Scheme this private key is under */
+ PurplePrivateKeyScheme * scheme;
+ /** Opaque pointer to internal data */
+ gpointer data;
+};
+
+/**
+ * Database for retrieval or storage of PrivateKeys
+ *
+ * More or less a hash table; all lookups and writes are controlled by a string
+ * key.
+ */
+struct _PurplePrivateKeyPool
+{
+ /** Scheme this Pool operates for */
+ gchar *scheme_name;
+ /** Internal name to refer to the pool by */
+ gchar *name;
+
+ /** User-friendly name for this type
+ * ex: N_("SSL Servers")
+ * When this is displayed anywhere, it should be i18ned
+ * ex: _(pool->fullname)
+ */
+ gchar *fullname;
+
+ /** Internal pool data */
+ gpointer 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
+ */
+ void (* uninit)(void);
+
+ /**
+ * Use password to protect the keys on disk on the pool using the password.
+ *
+ * @param 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 private key type
+ *
+ * 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.
+ */
+ gchar * name;
+
+ /** User-friendly name for this type
+ * ex: N_("X.509 PrivateKeys")
+ * When this is displayed anywhere, it should be i18ned
+ * ex: _(scheme->fullname)
+ */
+ gchar * 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
+ * or NULL on failure.
+ */
+ 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
+ * free(key)
+ *
+ * @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
+ * identify the key.
+ */
+ 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
+ */
+PurplePrivateKey *
+purple_privatekey_copy(PurplePrivateKey *key);
+
+/**
+ * Destroys and free()'s a PrivateKey
+ *
+ * @param key Instance to destroy. May be NULL.
+ */
+void
+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
+ */
+PurplePrivateKey *
+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
+ */
+gboolean
+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
+ */
+gchar *
+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
+ */
+gchar *
+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
+ */
+gboolean
+purple_privatekey_pool_usable(PurplePrivateKeyPool *pool);
+
+gboolean
+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
+ */
+gboolean
+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.
+ */
+void
+purple_privatekey_pool_retrieve_request(
+ PurplePrivateKeyPool *pool, const gchar *id,
+ GCallback ok_cb,
+ GCallback cancel_cb,
+ void* user_data);
+
+/**
+ * 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.
+ */
+void
+purple_privatekey_pool_store_request(
+ PurplePrivateKeyPool *pool, const gchar *id, PurplePrivateKey *key,
+ GCallback ok_cb,
+ GCallback cancel_cb,
+ void* user_data);
+
+/**
+ * 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
+ */
+PurplePrivateKey *
+purple_privatekey_pool_retrieve(PurplePrivateKeyPool *pool, const gchar *id, const gchar *password);
+
+/**
+ * Add a key to a pool
+ *
+ * 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
+ */
+gboolean
+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
+ */
+gboolean
+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()
+ */
+GList *
+purple_privatekey_pool_get_idlist(PurplePrivateKeyPool *pool);
+
+/**
+ * Destroys the result given by purple_privatekey_pool_get_idlist()
+ *
+ * @param idlist ID List to destroy
+ */
+void
+purple_privatekey_pool_destroy_idlist(GList *idlist);
+
+/*@}*/
+
+/*****************************************************************************/
+/** @name PrivateKey Subsystem API */
+/*****************************************************************************/
+/*@{*/
+
+/**
+ * Initialize the private key system
+ */
+void
+purple_privatekey_init(void);
+
+/**
+ * Un-initialize the private key system
+ */
+void
+purple_privatekey_uninit(void);
+
+/**
+ * Get the PrivateKey subsystem handle for signalling purposes
+ */
+gpointer
+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
+ */
+GList *
+purple_privatekey_get_schemes(void);
+
+/** Register a PrivateKeyScheme with libpurple
+ *
+ * No two schemes can be registered with the same name; this function enforces
+ * that.
+ *
+ * @param scheme Pointer to the scheme to register.
+ * @return TRUE if the scheme was successfully added, otherwise FALSE
+ */
+gboolean
+purple_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
+ */
+gboolean
+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.
+ */
+PurplePrivateKeyPool *
+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
+ */
+GList *
+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
+ */
+gboolean
+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
+ */
+gboolean
+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);
+
+#ifdef __cplusplus
+}
+#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")*/) {
g_free(mech_name);
continue;
}
--- 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->gc->inpa = 0;
- 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(
+ js->gc->account,
+ js->fd,
+ jabber_login_callback_ssl,
+ jabber_ssl_connect_failure,
+ js->certificate_CN,
+ purple_account_get_string(js->gc->account, "certificate_id", NULL),
+ js->gc);
/* The fd is no longer our concern */
js->fd = -1;
}
@@ -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(
+ account,
+ js->certificate_CN,
purple_account_get_int(account, "port", 5223),
- jabber_login_callback_ssl, jabber_ssl_connect_failure, gc);
+ jabber_login_callback_ssl,
+ jabber_ssl_connect_failure,
+ NULL,
+ purple_account_get_string(account, "certificate_id", NULL),
+ gc);
if (!js->gsc) {
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 "caps.h"
#include "data.h"
#include "ibb.h"
+#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 @@
return FALSE;
}
-
-static void
-init_plugin(PurplePlugin *plugin)
+static
+void destroy_account_options(GList *account_options)
{
- PurpleAccountUserSplit *split;
- PurpleAccountOption *option;
- GList *encryption_values = NULL;
+ GList *item = 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 *options = NULL;
+ 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 @@
#endif
encryption_values = g_list_reverse(encryption_values);
-#undef ADD_VALUE
option = purple_account_option_list_new(_("Connection security"), "connection_security", encryption_values);
- prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
- option);
+ 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");
+ if (cert_pool) {
+ GList *id_list = NULL;
+ GList *item = NULL;
+ 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);
+ if (cert) {
+ 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);
+ }
+ else {
+ 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);
+
+#undef ADD_VALUE
+
+ 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,
- option);
+ 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,
- option);
+ options = g_list_append(options, option);
option = purple_account_option_string_new(_("Connect server"),
"connect_server", NULL);
- prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
- option);
+ options = g_list_append(options, option);
option = purple_account_option_string_new(_("File transfer proxies"),
"ft_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,
- option);
+ options = g_list_append(options, option);
option = purple_account_option_string_new(_("BOSH URL"),
"bosh_url", NULL);
- prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
- option);
+ options = g_list_append(options, option);
/* this should probably be part of global smiley theme settings later on,
shared with MSN */
option = purple_account_option_bool_new(_("Show Custom Smileys"),
"custom_smileys", TRUE);
- prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
- option);
+ options = g_list_append(options, option);
+
+ /* Required until we stop using the protocol_options field */
+ prpl_info.protocol_options = options;
+
+ return options;
+}
+
+static void
+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();
my_protocol = plugin;
--- 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 @@
* @since 2.8.0
*/
OPT_PROTO_INVITE_MESSAGE = 0x00000800
-
} PurpleProtocolOptions;
/**
@@ -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 @@
#include "request.h"
#include "sslconn.h"
+/** To carry around connection and account references
+ * when doing client-side auth callbacks
+ */
+typedef struct {
+ PurpleSslConnection *gsc;
+ PurpleAccount *account;
+} ssl_connect_cb_data;
+
static gboolean _ssl_initialized = FALSE;
static PurpleSslOps *_ssl_ops = NULL;
@@ -71,6 +79,20 @@
#endif
}
+/**
+ * Destroy an allocated PurpleSslConnection. This frees any allocated memory,
+ * but does not close any connection. Use purple_ssl_close() instead.
+ */
+static void
+purple_ssl_destroy(PurpleSslConnection *gsc)
+{
+ purple_certificate_destroy(gsc->certificate);
+ purple_privatekey_destroy(gsc->key);
+ g_free(gsc->host);
+ g_free(gsc->certificate_id);
+ g_free(gsc);
+}
+
static void
purple_ssl_connect_cb(gpointer data, gint source, const gchar *error_message)
{
@@ -95,6 +117,128 @@
ops->connectfunc(gsc);
}
+/**
+ * Called when user enters a password for the private key password request.
+ * We check if the private key was found, and then connect the the host.
+ * For use with purple_ssl_connect_with_ssl_cn_auth().
+ */
+static void
+purple_ssl_connect_with_ssl_cn_auth_cb(PurplePrivateKey *key, void *data)
+{
+ PurpleSslConnection *gsc = ((ssl_connect_cb_data*)data)->gsc;
+ PurpleAccount *account = ((ssl_connect_cb_data*)data)->account;
+
+ /* If key is null either it wasn't found or the password was bad.
+ * We can't tell which as of now.
+ */
+ if (NULL == key) {
+ purple_debug_error("sslconn", "Failed to get private key from pool.\n");
+ if (NULL != gsc->error_cb)
+ gsc->error_cb(gsc, PURPLE_SSL_PRIVATEKEY_NOT_FOUND, gsc->connect_cb_data);
+ purple_ssl_destroy(gsc);
+ return;
+ }
+
+ gsc->key = key;
+
+ gsc->connect_data = purple_proxy_connect(NULL,
+ account,
+ gsc->host, gsc->port,
+ purple_ssl_connect_cb,
+ gsc);
+
+ g_free(data);
+
+ if (gsc->connect_data == NULL)
+ {
+ if (NULL != gsc->error_cb)
+ gsc->error_cb(gsc, PURPLE_SSL_CONNECT_FAILED, gsc->connect_cb_data);
+ purple_ssl_destroy(gsc);
+ return;
+ }
+}
+
+/**
+ * Called when the user cancels the request for the priavte key password.
+ * Common to all ssl connection types that use client authentication.
+ */
+static void
+purple_ssl_connect_cancel_cb(void *data)
+{
+ PurpleSslConnection *gsc = ((ssl_connect_cb_data*)data)->gsc;
+ purple_debug_error("sslconn", "User canceled password request for private key.\n");
+ if (gsc->error_cb != NULL)
+ gsc->error_cb(gsc, PURPLE_SSL_PRIVATEKEY_CANCELED, gsc->connect_cb_data);
+ purple_ssl_destroy(gsc);
+ g_free(data);
+}
+
+/**
+ * Get user's credentials for client-side SSL authentication. To retrieve
+ * the user's private key uses a user input request. ok_cb() is called if
+ * that request succeeds, otherwise purple_ssl_connect_cancel_cb() is
+ * called. Both the certificate and private key are retrieved from the
+ * x509:user pool.
+ *
+ * @param account Needed for the later call to purple_proxy_connect()
+ * @param gsc The SSL connection state.
+ * @param ok_cb Called when user acks the password request.
+ *
+ * @returns TRUE if we find everything and FALSE otherwise.
+ *
+ * TODO: Error handling is not great. Caller doesn't know what failed
+ * other than something. Maybe we should always use the error_cb
+ * instead of just returning.
+ */
+static gboolean
+purple_ssl_get_credentials(PurpleAccount *account, PurpleSslConnection *gsc,
+ GCallback ok_cb)
+{
+ PurpleCertificatePool *crt_pool = NULL;
+ PurplePrivateKeyPool *key_pool = NULL;
+ ssl_connect_cb_data *data = NULL;
+
+ g_return_val_if_fail(gsc, FALSE);
+ g_return_val_if_fail(gsc->certificate_id, FALSE);
+ g_return_val_if_fail(ok_cb, FALSE);
+
+ crt_pool = purple_certificate_find_pool("x509", "user");
+ if (NULL == crt_pool) {
+ purple_debug_error("sslconn",
+ "Failed to find certificate pool x509:user.\n");
+ return FALSE;
+ }
+
+ key_pool = purple_privatekey_find_pool("x509", "user");
+ if (NULL == key_pool) {
+ purple_debug_error("sslconn",
+ "Failed to find private key pool x509:user.\n");
+ return FALSE;
+ }
+
+ gsc->certificate = purple_certificate_pool_retrieve(crt_pool, gsc->certificate_id);
+ if (NULL == gsc->certificate) {
+ purple_debug_error("sslconn",
+ "Failed to find certificate with id '%s' in pool x509:user.\n",
+ gsc->certificate_id);
+ return FALSE;
+ }
+
+ data = g_new0(ssl_connect_cb_data, 1);
+ if (NULL == data)
+ return FALSE;
+
+ data->gsc = gsc;
+ data->account = account;
+
+ purple_privatekey_pool_retrieve_request(key_pool, gsc->certificate_id,
+ ok_cb,
+ G_CALLBACK(purple_ssl_connect_cancel_cb),
+ (void*)data);
+
+ return TRUE;
+}
+
PurpleSslConnection *
purple_ssl_connect(PurpleAccount *account, const char *host, int port,
PurpleSslInputFunction func, PurpleSslErrorFunction error_func,
@@ -109,6 +253,18 @@
PurpleSslInputFunction func, PurpleSslErrorFunction error_func,
const char *ssl_cn, void *data)
{
+ return purple_ssl_connect_with_ssl_cn_auth(account, host, port,
+ func, error_func, ssl_cn, NULL, data);
+}
+
+PurpleSslConnection *
+purple_ssl_connect_with_ssl_cn_auth(PurpleAccount *account, const char *host, int port,
+ PurpleSslInputFunction func,
+ PurpleSslErrorFunction error_func,
+ const char *ssl_cn,
+ const char* certificate_id,
+ void *data)
+{
PurpleSslConnection *gsc;
g_return_val_if_fail(host != NULL, NULL);
@@ -130,21 +286,30 @@
gsc->connect_cb_data = data;
gsc->connect_cb = func;
gsc->error_cb = error_func;
+ gsc->certificate_id = certificate_id ? g_strdup(certificate_id) : NULL;
/* TODO: Move this elsewhere */
gsc->verifier = purple_certificate_find_verifier("x509","tls_cached");
- gsc->connect_data = purple_proxy_connect(NULL, account, host, port, purple_ssl_connect_cb, gsc);
+ if (NULL != certificate_id) {
+ /* Caller requested client-side auth, so we need to get the credentials. */
+ if (!purple_ssl_get_credentials(account, gsc, G_CALLBACK(purple_ssl_connect_with_ssl_cn_auth_cb))) {
+ purple_debug_error("sslconn", "Could not retrieve client SSL credentials.\n");
+ purple_ssl_destroy(gsc);
+ return NULL;
+ }
+ }
+ else {
+ gsc->connect_data = purple_proxy_connect(NULL, account, host, port, purple_ssl_connect_cb, gsc);
- if (gsc->connect_data == NULL)
- {
- g_free(gsc->host);
- g_free(gsc);
-
- return NULL;
+ if (gsc->connect_data == NULL)
+ {
+ purple_ssl_destroy(gsc);
+ return NULL;
+ }
}
- return (PurpleSslConnection *)gsc;
+ return gsc;
}
static void
@@ -178,6 +343,12 @@
return _("SSL Handshake Failed");
case PURPLE_SSL_CERTIFICATE_INVALID:
return _("SSL peer presented an invalid certificate");
+ case PURPLE_SSL_PRIVATEKEY_NOT_FOUND:
+ return _("Private key was not found or had invalid password.");
+ case PURPLE_SSL_PRIVATEKEY_CANCELED:
+ return _("Request for private key password was canceled.");
+ case PURPLE_SSL_PRIVATEKEY_BAD_PASSWORD:
+ return _("Invalid password for private key.");
default:
purple_debug_warning("sslconn", "Unknown SSL error code %d\n", error);
return _("Unknown SSL error");
@@ -200,11 +371,50 @@
const char *host,
void *data)
{
+
+ return purple_ssl_connect_with_host_fd_auth(
+ account, fd,
+ func, error_func, host,
+ NULL, data);
+}
+
+static void
+purple_ssl_connect_with_host_fd_auth_cb(PurplePrivateKey *key, void *data)
+{
+ PurpleSslOps *ops;
+ PurpleSslConnection *gsc = ((ssl_connect_cb_data*)data)->gsc;
+
+ /* If key is null either it wasn't found or the password was bad.
+ * We can't tell which as of now.
+ */
+ if (NULL == key) {
+ purple_debug_error("sslconn", "Failed to get private key from pool.\n");
+ if (NULL != gsc->error_cb)
+ gsc->error_cb(gsc, PURPLE_SSL_PRIVATEKEY_NOT_FOUND, gsc->connect_cb_data);
+ purple_ssl_destroy(gsc);
+ return;
+ }
+
+ gsc->key = key;
+ g_free(data);
+
+ ops = purple_ssl_get_ops();
+ ops->connectfunc(gsc);
+}
+
+PurpleSslConnection *
+purple_ssl_connect_with_host_fd_auth(PurpleAccount *account, int fd,
+ PurpleSslInputFunction func,
+ PurpleSslErrorFunction error_func,
+ const char *host,
+ const char* certificate_id,
+ void *data)
+{
PurpleSslConnection *gsc;
PurpleSslOps *ops;
- g_return_val_if_fail(fd != -1, NULL);
- g_return_val_if_fail(func != NULL, NULL);
+ g_return_val_if_fail(fd != -1, NULL);
+ g_return_val_if_fail(func != NULL, NULL);
g_return_val_if_fail(purple_ssl_is_supported(), NULL);
if (!_ssl_initialized)
@@ -219,15 +429,25 @@
gsc->connect_cb = func;
gsc->error_cb = error_func;
gsc->fd = fd;
- if(host)
- gsc->host = g_strdup(host);
+ gsc->host = host ? g_strdup(host) : NULL;
+ gsc->certificate_id = certificate_id ? g_strdup(certificate_id) : NULL;
/* TODO: Move this elsewhere */
gsc->verifier = purple_certificate_find_verifier("x509","tls_cached");
- ops = purple_ssl_get_ops();
- ops->connectfunc(gsc);
+ if (NULL != certificate_id) {
+ /* Caller requested client-side auth, so we need to get the credentials. */
+ if (!purple_ssl_get_credentials(account, gsc, G_CALLBACK(purple_ssl_connect_with_host_fd_auth_cb))) {
+ purple_debug_error("sslconn", "Could not retrieve client SSL credentials.\n");
+ purple_ssl_destroy(gsc);
+ return NULL;
+ }
+ }
+ else {
+ ops = purple_ssl_get_ops();
+ ops->connectfunc(gsc);
+ }
return (PurpleSslConnection *)gsc;
}
@@ -254,8 +474,7 @@
if (gsc->fd >= 0)
close(gsc->fd);
- g_free(gsc->host);
- g_free(gsc);
+ purple_ssl_destroy(gsc);
}
size_t
@@ -295,6 +514,29 @@
return (ops->get_peer_certificates)(gsc);
}
+const char*
+purple_ssl_get_client_certificate_id(PurpleSslConnection *gsc)
+{
+ g_return_val_if_fail(gsc != NULL, NULL);
+
+ return gsc->certificate_id;
+}
+
+/*
+gboolean
+purple_ssl_set_client_auth(PurpleSslConnection *gsc, PurpleCertificate *crt, PurplePrivateKey *key)
+{
+ PurpleSslOps *ops;
+
+ g_return_val_if_fail(gsc != NULL, FALSE);
+ g_return_val_if_fail(crt != NULL, FALSE);
+ g_return_val_if_fail(key != NULL, FALSE);
+
+ ops = purple_ssl_get_ops();
+ return (ops->set_client_auth)(gsc, crt, key);
+}
+*/
+
void
purple_ssl_set_ops(PurpleSslOps *ops)
{
--- 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
+
} PurpleSslErrorType;
#include "certificate.h"
+#include "privatekey.h"
#include "proxy.h"
#define PURPLE_SSL_DEFAULT_PORT 443
@@ -46,6 +66,8 @@
PurpleInputCondition);
typedef void (*PurpleSslErrorFunction)(PurpleSslConnection *, PurpleSslErrorType,
gpointer);
+/* TODO: Do we need better parameters here for algorithms, complete certificate chain, etc? */
+typedef gboolean (*PurpleSslGetCredentialsFunction)(PurpleSslConnection*, PurpleCertificate **crt, PurplePrivateKey **key);
struct _PurpleSslConnection
{
@@ -77,6 +99,18 @@
/** Verifier to use in authenticating the peer */
PurpleCertificateVerifier *verifier;
+
+ /** Id of certificate to use for client-side authentication */
+ char* certificate_id;
+
+ /** Certificate to use for client authentication. Must match certificate_id */
+ PurpleCertificate *certificate;
+
+ /** Private key to use for client authentication. Must match certificate_id */
+ PurplePrivateKey *key;
+
+ /** Callback function to get credentials. */
+ PurpleSslGetCredentialsFunction get_credentials_cb;
};
/**
@@ -211,6 +245,37 @@
const char *ssl_host,
void *data);
+/**
+ * Makes a SSL connection to the specified host and port, using the separate
+ * name to verify with the certificate. Client-side authentication may be
+ * enabled by setting the cred_func which will retrieve a PurpleCertificate
+ * and PurplePrivateKey used to authenticate the client to the server.
+ * The caller should keep track of the
+ * returned value and use it to cancel the connection, if needed.
+ *
+ * @param account The account making the connection.
+ * @param host The destination host.
+ * @param port The destination port.
+ * @param func The SSL input handler function.
+ * @param error_func The SSL error handler function. This function
+ * should <strong>NOT</strong> call purple_ssl_close(). In
+ * the event of an error the #PurpleSslConnection will be
+ * destroyed for you.
+ * @param certificate_id Id of the certificate and private key for client-side
+ * authentication. NULL for no auth.
+ * @param ssl_host The hostname of the other peer (to verify the CN)
+ * @param data User-defined data.
+ *
+ * @return The SSL connection handle.
+ * @since ?.?.?
+ */
+PurpleSslConnection *
+purple_ssl_connect_with_ssl_cn_auth(PurpleAccount *account, const char *host, int port,
+ PurpleSslInputFunction func, PurpleSslErrorFunction error_func,
+ const char *ssl_cn,
+ const char* certificate_id,
+ void *data);
+
#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_SSLCONN_C_)
/**
* Makes a SSL connection using an already open file descriptor.
@@ -252,6 +317,34 @@
void *data);
/**
+ * Makes a SSL connection using an already open file descriptor.
+ * Client-side authentication may be
+ * enabled by setting the cred_func which will retrieve a PurpleCertificate
+ * and PurplePrivateKey used to authenticate the client to the server.
+ *
+ * @param account The account making the connection.
+ * @param fd The file descriptor.
+ * @param func The SSL input handler function.
+ * @param error_func The SSL error handler function.
+ * @param certificate_id Id of the certificate and private key for client-side
+ * authentication. NULL for no auth.
+ * @param host The hostname of the other peer (to verify the CN)
+ * @param data User-defined data.
+ *
+ * @return The SSL connection handle.
+ *
+ * @since ?.?.?
+ *
+ * TODO: Do we really need to pass certificate_id here? Should we put it in account?
+ */
+PurpleSslConnection *
+purple_ssl_connect_with_host_fd_auth(PurpleAccount *account, int fd,
+ PurpleSslInputFunction func,
+ PurpleSslErrorFunction error_func,
+ const char *host,
+ const char* certificate_id,
+ void *data);
+/**
* Adds an input watcher for the specified SSL connection.
* Once the SSL handshake is complete, use this to watch for actual data across it.
*
@@ -303,6 +396,14 @@
*/
GList * purple_ssl_get_peer_certificates(PurpleSslConnection *gsc);
+/**
+ * Get the id the of certificate to use for client-side SSL/TLS authentication.
+ *
+ * @param gsc The SSL connection handle
+ *
+ * @return .
+ */
+const char* purple_ssl_get_client_certificate_id(PurpleSslConnection *gsc);
/*@}*/
/**************************************************************************/
--- 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_USERINFO,
PURPLE_SUBTYPE_STORED_IMAGE,
PURPLE_SUBTYPE_CERTIFICATEPOOL,
+ PURPLE_SUBTYPE_PRIVATEKEYPOOL,
PURPLE_SUBTYPE_CHATBUDDY
} PurpleSubType;
--- 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 @@
const char *str_value;
gboolean bool_value;
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)
return;
- account = dialog->account;
+ protocol_options = purple_account_get_options(dialog->prpl_info);
+ if (protocol_options == NULL)
+ return;
+
/* Main vbox */
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 @@
#include "pidginstock.h"
#include "certificate.h"
+#include "pkcs12.h"
#include "debug.h"
#include "notify.h"
#include "request.h"
@@ -532,6 +533,748 @@
};
/*****************************************************************************
+ * X.509 user certificates management interface *
+ *****************************************************************************/
+
+typedef struct {
+ GtkWidget *mgmt_widget;
+ GtkTreeView *listview;
+ GtkTreeSelection *listselect;
+ GtkWidget *importbutton;
+ GtkWidget *exportbutton;
+ GtkWidget *infobutton;
+ GtkWidget *deletebutton;
+ PurpleCertificatePool *user_crts;
+ PurplePrivateKeyPool *user_keys;
+ PurplePkcs12Scheme *pkcs12;
+} user_mgmt_data;
+
+user_mgmt_data *um_dat = NULL;
+
+/* Columns
+ See http://developer.gnome.org/doc/API/2.0/gtk/TreeWidget.html */
+enum
+{
+ UM_NAME_COLUMN,
+ UM_N_COLUMNS
+};
+
+static void
+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;
+}
+
+static void
+user_mgmt_repopulate_list(void)
+{
+ GtkTreeView *listview = um_dat->listview;
+ PurpleCertificatePool *user_crts;
+ PurplePrivateKeyPool *user_keys;
+ GList *idlist, *l;
+
+ 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) {
+ GtkTreeIter iter;
+
+ 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",
+ (gchar*)l->data);
+ }
+
+ gtk_list_store_append(store, &iter);
+
+ gtk_list_store_set(GTK_LIST_STORE(store), &iter,
+ UM_NAME_COLUMN, l->data,
+ -1);
+ }
+ purple_certificate_pool_destroy_idlist(idlist);
+}
+
+static void
+user_mgmt_mod_cb(PurpleCertificatePool *pool, const gchar *id, gpointer data)
+{
+ g_assert (pool == um_dat->user_crts);
+
+ user_mgmt_repopulate_list();
+}
+
+static void
+user_mgmt_select_chg_cb(GtkTreeSelection *ignored, gpointer data)
+{
+ GtkTreeSelection *select = um_dat->listselect;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ /* 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);
+ } else {
+ /* 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
+ */
+
+typedef struct {
+ PurpleCertificate *crt;
+ PurplePrivateKey *key;
+ char *name;
+} pkcs12_import_data;
+
+static void
+pkcs12_import_data_free(pkcs12_import_data *data)
+{
+ purple_certificate_destroy(data->crt);
+ purple_privatekey_destroy(data->key);
+ g_free(data->name);
+ g_free(data);
+}
+
+static void
+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);
+}
+
+static void
+pkcs12_import_key_password_cancel_cb(pkcs12_import_data *data)
+{
+ pkcs12_import_data_free(data);
+}
+
+static void
+pkcs12_import_name_ok_cb(pkcs12_import_data *data, char* name)
+{
+ g_free(data->name);
+ data->name = g_strdup(name);
+
+ /* TODO: What if we have the same name? */
+ purple_privatekey_pool_store_request(
+ um_dat->user_keys,
+ data->name,
+ data->key,
+ pkcs12_import_key_password_ok_cb,
+ pkcs12_import_key_password_cancel_cb,
+ data);
+}
+
+static void
+pkcs12_import_name_cancel_cb(pkcs12_import_data *data)
+{
+ pkcs12_import_data_free(data);
+}
+
+static void
+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;
+ gboolean result;
+
+ 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);
+
+ /* Did it work? */
+ if (result) {
+ gchar *default_name;
+
+ /* 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
+ deletion */
+ /* TODO: Display some more information on the certificate? */
+
+ data = g_new0(pkcs12_import_data, 1);
+ data->crt = crt;
+ data->key = key;
+ data->name = default_name;
+
+ /* TODO: Enable custom cert name dialog */
+ purple_request_input(um_dat,
+ _("PKCS12 Import"),
+ _("Specify a name"),
+ _("Type the name for the imported certificate and key."),
+ default_name,
+ FALSE, /* Not multiline */
+ FALSE, /* Not masked? */
+ NULL, /* No hints? */
+ _("OK"),
+ G_CALLBACK(pkcs12_import_name_ok_cb),
+ _("Cancel"),
+ G_CALLBACK(pkcs12_import_name_cancel_cb),
+ NULL, NULL, NULL, /* No account/who/conv*/
+ data);
+ } else {
+ /* Errors! Oh no! */
+ /* TODO: Perhaps find a way to be specific about what just
+ went wrong? */
+ gchar * secondary;
+
+ 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"),
+ secondary,
+ g_free,
+ secondary);
+ }
+}
+
+static void
+pkcs12_import_password_ok_cb(char *filename, PurpleRequestFields *fields)
+{
+ const char *entry;
+ gboolean remember;
+
+ entry = purple_request_fields_get_string(fields, "password");
+/* remember = purple_request_fields_get_bool(fields, "remember");*/
+
+ if (!entry || !*entry)
+ {
+ purple_notify_error(um_dat, NULL, _("A password is required to access a PKCS12 file."), NULL);
+ g_free(filename);
+ return;
+ }
+/*
+ if(remember)
+ 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);
+ g_free(filename);
+}
+
+static void
+pkcs12_import_password_cancel_cb( char* filename)
+{
+ g_free(filename);
+}
+
+static void
+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(
+ um_dat,
+ filename,
+ G_CALLBACK(pkcs12_import_password_ok_cb),
+ G_CALLBACK(pkcs12_import_password_cancel_cb),
+ (void*)filename);
+}
+
+static void
+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"),
+ "certificate.p12",
+ 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
+ */
+
+typedef struct {
+ PurpleCertificate *crt;
+ PurplePrivateKey *key;
+ char* filename;
+ char* id;
+} pkcs12_export_data;
+
+static void
+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);
+ g_free(data->id);
+ g_free(data);
+
+}
+
+static void
+pkcs12_export_password_ok_cb(pkcs12_export_data *data, PurpleRequestFields *fields)
+{
+ const char *entry;
+ gboolean remember;
+
+ 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");*/
+
+ if (!entry || !*entry)
+ {
+ purple_notify_error(um_dat, NULL, _("A password is required to protect the PKCS12 file."), NULL);
+ pkcs12_export_data_free(data);
+ return;
+ }
+/*
+ if(remember)
+ 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)) {
+ /* Errors! Oh no! */
+ /* TODO: Perhaps find a way to be specific about what just
+ went wrong? */
+ gchar * secondary;
+
+ 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"),
+ secondary,
+ NULL,
+ NULL);
+ }
+
+ pkcs12_export_data_free(data);
+}
+
+static void
+pkcs12_export_password_cancel_cb(pkcs12_export_data* data)
+{
+ pkcs12_export_data_free(data);
+}
+
+static void
+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(
+ um_dat,
+ data->filename,
+ G_CALLBACK(pkcs12_export_password_ok_cb),
+ G_CALLBACK(pkcs12_export_password_cancel_cb),
+ data);
+}
+
+static void
+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);
+}
+
+static void
+pkcs12_get_key_password_ok_cb(PurplePrivateKey *key, pkcs12_export_data *data)
+{
+ char* secondary;
+
+ purple_debug_info("certmgr/user_mgmt/pkcs12_export", "key password ok cb\n");
+ if (NULL == key) {
+ purple_debug_error("gtkcertmgr/user_mgmt",
+ "Id %s was not in the user key pool or bad password\n",
+ data->id);
+
+ secondary = g_strdup_printf(
+ _("The private key named \"%s\" was not found or the key's password was incorrect."),
+ data->id);
+
+ purple_notify_message(NULL, PURPLE_NOTIFY_MSG_ERROR,
+ _("PKCS12 Export Error"),
+ _("PKCS12 certificate & key export failed"),
+ secondary,
+ NULL,
+ NULL);
+
+ pkcs12_export_data_free(data);
+
+ return;
+ }
+
+ data->key = key;
+
+ purple_request_file(um_dat,
+ _("PKCS12 Certificate & Key Export"),
+ "certificate.p12",
+ 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 */
+}
+
+static void
+pkcs12_get_key_password_cancel_cb(pkcs12_export_data *data)
+{
+ pkcs12_export_data_free(data);
+}
+
+static void
+user_mgmt_export_cb(GtkWidget *button, void* stuff)
+{
+ PurpleCertificate *crt = NULL;
+ GtkTreeSelection *select = um_dat->listselect;
+ GtkTreeIter iter;
+ GtkTreeModel *model = NULL;
+ gchar *id = 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");
+ return;
+ }
+
+ /* 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);
+
+ if (NULL == crt) {
+ purple_debug_error("gtkcertmgr/user_mgmt",
+ "Id %s was not in the user cert pool?!\n",
+ id);
+ g_free(id);
+ return;
+ }
+
+ /* stuff we will need in our callbacks */
+ data = g_new0(pkcs12_export_data, 1);
+ data->crt = crt;
+ data->id = id;
+
+ purple_privatekey_pool_retrieve_request(
+ um_dat->user_keys,
+ id,
+ G_CALLBACK(pkcs12_get_key_password_ok_cb),
+ G_CALLBACK(pkcs12_get_key_password_cancel_cb),
+ data);
+}
+
+/**********************************************************
+ * Display certificate and key info
+ */
+
+static void
+user_mgmt_info_cb(GtkWidget *button, gpointer data)
+{
+ GtkTreeSelection *select = um_dat->listselect;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gchar *id;
+ 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");
+ return;
+ }
+
+ /* 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);
+ g_return_if_fail(crt);
+
+ /* Fire the notification */
+ purple_certificate_display_x509(crt);
+
+ g_free(id);
+ purple_certificate_destroy(crt);
+}
+
+/***********************************************************
+ * Delete a certificate and key
+ */
+
+static void
+user_mgmt_delete_confirm_cb(gchar *id, gint choice)
+{
+ if (1 == 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",
+ id);
+ };
+ 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",
+ id);
+ }
+ }
+
+ g_free(id);
+}
+
+static void
+user_mgmt_delete_cb(GtkWidget *button, gpointer data)
+{
+ GtkTreeSelection *select = um_dat->listselect;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ /* See if things are selected */
+ if (gtk_tree_selection_get_selected(select, &model, &iter)) {
+
+ gchar *id;
+ gchar *primary;
+
+ /* 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 */
+ NULL, NULL, NULL,
+ id, /* id ownership passed to callback */
+ user_mgmt_delete_confirm_cb,
+ user_mgmt_delete_confirm_cb );
+
+ g_free(primary);
+
+ } else {
+ purple_debug_warning("gtkcertmgr/user_mgmt",
+ "Delete clicked with no selection?\n");
+ return;
+ }
+}
+
+/**********************************************************
+ * Setup the user certificate & key management tab
+ */
+
+static GtkWidget *
+user_mgmt_build(void)
+{
+ GtkWidget *bbox;
+ GtkListStore *store;
+ GtkWidget *sw;
+
+ /* This block of variables will end up in um_dat */
+ GtkTreeView *listview;
+ GtkTreeSelection *select;
+ GtkWidget *importbutton;
+ GtkWidget *exportbutton;
+ GtkWidget *infobutton;
+ 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 */
+ PIDGIN_HIG_BOX_SPACE);
+ gtk_container_set_border_width(GTK_CONTAINER(mgmt_widget),
+ PIDGIN_HIG_BOX_SPACE);
+ gtk_widget_show(mgmt_widget);
+
+ /* Ensure that everything gets cleaned up when the dialog box
+ is closed */
+ g_signal_connect(G_OBJECT(mgmt_widget), "destroy",
+ G_CALLBACK(user_mgmt_destroy), NULL);
+
+ /* Scrolled window */
+ 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 */
+ 0);
+ gtk_widget_show(GTK_WIDGET(sw));
+
+ /* List view */
+ 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(
+ _("Name"),
+ renderer,
+ "text", UM_NAME_COLUMN,
+ NULL);
+ 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 */
+ 0);
+ gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
+ gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_START);
+ gtk_widget_show(bbox);
+
+ /* Import button */
+ /* 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);
+
+
+ /* Export button */
+ /* 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);
+
+
+ /* Info button */
+ 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);
+
+
+ /* Delete button */
+ 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),
+ NULL);
+ purple_signal_connect(um_dat->user_crts, "certificate-deleted",
+ um_dat, PURPLE_CALLBACK(user_mgmt_mod_cb),
+ NULL);
+
+ return mgmt_widget;
+}
+
+const PidginCertificateManager user_mgmt = {
+ user_mgmt_build, /* Widget creation function */
+ N_("Your Certificates")
+};
+
+/*****************************************************************************
* GTK+ main certificate manager *
*****************************************************************************/
typedef struct
@@ -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),
+ (user_mgmt.build)(),
+ gtk_label_new(_(user_mgmt.label)) );
gtk_widget_show(win);
}