pidgin/pidgin

Make PurplePluginProtocolInfo definitions consistent
release-2.x.y
2020-05-20, David Woodhouse
159344ba2a49
Make PurplePluginProtocolInfo definitions consistent

Since we can't use C99 structure initialisers, we have to manually add
new NULL fields to all protocols whenever we extend the structure.

Make it slightly easier to script that, by making the current last
field (get_cb_alias) consistent in all cases. In particular, there's
no reason *not* to have the trailing comma, as most already do.

Now I can add a new field to the PRPL by doing something like this...

PROTOFILES=`grep -rl '[A-Za-z_][A-Za-z0-9_]*,[[:space:]]*/\* get_cb_alias \*/' libpurple/protocols/ `
sed '/\/\* get_cb_alias \*\//{p;s/[A-Za-z_][A-Za-
/**
* @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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include "internal.h"
#include "certificate.h"
#include "dbus-maybe.h"
#include "debug.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;
static const gchar *
invalidity_reason_to_string(PurpleCertificateInvalidityFlags flag)
{
switch (flag) {
case PURPLE_CERTIFICATE_SELF_SIGNED:
return _("The certificate is self-signed and cannot be "
"automatically checked.");
break;
case PURPLE_CERTIFICATE_CA_UNKNOWN:
return _("The certificate is not trusted because no certificate "
"that can verify it is currently trusted.");
break;
case PURPLE_CERTIFICATE_NOT_ACTIVATED:
return _("The certificate is not valid yet. Check that your "
"computer's date and time are accurate.");
break;
case PURPLE_CERTIFICATE_EXPIRED:
return _("The certificate has expired and should not be "
"considered valid. Check that your computer's date "
"and time are accurate.");
break;
case PURPLE_CERTIFICATE_NAME_MISMATCH:
/* Translators: "domain" refers to a DNS domain (e.g. talk.google.com) */
return _("The certificate presented is not issued to this domain.");
break;
case PURPLE_CERTIFICATE_NO_CA_POOL:
return _("You have no database of root certificates, so "
"this certificate cannot be validated.");
break;
case PURPLE_CERTIFICATE_INVALID_CHAIN:
return _("The certificate chain presented is invalid.");
break;
case PURPLE_CERTIFICATE_REVOKED:
return _("The certificate has been revoked.");
break;
case PURPLE_CERTIFICATE_UNKNOWN_ERROR:
default:
return _("An unknown certificate error occurred.");
break;
}
}
static void
get_ascii_fingerprints (PurpleCertificate *crt, gchar **sha1, gchar **sha256)
{
GByteArray *sha_bin;
if (sha1 != NULL) {
sha_bin = purple_certificate_get_fingerprint_sha1(crt);
*sha1 = purple_base16_encode_chunked(sha_bin->data, sha_bin->len);
g_byte_array_free(sha_bin, TRUE);
}
if (sha256 != NULL) {
sha_bin = purple_certificate_get_fingerprint_sha256(crt, FALSE);
*sha256 = (sha_bin == NULL) ? g_strdup("(null)") :
purple_base16_encode_chunked(sha_bin->data, sha_bin->len);
g_byte_array_free(sha_bin, TRUE);
}
}
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);
if (st == PURPLE_CERTIFICATE_VALID) {
purple_debug_info("certificate",
"Successfully verified certificate for %s\n",
vrq->subject_name);
} else {
purple_debug_error("certificate",
"Failed to verify certificate for %s\n",
vrq->subject_name);
}
/* 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, *l;
/* First, make a shallow copy of the list */
new_l = g_list_copy(crt_list);
/* Now go through and actually duplicate each certificate */
for (l = new_l; l; l = l->next) {
l->data = purple_certificate_copy(l->data);
}
return new_l;
}
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_with_failing(GList *chain,
PurpleCertificate **failing)
{
GList *cur;
PurpleCertificate *crt, *issuer;
gchar *uid;
time_t now, activation, expiration;
gboolean ret;
g_return_val_if_fail(chain, FALSE);
if (failing)
*failing = NULL;
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;
}
now = time(NULL);
/* 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);
uid = purple_certificate_get_unique_id(issuer);
ret = purple_certificate_get_times(issuer, &activation, &expiration);
if (!ret || now < activation || now > expiration) {
if (!ret)
purple_debug_error("certificate",
"...Failed to get validity times for certificate %s\n"
"Chain is INVALID\n", uid);
else if (now > expiration)
purple_debug_error("certificate",
"...Issuer %s expired at %s\nChain is INVALID\n",
uid, ctime(&expiration));
else
purple_debug_error("certificate",
"...Not-yet-activated issuer %s will be valid at %s\n"
"Chain is INVALID\n", uid, ctime(&activation));
if (failing)
*failing = crt;
g_free(uid);
return FALSE;
}
/* Check the signature for this link */
if (! purple_certificate_signed_by(crt, issuer) ) {
purple_debug_error("certificate",
"...Bad or missing signature by %s\nChain is INVALID\n",
uid);
g_free(uid);
if (failing)
*failing = crt;
return FALSE;
}
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;
}
gboolean
purple_certificate_check_signature_chain(GList *chain)
{
return purple_certificate_check_signature_chain_with_failing(chain, NULL);
}
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);
}
GSList *
purple_certificates_import(PurpleCertificateScheme *scheme, const gchar *filename)
{
g_return_val_if_fail(scheme, NULL);
g_return_val_if_fail(scheme->import_certificates, NULL);
g_return_val_if_fail(filename, NULL);
return (scheme->import_certificates)(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);
}
static gboolean
byte_arrays_equal(const GByteArray *array1, const GByteArray *array2)
{
g_return_val_if_fail(array1 != NULL, FALSE);
g_return_val_if_fail(array2 != NULL, FALSE);
return (array1->len == array2->len) &&
(0 == memcmp(array1->data, array2->data, array1->len));
}
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;
}
GByteArray *
purple_certificate_get_fingerprint_sha256(PurpleCertificate *crt, gboolean sha1_fallback)
{
PurpleCertificateScheme *scheme;
GByteArray *fpr = NULL;
g_return_val_if_fail(crt, NULL);
g_return_val_if_fail(crt->scheme, NULL);
scheme = crt->scheme;
if (!PURPLE_CERTIFICATE_SCHEME_HAS_FUNC(scheme, get_fingerprint_sha256)) {
/* outdated ssl module? fallback to sha1 and print a warning */
if (sha1_fallback) {
fpr = purple_certificate_get_fingerprint_sha1(crt);
}
g_return_val_if_reached(fpr);
}
fpr = (scheme->get_fingerprint_sha256)(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;
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);
}
gboolean
purple_certificate_compare_pubkeys(PurpleCertificate *crt1, PurpleCertificate *crt2)
{
PurpleCertificateScheme *scheme;
g_return_val_if_fail(crt1 && crt2, FALSE);
g_return_val_if_fail(crt1->scheme && crt2->scheme, FALSE);
g_return_val_if_fail(crt1->scheme == crt2->scheme, FALSE);
scheme = crt1->scheme;
if (!(PURPLE_CERTIFICATE_SCHEME_HAS_FUNC(scheme, compare_pubkeys))) {
return FALSE;
}
return (scheme->compare_pubkeys)(crt1, crt2);
}
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 = g_strdup(purple_escape_filename(pool->scheme_name));
esc_name = g_strdup(purple_escape_filename(pool->name));
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);
/* 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 *sha1_asc, *sha256_asc;
gchar *cn;
const gchar *cn_match;
gchar *primary, *secondary, *secondary_extra;
PurpleCertificate *crt = (PurpleCertificate *) vrq->cert_chain->data;
get_ascii_fingerprints(crt, &sha1_asc, &sha256_asc);
/* 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)");
}
/* 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, sha1_asc);
/* TODO: make this part of the translatable string above */
secondary_extra = g_strdup_printf("%s\nSHA256: %s", secondary, sha256_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_extra,
0, /* 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(cn);
g_free(primary);
g_free(secondary);
g_free(secondary_extra);
g_free(sha1_asc);
g_free(sha256_asc);
}
static void
x509_singleuse_destroy_request (PurpleCertificateVerificationRequest *vrq)
{
/* I don't do anything! */
}
static PurpleCertificateVerifier x509_singleuse = {
"x509", /* Scheme name */
"singleuse", /* Verifier name */
x509_singleuse_start_verify, /* start_verification function */
x509_singleuse_destroy_request, /* Request cleanup operation */
NULL,
NULL,
NULL,
NULL
};
/***** X.509 Certificate Authority pool, keyed by Distinguished Name *****/
/* This is implemented in what may be the most inefficient and bugprone way
possible; however, future optimizations should not be difficult. */
static PurpleCertificatePool x509_ca;
/** Holds a key-value pair for quickish certificate lookup */
typedef struct {
gchar *dn;
PurpleCertificate *crt;
} x509_ca_element;
static void
x509_ca_element_free(x509_ca_element *el)
{
if (NULL == el) return;
g_free(el->dn);
purple_certificate_destroy(el->crt);
g_free(el);
}
/** System directory to probe for CA certificates */
/* This is set in the lazy_init function */
static GList *x509_ca_paths = NULL;
/** A list of loaded CAs, populated from the above path whenever the lazy_init
happens. Contains pointers to x509_ca_elements */
static GList *x509_ca_certs = NULL;
/** Used for lazy initialization purposes. */
static gboolean x509_ca_initialized = FALSE;
/** Adds a certificate to the in-memory cache, and mark it as trusted */
static gboolean
x509_ca_quiet_put_cert(PurpleCertificate *crt)
{
gboolean ret;
x509_ca_element *el;
/* lazy_init calls this function, so calling lazy_init here is a
Bad Thing */
g_return_val_if_fail(crt, FALSE);
g_return_val_if_fail(crt->scheme, FALSE);
/* Make sure that this is some kind of X.509 certificate */
/* TODO: Perhaps just check crt->scheme->name instead? */
g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_ca.scheme_name), FALSE);
ret = TRUE;
if (crt->scheme->register_trusted_tls_cert) {
ret = (crt->scheme->register_trusted_tls_cert)(crt, TRUE);
}
if (ret) {
el = g_new0(x509_ca_element, 1);
el->dn = purple_certificate_get_unique_id(crt);
el->crt = purple_certificate_copy(crt);
x509_ca_certs = g_list_prepend(x509_ca_certs, el);
}
return ret;
}
/* Since the libpurple CertificatePools get registered before plugins are
loaded, an X.509 Scheme is generally not available when x509_ca_init is
called, but x509_ca requires X.509 operations in order to properly load.
To solve this, I present the lazy_init function. It attempts to finish
initialization of the Pool, but it usually fails when it is called from
x509_ca_init. However, this is OK; initialization is then simply deferred
until someone tries to use functions from the pool. */
static gboolean
x509_ca_lazy_init(void)
{
PurpleCertificateScheme *x509;
GDir *certdir;
const gchar *entry;
GPatternSpec *pempat, *crtpat;
GList *iter = NULL;
GSList *crts = NULL;
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_warning("certificate/x509/ca",
"Lazy init failed because an X.509 Scheme "
"is not yet registered. Maybe it will be "
"better later.\n");
return FALSE;
}
/* Use a glob to only read .pem files */
pempat = g_pattern_spec_new("*.pem");
crtpat = g_pattern_spec_new("*.crt");
/* Populate the certificates pool from the search path(s) */
for (iter = x509_ca_paths; iter; iter = iter->next) {
certdir = g_dir_open(iter->data, 0, NULL);
if (!certdir) {
purple_debug_error("certificate/x509/ca", "Couldn't open location '%s'\n", (const char *)iter->data);
continue;
}
while ( (entry = g_dir_read_name(certdir)) ) {
gchar *fullpath;
PurpleCertificate *crt;
if (!g_pattern_match_string(pempat, entry) && !g_pattern_match_string(crtpat, entry)) {
continue;
}
fullpath = g_build_filename(iter->data, entry, NULL);
/* TODO: Respond to a failure in the following? */
crts = purple_certificates_import(x509, fullpath);
while (crts && crts->data) {
crt = crts->data;
if (x509_ca_quiet_put_cert(crt)) {
gchar *name;
name = purple_certificate_get_subject_name(crt);
purple_debug_info("certificate/x509/ca",
"Loaded %s from %s\n",
name ? name : "(unknown)", fullpath);
g_free(name);
} else {
purple_debug_error("certificate/x509/ca",
"Failed to load certificate from %s\n",
fullpath);
}
purple_certificate_destroy(crt);
crts = g_slist_delete_link(crts, crts);
}
g_free(fullpath);
}
g_dir_close(certdir);
}
g_pattern_spec_free(pempat);
g_pattern_spec_free(crtpat);
purple_debug_info("certificate/x509/ca",
"Lazy init completed.\n");
x509_ca_initialized = TRUE;
return TRUE;
}
static gboolean
x509_ca_init(void)
{
/* Attempt to point at the appropriate system path */
if (NULL == x509_ca_paths) {
#ifdef _WIN32
x509_ca_paths = g_list_append(NULL, g_build_filename(DATADIR,
"ca-certs", NULL));
#else
# ifdef SSL_CERTIFICATES_DIR
x509_ca_paths = g_list_append(NULL, g_strdup(SSL_CERTIFICATES_DIR));
# endif
x509_ca_paths = g_list_append(x509_ca_paths,
g_build_filename(DATADIR, "purple", "ca-certs", NULL));
#endif
}
/* Attempt to initialize now, but if it doesn't work, that's OK;
it will get done later */
if ( ! x509_ca_lazy_init()) {
purple_debug_info("certificate/x509/ca",
"Init failed, probably because a "
"dependency is not yet registered. "
"It has been deferred to later.\n");
}
return TRUE;
}
static void
x509_ca_uninit(void)
{
GList *l;
for (l = x509_ca_certs; l; l = l->next) {
x509_ca_element *el = l->data;
x509_ca_element_free(el);
}
g_list_free(x509_ca_certs);
x509_ca_certs = NULL;
x509_ca_initialized = FALSE;
/** TODO: the cert store in the SSL implementation wouldn't be cleared by this */
g_list_foreach(x509_ca_paths, (GFunc)g_free, NULL);
g_list_free(x509_ca_paths);
x509_ca_paths = NULL;
}
/** Look up a ca_element by dn */
static x509_ca_element *
x509_ca_locate_cert(GList *lst, const gchar *dn)
{
GList *cur;
for (cur = lst; cur; cur = cur->next) {
x509_ca_element *el = cur->data;
if (purple_strequal(dn, el->dn)) {
return el;
}
}
return NULL;
}
static GSList *
x509_ca_locate_certs(GList *lst, const gchar *dn)
{
GList *cur;
GSList *crts = NULL;
for (cur = lst; cur; cur = cur->next) {
x509_ca_element *el = cur->data;
if (purple_strequal(dn, el->dn)) {
crts = g_slist_prepend(crts, el);
}
}
return crts;
}
static gboolean
x509_ca_cert_in_pool(const gchar *id)
{
g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
g_return_val_if_fail(id, FALSE);
if (x509_ca_locate_cert(x509_ca_certs, id) != NULL) {
return TRUE;
} else {
return FALSE;
}
return FALSE;
}
static PurpleCertificate *
x509_ca_get_cert(const gchar *id)
{
PurpleCertificate *crt = NULL;
x509_ca_element *el;
g_return_val_if_fail(x509_ca_lazy_init(), NULL);
g_return_val_if_fail(id, NULL);
/* Search the memory-cached pool */
el = x509_ca_locate_cert(x509_ca_certs, id);
if (el != NULL) {
/* Make a copy of the memcached one for the function caller
to play with */
crt = purple_certificate_copy(el->crt);
} else {
crt = NULL;
}
return crt;
}
static GSList *
x509_ca_get_certs(const gchar *id)
{
GSList *crts = NULL, *els = NULL;
g_return_val_if_fail(x509_ca_lazy_init(), NULL);
g_return_val_if_fail(id, NULL);
/* Search the memory-cached pool */
els = x509_ca_locate_certs(x509_ca_certs, id);
if (els != NULL) {
GSList *cur;
/* Make a copy of the memcached ones for the function caller
to play with */
for (cur = els; cur; cur = cur->next) {
x509_ca_element *el = cur->data;
crts = g_slist_prepend(crts, purple_certificate_copy(el->crt));
}
g_slist_free(els);
}
return crts;
}
static gboolean
x509_ca_put_cert(const gchar *id, PurpleCertificate *crt)
{
gboolean ret = FALSE;
g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
/* TODO: This is a quick way of doing this. At some point the change
ought to be flushed to disk somehow. */
ret = x509_ca_quiet_put_cert(crt);
return ret;
}
static gboolean
x509_ca_delete_cert(const gchar *id)
{
x509_ca_element *el;
g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
g_return_val_if_fail(id, FALSE);
/* Is the id even in the pool? */
el = x509_ca_locate_cert(x509_ca_certs, id);
if ( el == NULL ) {
purple_debug_warning("certificate/x509/ca",
"Id %s wasn't in the pool\n",
id);
return FALSE;
}
/* Unlink it from the memory cache and destroy it */
x509_ca_certs = g_list_remove(x509_ca_certs, el);
x509_ca_element_free(el);
return TRUE;
}
static GList *
x509_ca_get_idlist(void)
{
GList *l, *idlist;
g_return_val_if_fail(x509_ca_lazy_init(), NULL);
idlist = NULL;
for (l = x509_ca_certs; l; l = l->next) {
x509_ca_element *el = l->data;
idlist = g_list_prepend(idlist, g_strdup(el->dn));
}
return idlist;
}
static PurpleCertificatePool x509_ca = {
"x509", /* Scheme name */
"ca", /* Pool name */
N_("Certificate Authorities"),/* User-friendly name */
NULL, /* Internal data */
x509_ca_init, /* init */
x509_ca_uninit, /* uninit */
x509_ca_cert_in_pool, /* Certificate exists? */
x509_ca_get_cert, /* Cert retriever */
x509_ca_put_cert, /* Cert writer */
x509_ca_delete_cert, /* Cert remover */
x509_ca_get_idlist, /* idlist retriever */
NULL,
NULL,
NULL,
NULL
};
/***** 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 */
if (ret != 0)
purple_debug_info("certificate/tls_peers",
"Could not create %s. Certificates will not be cached.\n",
poolpath);
g_free(poolpath);
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);
if (crt->scheme->register_trusted_tls_cert) {
ret = (crt->scheme->register_trusted_tls_cert)(crt, FALSE);
}
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 */
NULL,
NULL,
NULL,
NULL
};
/***** A Verifier that uses the tls_peers cache and the CA pool to validate certificates *****/
static PurpleCertificateVerifier x509_tls_cached;
/* The following is several hacks piled together and needs to be fixed.
* It exists because show_cert (see its comments) needs the original reason
* given to user_auth in order to rebuild the dialog.
*/
/* TODO: This will cause a ua_ctx to become memleaked if the request(s) get
closed by handle or otherwise abnormally. */
typedef struct {
PurpleCertificateVerificationRequest *vrq;
gchar *reason;
} x509_tls_cached_ua_ctx;
static x509_tls_cached_ua_ctx *
x509_tls_cached_ua_ctx_new(PurpleCertificateVerificationRequest *vrq,
const gchar *reason)
{
x509_tls_cached_ua_ctx *c;
c = g_new0(x509_tls_cached_ua_ctx, 1);
c->vrq = vrq;
c->reason = g_strdup(reason);
return c;
}
static void
x509_tls_cached_ua_ctx_free(x509_tls_cached_ua_ctx *c)
{
g_return_if_fail(c);
g_free(c->reason);
g_free(c);
}
static void
x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq,
const gchar *reason);
static void
x509_tls_cached_show_cert(x509_tls_cached_ua_ctx *c, gint id)
{
PurpleCertificate *disp_crt = c->vrq->cert_chain->data;
/* Since clicking a button closes the request, show it again */
x509_tls_cached_user_auth(c->vrq, c->reason);
/* Show the certificate AFTER re-opening the dialog so that this
appears above the other */
purple_certificate_display_x509(disp_crt);
x509_tls_cached_ua_ctx_free(c);
}
static void
x509_tls_cached_user_auth_cb (x509_tls_cached_ua_ctx *c, gint id)
{
PurpleCertificateVerificationRequest *vrq;
PurpleCertificatePool *tls_peers;
g_return_if_fail(c);
g_return_if_fail(c->vrq);
vrq = c->vrq;
x509_tls_cached_ua_ctx_free(c);
tls_peers = purple_certificate_find_pool("x509","tls_peers");
if (2 == id) {
gchar *cache_id = vrq->subject_name;
purple_debug_info("certificate/x509/tls_cached",
"User ACCEPTED cert\nCaching first in chain for future use as %s...\n",
cache_id);
purple_certificate_pool_store(tls_peers, cache_id,
vrq->cert_chain->data);
purple_certificate_verify_complete(vrq,
PURPLE_CERTIFICATE_VALID);
} else {
purple_debug_warning("certificate/x509/tls_cached",
"User REJECTED cert\n");
purple_certificate_verify_complete(vrq,
PURPLE_CERTIFICATE_INVALID);
}
}
static void
x509_tls_cached_user_auth_accept_cb(x509_tls_cached_ua_ctx *c, gint ignore)
{
x509_tls_cached_user_auth_cb(c, 2);
}
static void
x509_tls_cached_user_auth_reject_cb(x509_tls_cached_ua_ctx *c, gint ignore)
{
x509_tls_cached_user_auth_cb(c, 1);
}
/** Validates a certificate by asking the user
* @param reason String to explain why the user needs to accept/refuse the
* certificate.
* @todo Needs a handle argument
*/
static void
x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq,
const gchar *reason)
{
gchar *primary;
/* Make messages */
primary = g_strdup_printf(_("Accept certificate for %s?"),
vrq->subject_name);
/* Make a semi-pretty display */
purple_request_action(
vrq->cb_data, /* TODO: Find what the handle ought to be */
_("SSL Certificate Verification"),
primary,
reason,
0, /* Accept by default */
NULL, /* No account */
NULL, /* No other user */
NULL, /* No associated conversation */
x509_tls_cached_ua_ctx_new(vrq, reason),
3, /* Number of actions */
_("Accept"), x509_tls_cached_user_auth_accept_cb,
_("Reject"), x509_tls_cached_user_auth_reject_cb,
_("_View Certificate..."), x509_tls_cached_show_cert);
/* Cleanup */
g_free(primary);
}
static void
x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq,
PurpleCertificateInvalidityFlags flags);
static void
x509_tls_cached_complete(PurpleCertificateVerificationRequest *vrq,
PurpleCertificateInvalidityFlags flags)
{
PurpleCertificatePool *tls_peers;
PurpleCertificate *peer_crt = vrq->cert_chain->data;
if (flags & PURPLE_CERTIFICATE_FATALS_MASK) {
/* TODO: Also print any other warnings? */
const gchar *error;
gchar *tmp, *secondary;
if (flags & PURPLE_CERTIFICATE_INVALID_CHAIN)
error = invalidity_reason_to_string(PURPLE_CERTIFICATE_INVALID_CHAIN);
else if (flags & PURPLE_CERTIFICATE_REVOKED)
error = invalidity_reason_to_string(PURPLE_CERTIFICATE_REVOKED);
else
error = invalidity_reason_to_string(PURPLE_CERTIFICATE_UNKNOWN_ERROR);
tmp = g_strdup_printf(_("The certificate for %s could not be validated."),
vrq->subject_name);
secondary = g_strconcat(tmp, " ", error, NULL);
g_free(tmp);
purple_notify_error(NULL, /* TODO: Probably wrong. */
_("SSL Certificate Error"),
_("Unable to validate certificate"),
secondary);
g_free(secondary);
purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_INVALID);
return;
} else if (flags & PURPLE_CERTIFICATE_NON_FATALS_MASK) {
/* Non-fatal error. Prompt the user. */
gchar *tmp;
GString *errors;
guint32 i = 1;
tmp = g_strdup_printf(_("The certificate for %s could not be validated."),
vrq->subject_name);
errors = g_string_new(tmp);
g_free(tmp);
errors = g_string_append_c(errors, '\n');
/* Special case a name mismatch because we want to display the two names... */
if (flags & PURPLE_CERTIFICATE_NAME_MISMATCH) {
gchar *sn = purple_certificate_get_subject_name(peer_crt);
if (sn) {
g_string_append_printf(errors, _("The certificate claims to be "
"from \"%s\" instead. This could mean that you are "
"not connecting to the service you believe you are."),
sn);
g_free(sn);
flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH;
}
}
while (i != PURPLE_CERTIFICATE_LAST) {
if (flags & i) {
errors = g_string_append_c(errors, '\n');
g_string_append(errors, invalidity_reason_to_string(i));
}
i <<= 1;
}
x509_tls_cached_user_auth(vrq, errors->str);
g_string_free(errors, TRUE);
return;
}
/* 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) {
if (!purple_certificate_pool_store(tls_peers,vrq->subject_name,
peer_crt)) {
purple_debug_error("certificate/x509/tls_cached",
"FAILED to cache peer certificate\n");
}
} else {
purple_debug_error("certificate/x509/tls_cached",
"Unable to locate tls_peers certificate cache.\n");
}
purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID);
}
static void
x509_tls_cached_cert_in_cache(PurpleCertificateVerificationRequest *vrq,
PurpleCertificateInvalidityFlags flags)
{
/* 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);
if ( !cached_crt ) {
purple_debug_warning("certificate/x509/tls_cached",
"Lookup failed on cached certificate!\n"
"Falling back to full verification.\n");
/* vrq now becomes the problem of unknown_peer */
x509_tls_cached_unknown_peer(vrq, flags);
return;
}
/* Now get SHA256 sums for both and compare them */
/* TODO: This is not an elegant way to compare certs */
peer_fpr = purple_certificate_get_fingerprint_sha256(peer_crt, TRUE);
cached_fpr = purple_certificate_get_fingerprint_sha256(cached_crt, TRUE);
if (!memcmp(peer_fpr->data, cached_fpr->data, peer_fpr->len)) {
purple_debug_info("certificate/x509/tls_cached",
"Peer cert matched cached\n");
x509_tls_cached_complete(vrq, flags);
} else {
purple_debug_error("certificate/x509/tls_cached",
"Peer cert did NOT match cached\n");
/* vrq now becomes the problem of the user */
x509_tls_cached_unknown_peer(vrq, flags);
}
purple_certificate_destroy(cached_crt);
g_byte_array_free(peer_fpr, TRUE);
g_byte_array_free(cached_fpr, TRUE);
}
/*
* This is called from two points in x509_tls_cached_unknown_peer below
* once we've verified the signature chain is valid. Now we need to verify
* the subject name of the certificate.
*/
static void
x509_tls_cached_check_subject_name(PurpleCertificateVerificationRequest *vrq,
PurpleCertificateInvalidityFlags flags)
{
PurpleCertificate *peer_crt;
GList *chain = vrq->cert_chain;
peer_crt = (PurpleCertificate *) chain->data;
/* Last, 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);
flags |= PURPLE_CERTIFICATE_NAME_MISMATCH;
purple_debug_error("certificate/x509/tls_cached",
"Name mismatch: Certificate given for %s "
"has a name of %s\n",
vrq->subject_name, sn);
g_free(sn);
}
x509_tls_cached_complete(vrq, flags);
}
/* For when we've never communicated with this party before */
/* TODO: Need ways to specify possibly multiple problems with a cert, or at
least reprioritize them.
*/
static void
x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq,
PurpleCertificateInvalidityFlags flags)
{
PurpleCertificatePool *ca;
PurpleCertificate *peer_crt;
PurpleCertificate *ca_crt, *end_crt;
PurpleCertificate *failing_crt;
GList *chain = vrq->cert_chain;
GSList *ca_crts, *cur;
GByteArray *last_fpr, *ca_fpr;
gboolean valid = FALSE;
gchar *ca_id, *ca2_id;
peer_crt = (PurpleCertificate *) chain->data;
if (peer_crt->scheme->verify_cert) {
/** Make sure we've loaded the CA certs (which causes NSS to trust them) */
g_return_if_fail(x509_ca_lazy_init());
peer_crt->scheme->verify_cert(vrq, &flags);
x509_tls_cached_complete(vrq, flags);
return;
}
/* TODO: Figure out a way to check for a bad signature, as opposed to
"not self-signed" */
if ( purple_certificate_signed_by(peer_crt, peer_crt) ) {
flags |= PURPLE_CERTIFICATE_SELF_SIGNED;
purple_debug_info("certificate/x509/tls_cached",
"Certificate for %s is self-signed.\n",
vrq->subject_name);
x509_tls_cached_check_subject_name(vrq, flags);
return;
} /* if (self signed) */
ca = purple_certificate_find_pool(x509_tls_cached.scheme_name, "ca");
/* Next, check that the certificate chain is valid */
if (!purple_certificate_check_signature_chain_with_failing(chain,
&failing_crt))
{
gboolean chain_validated = FALSE;
/*
* Check if the failing certificate is in the CA store. If it is, then
* consider this fully validated. This works around issues with some
* prominent intermediate CAs whose signature is md5WithRSAEncryption.
* I'm looking at CACert Class 3 here. See #4458 for details.
*/
if (ca) {
gchar *uid = purple_certificate_get_unique_id(failing_crt);
PurpleCertificate *ca_crt = purple_certificate_pool_retrieve(ca, uid);
if (ca_crt != NULL) {
GByteArray *failing_fpr;
GByteArray *ca_fpr;
failing_fpr = purple_certificate_get_fingerprint_sha256(failing_crt, TRUE);
ca_fpr = purple_certificate_get_fingerprint_sha256(ca_crt, TRUE);
if (byte_arrays_equal(failing_fpr, ca_fpr)) {
purple_debug_info("certificate/x509/tls_cached",
"Full chain verification failed (probably a bad "
"signature algorithm), but found the last "
"certificate %s in the CA pool.\n", uid);
chain_validated = TRUE;
}
g_byte_array_free(failing_fpr, TRUE);
g_byte_array_free(ca_fpr, TRUE);
}
purple_certificate_destroy(ca_crt);
g_free(uid);
}
/*
* If we get here, either the cert matched the stuff right above
* or it didn't, in which case we give up and complain to the user.
*/
if (!chain_validated)
/* TODO: Tell the user where the chain broke? */
flags |= PURPLE_CERTIFICATE_INVALID_CHAIN;
x509_tls_cached_check_subject_name(vrq, flags);
return;
} /* if (signature chain not good) */
/* Next, attempt to verify the last certificate is signed by a trusted
* CA, or is a trusted CA (based on fingerprint).
*/
/* If, for whatever reason, there is no Certificate Authority pool
loaded, we'll verify the subject name and then warn about thsi. */
if ( !ca ) {
purple_debug_error("certificate/x509/tls_cached",
"No X.509 Certificate Authority pool "
"could be found!\n");
flags |= PURPLE_CERTIFICATE_NO_CA_POOL;
x509_tls_cached_check_subject_name(vrq, flags);
return;
}
end_crt = g_list_last(chain)->data;
/* Attempt to look up the last certificate, and the last certificate's
* issuer.
*/
ca_id = purple_certificate_get_issuer_unique_id(end_crt);
ca2_id = purple_certificate_get_unique_id(end_crt);
purple_debug_info("certificate/x509/tls_cached",
"Checking for a CA with DN=%s\n",
ca_id);
purple_debug_info("certificate/x509/tls_cached",
"Also checking for a CA with DN=%s\n",
ca2_id);
ca_crts = g_slist_concat(x509_ca_get_certs(ca_id), x509_ca_get_certs(ca2_id));
g_free(ca_id);
g_free(ca2_id);
if ( NULL == ca_crts ) {
flags |= PURPLE_CERTIFICATE_CA_UNKNOWN;
purple_debug_warning("certificate/x509/tls_cached",
"No Certificate Authorities with either DN found "
"found. I'll prompt the user, I guess.\n");
x509_tls_cached_check_subject_name(vrq, flags);
return;
}
/*
* Check the fingerprints; if they match, then this certificate *is* one
* of the designated "trusted roots", and we don't need to verify the
* signature. This is good because some of the older roots are self-signed
* with bad hash algorithms that we don't want to allow in any other
* circumstances (one of Verisign's root CAs is self-signed with MD2).
*
* If the fingerprints don't match, we'll fall back to checking the
* signature.
*/
last_fpr = purple_certificate_get_fingerprint_sha256(end_crt, TRUE);
ca_id = purple_certificate_get_unique_id(end_crt);
for (cur = ca_crts; cur; cur = cur->next) {
ca_crt = cur->data;
ca_fpr = purple_certificate_get_fingerprint_sha256(ca_crt, TRUE);
ca2_id = purple_certificate_get_unique_id(ca_crt);
if ( byte_arrays_equal(last_fpr, ca_fpr) ||
(purple_strequal(ca_id, ca2_id) &&
purple_certificate_compare_pubkeys(end_crt, ca_crt)) ||
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. */
valid = TRUE;
g_byte_array_free(ca_fpr, TRUE);
g_free(ca2_id);
break;
}
g_byte_array_free(ca_fpr, TRUE);
g_free(ca2_id);
}
g_free(ca_id);
if (valid == FALSE)
flags |= PURPLE_CERTIFICATE_INVALID_CHAIN;
g_slist_foreach(ca_crts, (GFunc)purple_certificate_destroy, NULL);
g_slist_free(ca_crts);
g_byte_array_free(last_fpr, TRUE);
x509_tls_cached_check_subject_name(vrq, flags);
}
static void
x509_tls_cached_start_verify(PurpleCertificateVerificationRequest *vrq)
{
const gchar *tls_peers_name = "tls_peers"; /* Name of local cache */
PurpleCertificatePool *tls_peers;
time_t now, activation, expiration;
PurpleCertificateInvalidityFlags flags = PURPLE_CERTIFICATE_NO_PROBLEMS;
gboolean ret;
g_return_if_fail(vrq);
purple_debug_info("certificate/x509/tls_cached",
"Starting verify for %s\n",
vrq->subject_name);
/*
* Verify the first certificate (the main one) has been activated and
* isn't expired, i.e. activation < now < expiration.
*/
now = time(NULL);
ret = purple_certificate_get_times(vrq->cert_chain->data, &activation,
&expiration);
if (!ret) {
flags |= PURPLE_CERTIFICATE_EXPIRED | PURPLE_CERTIFICATE_NOT_ACTIVATED;
purple_debug_error("certificate/x509/tls_cached",
"Failed to get validity times for certificate %s\n",
vrq->subject_name);
} else if (now > expiration) {
flags |= PURPLE_CERTIFICATE_EXPIRED;
purple_debug_error("certificate/x509/tls_cached",
"Certificate %s expired at %s\n",
vrq->subject_name, ctime(&expiration));
} else if (now < activation) {
flags |= PURPLE_CERTIFICATE_NOT_ACTIVATED;
purple_debug_error("certificate/x509/tls_cached",
"Certificate %s is not yet valid, will be at %s\n",
vrq->subject_name, ctime(&activation));
}
tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,tls_peers_name);
if (!tls_peers) {
purple_debug_error("certificate/x509/tls_cached",
"Couldn't find local peers cache %s\n",
tls_peers_name);
/* vrq now becomes the problem of unknown_peer */
x509_tls_cached_unknown_peer(vrq, flags);
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, flags);
} else {
purple_debug_warning("certificate/x509/tls_cached",
"...Not in cache\n");
/* vrq now becomes the problem of unknown_peer */
x509_tls_cached_unknown_peer(vrq, flags);
}
}
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 */
NULL,
NULL,
NULL,
NULL
};
/****************************************************************************/
/* 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)
{
/* Unregister all Verifiers */
g_list_foreach(cert_verifiers, (GFunc)purple_certificate_unregister_verifier, NULL);
/* Unregister all Pools */
g_list_foreach(cert_pools, (GFunc)purple_certificate_unregister_pool, NULL);
}
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)
{
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) {
gboolean success;
success = pool->init();
if (!success)
return FALSE;
}
/* Register the Pool */
cert_pools = g_list_prepend(cert_pools, pool);
/* TODO: Emit a signal that the pool got registered */
PURPLE_DBUS_REGISTER_POINTER(pool, PurpleCertificatePool);
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;
}
gboolean
purple_certificate_unregister_pool(PurpleCertificatePool *pool)
{
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 */
PURPLE_DBUS_UNREGISTER_POINTER(pool);
if (pool->uninit) {
pool->uninit();
}
cert_pools = g_list_remove(cert_pools, pool);
/* TODO: Signalling? */
purple_signal_unregister(pool, "certificate-stored");
purple_signal_unregister(pool, "certificate-deleted");
purple_debug_info("certificate",
"CertificatePool %s unregistered\n",
pool->name);
return TRUE;
}
/****************************************************************************/
/* Scheme-specific functions */
/****************************************************************************/
static void display_x509_issuer(gchar *issuer_id) {
PurpleCertificate *issuer_crt;
issuer_crt = x509_ca_get_cert(issuer_id);
if (issuer_crt) {
purple_certificate_display_x509(issuer_crt);
purple_certificate_destroy(issuer_crt);
} else {
purple_notify_info(NULL, /* TODO: Find what the handle ought to be */
_("Certificate Information"),
"",
_("Unable to find Issuer Certificate"));
}
g_free(issuer_id);
}
void
purple_certificate_display_x509(PurpleCertificate *crt)
{
gchar *sha1_asc, *sha256_asc;
gchar *cn, *issuer_id;
time_t activation, expiration;
gchar *activ_str, *expir_str;
gchar *secondary, *secondary_extra;
gboolean self_signed;
get_ascii_fingerprints(crt, &sha1_asc, &sha256_asc);
/* Get the cert Common Name */
/* TODO: Will break on CA certs */
cn = purple_certificate_get_subject_name(crt);
issuer_id = purple_certificate_get_issuer_unique_id(crt);
/* Get the certificate times */
/* TODO: Check the times against localtime */
/* TODO: errorcheck? */
if (!purple_certificate_get_times(crt, &activation, &expiration)) {
purple_debug_error("certificate",
"Failed to get certificate times!\n");
activation = expiration = 0;
}
activ_str = g_strdup(ctime(&activation));
expir_str = g_strdup(ctime(&expiration));
self_signed = purple_certificate_signed_by(crt, crt);
/* Make messages */
secondary = g_strdup_printf(_("Common name: %s\n\n"
"Issued By: %s\n\n"
"Fingerprint (SHA1): %s\n\n"
"Activation date: %s\n"
"Expiration date: %s\n"),
cn ? cn : "(null)",
self_signed ? _("(self-signed)") : (issuer_id ? issuer_id : "(null)"),
sha1_asc ? sha1_asc : "(null)",
activ_str ? activ_str : "(null)",
expir_str ? expir_str : "(null)");
/* TODO: make this part of the translatable string above */
secondary_extra = g_strdup_printf("%sSHA256: %s", secondary, sha256_asc);
/* Make a semi-pretty display */
if (self_signed) {
purple_notify_info(NULL, /* TODO: Find what the handle ought to be */
_("Certificate Information"),
"",
secondary_extra);
} else {
purple_request_action(NULL, /* TODO: Find what the handle ought to be */
_("Certificate Information"), _("Certificate Information"),
secondary_extra, 2, NULL, NULL, NULL,
issuer_id, 2,
_("View Issuer Certificate"), PURPLE_CALLBACK(display_x509_issuer),
_("Close"), PURPLE_CALLBACK(g_free));
/* purple_request_action has taken ownership of issuer_id */
issuer_id = NULL;
}
/* Cleanup */
g_free(cn);
g_free(issuer_id);
g_free(secondary);
g_free(secondary_extra);
g_free(sha1_asc);
g_free(sha256_asc);
g_free(activ_str);
g_free(expir_str);
}
void purple_certificate_add_ca_search_path(const char *path)
{
if (g_list_find_custom(x509_ca_paths, path, (GCompareFunc)strcmp))
return;
x509_ca_paths = g_list_append(x509_ca_paths, g_strdup(path));
}