]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tls-verifier.c
Merge remote-tracking branch 'jonny/ft'
[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  * @author Stef Walter <stefw@collabora.co.uk>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 #include <config.h>
23
24 #include <gnutls/gnutls.h>
25 #include <gnutls/x509.h>
26
27 #include <telepathy-glib/util.h>
28
29 #include "empathy-tls-verifier.h"
30
31 #include <gcr/gcr.h>
32
33 #define DEBUG_FLAG EMPATHY_DEBUG_TLS
34 #include "empathy-debug.h"
35 #include "empathy-utils.h"
36
37 G_DEFINE_TYPE (EmpathyTLSVerifier, empathy_tls_verifier,
38     G_TYPE_OBJECT)
39
40 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTLSVerifier);
41
42 enum {
43   PROP_TLS_CERTIFICATE = 1,
44   PROP_HOSTNAME,
45   PROP_REFERENCE_IDENTITIES,
46
47   LAST_PROPERTY,
48 };
49
50 typedef struct {
51   EmpathyTLSCertificate *certificate;
52   gchar *hostname;
53   gchar **reference_identities;
54
55   GSimpleAsyncResult *verify_result;
56   GHashTable *details;
57
58   gboolean dispose_run;
59 } EmpathyTLSVerifierPriv;
60
61 static gboolean
62 verification_output_to_reason (gint res,
63     guint verify_output,
64     EmpTLSCertificateRejectReason *reason)
65 {
66   gboolean retval = TRUE;
67
68   g_assert (reason != NULL);
69
70   if (res != GNUTLS_E_SUCCESS)
71     {
72       retval = FALSE;
73
74       /* the certificate is not structurally valid */
75       switch (res)
76         {
77         case GNUTLS_E_INSUFFICIENT_CREDENTIALS:
78           *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED;
79           break;
80         case GNUTLS_E_CONSTRAINT_ERROR:
81           *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_LIMIT_EXCEEDED;
82           break;
83         default:
84           *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
85           break;
86         }
87
88       goto out;
89     }
90
91   /* the certificate is structurally valid, check for other errors. */
92   if (verify_output & GNUTLS_CERT_INVALID)
93     {
94       retval = FALSE;
95
96       if (verify_output & GNUTLS_CERT_SIGNER_NOT_FOUND)
97         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_SELF_SIGNED;
98       else if (verify_output & GNUTLS_CERT_SIGNER_NOT_CA)
99         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED;
100       else if (verify_output & GNUTLS_CERT_INSECURE_ALGORITHM)
101         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_INSECURE;
102       else if (verify_output & GNUTLS_CERT_NOT_ACTIVATED)
103         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_NOT_ACTIVATED;
104       else if (verify_output & GNUTLS_CERT_EXPIRED)
105         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_EXPIRED;
106       else
107         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
108
109       goto out;
110     }
111
112  out:
113   return retval;
114 }
115
116 static void
117 build_certificate_list_for_gnutls (GcrCertificateChain *chain,
118         gnutls_x509_crt_t **list,
119         guint *n_list,
120         gnutls_x509_crt_t **anchors,
121         guint *n_anchors)
122 {
123   GcrCertificate *cert;
124   guint idx, length;
125   gnutls_x509_crt_t *retval;
126   gnutls_x509_crt_t gcert;
127   gnutls_datum_t datum;
128   gsize n_data;
129
130   g_assert (list);
131   g_assert (n_list);
132   g_assert (anchors);
133   g_assert (n_anchors);
134
135   *list = *anchors = NULL;
136   *n_list = *n_anchors = 0;
137
138   length = gcr_certificate_chain_get_length (chain);
139   retval = g_malloc0 (sizeof (gnutls_x509_crt_t) * length);
140
141   /* Convert the main body of the chain to gnutls */
142   for (idx = 0; idx < length; ++idx)
143     {
144       cert = gcr_certificate_chain_get_certificate (chain, idx);
145       datum.data = (gpointer)gcr_certificate_get_der_data (cert, &n_data);
146       datum.size = n_data;
147
148       gnutls_x509_crt_init (&gcert);
149       if (gnutls_x509_crt_import (gcert, &datum, GNUTLS_X509_FMT_DER) < 0)
150         g_return_if_reached ();
151
152       retval[idx] = gcert;
153     }
154
155   *list = retval;
156   *n_list = length;
157
158   /* See if we have an anchor */
159   if (gcr_certificate_chain_get_status (chain) ==
160           GCR_CERTIFICATE_CHAIN_ANCHORED)
161     {
162       cert = gcr_certificate_chain_get_anchor (chain);
163       g_return_if_fail (cert);
164
165       datum.data = (gpointer)gcr_certificate_get_der_data (cert, &n_data);
166       datum.size = n_data;
167
168       gnutls_x509_crt_init (&gcert);
169       if (gnutls_x509_crt_import (gcert, &datum, GNUTLS_X509_FMT_DER) < 0)
170         g_return_if_reached ();
171
172       retval = g_malloc0 (sizeof (gnutls_x509_crt_t) * 1);
173       retval[0] = gcert;
174       *anchors = retval;
175       *n_anchors = 1;
176     }
177 }
178
179 static void
180 free_certificate_list_for_gnutls (gnutls_x509_crt_t *list,
181         guint n_list)
182 {
183   guint idx;
184
185   for (idx = 0; idx < n_list; idx++)
186     gnutls_x509_crt_deinit (list[idx]);
187   g_free (list);
188 }
189
190 static void
191 complete_verification (EmpathyTLSVerifier *self)
192 {
193   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
194
195   DEBUG ("Verification successful, completing...");
196
197   g_simple_async_result_complete_in_idle (priv->verify_result);
198
199   tp_clear_object (&priv->verify_result);
200 }
201
202 static void
203 abort_verification (EmpathyTLSVerifier *self,
204     EmpTLSCertificateRejectReason reason)
205 {
206   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
207
208   DEBUG ("Verification error %u, aborting...", reason);
209
210   g_simple_async_result_set_error (priv->verify_result,
211       G_IO_ERROR, reason, "TLS verification failed with reason %u",
212       reason);
213   g_simple_async_result_complete_in_idle (priv->verify_result);
214
215   tp_clear_object (&priv->verify_result);
216 }
217
218 static void
219 debug_certificate (GcrCertificate *cert)
220 {
221     gchar *subject = gcr_certificate_get_subject_dn (cert);
222     DEBUG ("Certificate: %s", subject);
223     g_free (subject);
224 }
225
226 static void
227 debug_certificate_chain (GcrCertificateChain *chain)
228 {
229     GEnumClass *enum_class;
230     GEnumValue *enum_value;
231     gint idx, length;
232     GcrCertificate *cert;
233
234     enum_class = G_ENUM_CLASS
235             (g_type_class_peek (GCR_TYPE_CERTIFICATE_CHAIN_STATUS));
236     enum_value = g_enum_get_value (enum_class,
237             gcr_certificate_chain_get_status (chain));
238     length = gcr_certificate_chain_get_length (chain);
239     DEBUG ("Certificate chain: length %u status %s",
240             length, enum_value ? enum_value->value_nick : "XXX");
241
242     for (idx = 0; idx < length; ++idx)
243       {
244         cert = gcr_certificate_chain_get_certificate (chain, idx);
245         debug_certificate (cert);
246       }
247 }
248
249 static void
250 perform_verification (EmpathyTLSVerifier *self,
251         GcrCertificateChain *chain)
252 {
253   gboolean ret = FALSE;
254   EmpTLSCertificateRejectReason reason =
255     EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
256   gnutls_x509_crt_t *list, *anchors;
257   guint n_list, n_anchors;
258   guint verify_output;
259   gint res;
260   gint i;
261   gboolean matched = FALSE;
262   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
263
264   DEBUG ("Performing verification");
265   debug_certificate_chain (chain);
266
267   list = anchors = NULL;
268   n_list = n_anchors = 0;
269
270   /*
271    * If the first certificate is an pinned certificate then we completely
272    * ignore the rest of the verification process.
273    */
274   if (gcr_certificate_chain_get_status (chain) == GCR_CERTIFICATE_CHAIN_PINNED)
275     {
276       DEBUG ("Found pinned certificate for %s", priv->hostname);
277       complete_verification (self);
278       goto out;
279   }
280
281   build_certificate_list_for_gnutls (chain, &list, &n_list,
282           &anchors, &n_anchors);
283   if (list == NULL || n_list == 0) {
284       g_warn_if_reached ();
285       abort_verification (self, EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN);
286       goto out;
287   }
288
289   verify_output = 0;
290   res = gnutls_x509_crt_list_verify (list, n_list, anchors, n_anchors,
291            NULL, 0, 0, &verify_output);
292   ret = verification_output_to_reason (res, verify_output, &reason);
293
294   DEBUG ("Certificate verification gave result %d with reason %u", ret,
295           reason);
296
297   if (!ret) {
298       abort_verification (self, reason);
299       goto out;
300   }
301
302   /* now check if the certificate matches one of the reference identities. */
303   if (priv->reference_identities != NULL)
304     {
305       for (i = 0, matched = FALSE; priv->reference_identities[i] != NULL; ++i)
306         {
307           if (gnutls_x509_crt_check_hostname (list[0],
308                   priv->reference_identities[i]) == 1)
309             {
310               matched = TRUE;
311               break;
312             }
313         }
314     }
315
316   if (!matched)
317     {
318       gchar *certified_hostname;
319
320       certified_hostname = empathy_get_x509_certificate_hostname (list[0]);
321       tp_asv_set_string (priv->details,
322           "expected-hostname", priv->hostname);
323       tp_asv_set_string (priv->details,
324           "certificate-hostname", certified_hostname);
325
326       DEBUG ("Hostname mismatch: got %s but expected %s",
327           certified_hostname, priv->hostname);
328
329       g_free (certified_hostname);
330       abort_verification (self,
331               EMP_TLS_CERTIFICATE_REJECT_REASON_HOSTNAME_MISMATCH);
332       goto out;
333     }
334
335   DEBUG ("Hostname matched");
336   complete_verification (self);
337
338  out:
339   free_certificate_list_for_gnutls (list, n_list);
340   free_certificate_list_for_gnutls (anchors, n_anchors);
341 }
342
343 static void
344 perform_verification_cb (GObject *object,
345         GAsyncResult *res,
346         gpointer user_data)
347 {
348   GError *error = NULL;
349
350   GcrCertificateChain *chain = GCR_CERTIFICATE_CHAIN (object);
351   EmpathyTLSVerifier *self = EMPATHY_TLS_VERIFIER (user_data);
352
353   /* Even if building the chain fails, try verifying what we have */
354   if (!gcr_certificate_chain_build_finish (chain, res, &error))
355     {
356       DEBUG ("Building of certificate chain failed: %s", error->message);
357       g_clear_error (&error);
358     }
359
360   perform_verification (self, chain);
361
362   /* Matches ref when staring chain build */
363   g_object_unref (self);
364 }
365
366 static void
367 empathy_tls_verifier_get_property (GObject *object,
368     guint property_id,
369     GValue *value,
370     GParamSpec *pspec)
371 {
372   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
373
374   switch (property_id)
375     {
376     case PROP_TLS_CERTIFICATE:
377       g_value_set_object (value, priv->certificate);
378       break;
379     case PROP_HOSTNAME:
380       g_value_set_string (value, priv->hostname);
381       break;
382     case PROP_REFERENCE_IDENTITIES:
383       g_value_set_boxed (value, priv->reference_identities);
384       break;
385     default:
386       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
387       break;
388     }
389 }
390
391 static void
392 empathy_tls_verifier_set_property (GObject *object,
393     guint property_id,
394     const GValue *value,
395     GParamSpec *pspec)
396 {
397   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
398
399   switch (property_id)
400     {
401     case PROP_TLS_CERTIFICATE:
402       priv->certificate = g_value_dup_object (value);
403       break;
404     case PROP_HOSTNAME:
405       priv->hostname = g_value_dup_string (value);
406       break;
407     case PROP_REFERENCE_IDENTITIES:
408       priv->reference_identities = g_value_dup_boxed (value);
409       break;
410     default:
411       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
412       break;
413     }
414 }
415
416 static void
417 empathy_tls_verifier_dispose (GObject *object)
418 {
419   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
420
421   if (priv->dispose_run)
422     return;
423
424   priv->dispose_run = TRUE;
425
426   tp_clear_object (&priv->certificate);
427
428   G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->dispose (object);
429 }
430
431 static void
432 empathy_tls_verifier_finalize (GObject *object)
433 {
434   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
435
436   DEBUG ("%p", object);
437
438   tp_clear_boxed (G_TYPE_HASH_TABLE, &priv->details);
439   g_free (priv->hostname);
440   g_strfreev (priv->reference_identities);
441
442   G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->finalize (object);
443 }
444
445 static void
446 empathy_tls_verifier_init (EmpathyTLSVerifier *self)
447 {
448   EmpathyTLSVerifierPriv *priv;
449
450   priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
451       EMPATHY_TYPE_TLS_VERIFIER, EmpathyTLSVerifierPriv);
452   priv->details = tp_asv_new (NULL, NULL);
453 }
454
455 static void
456 empathy_tls_verifier_class_init (EmpathyTLSVerifierClass *klass)
457 {
458   GParamSpec *pspec;
459   GObjectClass *oclass = G_OBJECT_CLASS (klass);
460
461   g_type_class_add_private (klass, sizeof (EmpathyTLSVerifierPriv));
462
463   oclass->set_property = empathy_tls_verifier_set_property;
464   oclass->get_property = empathy_tls_verifier_get_property;
465   oclass->finalize = empathy_tls_verifier_finalize;
466   oclass->dispose = empathy_tls_verifier_dispose;
467
468   pspec = g_param_spec_object ("certificate", "The EmpathyTLSCertificate",
469       "The EmpathyTLSCertificate to be verified.",
470       EMPATHY_TYPE_TLS_CERTIFICATE,
471       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
472   g_object_class_install_property (oclass, PROP_TLS_CERTIFICATE, pspec);
473
474   pspec = g_param_spec_string ("hostname", "The hostname",
475       "The hostname which is certified by the certificate.",
476       NULL,
477       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
478   g_object_class_install_property (oclass, PROP_HOSTNAME, pspec);
479
480   pspec = g_param_spec_boxed ("reference-identities",
481       "The reference identities",
482       "The certificate should certify one of these identities.",
483       G_TYPE_STRV,
484       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
485   g_object_class_install_property (oclass, PROP_REFERENCE_IDENTITIES, pspec);
486 }
487
488 EmpathyTLSVerifier *
489 empathy_tls_verifier_new (EmpathyTLSCertificate *certificate,
490     const gchar *hostname, const gchar **reference_identities)
491 {
492   g_assert (EMPATHY_IS_TLS_CERTIFICATE (certificate));
493   g_assert (hostname != NULL);
494   g_assert (reference_identities != NULL);
495
496   return g_object_new (EMPATHY_TYPE_TLS_VERIFIER,
497       "certificate", certificate,
498       "hostname", hostname,
499       "reference-identities", reference_identities,
500       NULL);
501 }
502
503 void
504 empathy_tls_verifier_verify_async (EmpathyTLSVerifier *self,
505     GAsyncReadyCallback callback,
506     gpointer user_data)
507 {
508   GcrCertificateChain *chain;
509   GcrCertificate *cert;
510   GPtrArray *cert_data = NULL;
511   GArray *data;
512   guint idx;
513   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
514
515   DEBUG ("Starting verification");
516
517   g_return_if_fail (priv->verify_result == NULL);
518
519   g_object_get (priv->certificate, "cert-data", &cert_data, NULL);
520   g_return_if_fail (cert_data);
521
522   priv->verify_result = g_simple_async_result_new (G_OBJECT (self),
523       callback, user_data, NULL);
524
525   /* Create a certificate chain */
526   chain = gcr_certificate_chain_new ();
527   for (idx = 0; idx < cert_data->len; ++idx) {
528     data = g_ptr_array_index (cert_data, idx);
529     cert = gcr_simple_certificate_new ((guchar *) data->data, data->len);
530     gcr_certificate_chain_add (chain, cert);
531     g_object_unref (cert);
532   }
533
534   gcr_certificate_chain_build_async (chain, GCR_PURPOSE_CLIENT_AUTH, priv->hostname, 0,
535           NULL, perform_verification_cb, g_object_ref (self));
536
537   g_object_unref (chain);
538   g_boxed_free (TP_ARRAY_TYPE_UCHAR_ARRAY_LIST, cert_data);
539 }
540
541 gboolean
542 empathy_tls_verifier_verify_finish (EmpathyTLSVerifier *self,
543     GAsyncResult *res,
544     EmpTLSCertificateRejectReason *reason,
545     GHashTable **details,
546     GError **error)
547 {
548   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
549
550   if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res),
551           error))
552     {
553       if (reason != NULL)
554         *reason = (*error)->code;
555
556       if (details != NULL)
557         {
558           *details = tp_asv_new (NULL, NULL);
559           tp_g_hash_table_update (*details, priv->details,
560               (GBoxedCopyFunc) g_strdup,
561               (GBoxedCopyFunc) tp_g_value_slice_dup);
562         }
563
564       return FALSE;
565     }
566
567   if (reason != NULL)
568     *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
569
570   return TRUE;
571 }
572
573 void
574 empathy_tls_verifier_store_exception (EmpathyTLSVerifier *self)
575 {
576   GArray *data;
577   GcrCertificate *cert;
578   GPtrArray *cert_data = NULL;
579   GError *error = NULL;
580   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
581
582   g_object_get (priv->certificate, "cert-data", &cert_data, NULL);
583   g_return_if_fail (cert_data);
584
585   if (!cert_data->len)
586     {
587       DEBUG ("No certificate to pin.");
588       return;
589     }
590
591   /* The first certificate in the chain is for the host */
592   data = g_ptr_array_index (cert_data, 0);
593   cert = gcr_simple_certificate_new ((gpointer)data->data, data->len);
594
595   DEBUG ("Storing pinned certificate:");
596   debug_certificate (cert);
597
598   if (!gcr_trust_add_pinned_certificate (cert, GCR_PURPOSE_CLIENT_AUTH,
599           priv->hostname, NULL, &error))
600       DEBUG ("Can't store the pinned certificate: %s", error->message);
601
602   g_object_unref (cert);
603   g_boxed_free (TP_ARRAY_TYPE_UCHAR_ARRAY_LIST, cert_data);
604 }