[empathy/wip/rishi/tls: 2/3] tls-verifier: Use GIO to verify the chain of TLS certificates



commit 01512fd11e2b356d3c6e0be20eb14ba5d7195a99
Author: Debarshi Ray <debarshir gnome org>
Date:   Wed Mar 15 20:24:08 2017 +0100

    tls-verifier: Use GIO to verify the chain of TLS certificates
    
    Gcr has its own hand rolled code to complete the certificate chain and
    validate it, which predates the equivalent functionality in GIO. These
    days, GIO's GnuTLS backend is a better option because it defers to
    GnuTLS to do the right thing. It benefits automatically from any
    improvements made to GnuTLS itself.
    
    Bump required GLib version to 2.48. We really do need 2.48 because we
    rely on the improvements to GIO's GnuTLS backend.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=780160

 configure.ac                      |    6 +-
 libempathy/empathy-tls-verifier.c |  404 +++++++++++++++----------------------
 2 files changed, 163 insertions(+), 247 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 12bb702..e35cbeb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -37,9 +37,9 @@ AC_COPYRIGHT([
 FOLKS_REQUIRED=0.9.5
 GNUTLS_REQUIRED=2.8.5
 
-GLIB_REQUIRED=2.37.6
-AC_DEFINE(GLIB_VERSION_MIN_REQUIRED, GLIB_VERSION_2_30, [Ignore post 2.30 deprecations])
-AC_DEFINE(GLIB_VERSION_MAX_ALLOWED, GLIB_VERSION_2_38, [Prevent post 2.38 APIs])
+GLIB_REQUIRED=2.48.0
+AC_DEFINE(GLIB_VERSION_MIN_REQUIRED, GLIB_VERSION_2_48, [Ignore post 2.48 deprecations])
+AC_DEFINE(GLIB_VERSION_MAX_ALLOWED, GLIB_VERSION_2_48, [Prevent post 2.48 APIs])
 
 GTK_REQUIRED=3.9.4
 AC_DEFINE(GDK_VERSION_MIN_REQUIRED, GDK_VERSION_3_8, [Ignore post 3.8 deprecations])
diff --git a/libempathy/empathy-tls-verifier.c b/libempathy/empathy-tls-verifier.c
index 8f80b43..0930ec6 100644
--- a/libempathy/empathy-tls-verifier.c
+++ b/libempathy/empathy-tls-verifier.c
@@ -1,7 +1,9 @@
 /*
  * empathy-tls-verifier.c - Source for EmpathyTLSVerifier
  * Copyright (C) 2010 Collabora Ltd.
+ * Copyright (C) 2012 Red Hat, Inc.
  * @author Cosimo Cecchi <cosimo cecchi collabora co uk>
+ * @author Debarshi Ray <debarshir gnome org>
  * @author Stef Walter <stefw collabora co uk>
  *
  * This library is free software; you can redistribute it and/or
@@ -53,138 +55,43 @@ typedef struct {
   gboolean dispose_run;
 } EmpathyTLSVerifierPriv;
 
-static gboolean
-verification_output_to_reason (gint res,
-    guint verify_output,
-    TpTLSCertificateRejectReason *reason)
+static TpTLSCertificateRejectReason
+verification_output_to_reason (GTlsCertificateFlags flags)
 {
-  gboolean retval = TRUE;
+  TpTLSCertificateRejectReason retval;
 
-  g_assert (reason != NULL);
+  g_assert (flags != 0);
 
-  if (res != GNUTLS_E_SUCCESS)
+  switch (flags)
     {
-      retval = FALSE;
-
-      /* the certificate is not structurally valid */
-      switch (res)
-        {
-        case GNUTLS_E_INSUFFICIENT_CREDENTIALS:
-          *reason = TP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED;
-          break;
-        case GNUTLS_E_CONSTRAINT_ERROR:
-          *reason = TP_TLS_CERTIFICATE_REJECT_REASON_LIMIT_EXCEEDED;
-          break;
-        default:
-          *reason = TP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
-          break;
-        }
-
-      goto out;
+      case G_TLS_CERTIFICATE_UNKNOWN_CA:
+        retval = TP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED;
+        break;
+      case G_TLS_CERTIFICATE_BAD_IDENTITY:
+        retval = TP_TLS_CERTIFICATE_REJECT_REASON_HOSTNAME_MISMATCH;
+        break;
+      case G_TLS_CERTIFICATE_NOT_ACTIVATED:
+        retval = TP_TLS_CERTIFICATE_REJECT_REASON_NOT_ACTIVATED;
+        break;
+      case G_TLS_CERTIFICATE_EXPIRED:
+        retval = TP_TLS_CERTIFICATE_REJECT_REASON_EXPIRED;
+        break;
+      case G_TLS_CERTIFICATE_REVOKED:
+        retval = TP_TLS_CERTIFICATE_REJECT_REASON_REVOKED;
+        break;
+      case G_TLS_CERTIFICATE_INSECURE:
+        retval = TP_TLS_CERTIFICATE_REJECT_REASON_INSECURE;
+        break;
+      case G_TLS_CERTIFICATE_GENERIC_ERROR:
+      default:
+        retval = TP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
+        break;
     }
 
-  /* the certificate is structurally valid, check for other errors. */
-  if (verify_output & GNUTLS_CERT_INVALID)
-    {
-      retval = FALSE;
-
-      if (verify_output & GNUTLS_CERT_SIGNER_NOT_FOUND)
-        *reason = TP_TLS_CERTIFICATE_REJECT_REASON_SELF_SIGNED;
-      else if (verify_output & GNUTLS_CERT_SIGNER_NOT_CA)
-        *reason = TP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED;
-      else if (verify_output & GNUTLS_CERT_INSECURE_ALGORITHM)
-        *reason = TP_TLS_CERTIFICATE_REJECT_REASON_INSECURE;
-      else if (verify_output & GNUTLS_CERT_NOT_ACTIVATED)
-        *reason = TP_TLS_CERTIFICATE_REJECT_REASON_NOT_ACTIVATED;
-      else if (verify_output & GNUTLS_CERT_EXPIRED)
-        *reason = TP_TLS_CERTIFICATE_REJECT_REASON_EXPIRED;
-      else if (verify_output & GNUTLS_CERT_REVOKED)
-        *reason = TP_TLS_CERTIFICATE_REJECT_REASON_REVOKED;
-      else
-        *reason = TP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN;
-
-      goto out;
-    }
-
- out:
   return retval;
 }
 
 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)
