[glib-networking] Revert "gnutls: use gnutls's verification code for GTlsFileDatabaseGnutls"



commit 79686a3c8d7daa702cb95ab9cd8f886732cdfd4b
Author: Emmanuele Bassi <ebassi gnome org>
Date:   Mon Feb 8 13:40:29 2016 +0000

    Revert "gnutls: use gnutls's verification code for GTlsFileDatabaseGnutls"
    
    This reverts commit 55c61d1c2de20af86adf711e7b6e10787077420a.
    
    The commit implicitly bumps up the dependency of GnuTLS for
    glib-networking to be ≥ 3.3, because of the use of the
    gnutls_x509_trust_list_verify_crt2() function.
    
    Glib-networking still depends on GnuTLS 3.0; at the very least, this
    commit should also bump up the dependency inside configure.ac — but both
    JHBuild and Continuous provide older versions.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=761713

 tls/gnutls/gtlscertificate-gnutls.c  |    3 +-
 tls/gnutls/gtlsfiledatabase-gnutls.c |  343 ++++++++++++++++++++++++++++------
 2 files changed, 288 insertions(+), 58 deletions(-)
---
diff --git a/tls/gnutls/gtlscertificate-gnutls.c b/tls/gnutls/gtlscertificate-gnutls.c
index 4ff996c..7aaa6f6 100644
--- a/tls/gnutls/gtlscertificate-gnutls.c
+++ b/tls/gnutls/gtlscertificate-gnutls.c
@@ -511,8 +511,7 @@ static const struct {
   { GNUTLS_CERT_NOT_ACTIVATED, G_TLS_CERTIFICATE_NOT_ACTIVATED },
   { GNUTLS_CERT_EXPIRED, G_TLS_CERTIFICATE_EXPIRED },
   { GNUTLS_CERT_REVOKED, G_TLS_CERTIFICATE_REVOKED },
-  { GNUTLS_CERT_INSECURE_ALGORITHM, G_TLS_CERTIFICATE_INSECURE },
-  { GNUTLS_CERT_UNEXPECTED_OWNER, G_TLS_CERTIFICATE_BAD_IDENTITY }
+  { GNUTLS_CERT_INSECURE_ALGORITHM, G_TLS_CERTIFICATE_INSECURE }
 };
 static const int flags_map_size = G_N_ELEMENTS (flags_map);
 
