[glib-networking/tls-database: 2/2] gnutls: Add initial implemention of GTlsDatabaseGnutls



commit 8b014927bd8f07f36491a2eaf8f0be1aab539256
Author: Stef Walter <stefw collabora co uk>
Date:   Thu Dec 16 03:10:51 2010 +0000

    gnutls: Add initial implemention of GTlsDatabaseGnutls
    
    This is a GTlsDatabase which uses gnutls to verify and build
    certificate chains. An abstract base class.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=636572

 tls/gnutls/Makefile.am           |    2 +
 tls/gnutls/gtlsdatabase-gnutls.c |  351 ++++++++++++++++++++++++++++++++++++++
 tls/gnutls/gtlsdatabase-gnutls.h |   69 ++++++++
 3 files changed, 422 insertions(+), 0 deletions(-)
---
diff --git a/tls/gnutls/Makefile.am b/tls/gnutls/Makefile.am
index a206b08..9b8c0f6 100644
--- a/tls/gnutls/Makefile.am
+++ b/tls/gnutls/Makefile.am
@@ -28,6 +28,8 @@ libgiognutls_la_SOURCES = 		\
 	gtlsclientconnection-gnutls.h	\
 	gtlsconnection-gnutls.c		\
 	gtlsconnection-gnutls.h		\
+	gtlsdatabase-gnutls.c		\
+	gtlsdatabase-gnutls.h		\
 	gtlsinputstream-gnutls.c	\
 	gtlsinputstream-gnutls.h	\
 	gtlsoutputstream-gnutls.c	\
