I think this was accidentally changed in revision
[pidgin-git.git] / libpurple / certificate.c
blob8cc66fb3cd250f7723a0b7457f59fa640a2194cd
1 /**
2 * @file certificate.c Public-Key Certificate API
3 * @ingroup core
4 */
6 /*
8 * purple
10 * Purple is the legal property of its developers, whose names are too numerous
11 * to list here. Please refer to the COPYRIGHT file distributed with this
12 * source distribution.
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
29 #include "internal.h"
30 #include "certificate.h"
31 #include "dbus-maybe.h"
32 #include "debug.h"
33 #include "request.h"
34 #include "signals.h"
35 #include "util.h"
37 /** List holding pointers to all registered certificate schemes */
38 static GList *cert_schemes = NULL;
39 /** List of registered Verifiers */
40 static GList *cert_verifiers = NULL;
41 /** List of registered Pools */
42 static GList *cert_pools = NULL;
45 * TODO: Merge this with PurpleCertificateVerificationStatus for 3.0.0 */
46 typedef enum {
47 PURPLE_CERTIFICATE_UNKNOWN_ERROR = -1,
49 /* Not an error */
50 PURPLE_CERTIFICATE_NO_PROBLEMS = 0,
52 /* Non-fatal */
53 PURPLE_CERTIFICATE_NON_FATALS_MASK = 0x0000FFFF,
55 /* The certificate is self-signed. */
56 PURPLE_CERTIFICATE_SELF_SIGNED = 0x01,
58 /* The CA is not in libpurple's pool of certificates. */
59 PURPLE_CERTIFICATE_CA_UNKNOWN = 0x02,
61 /* The current time is before the certificate's specified
62 * activation time.
64 PURPLE_CERTIFICATE_NOT_ACTIVATED = 0x04,
66 /* The current time is after the certificate's specified expiration time */
67 PURPLE_CERTIFICATE_EXPIRED = 0x08,
69 /* The certificate's subject name doesn't match the expected */
70 PURPLE_CERTIFICATE_NAME_MISMATCH = 0x10,
72 /* No CA pool was found. This shouldn't happen... */
73 PURPLE_CERTIFICATE_NO_CA_POOL = 0x20,
75 /* Fatal */
76 PURPLE_CERTIFICATE_FATALS_MASK = 0xFFFF0000,
78 /* The signature chain could not be validated. Due to limitations in the
79 * the current API, this also indicates one of the CA certificates in the
80 * chain is expired (or not yet activated). FIXME 3.0.0 */
81 PURPLE_CERTIFICATE_INVALID_CHAIN = 0x10000,
83 /* The signature has been revoked. */
84 PURPLE_CERTIFICATE_REVOKED = 0x20000,
86 PURPLE_CERTIFICATE_LAST = 0x40000,
87 } PurpleCertificateInvalidityFlags;
89 static const gchar *
90 invalidity_reason_to_string(PurpleCertificateInvalidityFlags flag)
92 switch (flag) {
93 case PURPLE_CERTIFICATE_SELF_SIGNED:
94 return _("The certificate is self-signed and cannot be "
95 "automatically checked.");
96 break;
97 case PURPLE_CERTIFICATE_CA_UNKNOWN:
98 return _("The certificate is not trusted because no certificate "
99 "that can verify it is currently trusted.");
100 break;
101 case PURPLE_CERTIFICATE_NOT_ACTIVATED:
102 return _("The certificate is not valid yet. Check that your "
103 "computer's date and time are accurate.");
104 break;
105 case PURPLE_CERTIFICATE_EXPIRED:
106 return _("The certificate has expired and should not be "
107 "considered valid. Check that your computer's date "
108 "and time are accurate.");
109 break;
110 case PURPLE_CERTIFICATE_NAME_MISMATCH:
111 /* Translators: "domain" refers to a DNS domain (e.g. talk.google.com) */
112 return _("The certificate presented is not issued to this domain.");
113 break;
114 case PURPLE_CERTIFICATE_NO_CA_POOL:
115 return _("You have no database of root certificates, so "
116 "this certificate cannot be validated.");
117 break;
118 case PURPLE_CERTIFICATE_INVALID_CHAIN:
119 return _("The certificate chain presented is invalid.");
120 break;
121 case PURPLE_CERTIFICATE_REVOKED:
122 return _("The certificate has been revoked.");
123 break;
124 case PURPLE_CERTIFICATE_UNKNOWN_ERROR:
125 default:
126 return _("An unknown certificate error occurred.");
127 break;
131 void
132 purple_certificate_verify (PurpleCertificateVerifier *verifier,
133 const gchar *subject_name, GList *cert_chain,
134 PurpleCertificateVerifiedCallback cb,
135 gpointer cb_data)
137 PurpleCertificateVerificationRequest *vrq;
138 PurpleCertificateScheme *scheme;
140 g_return_if_fail(subject_name != NULL);
141 /* If you don't have a cert to check, why are you requesting that it
142 be verified? */
143 g_return_if_fail(cert_chain != NULL);
144 g_return_if_fail(cb != NULL);
146 /* Look up the CertificateScheme */
147 scheme = purple_certificate_find_scheme(verifier->scheme_name);
148 g_return_if_fail(scheme);
150 /* Check that at least the first cert in the chain matches the
151 Verifier scheme */
152 g_return_if_fail(scheme ==
153 ((PurpleCertificate *) (cert_chain->data))->scheme);
155 /* Construct and fill in the request fields */
156 vrq = g_new0(PurpleCertificateVerificationRequest, 1);
157 vrq->verifier = verifier;
158 vrq->scheme = scheme;
159 vrq->subject_name = g_strdup(subject_name);
160 vrq->cert_chain = purple_certificate_copy_list(cert_chain);
161 vrq->cb = cb;
162 vrq->cb_data = cb_data;
164 /* Initiate verification */
165 (verifier->start_verification)(vrq);
168 void
169 purple_certificate_verify_complete(PurpleCertificateVerificationRequest *vrq,
170 PurpleCertificateVerificationStatus st)
172 PurpleCertificateVerifier *vr;
174 g_return_if_fail(vrq);
176 if (st == PURPLE_CERTIFICATE_VALID) {
177 purple_debug_info("certificate",
178 "Successfully verified certificate for %s\n",
179 vrq->subject_name);
180 } else {
181 purple_debug_error("certificate",
182 "Failed to verify certificate for %s\n",
183 vrq->subject_name);
186 /* Pass the results on to the request's callback */
187 (vrq->cb)(st, vrq->cb_data);
189 /* And now to eliminate the request */
190 /* Fetch the Verifier responsible... */
191 vr = vrq->verifier;
192 /* ...and order it to KILL */
193 (vr->destroy_request)(vrq);
195 /* Now the internals have been cleaned up, so clean up the libpurple-
196 created elements */
197 g_free(vrq->subject_name);
198 purple_certificate_destroy_list(vrq->cert_chain);
200 /* A structure born
201 * to much ado
202 * and with so much within.
203 * It reaches now
204 * its quiet end. */
205 g_free(vrq);
209 PurpleCertificate *
210 purple_certificate_copy(PurpleCertificate *crt)
212 g_return_val_if_fail(crt, NULL);
213 g_return_val_if_fail(crt->scheme, NULL);
214 g_return_val_if_fail(crt->scheme->copy_certificate, NULL);
216 return (crt->scheme->copy_certificate)(crt);
219 GList *
220 purple_certificate_copy_list(GList *crt_list)
222 GList *new_l, *l;
224 /* First, make a shallow copy of the list */
225 new_l = g_list_copy(crt_list);
227 /* Now go through and actually duplicate each certificate */
228 for (l = new_l; l; l = l->next) {
229 l->data = purple_certificate_copy(l->data);
232 return new_l;
235 void
236 purple_certificate_destroy (PurpleCertificate *crt)
238 PurpleCertificateScheme *scheme;
240 if (NULL == crt) return;
242 scheme = crt->scheme;
244 (scheme->destroy_certificate)(crt);
247 void
248 purple_certificate_destroy_list (GList * crt_list)
250 PurpleCertificate *crt;
251 GList *l;
253 for (l=crt_list; l; l = l->next) {
254 crt = (PurpleCertificate *) l->data;
255 purple_certificate_destroy(crt);
258 g_list_free(crt_list);
261 gboolean
262 purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer)
264 PurpleCertificateScheme *scheme;
266 g_return_val_if_fail(crt, FALSE);
267 g_return_val_if_fail(issuer, FALSE);
269 scheme = crt->scheme;
270 g_return_val_if_fail(scheme, FALSE);
271 /* We can't compare two certs of unrelated schemes, obviously */
272 g_return_val_if_fail(issuer->scheme == scheme, FALSE);
274 return (scheme->signed_by)(crt, issuer);
277 gboolean
278 purple_certificate_check_signature_chain_with_failing(GList *chain,
279 PurpleCertificate **failing)
281 GList *cur;
282 PurpleCertificate *crt, *issuer;
283 gchar *uid;
284 time_t now, activation, expiration;
285 gboolean ret;
287 g_return_val_if_fail(chain, FALSE);
289 if (failing)
290 *failing = NULL;
292 uid = purple_certificate_get_unique_id((PurpleCertificate *) chain->data);
293 purple_debug_info("certificate",
294 "Checking signature chain for uid=%s\n",
295 uid);
296 g_free(uid);
298 /* If this is a single-certificate chain, say that it is valid */
299 if (chain->next == NULL) {
300 purple_debug_info("certificate",
301 "...Singleton. We'll say it's valid.\n");
302 return TRUE;
305 now = time(NULL);
307 /* Load crt with the first certificate */
308 crt = (PurpleCertificate *)(chain->data);
309 /* And start with the second certificate in the chain */
310 for ( cur = chain->next; cur; cur = cur->next ) {
312 issuer = (PurpleCertificate *)(cur->data);
314 uid = purple_certificate_get_unique_id(issuer);
316 ret = purple_certificate_get_times(issuer, &activation, &expiration);
317 if (!ret || now < activation || now > expiration) {
318 if (!ret)
319 purple_debug_error("certificate",
320 "...Failed to get validity times for certificate %s\n"
321 "Chain is INVALID\n", uid);
322 else if (now > expiration)
323 purple_debug_error("certificate",
324 "...Issuer %s expired at %s\nChain is INVALID\n",
325 uid, ctime(&expiration));
326 else
327 purple_debug_error("certificate",
328 "...Not-yet-activated issuer %s will be valid at %s\n"
329 "Chain is INVALID\n", uid, ctime(&activation));
331 if (failing)
332 *failing = crt;
334 g_free(uid);
335 return FALSE;
338 /* Check the signature for this link */
339 if (! purple_certificate_signed_by(crt, issuer) ) {
340 purple_debug_error("certificate",
341 "...Bad or missing signature by %s\nChain is INVALID\n",
342 uid);
343 g_free(uid);
345 if (failing)
346 *failing = crt;
348 return FALSE;
351 purple_debug_info("certificate",
352 "...Good signature by %s\n",
353 uid);
354 g_free(uid);
356 /* The issuer is now the next crt whose signature is to be
357 checked */
358 crt = issuer;
361 /* If control reaches this point, the chain is valid */
362 purple_debug_info("certificate", "Chain is VALID\n");
363 return TRUE;
366 gboolean
367 purple_certificate_check_signature_chain(GList *chain)
369 return purple_certificate_check_signature_chain_with_failing(chain, NULL);
372 PurpleCertificate *
373 purple_certificate_import(PurpleCertificateScheme *scheme, const gchar *filename)
375 g_return_val_if_fail(scheme, NULL);
376 g_return_val_if_fail(scheme->import_certificate, NULL);
377 g_return_val_if_fail(filename, NULL);
379 return (scheme->import_certificate)(filename);
382 GSList *
383 purple_certificates_import(PurpleCertificateScheme *scheme, const gchar *filename)
385 g_return_val_if_fail(scheme, NULL);
386 g_return_val_if_fail(scheme->import_certificates, NULL);
387 g_return_val_if_fail(filename, NULL);
389 return (scheme->import_certificates)(filename);
392 gboolean
393 purple_certificate_export(const gchar *filename, PurpleCertificate *crt)
395 PurpleCertificateScheme *scheme;
397 g_return_val_if_fail(filename, FALSE);
398 g_return_val_if_fail(crt, FALSE);
399 g_return_val_if_fail(crt->scheme, FALSE);
401 scheme = crt->scheme;
402 g_return_val_if_fail(scheme->export_certificate, FALSE);
404 return (scheme->export_certificate)(filename, crt);
407 static gboolean
408 byte_arrays_equal(const GByteArray *array1, const GByteArray *array2)
410 g_return_val_if_fail(array1 != NULL, FALSE);
411 g_return_val_if_fail(array2 != NULL, FALSE);
413 return (array1->len == array2->len) &&
414 (0 == memcmp(array1->data, array2->data, array1->len));
417 GByteArray *
418 purple_certificate_get_fingerprint_sha1(PurpleCertificate *crt)
420 PurpleCertificateScheme *scheme;
421 GByteArray *fpr;
423 g_return_val_if_fail(crt, NULL);
424 g_return_val_if_fail(crt->scheme, NULL);
426 scheme = crt->scheme;
428 g_return_val_if_fail(scheme->get_fingerprint_sha1, NULL);
430 fpr = (scheme->get_fingerprint_sha1)(crt);
432 return fpr;
435 gchar *
436 purple_certificate_get_unique_id(PurpleCertificate *crt)
438 g_return_val_if_fail(crt, NULL);
439 g_return_val_if_fail(crt->scheme, NULL);
440 g_return_val_if_fail(crt->scheme->get_unique_id, NULL);
442 return (crt->scheme->get_unique_id)(crt);
445 gchar *
446 purple_certificate_get_issuer_unique_id(PurpleCertificate *crt)
448 g_return_val_if_fail(crt, NULL);
449 g_return_val_if_fail(crt->scheme, NULL);
450 g_return_val_if_fail(crt->scheme->get_issuer_unique_id, NULL);
452 return (crt->scheme->get_issuer_unique_id)(crt);
455 gchar *
456 purple_certificate_get_subject_name(PurpleCertificate *crt)
458 PurpleCertificateScheme *scheme;
459 gchar *subject_name;
461 g_return_val_if_fail(crt, NULL);
462 g_return_val_if_fail(crt->scheme, NULL);
464 scheme = crt->scheme;
466 g_return_val_if_fail(scheme->get_subject_name, NULL);
468 subject_name = (scheme->get_subject_name)(crt);
470 return subject_name;
473 gboolean
474 purple_certificate_check_subject_name(PurpleCertificate *crt, const gchar *name)
476 PurpleCertificateScheme *scheme;
478 g_return_val_if_fail(crt, FALSE);
479 g_return_val_if_fail(crt->scheme, FALSE);
480 g_return_val_if_fail(name, FALSE);
482 scheme = crt->scheme;
484 g_return_val_if_fail(scheme->check_subject_name, FALSE);
486 return (scheme->check_subject_name)(crt, name);
489 gboolean
490 purple_certificate_get_times(PurpleCertificate *crt, time_t *activation, time_t *expiration)
492 PurpleCertificateScheme *scheme;
494 g_return_val_if_fail(crt, FALSE);
496 scheme = crt->scheme;
498 g_return_val_if_fail(scheme, FALSE);
500 /* If both provided references are NULL, what are you doing calling
501 this? */
502 g_return_val_if_fail( (activation != NULL) || (expiration != NULL), FALSE);
504 /* Throw the request on down to the certscheme */
505 return (scheme->get_times)(crt, activation, expiration);
508 gchar *
509 purple_certificate_pool_mkpath(PurpleCertificatePool *pool, const gchar *id)
511 gchar *path;
512 gchar *esc_scheme_name, *esc_name, *esc_id;
514 g_return_val_if_fail(pool, NULL);
515 g_return_val_if_fail(pool->scheme_name, NULL);
516 g_return_val_if_fail(pool->name, NULL);
518 /* Escape all the elements for filesystem-friendliness */
519 esc_scheme_name = pool ? g_strdup(purple_escape_filename(pool->scheme_name)) : NULL;
520 esc_name = pool ? g_strdup(purple_escape_filename(pool->name)) : NULL;
521 esc_id = id ? g_strdup(purple_escape_filename(id)) : NULL;
523 path = g_build_filename(purple_user_dir(),
524 "certificates", /* TODO: constantize this? */
525 esc_scheme_name,
526 esc_name,
527 esc_id,
528 NULL);
530 g_free(esc_scheme_name);
531 g_free(esc_name);
532 g_free(esc_id);
533 return path;
536 gboolean
537 purple_certificate_pool_usable(PurpleCertificatePool *pool)
539 g_return_val_if_fail(pool, FALSE);
540 g_return_val_if_fail(pool->scheme_name, FALSE);
542 /* Check that the pool's scheme is loaded */
543 if (purple_certificate_find_scheme(pool->scheme_name) == NULL) {
544 return FALSE;
547 return TRUE;
550 PurpleCertificateScheme *
551 purple_certificate_pool_get_scheme(PurpleCertificatePool *pool)
553 g_return_val_if_fail(pool, NULL);
554 g_return_val_if_fail(pool->scheme_name, NULL);
556 return purple_certificate_find_scheme(pool->scheme_name);
559 gboolean
560 purple_certificate_pool_contains(PurpleCertificatePool *pool, const gchar *id)
562 g_return_val_if_fail(pool, FALSE);
563 g_return_val_if_fail(id, FALSE);
564 g_return_val_if_fail(pool->cert_in_pool, FALSE);
566 return (pool->cert_in_pool)(id);
569 PurpleCertificate *
570 purple_certificate_pool_retrieve(PurpleCertificatePool *pool, const gchar *id)
572 g_return_val_if_fail(pool, NULL);
573 g_return_val_if_fail(id, NULL);
574 g_return_val_if_fail(pool->get_cert, NULL);
576 return (pool->get_cert)(id);
579 gboolean
580 purple_certificate_pool_store(PurpleCertificatePool *pool, const gchar *id, PurpleCertificate *crt)
582 gboolean ret = FALSE;
584 g_return_val_if_fail(pool, FALSE);
585 g_return_val_if_fail(id, FALSE);
586 g_return_val_if_fail(pool->put_cert, FALSE);
588 /* Whether crt->scheme matches find_scheme(pool->scheme_name) is not
589 relevant... I think... */
590 g_return_val_if_fail(
591 g_ascii_strcasecmp(pool->scheme_name, crt->scheme->name) == 0,
592 FALSE);
594 ret = (pool->put_cert)(id, crt);
596 /* Signal that the certificate was stored if success*/
597 if (ret) {
598 purple_signal_emit(pool, "certificate-stored",
599 pool, id);
602 return ret;
605 gboolean
606 purple_certificate_pool_delete(PurpleCertificatePool *pool, const gchar *id)
608 gboolean ret = FALSE;
610 g_return_val_if_fail(pool, FALSE);
611 g_return_val_if_fail(id, FALSE);
612 g_return_val_if_fail(pool->delete_cert, FALSE);
614 ret = (pool->delete_cert)(id);
616 /* Signal that the certificate was deleted if success */
617 if (ret) {
618 purple_signal_emit(pool, "certificate-deleted",
619 pool, id);
622 return ret;
625 GList *
626 purple_certificate_pool_get_idlist(PurpleCertificatePool *pool)
628 g_return_val_if_fail(pool, NULL);
629 g_return_val_if_fail(pool->get_idlist, NULL);
631 return (pool->get_idlist)();
634 void
635 purple_certificate_pool_destroy_idlist(GList *idlist)
637 GList *l;
639 /* Iterate through and free them strings */
640 for ( l = idlist; l; l = l->next ) {
641 g_free(l->data);
644 g_list_free(idlist);
648 /****************************************************************************/
649 /* Builtin Verifiers, Pools, etc. */
650 /****************************************************************************/
652 static void
653 x509_singleuse_verify_cb (PurpleCertificateVerificationRequest *vrq, gint id)
655 g_return_if_fail(vrq);
657 purple_debug_info("certificate/x509_singleuse",
658 "VRQ on cert from %s gave %d\n",
659 vrq->subject_name, id);
661 /* Signal what happened back to the caller */
662 if (1 == id) {
663 /* Accepted! */
664 purple_certificate_verify_complete(vrq,
665 PURPLE_CERTIFICATE_VALID);
666 } else {
667 /* Not accepted */
668 purple_certificate_verify_complete(vrq,
669 PURPLE_CERTIFICATE_INVALID);
674 static void
675 x509_singleuse_start_verify (PurpleCertificateVerificationRequest *vrq)
677 gchar *sha_asc;
678 GByteArray *sha_bin;
679 gchar *cn;
680 const gchar *cn_match;
681 gchar *primary, *secondary;
682 PurpleCertificate *crt = (PurpleCertificate *) vrq->cert_chain->data;
684 /* Pull out the SHA1 checksum */
685 sha_bin = purple_certificate_get_fingerprint_sha1(crt);
686 /* Now decode it for display */
687 sha_asc = purple_base16_encode_chunked(sha_bin->data,
688 sha_bin->len);
690 /* Get the cert Common Name */
691 cn = purple_certificate_get_subject_name(crt);
693 /* Determine whether the name matches */
694 if (purple_certificate_check_subject_name(crt, vrq->subject_name)) {
695 cn_match = "";
696 } else {
697 cn_match = _("(DOES NOT MATCH)");
700 /* Make messages */
701 primary = g_strdup_printf(_("%s has presented the following certificate for just-this-once use:"), vrq->subject_name);
702 secondary = g_strdup_printf(_("Common name: %s %s\nFingerprint (SHA1): %s"), cn, cn_match, sha_asc);
704 /* Make a semi-pretty display */
705 purple_request_accept_cancel(
706 vrq->cb_data, /* TODO: Find what the handle ought to be */
707 _("Single-use Certificate Verification"),
708 primary,
709 secondary,
710 0, /* Accept by default */
711 NULL, /* No account */
712 NULL, /* No other user */
713 NULL, /* No associated conversation */
714 vrq,
715 x509_singleuse_verify_cb,
716 x509_singleuse_verify_cb );
718 /* Cleanup */
719 g_free(cn);
720 g_free(primary);
721 g_free(secondary);
722 g_free(sha_asc);
723 g_byte_array_free(sha_bin, TRUE);
726 static void
727 x509_singleuse_destroy_request (PurpleCertificateVerificationRequest *vrq)
729 /* I don't do anything! */
732 static PurpleCertificateVerifier x509_singleuse = {
733 "x509", /* Scheme name */
734 "singleuse", /* Verifier name */
735 x509_singleuse_start_verify, /* start_verification function */
736 x509_singleuse_destroy_request, /* Request cleanup operation */
738 NULL,
739 NULL,
740 NULL,
741 NULL
746 /***** X.509 Certificate Authority pool, keyed by Distinguished Name *****/
747 /* This is implemented in what may be the most inefficient and bugprone way
748 possible; however, future optimizations should not be difficult. */
750 static PurpleCertificatePool x509_ca;
752 /** Holds a key-value pair for quickish certificate lookup */
753 typedef struct {
754 gchar *dn;
755 PurpleCertificate *crt;
756 } x509_ca_element;
758 static void
759 x509_ca_element_free(x509_ca_element *el)
761 if (NULL == el) return;
763 g_free(el->dn);
764 purple_certificate_destroy(el->crt);
765 g_free(el);
768 /** System directory to probe for CA certificates */
769 /* This is set in the lazy_init function */
770 static GList *x509_ca_paths = NULL;
772 /** A list of loaded CAs, populated from the above path whenever the lazy_init
773 happens. Contains pointers to x509_ca_elements */
774 static GList *x509_ca_certs = NULL;
776 /** Used for lazy initialization purposes. */
777 static gboolean x509_ca_initialized = FALSE;
779 /** Adds a certificate to the in-memory cache, doing nothing else */
780 static gboolean
781 x509_ca_quiet_put_cert(PurpleCertificate *crt)
783 x509_ca_element *el;
785 /* lazy_init calls this function, so calling lazy_init here is a
786 Bad Thing */
788 g_return_val_if_fail(crt, FALSE);
789 g_return_val_if_fail(crt->scheme, FALSE);
790 /* Make sure that this is some kind of X.509 certificate */
791 /* TODO: Perhaps just check crt->scheme->name instead? */
792 g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_ca.scheme_name), FALSE);
794 el = g_new0(x509_ca_element, 1);
795 el->dn = purple_certificate_get_unique_id(crt);
796 el->crt = purple_certificate_copy(crt);
797 x509_ca_certs = g_list_prepend(x509_ca_certs, el);
799 return TRUE;
802 /* Since the libpurple CertificatePools get registered before plugins are
803 loaded, an X.509 Scheme is generally not available when x509_ca_init is
804 called, but x509_ca requires X.509 operations in order to properly load.
806 To solve this, I present the lazy_init function. It attempts to finish
807 initialization of the Pool, but it usually fails when it is called from
808 x509_ca_init. However, this is OK; initialization is then simply deferred
809 until someone tries to use functions from the pool. */
810 static gboolean
811 x509_ca_lazy_init(void)
813 PurpleCertificateScheme *x509;
814 GDir *certdir;
815 const gchar *entry;
816 GPatternSpec *pempat, *crtpat;
817 GList *iter = NULL;
818 GSList *crts = NULL;
820 if (x509_ca_initialized) return TRUE;
822 /* Check that X.509 is registered */
823 x509 = purple_certificate_find_scheme(x509_ca.scheme_name);
824 if ( !x509 ) {
825 purple_debug_warning("certificate/x509/ca",
826 "Lazy init failed because an X.509 Scheme "
827 "is not yet registered. Maybe it will be "
828 "better later.\n");
829 return FALSE;
832 /* Use a glob to only read .pem files */
833 pempat = g_pattern_spec_new("*.pem");
834 crtpat = g_pattern_spec_new("*.crt");
836 /* Populate the certificates pool from the search path(s) */
837 for (iter = x509_ca_paths; iter; iter = iter->next) {
838 certdir = g_dir_open(iter->data, 0, NULL);
839 if (!certdir) {
840 purple_debug_error("certificate/x509/ca", "Couldn't open location '%s'\n", (const char *)iter->data);
841 continue;
844 while ( (entry = g_dir_read_name(certdir)) ) {
845 gchar *fullpath;
846 PurpleCertificate *crt;
848 if (!g_pattern_match_string(pempat, entry) && !g_pattern_match_string(crtpat, entry)) {
849 continue;
852 fullpath = g_build_filename(iter->data, entry, NULL);
854 /* TODO: Respond to a failure in the following? */
855 crts = purple_certificates_import(x509, fullpath);
857 while (crts && crts->data) {
858 crt = crts->data;
859 if (x509_ca_quiet_put_cert(crt)) {
860 gchar *name;
861 name = purple_certificate_get_subject_name(crt);
862 purple_debug_info("certificate/x509/ca",
863 "Loaded %s from %s\n",
864 name ? name : "(unknown)", fullpath);
865 g_free(name);
866 } else {
867 purple_debug_error("certificate/x509/ca",
868 "Failed to load certificate from %s\n",
869 fullpath);
871 purple_certificate_destroy(crt);
872 crts = g_slist_delete_link(crts, crts);
875 g_free(fullpath);
877 g_dir_close(certdir);
880 g_pattern_spec_free(pempat);
881 g_pattern_spec_free(crtpat);
883 purple_debug_info("certificate/x509/ca",
884 "Lazy init completed.\n");
885 x509_ca_initialized = TRUE;
886 return TRUE;
889 static gboolean
890 x509_ca_init(void)
892 /* Attempt to point at the appropriate system path */
893 if (NULL == x509_ca_paths) {
894 #ifdef _WIN32
895 x509_ca_paths = g_list_append(NULL, g_build_filename(DATADIR,
896 "ca-certs", NULL));
897 #else
898 # ifdef SSL_CERTIFICATES_DIR
899 x509_ca_paths = g_list_append(NULL, g_strdup(SSL_CERTIFICATES_DIR));
900 # endif
901 x509_ca_paths = g_list_append(x509_ca_paths,
902 g_build_filename(DATADIR, "purple", "ca-certs", NULL));
903 #endif
906 /* Attempt to initialize now, but if it doesn't work, that's OK;
907 it will get done later */
908 if ( ! x509_ca_lazy_init()) {
909 purple_debug_info("certificate/x509/ca",
910 "Init failed, probably because a "
911 "dependency is not yet registered. "
912 "It has been deferred to later.\n");
915 return TRUE;
918 static void
919 x509_ca_uninit(void)
921 GList *l;
923 for (l = x509_ca_certs; l; l = l->next) {
924 x509_ca_element *el = l->data;
925 x509_ca_element_free(el);
927 g_list_free(x509_ca_certs);
928 x509_ca_certs = NULL;
929 x509_ca_initialized = FALSE;
930 g_list_foreach(x509_ca_paths, (GFunc)g_free, NULL);
931 g_list_free(x509_ca_paths);
932 x509_ca_paths = NULL;
935 /** Look up a ca_element by dn */
936 static x509_ca_element *
937 x509_ca_locate_cert(GList *lst, const gchar *dn)
939 GList *cur;
941 for (cur = lst; cur; cur = cur->next) {
942 x509_ca_element *el = cur->data;
943 if (purple_strequal(dn, el->dn)) {
944 return el;
947 return NULL;
950 static GSList *
951 x509_ca_locate_certs(GList *lst, const gchar *dn)
953 GList *cur;
954 GSList *crts = NULL;
956 for (cur = lst; cur; cur = cur->next) {
957 x509_ca_element *el = cur->data;
958 if (purple_strequal(dn, el->dn)) {
959 crts = g_slist_prepend(crts, el);
962 return crts;
966 static gboolean
967 x509_ca_cert_in_pool(const gchar *id)
969 g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
970 g_return_val_if_fail(id, FALSE);
972 if (x509_ca_locate_cert(x509_ca_certs, id) != NULL) {
973 return TRUE;
974 } else {
975 return FALSE;
978 return FALSE;
981 static PurpleCertificate *
982 x509_ca_get_cert(const gchar *id)
984 PurpleCertificate *crt = NULL;
985 x509_ca_element *el;
987 g_return_val_if_fail(x509_ca_lazy_init(), NULL);
988 g_return_val_if_fail(id, NULL);
990 /* Search the memory-cached pool */
991 el = x509_ca_locate_cert(x509_ca_certs, id);
993 if (el != NULL) {
994 /* Make a copy of the memcached one for the function caller
995 to play with */
996 crt = purple_certificate_copy(el->crt);
997 } else {
998 crt = NULL;
1001 return crt;
1004 static GSList *
1005 x509_ca_get_certs(const gchar *id)
1007 GSList *crts = NULL, *els = NULL;
1009 g_return_val_if_fail(x509_ca_lazy_init(), NULL);
1010 g_return_val_if_fail(id, NULL);
1012 /* Search the memory-cached pool */
1013 els = x509_ca_locate_certs(x509_ca_certs, id);
1015 if (els != NULL) {
1016 GSList *cur;
1017 /* Make a copy of the memcached ones for the function caller
1018 to play with */
1019 for (cur = els; cur; cur = cur->next) {
1020 x509_ca_element *el = cur->data;
1021 crts = g_slist_prepend(crts, purple_certificate_copy(el->crt));
1023 g_slist_free(els);
1026 return crts;
1029 static gboolean
1030 x509_ca_put_cert(const gchar *id, PurpleCertificate *crt)
1032 gboolean ret = FALSE;
1034 g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
1036 /* TODO: This is a quick way of doing this. At some point the change
1037 ought to be flushed to disk somehow. */
1038 ret = x509_ca_quiet_put_cert(crt);
1040 return ret;
1043 static gboolean
1044 x509_ca_delete_cert(const gchar *id)
1046 x509_ca_element *el;
1048 g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
1049 g_return_val_if_fail(id, FALSE);
1051 /* Is the id even in the pool? */
1052 el = x509_ca_locate_cert(x509_ca_certs, id);
1053 if ( el == NULL ) {
1054 purple_debug_warning("certificate/x509/ca",
1055 "Id %s wasn't in the pool\n",
1056 id);
1057 return FALSE;
1060 /* Unlink it from the memory cache and destroy it */
1061 x509_ca_certs = g_list_remove(x509_ca_certs, el);
1062 x509_ca_element_free(el);
1064 return TRUE;
1067 static GList *
1068 x509_ca_get_idlist(void)
1070 GList *l, *idlist;
1072 g_return_val_if_fail(x509_ca_lazy_init(), NULL);
1074 idlist = NULL;
1075 for (l = x509_ca_certs; l; l = l->next) {
1076 x509_ca_element *el = l->data;
1077 idlist = g_list_prepend(idlist, g_strdup(el->dn));
1080 return idlist;
1084 static PurpleCertificatePool x509_ca = {
1085 "x509", /* Scheme name */
1086 "ca", /* Pool name */
1087 N_("Certificate Authorities"),/* User-friendly name */
1088 NULL, /* Internal data */
1089 x509_ca_init, /* init */
1090 x509_ca_uninit, /* uninit */
1091 x509_ca_cert_in_pool, /* Certificate exists? */
1092 x509_ca_get_cert, /* Cert retriever */
1093 x509_ca_put_cert, /* Cert writer */
1094 x509_ca_delete_cert, /* Cert remover */
1095 x509_ca_get_idlist, /* idlist retriever */
1097 NULL,
1098 NULL,
1099 NULL,
1100 NULL
1106 /***** Cache of certificates given by TLS/SSL peers *****/
1107 static PurpleCertificatePool x509_tls_peers;
1109 static gboolean
1110 x509_tls_peers_init(void)
1112 gchar *poolpath;
1113 int ret;
1115 /* Set up key cache here if it isn't already done */
1116 poolpath = purple_certificate_pool_mkpath(&x509_tls_peers, NULL);
1117 ret = purple_build_dir(poolpath, 0700); /* Make it this user only */
1119 if (ret != 0)
1120 purple_debug_info("certificate/tls_peers",
1121 "Could not create %s. Certificates will not be cached.\n",
1122 poolpath);
1124 g_free(poolpath);
1126 return TRUE;
1129 static gboolean
1130 x509_tls_peers_cert_in_pool(const gchar *id)
1132 gchar *keypath;
1133 gboolean ret = FALSE;
1135 g_return_val_if_fail(id, FALSE);
1137 keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
1139 ret = g_file_test(keypath, G_FILE_TEST_IS_REGULAR);
1141 g_free(keypath);
1142 return ret;
1145 static PurpleCertificate *
1146 x509_tls_peers_get_cert(const gchar *id)
1148 PurpleCertificateScheme *x509;
1149 PurpleCertificate *crt;
1150 gchar *keypath;
1152 g_return_val_if_fail(id, NULL);
1154 /* Is it in the pool? */
1155 if ( !x509_tls_peers_cert_in_pool(id) ) {
1156 return NULL;
1159 /* Look up the X.509 scheme */
1160 x509 = purple_certificate_find_scheme("x509");
1161 g_return_val_if_fail(x509, NULL);
1163 /* Okay, now find and load that key */
1164 keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
1165 crt = purple_certificate_import(x509, keypath);
1167 g_free(keypath);
1169 return crt;
1172 static gboolean
1173 x509_tls_peers_put_cert(const gchar *id, PurpleCertificate *crt)
1175 gboolean ret = FALSE;
1176 gchar *keypath;
1178 g_return_val_if_fail(crt, FALSE);
1179 g_return_val_if_fail(crt->scheme, FALSE);
1180 /* Make sure that this is some kind of X.509 certificate */
1181 /* TODO: Perhaps just check crt->scheme->name instead? */
1182 g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_tls_peers.scheme_name), FALSE);
1184 /* Work out the filename and export */
1185 keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
1186 ret = purple_certificate_export(keypath, crt);
1188 g_free(keypath);
1189 return ret;
1192 static gboolean
1193 x509_tls_peers_delete_cert(const gchar *id)
1195 gboolean ret = FALSE;
1196 gchar *keypath;
1198 g_return_val_if_fail(id, FALSE);
1200 /* Is the id even in the pool? */
1201 if (!x509_tls_peers_cert_in_pool(id)) {
1202 purple_debug_warning("certificate/tls_peers",
1203 "Id %s wasn't in the pool\n",
1204 id);
1205 return FALSE;
1208 /* OK, so work out the keypath and delete the thing */
1209 keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
1210 if ( unlink(keypath) != 0 ) {
1211 purple_debug_error("certificate/tls_peers",
1212 "Unlink of %s failed!\n",
1213 keypath);
1214 ret = FALSE;
1215 } else {
1216 ret = TRUE;
1219 g_free(keypath);
1220 return ret;
1223 static GList *
1224 x509_tls_peers_get_idlist(void)
1226 GList *idlist = NULL;
1227 GDir *dir;
1228 const gchar *entry;
1229 gchar *poolpath;
1231 /* Get a handle on the pool directory */
1232 poolpath = purple_certificate_pool_mkpath(&x509_tls_peers, NULL);
1233 dir = g_dir_open(poolpath,
1234 0, /* No flags */
1235 NULL); /* Not interested in what the error is */
1236 g_free(poolpath);
1238 g_return_val_if_fail(dir, NULL);
1240 /* Traverse the directory listing and create an idlist */
1241 while ( (entry = g_dir_read_name(dir)) != NULL ) {
1242 /* Unescape the filename */
1243 const char *unescaped = purple_unescape_filename(entry);
1245 /* Copy the entry name into our list (GLib owns the original
1246 string) */
1247 idlist = g_list_prepend(idlist, g_strdup(unescaped));
1250 /* Release the directory */
1251 g_dir_close(dir);
1253 return idlist;
1256 static PurpleCertificatePool x509_tls_peers = {
1257 "x509", /* Scheme name */
1258 "tls_peers", /* Pool name */
1259 N_("SSL Peers Cache"), /* User-friendly name */
1260 NULL, /* Internal data */
1261 x509_tls_peers_init, /* init */
1262 NULL, /* uninit not required */
1263 x509_tls_peers_cert_in_pool, /* Certificate exists? */
1264 x509_tls_peers_get_cert, /* Cert retriever */
1265 x509_tls_peers_put_cert, /* Cert writer */
1266 x509_tls_peers_delete_cert, /* Cert remover */
1267 x509_tls_peers_get_idlist, /* idlist retriever */
1269 NULL,
1270 NULL,
1271 NULL,
1272 NULL
1276 /***** A Verifier that uses the tls_peers cache and the CA pool to validate certificates *****/
1277 static PurpleCertificateVerifier x509_tls_cached;
1280 /* The following is several hacks piled together and needs to be fixed.
1281 * It exists because show_cert (see its comments) needs the original reason
1282 * given to user_auth in order to rebuild the dialog.
1284 /* TODO: This will cause a ua_ctx to become memleaked if the request(s) get
1285 closed by handle or otherwise abnormally. */
1286 typedef struct {
1287 PurpleCertificateVerificationRequest *vrq;
1288 gchar *reason;
1289 } x509_tls_cached_ua_ctx;
1291 static x509_tls_cached_ua_ctx *
1292 x509_tls_cached_ua_ctx_new(PurpleCertificateVerificationRequest *vrq,
1293 const gchar *reason)
1295 x509_tls_cached_ua_ctx *c;
1297 c = g_new0(x509_tls_cached_ua_ctx, 1);
1298 c->vrq = vrq;
1299 c->reason = g_strdup(reason);
1301 return c;
1305 static void
1306 x509_tls_cached_ua_ctx_free(x509_tls_cached_ua_ctx *c)
1308 g_return_if_fail(c);
1309 g_free(c->reason);
1310 g_free(c);
1313 static void
1314 x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq,
1315 const gchar *reason);
1317 static void
1318 x509_tls_cached_show_cert(x509_tls_cached_ua_ctx *c, gint id)
1320 PurpleCertificate *disp_crt = c->vrq->cert_chain->data;
1322 /* Since clicking a button closes the request, show it again */
1323 x509_tls_cached_user_auth(c->vrq, c->reason);
1325 /* Show the certificate AFTER re-opening the dialog so that this
1326 appears above the other */
1327 purple_certificate_display_x509(disp_crt);
1329 x509_tls_cached_ua_ctx_free(c);
1332 static void
1333 x509_tls_cached_user_auth_cb (x509_tls_cached_ua_ctx *c, gint id)
1335 PurpleCertificateVerificationRequest *vrq;
1336 PurpleCertificatePool *tls_peers;
1338 g_return_if_fail(c);
1339 g_return_if_fail(c->vrq);
1341 vrq = c->vrq;
1343 x509_tls_cached_ua_ctx_free(c);
1345 tls_peers = purple_certificate_find_pool("x509","tls_peers");
1347 if (2 == id) {
1348 gchar *cache_id = vrq->subject_name;
1349 purple_debug_info("certificate/x509/tls_cached",
1350 "User ACCEPTED cert\nCaching first in chain for future use as %s...\n",
1351 cache_id);
1353 purple_certificate_pool_store(tls_peers, cache_id,
1354 vrq->cert_chain->data);
1356 purple_certificate_verify_complete(vrq,
1357 PURPLE_CERTIFICATE_VALID);
1358 } else {
1359 purple_debug_warning("certificate/x509/tls_cached",
1360 "User REJECTED cert\n");
1361 purple_certificate_verify_complete(vrq,
1362 PURPLE_CERTIFICATE_INVALID);
1366 static void
1367 x509_tls_cached_user_auth_accept_cb(x509_tls_cached_ua_ctx *c, gint ignore)
1369 x509_tls_cached_user_auth_cb(c, 2);
1372 static void
1373 x509_tls_cached_user_auth_reject_cb(x509_tls_cached_ua_ctx *c, gint ignore)
1375 x509_tls_cached_user_auth_cb(c, 1);
1378 /** Validates a certificate by asking the user
1379 * @param reason String to explain why the user needs to accept/refuse the
1380 * certificate.
1381 * @todo Needs a handle argument
1383 static void
1384 x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq,
1385 const gchar *reason)
1387 gchar *primary;
1389 /* Make messages */
1390 primary = g_strdup_printf(_("Accept certificate for %s?"),
1391 vrq->subject_name);
1393 /* Make a semi-pretty display */
1394 purple_request_action(
1395 vrq->cb_data, /* TODO: Find what the handle ought to be */
1396 _("SSL Certificate Verification"),
1397 primary,
1398 reason,
1399 0, /* Accept by default */
1400 NULL, /* No account */
1401 NULL, /* No other user */
1402 NULL, /* No associated conversation */
1403 x509_tls_cached_ua_ctx_new(vrq, reason),
1404 3, /* Number of actions */
1405 _("Accept"), x509_tls_cached_user_auth_accept_cb,
1406 _("Reject"), x509_tls_cached_user_auth_reject_cb,
1407 _("_View Certificate..."), x509_tls_cached_show_cert);
1409 /* Cleanup */
1410 g_free(primary);
1413 static void
1414 x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq,
1415 PurpleCertificateInvalidityFlags flags);
1417 static void
1418 x509_tls_cached_complete(PurpleCertificateVerificationRequest *vrq,
1419 PurpleCertificateInvalidityFlags flags)
1421 PurpleCertificatePool *tls_peers;
1422 PurpleCertificate *peer_crt = vrq->cert_chain->data;
1424 if (flags & PURPLE_CERTIFICATE_FATALS_MASK) {
1425 /* TODO: Also print any other warnings? */
1426 const gchar *error;
1427 gchar *tmp, *secondary;
1429 if (flags & PURPLE_CERTIFICATE_INVALID_CHAIN)
1430 error = invalidity_reason_to_string(PURPLE_CERTIFICATE_INVALID_CHAIN);
1431 else if (flags & PURPLE_CERTIFICATE_REVOKED)
1432 error = invalidity_reason_to_string(PURPLE_CERTIFICATE_REVOKED);
1433 else
1434 error = invalidity_reason_to_string(PURPLE_CERTIFICATE_UNKNOWN_ERROR);
1436 tmp = g_strdup_printf(_("The certificate for %s could not be validated."),
1437 vrq->subject_name);
1438 secondary = g_strconcat(tmp, " ", error, NULL);
1439 g_free(tmp);
1441 purple_notify_error(NULL, /* TODO: Probably wrong. */
1442 _("SSL Certificate Error"),
1443 _("Unable to validate certificate"),
1444 secondary);
1445 g_free(secondary);
1447 purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_INVALID);
1448 return;
1449 } else if (flags & PURPLE_CERTIFICATE_NON_FATALS_MASK) {
1450 /* Non-fatal error. Prompt the user. */
1451 gchar *tmp;
1452 GString *errors;
1453 guint32 i = 1;
1455 tmp = g_strdup_printf(_("The certificate for %s could not be validated."),
1456 vrq->subject_name);
1457 errors = g_string_new(tmp);
1458 g_free(tmp);
1460 errors = g_string_append_c(errors, '\n');
1462 /* Special case a name mismatch because we want to display the two names... */
1463 if (flags & PURPLE_CERTIFICATE_NAME_MISMATCH) {
1464 gchar *sn = purple_certificate_get_subject_name(peer_crt);
1466 if (sn) {
1467 g_string_append_printf(errors, _("The certificate claims to be "
1468 "from \"%s\" instead. This could mean that you are "
1469 "not connecting to the service you believe you are."),
1470 sn);
1471 g_free(sn);
1473 flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH;
1477 while (i != PURPLE_CERTIFICATE_LAST) {
1478 if (flags & i) {
1479 errors = g_string_append_c(errors, '\n');
1480 g_string_append(errors, invalidity_reason_to_string(i));
1483 i <<= 1;
1486 x509_tls_cached_user_auth(vrq, errors->str);
1487 g_string_free(errors, TRUE);
1488 return;
1491 /* If we reach this point, the certificate is good. */
1493 /* Look up the local cache and store it there for future use */
1494 tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,
1495 "tls_peers");
1496 if (tls_peers) {
1497 if (!purple_certificate_pool_store(tls_peers,vrq->subject_name,
1498 peer_crt)) {
1499 purple_debug_error("certificate/x509/tls_cached",
1500 "FAILED to cache peer certificate\n");
1502 } else {
1503 purple_debug_error("certificate/x509/tls_cached",
1504 "Unable to locate tls_peers certificate cache.\n");
1507 purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID);
1510 static void
1511 x509_tls_cached_cert_in_cache(PurpleCertificateVerificationRequest *vrq,
1512 PurpleCertificateInvalidityFlags flags)
1514 /* TODO: Looking this up by name over and over is expensive.
1515 Fix, please! */
1516 PurpleCertificatePool *tls_peers =
1517 purple_certificate_find_pool(x509_tls_cached.scheme_name,
1518 "tls_peers");
1520 /* The peer's certificate should be the first in the list */
1521 PurpleCertificate *peer_crt =
1522 (PurpleCertificate *) vrq->cert_chain->data;
1524 PurpleCertificate *cached_crt;
1525 GByteArray *peer_fpr, *cached_fpr;
1527 /* Load up the cached certificate */
1528 cached_crt = purple_certificate_pool_retrieve(
1529 tls_peers, vrq->subject_name);
1530 if ( !cached_crt ) {
1531 purple_debug_warning("certificate/x509/tls_cached",
1532 "Lookup failed on cached certificate!\n"
1533 "Falling back to full verification.\n");
1534 /* vrq now becomes the problem of unknown_peer */
1535 x509_tls_cached_unknown_peer(vrq, flags);
1536 return;
1539 /* Now get SHA1 sums for both and compare them */
1540 /* TODO: This is not an elegant way to compare certs */
1541 peer_fpr = purple_certificate_get_fingerprint_sha1(peer_crt);
1542 cached_fpr = purple_certificate_get_fingerprint_sha1(cached_crt);
1543 if (!memcmp(peer_fpr->data, cached_fpr->data, peer_fpr->len)) {
1544 purple_debug_info("certificate/x509/tls_cached",
1545 "Peer cert matched cached\n");
1546 x509_tls_cached_complete(vrq, flags);
1547 } else {
1548 purple_debug_error("certificate/x509/tls_cached",
1549 "Peer cert did NOT match cached\n");
1550 /* vrq now becomes the problem of the user */
1551 x509_tls_cached_unknown_peer(vrq, flags);
1554 purple_certificate_destroy(cached_crt);
1555 g_byte_array_free(peer_fpr, TRUE);
1556 g_byte_array_free(cached_fpr, TRUE);
1560 * This is called from two points in x509_tls_cached_unknown_peer below
1561 * once we've verified the signature chain is valid. Now we need to verify
1562 * the subject name of the certificate.
1564 static void
1565 x509_tls_cached_check_subject_name(PurpleCertificateVerificationRequest *vrq,
1566 PurpleCertificateInvalidityFlags flags)
1568 PurpleCertificate *peer_crt;
1569 GList *chain = vrq->cert_chain;
1571 peer_crt = (PurpleCertificate *) chain->data;
1573 /* Last, check that the hostname matches */
1574 if ( ! purple_certificate_check_subject_name(peer_crt,
1575 vrq->subject_name) ) {
1576 gchar *sn = purple_certificate_get_subject_name(peer_crt);
1578 flags |= PURPLE_CERTIFICATE_NAME_MISMATCH;
1579 purple_debug_error("certificate/x509/tls_cached",
1580 "Name mismatch: Certificate given for %s "
1581 "has a name of %s\n",
1582 vrq->subject_name, sn);
1583 g_free(sn);
1586 x509_tls_cached_complete(vrq, flags);
1589 /* For when we've never communicated with this party before */
1590 /* TODO: Need ways to specify possibly multiple problems with a cert, or at
1591 least reprioritize them.
1593 static void
1594 x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq,
1595 PurpleCertificateInvalidityFlags flags)
1597 PurpleCertificatePool *ca;
1598 PurpleCertificate *peer_crt;
1599 PurpleCertificate *ca_crt, *end_crt;
1600 PurpleCertificate *failing_crt;
1601 GList *chain = vrq->cert_chain;
1602 GSList *ca_crts, *cur;
1603 GByteArray *last_fpr, *ca_fpr;
1604 gboolean valid = FALSE;
1605 gchar *ca_id, *ca2_id;
1607 peer_crt = (PurpleCertificate *) chain->data;
1609 /* TODO: Figure out a way to check for a bad signature, as opposed to
1610 "not self-signed" */
1611 if ( purple_certificate_signed_by(peer_crt, peer_crt) ) {
1612 flags |= PURPLE_CERTIFICATE_SELF_SIGNED;
1614 purple_debug_info("certificate/x509/tls_cached",
1615 "Certificate for %s is self-signed.\n",
1616 vrq->subject_name);
1618 x509_tls_cached_check_subject_name(vrq, flags);
1619 return;
1620 } /* if (self signed) */
1622 ca = purple_certificate_find_pool(x509_tls_cached.scheme_name, "ca");
1624 /* Next, check that the certificate chain is valid */
1625 if (!purple_certificate_check_signature_chain_with_failing(chain,
1626 &failing_crt))
1628 gboolean chain_validated = FALSE;
1630 * Check if the failing certificate is in the CA store. If it is, then
1631 * consider this fully validated. This works around issues with some
1632 * prominent intermediate CAs whose signature is md5WithRSAEncryption.
1633 * I'm looking at CACert Class 3 here. See #4458 for details.
1635 if (ca) {
1636 gchar *uid = purple_certificate_get_unique_id(failing_crt);
1637 PurpleCertificate *ca_crt = purple_certificate_pool_retrieve(ca, uid);
1638 if (ca_crt != NULL) {
1639 GByteArray *failing_fpr;
1640 GByteArray *ca_fpr;
1641 failing_fpr = purple_certificate_get_fingerprint_sha1(failing_crt);
1642 ca_fpr = purple_certificate_get_fingerprint_sha1(ca_crt);
1643 if (byte_arrays_equal(failing_fpr, ca_fpr)) {
1644 purple_debug_info("certificate/x509/tls_cached",
1645 "Full chain verification failed (probably a bad "
1646 "signature algorithm), but found the last "
1647 "certificate %s in the CA pool.\n", uid);
1648 chain_validated = TRUE;
1651 g_byte_array_free(failing_fpr, TRUE);
1652 g_byte_array_free(ca_fpr, TRUE);
1655 purple_certificate_destroy(ca_crt);
1656 g_free(uid);
1660 * If we get here, either the cert matched the stuff right above
1661 * or it didn't, in which case we give up and complain to the user.
1663 if (!chain_validated)
1664 /* TODO: Tell the user where the chain broke? */
1665 flags |= PURPLE_CERTIFICATE_INVALID_CHAIN;
1667 x509_tls_cached_check_subject_name(vrq, flags);
1668 return;
1669 } /* if (signature chain not good) */
1671 /* Next, attempt to verify the last certificate is signed by a trusted
1672 * CA, or is a trusted CA (based on fingerprint).
1674 /* If, for whatever reason, there is no Certificate Authority pool
1675 loaded, we'll verify the subject name and then warn about thsi. */
1676 if ( !ca ) {
1677 purple_debug_error("certificate/x509/tls_cached",
1678 "No X.509 Certificate Authority pool "
1679 "could be found!\n");
1681 flags |= PURPLE_CERTIFICATE_NO_CA_POOL;
1683 x509_tls_cached_check_subject_name(vrq, flags);
1684 return;
1687 end_crt = g_list_last(chain)->data;
1689 /* Attempt to look up the last certificate, and the last certificate's
1690 * issuer.
1692 ca_id = purple_certificate_get_issuer_unique_id(end_crt);
1693 ca2_id = purple_certificate_get_unique_id(end_crt);
1694 purple_debug_info("certificate/x509/tls_cached",
1695 "Checking for a CA with DN=%s\n",
1696 ca_id);
1697 purple_debug_info("certificate/x509/tls_cached",
1698 "Also checking for a CA with DN=%s\n",
1699 ca2_id);
1700 ca_crts = g_slist_concat(x509_ca_get_certs(ca_id), x509_ca_get_certs(ca2_id));
1701 g_free(ca_id);
1702 g_free(ca2_id);
1703 if ( NULL == ca_crts ) {
1704 flags |= PURPLE_CERTIFICATE_CA_UNKNOWN;
1706 purple_debug_warning("certificate/x509/tls_cached",
1707 "No Certificate Authorities with either DN found "
1708 "found. I'll prompt the user, I guess.\n");
1710 x509_tls_cached_check_subject_name(vrq, flags);
1711 return;
1715 * Check the fingerprints; if they match, then this certificate *is* one
1716 * of the designated "trusted roots", and we don't need to verify the
1717 * signature. This is good because some of the older roots are self-signed
1718 * with bad hash algorithms that we don't want to allow in any other
1719 * circumstances (one of Verisign's root CAs is self-signed with MD2).
1721 * If the fingerprints don't match, we'll fall back to checking the
1722 * signature.
1724 last_fpr = purple_certificate_get_fingerprint_sha1(end_crt);
1725 for (cur = ca_crts; cur; cur = cur->next) {
1726 ca_crt = cur->data;
1727 ca_fpr = purple_certificate_get_fingerprint_sha1(ca_crt);
1729 if ( byte_arrays_equal(last_fpr, ca_fpr) ||
1730 purple_certificate_signed_by(end_crt, ca_crt) )
1732 /* TODO: If signed_by ever returns a reason, maybe mention
1733 that, too. */
1734 /* TODO: Also mention the CA involved. While I could do this
1735 now, a full DN is a little much with which to assault the
1736 user's poor, leaky eyes. */
1737 valid = TRUE;
1738 g_byte_array_free(ca_fpr, TRUE);
1739 break;
1742 g_byte_array_free(ca_fpr, TRUE);
1745 if (valid == FALSE)
1746 flags |= PURPLE_CERTIFICATE_INVALID_CHAIN;
1748 g_slist_foreach(ca_crts, (GFunc)purple_certificate_destroy, NULL);
1749 g_slist_free(ca_crts);
1750 g_byte_array_free(last_fpr, TRUE);
1752 x509_tls_cached_check_subject_name(vrq, flags);
1755 static void
1756 x509_tls_cached_start_verify(PurpleCertificateVerificationRequest *vrq)
1758 const gchar *tls_peers_name = "tls_peers"; /* Name of local cache */
1759 PurpleCertificatePool *tls_peers;
1760 time_t now, activation, expiration;
1761 PurpleCertificateInvalidityFlags flags = PURPLE_CERTIFICATE_NO_PROBLEMS;
1762 gboolean ret;
1764 g_return_if_fail(vrq);
1766 purple_debug_info("certificate/x509/tls_cached",
1767 "Starting verify for %s\n",
1768 vrq->subject_name);
1771 * Verify the first certificate (the main one) has been activated and
1772 * isn't expired, i.e. activation < now < expiration.
1774 now = time(NULL);
1775 ret = purple_certificate_get_times(vrq->cert_chain->data, &activation,
1776 &expiration);
1777 if (!ret) {
1778 flags |= PURPLE_CERTIFICATE_EXPIRED | PURPLE_CERTIFICATE_NOT_ACTIVATED;
1779 purple_debug_error("certificate/x509/tls_cached",
1780 "Failed to get validity times for certificate %s\n",
1781 vrq->subject_name);
1782 } else if (now > expiration) {
1783 flags |= PURPLE_CERTIFICATE_EXPIRED;
1784 purple_debug_error("certificate/x509/tls_cached",
1785 "Certificate %s expired at %s\n",
1786 vrq->subject_name, ctime(&expiration));
1787 } else if (now < activation) {
1788 flags |= PURPLE_CERTIFICATE_NOT_ACTIVATED;
1789 purple_debug_error("certificate/x509/tls_cached",
1790 "Certificate %s is not yet valid, will be at %s\n",
1791 vrq->subject_name, ctime(&activation));
1794 tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,tls_peers_name);
1796 if (!tls_peers) {
1797 purple_debug_error("certificate/x509/tls_cached",
1798 "Couldn't find local peers cache %s\n",
1799 tls_peers_name);
1801 /* vrq now becomes the problem of unknown_peer */
1802 x509_tls_cached_unknown_peer(vrq, flags);
1803 return;
1806 /* Check if the peer has a certificate cached already */
1807 purple_debug_info("certificate/x509/tls_cached",
1808 "Checking for cached cert...\n");
1809 if (purple_certificate_pool_contains(tls_peers, vrq->subject_name)) {
1810 purple_debug_info("certificate/x509/tls_cached",
1811 "...Found cached cert\n");
1812 /* vrq is now the responsibility of cert_in_cache */
1813 x509_tls_cached_cert_in_cache(vrq, flags);
1814 } else {
1815 purple_debug_warning("certificate/x509/tls_cached",
1816 "...Not in cache\n");
1817 /* vrq now becomes the problem of unknown_peer */
1818 x509_tls_cached_unknown_peer(vrq, flags);
1822 static void
1823 x509_tls_cached_destroy_request(PurpleCertificateVerificationRequest *vrq)
1825 g_return_if_fail(vrq);
1828 static PurpleCertificateVerifier x509_tls_cached = {
1829 "x509", /* Scheme name */
1830 "tls_cached", /* Verifier name */
1831 x509_tls_cached_start_verify, /* Verification begin */
1832 x509_tls_cached_destroy_request,/* Request cleanup */
1834 NULL,
1835 NULL,
1836 NULL,
1837 NULL
1841 /****************************************************************************/
1842 /* Subsystem */
1843 /****************************************************************************/
1844 void
1845 purple_certificate_init(void)
1847 /* Register builtins */
1848 purple_certificate_register_verifier(&x509_singleuse);
1849 purple_certificate_register_pool(&x509_ca);
1850 purple_certificate_register_pool(&x509_tls_peers);
1851 purple_certificate_register_verifier(&x509_tls_cached);
1854 void
1855 purple_certificate_uninit(void)
1857 /* Unregister all Verifiers */
1858 g_list_foreach(cert_verifiers, (GFunc)purple_certificate_unregister_verifier, NULL);
1860 /* Unregister all Pools */
1861 g_list_foreach(cert_pools, (GFunc)purple_certificate_unregister_pool, NULL);
1864 gpointer
1865 purple_certificate_get_handle(void)
1867 static gint handle;
1868 return &handle;
1871 PurpleCertificateScheme *
1872 purple_certificate_find_scheme(const gchar *name)
1874 PurpleCertificateScheme *scheme = NULL;
1875 GList *l;
1877 g_return_val_if_fail(name, NULL);
1879 /* Traverse the list of registered schemes and locate the
1880 one whose name matches */
1881 for(l = cert_schemes; l; l = l->next) {
1882 scheme = (PurpleCertificateScheme *)(l->data);
1884 /* Name matches? that's our man */
1885 if(!g_ascii_strcasecmp(scheme->name, name))
1886 return scheme;
1889 purple_debug_warning("certificate",
1890 "CertificateScheme %s requested but not found.\n",
1891 name);
1893 /* TODO: Signalling and such? */
1895 return NULL;
1898 GList *
1899 purple_certificate_get_schemes(void)
1901 return cert_schemes;
1904 gboolean
1905 purple_certificate_register_scheme(PurpleCertificateScheme *scheme)
1907 g_return_val_if_fail(scheme != NULL, FALSE);
1909 /* Make sure no scheme is registered with the same name */
1910 if (purple_certificate_find_scheme(scheme->name) != NULL) {
1911 return FALSE;
1914 /* Okay, we're golden. Register it. */
1915 cert_schemes = g_list_prepend(cert_schemes, scheme);
1917 /* TODO: Signalling and such? */
1919 purple_debug_info("certificate",
1920 "CertificateScheme %s registered\n",
1921 scheme->name);
1923 return TRUE;
1926 gboolean
1927 purple_certificate_unregister_scheme(PurpleCertificateScheme *scheme)
1929 if (NULL == scheme) {
1930 purple_debug_warning("certificate",
1931 "Attempting to unregister NULL scheme\n");
1932 return FALSE;
1935 /* TODO: signalling? */
1937 /* TODO: unregister all CertificateVerifiers for this scheme?*/
1938 /* TODO: unregister all CertificatePools for this scheme? */
1939 /* Neither of the above should be necessary, though */
1940 cert_schemes = g_list_remove(cert_schemes, scheme);
1942 purple_debug_info("certificate",
1943 "CertificateScheme %s unregistered\n",
1944 scheme->name);
1947 return TRUE;
1950 PurpleCertificateVerifier *
1951 purple_certificate_find_verifier(const gchar *scheme_name, const gchar *ver_name)
1953 PurpleCertificateVerifier *vr = NULL;
1954 GList *l;
1956 g_return_val_if_fail(scheme_name, NULL);
1957 g_return_val_if_fail(ver_name, NULL);
1959 /* Traverse the list of registered verifiers and locate the
1960 one whose name matches */
1961 for(l = cert_verifiers; l; l = l->next) {
1962 vr = (PurpleCertificateVerifier *)(l->data);
1964 /* Scheme and name match? */
1965 if(!g_ascii_strcasecmp(vr->scheme_name, scheme_name) &&
1966 !g_ascii_strcasecmp(vr->name, ver_name))
1967 return vr;
1970 purple_debug_warning("certificate",
1971 "CertificateVerifier %s, %s requested but not found.\n",
1972 scheme_name, ver_name);
1974 /* TODO: Signalling and such? */
1976 return NULL;
1980 GList *
1981 purple_certificate_get_verifiers(void)
1983 return cert_verifiers;
1986 gboolean
1987 purple_certificate_register_verifier(PurpleCertificateVerifier *vr)
1989 g_return_val_if_fail(vr != NULL, FALSE);
1991 /* Make sure no verifier is registered with the same scheme/name */
1992 if (purple_certificate_find_verifier(vr->scheme_name, vr->name) != NULL) {
1993 return FALSE;
1996 /* Okay, we're golden. Register it. */
1997 cert_verifiers = g_list_prepend(cert_verifiers, vr);
1999 /* TODO: Signalling and such? */
2001 purple_debug_info("certificate",
2002 "CertificateVerifier %s registered\n",
2003 vr->name);
2004 return TRUE;
2007 gboolean
2008 purple_certificate_unregister_verifier(PurpleCertificateVerifier *vr)
2010 if (NULL == vr) {
2011 purple_debug_warning("certificate",
2012 "Attempting to unregister NULL verifier\n");
2013 return FALSE;
2016 /* TODO: signalling? */
2018 cert_verifiers = g_list_remove(cert_verifiers, vr);
2021 purple_debug_info("certificate",
2022 "CertificateVerifier %s unregistered\n",
2023 vr->name);
2025 return TRUE;
2028 PurpleCertificatePool *
2029 purple_certificate_find_pool(const gchar *scheme_name, const gchar *pool_name)
2031 PurpleCertificatePool *pool = NULL;
2032 GList *l;
2034 g_return_val_if_fail(scheme_name, NULL);
2035 g_return_val_if_fail(pool_name, NULL);
2037 /* Traverse the list of registered pools and locate the
2038 one whose name matches */
2039 for(l = cert_pools; l; l = l->next) {
2040 pool = (PurpleCertificatePool *)(l->data);
2042 /* Scheme and name match? */
2043 if(!g_ascii_strcasecmp(pool->scheme_name, scheme_name) &&
2044 !g_ascii_strcasecmp(pool->name, pool_name))
2045 return pool;
2048 purple_debug_warning("certificate",
2049 "CertificatePool %s, %s requested but not found.\n",
2050 scheme_name, pool_name);
2052 /* TODO: Signalling and such? */
2054 return NULL;
2058 GList *
2059 purple_certificate_get_pools(void)
2061 return cert_pools;
2064 gboolean
2065 purple_certificate_register_pool(PurpleCertificatePool *pool)
2067 g_return_val_if_fail(pool, FALSE);
2068 g_return_val_if_fail(pool->scheme_name, FALSE);
2069 g_return_val_if_fail(pool->name, FALSE);
2070 g_return_val_if_fail(pool->fullname, FALSE);
2072 /* Make sure no pools are registered under this name */
2073 if (purple_certificate_find_pool(pool->scheme_name, pool->name)) {
2074 return FALSE;
2077 /* Initialize the pool if needed */
2078 if (pool->init) {
2079 gboolean success;
2081 success = pool->init();
2082 if (!success)
2083 return FALSE;
2086 /* Register the Pool */
2087 cert_pools = g_list_prepend(cert_pools, pool);
2089 /* TODO: Emit a signal that the pool got registered */
2091 PURPLE_DBUS_REGISTER_POINTER(pool, PurpleCertificatePool);
2092 purple_signal_register(pool, /* Signals emitted from pool */
2093 "certificate-stored",
2094 purple_marshal_VOID__POINTER_POINTER,
2095 NULL, /* No callback return value */
2096 2, /* Two non-data arguments */
2097 purple_value_new(PURPLE_TYPE_SUBTYPE,
2098 PURPLE_SUBTYPE_CERTIFICATEPOOL),
2099 purple_value_new(PURPLE_TYPE_STRING));
2101 purple_signal_register(pool, /* Signals emitted from pool */
2102 "certificate-deleted",
2103 purple_marshal_VOID__POINTER_POINTER,
2104 NULL, /* No callback return value */
2105 2, /* Two non-data arguments */
2106 purple_value_new(PURPLE_TYPE_SUBTYPE,
2107 PURPLE_SUBTYPE_CERTIFICATEPOOL),
2108 purple_value_new(PURPLE_TYPE_STRING));
2110 purple_debug_info("certificate",
2111 "CertificatePool %s registered\n",
2112 pool->name);
2114 return TRUE;
2117 gboolean
2118 purple_certificate_unregister_pool(PurpleCertificatePool *pool)
2120 if (NULL == pool) {
2121 purple_debug_warning("certificate",
2122 "Attempting to unregister NULL pool\n");
2123 return FALSE;
2126 /* Check that the pool is registered */
2127 if (!g_list_find(cert_pools, pool)) {
2128 purple_debug_warning("certificate",
2129 "Pool to unregister isn't registered!\n");
2131 return FALSE;
2134 /* Uninit the pool if needed */
2135 PURPLE_DBUS_UNREGISTER_POINTER(pool);
2136 if (pool->uninit) {
2137 pool->uninit();
2140 cert_pools = g_list_remove(cert_pools, pool);
2142 /* TODO: Signalling? */
2143 purple_signal_unregister(pool, "certificate-stored");
2144 purple_signal_unregister(pool, "certificate-deleted");
2146 purple_debug_info("certificate",
2147 "CertificatePool %s unregistered\n",
2148 pool->name);
2149 return TRUE;
2152 /****************************************************************************/
2153 /* Scheme-specific functions */
2154 /****************************************************************************/
2156 void
2157 purple_certificate_display_x509(PurpleCertificate *crt)
2159 gchar *sha_asc;
2160 GByteArray *sha_bin;
2161 gchar *cn;
2162 time_t activation, expiration;
2163 gchar *activ_str, *expir_str;
2164 gchar *secondary;
2166 /* Pull out the SHA1 checksum */
2167 sha_bin = purple_certificate_get_fingerprint_sha1(crt);
2168 /* Now decode it for display */
2169 sha_asc = purple_base16_encode_chunked(sha_bin->data,
2170 sha_bin->len);
2172 /* Get the cert Common Name */
2173 /* TODO: Will break on CA certs */
2174 cn = purple_certificate_get_subject_name(crt);
2176 /* Get the certificate times */
2177 /* TODO: Check the times against localtime */
2178 /* TODO: errorcheck? */
2179 if (!purple_certificate_get_times(crt, &activation, &expiration)) {
2180 purple_debug_error("certificate",
2181 "Failed to get certificate times!\n");
2182 activation = expiration = 0;
2184 activ_str = g_strdup(ctime(&activation));
2185 expir_str = g_strdup(ctime(&expiration));
2187 /* Make messages */
2188 secondary = g_strdup_printf(_("Common name: %s\n\n"
2189 "Fingerprint (SHA1): %s\n\n"
2190 "Activation date: %s\n"
2191 "Expiration date: %s\n"),
2192 cn ? cn : "(null)",
2193 sha_asc ? sha_asc : "(null)",
2194 activ_str ? activ_str : "(null)",
2195 expir_str ? expir_str : "(null)");
2197 /* Make a semi-pretty display */
2198 purple_notify_info(
2199 NULL, /* TODO: Find what the handle ought to be */
2200 _("Certificate Information"),
2202 secondary);
2204 /* Cleanup */
2205 g_free(cn);
2206 g_free(secondary);
2207 g_free(sha_asc);
2208 g_free(activ_str);
2209 g_free(expir_str);
2210 g_byte_array_free(sha_bin, TRUE);
2213 void purple_certificate_add_ca_search_path(const char *path)
2215 if (g_list_find_custom(x509_ca_paths, path, (GCompareFunc)strcmp))
2216 return;
2217 x509_ca_paths = g_list_append(x509_ca_paths, g_strdup(path));