[glib-networking/mcatanzaro/#96] gnutls: download missing intermediate certificates at runtime




commit 248003f3f1b631bed332e09b68ff1c1312e1db43
Author: Michael Catanzaro <mcatanzaro gnome org>
Date:   Fri Aug 7 18:37:35 2020 -0500

    gnutls: download missing intermediate certificates at runtime
    
    This is slow. Don't misconfigure your server by failing to include
    required certificates if you don't want your website to be slow.
    
    Note that since we're already on a dedicated handshake thread if called
    from GTlsConnection, we can do sync I/O throughout, no worries.
    
    Fixes #96

 tls/gnutls/gtlsdatabase-gnutls.c | 137 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 137 insertions(+)
---
diff --git a/tls/gnutls/gtlsdatabase-gnutls.c b/tls/gnutls/gtlsdatabase-gnutls.c
index 3825a35..85b771f 100644
--- a/tls/gnutls/gtlsdatabase-gnutls.c
+++ b/tls/gnutls/gtlsdatabase-gnutls.c
@@ -66,6 +66,9 @@ typedef struct
    * string handles. This array is populated on demand.
    */
   GHashTable *handles;
+
+  /* Unowned. This is only set temporarily, during certificate verification. */
+  GCancellable *verify_chain_cancellable;
 } GTlsDatabaseGnutlsPrivate;
 
 static void g_tls_database_gnutls_initable_interface_init (GInitableIface *iface);
@@ -220,6 +223,8 @@ g_tls_database_gnutls_finalize (GObject *object)
   GTlsDatabaseGnutls *self = G_TLS_DATABASE_GNUTLS (object);
   GTlsDatabaseGnutlsPrivate *priv = g_tls_database_gnutls_get_instance_private (self);
 
+  g_assert (!priv->verify_chain_cancellable);
+
   g_clear_pointer (&priv->subjects, g_hash_table_destroy);
   g_clear_pointer (&priv->issuers, g_hash_table_destroy);
   g_clear_pointer (&priv->complete, g_hash_table_destroy);