diff --git a/tls/gnutls/gtlsdatabase-gnutls.c b/tls/gnutls/gtlsdatabase-gnutls.c
new file mode 100644
index 0000000..aeacf27
--- /dev/null
+++ b/tls/gnutls/gtlsdatabase-gnutls.c
@@ -0,0 +1,351 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright © 2010 Collabora, Ltd
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#include "config.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+
+#include "gtlsdatabase-gnutls.h"
+
+#include "gtlscertificate-gnutls.h"
+
+#include <glib/gi18n-lib.h>
+
+G_DEFINE_ABSTRACT_TYPE (GTlsDatabaseGnutls, g_tls_database_gnutls, G_TYPE_TLS_DATABASE);
+
+enum {
+    STATUS_FAILURE,
+    STATUS_INCOMPLETE,
+    STATUS_SELFSIGNED,
+    STATUS_PINNED,
+    STATUS_ANCHORED,
+};
+
+static void
+g_tls_database_gnutls_init (GTlsDatabaseGnutls *self)
+{
+
+}
+
+static GList*
+list_unref_free (GList *list)
+{
+  GList *l;
+  for (l = list; l; l = g_list_next (l))
+    g_object_unref (l->data);
+  g_list_free (list);
+  return NULL;
+}
+
+static GTlsCertificateGnutls*
+lookup_best_issuer (GTlsDatabaseGnutls *self,
+                    GTlsCertificateGnutls *certificate,
+                    GCancellable *cancellable,
+                    GError **error)
+{
+  GTlsCertificateGnutls *result;
+  gnutls_datum_t dn = { NULL, 0 };
+  gnutls_x509_crt_t cert;
+  GList *list;
+  int gerr;
+
+  g_assert (error);
+  g_assert (!*error);
+
+  /* Dig out the issuer of this certificate */
+  cert = g_tls_certificate_gnutls_get_cert (certificate);
+  gerr = gnutls_x509_crt_get_raw_issuer_dn (cert, &dn);
+  if (gerr < 0)
+    {
+      g_warning ("failed to get issuer of certificate: %s", gnutls_strerror (gerr));
+      return NULL;
+    }
+
+  list = g_tls_database_lookup_certificates (G_TLS_DATABASE (self),
+                                             G_TLS_DATABASE_LOOKUP_ISSUER,
+                                             dn.data,
+                                             dn.size,
+                                             cancellable,
+                                             error);
+
+  gnutls_free (dn.data);
+
+  if (*error)
+    return NULL;
+
+  /*
+   * TODO: For now we just select the first in the list, but do we need
+   * to select the best issuer based on notBefore/notAfter date, and
+   * perhaps the signature. Need to check RFC 5280
+   */
+  result = list ? list->data : NULL;
+  list_unref_free (list);
+  return result;
+}
+
+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 (GTlsDatabaseGnutls        *self,
+                         GList                     *input,
+                         GSocketConnectable        *identity,
+                         gint                       flags,
+                         GCancellable              *cancellable,
+                         GList                    **chain,
+                         GList                    **anchors,
+                         GError                   **error)
+{
+
+  GTlsCertificateGnutls *certificate;
+
+  g_assert (anchors);
+  g_assert (chain);
+  g_assert (error);
+  g_assert (!*error);
+
+  *chain = NULL;
+  *anchors = NULL;
+
+  /* Get the first certificate */
+  certificate = g_object_ref (G_TLS_CERTIFICATE_GNUTLS (input->data));
+  input = g_list_next (input);
+
+  /* Add this to the output chain */
+  *chain = g_list_append (*chain, certificate);
+
+  /* First check for pinned certificate */
+  if (g_tls_database_gnutls_lookup_assertion (self, certificate,
+                                              G_TLS_DATABASE_GNUTLS_PINNED_CERTIFICATE,
+                                              identity, cancellable, error))
+    {
+      return STATUS_PINNED;
+    }
+  else if(*error)
+    {
+      *chain = list_unref_free (*chain);
+      return STATUS_FAILURE;
+    }
+
+  for (;;)
+    {
+      /* Was the last certificate self-signed? */
+      if (is_self_signed (certificate))
+          return STATUS_SELFSIGNED;
+
+      /* Bring over the next certificate in the chain */
+      if (input)
+        {
+          /* XXXX should we be skipping certs with wrong issuer here? */
+          certificate = g_object_ref (G_TLS_CERTIFICATE_GNUTLS (input->data));
+          input = g_list_next (input);
+        }
+
+      /* No more in chain, try to lookup */
+      else
+        {
+          certificate = lookup_best_issuer (self, certificate, cancellable, error);
+          if (*error)
+            {
+              *chain = list_unref_free (*chain);
+              return STATUS_FAILURE;
+            }
+          else if (!certificate)
+            {
+              return STATUS_INCOMPLETE;
+            }
+        }
+
+      /* Now look up whether this certificate is an anchor */
+      if (g_tls_database_gnutls_lookup_assertion (self, certificate,
+                                                  G_TLS_DATABASE_GNUTLS_ANCHORED_CERTIFICATE,
+                                                  identity, cancellable, error))
+        {
+          *anchors = g_list_append (NULL, certificate);
+          return STATUS_ANCHORED;
+        }
+      else if (error)
+        {
+          g_object_unref (certificate);
+          *chain = list_unref_free (*chain);
+          return STATUS_FAILURE;
+        }
+      else
+        {
+          *chain = g_list_append (*chain, certificate);
+        }
+    }
+
+  g_assert_not_reached ();
+}
+
+static GTlsCertificateFlags
+double_check_before_after_dates (GList *chain)
+{
+  GTlsCertificateFlags gtls_flags = 0;
+  gnutls_x509_crt_t cert;
+  time_t t, now;
+  GList *l;
+
+  now = time (NULL);
+  for (l = chain; l; l = g_list_next (l))
+    {
+      cert = g_tls_certificate_gnutls_get_cert (l->data);
+      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;
+    }
+
+  return gtls_flags;
+}
+
+static void
+convert_certificate_list_to_gnutls (GList *chain,
+                                    gnutls_x509_crt_t **gnutls_chain,
+                                    guint *n_chain)
+{
+  guint i;
+
+  g_assert (gnutls_chain);
+  g_assert (n_chain);
+
+  *n_chain = g_list_length (chain);
+  if (*n_chain)
+    *gnutls_chain = g_new0 (gnutls_x509_crt_t, *n_chain);
+  else
+    *gnutls_chain = NULL;
+
+  for (i = 0; i < *n_chain; ++i, chain = g_list_next (chain))
+    (*gnutls_chain)[i] = g_tls_certificate_gnutls_get_cert (chain->data);
+}
+
+static GList*
+g_tls_database_gnutls_build_chain_and_verify (GTlsDatabase          *database,
+                                              GList                 *input,
+                                              GSocketConnectable    *identity,
+                                              gint                   flags,
+                                              GCancellable          *cancellable,
+                                              GTlsCertificateFlags  *verify_result,
+                                              GError               **error)
+{
+  GTlsDatabaseGnutls *self;
+  gnutls_x509_crt_t *gnutls_chain;
+  gnutls_x509_crt_t *gnutls_anchors;
+  GError *err = NULL;
+  GList *chain, *anchors;
+  guint gnutls_result;
+  guint n_anchors, n_chain;
+  gint status, gerr;
+
+  g_return_val_if_fail (G_IS_TLS_DATABASE_GNUTLS (database), NULL);
+  g_return_val_if_fail (input, NULL);
+  g_return_val_if_fail (G_IS_SOCKET_CONNECTABLE (identity), NULL);
+  g_return_val_if_fail (verify_result, NULL);
+  g_return_val_if_fail (!error || !*error, NULL);
+
+  self = G_TLS_DATABASE_GNUTLS (database);
+  chain = anchors = NULL;
+
+  status = build_certificate_chain (self, input, identity, flags, cancellable,
+                                    &chain, &anchors, &err);
+  if (err)
+    {
+      g_propagate_error (error, err);
+      return NULL;
+    }
+
+  /* A pinned certificate is verified on its own */
+  if (status == STATUS_PINNED)
+    {
+      list_unref_free (anchors);
+      *verify_result = 0;
+      return chain;
+    }
+
+  /* The certificate chain for validation by gnutls */
+  convert_certificate_list_to_gnutls (chain, &gnutls_chain, &n_chain);
+  convert_certificate_list_to_gnutls (anchors, &gnutls_anchors, &n_anchors);
+
+  gerr = gnutls_x509_crt_list_verify (gnutls_chain, n_chain,
+                                      gnutls_anchors, n_anchors,
+                                      NULL, 0, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT,
+                                      &gnutls_result);
+
+  list_unref_free (anchors);
+  g_free (gnutls_chain);
+  g_free (gnutls_anchors);
+
+  if (gerr != 0)
+    {
+      g_free (gnutls_chain);
+      g_free (gnutls_anchors);
+      *verify_result = G_TLS_CERTIFICATE_GENERIC_ERROR;
+      return chain;
+    }
+
+  *verify_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.
+   */
+  *verify_result |= double_check_before_after_dates (chain);
+
+  if (identity)
+    *verify_result |= g_tls_certificate_gnutls_verify_identity (chain->data, identity);
+
+  return chain;
+}
+
+static void
+g_tls_database_gnutls_class_init (GTlsDatabaseGnutlsClass *klass)
+{
+  GTlsDatabaseClass *database_class = G_TLS_DATABASE_CLASS (klass);
+  database_class->build_chain_and_verify = g_tls_database_gnutls_build_chain_and_verify;
+}
+
+gboolean
+g_tls_database_gnutls_lookup_assertion (GTlsDatabaseGnutls          *self,
+                                        GTlsCertificateGnutls       *certificate,
+                                        GTlsDatabaseGnutlsAssertion  assertion,
+                                        GSocketConnectable          *connectable,
+                                        GCancellable                *cancellable,
+                                        GError                     **error)
+{
+  g_return_val_if_fail (G_IS_TLS_DATABASE (self), FALSE);
+  g_return_val_if_fail (G_TLS_DATABASE_GNUTLS_GET_CLASS (self)->lookup_assertion, FALSE);
+  return G_TLS_DATABASE_GNUTLS_GET_CLASS (self)->lookup_assertion (self,
+                                                                   certificate,
+                                                                   assertion,
+                                                                   connectable,
+                                                                   cancellable,
+                                                                   error);
+}
diff --git a/tls/gnutls/gtlsdatabase-gnutls.h b/tls/gnutls/gtlsdatabase-gnutls.h
new file mode 100644
index 0000000..840265b
--- /dev/null
+++ b/tls/gnutls/gtlsdatabase-gnutls.h
@@ -0,0 +1,69 @@
+/* GIO - GLib Certificate, Output and Gnutlsing Library
+ *
+ * Copyright © 2010 Collabora, Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#ifndef __G_TLS_DATABASE_GNUTLS_H__
+#define __G_TLS_DATABASE_GNUTLS_H__
+
+#include <gio/gio.h>
+
+#include "gtlscertificate-gnutls.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+  G_TLS_DATABASE_GNUTLS_PINNED_CERTIFICATE = 1,
+  G_TLS_DATABASE_GNUTLS_ANCHORED_CERTIFICATE = 2,
+} GTlsDatabaseGnutlsAssertion;
+
+#define G_TYPE_TLS_DATABASE_GNUTLS            (g_tls_database_gnutls_get_type ())
+#define G_TLS_DATABASE_GNUTLS(inst)           (G_TYPE_CHECK_INSTANCE_CAST ((inst), G_TYPE_TLS_DATABASE_GNUTLS, GTlsDatabaseGnutls))
+#define G_TLS_DATABASE_GNUTLS_CLASS(class)    (G_TYPE_CHECK_CLASS_CAST ((class), G_TYPE_TLS_DATABASE_GNUTLS, GTlsDatabaseGnutlsClass))
+#define G_IS_TLS_DATABASE_GNUTLS(inst)        (G_TYPE_CHECK_INSTANCE_TYPE ((inst), G_TYPE_TLS_DATABASE_GNUTLS))
+#define G_IS_TLS_DATABASE_GNUTLS_CLASS(class) (G_TYPE_CHECK_CLASS_TYPE ((class), G_TYPE_TLS_DATABASE_GNUTLS))
+#define G_TLS_DATABASE_GNUTLS_GET_CLASS(inst) (G_TYPE_INSTANCE_GET_CLASS ((inst), G_TYPE_TLS_DATABASE_GNUTLS, GTlsDatabaseGnutlsClass))
+
+typedef struct _GTlsDatabaseGnutlsPrivate                   GTlsDatabaseGnutlsPrivate;
+typedef struct _GTlsDatabaseGnutlsClass                     GTlsDatabaseGnutlsClass;
+typedef struct _GTlsDatabaseGnutls                          GTlsDatabaseGnutls;
+
+struct _GTlsDatabaseGnutlsClass
+{
+  GTlsDatabaseClass parent_class;
+
+  gboolean       (*lookup_assertion)      (GTlsDatabaseGnutls          *self,
+                                           GTlsCertificateGnutls       *certificate,
+                                           GTlsDatabaseGnutlsAssertion  assertion,
+                                           GSocketConnectable          *connectable,
+                                           GCancellable                *cancellable,
+                                           GError                     **error);
+};
+
+struct _GTlsDatabaseGnutls
+{
+  GTlsDatabase parent_instance;
+  GTlsDatabaseGnutlsPrivate *priv;
+};
+
+GType          g_tls_database_gnutls_get_type              (void) G_GNUC_CONST;
+
+gboolean       g_tls_database_gnutls_lookup_assertion      (GTlsDatabaseGnutls          *self,
+                                                            GTlsCertificateGnutls       *certificate,
+                                                            GTlsDatabaseGnutlsAssertion  assertion,
+                                                            GSocketConnectable          *connectable,
+                                                            GCancellable                *cancellable,
+                                                            GError                     **error);
+
+G_END_DECLS
+
+#endif /* __G_TLS_DATABASE_GNUTLS_H___ */



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