]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tls-verifier.c
Merge branch 'muc-crash-633329'
[empathy.git] / libempathy / empathy-tls-verifier.c
1 /*
2  * empathy-tls-verifier.c - Source for EmpathyTLSVerifier
3  * Copyright (C) 2010 Collabora Ltd.
4  * @author Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
5  *
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.
10  *
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.
15  *
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
19  */
20
21 #include <config.h>
22
23 #include <gnutls/gnutls.h>
24 #include <gnutls/x509.h>
25
26 #include <telepathy-glib/util.h>
27
28 #include "empathy-tls-verifier.h"
29
30 #define DEBUG_FLAG EMPATHY_DEBUG_TLS
31 #include "empathy-debug.h"
32 #include "empathy-utils.h"
33
34 G_DEFINE_TYPE (EmpathyTLSVerifier, empathy_tls_verifier,
35     G_TYPE_OBJECT)
36
37 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTLSVerifier);
38
39 enum {
40   PROP_TLS_CERTIFICATE = 1,
41   PROP_HOSTNAME,
42
43   LAST_PROPERTY,
44 };
45
46 static const gchar* system_ca_paths[] = {
47   "/etc/ssl/certs/ca-certificates.crt",
48   NULL,
49 };
50
51 typedef struct {
52   GPtrArray *cert_chain;
53
54   GPtrArray *trusted_ca_list;
55   GPtrArray *trusted_crl_list;
56
57   EmpathyTLSCertificate *certificate;
58   gchar *hostname;
59
60   GSimpleAsyncResult *verify_result;
61   GHashTable *details;
62
63   gboolean dispose_run;
64 } EmpathyTLSVerifierPriv;
65
66 static gnutls_x509_crt_t *
67 ptr_array_to_x509_crt_list (GPtrArray *chain)
68 {
69   gnutls_x509_crt_t *retval;
70   gint idx;
71
72   retval = g_malloc0 (sizeof (gnutls_x509_crt_t) * chain->len);
73
74   for (idx = 0; idx < (gint) chain->len; idx++)
75     retval[idx] = g_ptr_array_index (chain, idx);
76
77   return retval;
78 }
79
80 static gboolean
81 verification_output_to_reason (gint res,
82     guint verify_output,
83     EmpTLSCertificateRejectReason *reason)
84 {
85   gboolean retval = TRUE;
86
87   g_assert (reason != NULL);
88
89   if (res != GNUTLS_E_SUCCESS)
90     {
91       retval = FALSE;
92
93       /* the certificate is not structurally valid */
94       switch (res)
95         {
96         case GNUTLS_E_INSUFFICIENT_CREDENTIALS:
97           *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED;
98           break;
99         case GNUTLS_E_CONSTRAINT_ERROR:
100           *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_LIMIT_EXCEEDED;
101           break;
102         default:
103           *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
104           break;
105         }
106
107       goto out;
108     }
109
110   /* the certificate is structurally valid, check for other errors. */
111   if (verify_output & GNUTLS_CERT_INVALID)
112     {
113       retval = FALSE;
114
115       if (verify_output & GNUTLS_CERT_SIGNER_NOT_FOUND)
116         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_SELF_SIGNED;
117       else if (verify_output & GNUTLS_CERT_SIGNER_NOT_CA)
118         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED;
119       else if (verify_output & GNUTLS_CERT_INSECURE_ALGORITHM)
120         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_INSECURE;
121       else if (verify_output & GNUTLS_CERT_NOT_ACTIVATED)
122         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_NOT_ACTIVATED;
123       else if (verify_output & GNUTLS_CERT_EXPIRED)
124         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_EXPIRED;
125       else
126         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
127
128       goto out;
129     }
130
131  out:
132   return retval;
133 }
134
135 static gboolean
136 verify_last_certificate (EmpathyTLSVerifier *self,
137     gnutls_x509_crt_t cert,
138     EmpTLSCertificateRejectReason *reason)
139 {
140   guint verify_output;
141   gint res;
142   gnutls_x509_crt_t *trusted_ca_list;
143   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
144
145   if (priv->trusted_ca_list->len > 0)
146     {
147       trusted_ca_list = ptr_array_to_x509_crt_list (priv->trusted_ca_list);
148       res = gnutls_x509_crt_verify (cert, trusted_ca_list,
149           priv->trusted_ca_list->len, 0, &verify_output);
150
151       DEBUG ("Checking last certificate %p against trusted CAs, output %u",
152           cert, verify_output);
153
154       g_free (trusted_ca_list);
155     }
156   else
157     {
158       /* check it against itself to see if it's structurally valid */
159       res = gnutls_x509_crt_verify (cert, &cert, 1, 0, &verify_output);
160
161       DEBUG ("Checking last certificate %p against itself, output %u", cert,
162           verify_output);
163
164       /* if it's valid, return the SelfSigned error, so that we can add it
165        * later to our trusted CAs whitelist.
166        */
167       if (res == GNUTLS_E_SUCCESS)
168         {
169           *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_SELF_SIGNED;
170           return FALSE;
171         }
172     }
173
174   return verification_output_to_reason (res, verify_output, reason);
175 }
176
177 static gboolean
178 verify_certificate (EmpathyTLSVerifier *self,
179     gnutls_x509_crt_t cert,
180     gnutls_x509_crt_t issuer,
181     EmpTLSCertificateRejectReason *reason)
182 {
183   guint verify_output;
184   gint res;
185
186   res = gnutls_x509_crt_verify (cert, &issuer, 1, 0, &verify_output);
187
188   DEBUG ("Verifying %p against %p, output %u", cert, issuer, verify_output);
189
190   return verification_output_to_reason (res, verify_output, reason);
191 }
192
193 static void
194 complete_verification (EmpathyTLSVerifier *self)
195 {
196   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
197
198   DEBUG ("Verification successful, completing...");
199
200   g_simple_async_result_complete_in_idle (priv->verify_result);
201
202   tp_clear_object (&priv->verify_result);
203 }
204
205 static void
206 abort_verification (EmpathyTLSVerifier *self,
207     EmpTLSCertificateRejectReason reason)
208 {
209   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
210
211   DEBUG ("Verification error %u, aborting...", reason);
212
213   g_simple_async_result_set_error (priv->verify_result,
214       G_IO_ERROR, reason, "TLS verification failed with reason %u",
215       reason);
216   g_simple_async_result_complete_in_idle (priv->verify_result);
217
218   tp_clear_object (&priv->verify_result);
219 }
220
221 static void
222 real_start_verification (EmpathyTLSVerifier *self)
223 {
224   gnutls_x509_crt_t first_cert, last_cert;
225   gint idx;
226   gboolean res = FALSE;
227   gint num_certs;
228   EmpTLSCertificateRejectReason reason =
229     EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
230   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
231
232   DEBUG ("Starting verification");
233
234   /* check if the certificate matches the hostname first. */
235   first_cert = g_ptr_array_index (priv->cert_chain, 0);
236   if (gnutls_x509_crt_check_hostname (first_cert, priv->hostname) == 0)
237     {
238       gchar *certified_hostname;
239
240       reason = EMP_TLS_CERTIFICATE_REJECT_REASON_HOSTNAME_MISMATCH;
241       certified_hostname = empathy_get_x509_certificate_hostname (first_cert);
242       tp_asv_set_string (priv->details,
243           "expected-hostname", priv->hostname);
244       tp_asv_set_string (priv->details,
245           "certificate-hostname", certified_hostname);
246
247       DEBUG ("Hostname mismatch: got %s but expected %s",
248           certified_hostname, priv->hostname);
249
250       g_free (certified_hostname);
251       goto out;
252     }
253
254   DEBUG ("Hostname matched");
255
256   num_certs = priv->cert_chain->len;
257
258   if (priv->trusted_ca_list->len > 0)
259     {
260       /* if the last certificate is self-signed, and we have a list of
261        * trusted CAs, ignore it, as we want to check the chain against our
262        * trusted CAs list first.
263        * if we have only one certificate in the chain, don't ignore it though,
264        * as it's the CA certificate itself.
265        */
266       last_cert = g_ptr_array_index (priv->cert_chain, num_certs - 1);
267
268       if (gnutls_x509_crt_check_issuer (last_cert, last_cert) > 0 &&
269           num_certs > 1)
270         num_certs--;
271     }
272
273   for (idx = 1; idx < num_certs; idx++)
274     {
275       res = verify_certificate (self,
276           g_ptr_array_index (priv->cert_chain, idx -1),
277           g_ptr_array_index (priv->cert_chain, idx),
278           &reason);
279
280       DEBUG ("Certificate verification %d gave result %d with reason %u", idx,
281           res, reason);
282
283       if (!res)
284         {
285           abort_verification (self, reason);
286           return;
287         }
288     }
289
290   res = verify_last_certificate (self,
291       g_ptr_array_index (priv->cert_chain, num_certs - 1),
292       &reason);
293
294   DEBUG ("Last verification gave result %d with reason %u", res, reason);
295
296  out:
297   if (!res)
298     {
299       abort_verification (self, reason);
300       return;
301     }
302
303   complete_verification (self);
304 }
305
306 static gboolean
307 start_verification (gpointer user_data)
308 {
309   EmpathyTLSVerifier *self = user_data;
310
311   real_start_verification (self);
312
313   return FALSE;
314 }
315
316 static void
317 build_gnutls_cert_list (EmpathyTLSVerifier *self)
318 {
319   guint num_certs;
320   guint idx;
321   GPtrArray *certificate_data = NULL;
322   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
323
324   g_object_get (priv->certificate,
325       "cert-data", &certificate_data,
326       NULL);
327   num_certs = certificate_data->len;
328
329   priv->cert_chain = g_ptr_array_new_with_free_func (
330       (GDestroyNotify) gnutls_x509_crt_deinit);
331
332   for (idx = 0; idx < num_certs; idx++)
333     {
334       gnutls_x509_crt_t cert;
335       GArray *one_cert;
336       gnutls_datum_t datum = { NULL, 0 };
337
338       one_cert = g_ptr_array_index (certificate_data, idx);
339       datum.data = (guchar *) one_cert->data;
340       datum.size = one_cert->len;
341
342       gnutls_x509_crt_init (&cert);
343       gnutls_x509_crt_import (cert, &datum, GNUTLS_X509_FMT_DER);
344
345       g_ptr_array_add (priv->cert_chain, cert);
346     }
347 }
348
349 static gint
350 get_number_and_type_of_certificates (gnutls_datum_t *datum,
351     gnutls_x509_crt_fmt_t *format)
352 {
353   gnutls_x509_crt_t fake;
354   guint retval = 1;
355   gint res;
356
357   res = gnutls_x509_crt_list_import (&fake, &retval, datum,
358       GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
359
360   if (res == GNUTLS_E_SHORT_MEMORY_BUFFER || res > 0)
361     {
362       DEBUG ("Found PEM, with %u certificates", retval);
363       *format = GNUTLS_X509_FMT_PEM;
364       return retval;
365     }
366
367   /* try DER */
368   res = gnutls_x509_crt_list_import (&fake, &retval, datum,
369       GNUTLS_X509_FMT_DER, 0);
370
371   if (res > 0)
372     {
373       *format = GNUTLS_X509_FMT_DER;
374       return retval;
375     }
376
377   return res;
378 }
379
380 static gboolean
381 build_gnutls_ca_and_crl_lists (GIOSchedulerJob *job,
382     GCancellable *cancellable,
383     gpointer user_data)
384 {
385   gint idx;
386   gchar *user_certs_dir;
387   GDir *dir;
388   GError *error = NULL;
389   EmpathyTLSVerifier *self = user_data;
390   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
391
392   priv->trusted_ca_list = g_ptr_array_new_with_free_func
393     ((GDestroyNotify) gnutls_x509_crt_deinit);
394
395   for (idx = 0; idx < (gint) G_N_ELEMENTS (system_ca_paths) - 1; idx++)
396     {
397       const gchar *path;
398       gchar *contents = NULL;
399       gsize length = 0;
400       gint res, n_certs;
401       gnutls_x509_crt_t *cert_list;
402       gnutls_datum_t datum = { NULL, 0 };
403       gnutls_x509_crt_fmt_t format = 0;
404
405       path = system_ca_paths[idx];
406       g_file_get_contents (path, &contents, &length, &error);
407
408       if (error != NULL)
409         {
410           DEBUG ("Unable to read system CAs from path %s: %s", path,
411               error->message);
412           g_clear_error (&error);
413           continue;
414         }
415
416       datum.data = (guchar *) contents;
417       datum.size = length;
418       n_certs = get_number_and_type_of_certificates (&datum, &format);
419
420       if (n_certs < 0)
421         {
422           DEBUG ("Unable to parse the system CAs from path %s: GnuTLS "
423               "returned error %d", path, n_certs);
424
425           g_free (contents);
426           continue;
427         }
428
429       cert_list = g_malloc0 (sizeof (gnutls_x509_crt_t) * n_certs);
430       res = gnutls_x509_crt_list_import (cert_list, (guint *) &n_certs, &datum,
431           format, 0);
432
433       if (res < 0)
434         {
435           DEBUG ("Unable to import system CAs from path %s; "
436               "GnuTLS returned error %d", path, res);
437
438           g_free (contents);
439           continue;
440         }
441
442       DEBUG ("Successfully imported %d system CA certificates from path %s",
443           n_certs, path);
444
445       /* append the newly created cert structutes into the global GPtrArray */
446       for (idx = 0; idx < n_certs; idx++)
447         g_ptr_array_add (priv->trusted_ca_list, cert_list[idx]);
448
449       g_free (contents);
450       g_free (cert_list);
451     }
452
453   /* user certs */
454   user_certs_dir = g_build_filename (g_get_user_config_dir (),
455       "telepathy", "certs", NULL);
456   dir = g_dir_open (user_certs_dir, 0, &error);
457
458   if (error != NULL)
459     {
460       DEBUG ("Can't open the user certs dir at %s: %s", user_certs_dir,
461           error->message);
462
463       g_error_free (error);
464     }
465   else
466     {
467       const gchar *cert_name;
468
469       while ((cert_name = g_dir_read_name (dir)) != NULL)
470         {
471           gchar *contents = NULL, *cert_path = NULL;
472           gsize length = 0;
473           gint res;
474           gnutls_datum_t datum = { NULL, 0 };
475           gnutls_x509_crt_t cert;
476
477           cert_path = g_build_filename (user_certs_dir, cert_name, NULL);
478
479           g_file_get_contents (cert_path, &contents, &length, &error);
480
481           if (error != NULL)
482             {
483               DEBUG ("Can't open the certificate file at path %s: %s",
484                   cert_path, error->message);
485
486               g_clear_error (&error);
487               g_free (cert_path);
488               continue;
489             }
490
491           datum.data = (guchar *) contents;
492           datum.size = length;
493
494           gnutls_x509_crt_init (&cert);
495           res = gnutls_x509_crt_import (cert, &datum, GNUTLS_X509_FMT_PEM);
496
497           if (res != GNUTLS_E_SUCCESS)
498             {
499               DEBUG ("Can't import the certificate at path %s: "
500                   "GnuTLS returned %d", cert_path, res);
501             }
502           else
503             {
504               g_ptr_array_add (priv->trusted_ca_list, cert);
505             }
506
507           g_free (contents);
508           g_free (cert_path);
509         }
510
511       g_dir_close (dir);
512     }
513
514   g_free (user_certs_dir);
515
516   /* TODO: do the CRL too */
517
518   g_io_scheduler_job_send_to_mainloop_async (job,
519       start_verification, self, NULL);
520
521   return FALSE;
522 }
523
524 static void
525 empathy_tls_verifier_get_property (GObject *object,
526     guint property_id,
527     GValue *value,
528     GParamSpec *pspec)
529 {
530   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
531
532   switch (property_id)
533     {
534     case PROP_TLS_CERTIFICATE:
535       g_value_set_object (value, priv->certificate);
536       break;
537     case PROP_HOSTNAME:
538       g_value_set_string (value, priv->hostname);
539       break;
540     default:
541       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
542       break;
543     }
544 }
545
546 static void
547 empathy_tls_verifier_set_property (GObject *object,
548     guint property_id,
549     const GValue *value,
550     GParamSpec *pspec)
551 {
552   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
553
554   switch (property_id)
555     {
556     case PROP_TLS_CERTIFICATE:
557       priv->certificate = g_value_dup_object (value);
558       break;
559     case PROP_HOSTNAME:
560       priv->hostname = g_value_dup_string (value);
561       break;
562     default:
563       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
564       break;
565     }
566 }
567
568 static void
569 empathy_tls_verifier_dispose (GObject *object)
570 {
571   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
572
573   if (priv->dispose_run)
574     return;
575
576   priv->dispose_run = TRUE;
577
578   tp_clear_object (&priv->certificate);
579
580   G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->dispose (object);
581 }
582
583 static void
584 empathy_tls_verifier_finalize (GObject *object)
585 {
586   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
587
588   DEBUG ("%p", object);
589
590   tp_clear_pointer (&priv->trusted_ca_list, g_ptr_array_unref);
591   tp_clear_pointer (&priv->cert_chain, g_ptr_array_unref);
592   tp_clear_boxed (G_TYPE_HASH_TABLE, &priv->details);
593   g_free (priv->hostname);
594
595   G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->finalize (object);
596 }
597
598 static void
599 empathy_tls_verifier_constructed (GObject *object)
600 {
601   EmpathyTLSVerifier *self = EMPATHY_TLS_VERIFIER (object);
602
603   build_gnutls_cert_list (self);
604
605   if (G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->constructed != NULL)
606     G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->constructed (object);
607 }
608
609 static void
610 empathy_tls_verifier_init (EmpathyTLSVerifier *self)
611 {
612   EmpathyTLSVerifierPriv *priv;
613
614   priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
615       EMPATHY_TYPE_TLS_VERIFIER, EmpathyTLSVerifierPriv);
616   priv->details = tp_asv_new (NULL, NULL);
617 }
618
619 static void
620 empathy_tls_verifier_class_init (EmpathyTLSVerifierClass *klass)
621 {
622   GParamSpec *pspec;
623   GObjectClass *oclass = G_OBJECT_CLASS (klass);
624
625   g_type_class_add_private (klass, sizeof (EmpathyTLSVerifierPriv));
626
627   oclass->set_property = empathy_tls_verifier_set_property;
628   oclass->get_property = empathy_tls_verifier_get_property;
629   oclass->finalize = empathy_tls_verifier_finalize;
630   oclass->dispose = empathy_tls_verifier_dispose;
631   oclass->constructed = empathy_tls_verifier_constructed;
632
633   pspec = g_param_spec_object ("certificate", "The EmpathyTLSCertificate",
634       "The EmpathyTLSCertificate to be verified.",
635       EMPATHY_TYPE_TLS_CERTIFICATE,
636       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
637   g_object_class_install_property (oclass, PROP_TLS_CERTIFICATE, pspec);
638
639   pspec = g_param_spec_string ("hostname", "The hostname",
640       "The hostname which should be certified by the certificate.",
641       NULL,
642       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
643   g_object_class_install_property (oclass, PROP_HOSTNAME, pspec);
644 }
645
646 EmpathyTLSVerifier *
647 empathy_tls_verifier_new (EmpathyTLSCertificate *certificate,
648     const gchar *hostname)
649 {
650   g_assert (EMPATHY_IS_TLS_CERTIFICATE (certificate));
651   g_assert (hostname != NULL);
652
653   return g_object_new (EMPATHY_TYPE_TLS_VERIFIER,
654       "certificate", certificate,
655       "hostname", hostname,
656       NULL);
657 }
658
659 void
660 empathy_tls_verifier_verify_async (EmpathyTLSVerifier *self,
661     GAsyncReadyCallback callback,
662     gpointer user_data)
663 {
664   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
665
666   g_return_if_fail (priv->verify_result == NULL);
667
668   priv->verify_result = g_simple_async_result_new (G_OBJECT (self),
669       callback, user_data, NULL);
670
671   g_io_scheduler_push_job (build_gnutls_ca_and_crl_lists,
672       self, NULL, G_PRIORITY_DEFAULT, NULL);
673 }
674
675 gboolean
676 empathy_tls_verifier_verify_finish (EmpathyTLSVerifier *self,
677     GAsyncResult *res,
678     EmpTLSCertificateRejectReason *reason,
679     GHashTable **details,
680     GError **error)
681 {
682   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
683
684   if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res),
685           error))
686     {
687       if (reason != NULL)
688         *reason = (*error)->code;
689
690       if (details != NULL)
691         {
692           *details = tp_asv_new (NULL, NULL);
693           tp_g_hash_table_update (*details, priv->details,
694               (GBoxedCopyFunc) g_strdup,
695               (GBoxedCopyFunc) tp_g_value_slice_dup);
696         }
697
698       return FALSE;
699     }
700
701   if (reason != NULL)
702     *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
703
704   return TRUE;
705 }