-{
-  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;
-
-      gnutls_x509_crt_init (&gcert);
-      if (gnutls_x509_crt_import (gcert, &datum, GNUTLS_X509_FMT_DER) < 0)
-        g_return_if_reached ();
-
-      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;
-
-      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 void
-free_certificate_list_for_gnutls (gnutls_x509_crt_t *list,
-        guint n_list)
-{
-  guint idx;
-
-  for (idx = 0; idx < n_list; idx++)
-    gnutls_x509_crt_deinit (list[idx]);
-  g_free (list);
-}
-
-static void
 complete_verification (EmpathyTLSVerifier *self)
 {
   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
@@ -221,142 +128,150 @@ debug_certificate (GcrCertificate *cert)
 }
 
 static void
-debug_certificate_chain (GcrCertificateChain *chain)
+verify_chain_cb (GObject *object,
+        GAsyncResult *res,
+        gpointer user_data)
 {
-    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);
-      }
-}
+  GError *error = NULL;
 
-static void
-perform_verification (EmpathyTLSVerifier *self,
-        GcrCertificateChain *chain)
-{
-  gboolean ret = FALSE;
-  TpTLSCertificateRejectReason reason =
-    TP_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;
+  GTlsCertificateFlags flags;
+  GTlsDatabase *tls_database = G_TLS_DATABASE (object);
+  EmpathyTLSVerifier *self = EMPATHY_TLS_VERIFIER (user_data);
   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
 
-  DEBUG ("Performing verification");
-  debug_certificate_chain (chain);
-
-  list = anchors = NULL;
-  n_list = n_anchors = 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)
+  flags = g_tls_database_verify_chain_finish (tls_database, res, &error);
+  if (error != NULL)
     {
-      DEBUG ("Found pinned certificate for %s", priv->hostname);
-      complete_verification (self);
-      goto out;
-  }
-
-  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, TP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN);
-      goto out;
-  }
+      TpTLSCertificateRejectReason reason;
 
