summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWilliam Ehlhardt <williamehlhardt@gmail.com>2007-08-20 02:49:47 +0000
committerWilliam Ehlhardt <williamehlhardt@gmail.com>2007-08-20 02:49:47 +0000
commit02b7780d3f99e7c044ab528f8fb5b61233207e70 (patch)
tree3d9df170f78aaad5dea3a6c7562afeafcc08e608
parentcc8e8cc9130eaefc0b5dc7dbccdb832c4d3832c4 (diff)
parent99c074f294563b04ac7f459d496a325d069f1b66 (diff)
downloadpidgin-02b7780d3f99e7c044ab528f8fb5b61233207e70.tar.gz
propagate from branch 'im.pidgin.pidgin' (head f0024ed911ca108b04d01d6f658ea401017e26bb)
to branch 'im.pidgin.soc.2007.certmgr' (head b8dff22498f6660bb361a2ea0115791d34b59390)
-rw-r--r--configure.ac1
-rw-r--r--doc/Makefile.am2
-rw-r--r--doc/SIGNAL-HOWTO.dox137
-rw-r--r--doc/certificate-signals.dox31
-rw-r--r--libpurple/Makefile.am2
-rw-r--r--libpurple/certificate.c1821
-rw-r--r--libpurple/certificate.h779
-rw-r--r--libpurple/core.c3
-rw-r--r--libpurple/plugins/ssl/ssl-gnutls.c708
-rw-r--r--libpurple/plugins/ssl/ssl-nss.c287
-rw-r--r--libpurple/prefs.h4
-rw-r--r--libpurple/protocols/irc/irc.c9
-rw-r--r--libpurple/protocols/jabber/jabber.c17
-rw-r--r--libpurple/protocols/jabber/jabber.h3
-rw-r--r--libpurple/sslconn.c56
-rw-r--r--libpurple/sslconn.h59
-rw-r--r--libpurple/value.h3
-rw-r--r--pidgin/Makefile.am2
-rw-r--r--pidgin/gtkblist.c2
-rw-r--r--pidgin/gtkcertmgr.c685
-rw-r--r--pidgin/gtkcertmgr.h62
-rw-r--r--share/Makefile.am2
-rw-r--r--share/ca-certs/Equifax_Secure_CA.pem20
-rw-r--r--share/ca-certs/Makefile.am8
-rw-r--r--share/ca-certs/Verisign_RSA_Secure_Server_CA.pem15
25 files changed, 4682 insertions, 36 deletions
diff --git a/configure.ac b/configure.ac
index 315ec7770e..0ce8f34525 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2202,6 +2202,7 @@ AC_OUTPUT([Makefile
libpurple/version.h
share/Makefile
share/sounds/Makefile
+ share/ca-certs/Makefile
finch/Makefile
finch/libgnt/Makefile
finch/libgnt/gnt.pc
diff --git a/doc/Makefile.am b/doc/Makefile.am
index c44993b33f..ccabc2cd39 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -3,11 +3,13 @@ man_MANS = pidgin.1 finch.1
EXTRA_DIST = \
C-HOWTO.dox \
PERL-HOWTO.dox \
+ SIGNAL-HOWTO.dox \
TCL-HOWTO.dox \
TracFooter.html \
TracHeader.html \
account-signals.dox \
blist-signals.dox \
+ certificate-signals.dox \
cipher-signals.dox \
connection-signals.dox \
conversation-signals.dox \
diff --git a/doc/SIGNAL-HOWTO.dox b/doc/SIGNAL-HOWTO.dox
new file mode 100644
index 0000000000..15fda66ec3
--- /dev/null
+++ b/doc/SIGNAL-HOWTO.dox
@@ -0,0 +1,137 @@
+/** @page signal-howto Signals HOWTO
+
+ @section Introduction
+ The libpurple signals interface is used for general event notification, such
+ as plugins being loaded or unloaded, allowing the GUI frontend to respond
+ appropriately to changing internal data. Unfortunately, its use is not at all
+ obvious from the information in the header files. This document uses code
+ snippets from the Pidgin/libpurple plugin systems to illustrate the proper
+ use of signals.
+
+ @section overview Overview of Signals
+ Signals in libpurple are very similar to those in GTK+. When certain events
+ happen, a named signal is "emitted" from a certain object. Emitting the
+ signal triggers a series of callbacks that have been "connected" to that
+ signal for that object. These callbacks take appropriate action in response
+ to the signal.
+
+ @section registering_signal Registering a Signal
+ The first step of using a signal is registering it with libpurple so that
+ callbacks may be connected to it. This is done using purple_signal_register()
+ Here is a slightly modified example from @c purple_plugins_init in
+ @c libpurple/plugin.c :
+
+ @code
+ purple_signal_register( purple_plugins_get_handle(), /* Instance */
+ "plugin-load", /* Signal name */
+ purple_marshal_VOID__POINTER,/* Marshal function */
+ NULL, /* Callback return value type */
+ 1, /* Number of callback arguments (not including void *data) */
+ purple_value_new(PURPLE_TYPE_SUBTYPE,PURPLE_SUBTYPE_PLUGIN) /* Type of first callback argument */
+ );
+ @endcode
+
+ @subsection Instance
+ A reference to the object from which this signal is emitted, and to which
+ potential callbacks should be connected. In this case, it will be the entire
+ plugin module emitting the signal.
+
+ @subsection signalname Signal Name
+ Unique identifier for the signal itself.
+
+ @subsection therest Callback function definition
+ The rest of the arguments specify the form of the callback function.
+
+ @subsubsection marshalfunc Marshal Function
+ @c purple_marshal_VOID__POINTER represents the callback function prototype,
+ not including a "data" argument, explained later. The form is
+ @c purple_marshal_RETURNVALUETYPE__ARG1TYPE_ARG2TYPE_ETC. See signals.h for
+ more possible types.
+
+ In this case, the callback will have the form
+ @code
+ void cb(void *arg1, void *data)
+ @endcode
+
+ If @c purple_marshal_BOOLEAN__POINTER_POINTER_POINTER were specified, it
+ would be:
+ @code
+ gboolean cb(void *arg1, void *arg2, void *arg3, void *data)
+ @endcode
+
+ The @c void @c *data argument at the end of each callback function
+ provides the data argument given to purple_signal_connect() .
+
+ @subsubsection cb_ret_type Callback return value type
+ In our case, this is NULL, meaning "returns void".
+ @todo This could be described better.
+
+ @subsubsection num_args Number of arguments
+ The number of arguments (not including @c data ) that the callback function
+ will take.
+
+ @subsubsection type_arg Type of argument
+ @c purple_value_new(PURPLE_TYPE_SUBTYPE,PURPLE_SUBTYPE_PLUGIN) specifies that
+ the first argument given to the callback will be a @c PurplePlugin* . You
+ will need as many "type of argument" arguments to purple_signal_register() as
+ you specified in "Number of arguments" above.
+
+ @todo Describe this more.
+
+ @See value.h
+
+ @section connect Connecting to the signal
+ Once the signal is registered, you can connect callbacks to it. First, you
+ must define a callback function, such as this one from gtkplugin.c :
+ @code
+static void plugin_load_cb(PurplePlugin *plugin, gpointer data)
+{
+ GtkTreeView *view = (GtkTreeView *)data;
+ plugin_loading_common(plugin, view, TRUE);
+}
+ @endcode
+ Note that the callback function prototype matches that specified in the call
+ to purple_signal_register() above.
+
+ Once the callback function is defined, you can connect it to the signal.
+ Again from gtkplugin.c , in @c pidgin_plugin_dialog_show() :
+ @code
+ purple_signal_connect(purple_plugins_get_handle(), "plugin-load", /* What to connect to */
+ plugin_dialog, /* Object receiving the signal */
+ PURPLE_CALLBACK(plugin_load_cb), /* Callback function */
+ event_view, /* Data to pass to the callback function
+ );
+ @endcode
+
+ The first two arguments ("What to connect to") specify the object emitting
+ the signal (the plugin module) and what signal to listen for ("plugin-load").
+
+ The object receiving the signal is @c plugin_dialog , the Pidgin plugins
+ dialog. When @c plugin_dialog is deleted, then
+ @c purple_signals_disconnect_by_handle(plugin_dialog) should be called to
+ remove all signal connections it is associated with.
+
+ The callback function is given using a helper macro, and finally the
+ @c data argument to be passed to @c plugin_load_cb is given as @c event_view,
+ a pointer to the GTK widget that @c plugin_load_cb needs to update.
+
+ @section emit-signal Emitting a signal
+ Connecting callbacks to signals is all well and good, but how do you "fire"
+ the signal and trigger the callback? At some point, you must "emit" the
+ signal, which immediately calls all connected callbacks.
+
+ As seen in @c purple_plugin_load() in plugin.c :
+ @code
+ purple_signal_emit(purple_plugins_get_handle(), "plugin-load", plugin);
+ @endcode
+ This causes the signal "plugin-load" to be emitted from the plugin module
+ (given by @c purple_plugins_get_handle() ), with the newly loaded plugin as
+ the argument to pass to any registered callback functions.
+
+ In our example, @c plugin_load_cb is called immediately as
+ @code
+ plugin_load_cb(plugin, event_view);
+ @endcode
+ and does whatever it does.
+
+ */
diff --git a/doc/certificate-signals.dox b/doc/certificate-signals.dox
new file mode 100644
index 0000000000..4631f302eb
--- /dev/null
+++ b/doc/certificate-signals.dox
@@ -0,0 +1,31 @@
+/** @page certificate-signals Certificate Signals
+
+ @signals
+ @signal certificate-stored
+ @signal certificate-deleted
+ @endsignals
+
+ <hr>
+
+ @signaldef certificate-stored
+ @signalproto
+void (*certificate_stored)(PurpleCertificatePool *pool, const gchar *id, gpointer data);
+ @endsignalproto
+ @signaldesc
+ Emitted when a pool stores a certificate. Connect to the pool instance.
+ @param pool Pool the certificate has been stored into
+ @param id Key the certificate was stored under
+ @endsignaldef
+
+ @signaldef certificate-deleted
+ @signalproto
+void (*certificate_deleted)(PurpleCertificatePool *pool, const gchar *id, gpointer data);
+ @endsignalproto
+ @signaldesc
+ Emitted when a pool deletes a certificate. Connect to the pool instance.
+ @param pool Pool the certificate was deleted from
+ @param id Key that was deleted
+ @endsignaldef
+
+ */
+// vim: syntax=c tw=75 et
diff --git a/libpurple/Makefile.am b/libpurple/Makefile.am
index 36ddf742af..8e6548ffd1 100644
--- a/libpurple/Makefile.am
+++ b/libpurple/Makefile.am
@@ -36,6 +36,7 @@ purple_coresources = \
accountopt.c \
blist.c \
buddyicon.c \
+ certificate.c \
cipher.c \
circbuffer.c \
cmds.c \
@@ -85,6 +86,7 @@ purple_coreheaders = \
accountopt.h \
blist.h \
buddyicon.h \
+ certificate.h \
cipher.h \
circbuffer.h \
cmds.h \
diff --git a/libpurple/certificate.c b/libpurple/certificate.c
new file mode 100644
index 0000000000..bd916b53d6
--- /dev/null
+++ b/libpurple/certificate.c
@@ -0,0 +1,1821 @@
+/**
+ * @file certificate.c Public-Key Certificate API
+ * @ingroup core
+ */
+
+/*
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <glib.h>
+
+#include "certificate.h"
+#include "debug.h"
+#include "internal.h"
+#include "request.h"
+#include "signals.h"
+#include "util.h"
+
+/** List holding pointers to all registered certificate schemes */
+static GList *cert_schemes = NULL;
+/** List of registered Verifiers */
+static GList *cert_verifiers = NULL;
+/** List of registered Pools */
+static GList *cert_pools = NULL;
+
+void
+purple_certificate_verify (PurpleCertificateVerifier *verifier,
+ const gchar *subject_name, GList *cert_chain,
+ PurpleCertificateVerifiedCallback cb,
+ gpointer cb_data)
+{
+ PurpleCertificateVerificationRequest *vrq;
+ PurpleCertificateScheme *scheme;
+
+ g_return_if_fail(subject_name != NULL);
+ /* If you don't have a cert to check, why are you requesting that it
+ be verified? */
+ g_return_if_fail(cert_chain != NULL);
+ g_return_if_fail(cb != NULL);
+
+ /* Look up the CertificateScheme */
+ scheme = purple_certificate_find_scheme(verifier->scheme_name);
+ g_return_if_fail(scheme);
+
+ /* Check that at least the first cert in the chain matches the
+ Verifier scheme */
+ g_return_if_fail(scheme ==
+ ((PurpleCertificate *) (cert_chain->data))->scheme);
+
+ /* Construct and fill in the request fields */
+ vrq = g_new0(PurpleCertificateVerificationRequest, 1);
+ vrq->verifier = verifier;
+ vrq->scheme = scheme;
+ vrq->subject_name = g_strdup(subject_name);
+ vrq->cert_chain = purple_certificate_copy_list(cert_chain);
+ vrq->cb = cb;
+ vrq->cb_data = cb_data;
+
+ /* Initiate verification */
+ (verifier->start_verification)(vrq);
+}
+
+void
+purple_certificate_verify_complete(PurpleCertificateVerificationRequest *vrq,
+ PurpleCertificateVerificationStatus st)
+{
+ PurpleCertificateVerifier *vr;
+
+ g_return_if_fail(vrq);
+
+ /* Pass the results on to the request's callback */
+ (vrq->cb)(st, vrq->cb_data);
+
+ /* And now to eliminate the request */
+ /* Fetch the Verifier responsible... */
+ vr = vrq->verifier;
+ /* ...and order it to KILL */
+ (vr->destroy_request)(vrq);
+
+ /* Now the internals have been cleaned up, so clean up the libpurple-
+ created elements */
+ g_free(vrq->subject_name);
+ purple_certificate_destroy_list(vrq->cert_chain);
+
+ /* A structure born
+ * to much ado
+ * and with so much within.
+ * It reaches now
+ * its quiet end. */
+ g_free(vrq);
+}
+
+
+PurpleCertificate *
+purple_certificate_copy(PurpleCertificate *crt)
+{
+ g_return_val_if_fail(crt, NULL);
+ g_return_val_if_fail(crt->scheme, NULL);
+ g_return_val_if_fail(crt->scheme->copy_certificate, NULL);
+
+ return (crt->scheme->copy_certificate)(crt);
+}
+
+GList *
+purple_certificate_copy_list(GList *crt_list)
+{
+ GList *new, *l;
+
+ /* First, make a shallow copy of the list */
+ new = g_list_copy(crt_list);
+
+ /* Now go through and actually duplicate each certificate */
+ for (l = new; l; l = l->next) {
+ l->data = purple_certificate_copy(l->data);
+ }
+
+ return new;
+}
+
+void
+purple_certificate_destroy (PurpleCertificate *crt)
+{
+ PurpleCertificateScheme *scheme;
+
+ if (NULL == crt) return;
+
+ scheme = crt->scheme;
+
+ (scheme->destroy_certificate)(crt);
+}
+
+void
+purple_certificate_destroy_list (GList * crt_list)
+{
+ PurpleCertificate *crt;
+ GList *l;
+
+ for (l=crt_list; l; l = l->next) {
+ crt = (PurpleCertificate *) l->data;
+ purple_certificate_destroy(crt);
+ }
+
+ g_list_free(crt_list);
+}
+
+gboolean
+purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer)
+{
+ PurpleCertificateScheme *scheme;
+
+ g_return_val_if_fail(crt, FALSE);
+ g_return_val_if_fail(issuer, FALSE);
+
+ scheme = crt->scheme;
+ g_return_val_if_fail(scheme, FALSE);
+ /* We can't compare two certs of unrelated schemes, obviously */
+ g_return_val_if_fail(issuer->scheme == scheme, FALSE);
+
+ return (scheme->signed_by)(crt, issuer);
+}
+
+gboolean
+purple_certificate_check_signature_chain(GList *chain)
+{
+ GList *cur;
+ PurpleCertificate *crt, *issuer;
+ gchar *uid;
+
+ g_return_val_if_fail(chain, FALSE);
+
+ uid = purple_certificate_get_unique_id((PurpleCertificate *) chain->data);
+ purple_debug_info("certificate",
+ "Checking signature chain for uid=%s\n",
+ uid);
+ g_free(uid);
+
+ /* If this is a single-certificate chain, say that it is valid */
+ if (chain->next == NULL) {
+ purple_debug_info("certificate",
+ "...Singleton. We'll say it's valid.\n");
+ return TRUE;
+ }
+
+ /* Load crt with the first certificate */
+ crt = (PurpleCertificate *)(chain->data);
+ /* And start with the second certificate in the chain */
+ for ( cur = chain->next; cur; cur = cur->next ) {
+
+ issuer = (PurpleCertificate *)(cur->data);
+
+ /* Check the signature for this link */
+ if (! purple_certificate_signed_by(crt, issuer) ) {
+ uid = purple_certificate_get_unique_id(issuer);
+ purple_debug_info("certificate",
+ "...Bad or missing signature by %s\nChain is INVALID\n",
+ uid);
+ g_free(uid);
+
+ return FALSE;
+ }
+
+ uid = purple_certificate_get_unique_id(issuer);
+ purple_debug_info("certificate",
+ "...Good signature by %s\n",
+ uid);
+ g_free(uid);
+
+ /* The issuer is now the next crt whose signature is to be
+ checked */
+ crt = issuer;
+ }
+
+ /* If control reaches this point, the chain is valid */
+ purple_debug_info("certificate", "Chain is VALID\n");
+ return TRUE;
+}
+
+PurpleCertificate *
+purple_certificate_import(PurpleCertificateScheme *scheme, const gchar *filename)
+{
+ g_return_val_if_fail(scheme, NULL);
+ g_return_val_if_fail(scheme->import_certificate, NULL);
+ g_return_val_if_fail(filename, NULL);
+
+ return (scheme->import_certificate)(filename);
+}
+
+gboolean
+purple_certificate_export(const gchar *filename, PurpleCertificate *crt)
+{
+ PurpleCertificateScheme *scheme;
+
+ g_return_val_if_fail(filename, FALSE);
+ g_return_val_if_fail(crt, FALSE);
+ g_return_val_if_fail(crt->scheme, FALSE);
+
+ scheme = crt->scheme;
+ g_return_val_if_fail(scheme->export_certificate, FALSE);
+
+ return (scheme->export_certificate)(filename, crt);
+}
+
+GByteArray *
+purple_certificate_get_fingerprint_sha1(PurpleCertificate *crt)
+{
+ PurpleCertificateScheme *scheme;
+ GByteArray *fpr;
+
+ g_return_val_if_fail(crt, NULL);
+ g_return_val_if_fail(crt->scheme, NULL);
+
+ scheme = crt->scheme;
+
+ g_return_val_if_fail(scheme->get_fingerprint_sha1, NULL);
+
+ fpr = (scheme->get_fingerprint_sha1)(crt);
+
+ return fpr;
+}
+
+gchar *
+purple_certificate_get_unique_id(PurpleCertificate *crt)
+{
+ g_return_val_if_fail(crt, NULL);
+ g_return_val_if_fail(crt->scheme, NULL);
+ g_return_val_if_fail(crt->scheme->get_unique_id, NULL);
+
+ return (crt->scheme->get_unique_id)(crt);
+}
+
+gchar *
+purple_certificate_get_issuer_unique_id(PurpleCertificate *crt)
+{
+ g_return_val_if_fail(crt, NULL);
+ g_return_val_if_fail(crt->scheme, NULL);
+ g_return_val_if_fail(crt->scheme->get_issuer_unique_id, NULL);
+
+ return (crt->scheme->get_issuer_unique_id)(crt);
+}
+
+gchar *
+purple_certificate_get_subject_name(PurpleCertificate *crt)
+{
+ PurpleCertificateScheme *scheme;
+ gchar *subject_name;
+
+ g_return_val_if_fail(crt, NULL);
+ g_return_val_if_fail(crt->scheme, NULL);
+
+ scheme = crt->scheme;
+
+ g_return_val_if_fail(scheme->get_subject_name, NULL);
+
+ subject_name = (scheme->get_subject_name)(crt);
+
+ return subject_name;
+}
+
+gboolean
+purple_certificate_check_subject_name(PurpleCertificate *crt, const gchar *name)
+{
+ PurpleCertificateScheme *scheme;
+
+ g_return_val_if_fail(crt, FALSE);
+ g_return_val_if_fail(crt->scheme, FALSE);
+ g_return_val_if_fail(name, FALSE);
+
+ scheme = crt->scheme;
+
+ /* TODO: Instead of failing, maybe use get_subject_name and strcmp? */
+ g_return_val_if_fail(scheme->check_subject_name, FALSE);
+
+ return (scheme->check_subject_name)(crt, name);
+}
+
+gboolean
+purple_certificate_get_times(PurpleCertificate *crt, time_t *activation, time_t *expiration)
+{
+ PurpleCertificateScheme *scheme;
+
+ g_return_val_if_fail(crt, FALSE);
+
+ scheme = crt->scheme;
+
+ g_return_val_if_fail(scheme, FALSE);
+
+ /* If both provided references are NULL, what are you doing calling
+ this? */
+ g_return_val_if_fail( (activation != NULL) || (expiration != NULL), FALSE);
+
+ /* Throw the request on down to the certscheme */
+ return (scheme->get_times)(crt, activation, expiration);
+}
+
+
+gchar *
+purple_certificate_pool_mkpath(PurpleCertificatePool *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(),
+ "certificates", /* 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;
+}
+
+gboolean
+purple_certificate_pool_usable(PurpleCertificatePool *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_certificate_find_scheme(pool->scheme_name) == NULL) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+PurpleCertificateScheme *
+purple_certificate_pool_get_scheme(PurpleCertificatePool *pool)
+{
+ g_return_val_if_fail(pool, NULL);
+ g_return_val_if_fail(pool->scheme_name, NULL);
+
+ return purple_certificate_find_scheme(pool->scheme_name);
+}
+
+gboolean
+purple_certificate_pool_contains(PurpleCertificatePool *pool, const gchar *id)
+{
+ g_return_val_if_fail(pool, FALSE);
+ g_return_val_if_fail(id, FALSE);
+ g_return_val_if_fail(pool->cert_in_pool, FALSE);
+
+ return (pool->cert_in_pool)(id);
+}
+
+PurpleCertificate *
+purple_certificate_pool_retrieve(PurpleCertificatePool *pool, const gchar *id)
+{
+ g_return_val_if_fail(pool, NULL);
+ g_return_val_if_fail(id, NULL);
+ g_return_val_if_fail(pool->get_cert, NULL);
+
+ return (pool->get_cert)(id);
+}
+
+gboolean
+purple_certificate_pool_store(PurpleCertificatePool *pool, const gchar *id, PurpleCertificate *crt)
+{
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail(pool, FALSE);
+ g_return_val_if_fail(id, FALSE);
+ g_return_val_if_fail(pool->put_cert, FALSE);
+
+ /* TODO: Should this just be someone else's problem? */
+ /* Whether crt->scheme matches find_scheme(pool->scheme_name) is not
+ relevant... I think... */
+ g_return_val_if_fail(
+ g_ascii_strcasecmp(pool->scheme_name, crt->scheme->name) == 0,
+ FALSE);
+
+ ret = (pool->put_cert)(id, crt);
+
+ /* Signal that the certificate was stored if success*/
+ if (ret) {
+ purple_signal_emit(pool, "certificate-stored",
+ pool, id);
+ }
+
+ return ret;
+}
+
+gboolean
+purple_certificate_pool_delete(PurpleCertificatePool *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_cert, FALSE);
+
+ ret = (pool->delete_cert)(id);
+
+ /* Signal that the certificate was deleted if success */
+ if (ret) {
+ purple_signal_emit(pool, "certificate-deleted",
+ pool, id);
+ }
+
+ return ret;
+}
+
+GList *
+purple_certificate_pool_get_idlist(PurpleCertificatePool *pool)
+{
+ g_return_val_if_fail(pool, NULL);
+ g_return_val_if_fail(pool->get_idlist, NULL);
+
+ return (pool->get_idlist)();
+}
+
+void
+purple_certificate_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 Verifiers, Pools, etc. */
+/****************************************************************************/
+
+static void
+x509_singleuse_verify_cb (PurpleCertificateVerificationRequest *vrq, gint id)
+{
+ g_return_if_fail(vrq);
+
+ purple_debug_info("certificate/x509_singleuse",
+ "VRQ on cert from %s gave %d\n",
+ vrq->subject_name, id);
+
+ /* Signal what happened back to the caller */
+ if (1 == id) {
+ /* Accepted! */
+ purple_certificate_verify_complete(vrq,
+ PURPLE_CERTIFICATE_VALID);
+ } else {
+ /* Not accepted */
+ purple_certificate_verify_complete(vrq,
+ PURPLE_CERTIFICATE_INVALID);
+
+ }
+}
+
+static void
+x509_singleuse_start_verify (PurpleCertificateVerificationRequest *vrq)
+{
+ gchar *sha_asc;
+ GByteArray *sha_bin;
+ gchar *cn;
+ const gchar *cn_match;
+ gchar *primary, *secondary;
+ PurpleCertificate *crt = (PurpleCertificate *) vrq->cert_chain->data;
+
+ /* Pull out the SHA1 checksum */
+ sha_bin = purple_certificate_get_fingerprint_sha1(crt);
+ /* Now decode it for display */
+ sha_asc = purple_base16_encode_chunked(sha_bin->data,
+ sha_bin->len);
+
+ /* Get the cert Common Name */
+ cn = purple_certificate_get_subject_name(crt);
+
+ /* Determine whether the name matches */
+ /* TODO: Worry about strcmp safety? */
+ if (!strcmp(cn, vrq->subject_name)) {
+ cn_match = _("");
+ } else {
+ cn_match = _("(DOES NOT MATCH)");
+ }
+
+ /* Make messages */
+ primary = g_strdup_printf(_("%s has presented the following certificate for just-this-once use:"), vrq->subject_name);
+ secondary = g_strdup_printf(_("Common name: %s %s\nFingerprint (SHA1): %s"), cn, cn_match, sha_asc);
+
+ /* Make a semi-pretty display */
+ purple_request_accept_cancel(
+ vrq->cb_data, /* TODO: Find what the handle ought to be */
+ _("Single-use Certificate Verification"),
+ primary,
+ secondary,
+ 1, /* Accept by default */
+ NULL, /* No account */
+ NULL, /* No other user */
+ NULL, /* No associated conversation */
+ vrq,
+ x509_singleuse_verify_cb,
+ x509_singleuse_verify_cb );
+
+ /* Cleanup */
+ g_free(primary);
+ g_free(secondary);
+ g_free(sha_asc);
+ g_byte_array_free(sha_bin, TRUE);
+}
+
+static void
+x509_singleuse_destroy_request (PurpleCertificateVerificationRequest *vrq)
+{
+ /* I don't do anything! */
+}
+
+PurpleCertificateVerifier x509_singleuse = {
+ "x509", /* Scheme name */
+ "singleuse", /* Verifier name */
+ x509_singleuse_start_verify, /* start_verification function */
+ x509_singleuse_destroy_request /* Request cleanup operation */
+};
+
+
+
+/***** X.509 Certificate Authority pool, keyed by Distinguished Name *****/
+/* This is implemented in what may be the most inefficient and bugprone way
+ possible; however, future optimizations should not be difficult. */
+
+static PurpleCertificatePool x509_ca;
+
+/** Holds a key-value pair for quickish certificate lookup */
+typedef struct {
+ gchar *dn;
+ PurpleCertificate *crt;
+} x509_ca_element;
+
+static void
+x509_ca_element_free(x509_ca_element *el)
+{
+ if (NULL == el) return;
+
+ g_free(el->dn);
+ purple_certificate_destroy(el->crt);
+ g_free(el);
+}
+
+/** System directory to probe for CA certificates */
+/* This is set in the lazy_init function */
+static const gchar *x509_ca_syspath = NULL;
+
+/** A list of loaded CAs, populated from the above path whenever the lazy_init
+ happens. Contains pointers to x509_ca_elements */
+static GList *x509_ca_certs = NULL;
+
+/** Used for lazy initialization purposes. */
+static gboolean x509_ca_initialized = FALSE;
+
+/** Adds a certificate to the in-memory cache, doing nothing else */
+static gboolean
+x509_ca_quiet_put_cert(PurpleCertificate *crt)
+{
+ x509_ca_element *el;
+
+ /* lazy_init calls this function, so calling lazy_init here is a
+ Bad Thing */
+
+ 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_ca.scheme_name), FALSE);
+
+ el = g_new0(x509_ca_element, 1);
+ el->dn = purple_certificate_get_unique_id(crt);
+ el->crt = purple_certificate_copy(crt);
+ x509_ca_certs = g_list_prepend(x509_ca_certs, el);
+
+ return TRUE;
+}
+
+/* Since the libpurple CertificatePools get registered before plugins are
+ loaded, an X.509 Scheme is generally not available when x509_ca_init is
+ called, but x509_ca requires X.509 operations in order to properly load.
+
+ To solve this, I present the lazy_init function. It attempts to finish
+ initialization of the Pool, but it usually fails when it is called from
+ x509_ca_init. However, this is OK; initialization is then simply deferred
+ until someone tries to use functions from the pool. */
+static gboolean
+x509_ca_lazy_init(void)
+{
+ PurpleCertificateScheme *x509;
+ GDir *certdir;
+ const gchar *entry;
+ GPatternSpec *pempat;
+
+ if (x509_ca_initialized) return TRUE;
+
+ /* Check that X.509 is registered */
+ x509 = purple_certificate_find_scheme(x509_ca.scheme_name);
+ if ( !x509 ) {
+ purple_debug_info("certificate/x509/ca",
+ "Lazy init failed because an X.509 Scheme "
+ "is not yet registered. Maybe it will be "
+ "better later.\n");
+ return FALSE;
+ }
+
+ /* Attempt to point at the appropriate system path */
+ if (NULL == x509_ca_syspath) {
+ x509_ca_syspath = g_build_filename(DATADIR,
+ "purple", "ca-certs", NULL);
+ }
+
+ /* Populate the certificates pool from the system path */
+ certdir = g_dir_open(x509_ca_syspath, 0, NULL);
+ g_return_val_if_fail(certdir, FALSE);
+
+ /* Use a glob to only read .pem files */
+ pempat = g_pattern_spec_new("*.pem");
+
+ while ( (entry = g_dir_read_name(certdir)) ) {
+ gchar *fullpath;
+ PurpleCertificate *crt;
+
+ if ( !g_pattern_match_string(pempat, entry) ) {
+ continue;
+ }
+
+ fullpath = g_build_filename(x509_ca_syspath, entry, NULL);
+
+ /* TODO: Respond to a failure in the following? */
+ crt = purple_certificate_import(x509, fullpath);
+
+ if (x509_ca_quiet_put_cert(crt)) {
+ purple_debug_info("certificate/x509/ca",
+ "Loaded %s\n",
+ fullpath);
+ } else {
+ purple_debug_error("certificate/x509/ca",
+ "Failed to load %s\n",
+ fullpath);
+ }
+
+ purple_certificate_destroy(crt);
+ g_free(fullpath);
+ }
+
+ g_pattern_spec_free(pempat);
+ g_dir_close(certdir);
+
+ purple_debug_info("certificate/x509/ca",
+ "Lazy init completed.\n");
+ x509_ca_initialized = TRUE;
+ return TRUE;
+}
+
+static gboolean
+x509_ca_init(void)
+{
+ /* Attempt to initialize now, but if it doesn't work, that's OK;
+ it will get done later */
+ if ( ! x509_ca_lazy_init()) {
+ purple_debug_info("certificate/x509/ca",
+ "Init failed, probably because a "
+ "dependency is not yet registered. "
+ "It has been deferred to later.\n");
+ }
+
+ return TRUE;
+}
+
+static void
+x509_ca_uninit(void)
+{
+ GList *l;
+
+ for (l = x509_ca_certs; l; l = l->next) {
+ x509_ca_element *el = l->data;
+ x509_ca_element_free(el);
+ }
+ g_list_free(x509_ca_certs);
+ x509_ca_certs = NULL;
+ x509_ca_initialized = FALSE;
+}
+
+/** Look up a ca_element by dn */
+static x509_ca_element *
+x509_ca_locate_cert(GList *lst, const gchar *dn)
+{
+ GList *cur;
+
+ for (cur = lst; cur; cur = cur->next) {
+ x509_ca_element *el = cur->data;
+ /* TODO: Unsafe? */
+ if ( !strcmp(dn, el->dn) ) {
+ return el;
+ }
+ }
+ return NULL;
+}
+
+static gboolean
+x509_ca_cert_in_pool(const gchar *id)
+{
+ g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
+ g_return_val_if_fail(id, FALSE);
+
+ if (x509_ca_locate_cert(x509_ca_certs, id) != NULL) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+static PurpleCertificate *
+x509_ca_get_cert(const gchar *id)
+{
+ PurpleCertificate *crt = NULL;
+ x509_ca_element *el;
+
+ g_return_val_if_fail(x509_ca_lazy_init(), NULL);
+ g_return_val_if_fail(id, NULL);
+
+ /* Search the memory-cached pool */
+ el = x509_ca_locate_cert(x509_ca_certs, id);
+
+ if (el != NULL) {
+ /* Make a copy of the memcached one for the function caller
+ to play with */
+ crt = purple_certificate_copy(el->crt);
+ } else {
+ crt = NULL;
+ }
+
+ return crt;
+}
+
+static gboolean
+x509_ca_put_cert(const gchar *id, PurpleCertificate *crt)
+{
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
+
+ /* TODO: This is a quick way of doing this. At some point the change
+ ought to be flushed to disk somehow. */
+ ret = x509_ca_quiet_put_cert(crt);
+
+ return ret;
+}
+
+static gboolean
+x509_ca_delete_cert(const gchar *id)
+{
+ x509_ca_element *el;
+
+ g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
+ g_return_val_if_fail(id, FALSE);
+
+ /* Is the id even in the pool? */
+ el = x509_ca_locate_cert(x509_ca_certs, id);
+ if ( el == NULL ) {
+ purple_debug_warning("certificate/x509/ca",
+ "Id %s wasn't in the pool\n",
+ id);
+ return FALSE;
+ }
+
+ /* Unlink it from the memory cache and destroy it */
+ x509_ca_certs = g_list_remove(x509_ca_certs, el);
+ x509_ca_element_free(el);
+
+ return TRUE;
+}
+
+static GList *
+x509_ca_get_idlist(void)
+{
+ GList *l, *idlist;
+
+ g_return_val_if_fail(x509_ca_lazy_init(), NULL);
+
+ idlist = NULL;
+ for (l = x509_ca_certs; l; l = l->next) {
+ x509_ca_element *el = l->data;
+ idlist = g_list_prepend(idlist, g_strdup(el->dn));
+ }
+
+ return idlist;
+}
+
+
+static PurpleCertificatePool x509_ca = {
+ "x509", /* Scheme name */
+ "ca", /* Pool name */
+ N_("Certificate Authorities"),/* User-friendly name */
+ NULL, /* Internal data */
+ x509_ca_init, /* init */
+ x509_ca_uninit, /* uninit */
+ x509_ca_cert_in_pool, /* Certificate exists? */
+ x509_ca_get_cert, /* Cert retriever */
+ x509_ca_put_cert, /* Cert writer */
+ x509_ca_delete_cert, /* Cert remover */
+ x509_ca_get_idlist /* idlist retriever */
+};
+
+
+
+/***** Cache of certificates given by TLS/SSL peers *****/
+static PurpleCertificatePool x509_tls_peers;
+
+static gboolean
+x509_tls_peers_init(void)
+{
+ gchar *poolpath;
+ int ret;
+
+ /* Set up key cache here if it isn't already done */
+ poolpath = purple_certificate_pool_mkpath(&x509_tls_peers, NULL);
+ ret = purple_build_dir(poolpath, 0700); /* Make it this user only */
+
+ g_free(poolpath);
+
+ g_return_val_if_fail(ret == 0, FALSE);
+ return TRUE;
+}
+
+static gboolean
+x509_tls_peers_cert_in_pool(const gchar *id)
+{
+ gchar *keypath;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail(id, FALSE);
+
+ keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
+
+ ret = g_file_test(keypath, G_FILE_TEST_IS_REGULAR);
+
+ g_free(keypath);
+ return ret;
+}
+
+static PurpleCertificate *
+x509_tls_peers_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_tls_peers_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_tls_peers, id);
+ crt = purple_certificate_import(x509, keypath);
+
+ g_free(keypath);
+
+ return crt;
+}
+
+static gboolean
+x509_tls_peers_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_tls_peers.scheme_name), FALSE);
+
+ /* Work out the filename and export */
+ keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
+ ret = purple_certificate_export(keypath, crt);
+
+ g_free(keypath);
+ return ret;
+}
+
+static gboolean
+x509_tls_peers_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_tls_peers_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_tls_peers, 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_tls_peers_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_tls_peers, 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_tls_peers = {
+ "x509", /* Scheme name */
+ "tls_peers", /* Pool name */
+ N_("SSL Peers Cache"), /* User-friendly name */
+ NULL, /* Internal data */
+ x509_tls_peers_init, /* init */
+ NULL, /* uninit not required */
+ x509_tls_peers_cert_in_pool, /* Certificate exists? */
+ x509_tls_peers_get_cert, /* Cert retriever */
+ x509_tls_peers_put_cert, /* Cert writer */
+ x509_tls_peers_delete_cert, /* Cert remover */
+ x509_tls_peers_get_idlist /* idlist retriever */
+};
+
+
+/***** A Verifier that uses the tls_peers cache and the CA pool to validate certificates *****/
+static PurpleCertificateVerifier x509_tls_cached;
+
+
+/* The following is several hacks piled together and needs to be fixed.
+ * It exists because show_cert (see its comments) needs the original reason
+ * given to user_auth in order to rebuild the dialog.
+ */
+/* TODO: This will cause a ua_ctx to become memleaked if the request(s) get
+ closed by handle or otherwise abnormally. */
+typedef struct {
+ PurpleCertificateVerificationRequest *vrq;
+ gchar *reason;
+} x509_tls_cached_ua_ctx;
+
+static x509_tls_cached_ua_ctx *
+x509_tls_cached_ua_ctx_new(PurpleCertificateVerificationRequest *vrq,
+ const gchar *reason)
+{
+ x509_tls_cached_ua_ctx *c;
+
+ c = g_new0(x509_tls_cached_ua_ctx, 1);
+ c->vrq = vrq;
+ c->reason = g_strdup(reason);
+
+ return c;
+}
+
+
+static void
+x509_tls_cached_ua_ctx_free(x509_tls_cached_ua_ctx *c)
+{
+ g_return_if_fail(c);
+ g_free(c->reason);
+ g_free(c);
+}
+
+static void
+x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq,
+ const gchar *reason);
+
+static void
+x509_tls_cached_show_cert(x509_tls_cached_ua_ctx *c, gint id)
+{
+ PurpleCertificate *disp_crt = c->vrq->cert_chain->data;
+ purple_certificate_display_x509(disp_crt);
+
+ /* Since clicking a button closes the request, show it again */
+ x509_tls_cached_user_auth(c->vrq, c->reason);
+
+ x509_tls_cached_ua_ctx_free(c);
+}
+
+static void
+x509_tls_cached_user_auth_cb (x509_tls_cached_ua_ctx *c, gint id)
+{
+ PurpleCertificateVerificationRequest *vrq;
+ PurpleCertificatePool *tls_peers;
+
+ g_return_if_fail(c);
+ g_return_if_fail(c->vrq);
+
+ vrq = c->vrq;
+
+ x509_tls_cached_ua_ctx_free(c);
+
+ tls_peers = purple_certificate_find_pool("x509","tls_peers");
+
+ if (2 == id) {
+ gchar *cache_id = vrq->subject_name;
+ purple_debug_info("certificate/x509/tls_cached",
+ "User ACCEPTED cert\nCaching first in chain for future use as %s...\n",
+ cache_id);
+
+ purple_certificate_pool_store(tls_peers, cache_id,
+ vrq->cert_chain->data);
+
+ purple_certificate_verify_complete(vrq,
+ PURPLE_CERTIFICATE_VALID);
+ } else {
+ purple_debug_info("certificate/x509/tls_cached",
+ "User REJECTED cert\n");
+ purple_certificate_verify_complete(vrq,
+ PURPLE_CERTIFICATE_INVALID);
+ }
+}
+
+/** Validates a certificate by asking the user
+ * @param reason String to explain why the user needs to accept/refuse the
+ * certificate.
+ * @todo Needs a handle argument
+ */
+static void
+x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq,
+ const gchar *reason)
+{
+ gchar *primary;
+
+ /* Make messages */
+ primary = g_strdup_printf(_("Accept certificate for %s?"),
+ vrq->subject_name);
+
+ /* Make a semi-pretty display */
+ purple_request_action(
+ vrq->cb_data, /* TODO: Find what the handle ought to be */
+ _("SSL Certificate Verification"),
+ primary,
+ reason,
+ 2, /* Accept by default */
+ NULL, /* No account */
+ NULL, /* No other user */
+ NULL, /* No associated conversation */
+ x509_tls_cached_ua_ctx_new(vrq, reason),
+ 3, /* Number of actions */
+ _("Yes"), x509_tls_cached_user_auth_cb,
+ _("No"), x509_tls_cached_user_auth_cb,
+ _("_View Certificate..."), x509_tls_cached_show_cert);
+
+ /* Cleanup */
+ g_free(primary);
+}
+
+static void
+x509_tls_cached_peer_cert_changed(PurpleCertificateVerificationRequest *vrq)
+{
+ /* TODO: Prompt the user, etc. */
+
+ purple_debug_info("certificate/x509/tls_cached",
+ "Certificate for %s does not match cached. "
+ "Auto-rejecting!\n",
+ vrq->subject_name);
+
+ purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_INVALID);
+ return;
+}
+
+static void
+x509_tls_cached_cert_in_cache(PurpleCertificateVerificationRequest *vrq)
+{
+ /* TODO: Looking this up by name over and over is expensive.
+ Fix, please! */
+ PurpleCertificatePool *tls_peers =
+ purple_certificate_find_pool(x509_tls_cached.scheme_name,
+ "tls_peers");
+
+ /* The peer's certificate should be the first in the list */
+ PurpleCertificate *peer_crt =
+ (PurpleCertificate *) vrq->cert_chain->data;
+
+ PurpleCertificate *cached_crt;
+ GByteArray *peer_fpr, *cached_fpr;
+
+ /* Load up the cached certificate */
+ cached_crt = purple_certificate_pool_retrieve(
+ tls_peers, vrq->subject_name);
+ g_assert(cached_crt);
+
+ /* Now get SHA1 sums for both and compare them */
+ /* TODO: This is not an elegant way to compare certs */
+ peer_fpr = purple_certificate_get_fingerprint_sha1(peer_crt);
+ cached_fpr = purple_certificate_get_fingerprint_sha1(cached_crt);
+ if (!memcmp(peer_fpr->data, cached_fpr->data, peer_fpr->len)) {
+ purple_debug_info("certificate/x509/tls_cached",
+ "Peer cert matched cached\n");
+ /* vrq is now finished */
+ purple_certificate_verify_complete(vrq,
+ PURPLE_CERTIFICATE_VALID);
+ } else {
+ purple_debug_info("certificate/x509/tls_cached",
+ "Peer cert did NOT match cached\n");
+ /* vrq now becomes the problem of cert_changed */
+ x509_tls_cached_peer_cert_changed(vrq);
+ }
+
+ purple_certificate_destroy(cached_crt);
+ g_byte_array_free(peer_fpr, TRUE);
+ g_byte_array_free(cached_fpr, TRUE);
+}
+
+/* For when we've never communicated with this party before */
+static void
+x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq)
+{
+ PurpleCertificatePool *ca, *tls_peers;
+ PurpleCertificate *end_crt, *ca_crt, *peer_crt;
+ GList *chain = vrq->cert_chain;
+ GList *last;
+ gchar *ca_id;
+
+ peer_crt = (PurpleCertificate *) chain->data;
+
+ /* First, check that the hostname matches */
+ if ( ! purple_certificate_check_subject_name(peer_crt,
+ vrq->subject_name) ) {
+ gchar *sn = purple_certificate_get_subject_name(peer_crt);
+ gchar *msg;
+
+ purple_debug_info("certificate/x509/tls_cached",
+ "Name mismatch: Certificate given for %s "
+ "has a name of %s\n",
+ vrq->subject_name, sn);
+
+ /* Prompt the user to authenticate the certificate */
+ /* TODO: Provide the user with more guidance about why he is
+ being prompted */
+ /* vrq will be completed by user_auth */
+ msg = g_strdup_printf(_("The certificate given by %s has a "
+ "name on it of %s instead. This could "
+ "mean that you are not connecting to "
+ "the service you want to."),
+ vrq->subject_name, sn);
+
+ x509_tls_cached_user_auth(vrq,msg);
+
+ g_free(sn);
+ g_free(msg);
+ return;
+ } /* if (name mismatch) */
+
+
+
+ /* Next, check that the certificate chain is valid */
+ if ( ! purple_certificate_check_signature_chain(chain) ) {
+ /* TODO: Tell the user where the chain broke? */
+ /* TODO: This error will hopelessly confuse any
+ non-elite user. */
+ gchar *secondary;
+
+ secondary = g_strdup_printf(_("The certificate chain presented"
+ " for %s is not valid."),
+ vrq->subject_name);
+
+ /* TODO: Make this error either block the ensuing SSL
+ connection error until the user dismisses this one, or
+ stifle it. */
+ purple_notify_error(NULL, /* TODO: Probably wrong. */
+ _("SSL Certificate Error"),
+ _("Invalid certificate chain"),
+ secondary );
+ g_free(secondary);
+
+ /* Okay, we're done here */
+ purple_certificate_verify_complete(vrq,
+ PURPLE_CERTIFICATE_INVALID);
+ } /* if (signature chain not good) */
+
+ /* Next, attempt to verify the last certificate against a CA */
+ ca = purple_certificate_find_pool(x509_tls_cached.scheme_name, "ca");
+
+ /* If, for whatever reason, there is no Certificate Authority pool
+ loaded, we will simply present it to the user for checking. */
+ if ( !ca ) {
+ purple_debug_error("certificate/x509/tls_cached",
+ "No X.509 Certificate Authority pool "
+ "could be found!\n");
+
+ /* vrq will be completed by user_auth */
+ x509_tls_cached_user_auth(vrq,_("You have no database of root "
+ "certificates, so this "
+ "certificate cannot be "
+ "validated."));
+ return;
+ }
+
+ /* TODO: I don't have the Glib documentation handy; is this correct? */
+ last = g_list_last(chain);
+ end_crt = (PurpleCertificate *) last->data;
+
+ /* Attempt to look up the last certificate's issuer */
+ ca_id = purple_certificate_get_issuer_unique_id(end_crt);
+ purple_debug_info("certificate/x509/tls_cached",
+ "Checking for a CA with DN=%s\n",
+ ca_id);
+ if ( !purple_certificate_pool_contains(ca, ca_id) ) {
+ purple_debug_info("certificate/x509/tls_cached",
+ "Certificate Authority with DN='%s' not "
+ "found. I'll prompt the user, I guess.\n",
+ ca_id);
+ g_free(ca_id);
+ /* vrq will be completed by user_auth */
+ x509_tls_cached_user_auth(vrq,_("The root certificate this "
+ "one claims to be issued by "
+ "is unknown to Pidgin."));
+ return;
+ }
+
+ ca_crt = purple_certificate_pool_retrieve(ca, ca_id);
+ g_free(ca_id);
+ g_assert(ca_crt);
+
+ /* Check the signature */
+ if ( !purple_certificate_signed_by(end_crt, ca_crt) ) {
+ /* TODO: If signed_by ever returns a reason, maybe mention
+ that, too. */
+ /* TODO: Also mention the CA involved. While I could do this
+ now, a full DN is a little much with which to assault the
+ user's poor, leaky eyes. */
+ /* TODO: This error message makes my eyes cross, and I wrote it */
+ gchar * secondary =
+ g_strdup_printf(_("The certificate chain presented by "
+ "%s does not have a valid digital "
+ "signature from the Certificate "
+ "Authority it claims to have one "
+ "from."),
+ vrq->subject_name);
+
+ purple_notify_error(NULL, /* TODO: Probably wrong */
+ _("SSL Certificate Error"),
+ _("Invalid certificate authority"
+ " signature"),
+ secondary);
+ g_free(secondary);
+
+ /* Signal "bad cert" */
+ purple_certificate_verify_complete(vrq,
+ PURPLE_CERTIFICATE_INVALID);
+ return;
+ } /* if (CA signature not good) */
+
+ /* If we reach this point, the certificate is good. */
+ /* Look up the local cache and store it there for future use */
+ tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,
+ "tls_peers");
+
+ if (tls_peers) {
+ g_assert(purple_certificate_pool_store(tls_peers,
+ vrq->subject_name,
+ peer_crt) );
+ } else {
+ purple_debug_error("certificate/x509/tls_cached",
+ "Unable to locate tls_peers certificate "
+ "cache.\n");
+ }
+
+ /* Whew! Done! */
+ purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID);
+}
+
+static void
+x509_tls_cached_start_verify(PurpleCertificateVerificationRequest *vrq)
+{
+ const gchar *tls_peers_name = "tls_peers"; /* Name of local cache */
+ PurpleCertificatePool *tls_peers;
+
+ g_return_if_fail(vrq);
+
+ purple_debug_info("certificate/x509/tls_cached",
+ "Starting verify for %s\n",
+ vrq->subject_name);
+
+ tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,tls_peers_name);
+
+ /* TODO: This should probably just prompt the user instead of throwing
+ an angry fit */
+ if (!tls_peers) {
+ purple_debug_error("certificate/x509/tls_cached",
+ "Couldn't find local peers cache %s\nReturning INVALID to callback\n",
+ tls_peers_name);
+
+ purple_certificate_verify_complete(vrq,
+ PURPLE_CERTIFICATE_INVALID);
+ return;
+ }
+
+ /* Check if the peer has a certificate cached already */
+ purple_debug_info("certificate/x509/tls_cached",
+ "Checking for cached cert...\n");
+ if (purple_certificate_pool_contains(tls_peers, vrq->subject_name)) {
+ purple_debug_info("certificate/x509/tls_cached",
+ "...Found cached cert\n");
+ /* vrq is now the responsibility of cert_in_cache */
+ x509_tls_cached_cert_in_cache(vrq);
+ } else {
+ /* TODO: Prompt the user, etc. */
+ purple_debug_info("certificate/x509/tls_cached",
+ "...Not in cache\n");
+ /* vrq now becomes the problem of unknown_peer */
+ x509_tls_cached_unknown_peer(vrq);
+ }
+}
+
+static void
+x509_tls_cached_destroy_request(PurpleCertificateVerificationRequest *vrq)
+{
+ g_return_if_fail(vrq);
+}
+
+static PurpleCertificateVerifier x509_tls_cached = {
+ "x509", /* Scheme name */
+ "tls_cached", /* Verifier name */
+ x509_tls_cached_start_verify, /* Verification begin */
+ x509_tls_cached_destroy_request /* Request cleanup */
+};
+
+/****************************************************************************/
+/* Subsystem */
+/****************************************************************************/
+void
+purple_certificate_init(void)
+{
+ /* Register builtins */
+ purple_certificate_register_verifier(&x509_singleuse);
+ purple_certificate_register_pool(&x509_ca);
+ purple_certificate_register_pool(&x509_tls_peers);
+ purple_certificate_register_verifier(&x509_tls_cached);
+}
+
+void
+purple_certificate_uninit(void)
+{
+ GList *full_list, *l;
+
+ /* Unregister all Schemes */
+ full_list = g_list_copy(cert_schemes); /* Make a working copy */
+ for (l = full_list; l; l = l->next) {
+ purple_certificate_unregister_scheme(
+ (PurpleCertificateScheme *) l->data );
+ }
+ g_list_free(full_list);
+
+ /* Unregister all Verifiers */
+ full_list = g_list_copy(cert_verifiers); /* Make a working copy */
+ for (l = full_list; l; l = l->next) {
+ purple_certificate_unregister_verifier(
+ (PurpleCertificateVerifier *) l->data );
+ }
+ g_list_free(full_list);
+
+ /* Unregister all Pools */
+ full_list = g_list_copy(cert_pools); /* Make a working copy */
+ for (l = full_list; l; l = l->next) {
+ purple_certificate_unregister_pool(
+ (PurpleCertificatePool *) l->data );
+ }
+ g_list_free(full_list);
+}
+
+gpointer
+purple_certificate_get_handle(void)
+{
+ static gint handle;
+ return &handle;
+}
+
+PurpleCertificateScheme *
+purple_certificate_find_scheme(const gchar *name)
+{
+ PurpleCertificateScheme *scheme = NULL;
+ GList *l;
+
+ g_return_val_if_fail(name, NULL);
+
+ /* Traverse the list of registered schemes and locate the
+ one whose name matches */
+ for(l = cert_schemes; l; l = l->next) {
+ scheme = (PurpleCertificateScheme *)(l->data);
+
+ /* Name matches? that's our man */
+ if(!g_ascii_strcasecmp(scheme->name, name))
+ return scheme;
+ }
+
+ purple_debug_warning("certificate",
+ "CertificateScheme %s requested but not found.\n",
+ name);
+
+ /* TODO: Signalling and such? */
+
+ return NULL;
+}
+
+GList *
+purple_certificate_get_schemes(void)
+{
+ return cert_schemes;
+}
+
+gboolean
+purple_certificate_register_scheme(PurpleCertificateScheme *scheme)
+{
+ g_return_val_if_fail(scheme != NULL, FALSE);
+
+ /* Make sure no scheme is registered with the same name */
+ if (purple_certificate_find_scheme(scheme->name) != NULL) {
+ return FALSE;
+ }
+
+ /* Okay, we're golden. Register it. */
+ cert_schemes = g_list_prepend(cert_schemes, scheme);
+
+ /* TODO: Signalling and such? */
+
+ purple_debug_info("certificate",
+ "CertificateScheme %s registered\n",
+ scheme->name);
+
+ return TRUE;
+}
+
+gboolean
+purple_certificate_unregister_scheme(PurpleCertificateScheme *scheme)
+{
+ if (NULL == scheme) {
+ purple_debug_warning("certificate",
+ "Attempting to unregister NULL scheme\n");
+ return FALSE;
+ }
+
+ /* TODO: signalling? */
+
+ /* TODO: unregister all CertificateVerifiers for this scheme?*/
+ /* TODO: unregister all CertificatePools for this scheme? */
+ /* Neither of the above should be necessary, though */
+ cert_schemes = g_list_remove(cert_schemes, scheme);
+
+ purple_debug_info("certificate",
+ "CertificateScheme %s unregistered\n",
+ scheme->name);
+
+
+ return TRUE;
+}
+
+PurpleCertificateVerifier *
+purple_certificate_find_verifier(const gchar *scheme_name, const gchar *ver_name)
+{
+ PurpleCertificateVerifier *vr = NULL;
+ GList *l;
+
+ g_return_val_if_fail(scheme_name, NULL);
+ g_return_val_if_fail(ver_name, NULL);
+
+ /* Traverse the list of registered verifiers and locate the
+ one whose name matches */
+ for(l = cert_verifiers; l; l = l->next) {
+ vr = (PurpleCertificateVerifier *)(l->data);
+
+ /* Scheme and name match? */
+ if(!g_ascii_strcasecmp(vr->scheme_name, scheme_name) &&
+ !g_ascii_strcasecmp(vr->name, ver_name))
+ return vr;
+ }
+
+ purple_debug_warning("certificate",
+ "CertificateVerifier %s, %s requested but not found.\n",
+ scheme_name, ver_name);
+
+ /* TODO: Signalling and such? */
+
+ return NULL;
+}
+
+
+GList *
+purple_certificate_get_verifiers(void)
+{
+ return cert_verifiers;
+}
+
+gboolean
+purple_certificate_register_verifier(PurpleCertificateVerifier *vr)
+{
+ g_return_val_if_fail(vr != NULL, FALSE);
+
+ /* Make sure no verifier is registered with the same scheme/name */
+ if (purple_certificate_find_verifier(vr->scheme_name, vr->name) != NULL) {
+ return FALSE;
+ }
+
+ /* Okay, we're golden. Register it. */
+ cert_verifiers = g_list_prepend(cert_verifiers, vr);
+
+ /* TODO: Signalling and such? */
+
+ purple_debug_info("certificate",
+ "CertificateVerifier %s registered\n",
+ vr->name);
+ return TRUE;
+}
+
+gboolean
+purple_certificate_unregister_verifier(PurpleCertificateVerifier *vr)
+{
+ if (NULL == vr) {
+ purple_debug_warning("certificate",
+ "Attempting to unregister NULL verifier\n");
+ return FALSE;
+ }
+
+ /* TODO: signalling? */
+
+ cert_verifiers = g_list_remove(cert_verifiers, vr);
+
+
+ purple_debug_info("certificate",
+ "CertificateVerifier %s unregistered\n",
+ vr->name);
+
+ return TRUE;
+}
+
+PurpleCertificatePool *
+purple_certificate_find_pool(const gchar *scheme_name, const gchar *pool_name)
+{
+ PurpleCertificatePool *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 = cert_pools; l; l = l->next) {
+ pool = (PurpleCertificatePool *)(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("certificate",
+ "CertificatePool %s, %s requested but not found.\n",
+ scheme_name, pool_name);
+
+ /* TODO: Signalling and such? */
+
+ return NULL;
+
+}
+
+GList *
+purple_certificate_get_pools(void)
+{
+ return cert_pools;
+}
+
+gboolean
+purple_certificate_register_pool(PurpleCertificatePool *pool)
+{
+ gboolean success = FALSE;
+ 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_certificate_find_pool(pool->scheme_name, pool->name)) {
+ return FALSE;
+ }
+
+ /* Initialize the pool if needed */
+ if (pool->init) {
+ success = pool->init();
+ } else {
+ success = TRUE;
+ }
+
+ if (success) {
+ /* Register the Pool */
+ cert_pools = g_list_prepend(cert_pools, pool);
+
+ /* TODO: Emit a signal that the pool got registered */
+
+ purple_signal_register(pool, /* Signals emitted from pool */
+ "certificate-stored",
+ purple_marshal_VOID__POINTER_POINTER,
+ NULL, /* No callback return value */
+ 2, /* Two non-data arguments */
+ purple_value_new(PURPLE_TYPE_SUBTYPE,
+ PURPLE_SUBTYPE_CERTIFICATEPOOL),
+ purple_value_new(PURPLE_TYPE_STRING));
+
+ purple_signal_register(pool, /* Signals emitted from pool */
+ "certificate-deleted",
+ purple_marshal_VOID__POINTER_POINTER,
+ NULL, /* No callback return value */
+ 2, /* Two non-data arguments */
+ purple_value_new(PURPLE_TYPE_SUBTYPE,
+ PURPLE_SUBTYPE_CERTIFICATEPOOL),
+ purple_value_new(PURPLE_TYPE_STRING));
+
+
+ purple_debug_info("certificate",
+ "CertificatePool %s registered\n",
+ pool->name);
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+
+ /* Control does not reach this point */
+}
+
+gboolean
+purple_certificate_unregister_pool(PurpleCertificatePool *pool)
+{
+ /* TODO: Better error checking? */
+ if (NULL == pool) {
+ purple_debug_warning("certificate",
+ "Attempting to unregister NULL pool\n");
+ return FALSE;
+ }
+
+ /* Check that the pool is registered */
+ if (!g_list_find(cert_pools, pool)) {
+ purple_debug_warning("certificate",
+ "Pool to unregister isn't registered!\n");
+
+ return FALSE;
+ }
+
+ /* Uninit the pool if needed */
+ if (pool->uninit) {
+ pool->uninit();
+ }
+
+ cert_pools = g_list_remove(cert_pools, pool);
+
+ /* TODO: Signalling? */
+ purple_signal_unregister(pool, "certificate-stored");
+ purple_signal_unregister(pool, "certificate-deleted");
+
+ purple_debug_info("certificate",
+ "CertificatePool %s unregistered\n",
+ pool->name);
+ return TRUE;
+}
+
+/****************************************************************************/
+/* Scheme-specific functions */
+/****************************************************************************/
+
+void
+purple_certificate_display_x509(PurpleCertificate *crt)
+{
+ gchar *sha_asc;
+ GByteArray *sha_bin;
+ gchar *cn;
+ time_t activation, expiration;
+ /* Length of these buffers is dictated by 'man ctime_r' */
+ gchar activ_str[26], expir_str[26];
+ gchar *secondary;
+
+ /* Pull out the SHA1 checksum */
+ sha_bin = purple_certificate_get_fingerprint_sha1(crt);
+ /* Now decode it for display */
+ sha_asc = purple_base16_encode_chunked(sha_bin->data,
+ sha_bin->len);
+
+ /* Get the cert Common Name */
+ /* TODO: Will break on CA certs */
+ cn = purple_certificate_get_subject_name(crt);
+
+ /* Get the certificate times */
+ /* TODO: Check the times against localtime */
+ /* TODO: errorcheck? */
+ g_assert(purple_certificate_get_times(crt, &activation, &expiration));
+ ctime_r(&activation, activ_str);
+ ctime_r(&expiration, expir_str);
+
+ /* Make messages */
+ secondary = g_strdup_printf(_("Common name: %s\n\n"
+ "Fingerprint (SHA1): %s\n\n"
+ "Activation date: %s\n"
+ "Expiration date: %s\n"),
+ cn, sha_asc, activ_str, expir_str);
+
+ /* Make a semi-pretty display */
+ purple_notify_info(
+ NULL, /* TODO: Find what the handle ought to be */
+ _("Certificate Information"),
+ "",
+ secondary);
+
+ /* Cleanup */
+ g_free(cn);
+ g_free(secondary);
+ g_free(sha_asc);
+ g_byte_array_free(sha_bin, TRUE);
+}
+
+
+
+
diff --git a/libpurple/certificate.h b/libpurple/certificate.h
new file mode 100644
index 0000000000..88b56a9f9e
--- /dev/null
+++ b/libpurple/certificate.h
@@ -0,0 +1,779 @@
+/**
+ * @file certificate.h Public-Key Certificate API
+ * @ingroup core
+ */
+
+/*
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _PURPLE_CERTIFICATE_H
+#define _PURPLE_CERTIFICATE_H
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+typedef enum
+{
+ PURPLE_CERTIFICATE_INVALID = 0,
+ PURPLE_CERTIFICATE_VALID = 1
+} PurpleCertificateVerificationStatus;
+
+typedef struct _PurpleCertificate PurpleCertificate;
+typedef struct _PurpleCertificatePool PurpleCertificatePool;
+typedef struct _PurpleCertificateScheme PurpleCertificateScheme;
+typedef struct _PurpleCertificateVerifier PurpleCertificateVerifier;
+typedef struct _PurpleCertificateVerificationRequest PurpleCertificateVerificationRequest;
+
+/**
+ * Callback function for the results of a verification check
+ * @param st Status code
+ * @param userdata User-defined data
+ */
+typedef void (*PurpleCertificateVerifiedCallback)
+ (PurpleCertificateVerificationStatus st,
+ gpointer userdata);
+
+/** A certificate instance
+ *
+ * An opaque data structure representing a single certificate under some
+ * CertificateScheme
+ */
+struct _PurpleCertificate
+{
+ /** Scheme this certificate is under */
+ PurpleCertificateScheme * scheme;
+ /** Opaque pointer to internal data */
+ gpointer data;
+};
+
+/**
+ * Database for retrieval or storage of Certificates
+ *
+ * More or less a hash table; all lookups and writes are controlled by a string
+ * key.
+ */
+struct _PurpleCertificatePool
+{
+ /** 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_certificate_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_certificate_unregister_pool() . May be NULL
+ */
+ void (* uninit)(void);
+
+ /** Check for presence of a certificate in the pool using unique ID */
+ gboolean (* cert_in_pool)(const gchar *id);
+ /** Retrieve a PurpleCertificate from the pool */
+ PurpleCertificate * (* get_cert)(const gchar *id);
+ /** Add a certificate to the pool. Must overwrite any other
+ * certificates sharing the same ID in the pool.
+ * @return TRUE if the operation succeeded, otherwise FALSE
+ */
+ gboolean (* put_cert)(const gchar *id, PurpleCertificate *crt);
+ /** Delete a certificate from the pool */
+ gboolean (* delete_cert)(const gchar *id);
+
+ /** Returns a list of IDs stored in the pool */
+ GList * (* get_idlist)(void);
+};
+
+/** A certificate type
+ *
+ * A CertificateScheme must implement all of the fields in the structure,
+ * and register it using purple_certificate_register_scheme()
+ *
+ * There may be only ONE CertificateScheme provided for each certificate
+ * type, as specified by the "name" field.
+ */
+struct _PurpleCertificateScheme
+{
+ /** Name of the certificate type
+ * ex: "x509", "pgp", etc.
+ * This must be globally unique - you may not register more than one
+ * CertificateScheme of the same name at a time.
+ */
+ gchar * name;
+
+ /** User-friendly name for this type
+ * ex: N_("X.509 Certificates")
+ * When this is displayed anywhere, it should be i18ned
+ * ex: _(scheme->fullname)
+ */
+ gchar * fullname;
+
+ /** Imports a certificate from a file
+ *
+ * @param filename File to import the certificate from
+ * @return Pointer to the newly allocated Certificate struct
+ * or NULL on failure.
+ */
+ PurpleCertificate * (* import_certificate)(const gchar * filename);
+
+ /**
+ * Exports a certificate to a file
+ *
+ * @param filename File to export the certificate to
+ * @param crt Certificate to export
+ * @return TRUE if the export succeeded, otherwise FALSE
+ * @see purple_certificate_export()
+ */
+ gboolean (* export_certificate)(const gchar *filename, PurpleCertificate *crt);
+
+ /**
+ * Duplicates a certificate
+ *
+ * Certificates 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
+ */
+ PurpleCertificate * (* copy_certificate)(PurpleCertificate *crt);
+
+ /** Destroys and frees a Certificate structure
+ *
+ * Destroys a Certificate's internal data structures and calls
+ * free(crt)
+ *
+ * @param crt Certificate instance to be destroyed. It WILL NOT be
+ * destroyed if it is not of the correct
+ * CertificateScheme. Can be NULL
+ */
+ void (* destroy_certificate)(PurpleCertificate * crt);
+
+ /** Find whether "crt" has a valid signature from issuer "issuer"
+ * @see purple_certificate_signed_by() */
+ gboolean (*signed_by)(PurpleCertificate *crt, PurpleCertificate *issuer);
+ /**
+ * Retrieves the certificate public key fingerprint using SHA1
+ *
+ * @param crt Certificate instance
+ * @return Binary representation of SHA1 hash - must be freed using
+ * g_byte_array_free()
+ */
+ GByteArray * (* get_fingerprint_sha1)(PurpleCertificate *crt);
+
+ /**
+ * Retrieves a unique certificate identifier
+ *
+ * @param crt Certificate instance
+ * @return Newly allocated string that can be used to uniquely
+ * identify the certificate.
+ */
+ gchar * (* get_unique_id)(PurpleCertificate *crt);
+
+ /**
+ * Retrieves a unique identifier for the certificate's issuer
+ *
+ * @param crt Certificate instance
+ * @return Newly allocated string that can be used to uniquely
+ * identify the issuer's certificate.
+ */
+ gchar * (* get_issuer_unique_id)(PurpleCertificate *crt);
+
+ /**
+ * Gets the certificate subject's name
+ *
+ * For X.509, this is the "Common Name" field, as we're only using it
+ * for hostname verification at the moment
+ *
+ * @see purple_certificate_get_subject_name()
+ *
+ * @param crt Certificate instance
+ * @return Newly allocated string with the certificate subject.
+ */
+ gchar * (* get_subject_name)(PurpleCertificate *crt);
+
+ /**
+ * Check the subject name against that on the certificate
+ * @see purple_certificate_check_subject_name()
+ * @return TRUE if it is a match, else FALSE
+ */
+ gboolean (* check_subject_name)(PurpleCertificate *crt, const gchar *name);
+
+ /** Retrieve the certificate activation/expiration times */
+ gboolean (* get_times)(PurpleCertificate *crt, time_t *activation, time_t *expiration);
+
+ /* TODO: Fill out this structure */
+};
+
+/** A set of operations used to provide logic for verifying a Certificate's
+ * authenticity.
+ *
+ * A Verifier provider must fill out these fields, then register it using
+ * purple_certificate_register_verifier()
+ *
+ * The (scheme_name, name) value must be unique for each Verifier - you may not
+ * register more than one Verifier of the same name for each Scheme
+ */
+struct _PurpleCertificateVerifier
+{
+ /** Name of the scheme this Verifier operates on
+ *
+ * The scheme will be looked up by name when a Request is generated
+ * using this Verifier
+ */
+ gchar *scheme_name;
+
+ /** Name of the Verifier - case insensitive */
+ gchar *name;
+
+ /**
+ * Start the verification process
+ *
+ * To be called from purple_certificate_verify once it has
+ * constructed the request. This will use the information in the
+ * given VerificationRequest to check the certificate and callback
+ * the requester with the verification results.
+ *
+ * @param vrq Request to process
+ */
+ void (* start_verification)(PurpleCertificateVerificationRequest *vrq);
+
+ /**
+ * Destroy a completed Request under this Verifier
+ * The function pointed to here is only responsible for cleaning up
+ * whatever PurpleCertificateVerificationRequest::data points to.
+ * It should not call free(vrq)
+ *
+ * @param vrq Request to destroy
+ */
+ void (* destroy_request)(PurpleCertificateVerificationRequest *vrq);
+};
+
+/** Structure for a single certificate request
+ *
+ * Useful for keeping track of the state of a verification that involves
+ * several steps
+ */
+struct _PurpleCertificateVerificationRequest
+{
+ /** Reference to the verification logic used */
+ PurpleCertificateVerifier *verifier;
+ /** Reference to the scheme used.
+ *
+ * This is looked up from the Verifier when the Request is generated
+ */
+ PurpleCertificateScheme *scheme;
+
+ /**
+ * Name to check that the certificate is issued to
+ *
+ * For X.509 certificates, this is the Common Name
+ */
+ gchar *subject_name;
+
+ /** List of certificates in the chain to be verified (such as that returned by purple_ssl_get_peer_certificates )
+ *
+ * This is most relevant for X.509 certificates used in SSL sessions.
+ * The list order should be: certificate, issuer, issuer's issuer, etc.
+ */
+ GList *cert_chain;
+
+ /** Internal data used by the Verifier code */
+ gpointer data;
+
+ /** Function to call with the verification result */
+ PurpleCertificateVerifiedCallback cb;
+ /** Data to pass to the post-verification callback */
+ gpointer cb_data;
+};
+
+/*****************************************************************************/
+/** @name Certificate Verification Functions */
+/*****************************************************************************/
+/*@{*/
+
+/**
+ * Constructs a verification request and passed control to the specified Verifier
+ *
+ * It is possible that the callback will be called immediately upon calling
+ * this function. Plan accordingly.
+ *
+ * @param verifier Verification logic to use.
+ * @see purple_certificate_find_verifier()
+ *
+ * @param subject_name Name that should match the first certificate in the
+ * chain for the certificate to be valid. Will be strdup'd
+ * into the Request struct
+ *
+ * @param cert_chain Certificate chain to check. If there is more than one
+ * certificate in the chain (X.509), the peer's
+ * certificate comes first, then the issuer/signer's
+ * certificate, etc. The whole list is duplicated into the
+ * Request struct.
+ *
+ * @param cb Callback function to be called with whether the
+ * certificate was approved or not.
+ * @param cb_data User-defined data for the above.
+ */
+void
+purple_certificate_verify (PurpleCertificateVerifier *verifier,
+ const gchar *subject_name, GList *cert_chain,
+ PurpleCertificateVerifiedCallback cb,
+ gpointer cb_data);
+
+/**
+ * Completes and destroys a VerificationRequest
+ *
+ * @param vrq Request to conclude
+ * @param st Success/failure code to pass to the request's
+ * completion callback.
+ */
+void
+purple_certificate_verify_complete(PurpleCertificateVerificationRequest *vrq,
+ PurpleCertificateVerificationStatus st);
+
+/*@}*/
+
+/*****************************************************************************/
+/** @name Certificate Functions */
+/*****************************************************************************/
+/*@{*/
+
+/**
+ * Makes a duplicate of a certificate
+ *
+ * @param crt Instance to duplicate
+ * @return Pointer to new instance
+ */
+PurpleCertificate *
+purple_certificate_copy(PurpleCertificate *crt);
+
+/**
+ * Duplicates an entire list of certificates
+ *
+ * @param crt_list List to duplicate
+ * @return New list copy
+ */
+GList *
+purple_certificate_copy_list(GList *crt_list);
+
+/**
+ * Destroys and free()'s a Certificate
+ *
+ * @param crt Instance to destroy. May be NULL.
+ */
+void
+purple_certificate_destroy (PurpleCertificate *crt);
+
+/**
+ * Destroy an entire list of Certificate instances and the containing list
+ *
+ * @param crt_list List of certificates to destroy. May be NULL.
+ */
+void
+purple_certificate_destroy_list (GList * crt_list);
+
+/**
+ * Check whether 'crt' has a valid signature made by 'issuer'
+ *
+ * @param crt Certificate instance to check signature of
+ * @param issuer Certificate thought to have signed 'crt'
+ *
+ * @return TRUE if 'crt' has a valid signature made by 'issuer',
+ * otherwise FALSE
+ * @TODO Find a way to give the reason (bad signature, not the issuer, etc.)
+ */
+gboolean
+purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer);
+
+/**
+ * Check that a certificate chain is valid
+ *
+ * Uses purple_certificate_signed_by() to verify that each PurpleCertificate
+ * in the chain carries a valid signature from the next. A single-certificate
+ * chain is considered to be valid.
+ *
+ * @param chain List of PurpleCertificate instances comprising the chain,
+ * in the order certificate, issuer, issuer's issuer, etc.
+ * @return TRUE if the chain is valid. See description.
+ * @TODO Specify which certificate in the chain caused a failure
+ */
+gboolean
+purple_certificate_check_signature_chain(GList *chain);
+
+/**
+ * Imports a PurpleCertificate from a file
+ *
+ * @param scheme Scheme to import under
+ * @param filename File path to import from
+ * @return Pointer to a new PurpleCertificate, or NULL on failure
+ */
+PurpleCertificate *
+purple_certificate_import(PurpleCertificateScheme *scheme, const gchar *filename);
+
+/**
+ * Exports a PurpleCertificate to a file
+ *
+ * @param filename File to export the certificate to
+ * @param crt Certificate to export
+ * @return TRUE if the export succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_export(const gchar *filename, PurpleCertificate *crt);
+
+
+/**
+ * Retrieves the certificate public key fingerprint using SHA1.
+ *
+ * @param crt Certificate instance
+ * @return Binary representation of the hash. You are responsible for free()ing
+ * this.
+ * @see purple_base16_encode_chunked()
+ */
+GByteArray *
+purple_certificate_get_fingerprint_sha1(PurpleCertificate *crt);
+
+/**
+ * Get a unique identifier for the certificate
+ *
+ * @param crt Certificate instance
+ * @return String representing the certificate uniquely. Must be g_free()'ed
+ */
+gchar *
+purple_certificate_get_unique_id(PurpleCertificate *crt);
+
+/**
+ * Get a unique identifier for the certificate's issuer
+ *
+ * @param crt Certificate instance
+ * @return String representing the certificate's issuer uniquely. Must be
+ * g_free()'ed
+ */
+gchar *
+purple_certificate_get_issuer_unique_id(PurpleCertificate *crt);
+
+/**
+ * Gets the certificate subject's name
+ *
+ * For X.509, this is the "Common Name" field, as we're only using it
+ * for hostname verification at the moment
+ *
+ * @param crt Certificate instance
+ * @return Newly allocated string with the certificate subject.
+ */
+gchar *
+purple_certificate_get_subject_name(PurpleCertificate *crt);
+
+/**
+ * Check the subject name against that on the certificate
+ * @param crt Certificate instance
+ * @param name Name to check.
+ * @return TRUE if it is a match, else FALSE
+ */
+gboolean
+purple_certificate_check_subject_name(PurpleCertificate *crt, const gchar *name);
+
+/**
+ * Get the expiration/activation times.
+ *
+ * @param crt Certificate instance
+ * @param activation Reference to store the activation time at. May be NULL
+ * if you don't actually want it.
+ * @param expiration Reference to store the expiration time at. May be NULL
+ * if you don't actually want it.
+ * @return TRUE if the requested values were obtained, otherwise FALSE.
+ */
+gboolean
+purple_certificate_get_times(PurpleCertificate *crt, time_t *activation, time_t *expiration);
+
+/*@}*/
+
+/*****************************************************************************/
+/** @name Certificate Pool Functions */
+/*****************************************************************************/
+/*@{*/
+/**
+ * Helper function for generating file paths in ~/.purple/certificates for
+ * CertificatePools that use them.
+ *
+ * All components will be escaped for filesystem friendliness.
+ *
+ * @param pool CertificatePool to build a path for
+ * @param id Key to look up a Certificate by. May be NULL.
+ * @return A newly allocated path of the form
+ * ~/.purple/certificates/scheme_name/pool_name/unique_id
+ */
+gchar *
+purple_certificate_pool_mkpath(PurpleCertificatePool *pool, const gchar *id);
+
+/**
+ * Determines whether a pool can be used.
+ *
+ * Checks whether the associated CertificateScheme is loaded.
+ *
+ * @param pool Pool to check
+ *
+ * @return TRUE if the pool can be used, otherwise FALSE
+ */
+gboolean
+purple_certificate_pool_usable(PurpleCertificatePool *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_certificate_pool_usable()
+ */
+PurpleCertificateScheme *
+purple_certificate_pool_get_scheme(PurpleCertificatePool *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_certificate_pool_contains(PurpleCertificatePool *pool, const gchar *id);
+
+/**
+ * Retrieve a certificate from a pool.
+ * @param pool Pool to fish in
+ * @param id ID to look up
+ * @return Retrieved certificate, or NULL if it wasn't there
+ */
+PurpleCertificate *
+purple_certificate_pool_retrieve(PurpleCertificatePool *pool, const gchar *id);
+
+/**
+ * Add a certificate to a pool
+ *
+ * Any pre-existing certificate of the same ID will be overwritten.
+ *
+ * @param pool Pool to add to
+ * @param id ID to store the certificate with
+ * @param crt Certificate to store
+ * @return TRUE if the operation succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_pool_store(PurpleCertificatePool *pool, const gchar *id, PurpleCertificate *crt);
+
+/**
+ * Remove a certificate from a pool
+ *
+ * @param pool Pool to remove from
+ * @param id ID to remove
+ * @return TRUE if the operation succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_pool_delete(PurpleCertificatePool *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_certificate_pool_destroy_idlist()
+ */
+GList *
+purple_certificate_pool_get_idlist(PurpleCertificatePool *pool);
+
+/**
+ * Destroys the result given by purple_certificate_pool_get_idlist()
+ *
+ * @param idlist ID List to destroy
+ */
+void
+purple_certificate_pool_destroy_idlist(GList *idlist);
+
+/*@}*/
+
+/*****************************************************************************/
+/** @name Certificate Subsystem API */
+/*****************************************************************************/
+/*@{*/
+
+/**
+ * Initialize the certificate system
+ */
+void
+purple_certificate_init(void);
+
+/**
+ * Un-initialize the certificate system
+ */
+void
+purple_certificate_uninit(void);
+
+/**
+ * Get the Certificate subsystem handle for signalling purposes
+ */
+gpointer
+purple_certificate_get_handle(void);
+
+/** Look up a registered CertificateScheme by name
+ * @param name The scheme name. Case insensitive.
+ * @return Pointer to the located Scheme, or NULL if it isn't found.
+ */
+PurpleCertificateScheme *
+purple_certificate_find_scheme(const gchar *name);
+
+/**
+ * Get all registered CertificateSchemes
+ *
+ * @return GList pointing to all registered CertificateSchemes . This value
+ * is owned by libpurple
+ */
+GList *
+purple_certificate_get_schemes(void);
+
+/** Register a CertificateScheme with libpurple
+ *
+ * No two schemes can be registered with the same name; this function enforces
+ * that.
+ *
+ * @param scheme Pointer to the scheme to register.
+ * @return TRUE if the scheme was successfully added, otherwise FALSE
+ */
+gboolean
+purple_certificate_register_scheme(PurpleCertificateScheme *scheme);
+
+/** Unregister a CertificateScheme from libpurple
+ *
+ * @param scheme Scheme to unregister.
+ * If the scheme is not registered, this is a no-op.
+ *
+ * @return TRUE if the unregister completed successfully
+ */
+gboolean
+purple_certificate_unregister_scheme(PurpleCertificateScheme *scheme);
+
+/** Look up a registered PurpleCertificateVerifier by scheme and name
+ * @param scheme_name Scheme name. Case insensitive.
+ * @param ver_name The verifier name. Case insensitive.
+ * @return Pointer to the located Verifier, or NULL if it isn't found.
+ */
+PurpleCertificateVerifier *
+purple_certificate_find_verifier(const gchar *scheme_name, const gchar *ver_name);
+
+/**
+ * Get the list of registered CertificateVerifiers
+ *
+ * @return GList of all registered PurpleCertificateVerifier. This value
+ * is owned by libpurple
+ */
+GList *
+purple_certificate_get_verifiers(void);
+
+/**
+ * Register a CertificateVerifier with libpurple
+ *
+ * @param vr Verifier to register.
+ * @return TRUE if register succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_register_verifier(PurpleCertificateVerifier *vr);
+
+/**
+ * Unregister a CertificateVerifier with libpurple
+ *
+ * @param vr Verifier to unregister.
+ * @return TRUE if unregister succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_unregister_verifier(PurpleCertificateVerifier *vr);
+
+/** Look up a registered PurpleCertificatePool 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.
+ */
+PurpleCertificatePool *
+purple_certificate_find_pool(const gchar *scheme_name, const gchar *pool_name);
+
+/**
+ * Get the list of registered Pools
+ *
+ * @return GList of all registered PurpleCertificatePool s. This value
+ * is owned by libpurple
+ */
+GList *
+purple_certificate_get_pools(void);
+
+/**
+ * Register a CertificatePool with libpurple and call its init function
+ *
+ * @param pool Pool to register.
+ * @return TRUE if the register succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_register_pool(PurpleCertificatePool *pool);
+
+/**
+ * Unregister a CertificatePool with libpurple and call its uninit function
+ *
+ * @param pool Pool to unregister.
+ * @return TRUE if the unregister succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_unregister_pool(PurpleCertificatePool *pool);
+
+/*@}*/
+
+
+/**
+ * Displays a window showing X.509 certificate information
+ *
+ * @param crt Certificate under an "x509" Scheme
+ * @TODO Will break on CA certs, as they have no Common Name
+ */
+void
+purple_certificate_display_x509(PurpleCertificate *crt);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _PURPLE_CERTIFICATE_H */
diff --git a/libpurple/core.c b/libpurple/core.c
index 8c6474c0cd..5f1e31739f 100644
--- a/libpurple/core.c
+++ b/libpurple/core.c
@@ -24,6 +24,7 @@
*/
#include "internal.h"
#include "cipher.h"
+#include "certificate.h"
#include "connection.h"
#include "conversation.h"
#include "core.h"
@@ -141,6 +142,7 @@ purple_core_init(const char *ui)
purple_accounts_init();
purple_savedstatuses_init();
purple_notify_init();
+ purple_certificate_init();
purple_connections_init();
purple_conversations_init();
purple_blist_init();
@@ -192,6 +194,7 @@ purple_core_quit(void)
purple_notify_uninit();
purple_conversations_uninit();
purple_connections_uninit();
+ purple_certificate_uninit();
purple_buddy_icons_uninit();
purple_accounts_uninit();
purple_savedstatuses_uninit();
diff --git a/libpurple/plugins/ssl/ssl-gnutls.c b/libpurple/plugins/ssl/ssl-gnutls.c
index e4d56cbbb6..8e391679f6 100644
--- a/libpurple/plugins/ssl/ssl-gnutls.c
+++ b/libpurple/plugins/ssl/ssl-gnutls.c
@@ -21,15 +21,18 @@
*/
#include "internal.h"
#include "debug.h"
+#include "certificate.h"
#include "plugin.h"
#include "sslconn.h"
#include "version.h"
+#include "util.h"
#define SSL_GNUTLS_PLUGIN_ID "ssl-gnutls"
#ifdef HAVE_GNUTLS
#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
typedef struct
{
@@ -44,9 +47,25 @@ static gnutls_certificate_client_credentials xcred;
static void
ssl_gnutls_init_gnutls(void)
{
+ /* Configure GnuTLS to use glib memory management */
+ /* I expect that this isn't really necessary, but it may prevent
+ some bugs */
+ /* TODO: It may be necessary to wrap this allocators for GnuTLS.
+ If there are strange bugs, perhaps look here (yes, I am a
+ hypocrite) */
+ gnutls_global_set_mem_functions(
+ (gnutls_alloc_function) g_malloc0, /* malloc */
+ (gnutls_alloc_function) g_malloc0, /* secure malloc */
+ NULL, /* mem_is_secure */
+ (gnutls_realloc_function) g_realloc, /* realloc */
+ (gnutls_free_function) g_free /* free */
+ );
+
gnutls_global_init();
gnutls_certificate_allocate_credentials(&xcred);
+
+ /* TODO: I can likely remove this */
gnutls_certificate_set_x509_trust_file(xcred, "ca.pem",
GNUTLS_X509_FMT_PEM);
}
@@ -65,6 +84,25 @@ ssl_gnutls_uninit(void)
gnutls_certificate_free_credentials(xcred);
}
+static void
+ssl_gnutls_verified_cb(PurpleCertificateVerificationStatus st,
+ gpointer userdata)
+{
+ PurpleSslConnection *gsc = (PurpleSslConnection *) userdata;
+
+ if (st == PURPLE_CERTIFICATE_VALID) {
+ /* Certificate valid? Good! Do the connection! */
+ gsc->connect_cb(gsc->connect_cb_data, gsc, PURPLE_INPUT_READ);
+ } else {
+ /* Otherwise, signal an error */
+ if(gsc->error_cb != NULL)
+ gsc->error_cb(gsc, PURPLE_SSL_CERTIFICATE_INVALID,
+ gsc->connect_cb_data);
+ purple_ssl_close(gsc);
+ }
+}
+
+
static void ssl_gnutls_handshake_cb(gpointer data, gint source,
PurpleInputCondition cond)
@@ -73,7 +111,7 @@ static void ssl_gnutls_handshake_cb(gpointer data, gint source,
PurpleSslGnutlsData *gnutls_data = PURPLE_SSL_GNUTLS_DATA(gsc);
ssize_t ret;
- purple_debug_info("gnutls", "Handshaking\n");
+ purple_debug_info("gnutls", "Handshaking with %s\n", gsc->host);
ret = gnutls_handshake(gnutls_data->session);
if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
@@ -94,7 +132,117 @@ static void ssl_gnutls_handshake_cb(gpointer data, gint source,
} else {
purple_debug_info("gnutls", "Handshake complete\n");
- gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
+ /* TODO: Remove all this debugging babble */
+ /* Now we are cooking with gas! */
+ PurpleSslOps *ops = purple_ssl_get_ops();
+ GList * peers = ops->get_peer_certificates(gsc);
+
+ PurpleCertificateScheme *x509 =
+ purple_certificate_find_scheme("x509");
+
+ GList * l;
+ for (l=peers; l; l = l->next) {
+ PurpleCertificate *crt = l->data;
+ GByteArray *z =
+ x509->get_fingerprint_sha1(crt);
+ gchar * fpr =
+ purple_base16_encode_chunked(z->data,
+ z->len);
+
+ purple_debug_info("gnutls/x509",
+ "Key print: %s\n",
+ fpr);
+
+ /* Kill the cert! */
+ x509->destroy_certificate(crt);
+
+ g_free(fpr);
+ g_byte_array_free(z, TRUE);
+ }
+ g_list_free(peers);
+
+ {
+ const gnutls_datum_t *cert_list;
+ unsigned int cert_list_size = 0;
+ gnutls_session_t session=gnutls_data->session;
+
+ cert_list =
+ gnutls_certificate_get_peers(session, &cert_list_size);
+
+ purple_debug_info("gnutls",
+ "Peer provided %d certs\n",
+ cert_list_size);
+ int i;
+ for (i=0; i<cert_list_size; i++)
+ {
+ gchar fpr_bin[256];
+ gsize fpr_bin_sz = sizeof(fpr_bin);
+ gchar * fpr_asc = NULL;
+ gchar tbuf[256];
+ gsize tsz=sizeof(tbuf);
+ gchar * tasc = NULL;
+ gnutls_x509_crt_t cert;
+
+ gnutls_x509_crt_init(&cert);
+ gnutls_x509_crt_import (cert, &cert_list[i],
+ GNUTLS_X509_FMT_DER);
+
+ gnutls_x509_crt_get_fingerprint(cert, GNUTLS_MAC_SHA,
+ fpr_bin, &fpr_bin_sz);
+
+ fpr_asc =
+ purple_base16_encode_chunked(fpr_bin,fpr_bin_sz);
+
+ purple_debug_info("gnutls",
+ "Lvl %d SHA1 fingerprint: %s\n",
+ i, fpr_asc);
+
+ tsz=sizeof(tbuf);
+ gnutls_x509_crt_get_serial(cert,tbuf,&tsz);
+ tasc=
+ purple_base16_encode_chunked(tbuf, tsz);
+ purple_debug_info("gnutls",
+ "Serial: %s\n",
+ tasc);
+ g_free(tasc);
+
+ tsz=sizeof(tbuf);
+ gnutls_x509_crt_get_dn (cert, tbuf, &tsz);
+ purple_debug_info("gnutls",
+ "Cert DN: %s\n",
+ tbuf);
+ tsz=sizeof(tbuf);
+ gnutls_x509_crt_get_issuer_dn (cert, tbuf, &tsz);
+ purple_debug_info("gnutls",
+ "Cert Issuer DN: %s\n",
+ tbuf);
+
+ g_free(fpr_asc); fpr_asc = NULL;
+ gnutls_x509_crt_deinit(cert);
+ }
+
+ }
+
+ /* TODO: The following logic should really be in libpurple */
+ /* If a Verifier was given, hand control over to it */
+ if (gsc->verifier) {
+ GList *peers;
+ /* First, get the peer cert chain */
+ peers = purple_ssl_get_peer_certificates(gsc);
+
+ /* Now kick off the verification process */
+ purple_certificate_verify(gsc->verifier,
+ gsc->host,
+ peers,
+ ssl_gnutls_verified_cb,
+ gsc);
+
+ purple_certificate_destroy_list(peers);
+ } else {
+ /* Otherwise, just call the "connection complete"
+ callback */
+ gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
+ }
}
}
@@ -213,6 +361,554 @@ ssl_gnutls_write(PurpleSslConnection *gsc, const void *data, size_t len)
return s;
}
+/* Forward declarations are fun!
+ TODO: This is a stupid place for this */
+static PurpleCertificate *
+x509_import_from_datum(const gnutls_datum_t dt, gnutls_x509_crt_fmt_t mode);
+
+static GList *
+ssl_gnutls_get_peer_certificates(PurpleSslConnection * gsc)
+{
+ PurpleSslGnutlsData *gnutls_data = PURPLE_SSL_GNUTLS_DATA(gsc);
+
+ /* List of Certificate instances to return */
+ GList * peer_certs = NULL;
+
+ /* List of raw certificates as given by GnuTLS */
+ const gnutls_datum_t *cert_list;
+ unsigned int cert_list_size = 0;
+
+ unsigned int i;
+
+ /* This should never, ever happen. */
+ g_return_val_if_fail( gnutls_certificate_type_get (gnutls_data->session) == GNUTLS_CRT_X509, NULL);
+
+ /* Get the certificate list from GnuTLS */
+ /* TODO: I am _pretty sure_ this doesn't block or do other exciting things */
+ cert_list = gnutls_certificate_get_peers(gnutls_data->session,
+ &cert_list_size);
+
+ /* Convert each certificate to a Certificate and append it to the list */
+ for (i = 0; i < cert_list_size; i++) {
+ PurpleCertificate * newcrt = x509_import_from_datum(cert_list[i],
+ GNUTLS_X509_FMT_DER);
+ /* Append is somewhat inefficient on linked lists, but is easy
+ to read. If someone complains, I'll change it.
+ TODO: Is anyone complaining? (Maybe elb?) */
+ peer_certs = g_list_append(peer_certs, newcrt);
+ }
+
+ /* cert_list shouldn't need free()-ing */
+ /* TODO: double-check this */
+
+ return peer_certs;
+}
+
+/************************************************************************/
+/* X.509 functionality */
+/************************************************************************/
+const gchar * SCHEME_NAME = "x509";
+
+static PurpleCertificateScheme x509_gnutls;
+
+/** Refcounted GnuTLS certificate data instance */
+typedef struct {
+ gint refcount;
+ gnutls_x509_crt_t crt;
+} x509_crtdata_t;
+
+/** Helper functions for reference counting */
+static x509_crtdata_t *
+x509_crtdata_addref(x509_crtdata_t *cd)
+{
+ (cd->refcount)++;
+ return cd;
+}
+
+static void
+x509_crtdata_delref(x509_crtdata_t *cd)
+{
+ g_assert(cd->refcount > 0);
+
+ (cd->refcount)--;
+
+ /* If the refcount reaches zero, kill the structure */
+ if (cd->refcount == 0) {
+ purple_debug_info("gnutls/x509",
+ "Freeing unused cert data at %p\n",
+ cd);
+ /* Kill the internal data */
+ gnutls_x509_crt_deinit( cd->crt );
+ /* And kill the struct */
+ g_free( cd );
+ }
+}
+
+/** 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_t containing an X.509 certificate into a Certificate instance under the x509_gnutls scheme
+ *
+ * @param dt Datum to transform
+ * @param mode GnuTLS certificate format specifier (GNUTLS_X509_FMT_PEM for
+ * reading from files, and GNUTLS_X509_FMT_DER for converting
+ * "over the wire" certs for SSL)
+ *
+ * @return A newly allocated Certificate structure of the x509_gnutls scheme
+ */
+static PurpleCertificate *
+x509_import_from_datum(const gnutls_datum_t dt, gnutls_x509_crt_fmt_t mode)
+{
+ /* Internal certificate data structure */
+ x509_crtdata_t *certdat;
+ /* New certificate to return */
+ PurpleCertificate * crt;
+
+ /* Allocate and prepare the internal certificate data */
+ certdat = g_new0(x509_crtdata_t, 1);
+ gnutls_x509_crt_init(&(certdat->crt));
+ certdat->refcount = 0;
+
+ /* Perform the actual certificate parse */
+ /* Yes, certdat->crt should be passed as-is */
+ gnutls_x509_crt_import(certdat->crt, &dt, mode);
+
+ /* Allocate the certificate and load it with data */
+ crt = g_new0(PurpleCertificate, 1);
+ crt->scheme = &x509_gnutls;
+ crt->data = x509_crtdata_addref(certdat);
+
+ return crt;
+}
+
+/** Imports a PEM-formatted X.509 certificate from the specified file.
+ * @param filename Filename to import from. Format is PEM
+ *
+ * @return A newly allocated Certificate structure of the x509_gnutls scheme
+ */
+static PurpleCertificate *
+x509_import_from_file(const gchar * filename)
+{
+ PurpleCertificate *crt; /* Certificate being constructed */
+ gchar *buf; /* Used to load the raw file data */
+ gsize buf_sz; /* Size of the above */
+ gnutls_datum_t dt; /* Struct to pass down to GnuTLS */
+
+ purple_debug_info("gnutls",
+ "Attempting to load X.509 certificate from %s\n",
+ filename);
+
+ /* Next, we'll simply yank the entire contents of the file
+ into memory */
+ /* TODO: Should I worry about very large files here? */
+ /* TODO: Error checking */
+ g_file_get_contents(filename,
+ &buf,
+ &buf_sz,
+ NULL /* No error checking for now */
+ );
+
+ /* Load the datum struct */
+ dt.data = (unsigned char *) buf;
+ dt.size = buf_sz;
+
+ /* Perform the conversion */
+ crt = x509_import_from_datum(dt,
+ GNUTLS_X509_FMT_PEM); // files should be in PEM format
+
+ /* Cleanup */
+ g_free(buf);
+
+ return crt;
+}
+
+/**
+ * Exports a PEM-formatted X.509 certificate to the specified file.
+ * @param filename Filename to export to. Format will be PEM
+ * @param crt Certificate to export
+ *
+ * @return TRUE if success, otherwise FALSE
+ */
+static gboolean
+x509_export_certificate(const gchar *filename, PurpleCertificate *crt)
+{
+ gnutls_x509_crt_t crt_dat; /* GnuTLS cert struct */
+ int ret;
+ gchar * out_buf; /* Data to output */
+ size_t out_size; /* Output size */
+ gboolean success = FALSE;
+
+ /* Paranoia paranoia paranoia! */
+ g_return_val_if_fail(filename, FALSE);
+ g_return_val_if_fail(crt, FALSE);
+ g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+ g_return_val_if_fail(crt->data, FALSE);
+
+ crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+ /* Obtain the output size required */
+ out_size = 0;
+ ret = gnutls_x509_crt_export(crt_dat, GNUTLS_X509_FMT_PEM,
+ NULL, /* Provide no buffer yet */
+ &out_size /* Put size here */
+ );
+ g_return_val_if_fail(ret == GNUTLS_E_SHORT_MEMORY_BUFFER, FALSE);
+
+ /* Now allocate a buffer and *really* export it */
+ out_buf = g_new0(gchar, out_size);
+ ret = gnutls_x509_crt_export(crt_dat, GNUTLS_X509_FMT_PEM,
+ out_buf, /* Export to our new buffer */
+ &out_size /* Put size here */
+ );
+ if (ret != 0) {
+ purple_debug_error("gnutls/x509",
+ "Failed to export cert to buffer with code %d\n",
+ ret);
+ g_free(out_buf);
+ return FALSE;
+ }
+
+ /* Write it out to an actual file */
+ /* TODO: THIS IS A COMPATIBILITY VIOLATION
+ Look into util.c write_data_to_file. */
+ success = g_file_set_contents(filename,
+ out_buf,
+ out_size,
+ NULL);
+
+
+ g_free(out_buf);
+ g_return_val_if_fail(success, FALSE);
+ return success;
+}
+
+static PurpleCertificate *
+x509_copy_certificate(PurpleCertificate *crt)
+{
+ x509_crtdata_t *crtdat;
+ PurpleCertificate *newcrt;
+
+ g_return_val_if_fail(crt, NULL);
+ g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+ crtdat = (x509_crtdata_t *) crt->data;
+
+ newcrt = g_new0(PurpleCertificate, 1);
+ newcrt->scheme = &x509_gnutls;
+ newcrt->data = x509_crtdata_addref(crtdat);
+
+ return newcrt;
+}
+/** Frees a Certificate
+ *
+ * Destroys a Certificate's internal data structures and frees the pointer
+ * given.
+ * @param crt Certificate instance to be destroyed. It WILL NOT be destroyed
+ * if it is not of the correct CertificateScheme. Can be NULL
+ *
+ */
+static void
+x509_destroy_certificate(PurpleCertificate * crt)
+{
+ /* TODO: Issue a warning here? */
+ if (NULL == crt) return;
+
+ /* Check that the scheme is x509_gnutls */
+ if ( crt->scheme != &x509_gnutls ) {
+ purple_debug_error("gnutls",
+ "destroy_certificate attempted on certificate of wrong scheme (scheme was %s, expected %s)\n",
+ crt->scheme->name,
+ SCHEME_NAME);
+ return;
+ }
+
+ /* TODO: Different error checking? */
+ g_return_if_fail(crt->data != NULL);
+ g_return_if_fail(crt->scheme != NULL);
+
+ /* Use the reference counting system to free (or not) the
+ underlying data */
+ x509_crtdata_delref((x509_crtdata_t *)crt->data);
+
+ /* Kill the structure itself */
+ g_free(crt);
+}
+
+/** Determines whether one certificate has been issued and signed by another
+ *
+ * @param crt Certificate to check the signature of
+ * @param issuer Issuer's certificate
+ *
+ * @return TRUE if crt was signed and issued by issuer, otherwise FALSE
+ * @TODO Modify this function to return a reason for invalidity?
+ */
+static gboolean
+x509_certificate_signed_by(PurpleCertificate * crt,
+ PurpleCertificate * issuer)
+{
+ gnutls_x509_crt_t crt_dat;
+ gnutls_x509_crt_t issuer_dat;
+ unsigned int verify; /* used to store result from GnuTLS verifier */
+ int ret;
+
+ /* TODO: Change this error checking? */
+ g_return_val_if_fail(crt, FALSE);
+ g_return_val_if_fail(issuer, FALSE);
+
+ /* Verify that both certs are the correct scheme */
+ g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+ g_return_val_if_fail(issuer->scheme == &x509_gnutls, FALSE);
+
+ /* TODO: check for more nullness? */
+
+ crt_dat = X509_GET_GNUTLS_DATA(crt);
+ issuer_dat = X509_GET_GNUTLS_DATA(issuer);
+
+ /* First, let's check that crt.issuer is actually issuer */
+ ret = gnutls_x509_crt_check_issuer(crt_dat, issuer_dat);
+ if (ret <= 0) {
+
+ if (ret < 0) {
+ purple_debug_error("gnutls/x509",
+ "GnuTLS error %d while checking certificate issuer match.",
+ ret);
+ } else {
+ gchar *crt_id, *issuer_id, *crt_issuer_id;
+ crt_id = purple_certificate_get_unique_id(crt);
+ issuer_id = purple_certificate_get_unique_id(issuer);
+ crt_issuer_id =
+ purple_certificate_get_issuer_unique_id(crt);
+ purple_debug_info("gnutls/x509",
+ "Certificate for %s claims to be "
+ "issued by %s, but the certificate "
+ "for %s does not match. A strcmp "
+ "says %d\n",
+ crt_id, crt_issuer_id, issuer_id,
+ strcmp(crt_issuer_id, issuer_id));
+ g_free(crt_id);
+ g_free(issuer_id);
+ g_free(crt_issuer_id);
+ }
+
+ /* The issuer is not correct, or there were errors */
+ return FALSE;
+ }
+
+ /* Now, check the signature */
+ /* The second argument is a ptr to an array of "trusted" issuer certs,
+ but we're only using one trusted one */
+ ret = gnutls_x509_crt_verify(crt_dat, &issuer_dat, 1,
+ /* Permit signings by X.509v1 certs
+ (Verisign and possibly others have
+ root certificates that predate the
+ current standard) */
+ GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT,
+ &verify);
+
+ if (ret != 0) {
+ purple_debug_error("gnutls/x509",
+ "Attempted certificate verification caused a GnuTLS error code %d. I will just say the signature is bad, but you should look into this.\n", ret);
+ return FALSE;
+ }
+
+ if (verify & GNUTLS_CERT_INVALID) {
+ /* Signature didn't check out, but at least
+ there were no errors*/
+ gchar *crt_id = purple_certificate_get_unique_id(crt);
+ gchar *issuer_id = purple_certificate_get_issuer_unique_id(crt);
+ purple_debug_info("gnutls/x509",
+ "Bad signature for %s on %s\n",
+ issuer_id, crt_id);
+ g_free(crt_id);
+ g_free(issuer_id);
+
+ return FALSE;
+ } /* if (ret, etc.) */
+
+ /* If we got here, the signature is good */
+ return TRUE;
+}
+
+static GByteArray *
+x509_sha1sum(PurpleCertificate *crt)
+{
+ size_t hashlen = 20; /* SHA1 hashes are 20 bytes */
+ size_t tmpsz = hashlen; /* Throw-away variable for GnuTLS to stomp on*/
+ gnutls_x509_crt_t crt_dat;
+ GByteArray *hash; /**< Final hash container */
+ guchar hashbuf[hashlen]; /**< Temporary buffer to contain hash */
+
+ g_return_val_if_fail(crt, NULL);
+
+ crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+ /* Extract the fingerprint */
+ /* TODO: Errorcheck? */
+ gnutls_x509_crt_get_fingerprint(crt_dat, GNUTLS_MAC_SHA,
+ hashbuf, &tmpsz);
+
+ /* This shouldn't happen */
+ g_return_val_if_fail(tmpsz == hashlen, NULL);
+
+ /* Okay, now create and fill hash array */
+ hash = g_byte_array_new();
+ g_byte_array_append(hash, hashbuf, hashlen);
+
+ return hash;
+}
+
+static gchar *
+x509_cert_dn (PurpleCertificate *crt)
+{
+ gnutls_x509_crt_t cert_dat;
+ gchar *dn = NULL;
+ size_t dn_size;
+
+ g_return_val_if_fail(crt, NULL);
+ g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+ cert_dat = X509_GET_GNUTLS_DATA(crt);
+
+ /* TODO: Note return values? */
+
+ /* Figure out the length of the Distinguished Name */
+ /* Claim that the buffer is size 0 so GnuTLS just tells us how much
+ space it needs */
+ dn_size = 0;
+ gnutls_x509_crt_get_dn(cert_dat, dn, &dn_size);
+
+ /* Now allocate and get the Distinguished Name */
+ dn = g_new0(gchar, dn_size);
+ gnutls_x509_crt_get_dn(cert_dat, dn, &dn_size);
+
+ return dn;
+}
+
+static gchar *
+x509_issuer_dn (PurpleCertificate *crt)
+{
+ gnutls_x509_crt_t cert_dat;
+ gchar *dn = NULL;
+ size_t dn_size;
+
+ g_return_val_if_fail(crt, NULL);
+ g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+ cert_dat = X509_GET_GNUTLS_DATA(crt);
+
+ /* TODO: Note return values? */
+
+ /* Figure out the length of the Distinguished Name */
+ /* Claim that the buffer is size 0 so GnuTLS just tells us how much
+ space it needs */
+ dn_size = 0;
+ gnutls_x509_crt_get_issuer_dn(cert_dat, dn, &dn_size);
+
+ /* Now allocate and get the Distinguished Name */
+ dn = g_new0(gchar, dn_size);
+ gnutls_x509_crt_get_issuer_dn(cert_dat, dn, &dn_size);
+
+ return dn;
+}
+
+static gchar *
+x509_common_name (PurpleCertificate *crt)
+{
+ gnutls_x509_crt_t cert_dat;
+ gchar *cn = NULL;
+ size_t cn_size;
+
+ g_return_val_if_fail(crt, NULL);
+ g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+ cert_dat = X509_GET_GNUTLS_DATA(crt);
+
+ /* TODO: Note return values? */
+
+ /* Figure out the length of the Common Name */
+ /* Claim that the buffer is size 0 so GnuTLS just tells us how much
+ space it needs */
+ cn_size = 0;
+ gnutls_x509_crt_get_dn_by_oid(cert_dat,
+ GNUTLS_OID_X520_COMMON_NAME,
+ 0, /* First CN found, please */
+ 0, /* Not in raw mode */
+ cn, &cn_size);
+
+ /* Now allocate and get the Common Name */
+ cn = g_new0(gchar, cn_size);
+ gnutls_x509_crt_get_dn_by_oid(cert_dat,
+ GNUTLS_OID_X520_COMMON_NAME,
+ 0, /* First CN found, please */
+ 0, /* Not in raw mode */
+ cn, &cn_size);
+
+ return cn;
+}
+
+static gboolean
+x509_check_name (PurpleCertificate *crt, const gchar *name)
+{
+ gnutls_x509_crt_t crt_dat;
+
+ g_return_val_if_fail(crt, FALSE);
+ g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+ g_return_val_if_fail(name, FALSE);
+
+ crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+ if (gnutls_x509_crt_check_hostname(crt_dat, name)) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+static gboolean
+x509_times (PurpleCertificate *crt, time_t *activation, time_t *expiration)
+{
+ gnutls_x509_crt_t crt_dat;
+ /* GnuTLS time functions return this on error */
+ const time_t errval = (time_t) (-1);
+
+
+ g_return_val_if_fail(crt, FALSE);
+ g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+
+ crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+ if (activation) {
+ *activation = gnutls_x509_crt_get_activation_time(crt_dat);
+ }
+ if (expiration) {
+ *expiration = gnutls_x509_crt_get_expiration_time(crt_dat);
+ }
+
+ if (*activation == errval || *expiration == errval) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* X.509 certificate operations provided by this plugin */
+/* TODO: Flesh this out! */
+static PurpleCertificateScheme x509_gnutls = {
+ "x509", /* Scheme name */
+ N_("X.509 Certificates"), /* User-visible scheme name */
+ x509_import_from_file, /* Certificate import function */
+ x509_export_certificate, /* Certificate export function */
+ x509_copy_certificate, /* Copy */
+ x509_destroy_certificate, /* Destroy cert */
+ x509_certificate_signed_by, /* Signature checker */
+ x509_sha1sum, /* SHA1 fingerprint */
+ x509_cert_dn, /* Unique ID */
+ x509_issuer_dn, /* Issuer Unique ID */
+ x509_common_name, /* Subject name */
+ x509_check_name, /* Check subject name */
+ x509_times /* Activation/Expiration time */
+};
+
static PurpleSslOps ssl_ops =
{
ssl_gnutls_init,
@@ -221,11 +917,11 @@ static PurpleSslOps ssl_ops =
ssl_gnutls_close,
ssl_gnutls_read,
ssl_gnutls_write,
+ ssl_gnutls_get_peer_certificates,
/* padding */
NULL,
NULL,
- NULL,
NULL
};
@@ -242,6 +938,10 @@ plugin_load(PurplePlugin *plugin)
/* Init GNUTLS now so others can use it even if sslconn never does */
ssl_gnutls_init_gnutls();
+ /* Register that we're providing an X.509 CertScheme */
+ /* @TODO : error checking */
+ purple_certificate_register_scheme( &x509_gnutls );
+
return TRUE;
#else
return FALSE;
@@ -255,6 +955,8 @@ plugin_unload(PurplePlugin *plugin)
if(purple_ssl_get_ops() == &ssl_ops) {
purple_ssl_set_ops(NULL);
}
+
+ purple_certificate_unregister_scheme( &x509_gnutls );
#endif
return TRUE;
diff --git a/libpurple/plugins/ssl/ssl-nss.c b/libpurple/plugins/ssl/ssl-nss.c
index a0dcd45ce5..1428846817 100644
--- a/libpurple/plugins/ssl/ssl-nss.c
+++ b/libpurple/plugins/ssl/ssl-nss.c
@@ -21,6 +21,7 @@
*/
#include "internal.h"
#include "debug.h"
+#include "certificate.h"
#include "plugin.h"
#include "sslconn.h"
#include "version.h"
@@ -360,6 +361,284 @@ ssl_nss_write(PurpleSslConnection *gsc, const void *data, size_t len)
return ret;
}
+static GList *
+ssl_nss_peer_certs(PurpleSslConnection *gsc)
+{
+ PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc);
+ GList *chain = NULL;
+ CERTCertificate *cert;
+ void *pinArg;
+ SECStatus status;
+
+ /* TODO: this is a blind guess */
+ cert = SSL_PeerCertificate(nss_data->fd);
+
+
+
+ return NULL;
+}
+
+/************************************************************************/
+/* X.509 functionality */
+/************************************************************************/
+static PurpleCertificateScheme x509_nss;
+
+/** Helpr macro to retrieve the NSS certdata from a PurpleCertificate */
+#define X509_NSS_DATA(pcrt) ( (CERTCertificate * ) (pcrt->data) )
+
+/** Imports a PEM-formatted X.509 certificate from the specified file.
+ * @param filename Filename to import from. Format is PEM
+ *
+ * @return A newly allocated Certificate structure of the x509_gnutls scheme
+ */
+static PurpleCertificate *
+x509_import_from_file(const gchar *filename)
+{
+ gchar *rawcert;
+ gsize len = 0;
+ CERTCertificate *crt_dat;
+ PurpleCertificate *crt;
+
+ g_return_val_if_fail(filename, NULL);
+
+ purple_debug_info("nss/x509",
+ "Loading certificate from %s\n",
+ filename);
+
+ /* Load the raw data up */
+ g_return_val_if_fail(
+ g_file_get_contents(filename,
+ &rawcert, &len,
+ NULL ),
+ NULL);
+
+ /* Decode the certificate */
+ crt_dat = CERT_DecodeCertFromPackage(rawcert, len);
+ g_free(rawcert);
+
+ g_return_val_if_fail(crt_dat, NULL);
+
+ crt = g_new0(PurpleCertificate, 1);
+ crt->scheme = &x509_nss;
+ crt->data = crt_dat;
+
+ return crt;
+}
+
+/**
+ * Exports a PEM-formatted X.509 certificate to the specified file.
+ * @param filename Filename to export to. Format will be PEM
+ * @param crt Certificate to export
+ *
+ * @return TRUE if success, otherwise FALSE
+ */
+static gboolean
+x509_export_certificate(const gchar *filename, PurpleCertificate *crt)
+{
+ /* TODO: WRITEME */
+ return FALSE;
+}
+
+static PurpleCertificate *
+x509_copy_certificate(PurpleCertificate *crt)
+{
+ CERTCertificate *crt_dat;
+ PurpleCertificate *newcrt;
+
+ g_return_val_if_fail(crt, NULL);
+ g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
+
+ crt_dat = X509_NSS_DATA(crt);
+ g_return_val_if_fail(crt_dat, NULL);
+
+ /* Create the certificate copy */
+ newcrt = g_new0(PurpleCertificate, 1);
+ newcrt->scheme = &x509_nss;
+ /* NSS does refcounting automatically */
+ newcrt->data = CERT_DupCertificate(crt_dat);
+
+ return newcrt;
+}
+
+/** Frees a Certificate
+ *
+ * Destroys a Certificate's internal data structures and frees the pointer
+ * given.
+ * @param crt Certificate instance to be destroyed. It WILL NOT be destroyed
+ * if it is not of the correct CertificateScheme. Can be NULL
+ *
+ */
+static void
+x509_destroy_certificate(PurpleCertificate * crt)
+{
+ CERTCertificate *crt_dat;
+
+ g_return_if_fail(crt);
+ g_return_if_fail(crt->scheme == &x509_nss);
+
+ crt_dat = X509_NSS_DATA(crt);
+ g_return_if_fail(crt_dat);
+
+ /* Finally we have the certificate. So let's kill it */
+ /* NSS does refcounting automatically */
+ CERT_DestroyCertificate(crt_dat);
+
+ /* Delete the PurpleCertificate as well */
+ g_free(crt);
+}
+
+/** Determines whether one certificate has been issued and signed by another
+ *
+ * @param crt Certificate to check the signature of
+ * @param issuer Issuer's certificate
+ *
+ * @return TRUE if crt was signed and issued by issuer, otherwise FALSE
+ * @TODO Modify this function to return a reason for invalidity?
+ */
+static gboolean
+x509_certificate_signed_by(PurpleCertificate * crt,
+ PurpleCertificate * issuer)
+{
+ return FALSE;
+}
+
+static GByteArray *
+x509_sha1sum(PurpleCertificate *crt)
+{
+ CERTCertificate *crt_dat;
+ size_t hashlen = 20; /* Size of an sha1sum */
+ GByteArray *sha1sum;
+ SECItem *derCert; /* DER representation of the cert */
+ SECStatus st;
+
+ g_return_val_if_fail(crt, NULL);
+ g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
+
+ crt_dat = X509_NSS_DATA(crt);
+ g_return_val_if_fail(crt_dat, NULL);
+
+ /* Get the certificate DER representation */
+ derCert = &(crt_dat->derCert);
+
+ /* Make a hash! */
+ sha1sum = g_byte_array_sized_new(hashlen);
+ st = PK11_HashBuf(SEC_OID_SHA1, sha1sum->data,
+ derCert->data, derCert->len);
+
+ /* Check for errors */
+ if (st != SECSuccess) {
+ g_byte_array_free(sha1sum, TRUE);
+ purple_debug_error("nss/x509",
+ "Error: hashing failed!\n");
+ return NULL;
+ }
+
+ return sha1sum;
+}
+
+static gchar *
+x509_common_name (PurpleCertificate *crt)
+{
+ CERTCertificate *crt_dat;
+ char *nss_cn;
+ gchar *ret_cn;
+
+ g_return_val_if_fail(crt, NULL);
+ g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
+
+ crt_dat = X509_NSS_DATA(crt);
+ g_return_val_if_fail(crt_dat, NULL);
+
+ /* Q:
+ Why get a newly allocated string out of NSS, strdup it, and then
+ return the new copy?
+
+ A:
+ The NSS LXR docs state that I should use the NSPR free functions on
+ the strings that the NSS cert functions return. Since the libpurple
+ API expects a g_free()-able string, we make our own copy and return
+ that.
+
+ NSPR is something of a prima donna. */
+
+ nss_cn = CERT_GetCommonName( &(crt_dat->subject) );
+ ret_cn = g_strdup(nss_cn);
+ PORT_Free(nss_cn);
+
+ return ret_cn;
+}
+
+static gboolean
+x509_check_name (PurpleCertificate *crt, const gchar *name)
+{
+ CERTCertificate *crt_dat;
+ SECStatus st;
+
+ g_return_val_if_fail(crt, FALSE);
+ g_return_val_if_fail(crt->scheme == &x509_nss, FALSE);
+
+ crt_dat = X509_NSS_DATA(crt);
+ g_return_val_if_fail(crt_dat, FALSE);
+
+ st = CERT_VerifyCertName(crt_dat, name);
+
+ if (st == SECSuccess) {
+ return TRUE;
+ }
+ else if (st == SECFailure) {
+ return FALSE;
+ }
+
+ /* If we get here...bad things! */
+ g_assert(FALSE);
+ return FALSE;
+}
+
+static gboolean
+x509_times (PurpleCertificate *crt, time_t *activation, time_t *expiration)
+{
+ CERTCertificate *crt_dat;
+ PRTime nss_activ, nss_expir;
+
+ g_return_val_if_fail(crt, FALSE);
+ g_return_val_if_fail(crt->scheme == &x509_nss, FALSE);
+
+ crt_dat = X509_NSS_DATA(crt);
+ g_return_val_if_fail(crt_dat, FALSE);
+
+ /* Extract the times into ugly PRTime thingies */
+ /* TODO: Maybe this shouldn't throw an error? */
+ g_return_val_if_fail(
+ SECSuccess == CERT_GetCertTimes(crt_dat,
+ &nss_activ, &nss_expir),
+ FALSE);
+
+ if (activation) {
+ *activation = nss_activ;
+ }
+ if (expiration) {
+ *expiration = nss_expir;
+ }
+
+ return TRUE;
+}
+
+static PurpleCertificateScheme x509_nss = {
+ "x509", /* Scheme name */
+ N_("X.509 Certificates"), /* User-visible scheme name */
+ x509_import_from_file, /* Certificate import function */
+ x509_export_certificate, /* Certificate export function */
+ x509_copy_certificate, /* Copy */
+ x509_destroy_certificate, /* Destroy cert */
+ NULL, /* Signed-by */
+ x509_sha1sum, /* SHA1 fingerprint */
+ NULL, /* Unique ID */
+ NULL, /* Issuer Unique ID */
+ x509_common_name, /* Subject name */
+ x509_check_name, /* Check subject name */
+ x509_times /* Activation/Expiration time */
+};
+
static PurpleSslOps ssl_ops =
{
ssl_nss_init,
@@ -368,11 +647,11 @@ static PurpleSslOps ssl_ops =
ssl_nss_close,
ssl_nss_read,
ssl_nss_write,
+ ssl_nss_peer_certs,
/* padding */
NULL,
NULL,
- NULL,
NULL
};
@@ -390,6 +669,9 @@ plugin_load(PurplePlugin *plugin)
/* Init NSS now, so others can use it even if sslconn never does */
ssl_nss_init_nss();
+ /* Register the X.509 functions we provide */
+ purple_certificate_register_scheme(&x509_nss);
+
return TRUE;
#else
return FALSE;
@@ -403,6 +685,9 @@ plugin_unload(PurplePlugin *plugin)
if (purple_ssl_get_ops() == &ssl_ops) {
purple_ssl_set_ops(NULL);
}
+
+ /* Unregister our X.509 functions */
+ purple_certificate_unregister_scheme(&x509_nss);
#endif
return TRUE;
diff --git a/libpurple/prefs.h b/libpurple/prefs.h
index 51f34335a0..c41493e4ab 100644
--- a/libpurple/prefs.h
+++ b/libpurple/prefs.h
@@ -55,7 +55,9 @@ extern "C" {
#endif
/**************************************************************************/
-/** @name Prefs API */
+/** @name Prefs API
+ Preferences are named according to a directory-like structure.
+ Example: "/plugins/core/potato/is_from_idaho" (probably a boolean) */
/**************************************************************************/
/*@{*/
diff --git a/libpurple/protocols/irc/irc.c b/libpurple/protocols/irc/irc.c
index 3170ab789a..a783ae0fbc 100644
--- a/libpurple/protocols/irc/irc.c
+++ b/libpurple/protocols/irc/irc.c
@@ -433,14 +433,7 @@ irc_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
irc->gsc = NULL;
- switch(error) {
- case PURPLE_SSL_CONNECT_FAILED:
- purple_connection_error(gc, _("Connection Failed"));
- break;
- case PURPLE_SSL_HANDSHAKE_FAILED:
- purple_connection_error(gc, _("SSL Handshake Failed"));
- break;
- }
+ purple_connection_error(gc, purple_ssl_strerror(error));
}
static void irc_close(PurpleConnection *gc)
diff --git a/libpurple/protocols/jabber/jabber.c b/libpurple/protocols/jabber/jabber.c
index f0e39e1913..c5498112fe 100644
--- a/libpurple/protocols/jabber/jabber.c
+++ b/libpurple/protocols/jabber/jabber.c
@@ -494,29 +494,20 @@ jabber_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
js = gc->proto_data;
js->gsc = NULL;
- switch(error) {
- case PURPLE_SSL_CONNECT_FAILED:
- purple_connection_error(gc, _("Connection Failed"));
- break;
- case PURPLE_SSL_HANDSHAKE_FAILED:
- purple_connection_error(gc, _("SSL Handshake Failed"));
- break;
- }
+ purple_connection_error(gc, purple_ssl_strerror(error));
}
static void tls_init(JabberStream *js)
{
purple_input_remove(js->gc->inpa);
js->gc->inpa = 0;
- js->gsc = purple_ssl_connect_fd(js->gc->account, js->fd,
- jabber_login_callback_ssl, jabber_ssl_connect_failure, js->gc);
+ js->gsc = purple_ssl_connect_with_host_fd(js->gc->account, js->fd,
+ jabber_login_callback_ssl, jabber_ssl_connect_failure, js->serverFQDN, js->gc);
}
static void jabber_login_connect(JabberStream *js, const char *fqdn, const char *host, int port)
{
-#ifdef HAVE_CYRUS_SASL
js->serverFQDN = g_strdup(fqdn);
-#endif
if (purple_proxy_connect(js->gc, js->gc->account, host,
port, jabber_login_callback, js->gc) == NULL)
@@ -1025,9 +1016,9 @@ void jabber_close(PurpleConnection *gc)
g_string_free(js->sasl_mechs, TRUE);
if(js->sasl_cb)
g_free(js->sasl_cb);
+#endif
if(js->serverFQDN)
g_free(js->serverFQDN);
-#endif
g_free(js->server_name);
g_free(js->gmail_last_time);
g_free(js->gmail_last_tid);
diff --git a/libpurple/protocols/jabber/jabber.h b/libpurple/protocols/jabber/jabber.h
index b9c498efdb..b0005558d4 100644
--- a/libpurple/protocols/jabber/jabber.h
+++ b/libpurple/protocols/jabber/jabber.h
@@ -136,6 +136,8 @@ typedef struct _JabberStream
char *gmail_last_time;
char *gmail_last_tid;
+ char *serverFQDN;
+
/* OK, this stays at the end of the struct, so plugins can depend
* on the rest of the stuff being in the right place
*/
@@ -150,7 +152,6 @@ typedef struct _JabberStream
int sasl_state;
int sasl_maxbuf;
GString *sasl_mechs;
- char *serverFQDN;
gboolean vcard_fetched;
diff --git a/libpurple/sslconn.c b/libpurple/sslconn.c
index b42e987234..f31f4f0849 100644
--- a/libpurple/sslconn.c
+++ b/libpurple/sslconn.c
@@ -24,6 +24,7 @@
*/
#include "internal.h"
+#include "certificate.h"
#include "debug.h"
#include "sslconn.h"
@@ -117,6 +118,9 @@ purple_ssl_connect(PurpleAccount *account, const char *host, int port,
gsc->connect_cb = func;
gsc->error_cb = error_func;
+ /* 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 (gsc->connect_data == NULL)
@@ -151,10 +155,37 @@ purple_ssl_input_add(PurpleSslConnection *gsc, PurpleSslInputFunction func,
gsc->inpa = purple_input_add(gsc->fd, PURPLE_INPUT_READ, recv_cb, gsc);
}
+const gchar *
+purple_ssl_strerror(PurpleSslErrorType error)
+{
+ switch(error) {
+ case PURPLE_SSL_CONNECT_FAILED:
+ return _("SSL Connection Failed");
+ case PURPLE_SSL_HANDSHAKE_FAILED:
+ return _("SSL Handshake Failed");
+ case PURPLE_SSL_CERTIFICATE_INVALID:
+ return _("SSL peer presented an invalid certificate");
+ default:
+ purple_debug_warning("sslconn", "Unknown SSL error code %d\n", error);
+ return _("Unknown SSL error");
+ }
+}
+
PurpleSslConnection *
purple_ssl_connect_fd(PurpleAccount *account, int fd,
PurpleSslInputFunction func,
- PurpleSslErrorFunction error_func, void *data)
+ PurpleSslErrorFunction error_func,
+ void *data)
+{
+ return purple_ssl_connect_with_host_fd(account, fd, func, error_func, NULL, data);
+}
+
+PurpleSslConnection *
+purple_ssl_connect_with_host_fd(PurpleAccount *account, int fd,
+ PurpleSslInputFunction func,
+ PurpleSslErrorFunction error_func,
+ const char *host,
+ void *data)
{
PurpleSslConnection *gsc;
PurpleSslOps *ops;
@@ -175,7 +206,13 @@ purple_ssl_connect_fd(PurpleAccount *account, int fd,
gsc->connect_cb = func;
gsc->error_cb = error_func;
gsc->fd = fd;
+ if(host)
+ gsc->host = g_strdup(host);
+ /* TODO: Move this elsewhere */
+ gsc->verifier = purple_certificate_find_verifier("x509","tls_cached");
+
+
ops = purple_ssl_get_ops();
ops->connectfunc(gsc);
@@ -231,6 +268,17 @@ purple_ssl_write(PurpleSslConnection *gsc, const void *data, size_t len)
return (ops->write)(gsc, data, len);
}
+GList *
+purple_ssl_get_peer_certificates(PurpleSslConnection *gsc)
+{
+ PurpleSslOps *ops;
+
+ g_return_val_if_fail(gsc != NULL, NULL);
+
+ ops = purple_ssl_get_ops();
+ return (ops->get_peer_certificates)(gsc);
+}
+
void
purple_ssl_set_ops(PurpleSslOps *ops)
{
@@ -246,8 +294,10 @@ purple_ssl_get_ops(void)
void
purple_ssl_init(void)
{
- /* This doesn't do anything at the moment. All the actual init work
- * is handled by purple_ssl_is_supported upon demand. */
+ /* Although purple_ssl_is_supported will do the initialization on
+ command, SSL plugins tend to register CertificateSchemes as well
+ as providing SSL ops. */
+ g_assert(ssl_init());
}
void
diff --git a/libpurple/sslconn.h b/libpurple/sslconn.h
index 5f1a6593c9..e02e338b6c 100644
--- a/libpurple/sslconn.h
+++ b/libpurple/sslconn.h
@@ -25,6 +25,7 @@
#ifndef _PURPLE_SSLCONN_H_
#define _PURPLE_SSLCONN_H_
+#include "certificate.h"
#include "proxy.h"
#define PURPLE_SSL_DEFAULT_PORT 443
@@ -32,7 +33,8 @@
typedef enum
{
PURPLE_SSL_HANDSHAKE_FAILED = 1,
- PURPLE_SSL_CONNECT_FAILED = 2
+ PURPLE_SSL_CONNECT_FAILED = 2,
+ PURPLE_SSL_CERTIFICATE_INVALID = 3
} PurpleSslErrorType;
typedef struct _PurpleSslConnection PurpleSslConnection;
@@ -69,6 +71,9 @@ struct _PurpleSslConnection
/** Internal connection data managed by the SSL backend (GnuTLS/LibNSS/whatever) */
void *private_data;
+
+ /** Verifier to use in authenticating the peer */
+ PurpleCertificateVerifier *verifier;
};
/**
@@ -107,8 +112,17 @@ typedef struct
* @return The number of bytes written (may be less than len) or <0 on error
*/
size_t (*write)(PurpleSslConnection *gsc, const void *data, size_t len);
-
- void (*_purple_reserved1)(void);
+ /** Obtains the certificate chain provided by the peer
+ *
+ * @param gsc Connection context
+ * @return A newly allocated list containing the certificates
+ * the peer provided.
+ * @see PurpleCertificate
+ * @todo Decide whether the ordering of certificates in this
+ * list can be guaranteed.
+ */
+ GList * (* get_peer_certificates)(PurpleSslConnection * gsc);
+
void (*_purple_reserved2)(void);
void (*_purple_reserved3)(void);
void (*_purple_reserved4)(void);
@@ -131,6 +145,14 @@ extern "C" {
gboolean purple_ssl_is_supported(void);
/**
+ * Returns a human-readable string for an SSL error
+ *
+ * @param error Error code
+ * @return Human-readable error explanation
+ */
+const gchar * purple_ssl_strerror(PurpleSslErrorType error);
+
+/**
* Makes a SSL connection to the specified host and port. The caller
* should keep track of the returned value and use it to cancel the
* connection, if needed.
@@ -154,6 +176,7 @@ PurpleSslConnection *purple_ssl_connect(PurpleAccount *account, const char *host
/**
* Makes a SSL connection using an already open file descriptor.
+ * DEPRECATED. Use purple_ssl_connect_with_host_fd instead.
*
* @param account The account making the connection.
* @param fd The file descriptor.
@@ -166,7 +189,25 @@ PurpleSslConnection *purple_ssl_connect(PurpleAccount *account, const char *host
PurpleSslConnection *purple_ssl_connect_fd(PurpleAccount *account, int fd,
PurpleSslInputFunction func,
PurpleSslErrorFunction error_func,
- void *data);
+ void *data);
+
+/**
+ * Makes a SSL connection using an already open file descriptor.
+ *
+ * @param account The account making the connection.
+ * @param fd The file descriptor.
+ * @param func The SSL input handler function.
+ * @param error_func The SSL error handler function.
+ * @param host The hostname of the other peer (to verify the CN)
+ * @param data User-defined data.
+ *
+ * @return The SSL connection handle.
+ */
+PurpleSslConnection *purple_ssl_connect_with_host_fd(PurpleAccount *account, int fd,
+ PurpleSslInputFunction func,
+ PurpleSslErrorFunction error_func,
+ const char *host,
+ void *data);
/**
* Adds an input watcher for the specified SSL connection.
@@ -208,6 +249,16 @@ size_t purple_ssl_read(PurpleSslConnection *gsc, void *buffer, size_t len);
*/
size_t purple_ssl_write(PurpleSslConnection *gsc, const void *buffer, size_t len);
+/**
+ * Obtains the peer's presented certificates
+ *
+ * @param gsc The SSL connection handle
+ *
+ * @return The peer certificate chain, in the order of certificate, issuer,
+ * issuer's issuer, etc. NULL if no certificates have been provided,
+ */
+GList * purple_ssl_get_peer_certificates(PurpleSslConnection *gsc);
+
/*@}*/
/**************************************************************************/
diff --git a/libpurple/value.h b/libpurple/value.h
index fcee490818..3d1449b0be 100644
--- a/libpurple/value.h
+++ b/libpurple/value.h
@@ -77,7 +77,8 @@ typedef enum
PURPLE_SUBTYPE_SAVEDSTATUS,
PURPLE_SUBTYPE_XMLNODE,
PURPLE_SUBTYPE_USERINFO,
- PURPLE_SUBTYPE_STORED_IMAGE
+ PURPLE_SUBTYPE_STORED_IMAGE,
+ PURPLE_SUBTYPE_CERTIFICATEPOOL
} PurpleSubType;
/**
diff --git a/pidgin/Makefile.am b/pidgin/Makefile.am
index 4defa1da56..abb921e1f3 100644
--- a/pidgin/Makefile.am
+++ b/pidgin/Makefile.am
@@ -81,6 +81,7 @@ pidgin_SOURCES = \
gtkcellrendererprogress.c \
gtkcellview.c \
gtkcellviewmenuitem.c \
+ gtkcertmgr.c \
gtkconn.c \
gtkconv.c \
gtkdebug.c \
@@ -128,6 +129,7 @@ pidgin_headers = \
gtkcellviewmenuitem.h \
gtkcellview.h \
gtkcellviewmenuitem.h \
+ gtkcertmgr.h \
pidgincombobox.h \
gtkconn.h \
gtkconv.h \
diff --git a/pidgin/gtkblist.c b/pidgin/gtkblist.c
index 2d7480058e..2e9173777c 100644
--- a/pidgin/gtkblist.c
+++ b/pidgin/gtkblist.c
@@ -42,6 +42,7 @@
#include "gtkaccount.h"
#include "gtkblist.h"
#include "gtkcellrendererexpander.h"
+#include "gtkcertmgr.h"
#include "gtkconv.h"
#include "gtkdebug.h"
#include "gtkdialogs.h"
@@ -2872,6 +2873,7 @@ static GtkItemFactoryEntry blist_menu[] =
/* Tools */
{ N_("/_Tools"), NULL, NULL, 0, "<Branch>", NULL },
{ N_("/Tools/Buddy _Pounces"), NULL, pidgin_pounces_manager_show, 0, "<Item>", NULL },
+ { N_("/Tools/_Certificates"), NULL, pidgin_certmgr_show, 0, "<Item>", NULL },
{ N_("/Tools/Plu_gins"), "<CTL>U", pidgin_plugin_dialog_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_PLUGINS },
{ N_("/Tools/Pr_eferences"), "<CTL>P", pidgin_prefs_show, 0, "<StockItem>", GTK_STOCK_PREFERENCES },
{ N_("/Tools/Pr_ivacy"), NULL, pidgin_privacy_dialog_show, 0, "<Item>", NULL },
diff --git a/pidgin/gtkcertmgr.c b/pidgin/gtkcertmgr.c
new file mode 100644
index 0000000000..7f0cfdad50
--- /dev/null
+++ b/pidgin/gtkcertmgr.c
@@ -0,0 +1,685 @@
+/*
+ * @file gtkcertmgr.c GTK+ Certificate Manager API
+ * @ingroup pidgin
+ *
+ * pidgin
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <glib.h>
+
+#include "core.h"
+#include "internal.h"
+#include "pidgin.h"
+
+#include "certificate.h"
+#include "debug.h"
+#include "notify.h"
+#include "request.h"
+
+#include "gtkblist.h"
+#include "gtkutils.h"
+
+#include "gtkcertmgr.h"
+
+/*****************************************************************************
+ * X.509 tls_peers management interface *
+ *****************************************************************************/
+
+typedef struct {
+ GtkWidget *mgmt_widget;
+ GtkTreeView *listview;
+ GtkTreeSelection *listselect;
+ GtkWidget *importbutton;
+ GtkWidget *exportbutton;
+ GtkWidget *infobutton;
+ GtkWidget *deletebutton;
+ PurpleCertificatePool *tls_peers;
+} tls_peers_mgmt_data;
+
+tls_peers_mgmt_data *tpm_dat = NULL;
+
+/* Columns
+ See http://developer.gnome.org/doc/API/2.0/gtk/TreeWidget.html */
+enum
+{
+ TPM_HOSTNAME_COLUMN,
+ TPM_N_COLUMNS
+};
+
+static void
+tls_peers_mgmt_destroy(GtkWidget *mgmt_widget, gpointer data)
+{
+ purple_debug_info("certmgr",
+ "tls peers self-destructs\n");
+
+ purple_signals_disconnect_by_handle(tpm_dat);
+ purple_request_close_with_handle(tpm_dat);
+ g_free(tpm_dat); tpm_dat = NULL;
+}
+
+static void
+tls_peers_mgmt_repopulate_list(void)
+{
+ GtkTreeView *listview = tpm_dat->listview;
+ PurpleCertificatePool *tls_peers;
+ 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 "tls_peers" pool */
+ tls_peers = purple_certificate_find_pool("x509", "tls_peers");
+ g_return_if_fail(tls_peers);
+
+ /* Grab the loaded certificates */
+ idlist = purple_certificate_pool_get_idlist(tls_peers);
+
+ /* Populate the listview */
+ for (l = idlist; l; l = l->next) {
+ GtkTreeIter iter;
+ gtk_list_store_append(store, &iter);
+
+ gtk_list_store_set(GTK_LIST_STORE(store), &iter,
+ TPM_HOSTNAME_COLUMN, l->data,
+ -1);
+ }
+ purple_certificate_pool_destroy_idlist(idlist);
+}
+
+static void
+tls_peers_mgmt_mod_cb(PurpleCertificatePool *pool, const gchar *id, gpointer data)
+{
+ g_assert (pool == tpm_dat->tls_peers);
+
+ tls_peers_mgmt_repopulate_list();
+}
+
+static void
+tls_peers_mgmt_select_chg_cb(GtkTreeSelection *ignored, gpointer data)
+{
+ GtkTreeSelection *select = tpm_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(tpm_dat->exportbutton), TRUE);
+ gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->infobutton), TRUE);
+ gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->deletebutton), TRUE);
+ } else {
+ /* Otherwise, disable them */
+ gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->exportbutton), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->infobutton), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->deletebutton), FALSE);
+
+ }
+}
+
+static void
+tls_peers_mgmt_import_ok2_cb(gpointer data, const char *result)
+{
+ PurpleCertificate *crt = (PurpleCertificate *) data;
+ const char *id = result;
+
+ /* TODO: Perhaps prompt if you're overwriting a cert? */
+
+ /* Drop the certificate into the pool */
+ purple_certificate_pool_store(tpm_dat->tls_peers, id, crt);
+
+ /* And this certificate is not needed any more */
+ purple_certificate_destroy(crt);
+}
+
+static void
+tls_peers_mgmt_import_cancel2_cb(gpointer data, const char *result)
+{
+ PurpleCertificate *crt = (PurpleCertificate *) data;
+ purple_certificate_destroy(crt);
+}
+
+static void
+tls_peers_mgmt_import_ok_cb(gpointer data, const char *filename)
+{
+ PurpleCertificateScheme *x509;
+ PurpleCertificate *crt;
+
+ /* Load the scheme of our tls_peers pool (ought to be x509) */
+ x509 = purple_certificate_pool_get_scheme(tpm_dat->tls_peers);
+
+ /* Now load the certificate from disk */
+ crt = purple_certificate_import(x509, filename);
+
+ /* Did it work? */
+ if (crt != NULL) {
+ gchar *default_hostname;
+ /* Get name to add to pool as */
+ /* Make a guess about what the hostname should be */
+ default_hostname = purple_certificate_get_subject_name(crt);
+ /* TODO: Find a way to make sure that crt gets destroyed
+ if the window gets closed unusually, such as by handle
+ deletion */
+ /* TODO: Display some more information on the certificate? */
+ purple_request_input(tpm_dat,
+ _("Certificate Import"),
+ _("Specify a hostname"),
+ _("Type the host name this certificate is for."),
+ default_hostname,
+ FALSE, /* Not multiline */
+ FALSE, /* Not masked? */
+ NULL, /* No hints? */
+ _("OK"),
+ G_CALLBACK(tls_peers_mgmt_import_ok2_cb),
+ _("Cancel"),
+ G_CALLBACK(tls_peers_mgmt_import_cancel2_cb),
+ NULL, NULL, NULL, /* No account/who/conv*/
+ crt /* Pass cert instance to callback*/
+ );
+
+ g_free(default_hostname);
+ } else {
+ /* Errors! Oh no! */
+ /* TODO: Perhaps find a way to be specific about what just
+ went wrong? */
+ gchar * secondary;
+
+ secondary = g_strdup_printf(_("File %s could not be imported.\nMake sure that the file is readable and in PEM format.\n"), filename);
+ purple_notify_error(NULL,
+ _("Certificate Import Error"),
+ _("X.509 certificate import failed"),
+ secondary);
+ g_free(secondary);
+ }
+}
+
+static void
+tls_peers_mgmt_import_cb(GtkWidget *button, gpointer data)
+{
+ /* TODO: need to tell the user that we want a .PEM file! */
+ purple_request_file(tpm_dat,
+ _("Select a PEM certificate"),
+ "certificate.pem",
+ FALSE, /* Not a save dialog */
+ G_CALLBACK(tls_peers_mgmt_import_ok_cb),
+ NULL, /* Do nothing if cancelled */
+ NULL, NULL, NULL, NULL );/* No account,conv,etc. */
+}
+
+static void
+tls_peers_mgmt_export_ok_cb(gpointer data, const char *filename)
+{
+ PurpleCertificate *crt = (PurpleCertificate *) data;
+
+ g_assert(filename);
+
+ if (!purple_certificate_export(filename, crt)) {
+ /* 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"), filename);
+ purple_notify_error(NULL,
+ _("Certificate Export Error"),
+ _("X.509 certificate export failed"),
+ secondary);
+ g_free(secondary);
+ }
+
+ purple_certificate_destroy(crt);
+}
+
+static void
+tls_peers_mgmt_export_cancel_cb(gpointer data, const char *filename)
+{
+ PurpleCertificate *crt = (PurpleCertificate *) data;
+ /* Pressing cancel just frees the duplicated certificate */
+ purple_certificate_destroy(crt);
+}
+
+static void
+tls_peers_mgmt_export_cb(GtkWidget *button, gpointer data)
+{
+ PurpleCertificate *crt;
+ GtkTreeSelection *select = tpm_dat->listselect;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gchar *id;
+
+ /* See if things are selected */
+ if (!gtk_tree_selection_get_selected(select, &model, &iter)) {
+ purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
+ "Export clicked with no selection?\n");
+ return;
+ }
+
+ /* Retrieve the selected hostname */
+ gtk_tree_model_get(model, &iter, TPM_HOSTNAME_COLUMN, &id, -1);
+
+ /* Extract the certificate from the pool now to make sure it doesn't
+ get deleted out from under us */
+ crt = purple_certificate_pool_retrieve(tpm_dat->tls_peers, id);
+
+ if (NULL == crt) {
+ purple_debug_error("gtkcertmgr/tls_peers_mgmt",
+ "Id %s was not in the peers cache?!\n",
+ id);
+ g_free(id);
+ return;
+ }
+ g_free(id);
+
+
+ /* TODO: inform user that it will be a PEM? */
+ purple_request_file(tpm_dat,
+ _("PEM X.509 Certificate Export"),
+ "certificate.pem",
+ TRUE, /* Is a save dialog */
+ G_CALLBACK(tls_peers_mgmt_export_ok_cb),
+ G_CALLBACK(tls_peers_mgmt_export_cancel_cb),
+ NULL, NULL, NULL, /* No account,conv,etc. */
+ crt); /* Pass the certificate on to the callback */
+}
+
+static void
+tls_peers_mgmt_info_cb(GtkWidget *button, gpointer data)
+{
+ GtkTreeSelection *select = tpm_dat->listselect;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gchar *id;
+ PurpleCertificate *crt;
+ gchar *subject;
+ GByteArray *fpr_sha1;
+ gchar *fpr_sha1_asc;
+ gchar *primary, *secondary;
+
+ /* See if things are selected */
+ if (!gtk_tree_selection_get_selected(select, &model, &iter)) {
+ purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
+ "Info clicked with no selection?\n");
+ return;
+ }
+
+ /* Retrieve the selected hostname */
+ gtk_tree_model_get(model, &iter, TPM_HOSTNAME_COLUMN, &id, -1);
+
+ /* Now retrieve the certificate */
+ crt = purple_certificate_pool_retrieve(tpm_dat->tls_peers, id);
+ g_return_if_fail(crt);
+
+ /* Build a notification thing */
+ /* TODO: This needs a better GUI, but a notification will do for now */
+ primary = g_strdup_printf(_("Certificate for %s"), id);
+
+ fpr_sha1 = purple_certificate_get_fingerprint_sha1(crt);
+ fpr_sha1_asc = purple_base16_encode_chunked(fpr_sha1->data,
+ fpr_sha1->len);
+ subject = purple_certificate_get_subject_name(crt);
+
+ secondary = g_strdup_printf(_("Common name: %s\n\nSHA1 fingerprint:\n%s"), subject, fpr_sha1_asc);
+
+ purple_notify_info(tpm_dat,
+ _("SSL Host Certificate"), primary, secondary );
+
+ g_free(primary);
+ g_free(secondary);
+ g_byte_array_free(fpr_sha1, TRUE);
+ g_free(fpr_sha1_asc);
+ g_free(subject);
+ g_free(id);
+ purple_certificate_destroy(crt);
+}
+
+static void
+tls_peers_mgmt_delete_confirm_cb(gchar *id, gint choice)
+{
+ if (1 == choice) {
+ /* Yes, delete was confirmed */
+ /* Now delete the thing */
+ if (!purple_certificate_pool_delete(tpm_dat->tls_peers, id)) {
+ purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
+ "Deletion failed on id %s\n",
+ id);
+ };
+ }
+
+ g_free(id);
+}
+
+static void
+tls_peers_mgmt_delete_cb(GtkWidget *button, gpointer data)
+{
+ GtkTreeSelection *select = tpm_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, TPM_HOSTNAME_COLUMN, &id, -1);
+
+ /* Prompt to confirm deletion */
+ primary = g_strdup_printf(
+ _("Really delete certificate for %s?"), id );
+
+ purple_request_yes_no(tpm_dat, _("Confirm certificate delete"),
+ primary, NULL, /* Can this be NULL? */
+ 2, /* NO is default action */
+ NULL, NULL, NULL,
+ id, /* id ownership passed to callback */
+ tls_peers_mgmt_delete_confirm_cb,
+ tls_peers_mgmt_delete_confirm_cb );
+
+ g_free(primary);
+
+ } else {
+ purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
+ "Delete clicked with no selection?\n");
+ return;
+ }
+}
+
+static GtkWidget *
+tls_peers_mgmt_build(void)
+{
+ GtkWidget *bbox;
+ GtkListStore *store;
+
+ /* This block of variables will end up in tpm_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 */
+ tpm_dat = g_new0(tls_peers_mgmt_data, 1);
+
+ tpm_dat->mgmt_widget = mgmt_widget =
+ gtk_hbox_new(FALSE, /* Non-homogeneous */
+ PIDGIN_HIG_BORDER);
+ 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(tls_peers_mgmt_destroy), NULL);
+
+ /* List view */
+ store = gtk_list_store_new(TPM_N_COLUMNS, G_TYPE_STRING);
+
+ tpm_dat->listview = listview =
+ GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)));
+
+ {
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+
+ /* Set up the display columns */
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(
+ "Hostname",
+ renderer,
+ "text", TPM_HOSTNAME_COLUMN,
+ NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(listview), column);
+ }
+
+ /* Get the treeview selector into the struct */
+ tpm_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(tls_peers_mgmt_select_chg_cb), NULL);
+
+ gtk_box_pack_start(GTK_BOX(mgmt_widget), GTK_WIDGET(listview),
+ TRUE, TRUE, /* Take up lots of space */
+ 0); /* TODO: this padding is wrong */
+ gtk_widget_show(GTK_WIDGET(listview));
+
+ /* Fill the list for the first time */
+ tls_peers_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); /* TODO: this padding is probably wrong */
+ 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 */
+ tpm_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(tls_peers_mgmt_import_cb), NULL);
+
+
+ /* Export button */
+ /* TODO: This is the wrong stock button */
+ tpm_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(tls_peers_mgmt_export_cb), NULL);
+
+
+ /* Info button */
+ tpm_dat->infobutton = infobutton =
+ gtk_button_new_from_stock(GTK_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(tls_peers_mgmt_info_cb), NULL);
+
+
+ /* Delete button */
+ tpm_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(tls_peers_mgmt_delete_cb), NULL);
+
+ /* Call the "selection changed" callback, which will probably disable
+ all the buttons since nothing is selected yet */
+ tls_peers_mgmt_select_chg_cb(select, NULL);
+
+ /* Bind us to the tls_peers pool */
+ tpm_dat->tls_peers = purple_certificate_find_pool("x509", "tls_peers");
+
+ /**** libpurple signals ****/
+ /* Respond to certificate add/remove by just reloading everything */
+ purple_signal_connect(tpm_dat->tls_peers, "certificate-stored",
+ tpm_dat, PURPLE_CALLBACK(tls_peers_mgmt_mod_cb),
+ NULL);
+ purple_signal_connect(tpm_dat->tls_peers, "certificate-deleted",
+ tpm_dat, PURPLE_CALLBACK(tls_peers_mgmt_mod_cb),
+ NULL);
+
+ return mgmt_widget;
+}
+
+PidginCertificateManager tls_peers_mgmt = {
+ tls_peers_mgmt_build, /* Widget creation function */
+ N_("SSL Servers")
+};
+
+/*****************************************************************************
+ * GTK+ main certificate manager *
+ *****************************************************************************/
+typedef struct
+{
+ GtkWidget *window;
+ GtkWidget *notebook;
+
+ GtkWidget *closebutton;
+} CertMgrDialog;
+
+/* If a certificate manager window is open, this will point to it.
+ So if it is set, don't open another one! */
+CertMgrDialog *certmgr_dialog = NULL;
+
+static void
+certmgr_close_cb(GtkWidget *w, CertMgrDialog *dlg)
+{
+ /* TODO: Ignoring the arguments to this function may not be ideal,
+ but there *should* only be "one dialog to rule them all" at a time*/
+ pidgin_certmgr_hide();
+}
+
+void
+pidgin_certmgr_show(void)
+{
+ CertMgrDialog *dlg;
+ GtkWidget *win;
+ GtkWidget *vbox;
+ GtkWidget *bbox;
+
+ /* Enumerate all the certificates on file */
+ {
+ GList *idlist, *poollist;
+
+ for ( poollist = purple_certificate_get_pools();
+ poollist;
+ poollist = poollist->next ) {
+ PurpleCertificatePool *pool = poollist->data;
+ GList *l;
+
+ purple_debug_info("gtkcertmgr",
+ "Pool %s found for scheme %s -"
+ "Enumerating certificates:\n",
+ pool->name, pool->scheme_name);
+
+ idlist = purple_certificate_pool_get_idlist(pool);
+
+ for (l=idlist; l; l = l->next) {
+ purple_debug_info("gtkcertmgr",
+ "- %s\n",
+ (gchar *) l->data);
+ } /* idlist */
+ purple_certificate_pool_destroy_idlist(idlist);
+ } /* poollist */
+ }
+
+
+ /* If the manager is already open, bring it to the front */
+ if (certmgr_dialog != NULL) {
+ gtk_window_present(GTK_WINDOW(certmgr_dialog->window));
+ return;
+ }
+
+ /* Create the dialog, and set certmgr_dialog so we never create
+ more than one at a time */
+ dlg = certmgr_dialog = g_new0(CertMgrDialog, 1);
+
+ win = dlg->window =
+ pidgin_create_window(_("Certificate Manager"),/* Title */
+ PIDGIN_HIG_BORDER, /*Window border*/
+ "certmgr", /* Role */
+ TRUE); /* Allow resizing */
+ g_signal_connect(G_OBJECT(win), "delete_event",
+ G_CALLBACK(certmgr_close_cb), dlg);
+
+
+ /* TODO: Retrieve the user-set window size and use it */
+ gtk_window_set_default_size(GTK_WINDOW(win), 400, 400);
+
+ /* Main vbox */
+ vbox = gtk_vbox_new( FALSE, PIDGIN_HIG_BORDER );
+ gtk_container_add(GTK_CONTAINER(win), vbox);
+ gtk_widget_show(vbox);
+
+ /* Notebook of various certificate managers */
+ dlg->notebook = gtk_notebook_new();
+ gtk_box_pack_start(GTK_BOX(vbox), dlg->notebook,
+ TRUE, TRUE, /* Notebook should take extra space */
+ 0);
+ gtk_widget_show(dlg->notebook);
+
+ /* Box for the close button */
+ bbox = gtk_hbutton_box_new();
+ gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
+ gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
+ gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
+ gtk_widget_show(bbox);
+
+ /* Close button */
+ dlg->closebutton = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
+ gtk_box_pack_start(GTK_BOX(bbox), dlg->closebutton, FALSE, FALSE, 0);
+ gtk_widget_show(dlg->closebutton);
+ g_signal_connect(G_OBJECT(dlg->closebutton), "clicked",
+ G_CALLBACK(certmgr_close_cb), dlg);
+
+ /* Add the defined certificate managers */
+ /* TODO: Find a way of determining whether each is shown or not */
+ /* TODO: Implement this correctly */
+ gtk_notebook_append_page(GTK_NOTEBOOK (dlg->notebook),
+ (tls_peers_mgmt.build)(),
+ gtk_label_new(_(tls_peers_mgmt.label)) );
+
+ gtk_widget_show(win);
+}
+
+void
+pidgin_certmgr_hide(void)
+{
+ /* If it isn't open, do nothing */
+ if (certmgr_dialog == NULL) {
+ return;
+ }
+
+ purple_signals_disconnect_by_handle(certmgr_dialog);
+ purple_prefs_disconnect_by_handle(certmgr_dialog);
+
+ gtk_widget_destroy(certmgr_dialog->window);
+ g_free(certmgr_dialog);
+ certmgr_dialog = NULL;
+
+ /* If this was the only window left, quit */
+ if (PIDGIN_BLIST(purple_get_blist())->window == NULL &&
+ purple_connections_get_all() == NULL) {
+
+ purple_core_quit();
+ }
+}
diff --git a/pidgin/gtkcertmgr.h b/pidgin/gtkcertmgr.h
new file mode 100644
index 0000000000..3e110ca2d7
--- /dev/null
+++ b/pidgin/gtkcertmgr.h
@@ -0,0 +1,62 @@
+/**
+ * @file gtkcertmgr.h GTK+ Certificate Manager API
+ * @ingroup pidgin
+ */
+/*
+ * pidgin
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef _PIDGINCERTMGR_H_
+#define _PIDGINCERTMGR_H_
+
+/**************************************************************************
+ * @name Structures *
+ **************************************************************************/
+typedef struct _PidginCertificateManager PidginCertificateManager;
+
+/**
+ * GTK+ Certificate Manager subwidget
+ */
+struct _PidginCertificateManager {
+ /** Create, configure, show, and return the management interface */
+ GtkWidget * (* build)(void);
+ /** Notebook label to use in the CertMgr dialog */
+ gchar *label;
+};
+
+/**************************************************************************/
+/** @name Certificate Manager API */
+/**************************************************************************/
+/*@{*/
+/**
+ * Show the certificate manager window
+ */
+void pidgin_certmgr_show(void);
+
+/**
+ * Hide the certificate manager window
+ */
+void pidgin_certmgr_hide(void);
+
+/*@}*/
+
+#endif /* _PIDGINCERTMGR_H_ */
diff --git a/share/Makefile.am b/share/Makefile.am
index 59471a4288..aff605355a 100644
--- a/share/Makefile.am
+++ b/share/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = sounds
+SUBDIRS = sounds ca-certs
EXTRA_DIST = Makefile.mingw
diff --git a/share/ca-certs/Equifax_Secure_CA.pem b/share/ca-certs/Equifax_Secure_CA.pem
new file mode 100644
index 0000000000..9cebe1aca2
--- /dev/null
+++ b/share/ca-certs/Equifax_Secure_CA.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQG
+EwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1
+cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4
+MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgx
+LTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0
+eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2R
+FGiYCh7+2gRvE4RiIcPRfM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO
+/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuv
+K9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAGA1UdHwRp
+MGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEt
+MCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
+MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjAL
+BgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gjIBBPM5iQn9Qw
+HQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMBAf8w
+GgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GB
+AFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
+7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2u
+FHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
+-----END CERTIFICATE-----
diff --git a/share/ca-certs/Makefile.am b/share/ca-certs/Makefile.am
new file mode 100644
index 0000000000..d4bbd60ffe
--- /dev/null
+++ b/share/ca-certs/Makefile.am
@@ -0,0 +1,8 @@
+cacertsdir = $(datadir)/purple/ca-certs
+cacerts_DATA = \
+ Equifax_Secure_CA.pem \
+ Verisign_RSA_Secure_Server_CA.pem
+
+EXTRA_DIST = \
+ $(cacerts_DATA)
+
diff --git a/share/ca-certs/Verisign_RSA_Secure_Server_CA.pem b/share/ca-certs/Verisign_RSA_Secure_Server_CA.pem
new file mode 100644
index 0000000000..c385bcc25c
--- /dev/null
+++ b/share/ca-certs/Verisign_RSA_Secure_Server_CA.pem
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzEL
+MAkGA1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMu
+MS4wLAYDVQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5MB4XDTk0MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVowXzELMAkGA1UE
+BhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD
+VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGb
+MA0GCSqGSIb3DQEBAQUAA4GJADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6O
+LDfO6zV4ZFQD5YRAUcm/jwjiioII0haGN1XpsSECrXZogZoFokvJSyVmIlZs
+iAeP94FZbYQHZXATcXY+m3dM41CJVphIuR2nKRoTLkoRWZweFdVJVCxzOmmC
+sZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZIhvcNAQECBQADfgBl3X7hsuyw
+4jrg7HFGmhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3YQO2WxZpO8ZECAyIUwxr
+l0nHPjXcbLm7qt9cuzovk2C2qUtN8iD3zV9/ZHuO3ABc1/p3yjkWWW8O6tO1
+g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA==
+-----END CERTIFICATE-----