]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tls-verifier.c
Correctly treat the last certificate in the chain
[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
62   gboolean dispose_run;
63 } EmpathyTLSVerifierPriv;
64
65 static gnutls_x509_crt_t *
66 ptr_array_to_x509_crt_list (GPtrArray *chain)
67 {
68   gnutls_x509_crt_t *retval;
69   gint idx;
70
71   retval = g_malloc0 (sizeof (gnutls_x509_crt_t) * chain->len);
72
73   for (idx = 0; idx < (gint) chain->len; idx++)
74     retval[idx] = g_ptr_array_index (chain, idx);
75
76   return retval;
77 }
78
79 static gboolean
80 verification_output_to_reason (gint res,
81     guint verify_output,
82     EmpTLSCertificateRejectReason *reason)
83 {
84   gboolean retval = TRUE;
85
86   if (res != GNUTLS_E_SUCCESS)
87     {
88       retval = FALSE;
89
90       /* the certificate is not structurally valid */
91       switch (res)
92         {
93         case GNUTLS_E_INSUFFICIENT_CREDENTIALS:
94           *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED;
95           break;
96         case GNUTLS_E_CONSTRAINT_ERROR:
97           *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_LIMIT_EXCEEDED;
98           break;
99         default:
100           *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
101           break;
102         }
103
104       goto out;
105     }
106
107   /* the certificate is structurally valid, check for other errors. */
108   if (verify_output & GNUTLS_CERT_INVALID)
109     {
110       retval = FALSE;
111
112       if (verify_output & GNUTLS_CERT_SIGNER_NOT_FOUND)
113         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_SELF_SIGNED;
114       else if (verify_output & GNUTLS_CERT_SIGNER_NOT_CA)
115         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED;
116       else if (verify_output & GNUTLS_CERT_INSECURE_ALGORITHM)
117         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_INSECURE;
118       else if (verify_output & GNUTLS_CERT_NOT_ACTIVATED)
119         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_NOT_ACTIVATED;
120       else if (verify_output & GNUTLS_CERT_EXPIRED)
121         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_EXPIRED;
122       else
123         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
124
125       goto out;
126     }
127
128  out:
129   return retval;
130 }
131
132 static gboolean
133 verify_last_certificate (EmpathyTLSVerifier *self,
134     gnutls_x509_crt_t cert,
135     EmpTLSCertificateRejectReason *reason)
136 {
137   guint verify_output;
138   gint res;
139   gnutls_x509_crt_t *trusted_ca_list;
140   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
141
142   if (priv->trusted_ca_list->len > 0)
143     {
144       trusted_ca_list = ptr_array_to_x509_crt_list (priv->trusted_ca_list);
145       res = gnutls_x509_crt_verify (cert, trusted_ca_list,
146           priv->trusted_ca_list->len, 0, &verify_output);
147
148       DEBUG ("Checking last certificate %p against trusted CAs, output %u",
149           cert, verify_output);
150
151       g_free (trusted_ca_list);
152     }
153   else
154     {
155       /* check it against itself to see if it's structurally valid */
156       res = gnutls_x509_crt_verify (cert, &cert, 1, 0, &verify_output);
157
158       DEBUG ("Checking last certificate %p against itself, output %u", cert,
159           verify_output);
160
161       /* if it's valid, return the SelfSigned error, so that we can add it
162        * later to our trusted CAs whitelist.
163        */
164       if (res == GNUTLS_E_SUCCESS)
165         {
166           *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_SELF_SIGNED;
167           return FALSE;
168         }
169     }
170
171   return verification_output_to_reason (res, verify_output, reason);
172 }
173
174 static gboolean
175 verify_certificate (EmpathyTLSVerifier *self,
176     gnutls_x509_crt_t cert,
177     gnutls_x509_crt_t issuer,
178     EmpTLSCertificateRejectReason *reason)
179 {
180   guint verify_output;
181   gint res;
182
183   res = gnutls_x509_crt_verify (cert, &issuer, 1, 0, &verify_output);
184
185   return verification_output_to_reason (res, verify_output, reason);
186 }
187
188 static void
189 complete_verification (EmpathyTLSVerifier *self)
190 {
191   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
192
193   DEBUG ("Verification successful, completing...");
194
195   g_simple_async_result_complete_in_idle (priv->verify_result);
196
197   tp_clear_object (&priv->verify_result);  
198 }
199
200 static void
201 abort_verification (EmpathyTLSVerifier *self,
202     EmpTLSCertificateRejectReason reason)
203 {
204   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
205
206   DEBUG ("Verification error %u, aborting...", reason);
207
208   g_simple_async_result_set_error (priv->verify_result,
209       G_IO_ERROR, reason, "TLS verification failed with reason %u",
210       reason);
211   g_simple_async_result_complete_in_idle (priv->verify_result);
212
213   tp_clear_object (&priv->verify_result);
214 }
215
216 static void
217 real_start_verification (EmpathyTLSVerifier *self)
218 {
219   gnutls_x509_crt_t last_cert;
220   gint idx;
221   gboolean res = FALSE;
222   gint num_certs;
223   EmpTLSCertificateRejectReason reason =
224     EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
225   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
226
227   num_certs = priv->cert_chain->len;
228
229   DEBUG ("Starting verification");
230
231   if (priv->trusted_ca_list->len > 0)
232     {
233       /* if the last certificate is self-signed, ignore it, as we want to check
234        * the chain against our trusted CA list first.
235        */
236       last_cert = g_ptr_array_index (priv->cert_chain, num_certs - 1);
237
238       if (gnutls_x509_crt_check_issuer (last_cert, last_cert) > 0)
239         num_certs--;
240     }
241
242   for (idx = 1; idx < num_certs; idx++)
243     {
244       res = verify_certificate (self,
245           g_ptr_array_index (priv->cert_chain, idx -1),
246           g_ptr_array_index (priv->cert_chain, idx),
247           &reason);
248
249       DEBUG ("Certificate verification %d gave result %d with reason %u", idx,
250           res, reason);
251
252       if (!res)
253         {
254           abort_verification (self, reason);
255           return;
256         }
257     }
258
259   res = verify_last_certificate (self,
260       g_ptr_array_index (priv->cert_chain, num_certs - 1),
261       &reason);
262
263   DEBUG ("Last verification gave result %d with reason %u", res, reason);
264
265  out:
266   if (!res)
267     {
268       abort_verification (self, reason);
269       return;
270     }
271
272   complete_verification (self);
273 }
274
275 static gboolean
276 start_verification (gpointer user_data)
277 {
278   EmpathyTLSVerifier *self = user_data;
279
280   real_start_verification (self);
281
282   return FALSE;
283 }
284
285 static void
286 build_gnutls_cert_list (EmpathyTLSVerifier *self)
287 {
288   guint num_certs;
289   guint idx;
290   GPtrArray *certificate_data = NULL;
291   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
292
293   g_object_get (priv->certificate,
294       "cert-data", &certificate_data,
295       NULL);
296   num_certs = certificate_data->len;
297
298   priv->cert_chain = g_ptr_array_sized_new (num_certs);
299
300   for (idx = 0; idx < num_certs; idx++)
301     {
302       gnutls_x509_crt_t cert;
303       GArray *one_cert;
304       gnutls_datum_t datum = { NULL, 0 };
305
306       one_cert = g_ptr_array_index (certificate_data, idx);
307       datum.data = (guchar *) one_cert->data;
308       datum.size = one_cert->len;
309
310       gnutls_x509_crt_init (&cert);
311       gnutls_x509_crt_import (cert, &datum, GNUTLS_X509_FMT_DER);
312
313       g_ptr_array_add (priv->cert_chain, cert);
314     }
315 }
316
317 static gint
318 get_number_and_type_of_certificates (gnutls_datum_t *datum,
319     gnutls_x509_crt_fmt_t *format)
320 {
321   gnutls_x509_crt_t fake;
322   gint retval = 1;
323   gint res;
324
325   res = gnutls_x509_crt_list_import (&fake, (guint *) &retval, datum,
326       GNUTLS_X509_FMT_PEM, 0);
327
328   if (res == GNUTLS_E_SHORT_MEMORY_BUFFER || res > 0)
329     {
330       *format = GNUTLS_X509_FMT_PEM;
331       return retval;
332     }
333
334   /* try DER */
335   res = gnutls_x509_crt_list_import (&fake, (guint *) &retval, datum,
336       GNUTLS_X509_FMT_DER, 0);
337
338   if (res > 0)
339     {
340       *format = GNUTLS_X509_FMT_DER;
341       return retval;
342     }
343
344   return res;
345 }
346
347 static gboolean
348 build_gnutls_ca_and_crl_lists (GIOSchedulerJob *job,
349     GCancellable *cancellable,
350     gpointer user_data)
351 {
352   gint idx;
353   EmpathyTLSVerifier *self = user_data;
354   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
355
356   priv->trusted_ca_list = g_ptr_array_new ();
357
358   for (idx = 0; idx < (gint) G_N_ELEMENTS (system_ca_paths) - 1; idx++)
359     {
360       const gchar *path;
361       gchar *contents = NULL;
362       gsize length = 0;
363       gint res, n_certs;
364       gnutls_x509_crt_t *cert_list;
365       gnutls_datum_t datum = { NULL, 0 };
366       gnutls_x509_crt_fmt_t format = 0;
367       GError *error = NULL;
368
369       path = system_ca_paths[idx];
370       g_file_get_contents (path, &contents, &length, &error);
371
372       if (error != NULL)
373         {
374           DEBUG ("Unable to read system CAs from path %s", path);
375           g_error_free (error);
376           continue;
377         }
378
379       datum.data = (guchar *) contents;
380       datum.size = length;
381       n_certs = get_number_and_type_of_certificates (&datum, &format);
382
383       if (n_certs < 0)
384         {
385           DEBUG ("Unable to parse the system CAs from path %s: GnuTLS "
386               "returned error %d", path, n_certs);
387
388           g_free (contents);
389           continue;
390         }
391
392       cert_list = g_malloc0 (sizeof (gnutls_x509_crt_t) * n_certs);
393       res = gnutls_x509_crt_list_import (cert_list, (guint *) &n_certs, &datum,
394           format, 0);
395
396       if (res < 0)
397         {
398           DEBUG ("Unable to import system CAs from path %s; "
399               "GnuTLS returned error %d", path, res);
400
401           g_free (contents);
402           continue;
403         }
404
405       DEBUG ("Successfully imported %d system CA certificates from path %s",
406           n_certs, path);
407
408       /* append the newly created cert structutes into the global GPtrArray */
409       for (idx = 0; idx < n_certs; idx++)
410         g_ptr_array_add (priv->trusted_ca_list, cert_list[idx]);
411
412       g_free (contents);
413     }
414
415   /* TODO: do the CRL too */
416
417   g_io_scheduler_job_send_to_mainloop_async (job,
418       start_verification, self, NULL);
419
420   return FALSE;
421 }
422
423 static void
424 empathy_tls_verifier_get_property (GObject *object,
425     guint property_id,
426     GValue *value,
427     GParamSpec *pspec)
428 {
429   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
430
431   switch (property_id)
432     {
433     case PROP_TLS_CERTIFICATE:
434       g_value_set_object (value, priv->certificate);
435       break;
436     case PROP_HOSTNAME:
437       g_value_set_string (value, priv->hostname);
438       break;
439     default:
440       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
441       break;
442     }
443 }
444
445 static void
446 empathy_tls_verifier_set_property (GObject *object,
447     guint property_id,
448     const GValue *value,
449     GParamSpec *pspec)
450 {
451   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
452
453   switch (property_id)
454     {
455     case PROP_TLS_CERTIFICATE:
456       priv->certificate = g_value_dup_object (value);
457       break;
458     case PROP_HOSTNAME:
459       priv->hostname = g_value_dup_string (value);
460       break;
461     default:
462       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
463       break;
464     }
465 }
466
467 static void
468 empathy_tls_verifier_dispose (GObject *object)
469 {
470   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
471
472   if (priv->dispose_run)
473     return;
474
475   priv->dispose_run = TRUE;
476
477   tp_clear_object (&priv->certificate);
478
479   G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->dispose (object);
480 }
481
482 static void
483 empathy_tls_verifier_finalize (GObject *object)
484 {
485   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
486
487   DEBUG ("%p", object);
488   
489   g_free (priv->hostname);
490
491   G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->finalize (object);
492 }
493
494 static void
495 empathy_tls_verifier_constructed (GObject *object)
496 {
497   EmpathyTLSVerifier *self = EMPATHY_TLS_VERIFIER (object);
498
499   build_gnutls_cert_list (self);
500   
501   if (G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->constructed != NULL)
502     G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->constructed (object);
503 }
504
505 static void
506 empathy_tls_verifier_init (EmpathyTLSVerifier *self)
507 {
508   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
509       EMPATHY_TYPE_TLS_VERIFIER, EmpathyTLSVerifierPriv);
510 }
511
512 static void
513 empathy_tls_verifier_class_init (EmpathyTLSVerifierClass *klass)
514 {
515   GParamSpec *pspec;
516   GObjectClass *oclass = G_OBJECT_CLASS (klass);
517
518   g_type_class_add_private (klass, sizeof (EmpathyTLSVerifierPriv));
519
520   oclass->set_property = empathy_tls_verifier_set_property;
521   oclass->get_property = empathy_tls_verifier_get_property;
522   oclass->finalize = empathy_tls_verifier_finalize;
523   oclass->dispose = empathy_tls_verifier_dispose;
524   oclass->constructed = empathy_tls_verifier_constructed;
525
526   pspec = g_param_spec_object ("certificate", "The EmpathyTLSCertificate",
527       "The EmpathyTLSCertificate to be verified.",
528       EMPATHY_TYPE_TLS_CERTIFICATE,
529       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
530   g_object_class_install_property (oclass, PROP_TLS_CERTIFICATE, pspec);
531
532   pspec = g_param_spec_string ("hostname", "The hostname",
533       "The hostname which should be certified by the certificate.",
534       NULL,
535       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
536   g_object_class_install_property (oclass, PROP_HOSTNAME, pspec);
537 }
538
539 EmpathyTLSVerifier *
540 empathy_tls_verifier_new (EmpathyTLSCertificate *certificate,
541     const gchar *hostname)
542 {
543   g_assert (EMPATHY_IS_TLS_CERTIFICATE (certificate));
544   g_assert (hostname != NULL);
545
546   return g_object_new (EMPATHY_TYPE_TLS_VERIFIER,
547       "certificate", certificate,
548       "hostname", hostname,
549       NULL);
550 }
551
552 void
553 empathy_tls_verifier_verify_async (EmpathyTLSVerifier *self,
554     GAsyncReadyCallback callback,
555     gpointer user_data)
556 {
557   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
558
559   priv->verify_result = g_simple_async_result_new (G_OBJECT (self),
560       callback, user_data, NULL);
561
562   g_io_scheduler_push_job (build_gnutls_ca_and_crl_lists,
563       self, NULL, G_PRIORITY_DEFAULT, NULL);
564 }
565
566 gboolean
567 empathy_tls_verifier_verify_finish (EmpathyTLSVerifier *self,
568     GAsyncResult *res,
569     EmpTLSCertificateRejectReason *reason,
570     GError **error)
571 {
572   if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res),
573           error))
574     {
575       *reason = (*error)->code;
576       return FALSE;
577     }
578
579   *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
580   return TRUE;
581 }