-  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);
+      DEBUG ("Verification of certificate chain failed: %s", error->message);
 
-  DEBUG ("Certificate verification gave result %d with reason %u", ret,
+      reason = verification_output_to_reason (flags);
+      DEBUG ("Certificate verification gave flags %d with reason %u",
+          (gint) flags,
           reason);
 
-  if (!ret) {
-      abort_verification (self, reason);
-      goto out;
-  }
-
-  /* now check if the certificate matches one of the reference identities. */
-  if (priv->reference_identities != NULL)
-    {
-      for (i = 0, matched = FALSE; priv->reference_identities[i] != NULL; ++i)
+      if (flags & G_TLS_CERTIFICATE_BAD_IDENTITY)
         {
-          if (gnutls_x509_crt_check_hostname (list[0],
-                  priv->reference_identities[i]) == 1)
-            {
-              matched = TRUE;
-              break;
-            }
+          /* FIXME: We don't set "certificate-hostname" because
+           * GTlsCertificate doesn't expose the hostname used in the
+           * certificate. We will temporarily lose some verbosity in
+           * EmpathyTLSDialog, but that's balanced by no longer
+           * relying on a specific encryption library.
+           */
+          tp_asv_set_string (priv->details,
+              "expected-hostname", priv->hostname);
+
+          DEBUG ("Hostname mismatch: expected %s", priv->hostname);
         }
-    }
-
-  if (!matched)
-    {
-      gchar *certified_hostname;
-
-      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);
-
-      DEBUG ("Hostname mismatch: got %s but expected %s",
-          certified_hostname, priv->hostname);
 
-      g_free (certified_hostname);
-      abort_verification (self,
-              TP_TLS_CERTIFICATE_REJECT_REASON_HOSTNAME_MISMATCH);
+      abort_verification (self, reason);
+      g_clear_error (&error);
       goto out;
     }
 
-  DEBUG ("Hostname matched");
+  DEBUG ("Verified certificate chain");
   complete_verification (self);
 
- out:
-  free_certificate_list_for_gnutls (list, n_list);
-  free_certificate_list_for_gnutls (anchors, n_anchors);
+out:
+  /* Matches ref when starting verify chain */
+  g_object_unref (self);
 }
 
 static void
