]> git.0d.be Git - empathy.git/blobdiff - libempathy/empathy-tls-verifier.c
account-settings: allow to change the service
[empathy.git] / libempathy / empathy-tls-verifier.c
index 75943bfbdeb1b83c9d883019235d1ce6e6b8ebdd..2f20ca8e383ce99f880248917f36eef8cd8c3047 100644 (file)
@@ -2,6 +2,7 @@
  * empathy-tls-verifier.c - Source for EmpathyTLSVerifier
  * Copyright (C) 2010 Collabora Ltd.
  * @author Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
+ * @author Stef Walter <stefw@collabora.co.uk>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -27,6 +28,8 @@
 
 #include "empathy-tls-verifier.h"
 
+#include <gcr/gcr.h>
+
 #define DEBUG_FLAG EMPATHY_DEBUG_TLS
 #include "empathy-debug.h"
 #include "empathy-utils.h"
@@ -39,43 +42,22 @@ G_DEFINE_TYPE (EmpathyTLSVerifier, empathy_tls_verifier,
 enum {
   PROP_TLS_CERTIFICATE = 1,
   PROP_HOSTNAME,
+  PROP_REFERENCE_IDENTITIES,
 
   LAST_PROPERTY,
 };
 
-static const gchar* system_ca_paths[] = {
-  "/etc/ssl/certs/ca-certificates.crt",
-  NULL,
-};
-
 typedef struct {
-  GPtrArray *cert_chain;
-
-  GPtrArray *trusted_ca_list;
-  GPtrArray *trusted_crl_list;
-
   EmpathyTLSCertificate *certificate;
   gchar *hostname;
+  gchar **reference_identities;
 
   GSimpleAsyncResult *verify_result;
+  GHashTable *details;
 
   gboolean dispose_run;
 } EmpathyTLSVerifierPriv;
 
-static gnutls_x509_crt_t *
-ptr_array_to_x509_crt_list (GPtrArray *chain)
-{
-  gnutls_x509_crt_t *retval;
-  gint idx;
-
-  retval = g_malloc0 (sizeof (gnutls_x509_crt_t) * chain->len);
-
-  for (idx = 0; idx < (gint) chain->len; idx++)
-    retval[idx] = g_ptr_array_index (chain, idx);
-
-  return retval;
-}
-
 static gboolean
 verification_output_to_reason (gint res,
     guint verify_output,
@@ -83,6 +65,8 @@ verification_output_to_reason (gint res,
 {
   gboolean retval = TRUE;
 
+  g_assert (reason != NULL);
+
   if (res != GNUTLS_E_SUCCESS)
     {
       retval = FALSE;
@@ -110,9 +94,9 @@ verification_output_to_reason (gint res,
       retval = FALSE;
 
       if (verify_output & GNUTLS_CERT_SIGNER_NOT_FOUND)
-        *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED;
-      else if (verify_output & GNUTLS_CERT_SIGNER_NOT_CA)
         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_SELF_SIGNED;
+      else if (verify_output & GNUTLS_CERT_SIGNER_NOT_CA)
+        *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED;
       else if (verify_output & GNUTLS_CERT_INSECURE_ALGORITHM)
         *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_INSECURE;
       else if (verify_output & GNUTLS_CERT_NOT_ACTIVATED)
@@ -129,37 +113,78 @@ verification_output_to_reason (gint res,
   return retval;
 }
 
-static gboolean
-verify_last_certificate (EmpathyTLSVerifier *self,
-    gnutls_x509_crt_t cert,
-    EmpTLSCertificateRejectReason *reason)
+static void
+build_certificate_list_for_gnutls (GcrCertificateChain *chain,
+        gnutls_x509_crt_t **list,
+        guint *n_list,
+        gnutls_x509_crt_t **anchors,
+        guint *n_anchors)
 {
-  guint verify_output;
-  gint res;
-  gnutls_x509_crt_t *trusted_ca_list;
-  EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
+  GcrCertificate *cert;
+  guint idx, length;
+  gnutls_x509_crt_t *retval;
+  gnutls_x509_crt_t gcert;
+  gnutls_datum_t datum;
+  gsize n_data;
+
+  g_assert (list);
+  g_assert (n_list);
+  g_assert (anchors);
+  g_assert (n_anchors);
+
+  *list = *anchors = NULL;
+  *n_list = *n_anchors = 0;
+
+  length = gcr_certificate_chain_get_length (chain);
+  retval = g_malloc0 (sizeof (gnutls_x509_crt_t) * length);
+
+  /* Convert the main body of the chain to gnutls */
+  for (idx = 0; idx < length; ++idx)
+    {
+      cert = gcr_certificate_chain_get_certificate (chain, idx);
+      datum.data = (gpointer)gcr_certificate_get_der_data (cert, &n_data);
+      datum.size = n_data;
 
-  trusted_ca_list = ptr_array_to_x509_crt_list (priv->trusted_ca_list);
-  res = gnutls_x509_crt_verify (cert, trusted_ca_list,
-      priv->trusted_ca_list->len, 0, &verify_output);
+      gnutls_x509_crt_init (&gcert);
+      if (gnutls_x509_crt_import (gcert, &datum, GNUTLS_X509_FMT_DER) < 0)
+        g_return_if_reached ();
 
-  g_free (trusted_ca_list);
+      retval[idx] = gcert;
+    }
+
+  *list = retval;
+  *n_list = length;
+
+  /* See if we have an anchor */
+  if (gcr_certificate_chain_get_status (chain) ==
+          GCR_CERTIFICATE_CHAIN_ANCHORED)
+    {
+      cert = gcr_certificate_chain_get_anchor (chain);
+      g_return_if_fail (cert);
+
+      datum.data = (gpointer)gcr_certificate_get_der_data (cert, &n_data);
+      datum.size = n_data;
 
-  return verification_output_to_reason (res, verify_output, reason);
+      gnutls_x509_crt_init (&gcert);
+      if (gnutls_x509_crt_import (gcert, &datum, GNUTLS_X509_FMT_DER) < 0)
+        g_return_if_reached ();
+
+      retval = g_malloc0 (sizeof (gnutls_x509_crt_t) * 1);
+      retval[0] = gcert;
+      *anchors = retval;
+      *n_anchors = 1;
+    }
 }
 
-static gboolean
-verify_certificate (EmpathyTLSVerifier *self,
-    gnutls_x509_crt_t cert,
-    gnutls_x509_crt_t issuer,
-    EmpTLSCertificateRejectReason *reason)
+static void
+free_certificate_list_for_gnutls (gnutls_x509_crt_t *list,
+        guint n_list)
 {
-  guint verify_output;
-  gint res;
-
-  res = gnutls_x509_crt_verify (cert, &issuer, 1, 0, &verify_output);
+  guint idx;
 
-  return verification_output_to_reason (res, verify_output, reason);
+  for (idx = 0; idx < n_list; idx++)
+    gnutls_x509_crt_deinit (list[idx]);
+  g_free (list);
 }
 
 static void
@@ -171,7 +196,7 @@ complete_verification (EmpathyTLSVerifier *self)
 
   g_simple_async_result_complete_in_idle (priv->verify_result);
 
-  tp_clear_object (&priv->verify_result);  
+  tp_clear_object (&priv->verify_result);
 }
 
 static void
@@ -191,210 +216,151 @@ abort_verification (EmpathyTLSVerifier *self,
 }
 
 static void
-real_start_verification (EmpathyTLSVerifier *self)
+debug_certificate (GcrCertificate *cert)
 {
-  gnutls_x509_crt_t last_cert;
-  gint idx;
-  gboolean res = FALSE;
-  gint num_certs;
-  EmpTLSCertificateRejectReason reason =
-    EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
-  EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
-
-  num_certs = priv->cert_chain->len;
-
-  DEBUG ("Starting verification");
-
-  if (priv->trusted_ca_list->len > 0)
-    {
-      /* if the last certificate is self-signed, ignore it, as we want to check
-       * the chain against our trusted CA list first.
-       */
-      last_cert = g_ptr_array_index (priv->cert_chain, num_certs - 1);
-
-      if (gnutls_x509_crt_check_issuer (last_cert, last_cert) > 0)
-        num_certs--;
-    }
-
-  for (idx = 1; idx < num_certs; idx++)
-    {
-      res = verify_certificate (self,
-          g_ptr_array_index (priv->cert_chain, idx -1),
-          g_ptr_array_index (priv->cert_chain, idx),
-          &reason);
-
-      DEBUG ("Certificate verification %d gave result %d with reason %u", idx,
-          res, reason);
-
-      if (!res)
-        {
-          abort_verification (self, reason);
-          return;
-        }
-    }
-
-  if (priv->trusted_ca_list->len > 0)
-    {
-      res = verify_last_certificate (self,
-          g_ptr_array_index (priv->cert_chain, num_certs - 1),
-          &reason);
-    }
-
-  if (!res)
-    {
-      abort_verification (self, reason);
-      return;
-    }
-
-  complete_verification (self);
+    gchar *subject = gcr_certificate_get_subject_dn (cert);
+    DEBUG ("Certificate: %s", subject);
+    g_free (subject);
 }
 
-static gboolean
-start_verification (gpointer user_data)
+static void
+debug_certificate_chain (GcrCertificateChain *chain)
 {
-  EmpathyTLSVerifier *self = user_data;
-
-  real_start_verification (self);
-
-  return FALSE;
+    GEnumClass *enum_class;
+    GEnumValue *enum_value;
+    gint idx, length;
+    GcrCertificate *cert;
+
+    enum_class = G_ENUM_CLASS
+            (g_type_class_peek (GCR_TYPE_CERTIFICATE_CHAIN_STATUS));
+    enum_value = g_enum_get_value (enum_class,
+            gcr_certificate_chain_get_status (chain));
+    length = gcr_certificate_chain_get_length (chain);
+    DEBUG ("Certificate chain: length %u status %s",
+            length, enum_value ? enum_value->value_nick : "XXX");
+
+    for (idx = 0; idx < length; ++idx)
+      {
+        cert = gcr_certificate_chain_get_certificate (chain, idx);
+        debug_certificate (cert);
+      }
 }
 
 static void
-build_gnutls_cert_list (EmpathyTLSVerifier *self)
+perform_verification (EmpathyTLSVerifier *self,
+        GcrCertificateChain *chain)
 {
-  guint num_certs;
-  guint idx;
-  GPtrArray *certificate_data = NULL;
+  gboolean ret = FALSE;
+  EmpTLSCertificateRejectReason reason =
+    EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
+  gnutls_x509_crt_t *list, *anchors;
+  guint n_list, n_anchors;
+  guint verify_output;
+  gint res;
+  gint i;
+  gboolean matched = FALSE;
   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
 
-  g_object_get (priv->certificate,
-      "cert-data", &certificate_data,
-      NULL);
-  num_certs = certificate_data->len;
-
-  priv->cert_chain = g_ptr_array_sized_new (num_certs);
-
-  for (idx = 0; idx < num_certs; idx++)
-    {
-      gnutls_x509_crt_t cert;
-      GArray *one_cert;
-      gnutls_datum_t datum = { NULL, 0 };
-
-      one_cert = g_ptr_array_index (certificate_data, idx);
-      datum.data = (guchar *) one_cert->data;
-      datum.size = one_cert->len;
-
-      gnutls_x509_crt_init (&cert);
-      gnutls_x509_crt_import (cert, &datum, GNUTLS_X509_FMT_DER);
-
-      g_ptr_array_add (priv->cert_chain, cert);
-    }
-}
-
-static gint
-get_number_and_type_of_certificates (gnutls_datum_t *datum,
-    gnutls_x509_crt_fmt_t *format)
-{
-  gnutls_x509_crt_t fake;
-  gint retval = 1;
-  gint res;
+  DEBUG ("Performing verification");
+  debug_certificate_chain (chain);
 
-  res = gnutls_x509_crt_list_import (&fake, (guint *) &retval, datum,
-      GNUTLS_X509_FMT_PEM, 0);
+  list = anchors = NULL;
+  n_list = n_anchors = 0;
 
-  if (res == GNUTLS_E_SHORT_MEMORY_BUFFER || res > 0)
+  /*
+   * If the first certificate is an pinned certificate then we completely
+   * ignore the rest of the verification process.
+   */
+  if (gcr_certificate_chain_get_status (chain) == GCR_CERTIFICATE_CHAIN_PINNED)
     {
-      *format = GNUTLS_X509_FMT_PEM;
-      return retval;
-    }
-
-  /* try DER */
-  res = gnutls_x509_crt_list_import (&fake, (guint *) &retval, datum,
-      GNUTLS_X509_FMT_DER, 0);
+      DEBUG ("Found pinned certificate for %s", priv->hostname);
+      complete_verification (self);
+      goto out;
+  }
 
-  if (res > 0)
-    {
-      *format = GNUTLS_X509_FMT_DER;
-      return retval;
-    }
+  build_certificate_list_for_gnutls (chain, &list, &n_list,
+          &anchors, &n_anchors);
+  if (list == NULL || n_list == 0) {
+      g_warn_if_reached ();
+      abort_verification (self, EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN);
+      goto out;
+  }
 
-  return res;
-}
+  verify_output = 0;
+  res = gnutls_x509_crt_list_verify (list, n_list, anchors, n_anchors,
+           NULL, 0, 0, &verify_output);
+  ret = verification_output_to_reason (res, verify_output, &reason);
 
-static gboolean
-build_gnutls_ca_and_crl_lists (GIOSchedulerJob *job,
-    GCancellable *cancellable,
-    gpointer user_data)
-{
-  gint idx;
-  EmpathyTLSVerifier *self = user_data;
-  EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
+  DEBUG ("Certificate verification gave result %d with reason %u", ret,
+          reason);
 
-  priv->trusted_ca_list = g_ptr_array_new ();
+  if (!ret) {
+      abort_verification (self, reason);
+      goto out;
+  }
 
-  for (idx = 0; idx < (gint) G_N_ELEMENTS (system_ca_paths) - 1; idx++)
+  /* now check if the certificate matches one of the reference identities. */
+  if (priv->reference_identities != NULL)
     {
-      const gchar *path;
-      gchar *contents = NULL;
-      gsize length = 0;
-      gint res, n_certs;
-      gnutls_x509_crt_t *cert_list;
-      gnutls_datum_t datum = { NULL, 0 };
-      gnutls_x509_crt_fmt_t format = 0;
-      GError *error = NULL;
-
-      path = system_ca_paths[idx];
-      g_file_get_contents (path, &contents, &length, &error);
-
-      if (error != NULL)
+      for (i = 0, matched = FALSE; priv->reference_identities[i] != NULL; ++i)
         {
-          DEBUG ("Unable to read system CAs from path %s", path);
-          g_error_free (error);
-          continue;
+          if (gnutls_x509_crt_check_hostname (list[0],
+                  priv->reference_identities[i]) == 1)
+            {
+              matched = TRUE;
+              break;
+            }
         }
+    }
 
-      datum.data = (guchar *) contents;
-      datum.size = length;
-      n_certs = get_number_and_type_of_certificates (&datum, &format);
+  if (!matched)
+    {
+      gchar *certified_hostname;
 
-      if (n_certs < 0)
-        {
-          DEBUG ("Unable to parse the system CAs from path %s: GnuTLS "
-              "returned error %d", path, n_certs);
+      certified_hostname = empathy_get_x509_certificate_hostname (list[0]);
+      tp_asv_set_string (priv->details,
+          "expected-hostname", priv->hostname);
+      tp_asv_set_string (priv->details,
+          "certificate-hostname", certified_hostname);
 
-          g_free (contents);
-          continue;
-        }
+      DEBUG ("Hostname mismatch: got %s but expected %s",
+          certified_hostname, priv->hostname);
 
-      cert_list = g_malloc0 (sizeof (gnutls_x509_crt_t) * n_certs);
-      res = gnutls_x509_crt_list_import (cert_list, (guint *) &n_certs, &datum,
-          format, 0);
+      g_free (certified_hostname);
+      abort_verification (self,
+              EMP_TLS_CERTIFICATE_REJECT_REASON_HOSTNAME_MISMATCH);
+      goto out;
+    }
 
-      if (res < 0)
-        {
-          DEBUG ("Unable to import system CAs from path %s; "
-              "GnuTLS returned error %d", path, res);
+  DEBUG ("Hostname matched");
+  complete_verification (self);
 
-          g_free (contents);
-          continue;
-        }
+ out:
+  free_certificate_list_for_gnutls (list, n_list);
+  free_certificate_list_for_gnutls (anchors, n_anchors);
+}
 
-      DEBUG ("Successfully imported %d system CA certificates from path %s",
-          n_certs, path);
+static void
+perform_verification_cb (GObject *object,
+        GAsyncResult *res,
+        gpointer user_data)
+{
+  GError *error = NULL;
 
-      /* append the newly created cert structutes into the global GPtrArray */
-      for (idx = 0; idx < n_certs; idx++)
-        g_ptr_array_add (priv->trusted_ca_list, cert_list[idx]);
+  GcrCertificateChain *chain = GCR_CERTIFICATE_CHAIN (object);
+  EmpathyTLSVerifier *self = EMPATHY_TLS_VERIFIER (user_data);
 
-      g_free (contents);
+  /* Even if building the chain fails, try verifying what we have */
+  if (!gcr_certificate_chain_build_finish (chain, res, &error))
+    {
+      DEBUG ("Building of certificate chain failed: %s", error->message);
+      g_clear_error (&error);
     }
 
-  /* TODO: do the CRL too */
+  perform_verification (self, chain);
 
-  g_io_scheduler_job_send_to_mainloop_async (job,
-      start_verification, self, NULL);
-
-  return FALSE;
+  /* Matches ref when staring chain build */
+  g_object_unref (self);
 }
 
 static void
@@ -413,6 +379,9 @@ empathy_tls_verifier_get_property (GObject *object,
     case PROP_HOSTNAME:
       g_value_set_string (value, priv->hostname);
       break;
+    case PROP_REFERENCE_IDENTITIES:
+      g_value_set_boxed (value, priv->reference_identities);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       break;
@@ -435,6 +404,9 @@ empathy_tls_verifier_set_property (GObject *object,
     case PROP_HOSTNAME:
       priv->hostname = g_value_dup_string (value);
       break;
+    case PROP_REFERENCE_IDENTITIES:
+      priv->reference_identities = g_value_dup_boxed (value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       break;
@@ -462,28 +434,22 @@ empathy_tls_verifier_finalize (GObject *object)
   EmpathyTLSVerifierPriv *priv = GET_PRIV (object);
 
   DEBUG ("%p", object);
-  
+
+  tp_clear_boxed (G_TYPE_HASH_TABLE, &priv->details);
   g_free (priv->hostname);
+  g_strfreev (priv->reference_identities);
 
   G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->finalize (object);
 }
 
-static void
-empathy_tls_verifier_constructed (GObject *object)
-{
-  EmpathyTLSVerifier *self = EMPATHY_TLS_VERIFIER (object);
-
-  build_gnutls_cert_list (self);
-  
-  if (G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->constructed != NULL)
-    G_OBJECT_CLASS (empathy_tls_verifier_parent_class)->constructed (object);
-}
-
 static void
 empathy_tls_verifier_init (EmpathyTLSVerifier *self)
 {
-  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+  EmpathyTLSVerifierPriv *priv;
+
+  priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
       EMPATHY_TYPE_TLS_VERIFIER, EmpathyTLSVerifierPriv);
+  priv->details = tp_asv_new (NULL, NULL);
 }
 
 static void
@@ -498,7 +464,6 @@ empathy_tls_verifier_class_init (EmpathyTLSVerifierClass *klass)
   oclass->get_property = empathy_tls_verifier_get_property;
   oclass->finalize = empathy_tls_verifier_finalize;
   oclass->dispose = empathy_tls_verifier_dispose;
-  oclass->constructed = empathy_tls_verifier_constructed;
 
   pspec = g_param_spec_object ("certificate", "The EmpathyTLSCertificate",
       "The EmpathyTLSCertificate to be verified.",
@@ -507,22 +472,31 @@ empathy_tls_verifier_class_init (EmpathyTLSVerifierClass *klass)
   g_object_class_install_property (oclass, PROP_TLS_CERTIFICATE, pspec);
 
   pspec = g_param_spec_string ("hostname", "The hostname",
-      "The hostname which should be certified by the certificate.",
+      "The hostname which is certified by the certificate.",
       NULL,
       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
   g_object_class_install_property (oclass, PROP_HOSTNAME, pspec);
+
+  pspec = g_param_spec_boxed ("reference-identities",
+      "The reference identities",
+      "The certificate should certify one of these identities.",
+      G_TYPE_STRV,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (oclass, PROP_REFERENCE_IDENTITIES, pspec);
 }
 
 EmpathyTLSVerifier *
 empathy_tls_verifier_new (EmpathyTLSCertificate *certificate,
-    const gchar *hostname)
+    const gchar *hostname, const gchar **reference_identities)
 {
   g_assert (EMPATHY_IS_TLS_CERTIFICATE (certificate));
   g_assert (hostname != NULL);
+  g_assert (reference_identities != NULL);
 
   return g_object_new (EMPATHY_TYPE_TLS_VERIFIER,
       "certificate", certificate,
       "hostname", hostname,
+      "reference-identities", reference_identities,
       NULL);
 }
 
@@ -531,28 +505,100 @@ empathy_tls_verifier_verify_async (EmpathyTLSVerifier *self,
     GAsyncReadyCallback callback,
     gpointer user_data)
 {
+  GcrCertificateChain *chain;
+  GcrCertificate *cert;
+  GPtrArray *cert_data = NULL;
+  GArray *data;
+  guint idx;
   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
 
+  DEBUG ("Starting verification");
+
+  g_return_if_fail (priv->verify_result == NULL);
+
+  g_object_get (priv->certificate, "cert-data", &cert_data, NULL);
+  g_return_if_fail (cert_data);
+
   priv->verify_result = g_simple_async_result_new (G_OBJECT (self),
       callback, user_data, NULL);
 
-  g_io_scheduler_push_job (build_gnutls_ca_and_crl_lists,
-      self, NULL, G_PRIORITY_DEFAULT, NULL);
+  /* Create a certificate chain */
+  chain = gcr_certificate_chain_new ();
+  for (idx = 0; idx < cert_data->len; ++idx) {
+    data = g_ptr_array_index (cert_data, idx);
+    cert = gcr_simple_certificate_new ((guchar *) data->data, data->len);
+    gcr_certificate_chain_add (chain, cert);
+    g_object_unref (cert);
+  }
+
+  gcr_certificate_chain_build_async (chain, GCR_PURPOSE_CLIENT_AUTH, priv->hostname, 0,
+          NULL, perform_verification_cb, g_object_ref (self));
+
+  g_object_unref (chain);
+  g_boxed_free (TP_ARRAY_TYPE_UCHAR_ARRAY_LIST, cert_data);
 }
 
 gboolean
 empathy_tls_verifier_verify_finish (EmpathyTLSVerifier *self,
     GAsyncResult *res,
     EmpTLSCertificateRejectReason *reason,
+    GHashTable **details,
     GError **error)
 {
+  EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
+
   if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res),
           error))
     {
-      *reason = (*error)->code;
+      if (reason != NULL)
+        *reason = (*error)->code;
+
+      if (details != NULL)
+        {
+          *details = tp_asv_new (NULL, NULL);
+          tp_g_hash_table_update (*details, priv->details,
+              (GBoxedCopyFunc) g_strdup,
+              (GBoxedCopyFunc) tp_g_value_slice_dup);
+        }
+
       return FALSE;
     }
 
-  *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
+  if (reason != NULL)
+    *reason = EMP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
+
   return TRUE;
 }
+
+void
+empathy_tls_verifier_store_exception (EmpathyTLSVerifier *self)
+{
+  GArray *data;
+  GcrCertificate *cert;
+  GPtrArray *cert_data = NULL;
+  GError *error = NULL;
+  EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
+
+  g_object_get (priv->certificate, "cert-data", &cert_data, NULL);
+  g_return_if_fail (cert_data);
+
+  if (!cert_data->len)
+    {
+      DEBUG ("No certificate to pin.");
+      return;
+    }
+
+  /* The first certificate in the chain is for the host */
+  data = g_ptr_array_index (cert_data, 0);
+  cert = gcr_simple_certificate_new ((gpointer)data->data, data->len);
+
+  DEBUG ("Storing pinned certificate:");
+  debug_certificate (cert);
+
+  if (!gcr_trust_add_pinned_certificate (cert, GCR_PURPOSE_CLIENT_AUTH,
+          priv->hostname, NULL, &error))
+      DEBUG ("Can't store the pinned certificate: %s", error->message);
+
+  g_object_unref (cert);
+  g_boxed_free (TP_ARRAY_TYPE_UCHAR_ARRAY_LIST, cert_data);
+}