diff --git a/tls/gnutls/gtlsfiledatabase-gnutls.c b/tls/gnutls/gtlsfiledatabase-gnutls.c
index 456171a..c7cf147 100644
--- a/tls/gnutls/gtlsfiledatabase-gnutls.c
+++ b/tls/gnutls/gtlsfiledatabase-gnutls.c
@@ -53,7 +53,6 @@ struct _GTlsFileDatabaseGnutlsPrivate
 {
   /* read-only after construct */
   gchar *anchor_filename;
-  gnutls_x509_trust_list_t trust_list;
 
   /* protected by mutex */
   GMutex mutex;
@@ -259,15 +258,25 @@ g_tls_file_database_gnutls_finalize (GObject *object)
 {
   GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (object);
 
-  g_clear_pointer (&self->priv->subjects, g_hash_table_destroy);
-  g_clear_pointer (&self->priv->issuers, g_hash_table_destroy);
-  g_clear_pointer (&self->priv->complete, g_hash_table_destroy);
-  g_clear_pointer (&self->priv->handles, g_hash_table_destroy);
-  if (self->priv->anchor_filename)
-    {
-      g_free (self->priv->anchor_filename);
-      gnutls_x509_trust_list_deinit (self->priv->trust_list, 1);
-    }
+  if (self->priv->subjects)
+    g_hash_table_destroy (self->priv->subjects);
+  self->priv->subjects = NULL;
+
+  if (self->priv->issuers)
+    g_hash_table_destroy (self->priv->issuers);
+  self->priv->issuers = NULL;
+
+  if (self->priv->complete)
+    g_hash_table_destroy (self->priv->complete);
+  self->priv->complete = NULL;
+
+  if (self->priv->handles)
+    g_hash_table_destroy (self->priv->handles);
+  self->priv->handles = NULL;
+
+  g_free (self->priv->anchor_filename);
+  self->priv->anchor_filename = NULL;
+
   g_mutex_clear (&self->priv->mutex);
 
   G_OBJECT_CLASS (g_tls_file_database_gnutls_parent_class)->finalize (object);
@@ -298,29 +307,21 @@ g_tls_file_database_gnutls_set_property (GObject      *object,
                                          GParamSpec   *pspec)
 {
   GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (object);
-  const char *anchor_path;
+  gchar *anchor_path;
 
   switch (prop_id)
     {
     case PROP_ANCHORS:
-      anchor_path = g_value_get_string (value);
+      anchor_path = g_value_dup_string (value);
       if (anchor_path && !g_path_is_absolute (anchor_path))
-       {
-         g_warning ("The anchor file name used with a GTlsFileDatabase "
-                    "must be an absolute path, and not relative: %s", anchor_path);
-         return;
-       }
-
-      if (self->priv->anchor_filename)
-       {
-         g_free (self->priv->anchor_filename);
-         gnutls_x509_trust_list_deinit (self->priv->trust_list, 1);
-       }
-      self->priv->anchor_filename = g_strdup (anchor_path);
-      gnutls_x509_trust_list_init (&self->priv->trust_list, 0);
-      gnutls_x509_trust_list_add_trust_file (self->priv->trust_list,
-                                            anchor_path, NULL,
-                                            GNUTLS_X509_FMT_PEM, 0, 0);
+        {
+          g_warning ("The anchor file name for used with a GTlsFileDatabase "
+                     "must be an absolute path, and not relative: %s", anchor_path);
+        }
+      else
+        {
+          self->priv->anchor_filename = anchor_path;
+        }
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -411,6 +412,45 @@ g_tls_file_database_gnutls_lookup_certificate_for_handle (GTlsDatabase
   return cert;
 }
 
+static gboolean
+g_tls_file_database_gnutls_lookup_assertion (GTlsFileDatabaseGnutls       *self,
+                                             GTlsCertificateGnutls        *certificate,
+                                             GTlsDatabaseGnutlsAssertion   assertion,
+                                             const gchar                  *purpose,
+                                             GSocketConnectable           *identity,
+                                             GCancellable                 *cancellable,
+                                             GError                      **error)
+{
+  GBytes *der = NULL;
+  gboolean contains;
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return FALSE;
+
+  /* We only have anchored certificate assertions here */
+  if (assertion != G_TLS_DATABASE_GNUTLS_ANCHORED_CERTIFICATE)
+    return FALSE;
+
+  /*
+   * TODO: We should be parsing any Extended Key Usage attributes and
+   * comparing them to the purpose.
+   */
+
+  der = g_tls_certificate_gnutls_get_bytes (certificate);
+
+  g_mutex_lock (&self->priv->mutex);
+  contains = g_hash_table_lookup (self->priv->complete, der) ? TRUE : FALSE;
+  g_mutex_unlock (&self->priv->mutex);
+
+  g_bytes_unref (der);
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return FALSE;
+
+  /* All certificates in our file are anchored certificates */
+  return contains;
+}
+
 static GTlsCertificate *
 g_tls_file_database_gnutls_lookup_certificate_issuer (GTlsDatabase             *database,
                                                       GTlsCertificate          *certificate,
@@ -520,6 +560,158 @@ g_tls_file_database_gnutls_lookup_certificates_issued_by (GTlsDatabase
   return issued;
 }
 
+#define BUILD_CERTIFICATE_CHAIN_RECURSION_LIMIT 10
+
+enum {
+  STATUS_FAILURE,
+  STATUS_INCOMPLETE,
+  STATUS_SELFSIGNED,
+  STATUS_ANCHORED,
+  STATUS_RECURSION_LIMIT_REACHED
+};
+
+static gboolean
+is_self_signed (GTlsCertificateGnutls *certificate)
+{
+  const gnutls_x509_crt_t cert = g_tls_certificate_gnutls_get_cert (certificate);
+  return (gnutls_x509_crt_check_issuer (cert, cert) > 0);
+}
+
+static gint
+build_certificate_chain (GTlsFileDatabaseGnutls  *self,
+                         GTlsCertificateGnutls   *certificate,
+                         GTlsCertificateGnutls   *previous,
+                         gboolean                 certificate_is_from_db,
+                         guint                    recursion_depth,
+                         const gchar             *purpose,
+                         GSocketConnectable      *identity,
+                         GTlsInteraction         *interaction,
+                         GCancellable            *cancellable,
+                         GTlsCertificateGnutls  **anchor,
+                         GError                 **error)
+{
+  GTlsCertificate *issuer;
+  gint status;
+
+  if (recursion_depth++ > BUILD_CERTIFICATE_CHAIN_RECURSION_LIMIT)
+    return STATUS_RECURSION_LIMIT_REACHED;
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return STATUS_FAILURE;
+
+  /* Look up whether this certificate is an anchor */
+  if (g_tls_file_database_gnutls_lookup_assertion (self, certificate,
+                                                  G_TLS_DATABASE_GNUTLS_ANCHORED_CERTIFICATE,
+                                                  purpose, identity, cancellable, error))
+    {
+      g_tls_certificate_gnutls_set_issuer (certificate, NULL);
+      *anchor = certificate;
+      return STATUS_ANCHORED;
+    }
+  else if (*error)
+    {
+      return STATUS_FAILURE;
+    }
+
+  /* Is it self-signed? */
+  if (is_self_signed (certificate))
+    {
+      /*
+       * Since at this point we would fail with 'self-signed', can we replace
+       * this certificate with one from the database and do better?
+       */
+      if (previous && !certificate_is_from_db)
+        {
+          issuer = g_tls_database_lookup_certificate_issuer (G_TLS_DATABASE (self),
+                                                             G_TLS_CERTIFICATE (previous),
+                                                             interaction,
+                                                             G_TLS_DATABASE_LOOKUP_NONE,
+                                                             cancellable, error);
+          if (*error)
+            {
+              return STATUS_FAILURE;
+            }
+          else if (issuer)
+            {
+              /* Replaced with certificate in the db, restart step again with this certificate */
+              g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (issuer), STATUS_FAILURE);
+              certificate = G_TLS_CERTIFICATE_GNUTLS (issuer);
+              g_tls_certificate_gnutls_set_issuer (previous, certificate);
+              g_object_unref (issuer);
+
+              return build_certificate_chain (self, certificate, previous, TRUE, recursion_depth,
+                                              purpose, identity, interaction, cancellable, anchor, error);
+            }
+        }
+
+      g_tls_certificate_gnutls_set_issuer (certificate, NULL);
+      return STATUS_SELFSIGNED;
+    }
+
+  previous = certificate;
+
+  /* Bring over the next certificate in the chain */
+  issuer = g_tls_certificate_get_issuer (G_TLS_CERTIFICATE (certificate));
+  if (issuer)
+    {
+      g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (issuer), STATUS_FAILURE);
+      certificate = G_TLS_CERTIFICATE_GNUTLS (issuer);
+
+      status = build_certificate_chain (self, certificate, previous, FALSE, recursion_depth,
+                                        purpose, identity, interaction, cancellable, anchor, error);
+      if (status != STATUS_INCOMPLETE)
+        {
+          return status;
+        }
+    }
+
+  /* Search for the next certificate in chain */
+  issuer = g_tls_database_lookup_certificate_issuer (G_TLS_DATABASE (self),
+                                                     G_TLS_CERTIFICATE (certificate),
+                                                     interaction,
+                                                     G_TLS_DATABASE_LOOKUP_NONE,
+                                                     cancellable, error);
+  if (*error)
+    return STATUS_FAILURE;
+
+  if (!issuer)
+    return STATUS_INCOMPLETE;
+
+  g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (issuer), STATUS_FAILURE);
+  g_tls_certificate_gnutls_set_issuer (certificate, G_TLS_CERTIFICATE_GNUTLS (issuer));
+  certificate = G_TLS_CERTIFICATE_GNUTLS (issuer);
+  g_object_unref (issuer);
+
+  return build_certificate_chain (self, certificate, previous, TRUE, recursion_depth,
+                                  purpose, identity, interaction, cancellable, anchor, error);
+}
+
+static GTlsCertificateFlags
+double_check_before_after_dates (GTlsCertificateGnutls *chain)
+{
+  GTlsCertificateFlags gtls_flags = 0;
+  gnutls_x509_crt_t cert;
+  time_t t, now;
+
+  now = time (NULL);
+  while (chain)
+    {
+      cert = g_tls_certificate_gnutls_get_cert (chain);
+      t = gnutls_x509_crt_get_activation_time (cert);
+      if (t == (time_t) -1 || t > now)
+        gtls_flags |= G_TLS_CERTIFICATE_NOT_ACTIVATED;
+
+      t = gnutls_x509_crt_get_expiration_time (cert);
+      if (t == (time_t) -1 || t < now)
+        gtls_flags |= G_TLS_CERTIFICATE_EXPIRED;
+
+      chain = G_TLS_CERTIFICATE_GNUTLS (g_tls_certificate_get_issuer
+                                        (G_TLS_CERTIFICATE (chain)));
+    }
+
+  return gtls_flags;
+}
+
 static void
 convert_certificate_chain_to_gnutls (GTlsCertificateGnutls  *chain,
                                      gnutls_x509_crt_t     **gnutls_chain,
@@ -555,56 +747,95 @@ g_tls_file_database_gnutls_verify_chain (GTlsDatabase             *database,
                                         GError                  **error)
 {
   GTlsFileDatabaseGnutls *self;
+  GTlsCertificateFlags result;
+  GTlsCertificateGnutls *certificate;
+  GError *err = NULL;
+  GTlsCertificateGnutls *anchor;
   guint gnutls_result;
-  gnutls_x509_crt_t *certs;
-  guint certs_length;
-  const char *hostname = NULL;
-  char *free_hostname = NULL;
-  gnutls_typed_vdata_st vdata;
-  int gerr;
+  gnutls_x509_crt_t *certs, *anchors;
+  guint certs_length, anchors_length;
+  gint status, gerr;
+  guint recursion_depth = 0;
 
   g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (chain),
                         G_TLS_CERTIFICATE_GENERIC_ERROR);
   g_assert (purpose);
 
+  self = G_TLS_FILE_DATABASE_GNUTLS (database);
+  certificate = G_TLS_CERTIFICATE_GNUTLS (chain);
+
+  /* First check for pinned certificate */
+  if (g_tls_file_database_gnutls_lookup_assertion (self, certificate,
+                                                  G_TLS_DATABASE_GNUTLS_PINNED_CERTIFICATE,
+                                                  purpose, identity, cancellable, &err))
+    {
+      /*
+       * A pinned certificate is verified on its own, without any further
+       * verification.
+       */
+      g_tls_certificate_gnutls_set_issuer (certificate, NULL);
+      return 0;
+    }
+
+  if (err)
+    {
+      g_propagate_error (error, err);
+      return G_TLS_CERTIFICATE_GENERIC_ERROR;
+    }
+
+  anchor = NULL;
+  status = build_certificate_chain (self, certificate, NULL, FALSE, recursion_depth,
+                                    purpose, identity, interaction, cancellable, &anchor, &err);
+  if (status == STATUS_FAILURE)
+    {
+      g_propagate_error (error, err);
+      return G_TLS_CERTIFICATE_GENERIC_ERROR;
+    }
+
   if (g_cancellable_set_error_if_cancelled (cancellable, error))
     return G_TLS_CERTIFICATE_GENERIC_ERROR;
 
-  self = G_TLS_FILE_DATABASE_GNUTLS (database);
-
   convert_certificate_chain_to_gnutls (G_TLS_CERTIFICATE_GNUTLS (chain),
                                        &certs, &certs_length);
-  if (G_IS_NETWORK_ADDRESS (identity))
-    hostname = g_network_address_get_hostname (G_NETWORK_ADDRESS (identity));
-  else if (G_IS_NETWORK_SERVICE (identity))
-    hostname = g_network_service_get_domain (G_NETWORK_SERVICE (identity));
-  else if (G_IS_INET_SOCKET_ADDRESS (identity))
-    {
-      GInetAddress *addr;
 
-      addr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (identity));
-      hostname = free_hostname = g_inet_address_to_string (addr);
+  if (anchor)
+    {
+      g_assert (g_tls_certificate_get_issuer (G_TLS_CERTIFICATE (anchor)) == NULL);
+      convert_certificate_chain_to_gnutls (G_TLS_CERTIFICATE_GNUTLS (anchor),
+                                           &anchors, &anchors_length);
     }
-  if (hostname)
+  else
     {
-      vdata.type = GNUTLS_DT_DNS_HOSTNAME;
-      vdata.data = (gpointer) hostname;
-      vdata.size = strlen (hostname);
+      anchors = NULL;
+      anchors_length = 0;
     }
-  gerr = gnutls_x509_trust_list_verify_crt2 (self->priv->trust_list,
-                                            certs, certs_length,
-                                            &vdata, hostname ? 1 : 0,
-                                            0,
-                                            &gnutls_result, NULL);
+
+  gerr = gnutls_x509_crt_list_verify (certs, certs_length,
+                                      anchors, anchors_length,
+                                      NULL, 0, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT,
+                                      &gnutls_result);
+
   g_free (certs);
-  g_free (free_hostname);
+  g_free (anchors);
 
   if (gerr != 0)
     return G_TLS_CERTIFICATE_GENERIC_ERROR;
   else if (g_cancellable_set_error_if_cancelled (cancellable, error))
     return G_TLS_CERTIFICATE_GENERIC_ERROR;
 
-  return g_tls_certificate_gnutls_convert_flags (gnutls_result);
+  result = g_tls_certificate_gnutls_convert_flags (gnutls_result);
+
+  /*
+   * We have to check these ourselves since gnutls_x509_crt_list_verify
+   * won't bother if it gets an UNKNOWN_CA.
+   */
+  result |= double_check_before_after_dates (G_TLS_CERTIFICATE_GNUTLS (chain));
+
+  if (identity)
+    result |= g_tls_certificate_gnutls_verify_identity (G_TLS_CERTIFICATE_GNUTLS (chain),
+                                                        identity);
+
+  return result;
 }
 
 static void


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