-perform_verification_cb (GObject *object,
-        GAsyncResult *res,
-        gpointer user_data)
+is_certificate_pinned_cb (GObject *object,
+    GAsyncResult *res,
+    gpointer user_data)
 {
+  GArray *data;
   GError *error = NULL;
-
-  GcrCertificateChain *chain = GCR_CERTIFICATE_CHAIN (object);
+  GPtrArray *cert_data;
+  GSocketConnectable *identity = NULL;
+  GTlsCertificate *cert = NULL;
+  GTlsCertificate *issuer = NULL;
+  GTlsBackend *tls_backend;
+  GTlsDatabase *tls_database = NULL;
+  GType tls_certificate_type;
+  gint idx;
   EmpathyTLSVerifier *self = EMPATHY_TLS_VERIFIER (user_data);
+  EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
 
-  /* Even if building the chain fails, try verifying what we have */
-  if (!gcr_certificate_chain_build_finish (chain, res, &error))
+  if (gcr_trust_is_certificate_pinned_finish (res, &error))
     {
-      DEBUG ("Building of certificate chain failed: %s", error->message);
+      DEBUG ("Found pinned certificate for %s", priv->hostname);
+      complete_verification (self);
+      goto out;
+    }
+
+  /* error is set only when there is an actual failure. It won't be
+   * set, if it successfully determined that the ceritificate was not
+   * pinned. */
+  if (error != NULL)
+    {
+      DEBUG ("Failed to determine if certificate is pinned: %s",
+          error->message);
       g_clear_error (&error);
     }
 
-  perform_verification (self, chain);
+  cert_data = tp_tls_certificate_get_cert_data (priv->certificate);
+
+  tls_backend = g_tls_backend_get_default ();
+  tls_certificate_type = g_tls_backend_get_certificate_type (tls_backend);
+
+  /* Create a certificate chain */
+  for (idx = (gint) cert_data->len - 1; idx >= 0; --idx)
+    {
+      data = g_ptr_array_index (cert_data, idx);
+      cert = g_initable_new (tls_certificate_type,
+          NULL,
+          &error,
+          "certificate", (GByteArray *) data,
+          "issuer", issuer,
+          NULL);
+
+      if (error != NULL)
+        {
+          DEBUG ("Verification of certificate chain failed: %s",
+              error->message);
+
+          abort_verification (self, TP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN);
+          g_clear_error (&error);
+          goto out;
+        }
+
+      g_clear_object (&issuer);
+      issuer = g_object_ref (cert);
+      g_clear_object (&cert);
+    }
+
+  g_assert_null (cert);
+  g_assert_true (G_IS_TLS_CERTIFICATE (issuer));
+
+  cert = g_object_ref (issuer);
+  g_clear_object (&issuer);
+
+  tls_database = g_tls_backend_get_default_database (tls_backend);
+  identity = g_network_address_new (priv->hostname, 0);
+
+  DEBUG ("Performing verification");
+
+  g_tls_database_verify_chain_async (tls_database,
+      cert,
+      G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER,
+      identity,
+      NULL,
+      G_TLS_DATABASE_VERIFY_NONE,
+      NULL,
+      verify_chain_cb,
+      g_object_ref (self));
+
+out:
+  g_clear_object (&cert);
+  g_clear_object (&identity);
+  g_clear_object (&issuer);
+  g_clear_object (&tls_database);
 
-  /* Matches ref when staring chain build */
+  /* Matches ref when starting is certificate pinned */
   g_object_unref (self);
 }
 
@@ -503,11 +418,9 @@ empathy_tls_verifier_verify_async (EmpathyTLSVerifier *self,
     GAsyncReadyCallback callback,
     gpointer user_data)
 {
-  GcrCertificateChain *chain;
   GcrCertificate *cert;
   GPtrArray *cert_data;
   GArray *data;
-  guint idx;
   EmpathyTLSVerifierPriv *priv = GET_PRIV (self);
 
   DEBUG ("Starting verification");
@@ -520,19 +433,22 @@ empathy_tls_verifier_verify_async (EmpathyTLSVerifier *self,
   priv->verify_result = g_simple_async_result_new (G_OBJECT (self),
       callback, user_data, 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_SERVER_AUTH, priv->hostname, 0,
-          NULL, perform_verification_cb, g_object_ref (self));
-
-  g_object_unref (chain);
+  /* 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,
+      (gsize) data->len);
+
+  DEBUG ("Checking if certificate is pinned:");
+  debug_certificate (cert);
+
+  gcr_trust_is_certificate_pinned_async (cert,
+      GCR_PURPOSE_SERVER_AUTH,
+      priv->hostname,
+      NULL,
+      is_certificate_pinned_cb,
+      g_object_ref (self));
+
+  g_object_unref (cert);
 }
 
 gboolean


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]