@@ -496,10 +501,13 @@ g_tls_database_gnutls_verify_chain (GTlsDatabase             *database,
   if (g_cancellable_set_error_if_cancelled (cancellable, error))
     return G_TLS_CERTIFICATE_GENERIC_ERROR;
 
+  g_assert (!priv->verify_chain_cancellable);
+  priv->verify_chain_cancellable = cancellable;
   gnutls_chain = convert_certificate_chain_to_gnutls (G_TLS_CERTIFICATE_GNUTLS (chain));
   gerr = gnutls_x509_trust_list_verify_crt (priv->trust_list,
                                             gnutls_chain->chain, gnutls_chain->length,
                                             0, &gnutls_result, NULL);
+  priv->verify_chain_cancellable = NULL;
 
   if (gerr != 0 || g_cancellable_set_error_if_cancelled (cancellable, error))
     {
@@ -575,6 +583,131 @@ g_tls_database_gnutls_populate_trust_list (GTlsDatabaseGnutls        *self,
   return gerr >= 0;
 }
 
+#if GNUTLS_VERSION_MAJOR > 3 || GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR >= 7
+static int
+issuer_missing_cb (gnutls_x509_trust_list_t   tlist,
+                   const gnutls_x509_crt_t    crt,
+                   gnutls_x509_crt_t        **issuers,
+                   guint                     *issuers_size)
+{
+  GTlsDatabaseGnutls *self = gnutls_x509_trust_list_get_ptr (tlist);
+  GTlsDatabaseGnutlsPrivate *priv = g_tls_database_gnutls_get_instance_private (self);
+  gnutls_datum_t datum;
+  GFile *file = NULL;
+  GFileInputStream *istream = NULL;
+  char *aia = NULL;
+  char *scheme = NULL;
+  int gerr;
+  int ret = -1;
+  guchar buffer[2048];
+  gssize n_read;
+  GByteArray *der = NULL;
+  GError *error = NULL;
+
+  /* The server sent an incomplete certificate chain, but we may be able to
+   * download the missing certificate to allow verification to proceed. See
+   * Authority Information Access, RFC 5280 ยง4.2.2.1. Also see:
+   * 
https://blogs.gnome.org/mcatanzaro/2015/01/30/mozilla-is-responsible-for-the-redhat-corpmerchandise-com-fiasco/
+   */
+
+  for (int i = 0; ; i++)
+    {
+      gerr = gnutls_x509_crt_get_authority_info_access (crt, i, GNUTLS_IA_CAISSUERS_URI, &datum, NULL);
+      if (gerr == GNUTLS_E_UNKNOWN_ALGORITHM)
+        continue;
+
+      if (gerr == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+        return -1;
+
+      if (gerr < 0)
+        {
+          g_warning ("Failed to read Authority Information Access from certificate: %s", gnutls_strerror 
(gerr));
+          return -1;
+        }
+
+      /* Success */
+      break;
+    }
+  g_assert (gerr == GNUTLS_E_SUCCESS);
+
+  aia = g_malloc0 (datum.size + 1);
+  memcpy (aia, datum.data, datum.size);
+
+  if (!g_uri_is_valid (aia, G_URI_FLAGS_NONE, &error))
+    {
+      g_warning ("Authority Information Access URI %s is not a valid URI: %s", aia, error->message);
+      goto out;
+    }
+
+  /* We support only HTTP. Notably, HTTPS is not supported because (a) it is
+   * not specified by RFC 5280, and (b) since we have no way to break a
+   * recursive loop if the connection to retrieve the certificate itself also
+   * requires a missing certificate. We could easily support FTP, but we don't,
+   * because that's silly. Also note that we don't support "certs-only" CMS
+   * messages, we only support directly retrieving a DER certificate. Finally,
+   * we don't support the case where accessLocation is a directoryName, so no
+   * private DAP or LDAP.
+   */
+  scheme = g_uri_parse_scheme (aia);
+  if (!scheme || strcmp (scheme, "http") != 0)
+    {
+      g_warning ("Authority Information Access URI %s uses unsupported URI scheme '%s'", scheme, aia);
+      goto out;
+    }
+
+  file = g_file_new_for_uri (aia);
+  istream = g_file_read (file, priv->verify_chain_cancellable, &error);
+  if (!istream)
+    {
+      g_warning ("Failed to download missing issuer certificate from Authority Information Access URI %s: 
failed g_file_read (do you need to install gvfs?): %s",
+                 aia, error->message);
+      goto out;
+    }
+
+  der = g_byte_array_sized_new (sizeof (buffer));
+  do
+    {
+      n_read = g_input_stream_read (G_INPUT_STREAM (istream), buffer, sizeof (buffer),
+                                    priv->verify_chain_cancellable, &error);
+      if (n_read == -1)
+        {
+          g_warning ("Failed to download missing issuer certificate from Authority Information Access URI 
%s: failed g_input_stream_read: %s",
+                     aia, error->message);
+          goto out;
+        }
+      g_byte_array_append (der, buffer, n_read);
+    } while (n_read > 0);
+
+  gnutls_free (datum.data);
+  datum.size = der->len;
+  datum.data = (unsigned char *)g_byte_array_free (der, FALSE);
+  der = NULL;
+
+  gerr = gnutls_x509_crt_list_import2 (issuers, issuers_size, &datum, GNUTLS_X509_FMT_DER, 0);
+  if (gerr < 0)
+    {
+      g_warning ("Failed to download missing issuer certificate from Authority Information Access URI %s: 
failed gnutls_x509_crt_import: %s",
+                 aia, gnutls_strerror (gerr));
+      goto out;
+    }
+
+  ret = 0;
+
+out:
+  if (error)
+    g_error_free (error);
+  if (file)
+    g_object_unref (file);
+  if (istream)
+    g_object_unref (istream);
+  if (der)
+    g_byte_array_unref (der);
+  gnutls_free (datum.data);
+  g_free (aia);
+  return ret;
+}
+#endif
+
 static void
 g_tls_database_gnutls_class_init (GTlsDatabaseGnutlsClass *klass)
 {
@@ -610,6 +743,10 @@ g_tls_database_gnutls_initable_init (GInitable     *initable,
     return FALSE;
 
   gnutls_x509_trust_list_init (&trust_list, 0);
+#if GNUTLS_VERSION_MAJOR > 3 || GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR >= 7
+  gnutls_x509_trust_list_set_getissuer_function (trust_list, issuer_missing_cb);
+  gnutls_x509_trust_list_set_ptr (trust_list, self);
+#endif
 
   g_assert (G_TLS_DATABASE_GNUTLS_GET_CLASS (self)->populate_trust_list);
   if (!G_TLS_DATABASE_GNUTLS_GET_CLASS (self)->populate_trust_list (self, trust_list, error))


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