]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tls-verifier.c
coding style fixes
[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        */
264       last_cert = g_ptr_array_index (priv->cert_chain, num_certs - 1);
265
266       if (gnutls_x509_crt_check_issuer (last_cert, last_cert) > 0)
267         num_certs--;
268     }
269
270   for (idx = 1; idx < num_certs; idx++)
271     {
272       res = verify_certificate (self,
273           g_ptr_array_index (priv->cert_chain, idx -1),
274           g_ptr_array_index (priv->cert_chain, idx),
275           &reason);
276
277       DEBUG ("Certificate verification %d gave result %d with reason %u", idx,
278           res, reason);
279
280       if (!res)
281         {
282           abort_verification (self, reason);
283           return;
284         }
285     }
286
287   res = verify_last_certificate (self,
288       g_ptr_array_index (priv->cert_chain, num_certs - 1),
289       &reason);
290
291   DEBUG ("Last verification gave result %d with reason %u", res, reason);
292
293  out:
294   if (!res)
295     {
296       abort_verification (self, reason);
297       return;
298     }
299
300   complete_verification (self);
301 }
302
303 static gboolean
304 start_verification (gpointer user_data)
305 {
306   EmpathyTLSVerifier *self = user_data;
307
308   real_start_verification (self);
309
310   return FALSE;
311 }
312
313 static void
314 build_gnutls_cert_list (EmpathyTLSVerifier *self)
315 {
316   guint num_certs;
317   guint idx;
318   GPtrArray *certificate_data = NULL;
319   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
320
321   g_object_get (priv->certificate,
322       "cert-data", &certificate_data,
323       NULL);
324   num_certs = certificate_data->len;
325
326   priv->cert_chain = g_ptr_array_new_with_free_func (
327       (GDestroyNotify) gnutls_x509_crt_deinit);
328
329   for (idx = 0; idx < num_certs; idx++)
330     {
331       gnutls_x509_crt_t cert;
332       GArray *one_cert;
333       gnutls_datum_t datum = { NULL, 0 };
334
335       one_cert = g_ptr_array_index (certificate_data, idx);
336       datum.data = (guchar *) one_cert->data;
337       datum.size = one_cert->len;
338
339       gnutls_x509_crt_init (&cert);
340       gnutls_x509_crt_import (cert, &datum, GNUTLS_X509_FMT_DER);
341
342       g_ptr_array_add (priv->cert_chain, cert);
343     }
344 }
345
346 static gint
347 get_number_and_type_of_certificates (gnutls_datum_t *datum,
348     gnutls_x509_crt_fmt_t *format)
349 {
350   gnutls_x509_crt_t fake;
351   guint retval = 1;
352   gint res;
353
354   res = gnutls_x509_crt_list_import (&fake, &retval, datum,
355       GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
356
357   if (res == GNUTLS_E_SHORT_MEMORY_BUFFER || res > 0)
358     {
359       DEBUG ("Found PEM, with %u certificates", retval);
360       *format = GNUTLS_X509_FMT_PEM;
361       return retval;
362     }
363
364   /* try DER */
365   res = gnutls_x509_crt_list_import (&fake, &retval, datum,
366       GNUTLS_X509_FMT_DER, 0);
367
368   if (res > 0)
369     {
370       *format = GNUTLS_X509_FMT_DER;
371       return retval;
372     }
373
374   return res;
375 }
376
377 static gboolean
378 build_gnutls_ca_and_crl_lists (GIOSchedulerJob *job,
379     GCancellable *cancellable,
380     gpointer user_data)
381 {
382   gint idx;
383   gchar *user_certs_dir;
384   GDir *dir;
385   GError *error = NULL;
386   EmpathyTLSVerifier *self = user_data;
387   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
388
389   priv->trusted_ca_list = g_ptr_array_new_with_free_func
390     ((GDestroyNotify) gnutls_x509_crt_deinit);
391
392   for (idx = 0; idx < (gint) G_N_ELEMENTS (system_ca_paths) - 1; idx++)
393     {
394       const gchar *path;
395       gchar *contents = NULL;
396       gsize length = 0;
397       gint res, n_certs;
398       gnutls_x509_crt_t *cert_list;
399       gnutls_datum_t datum = { NULL, 0 };
400       gnutls_x509_crt_fmt_t format = 0;
401
402       path = system_ca_paths[idx];
403       g_file_get_contents (path, &contents, &length, &error);
404
405       if (error != NULL)
406         {
407           DEBUG ("Unable to read system CAs from path %s: %s", path,
408               error->message);
409           g_clear_error (&error);
410           continue;
411         }
412
413       datum.data = (guchar *) contents;
414       datum.size = length;
415       n_certs = get_number_and_type_of_certificates (&datum, &format);
416
417       if (n_certs < 0)
418         {
419           DEBUG ("Unable to parse the system CAs from path %s: GnuTLS "
420               "returned error %d", path, n_certs);
421
422           g_free (contents);
423           continue;
424         }
425
426       cert_list = g_malloc0 (sizeof (gnutls_x509_crt_t) * n_certs);
427       res = gnutls_x509_crt_list_import (cert_list, (guint *) &n_certs, &datum,
428           format, 0);
429
430       if (res < 0)
431         {
432           DEBUG ("Unable to import system CAs from path %s; "
433               "GnuTLS returned error %d", path, res);
434
435           g_free (contents);
436           continue;
437         }
438
439       DEBUG ("Successfully imported %d system CA certificates from path %s",
440           n_certs, path);
441
442       /* append the newly created cert structutes into the global GPtrArray */
443       for (idx = 0; idx < n_certs; idx++)
444         g_ptr_array_add (priv->trusted_ca_list, cert_list[idx]);
445
446       g_free (contents);
447       g_free (cert_list);
448     }
449
450   /* user certs */
451   user_certs_dir = g_build_filename (g_get_user_config_dir (),
452       "telepathy", "certs", NULL);
453   dir = g_dir_open (user_certs_dir, 0, &error);
454
455   if (error != NULL)
456     {
457       DEBUG ("Can't open the user certs dir at %s: %s", user_certs_dir,
458           error->message);
459
460       g_error_free (error);
461     }
462   else
463     {
464       const gchar *cert_name;
465
466       while ((cert_name = g_dir_read_name (dir)) != NULL)
467         {
468           gchar *contents = NULL, *cert_path = NULL;
469           gsize length = 0;
470           gint res;
471           gnutls_datum_t datum = { NULL, 0 };
472           gnutls_x509_crt_t cert;
473
474           cert_path = g_build_filename (user_certs_dir, cert_name, NULL);
475
476           g_file_get_contents (cert_path, &contents, &length, &error);
477
478           if (error != NULL)
479             {
480               DEBUG ("Can't open the certificate file at path %s: %s",
481                   cert_path, error->message);
482
483               g_clear_error (&error);
484               g_free (cert_path);
485               continue;
486             }
487
488           datum.data = (guchar *) contents;
489           datum.size = length;
490
491           gnutls_x509_crt_init (&cert);
492           res = gnutls_x509_crt_import (cert, &datum, GNUTLS_X509_FMT_PEM);
493
494           if (res != GNUTLS_E_SUCCESS)
495             {
496               DEBUG ("Can't import the certificate at path %s: "
497                   "GnuTLS returned %d", cert_path, res);
498             }
499           else
500             {
501               g_ptr_array_add (priv->trusted_ca_list, cert);
502             }
503
504           g_free (contents);
505           g_free (cert_path);
506         }
507
508       g_dir_close (dir);
509     }
510
511   g_free (user_certs_dir);
512
513   /* TODO: do the CRL too */
514
515   g_io_scheduler_job_send_to_mainloop_async (job,
516       start_verification, self, NULL);
517
518   return FALSE;
519 }
520
521 static void
522 empathy_tls_verifier_get_property (GObject *object,
523     guint property_id,
524     GValue *value,
525     GParamSpec *pspec)
526 {
527   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
528
529   switch (property_id)
530     {
531     case PROP_TLS_CERTIFICATE:
532       g_value_set_object (value, priv->certificate);
533       break;
534     case PROP_HOSTNAME:
535       g_value_set_string (value, priv->hostname);
536       break;
537     default:
538       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
539       break;
540     }
541 }
542
543 static void
544 empathy_tls_verifier_set_property (GObject *object,
545     guint property_id,
546     const GValue *value,
547     GParamSpec *pspec)
548 {
549   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
550
551   switch (property_id)
552     {
553     case PROP_TLS_CERTIFICATE:
554       priv->certificate = g_value_dup_object (value);
555       break;
556     case PROP_HOSTNAME:
557       priv->hostname = g_value_dup_string (value);
558       break;
559     default:
560       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
561       break;
562     }
563 }
564
565 static void
566 empathy_tls_verifier_dispose (GObject *object)
567 {
568   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
569
570   if (priv->dispose_run)
571     return;
572
573   priv->dispose_run = TRUE;
574
575   tp_clear_object (&priv->certificate);
576
577   G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->dispose (object);
578 }
579
580 static void
581 empathy_tls_verifier_finalize (GObject *object)
582 {
583   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
584
585   DEBUG ("%p", object);
586
587   tp_clear_pointer (&priv->trusted_ca_list, g_ptr_array_unref);
588   tp_clear_pointer (&priv->cert_chain, g_ptr_array_unref);
589   tp_clear_boxed (G_TYPE_HASH_TABLE, &priv->details);
590   g_free (priv->hostname);
591
592   G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->finalize (object);
593 }
594
595 static void
596 empathy_tls_verifier_constructed (GObject *object)
597 {
598   EmpathyTLSVerifier *self = EMPATHY_TLS_VERIFIER (object);
599
600   build_gnutls_cert_list (self);
601
602   if (G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->constructed != NULL)
603     G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->constructed (object);
604 }
605
606 static void
607 empathy_tls_verifier_init (EmpathyTLSVerifier *self)
608 {
609   EmpathyTLSVerifierPriv *priv;
610
611   priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
612       EMPATHY_TYPE_TLS_VERIFIER, EmpathyTLSVerifierPriv);
613   priv->details = tp_asv_new (NULL, NULL);
614 }
615
616 static void
617 empathy_tls_verifier_class_init (EmpathyTLSVerifierClass *klass)
618 {
619   GParamSpec *pspec;
620   GObjectClass *oclass = G_OBJECT_CLASS (klass);
621
622   g_type_class_add_private (klass, sizeof (EmpathyTLSVerifierPriv));
623
624   oclass->set_property = empathy_tls_verifier_set_property;
625   oclass->get_property = empathy_tls_verifier_get_property;
626   oclass->finalize = empathy_tls_verifier_finalize;
627   oclass->dispose = empathy_tls_verifier_dispose;
628   oclass->constructed = empathy_tls_verifier_constructed;
629
630   pspec = g_param_spec_object ("certificate", "The EmpathyTLSCertificate",
631       "The EmpathyTLSCertificate to be verified.",
632       EMPATHY_TYPE_TLS_CERTIFICATE,
633       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
634   g_object_class_install_property (oclass, PROP_TLS_CERTIFICATE, pspec);
635
636   pspec = g_param_spec_string ("hostname", "The hostname",
637       "The hostname which should be certified by the certificate.",
638       NULL,
639       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
640   g_object_class_install_property (oclass, PROP_HOSTNAME, pspec);
641 }
642
643 EmpathyTLSVerifier *
644 empathy_tls_verifier_new (EmpathyTLSCertificate *certificate,
645     const gchar *hostname)
646 {
647   g_assert (EMPATHY_IS_TLS_CERTIFICATE (certificate));
648   g_assert (hostname != NULL);
649
650   return g_object_new (EMPATHY_TYPE_TLS_VERIFIER,
651       "certificate", certificate,
652       "hostname", hostname,
653       NULL);
654 }
655
656 void
657 empathy_tls_verifier_verify_async (EmpathyTLSVerifier *self,
658     GAsyncReadyCallback callback,
659     gpointer user_data)
660 {
661   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
662
663   g_return_if_fail (priv->verify_result == NULL);
664
665   priv->verify_result = g_simple_async_result_new (G_OBJECT (self),
666       callback, user_data, NULL);
667
668   g_io_scheduler_push_job (build_gnutls_ca_and_crl_lists,
669       self, NULL, G_PRIORITY_DEFAULT, NULL);
670 }
671
672 gboolean
673 empathy_tls_verifier_verify_finish (EmpathyTLSVerifier *self,
674     GAsyncResult *res,
675     EmpTLSCertificateRejectReason *reason,
676     GHashTable **details,
677     GError **error)
678 {
679   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
680
681   if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res),
682           error))
683     {
684       if (reason != NULL)
685         *reason = (*error)->code;
686
687       if (details != NULL)
688         {
689           *details = tp_asv_new (NULL, NULL);
690           tp_g_hash_table_update (*details, priv->details,
691               (GBoxedCopyFunc) g_strdup,
692               (GBoxedCopyFunc) tp_g_value_slice_dup);
693         }
694
695       return FALSE;
696     }
697
698   if (reason != NULL)
699     *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
700
701   return TRUE;
702 }