Mercurial > pidgin.yaz
changeset 19200:d470d42d2cb1
propagate from branch 'im.pidgin.pidgin' (head e687870673f02e5605b0bf9877fd579c78fe8c01)
to branch 'im.pidgin.soc.2007.certmgr' (head d1d514d18e94b8444eef617bb29ee248c3a0a572)
author | William Ehlhardt <williamehlhardt@gmail.com> |
---|---|
date | Sun, 12 Aug 2007 20:48:34 +0000 |
parents | 81163e153778 (diff) b39aa44b9769 (current diff) |
children | 73d8dd2169c4 |
files | libpurple/Makefile.am libpurple/core.c libpurple/protocols/bonjour/mdns_howl.h libpurple/protocols/bonjour/mdns_win32.h libpurple/protocols/jabber/jabber.c pidgin/Makefile.am pidgin/gtkblist.c pidgin/gtkcellrendererexpander.c pidgin/pixmaps/status/16/message-pending.png pidgin/sounds/Makefile.am pidgin/sounds/Makefile.mingw pidgin/sounds/alert.wav pidgin/sounds/login.wav pidgin/sounds/logout.wav pidgin/sounds/receive.wav pidgin/sounds/send.wav |
diffstat | 20 files changed, 4341 insertions(+), 33 deletions(-) [+] |
line wrap: on
line diff
--- a/doc/Makefile.am Sun Aug 12 19:16:01 2007 +0000 +++ b/doc/Makefile.am Sun Aug 12 20:48:34 2007 +0000 @@ -3,11 +3,13 @@ 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 \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/SIGNAL-HOWTO.dox Sun Aug 12 20:48:34 2007 +0000 @@ -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. + + */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/certificate-signals.dox Sun Aug 12 20:48:34 2007 +0000 @@ -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
--- a/libpurple/Makefile.am Sun Aug 12 19:16:01 2007 +0000 +++ b/libpurple/Makefile.am Sun Aug 12 20:48:34 2007 +0000 @@ -36,6 +36,7 @@ accountopt.c \ blist.c \ buddyicon.c \ + certificate.c \ cipher.c \ circbuffer.c \ cmds.c \ @@ -85,6 +86,7 @@ accountopt.h \ blist.h \ buddyicon.h \ + certificate.h \ cipher.h \ circbuffer.h \ cmds.h \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/certificate.c Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,1600 @@ +/** + * @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; + +/** System directory to probe for CA certificates */ +/* TODO: The current path likely won't work on anything but Debian! Fix! */ +static const gchar *x509_ca_syspath = "/etc/ssl/certs/"; + +/** 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; + +static gboolean +x509_ca_lazy_init(void) +{ + PurpleCertificateScheme *x509; + + 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; + } + + /* Populate the certificates pool from the system path */ + /* TODO: Writeme! */ + + 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) +{ + +} + +static gboolean +x509_ca_cert_in_pool(const gchar *id) +{ + gboolean ret = FALSE; + + g_return_val_if_fail(x509_ca_lazy_init(), FALSE); + g_return_val_if_fail(id, FALSE); + + return ret; +} + +static PurpleCertificate * +x509_ca_get_cert(const gchar *id) +{ + PurpleCertificateScheme *x509; + PurpleCertificate *crt = NULL; + + g_return_val_if_fail(x509_ca_lazy_init(), NULL); + g_return_val_if_fail(id, NULL); + + /* Is it in the pool? */ + if ( !x509_ca_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); + + return crt; +} + +static gboolean +x509_ca_put_cert(const gchar *id, PurpleCertificate *crt) +{ + gboolean ret = FALSE; + x509_ca_element *el; + + g_return_val_if_fail(x509_ca_lazy_init(), FALSE); + 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); + + /* TODO: This is a quick way of doing this. At some point the change + ought to be flushed to disk somehow. */ + 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); + ret = TRUE; + + return ret; +} + +static gboolean +x509_ca_delete_cert(const gchar *id) +{ + gboolean ret = FALSE; + + g_return_val_if_fail(x509_ca_lazy_init(), FALSE); + g_return_val_if_fail(id, FALSE); + + /* Is the id even in the pool? */ + if (!x509_ca_cert_in_pool(id)) { + purple_debug_warning("certificate/x509/ca", + "Id %s wasn't in the pool\n", + id); + return FALSE; + } + + return ret; +} + +static GList * +x509_ca_get_idlist(void) +{ + g_return_val_if_fail(x509_ca_lazy_init(), NULL); + return NULL; +} + + +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; + +static void +x509_tls_cached_user_auth_cb (PurpleCertificateVerificationRequest *vrq, gint id) +{ + PurpleCertificatePool *tls_peers; + + g_return_if_fail(vrq); + + tls_peers = purple_certificate_find_pool("x509","tls_peers"); + + if (1 == 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 */ +static void +x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq) +{ + gchar *sha_asc; + GByteArray *sha_bin; + gchar *cn; + const gchar *cn_match; + time_t activation, expiration; + /* Length of these buffers is dictated by 'man ctime_r' */ + gchar activ_str[26], expir_str[26]; + 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 */ + if (purple_certificate_check_subject_name(crt, vrq->subject_name)) { + cn_match = _(""); + } else { + cn_match = _("(DOES NOT MATCH)"); + } + + /* 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 */ + primary = g_strdup_printf(_("%s has presented the following certificate:"), vrq->subject_name); + secondary = g_strdup_printf(_("Common name: %s %s\n\nFingerprint (SHA1): %s\n\nActivation date: %s\nExpiration date: %s\n"), cn, cn_match, sha_asc, activ_str, expir_str); + + /* Make a semi-pretty display */ + purple_request_accept_cancel( + vrq->cb_data, /* TODO: Find what the handle ought to be */ + _("SSL Certificate Verification"), + primary, + secondary, + 1, /* Accept by default */ + NULL, /* No account */ + NULL, /* No other user */ + NULL, /* No associated conversation */ + vrq, + x509_tls_cached_user_auth_cb, + x509_tls_cached_user_auth_cb ); + + /* Cleanup */ + g_free(primary); + g_free(secondary); + g_free(sha_asc); + g_byte_array_free(sha_bin, TRUE); +} + +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); + + purple_debug_info("certificate/x509/tls_cached", + "Name mismatch: Certificate given for %s " + "has a name of %s\n", + vrq->subject_name, sn); + g_free(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 */ + x509_tls_cached_user_auth(vrq); + 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); + 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); + 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); + 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; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/certificate.h Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,769 @@ +/** + * @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); + +/*@}*/ + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _PURPLE_CERTIFICATE_H */
--- a/libpurple/core.c Sun Aug 12 19:16:01 2007 +0000 +++ b/libpurple/core.c Sun Aug 12 20:48:34 2007 +0000 @@ -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_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_notify_uninit(); purple_conversations_uninit(); purple_connections_uninit(); + purple_certificate_uninit(); purple_buddy_icons_uninit(); purple_accounts_uninit(); purple_savedstatuses_uninit();
--- a/libpurple/plugins/ssl/ssl-gnutls.c Sun Aug 12 19:16:01 2007 +0000 +++ b/libpurple/plugins/ssl/ssl-gnutls.c Sun Aug 12 20:48:34 2007 +0000 @@ -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 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 @@ 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 @@ 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 @@ } 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,526 @@ 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 details 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); + } + + /* 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, 0, &verify); + + if (ret > 0) { + /* The certificate is good. */ + return TRUE; + } + else 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; + } + else { + /* Signature didn't check out, but at least + there were no errors*/ + return FALSE; + } /* if (ret, etc.) */ + + /* Control does not reach this point */ +} + +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 +889,11 @@ ssl_gnutls_close, ssl_gnutls_read, ssl_gnutls_write, + ssl_gnutls_get_peer_certificates, /* padding */ NULL, NULL, - NULL, NULL }; @@ -235,6 +903,10 @@ plugin_load(PurplePlugin *plugin) { #ifdef HAVE_GNUTLS + /* Register that we're providing an X.509 CertScheme */ + /* @TODO : error checking */ + purple_certificate_register_scheme( &x509_gnutls ); + if(!purple_ssl_get_ops()) { purple_ssl_set_ops(&ssl_ops); } @@ -255,6 +927,8 @@ if(purple_ssl_get_ops() == &ssl_ops) { purple_ssl_set_ops(NULL); } + + purple_certificate_unregister_scheme( &x509_gnutls ); #endif return TRUE;
--- a/libpurple/plugins/ssl/ssl-nss.c Sun Aug 12 19:16:01 2007 +0000 +++ b/libpurple/plugins/ssl/ssl-nss.c Sun Aug 12 20:48:34 2007 +0000 @@ -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,256 @@ 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) +{ + /* TODO: Write me! */ + return NULL; +} + +/** + * 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 */ + 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 +619,11 @@ ssl_nss_close, ssl_nss_read, ssl_nss_write, + ssl_nss_peer_certs, /* padding */ NULL, NULL, - NULL, NULL }; @@ -390,6 +641,9 @@ /* 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 +657,9 @@ 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;
--- a/libpurple/prefs.h Sun Aug 12 19:16:01 2007 +0000 +++ b/libpurple/prefs.h Sun Aug 12 20:48:34 2007 +0000 @@ -55,7 +55,9 @@ #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) */ /**************************************************************************/ /*@{*/
--- a/libpurple/protocols/irc/irc.c Sun Aug 12 19:16:01 2007 +0000 +++ b/libpurple/protocols/irc/irc.c Sun Aug 12 20:48:34 2007 +0000 @@ -433,14 +433,7 @@ 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)
--- a/libpurple/protocols/jabber/jabber.c Sun Aug 12 19:16:01 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.c Sun Aug 12 20:48:34 2007 +0000 @@ -494,29 +494,20 @@ 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 @@ 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);
--- a/libpurple/protocols/jabber/jabber.h Sun Aug 12 19:16:01 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.h Sun Aug 12 20:48:34 2007 +0000 @@ -136,6 +136,8 @@ 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 @@ int sasl_state; int sasl_maxbuf; GString *sasl_mechs; - char *serverFQDN; gboolean vcard_fetched;
--- a/libpurple/sslconn.c Sun Aug 12 19:16:01 2007 +0000 +++ b/libpurple/sslconn.c Sun Aug 12 20:48:34 2007 +0000 @@ -24,6 +24,7 @@ */ #include "internal.h" +#include "certificate.h" #include "debug.h" #include "sslconn.h" @@ -117,6 +118,9 @@ 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 @@ 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 @@ 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 @@ 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) {
--- a/libpurple/sslconn.h Sun Aug 12 19:16:01 2007 +0000 +++ b/libpurple/sslconn.h Sun Aug 12 20:48:34 2007 +0000 @@ -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 @@ /** 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 @@ * @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 @@ 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 @@ /** * 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_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_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); + /*@}*/ /**************************************************************************/
--- a/libpurple/value.h Sun Aug 12 19:16:01 2007 +0000 +++ b/libpurple/value.h Sun Aug 12 20:48:34 2007 +0000 @@ -77,7 +77,8 @@ PURPLE_SUBTYPE_SAVEDSTATUS, PURPLE_SUBTYPE_XMLNODE, PURPLE_SUBTYPE_USERINFO, - PURPLE_SUBTYPE_STORED_IMAGE + PURPLE_SUBTYPE_STORED_IMAGE, + PURPLE_SUBTYPE_CERTIFICATEPOOL } PurpleSubType; /**
--- a/pidgin/Makefile.am Sun Aug 12 19:16:01 2007 +0000 +++ b/pidgin/Makefile.am Sun Aug 12 20:48:34 2007 +0000 @@ -80,6 +80,7 @@ gtkcellrendererprogress.c \ gtkcellview.c \ gtkcellviewmenuitem.c \ + gtkcertmgr.c \ gtkconn.c \ gtkconv.c \ gtkdebug.c \ @@ -127,6 +128,7 @@ gtkcellviewmenuitem.h \ gtkcellview.h \ gtkcellviewmenuitem.h \ + gtkcertmgr.h \ pidgincombobox.h \ gtkconn.h \ gtkconv.h \
--- a/pidgin/gtkblist.c Sun Aug 12 19:16:01 2007 +0000 +++ b/pidgin/gtkblist.c Sun Aug 12 20:48:34 2007 +0000 @@ -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" @@ -2869,6 +2870,7 @@ /* 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 },
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkcertmgr.c Sun Aug 12 20:48:34 2007 +0000 @@ -0,0 +1,680 @@ +/* + * @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); +} + +static void +tls_peers_mgmt_delete_confirm_cb(gchar *id, gint choice) +{ + if (1 == choice) { + /* Yes, delete was confirmed */ + /* Now delete the thing */ + g_assert(purple_certificate_pool_delete(tpm_dat->tls_peers, 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(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkcertmgr.h Sun Aug 12 20:48:34 2007 +0000 @@ -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_ */