2 * empathy-tls-verifier.c - Source for EmpathyTLSVerifier
3 * Copyright (C) 2010 Collabora Ltd.
4 * @author Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 * Some snippets are taken from GnuTLS 2.8.6, which is distributed under the
21 * same GNU Lesser General Public License 2.1 (or later) version. See
22 * get_certified_hostname ().
27 #include <gnutls/gnutls.h>
28 #include <gnutls/x509.h>
30 #include <telepathy-glib/util.h>
32 #include "empathy-tls-verifier.h"
34 #define DEBUG_FLAG EMPATHY_DEBUG_TLS
35 #include "empathy-debug.h"
36 #include "empathy-utils.h"
38 G_DEFINE_TYPE (EmpathyTLSVerifier, empathy_tls_verifier,
41 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTLSVerifier);
44 PROP_TLS_CERTIFICATE = 1,
50 static const gchar* system_ca_paths[] = {
51 "/etc/ssl/certs/ca-certificates.crt",
56 GPtrArray *cert_chain;
58 GPtrArray *trusted_ca_list;
59 GPtrArray *trusted_crl_list;
61 EmpathyTLSCertificate *certificate;
64 GSimpleAsyncResult *verify_result;
68 } EmpathyTLSVerifierPriv;
70 static gnutls_x509_crt_t *
71 ptr_array_to_x509_crt_list (GPtrArray *chain)
73 gnutls_x509_crt_t *retval;
76 retval = g_malloc0 (sizeof (gnutls_x509_crt_t) * chain->len);
78 for (idx = 0; idx < (gint) chain->len; idx++)
79 retval[idx] = g_ptr_array_index (chain, idx);
85 verification_output_to_reason (gint res,
87 EmpTLSCertificateRejectReason *reason)
89 gboolean retval = TRUE;
91 g_assert (reason != NULL);
93 if (res != GNUTLS_E_SUCCESS)
97 /* the certificate is not structurally valid */
100 case GNUTLS_E_INSUFFICIENT_CREDENTIALS:
101 *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED;
103 case GNUTLS_E_CONSTRAINT_ERROR:
104 *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_LIMIT_EXCEEDED;
107 *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
114 /* the certificate is structurally valid, check for other errors. */
115 if (verify_output & GNUTLS_CERT_INVALID)
119 if (verify_output & GNUTLS_CERT_SIGNER_NOT_FOUND)
120 *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_SELF_SIGNED;
121 else if (verify_output & GNUTLS_CERT_SIGNER_NOT_CA)
122 *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED;
123 else if (verify_output & GNUTLS_CERT_INSECURE_ALGORITHM)
124 *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_INSECURE;
125 else if (verify_output & GNUTLS_CERT_NOT_ACTIVATED)
126 *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_NOT_ACTIVATED;
127 else if (verify_output & GNUTLS_CERT_EXPIRED)
128 *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_EXPIRED;
130 *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
140 verify_last_certificate (EmpathyTLSVerifier *self,
141 gnutls_x509_crt_t cert,
142 EmpTLSCertificateRejectReason *reason)
146 gnutls_x509_crt_t *trusted_ca_list;
147 EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
149 if (priv->trusted_ca_list->len > 0)
151 trusted_ca_list = ptr_array_to_x509_crt_list (priv->trusted_ca_list);
152 res = gnutls_x509_crt_verify (cert, trusted_ca_list,
153 priv->trusted_ca_list->len, 0, &verify_output);
155 DEBUG ("Checking last certificate %p against trusted CAs, output %u",
156 cert, verify_output);
158 g_free (trusted_ca_list);
162 /* check it against itself to see if it's structurally valid */
163 res = gnutls_x509_crt_verify (cert, &cert, 1, 0, &verify_output);
165 DEBUG ("Checking last certificate %p against itself, output %u", cert,
168 /* if it's valid, return the SelfSigned error, so that we can add it
169 * later to our trusted CAs whitelist.
171 if (res == GNUTLS_E_SUCCESS)
173 *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_SELF_SIGNED;
178 return verification_output_to_reason (res, verify_output, reason);
182 verify_certificate (EmpathyTLSVerifier *self,
183 gnutls_x509_crt_t cert,
184 gnutls_x509_crt_t issuer,
185 EmpTLSCertificateRejectReason *reason)
190 res = gnutls_x509_crt_verify (cert, &issuer, 1, 0, &verify_output);
192 DEBUG ("Verifying %p against %p, output %u", cert, issuer, verify_output);
194 return verification_output_to_reason (res, verify_output, reason);
198 complete_verification (EmpathyTLSVerifier *self)
200 EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
202 DEBUG ("Verification successful, completing...");
204 g_simple_async_result_complete_in_idle (priv->verify_result);
206 tp_clear_object (&priv->verify_result);
210 abort_verification (EmpathyTLSVerifier *self,
211 EmpTLSCertificateRejectReason reason)
213 EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
215 DEBUG ("Verification error %u, aborting...", reason);
217 g_simple_async_result_set_error (priv->verify_result,
218 G_IO_ERROR, reason, "TLS verification failed with reason %u",
220 g_simple_async_result_complete_in_idle (priv->verify_result);
222 tp_clear_object (&priv->verify_result);
226 get_certified_hostname (gnutls_x509_crt_t cert)
233 /* this snippet is taken from GnuTLS.
234 * see gnutls/lib/x509/rfc2818_hostname.c
236 for (idx = 0; res >= 0; idx++)
238 dns_name_size = sizeof (dns_name);
239 res = gnutls_x509_crt_get_subject_alt_name (cert, idx,
240 dns_name, &dns_name_size, NULL);
242 if (res == GNUTLS_SAN_DNSNAME || res == GNUTLS_SAN_IPADDRESS)
243 return g_strndup (dns_name, dns_name_size);
246 dns_name_size = sizeof (dns_name);
247 res = gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME,
248 0, 0, dns_name, &dns_name_size);
251 return g_strndup (dns_name, dns_name_size);
257 real_start_verification (EmpathyTLSVerifier *self)
259 gnutls_x509_crt_t first_cert, last_cert;
261 gboolean res = FALSE;
263 EmpTLSCertificateRejectReason reason =
264 EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
265 EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
267 DEBUG ("Starting verification");
269 /* check if the certificate matches the hostname first. */
270 first_cert = g_ptr_array_index (priv->cert_chain, 0);
271 if (gnutls_x509_crt_check_hostname (first_cert, priv->hostname) == 0)
273 gchar *certified_hostname;
275 reason = EMP_TLS_CERTIFICATE_REJECT_REASON_HOSTNAME_MISMATCH;
276 certified_hostname = get_certified_hostname (first_cert);
277 tp_asv_set_string (priv->details,
278 "expected-hostname", priv->hostname);
279 tp_asv_set_string (priv->details,
280 "certificate-hostname", certified_hostname);
282 DEBUG ("Hostname mismatch: got %s but expected %s",
283 certified_hostname, priv->hostname);
285 g_free (certified_hostname);
289 DEBUG ("Hostname matched");
291 num_certs = priv->cert_chain->len;
293 if (priv->trusted_ca_list->len > 0)
295 /* if the last certificate is self-signed, and we have a list of
296 * trusted CAs, ignore it, as we want to check the chain against our
297 * trusted CAs list first.
299 last_cert = g_ptr_array_index (priv->cert_chain, num_certs - 1);
301 if (gnutls_x509_crt_check_issuer (last_cert, last_cert) > 0)
305 for (idx = 1; idx < num_certs; idx++)
307 res = verify_certificate (self,
308 g_ptr_array_index (priv->cert_chain, idx -1),
309 g_ptr_array_index (priv->cert_chain, idx),
312 DEBUG ("Certificate verification %d gave result %d with reason %u", idx,
317 abort_verification (self, reason);
322 res = verify_last_certificate (self,
323 g_ptr_array_index (priv->cert_chain, num_certs - 1),
326 DEBUG ("Last verification gave result %d with reason %u", res, reason);
331 abort_verification (self, reason);
335 complete_verification (self);
339 start_verification (gpointer user_data)
341 EmpathyTLSVerifier *self = user_data;
343 real_start_verification (self);
349 build_gnutls_cert_list (EmpathyTLSVerifier *self)
353 GPtrArray *certificate_data = NULL;
354 EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
356 g_object_get (priv->certificate,
357 "cert-data", &certificate_data,
359 num_certs = certificate_data->len;
361 priv->cert_chain = g_ptr_array_new_with_free_func (
362 (GDestroyNotify) gnutls_x509_crt_deinit);
364 for (idx = 0; idx < num_certs; idx++)
366 gnutls_x509_crt_t cert;
368 gnutls_datum_t datum = { NULL, 0 };
370 one_cert = g_ptr_array_index (certificate_data, idx);
371 datum.data = (guchar *) one_cert->data;
372 datum.size = one_cert->len;
374 gnutls_x509_crt_init (&cert);
375 gnutls_x509_crt_import (cert, &datum, GNUTLS_X509_FMT_DER);
377 g_ptr_array_add (priv->cert_chain, cert);
382 get_number_and_type_of_certificates (gnutls_datum_t *datum,
383 gnutls_x509_crt_fmt_t *format)
385 gnutls_x509_crt_t fake;
389 res = gnutls_x509_crt_list_import (&fake, &retval, datum,
390 GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
392 if (res == GNUTLS_E_SHORT_MEMORY_BUFFER || res > 0)
394 DEBUG ("Found PEM, with %u certificates", retval);
395 *format = GNUTLS_X509_FMT_PEM;
400 res = gnutls_x509_crt_list_import (&fake, &retval, datum,
401 GNUTLS_X509_FMT_DER, 0);
405 *format = GNUTLS_X509_FMT_DER;
413 build_gnutls_ca_and_crl_lists (GIOSchedulerJob *job,
414 GCancellable *cancellable,
418 gchar *user_certs_dir;
420 GError *error = NULL;
421 EmpathyTLSVerifier *self = user_data;
422 EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
424 priv->trusted_ca_list = g_ptr_array_new_with_free_func
425 ((GDestroyNotify) gnutls_x509_crt_deinit);
427 for (idx = 0; idx < (gint) G_N_ELEMENTS (system_ca_paths) - 1; idx++)
430 gchar *contents = NULL;
433 gnutls_x509_crt_t *cert_list;
434 gnutls_datum_t datum = { NULL, 0 };
435 gnutls_x509_crt_fmt_t format = 0;
437 path = system_ca_paths[idx];
438 g_file_get_contents (path, &contents, &length, &error);
442 DEBUG ("Unable to read system CAs from path %s: %s", path,
444 g_clear_error (&error);
448 datum.data = (guchar *) contents;
450 n_certs = get_number_and_type_of_certificates (&datum, &format);
454 DEBUG ("Unable to parse the system CAs from path %s: GnuTLS "
455 "returned error %d", path, n_certs);
461 cert_list = g_malloc0 (sizeof (gnutls_x509_crt_t) * n_certs);
462 res = gnutls_x509_crt_list_import (cert_list, (guint *) &n_certs, &datum,
467 DEBUG ("Unable to import system CAs from path %s; "
468 "GnuTLS returned error %d", path, res);
474 DEBUG ("Successfully imported %d system CA certificates from path %s",
477 /* append the newly created cert structutes into the global GPtrArray */
478 for (idx = 0; idx < n_certs; idx++)
479 g_ptr_array_add (priv->trusted_ca_list, cert_list[idx]);
486 user_certs_dir = g_build_filename (g_get_user_config_dir (),
487 "telepathy", "certs", NULL);
488 dir = g_dir_open (user_certs_dir, 0, &error);
492 DEBUG ("Can't open the user certs dir at %s: %s", user_certs_dir,
495 g_error_free (error);
499 const gchar *cert_name;
501 while ((cert_name = g_dir_read_name (dir)) != NULL)
503 gchar *contents = NULL, *cert_path = NULL;
506 gnutls_datum_t datum = { NULL, 0 };
507 gnutls_x509_crt_t cert;
509 cert_path = g_build_filename (user_certs_dir, cert_name, NULL);
511 g_file_get_contents (cert_path, &contents, &length, &error);
515 DEBUG ("Can't open the certificate file at path %s: %s",
516 cert_path, error->message);
518 g_clear_error (&error);
523 datum.data = (guchar *) contents;
526 gnutls_x509_crt_init (&cert);
527 res = gnutls_x509_crt_import (cert, &datum, GNUTLS_X509_FMT_PEM);
529 if (res != GNUTLS_E_SUCCESS)
531 DEBUG ("Can't import the certificate at path %s: "
532 "GnuTLS returned %d", cert_path, res);
536 g_ptr_array_add (priv->trusted_ca_list, cert);
546 g_free (user_certs_dir);
548 /* TODO: do the CRL too */
550 g_io_scheduler_job_send_to_mainloop_async (job,
551 start_verification, self, NULL);
557 empathy_tls_verifier_get_property (GObject *object,
562 EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
566 case PROP_TLS_CERTIFICATE:
567 g_value_set_object (value, priv->certificate);
570 g_value_set_string (value, priv->hostname);
573 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
579 empathy_tls_verifier_set_property (GObject *object,
584 EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
588 case PROP_TLS_CERTIFICATE:
589 priv->certificate = g_value_dup_object (value);
592 priv->hostname = g_value_dup_string (value);
595 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
601 empathy_tls_verifier_dispose (GObject *object)
603 EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
605 if (priv->dispose_run)
608 priv->dispose_run = TRUE;
610 tp_clear_object (&priv->certificate);
612 G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->dispose (object);
616 empathy_tls_verifier_finalize (GObject *object)
618 EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
620 DEBUG ("%p", object);
622 tp_clear_pointer (&priv->trusted_ca_list, g_ptr_array_unref);
623 tp_clear_pointer (&priv->cert_chain, g_ptr_array_unref);
624 tp_clear_boxed (G_TYPE_HASH_TABLE, &priv->details);
625 g_free (priv->hostname);
627 G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->finalize (object);
631 empathy_tls_verifier_constructed (GObject *object)
633 EmpathyTLSVerifier *self = EMPATHY_TLS_VERIFIER (object);
635 build_gnutls_cert_list (self);
637 if (G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->constructed != NULL)
638 G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->constructed (object);
642 empathy_tls_verifier_init (EmpathyTLSVerifier *self)
644 EmpathyTLSVerifierPriv *priv;
646 priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
647 EMPATHY_TYPE_TLS_VERIFIER, EmpathyTLSVerifierPriv);
648 priv->details = tp_asv_new (NULL, NULL);
652 empathy_tls_verifier_class_init (EmpathyTLSVerifierClass *klass)
655 GObjectClass *oclass = G_OBJECT_CLASS (klass);
657 g_type_class_add_private (klass, sizeof (EmpathyTLSVerifierPriv));
659 oclass->set_property = empathy_tls_verifier_set_property;
660 oclass->get_property = empathy_tls_verifier_get_property;
661 oclass->finalize = empathy_tls_verifier_finalize;
662 oclass->dispose = empathy_tls_verifier_dispose;
663 oclass->constructed = empathy_tls_verifier_constructed;
665 pspec = g_param_spec_object ("certificate", "The EmpathyTLSCertificate",
666 "The EmpathyTLSCertificate to be verified.",
667 EMPATHY_TYPE_TLS_CERTIFICATE,
668 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
669 g_object_class_install_property (oclass, PROP_TLS_CERTIFICATE, pspec);
671 pspec = g_param_spec_string ("hostname", "The hostname",
672 "The hostname which should be certified by the certificate.",
674 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
675 g_object_class_install_property (oclass, PROP_HOSTNAME, pspec);
679 empathy_tls_verifier_new (EmpathyTLSCertificate *certificate,
680 const gchar *hostname)
682 g_assert (EMPATHY_IS_TLS_CERTIFICATE (certificate));
683 g_assert (hostname != NULL);
685 return g_object_new (EMPATHY_TYPE_TLS_VERIFIER,
686 "certificate", certificate,
687 "hostname", hostname,
692 empathy_tls_verifier_verify_async (EmpathyTLSVerifier *self,
693 GAsyncReadyCallback callback,
696 EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
698 priv->verify_result = g_simple_async_result_new (G_OBJECT (self),
699 callback, user_data, NULL);
701 g_io_scheduler_push_job (build_gnutls_ca_and_crl_lists,
702 self, NULL, G_PRIORITY_DEFAULT, NULL);
706 empathy_tls_verifier_verify_finish (EmpathyTLSVerifier *self,
708 EmpTLSCertificateRejectReason *reason,
709 GHashTable **details,
712 EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
714 if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res),
718 *reason = (*error)->code;
722 *details = tp_asv_new (NULL, NULL);
723 tp_g_hash_table_update (*details, priv->details,
724 (GBoxedCopyFunc) g_strdup,
725 (GBoxedCopyFunc) tp_g_value_slice_dup);
732 *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;