]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tls-verifier.c
Implement hostname checking
[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 gchar *
217 get_certified_hostname (gnutls_x509_crt_t cert)
218 {
219   gchar dns_name[256];
220   gsize dns_name_size;
221   gint idx;
222   gint res = 0;
223
224   /* this is taken from GnuTLS */
225   for (idx = 0; res >= 0; idx++)
226     {
227       dns_name_size = sizeof (dns_name);
228       res = gnutls_x509_crt_get_subject_alt_name (cert, idx,
229           dns_name, &dns_name_size, NULL);
230
231       if (res == GNUTLS_SAN_DNSNAME || res == GNUTLS_SAN_IPADDRESS)
232         return g_strndup (dns_name, dns_name_size);
233     }
234
235   dns_name_size = sizeof (dns_name);
236   res = gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME,
237       0, 0, dns_name, &dns_name_size);
238
239   if (res >= 0)
240     return g_strndup (dns_name, dns_name_size);
241
242   return NULL;
243 }
244
245 static void
246 real_start_verification (EmpathyTLSVerifier *self)
247 {
248   gnutls_x509_crt_t first_cert, last_cert;
249   gint idx;
250   gboolean res = FALSE;
251   gint num_certs;
252   EmpTLSCertificateRejectReason reason =
253     EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
254   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
255
256   DEBUG ("Starting verification");
257
258   /* check if the certificate matches the hostname first. */
259   first_cert = g_ptr_array_index (priv->cert_chain, 0);
260   if (gnutls_x509_crt_check_hostname (first_cert, priv->hostname) == 0)
261     {
262       gchar *certified_hostname;
263
264       certified_hostname = get_certified_hostname (first_cert);
265       DEBUG ("Hostname mismatch: got %s but expected %s",
266           certified_hostname, priv->hostname);
267
268       /* TODO: pass-through the expected hostname in the reject details */
269       reason = EMP_TLS_CERTIFICATE_REJECT_REASON_HOSTNAME_MISMATCH;
270
271       g_free (certified_hostname);
272       goto out;
273     }
274
275   DEBUG ("Hostname matched");
276
277   num_certs = priv->cert_chain->len;
278
279   if (priv->trusted_ca_list->len > 0)
280     {
281       /* if the last certificate is self-signed, and we have a list of
282        * trusted CAs, ignore it, as we want to check the chain against our
283        * trusted CAs list first.
284        */
285       last_cert = g_ptr_array_index (priv->cert_chain, num_certs - 1);
286
287       if (gnutls_x509_crt_check_issuer (last_cert, last_cert) > 0)
288         num_certs--;
289     }
290
291   for (idx = 1; idx < num_certs; idx++)
292     {
293       res = verify_certificate (self,
294           g_ptr_array_index (priv->cert_chain, idx -1),
295           g_ptr_array_index (priv->cert_chain, idx),
296           &reason);
297
298       DEBUG ("Certificate verification %d gave result %d with reason %u", idx,
299           res, reason);
300
301       if (!res)
302         {
303           abort_verification (self, reason);
304           return;
305         }
306     }
307
308   res = verify_last_certificate (self,
309       g_ptr_array_index (priv->cert_chain, num_certs - 1),
310       &reason);
311
312   DEBUG ("Last verification gave result %d with reason %u", res, reason);
313
314  out:
315   if (!res)
316     {
317       abort_verification (self, reason);
318       return;
319     }
320
321   complete_verification (self);
322 }
323
324 static gboolean
325 start_verification (gpointer user_data)
326 {
327   EmpathyTLSVerifier *self = user_data;
328
329   real_start_verification (self);
330
331   return FALSE;
332 }
333
334 static void
335 build_gnutls_cert_list (EmpathyTLSVerifier *self)
336 {
337   guint num_certs;
338   guint idx;
339   GPtrArray *certificate_data = NULL;
340   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
341
342   g_object_get (priv->certificate,
343       "cert-data", &certificate_data,
344       NULL);
345   num_certs = certificate_data->len;
346
347   priv->cert_chain = g_ptr_array_sized_new (num_certs);
348
349   for (idx = 0; idx < num_certs; idx++)
350     {
351       gnutls_x509_crt_t cert;
352       GArray *one_cert;
353       gnutls_datum_t datum = { NULL, 0 };
354
355       one_cert = g_ptr_array_index (certificate_data, idx);
356       datum.data = (guchar *) one_cert->data;
357       datum.size = one_cert->len;
358
359       gnutls_x509_crt_init (&cert);
360       gnutls_x509_crt_import (cert, &datum, GNUTLS_X509_FMT_DER);
361
362       g_ptr_array_add (priv->cert_chain, cert);
363     }
364 }
365
366 static gint
367 get_number_and_type_of_certificates (gnutls_datum_t *datum,
368     gnutls_x509_crt_fmt_t *format)
369 {
370   gnutls_x509_crt_t fake;
371   gint retval = 1;
372   gint res;
373
374   res = gnutls_x509_crt_list_import (&fake, (guint *) &retval, datum,
375       GNUTLS_X509_FMT_PEM, 0);
376
377   if (res == GNUTLS_E_SHORT_MEMORY_BUFFER || res > 0)
378     {
379       *format = GNUTLS_X509_FMT_PEM;
380       return retval;
381     }
382
383   /* try DER */
384   res = gnutls_x509_crt_list_import (&fake, (guint *) &retval, datum,
385       GNUTLS_X509_FMT_DER, 0);
386
387   if (res > 0)
388     {
389       *format = GNUTLS_X509_FMT_DER;
390       return retval;
391     }
392
393   return res;
394 }
395
396 static gboolean
397 build_gnutls_ca_and_crl_lists (GIOSchedulerJob *job,
398     GCancellable *cancellable,
399     gpointer user_data)
400 {
401   gint idx;
402   EmpathyTLSVerifier *self = user_data;
403   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
404
405   priv->trusted_ca_list = g_ptr_array_new ();
406
407   for (idx = 0; idx < (gint) G_N_ELEMENTS (system_ca_paths) - 1; idx++)
408     {
409       const gchar *path;
410       gchar *contents = NULL;
411       gsize length = 0;
412       gint res, n_certs;
413       gnutls_x509_crt_t *cert_list;
414       gnutls_datum_t datum = { NULL, 0 };
415       gnutls_x509_crt_fmt_t format = 0;
416       GError *error = NULL;
417
418       path = system_ca_paths[idx];
419       g_file_get_contents (path, &contents, &length, &error);
420
421       if (error != NULL)
422         {
423           DEBUG ("Unable to read system CAs from path %s", path);
424           g_error_free (error);
425           continue;
426         }
427
428       datum.data = (guchar *) contents;
429       datum.size = length;
430       n_certs = get_number_and_type_of_certificates (&datum, &format);
431
432       if (n_certs < 0)
433         {
434           DEBUG ("Unable to parse the system CAs from path %s: GnuTLS "
435               "returned error %d", path, n_certs);
436
437           g_free (contents);
438           continue;
439         }
440
441       cert_list = g_malloc0 (sizeof (gnutls_x509_crt_t) * n_certs);
442       res = gnutls_x509_crt_list_import (cert_list, (guint *) &n_certs, &datum,
443           format, 0);
444
445       if (res < 0)
446         {
447           DEBUG ("Unable to import system CAs from path %s; "
448               "GnuTLS returned error %d", path, res);
449
450           g_free (contents);
451           continue;
452         }
453
454       DEBUG ("Successfully imported %d system CA certificates from path %s",
455           n_certs, path);
456
457       /* append the newly created cert structutes into the global GPtrArray */
458       for (idx = 0; idx < n_certs; idx++)
459         g_ptr_array_add (priv->trusted_ca_list, cert_list[idx]);
460
461       g_free (contents);
462     }
463
464   /* TODO: do the CRL too */
465
466   g_io_scheduler_job_send_to_mainloop_async (job,
467       start_verification, self, NULL);
468
469   return FALSE;
470 }
471
472 static void
473 empathy_tls_verifier_get_property (GObject *object,
474     guint property_id,
475     GValue *value,
476     GParamSpec *pspec)
477 {
478   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
479
480   switch (property_id)
481     {
482     case PROP_TLS_CERTIFICATE:
483       g_value_set_object (value, priv->certificate);
484       break;
485     case PROP_HOSTNAME:
486       g_value_set_string (value, priv->hostname);
487       break;
488     default:
489       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
490       break;
491     }
492 }
493
494 static void
495 empathy_tls_verifier_set_property (GObject *object,
496     guint property_id,
497     const GValue *value,
498     GParamSpec *pspec)
499 {
500   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
501
502   switch (property_id)
503     {
504     case PROP_TLS_CERTIFICATE:
505       priv->certificate = g_value_dup_object (value);
506       break;
507     case PROP_HOSTNAME:
508       priv->hostname = g_value_dup_string (value);
509       break;
510     default:
511       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
512       break;
513     }
514 }
515
516 static void
517 empathy_tls_verifier_dispose (GObject *object)
518 {
519   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
520
521   if (priv->dispose_run)
522     return;
523
524   priv->dispose_run = TRUE;
525
526   tp_clear_object (&priv->certificate);
527
528   G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->dispose (object);
529 }
530
531 static void
532 empathy_tls_verifier_finalize (GObject *object)
533 {
534   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
535
536   DEBUG ("%p", object);
537   
538   g_free (priv->hostname);
539
540   G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->finalize (object);
541 }
542
543 static void
544 empathy_tls_verifier_constructed (GObject *object)
545 {
546   EmpathyTLSVerifier *self = EMPATHY_TLS_VERIFIER (object);
547
548   build_gnutls_cert_list (self);
549   
550   if (G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->constructed != NULL)
551     G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->constructed (object);
552 }
553
554 static void
555 empathy_tls_verifier_init (EmpathyTLSVerifier *self)
556 {
557   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
558       EMPATHY_TYPE_TLS_VERIFIER, EmpathyTLSVerifierPriv);
559 }
560
561 static void
562 empathy_tls_verifier_class_init (EmpathyTLSVerifierClass *klass)
563 {
564   GParamSpec *pspec;
565   GObjectClass *oclass = G_OBJECT_CLASS (klass);
566
567   g_type_class_add_private (klass, sizeof (EmpathyTLSVerifierPriv));
568
569   oclass->set_property = empathy_tls_verifier_set_property;
570   oclass->get_property = empathy_tls_verifier_get_property;
571   oclass->finalize = empathy_tls_verifier_finalize;
572   oclass->dispose = empathy_tls_verifier_dispose;
573   oclass->constructed = empathy_tls_verifier_constructed;
574
575   pspec = g_param_spec_object ("certificate", "The EmpathyTLSCertificate",
576       "The EmpathyTLSCertificate to be verified.",
577       EMPATHY_TYPE_TLS_CERTIFICATE,
578       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
579   g_object_class_install_property (oclass, PROP_TLS_CERTIFICATE, pspec);
580
581   pspec = g_param_spec_string ("hostname", "The hostname",
582       "The hostname which should be certified by the certificate.",
583       NULL,
584       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
585   g_object_class_install_property (oclass, PROP_HOSTNAME, pspec);
586 }
587
588 EmpathyTLSVerifier *
589 empathy_tls_verifier_new (EmpathyTLSCertificate *certificate,
590     const gchar *hostname)
591 {
592   g_assert (EMPATHY_IS_TLS_CERTIFICATE (certificate));
593   g_assert (hostname != NULL);
594
595   return g_object_new (EMPATHY_TYPE_TLS_VERIFIER,
596       "certificate", certificate,
597       "hostname", hostname,
598       NULL);
599 }
600
601 void
602 empathy_tls_verifier_verify_async (EmpathyTLSVerifier *self,
603     GAsyncReadyCallback callback,
604     gpointer user_data)
605 {
606   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
607
608   priv->verify_result = g_simple_async_result_new (G_OBJECT (self),
609       callback, user_data, NULL);
610
611   g_io_scheduler_push_job (build_gnutls_ca_and_crl_lists,
612       self, NULL, G_PRIORITY_DEFAULT, NULL);
613 }
614
615 gboolean
616 empathy_tls_verifier_verify_finish (EmpathyTLSVerifier *self,
617     GAsyncResult *res,
618     EmpTLSCertificateRejectReason *reason,
619     GError **error)
620 {
621   if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res),
622           error))
623     {
624       *reason = (*error)->code;
625       return FALSE;
626     }
627
628   *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
629   return TRUE;
630 }