]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tls-verifier.c
libempathy: Store certificate exceptions in gnome-keyring.
[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-simple-certificate.h>
32 #include <gcr/gcr-trust.h>
33
34 #define DEBUG_FLAG EMPATHY_DEBUG_TLS
35 #include "empathy-debug.h"
36 #include "empathy-utils.h"
37
38 G_DEFINE_TYPE (EmpathyTLSVerifier, empathy_tls_verifier,
39     G_TYPE_OBJECT)
40
41 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTLSVerifier);
42
43 enum {
44   PROP_TLS_CERTIFICATE = 1,
45   PROP_HOSTNAME,
46
47   LAST_PROPERTY,
48 };
49
50 typedef struct {
51   EmpathyTLSCertificate *certificate;
52   gchar *hostname;
53
54   GSimpleAsyncResult *verify_result;
55   GHashTable *details;
56
57   gboolean dispose_run;
58 } EmpathyTLSVerifierPriv;
59
60 static gboolean
61 verification_output_to_reason (gint res,
62     guint verify_output,
63     EmpTLSCertificateRejectReason *reason)
64 {
65   gboolean retval = TRUE;
66
67   g_assert (reason != NULL);
68
69   if (res != GNUTLS_E_SUCCESS)
70     {
71       retval = FALSE;
72
73       /* the certificate is not structurally valid */
74       switch (res)
75         {
76         case GNUTLS_E_INSUFFICIENT_CREDENTIALS:
77           *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED;
78           break;
79         case GNUTLS_E_CONSTRAINT_ERROR:
80           *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_LIMIT_EXCEEDED;
81           break;
82         default:
83           *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
84           break;
85         }
86
87       goto out;
88     }
89
90   /* the certificate is structurally valid, check for other errors. */
91   if (verify_output & GNUTLS_CERT_INVALID)
92     {
93       retval = FALSE;
94
95       if (verify_output & GNUTLS_CERT_SIGNER_NOT_FOUND)
96         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_SELF_SIGNED;
97       else if (verify_output & GNUTLS_CERT_SIGNER_NOT_CA)
98         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED;
99       else if (verify_output & GNUTLS_CERT_INSECURE_ALGORITHM)
100         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_INSECURE;
101       else if (verify_output & GNUTLS_CERT_NOT_ACTIVATED)
102         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_NOT_ACTIVATED;
103       else if (verify_output & GNUTLS_CERT_EXPIRED)
104         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_EXPIRED;
105       else
106         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
107
108       goto out;
109     }
110
111  out:
112   return retval;
113 }
114
115 static gboolean
116 check_is_certificate_exception (EmpathyTLSVerifier *self,
117         gconstpointer data, gsize n_data)
118 {
119   GcrCertificate *cert;
120   GError *error = NULL;
121   gboolean ret;
122   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
123
124   cert = gcr_simple_certificate_new_static (data, n_data);
125   ret = gcr_trust_is_certificate_exception (cert, GCR_PURPOSE_CLIENT_AUTH,
126           priv->hostname, NULL, &error);
127   g_object_unref (cert);
128
129   if (!ret && error) {
130       DEBUG ("Can't lookup certificate exception for %s: %s", priv->hostname,
131               error->message);
132       g_clear_error (&error);
133   }
134
135   return ret;
136 }
137
138 static gboolean
139 check_is_certificate_anchor (EmpathyTLSVerifier *self,
140         gconstpointer data, gsize n_data)
141 {
142   GcrCertificate *cert;
143   GError *error = NULL;
144   gboolean ret;
145
146   cert = gcr_simple_certificate_new_static (data, n_data);
147   ret = gcr_trust_is_certificate_anchor (cert, GCR_PURPOSE_CLIENT_AUTH,
148           NULL, &error);
149   g_object_unref (cert);
150
151   if (!ret && error) {
152       DEBUG ("Can't lookup certificate anchor: %s", error->message);
153       g_clear_error (&error);
154   }
155
156   return ret;
157 }
158
159 static gnutls_x509_crt_t
160 convert_cert_to_gnutls (GArray *cert_data)
161 {
162   gnutls_x509_crt_t cert;
163   gnutls_datum_t datum = { (unsigned char*)cert_data->data, cert_data->len };
164
165   gnutls_x509_crt_init (&cert);
166   gnutls_x509_crt_import (cert, &datum, GNUTLS_X509_FMT_DER);
167
168   return cert;
169 }
170
171 static void
172 complete_verification (EmpathyTLSVerifier *self)
173 {
174   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
175
176   DEBUG ("Verification successful, completing...");
177
178   g_simple_async_result_complete_in_idle (priv->verify_result);
179
180   tp_clear_object (&priv->verify_result);
181 }
182
183 static void
184 abort_verification (EmpathyTLSVerifier *self,
185     EmpTLSCertificateRejectReason reason)
186 {
187   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
188
189   DEBUG ("Verification error %u, aborting...", reason);
190
191   g_simple_async_result_set_error (priv->verify_result,
192       G_IO_ERROR, reason, "TLS verification failed with reason %u",
193       reason);
194   g_simple_async_result_complete_in_idle (priv->verify_result);
195
196   tp_clear_object (&priv->verify_result);
197 }
198
199 static void
200 perform_verification (EmpathyTLSVerifier *self)
201 {
202   gnutls_x509_crt_t cert, anchor;
203   gboolean ret = FALSE;
204   EmpTLSCertificateRejectReason reason =
205     EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
206   gsize idx;
207   GPtrArray *certs = NULL;
208   GArray *cert_data;
209   GPtrArray *cert_chain;
210   gint res;
211   guint verify_output;
212   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
213
214   DEBUG ("Starting verification");
215
216   g_object_get (priv->certificate, "cert-data", &certs, NULL);
217   cert_chain = g_ptr_array_new_with_free_func
218           ((GDestroyNotify)gnutls_x509_crt_deinit);
219
220   /*
221    * If the first certificate is an exception then we completely
222    * ignore the rest of the verification process.
223    */
224   cert_data = g_ptr_array_index (certs, 0);
225   if (check_is_certificate_exception (self, cert_data->data, cert_data->len)) {
226       DEBUG ("Found certificate exception for %s", priv->hostname);
227       complete_verification (self);
228       goto out;
229   }
230
231   cert = convert_cert_to_gnutls (cert_data);
232   g_ptr_array_add (cert_chain, cert);
233
234   /*
235    * Find out which of our certificates is the anchor. Note that we
236    * don't allow the leaf certificate on the tree to be an anchor.
237    * Also build up the certificate chain. But only up to our anchor.
238    */
239   anchor = NULL;
240   for (idx = 1; idx < certs->len; idx++)
241     {
242       cert_data = g_ptr_array_index (certs, idx);
243
244       /* Add this to the chain */
245       cert = convert_cert_to_gnutls (cert_data);
246       g_ptr_array_add (cert_chain, cert);
247
248       /* Stop the chain at the first anchor */
249       if (check_is_certificate_anchor (self, cert_data->data,
250               cert_data->len)) {
251           anchor = cert;
252           break;
253       }
254     }
255
256   verify_output = 0;
257   res = gnutls_x509_crt_list_verify
258           ((const gnutls_x509_crt_t*)cert_chain->pdata,
259            cert_chain->len, anchor ? &anchor : NULL, anchor ? 1 : 1,
260            NULL, 0, 0, &verify_output);
261   ret = verification_output_to_reason (res, verify_output, &reason);
262
263   DEBUG ("Certificate verification gave result %d with reason %u", ret,
264           reason);
265
266   if (!ret) {
267       g_ptr_array_free (cert_chain, TRUE);
268       abort_verification (self, reason);
269       goto out;
270   }
271
272   /* now check if the certificate matches the hostname first. */
273   cert = g_ptr_array_index (cert_chain, 0);
274   if (gnutls_x509_crt_check_hostname (cert, priv->hostname) == 0)
275     {
276       gchar *certified_hostname;
277
278       certified_hostname = empathy_get_x509_certificate_hostname (cert);
279       tp_asv_set_string (priv->details,
280           "expected-hostname", priv->hostname);
281       tp_asv_set_string (priv->details,
282           "certificate-hostname", certified_hostname);
283
284       DEBUG ("Hostname mismatch: got %s but expected %s",
285           certified_hostname, priv->hostname);
286
287       g_free (certified_hostname);
288       abort_verification (self,
289               EMP_TLS_CERTIFICATE_REJECT_REASON_HOSTNAME_MISMATCH);
290       goto out;
291     }
292
293   DEBUG ("Hostname matched");
294
295   /* TODO: And here is where we check negative trust (ie: revocation) */
296
297  out:
298   g_ptr_array_free (cert_chain, TRUE);
299 }
300
301 static gboolean
302 perform_verification_cb (gpointer user_data)
303 {
304   EmpathyTLSVerifier *self = user_data;
305
306   perform_verification (self);
307
308   return FALSE;
309 }
310
311 static gboolean
312 start_verification (GIOSchedulerJob *job,
313     GCancellable *cancellable,
314     gpointer user_data)
315 {
316   EmpathyTLSVerifier *self = user_data;
317
318   g_io_scheduler_job_send_to_mainloop_async (job,
319       perform_verification_cb, self, NULL);
320
321   return FALSE;
322 }
323
324 static void
325 empathy_tls_verifier_get_property (GObject *object,
326     guint property_id,
327     GValue *value,
328     GParamSpec *pspec)
329 {
330   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
331
332   switch (property_id)
333     {
334     case PROP_TLS_CERTIFICATE:
335       g_value_set_object (value, priv->certificate);
336       break;
337     case PROP_HOSTNAME:
338       g_value_set_string (value, priv->hostname);
339       break;
340     default:
341       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
342       break;
343     }
344 }
345
346 static void
347 empathy_tls_verifier_set_property (GObject *object,
348     guint property_id,
349     const GValue *value,
350     GParamSpec *pspec)
351 {
352   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
353
354   switch (property_id)
355     {
356     case PROP_TLS_CERTIFICATE:
357       priv->certificate = g_value_dup_object (value);
358       break;
359     case PROP_HOSTNAME:
360       priv->hostname = g_value_dup_string (value);
361       break;
362     default:
363       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
364       break;
365     }
366 }
367
368 static void
369 empathy_tls_verifier_dispose (GObject *object)
370 {
371   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
372
373   if (priv->dispose_run)
374     return;
375
376   priv->dispose_run = TRUE;
377
378   tp_clear_object (&priv->certificate);
379
380   G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->dispose (object);
381 }
382
383 static void
384 empathy_tls_verifier_finalize (GObject *object)
385 {
386   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
387
388   DEBUG ("%p", object);
389
390   tp_clear_boxed (G_TYPE_HASH_TABLE, &priv->details);
391   g_free (priv->hostname);
392
393   G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->finalize (object);
394 }
395
396 static void
397 empathy_tls_verifier_init (EmpathyTLSVerifier *self)
398 {
399   EmpathyTLSVerifierPriv *priv;
400
401   priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
402       EMPATHY_TYPE_TLS_VERIFIER, EmpathyTLSVerifierPriv);
403   priv->details = tp_asv_new (NULL, NULL);
404 }
405
406 static void
407 empathy_tls_verifier_class_init (EmpathyTLSVerifierClass *klass)
408 {
409   GParamSpec *pspec;
410   GObjectClass *oclass = G_OBJECT_CLASS (klass);
411
412   g_type_class_add_private (klass, sizeof (EmpathyTLSVerifierPriv));
413
414   oclass->set_property = empathy_tls_verifier_set_property;
415   oclass->get_property = empathy_tls_verifier_get_property;
416   oclass->finalize = empathy_tls_verifier_finalize;
417   oclass->dispose = empathy_tls_verifier_dispose;
418
419   pspec = g_param_spec_object ("certificate", "The EmpathyTLSCertificate",
420       "The EmpathyTLSCertificate to be verified.",
421       EMPATHY_TYPE_TLS_CERTIFICATE,
422       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
423   g_object_class_install_property (oclass, PROP_TLS_CERTIFICATE, pspec);
424
425   pspec = g_param_spec_string ("hostname", "The hostname",
426       "The hostname which should be certified by the certificate.",
427       NULL,
428       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
429   g_object_class_install_property (oclass, PROP_HOSTNAME, pspec);
430 }
431
432 EmpathyTLSVerifier *
433 empathy_tls_verifier_new (EmpathyTLSCertificate *certificate,
434     const gchar *hostname)
435 {
436   g_assert (EMPATHY_IS_TLS_CERTIFICATE (certificate));
437   g_assert (hostname != NULL);
438
439   return g_object_new (EMPATHY_TYPE_TLS_VERIFIER,
440       "certificate", certificate,
441       "hostname", hostname,
442       NULL);
443 }
444
445 void
446 empathy_tls_verifier_verify_async (EmpathyTLSVerifier *self,
447     GAsyncReadyCallback callback,
448     gpointer user_data)
449 {
450   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
451
452   g_return_if_fail (priv->verify_result == NULL);
453
454   priv->verify_result = g_simple_async_result_new (G_OBJECT (self),
455       callback, user_data, NULL);
456
457   g_io_scheduler_push_job (start_verification,
458       self, NULL, G_PRIORITY_DEFAULT, NULL);
459 }
460
461 gboolean
462 empathy_tls_verifier_verify_finish (EmpathyTLSVerifier *self,
463     GAsyncResult *res,
464     EmpTLSCertificateRejectReason *reason,
465     GHashTable **details,
466     GError **error)
467 {
468   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
469
470   if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res),
471           error))
472     {
473       if (reason != NULL)
474         *reason = (*error)->code;
475
476       if (details != NULL)
477         {
478           *details = tp_asv_new (NULL, NULL);
479           tp_g_hash_table_update (*details, priv->details,
480               (GBoxedCopyFunc) g_strdup,
481               (GBoxedCopyFunc) tp_g_value_slice_dup);
482         }
483
484       return FALSE;
485     }
486
487   if (reason != NULL)
488     *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
489
490   return TRUE;
491 }
492
493 void
494 empathy_tls_verifier_store_exception (EmpathyTLSVerifier *self)
495 {
496   GArray *last_cert;
497   GcrCertificate *cert;
498   GPtrArray *certs;
499   GError *error = NULL;
500   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
501
502   g_object_get (priv->certificate, "cert-data", &certs, NULL);
503   last_cert = g_ptr_array_index (certs, certs->len - 1);
504   cert = gcr_simple_certificate_new_static ((gpointer)last_cert->data,
505           last_cert->len);
506
507   if (!gcr_trust_add_certificate_exception (cert, GCR_PURPOSE_CLIENT_AUTH,
508           priv->hostname, NULL, &error))
509       DEBUG ("Can't store the certificate exeption: %s", error->message);
510
511   g_object_unref (cert);
512 }