[glib-networking] gnutls: Implement GTlsDatabase and related objects



commit e484f34fdd0d132acaedf0cbabc99708137e1293
Author: Stef Walter <stefw collabora co uk>
Date:   Thu Aug 4 09:08:28 2011 +0200

    gnutls: Implement GTlsDatabase and related objects
    
    The database is an abstract object implemented by the various TLS
    backends, which is used by GTlsConnection to lookup certificates
    and keys, as well as verify certificate chains.
    
    Here we implement a file based database to lookup anchor certificates
    for use with GnuTLS.
    
    Also hookup the various bits for GTlsInteraction, but don't actually
    use that yet.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=636572

 .gitignore                               |    2 +
 Makefile.am                              |    2 +
 configure.ac                             |    5 +-
 tls/gnutls/Makefile.am                   |    4 +
 tls/gnutls/gtlsbackend-gnutls.c          |  123 ++--
 tls/gnutls/gtlsbackend-gnutls.h          |   10 +-
 tls/gnutls/gtlscertificate-gnutls.c      |   15 +
 tls/gnutls/gtlscertificate-gnutls.h      |    4 +
 tls/gnutls/gtlsclientconnection-gnutls.c |   24 +-
 tls/gnutls/gtlsconnection-gnutls.c       |   83 ++-
 tls/gnutls/gtlsdatabase-gnutls.c         |  314 ++++++++++
 tls/gnutls/gtlsdatabase-gnutls.h         |   71 +++
 tls/gnutls/gtlsfiledatabase-gnutls.c     |  629 +++++++++++++++++++
 tls/gnutls/gtlsfiledatabase-gnutls.h     |   52 ++
 tls/gnutls/gtlsserverconnection-gnutls.c |   26 +-
 tls/tests/Makefile.am                    |   25 +
 tls/tests/files/ca-roots.pem             |  202 ++++++
 tls/tests/files/ca.pem                   |   23 +
 tls/tests/files/client-and-key.pem       |   45 ++
 tls/tests/files/client-future.pem        |   18 +
 tls/tests/files/client-past.pem          |   18 +
 tls/tests/files/client.pem               |   18 +
 tls/tests/files/server-and-key.pem       |   23 +
 tls/tests/files/server-self.pem          |   11 +
 tls/tests/files/server.der               |  Bin 0 -> 554 bytes
 tls/tests/files/server.pem               |   14 +
 tls/tests/tls.c                          |  995 ++++++++++++++++++++++++++++++
 27 files changed, 2652 insertions(+), 104 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 20f00a3..236597e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,5 @@ m4
 
 proxy/libproxy/glib-pacrunner
 proxy/libproxy/org.gtk.GLib.PACRunner.service
+
+/tls/tests/tls
diff --git a/Makefile.am b/Makefile.am
index 2c34280..41c7a60 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -17,6 +17,8 @@ if HAVE_GNUTLS
 SUBDIRS += tls/gnutls
 endif
 
+SUBDIRS += tls/tests
+
 install-exec-hook:
 	if test -n "$(GIO_QUERYMODULES)" -a -z "$(DESTDIR)"; then \
 		$(GIO_QUERYMODULES) $(GIO_MODULE_DIR) ;           \
diff --git a/configure.ac b/configure.ac
index 697b467..a355c65 100644
--- a/configure.ac
+++ b/configure.ac
@@ -157,8 +157,9 @@ AC_CONFIG_FILES([Makefile
                  po/Makefile.in po/Makefile
                  proxy/libproxy/Makefile
                  proxy/gnome/Makefile
-		 tls/gnutls/Makefile
-		])
+                 tls/gnutls/Makefile
+                 tls/tests/Makefile
+                ])
 AC_OUTPUT
 
 echo ""
diff --git a/tls/gnutls/Makefile.am b/tls/gnutls/Makefile.am
index a206b08..f9f1965 100644
--- a/tls/gnutls/Makefile.am
+++ b/tls/gnutls/Makefile.am
@@ -28,6 +28,10 @@ libgiognutls_la_SOURCES = 		\
 	gtlsclientconnection-gnutls.h	\
 	gtlsconnection-gnutls.c		\
 	gtlsconnection-gnutls.h		\
+	gtlsdatabase-gnutls.c		\
+	gtlsdatabase-gnutls.h		\
+	gtlsfiledatabase-gnutls.c	\
+	gtlsfiledatabase-gnutls.h	\
 	gtlsinputstream-gnutls.c	\
 	gtlsinputstream-gnutls.h	\
 	gtlsoutputstream-gnutls.c	\
diff --git a/tls/gnutls/gtlsbackend-gnutls.c b/tls/gnutls/gtlsbackend-gnutls.c
index da15b01..75b3e93 100644
--- a/tls/gnutls/gtlsbackend-gnutls.c
+++ b/tls/gnutls/gtlsbackend-gnutls.c
@@ -32,8 +32,15 @@
 #include "gtlsbackend-gnutls.h"
 #include "gtlscertificate-gnutls.h"
 #include "gtlsclientconnection-gnutls.h"
+#include "gtlsfiledatabase-gnutls.h"
 #include "gtlsserverconnection-gnutls.h"
 
+struct _GTlsBackendGnutlsPrivate
+{
+  GMutex *mutex;
+  GTlsDatabase *default_database;
+};
+
 static void g_tls_backend_gnutls_interface_init (GTlsBackendInterface *iface);
 
 G_DEFINE_DYNAMIC_TYPE_EXTENDED (GTlsBackendGnutls, g_tls_backend_gnutls, G_TYPE_OBJECT, 0,
@@ -120,95 +127,81 @@ g_tls_backend_gnutls_init (GTlsBackendGnutls *backend)
    * g_io_modules_scan_all_in_directory()).
    */
   g_once (&gnutls_inited, gtls_gnutls_init, NULL);
+
+  backend->priv = G_TYPE_INSTANCE_GET_PRIVATE (backend, G_TYPE_TLS_BACKEND_GNUTLS, GTlsBackendGnutlsPrivate);
+  backend->priv->mutex = g_mutex_new ();
 }
 
 static void
-g_tls_backend_gnutls_class_init (GTlsBackendGnutlsClass *backend_class)
+g_tls_backend_gnutls_finalize (GObject *object)
 {
+  GTlsBackendGnutls *backend = G_TLS_BACKEND_GNUTLS (object);
+
+  if (backend->priv->default_database)
+    g_object_unref (backend->priv->default_database);
+  g_mutex_free (backend->priv->mutex);
+
+  G_OBJECT_CLASS (g_tls_backend_gnutls_parent_class)->finalize (object);
 }
 
 static void
-g_tls_backend_gnutls_class_finalize (GTlsBackendGnutlsClass *backend_class)
+g_tls_backend_gnutls_class_init (GTlsBackendGnutlsClass *backend_class)
 {
+  GObjectClass *gobject_class = G_OBJECT_CLASS (backend_class);
+  gobject_class->finalize = g_tls_backend_gnutls_finalize;
+  g_type_class_add_private (backend_class, sizeof (GTlsBackendGnutlsPrivate));
 }
 
 static void
-g_tls_backend_gnutls_interface_init (GTlsBackendInterface *iface)
+g_tls_backend_gnutls_class_finalize (GTlsBackendGnutlsClass *backend_class)
 {
-  iface->get_certificate_type       = g_tls_certificate_gnutls_get_type;
-  iface->get_client_connection_type = g_tls_client_connection_gnutls_get_type;
-  iface->get_server_connection_type = g_tls_server_connection_gnutls_get_type;
 }
 
-#ifdef GTLS_SYSTEM_CA_FILE
-/* Parsing the system CA list takes a noticeable amount of time.
- * So we only do it once, and only when we actually need to see it.
- */
-static const GList *
-get_ca_lists (gnutls_x509_crt_t **cas,
-	      int                *num_cas)
+static GTlsDatabase*
+g_tls_backend_gnutls_get_default_database (GTlsBackend *backend)
 {
-  static gnutls_x509_crt_t *ca_list_gnutls;
-  static int ca_list_length;
-  static GList *ca_list;
+  GTlsBackendGnutls *self = G_TLS_BACKEND_GNUTLS (backend);
+  const gchar *anchor_file = NULL;
+  GTlsDatabase *result;
+  GError *error = NULL;
 
-  if (g_once_init_enter ((volatile gsize *)&ca_list_gnutls))
-    {
-      GError *error = NULL;
-      gnutls_x509_crt_t *x509_crts;
-      GList *c;
-      int i;
+  g_mutex_lock (self->priv->mutex);
 
-      ca_list = g_tls_certificate_list_new_from_file (GTLS_SYSTEM_CA_FILE, &error);
+  if (self->priv->default_database)
+    {
+      result = g_object_ref (self->priv->default_database);
+    }
+  else
+    {
+#ifdef GTLS_SYSTEM_CA_FILE
+      anchor_file = GTLS_SYSTEM_CA_FILE;
+#endif
+      result = g_tls_file_database_new (anchor_file, &error);
       if (error)
-	{
-	  g_warning ("Failed to read system CA file %s: %s.",
-		     GTLS_SYSTEM_CA_FILE, error->message);
-	  g_error_free (error);
-	  /* Note that this is not a security problem, since if
-	   * G_TLS_VALIDATE_CA is set, then this just means validation
-	   * will always fail, and if it isn't set, then it doesn't
-	   * matter that we couldn't read the CAs.
-	   */
-	}
-
-      ca_list_length = g_list_length (ca_list);
-      x509_crts = g_new (gnutls_x509_crt_t, ca_list_length);
-      for (c = ca_list, i = 0; c; c = c->next, i++)
-	x509_crts[i] = g_tls_certificate_gnutls_get_cert (c->data);
-
-      g_once_init_leave ((volatile gsize *)&ca_list_gnutls, GPOINTER_TO_SIZE (x509_crts));
+        {
+          g_warning ("couldn't load TLS file database: %s",
+                     error->message);
+          g_clear_error (&error);
+        }
+      else
+        {
+          self->priv->default_database = g_object_ref (result);
+        }
     }
 
-  if (cas)
-    *cas = ca_list_gnutls;
-  if (num_cas)
-    *num_cas = ca_list_length;
-  
-  return ca_list;
-}
-#endif
+  g_mutex_unlock (self->priv->mutex);
 
-const GList *
-g_tls_backend_gnutls_get_system_ca_list_gtls (void)
-{
-#ifdef GTLS_SYSTEM_CA_FILE
-  return get_ca_lists (NULL, NULL);
-#else
-  return NULL;
-#endif
+  return result;
 }
 
-void
-g_tls_backend_gnutls_get_system_ca_list_gnutls (gnutls_x509_crt_t **cas,
-						int                *num_cas)
+static void
+g_tls_backend_gnutls_interface_init (GTlsBackendInterface *iface)
 {
-#ifdef GTLS_SYSTEM_CA_FILE
-  get_ca_lists (cas, num_cas);
-#else
-  *cas = NULL;
-  *num_cas = 0;
-#endif
+  iface->get_certificate_type       = g_tls_certificate_gnutls_get_type;
+  iface->get_client_connection_type = g_tls_client_connection_gnutls_get_type;
+  iface->get_server_connection_type = g_tls_server_connection_gnutls_get_type;
+  iface->get_file_database_type =     g_tls_file_database_gnutls_get_type;
+  iface->get_default_database =       g_tls_backend_gnutls_get_default_database;
 }
 
 /* Session cache support; all the details are sort of arbitrary. Note
diff --git a/tls/gnutls/gtlsbackend-gnutls.h b/tls/gnutls/gtlsbackend-gnutls.h
index 97ebd90..6ab0344 100644
--- a/tls/gnutls/gtlsbackend-gnutls.h
+++ b/tls/gnutls/gtlsbackend-gnutls.h
@@ -25,8 +25,9 @@ G_BEGIN_DECLS
 #define G_IS_TLS_BACKEND_GNUTLS_CLASS(class) (G_TYPE_CHECK_CLASS_TYPE ((class), G_TYPE_TLS_BACKEND_GNUTLS))
 #define G_TLS_BACKEND_GNUTLS_GET_CLASS(inst) (G_TYPE_INSTANCE_GET_CLASS ((inst), G_TYPE_TLS_BACKEND_GNUTLS, GTlsBackendGnutlsClass))
 
-typedef struct _GTlsBackendGnutlsClass GTlsBackendGnutlsClass;
-typedef struct _GTlsBackendGnutls      GTlsBackendGnutls;
+typedef struct _GTlsBackendGnutls        GTlsBackendGnutls;
+typedef struct _GTlsBackendGnutlsClass   GTlsBackendGnutlsClass;
+typedef struct _GTlsBackendGnutlsPrivate GTlsBackendGnutlsPrivate;
 
 struct _GTlsBackendGnutlsClass
 {
@@ -36,15 +37,12 @@ struct _GTlsBackendGnutlsClass
 struct _GTlsBackendGnutls
 {
   GObject parent_instance;
+  GTlsBackendGnutlsPrivate *priv;
 };
 
 GType g_tls_backend_gnutls_get_type (void) G_GNUC_CONST;
 void  g_tls_backend_gnutls_register (GIOModule *module);
 
-const GList *g_tls_backend_gnutls_get_system_ca_list_gtls   (void) G_GNUC_CONST;
-void         g_tls_backend_gnutls_get_system_ca_list_gnutls (gnutls_x509_crt_t **cas,
-							     int                *num_cas);
-
 void         g_tls_backend_gnutls_cache_session_data        (const gchar *session_id,
 							     guchar      *session_data,
 							     gsize        session_data_length);
diff --git a/tls/gnutls/gtlscertificate-gnutls.c b/tls/gnutls/gtlscertificate-gnutls.c
index f4269a7..956235e 100644
--- a/tls/gnutls/gtlscertificate-gnutls.c
+++ b/tls/gnutls/gtlscertificate-gnutls.c
@@ -498,3 +498,18 @@ g_tls_certificate_gnutls_verify_identity (GTlsCertificateGnutls *gnutls,
 
   return G_TLS_CERTIFICATE_BAD_IDENTITY;
 }
+
+void
+g_tls_certificate_gnutls_set_issuer (GTlsCertificateGnutls *gnutls,
+                                     GTlsCertificateGnutls *issuer)
+{
+  g_return_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (gnutls));
+  g_return_if_fail (!issuer || G_IS_TLS_CERTIFICATE_GNUTLS (issuer));
+
+  if (issuer)
+    g_object_ref (issuer);
+  if (gnutls->priv->issuer)
+    g_object_unref (gnutls->priv->issuer);
+  gnutls->priv->issuer = issuer;
+  g_object_notify (G_OBJECT (gnutls), "issuer");
+}
diff --git a/tls/gnutls/gtlscertificate-gnutls.h b/tls/gnutls/gtlscertificate-gnutls.h
index a94117a..201dbb4 100644
--- a/tls/gnutls/gtlscertificate-gnutls.h
+++ b/tls/gnutls/gtlscertificate-gnutls.h
@@ -56,6 +56,10 @@ GTlsCertificateFlags         g_tls_certificate_gnutls_verify_identity (GTlsCerti
 
 GTlsCertificateFlags         g_tls_certificate_gnutls_convert_flags   (guint                  gnutls_flags);
 
+void                         g_tls_certificate_gnutls_set_issuer      (GTlsCertificateGnutls *gnutls,
+                                                                       GTlsCertificateGnutls *issuer);
+
+GTlsCertificateGnutls*       g_tls_certificate_gnutls_steal_issuer    (GTlsCertificateGnutls *gnutls);
 
 G_END_DECLS
 
diff --git a/tls/gnutls/gtlsclientconnection-gnutls.c b/tls/gnutls/gtlsclientconnection-gnutls.c
index 2710e9c..b6fc2fb 100644
--- a/tls/gnutls/gtlsclientconnection-gnutls.c
+++ b/tls/gnutls/gtlsclientconnection-gnutls.c
@@ -327,12 +327,30 @@ g_tls_client_connection_gnutls_verify_peer (GTlsConnectionGnutls  *conn_gnutls,
 					    GTlsCertificateFlags  *errors)
 {
   GTlsClientConnectionGnutls *gnutls = G_TLS_CLIENT_CONNECTION_GNUTLS (conn_gnutls);
+  GTlsDatabase *database;
   gboolean accepted;
+  GError *error = NULL;
 
-  if (gnutls->priv->server_identity)
+  database = g_tls_connection_get_database (G_TLS_CONNECTION (conn_gnutls));
+  if (database == NULL)
+    {
+      *errors |= G_TLS_CERTIFICATE_UNKNOWN_CA;
+      *errors |= g_tls_certificate_verify (peer_certificate, gnutls->priv->server_identity, NULL);
+    }
+  else
     {
-      *errors |= g_tls_certificate_gnutls_verify_identity (G_TLS_CERTIFICATE_GNUTLS (peer_certificate),
-							   gnutls->priv->server_identity);
+      *errors |= g_tls_database_verify_chain (database, peer_certificate,
+                                              G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER,
+                                              gnutls->priv->server_identity,
+                                              g_tls_connection_get_interaction (G_TLS_CONNECTION (gnutls)),
+                                              G_TLS_DATABASE_VERIFY_NONE,
+                                              NULL, &error);
+      if (error)
+        {
+          g_warning ("failure verifying certificate chain: %s",
+                     error->message);
+          g_clear_error (&error);
+        }
     }
 
   if (*errors & gnutls->priv->validation_flags)
diff --git a/tls/gnutls/gtlsconnection-gnutls.c b/tls/gnutls/gtlsconnection-gnutls.c
index 5b15910..48e05d6 100644
--- a/tls/gnutls/gtlsconnection-gnutls.c
+++ b/tls/gnutls/gtlsconnection-gnutls.c
@@ -99,7 +99,9 @@ enum
   PROP_REQUIRE_CLOSE_NOTIFY,
   PROP_REHANDSHAKE_MODE,
   PROP_USE_SYSTEM_CERTDB,
+  PROP_DATABASE,
   PROP_CERTIFICATE,
+  PROP_INTERACTION,
   PROP_PEER_CERTIFICATE,
   PROP_PEER_CERTIFICATE_ERRORS
 };
@@ -110,7 +112,6 @@ struct _GTlsConnectionGnutlsPrivate
   GPollableInputStream *base_istream;
   GPollableOutputStream *base_ostream;
 
-  GList *ca_list;
   gnutls_certificate_credentials creds;
   gnutls_session session;
 
@@ -118,13 +119,17 @@ struct _GTlsConnectionGnutlsPrivate
   GTlsCertificateFlags peer_certificate_errors;
   gboolean require_close_notify;
   GTlsRehandshakeMode rehandshake_mode;
-  gboolean use_system_certdb;
+  gboolean is_system_certdb;
+  GTlsDatabase *database;
+  gboolean database_is_unset;
   gboolean need_handshake, handshaking, ever_handshaked;
   gboolean closing;
 
   GInputStream *tls_istream;
   GOutputStream *tls_ostream;
 
+  GTlsInteraction *interaction;
+
   GError *error;
   GCancellable *cancellable;
   gboolean blocking, eof;
@@ -158,7 +163,9 @@ g_tls_connection_gnutls_class_init (GTlsConnectionGnutlsClass *klass)
   g_object_class_override_property (gobject_class, PROP_REQUIRE_CLOSE_NOTIFY, "require-close-notify");
   g_object_class_override_property (gobject_class, PROP_REHANDSHAKE_MODE, "rehandshake-mode");
   g_object_class_override_property (gobject_class, PROP_USE_SYSTEM_CERTDB, "use-system-certdb");
+  g_object_class_override_property (gobject_class, PROP_DATABASE, "database");
   g_object_class_override_property (gobject_class, PROP_CERTIFICATE, "certificate");
+  g_object_class_override_property (gobject_class, PROP_INTERACTION, "interaction");
   g_object_class_override_property (gobject_class, PROP_PEER_CERTIFICATE, "peer-certificate");
   g_object_class_override_property (gobject_class, PROP_PEER_CERTIFICATE_ERRORS, "peer-certificate-errors");
 }
@@ -179,6 +186,9 @@ g_tls_connection_gnutls_init (GTlsConnectionGnutls *gnutls)
 				       GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
 
   gnutls->priv->need_handshake = TRUE;
+
+  gnutls->priv->database_is_unset = TRUE;
+  gnutls->priv->is_system_certdb = TRUE;
 }
 
 static gnutls_priority_t priorities[2][2];
@@ -274,11 +284,15 @@ g_tls_connection_gnutls_finalize (GObject *object)
   if (connection->priv->creds)
     gnutls_certificate_free_credentials (connection->priv->creds);
 
+  if (connection->priv->database)
+    g_object_unref (connection->priv->database);
   if (connection->priv->certificate)
     g_object_unref (connection->priv->certificate);
   if (connection->priv->peer_certificate)
     g_object_unref (connection->priv->peer_certificate);
 
+  g_clear_object (&connection->priv->interaction);
+
   if (connection->priv->error)
     g_error_free (connection->priv->error);
 
@@ -292,6 +306,7 @@ g_tls_connection_gnutls_get_property (GObject    *object,
 				      GParamSpec *pspec)
 {
   GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object);
+  GTlsBackend *backend;
 
   switch (prop_id)
     {
@@ -308,13 +323,27 @@ g_tls_connection_gnutls_get_property (GObject    *object,
       break;
 
     case PROP_USE_SYSTEM_CERTDB:
-      g_value_set_boolean (value, gnutls->priv->use_system_certdb);
+      g_value_set_boolean (value, gnutls->priv->is_system_certdb);
+      break;
+
+    case PROP_DATABASE:
+      if (gnutls->priv->database_is_unset)
+        {
+          backend = g_tls_backend_get_default ();
+          gnutls->priv->database =  g_tls_backend_get_default_database (backend);
+          gnutls->priv->database_is_unset = FALSE;
+        }
+      g_value_set_object (value, gnutls->priv->database);
       break;
 
     case PROP_CERTIFICATE:
       g_value_set_object (value, gnutls->priv->certificate);
       break;
 
+    case PROP_INTERACTION:
+      g_value_set_object (value, gnutls->priv->interaction);
+      break;
+
     case PROP_PEER_CERTIFICATE:
       g_value_set_object (value, gnutls->priv->peer_certificate);
       break;
@@ -337,6 +366,8 @@ g_tls_connection_gnutls_set_property (GObject      *object,
   GTlsConnectionGnutls *gnutls = G_TLS_CONNECTION_GNUTLS (object);
   GInputStream *istream;
   GOutputStream *ostream;
+  gboolean system_certdb;
+  GTlsBackend *backend;
 
   switch (prop_id)
     {
@@ -371,17 +402,24 @@ g_tls_connection_gnutls_set_property (GObject      *object,
       break;
 
     case PROP_USE_SYSTEM_CERTDB:
-      gnutls->priv->use_system_certdb = g_value_get_boolean (value);
-
-      gnutls_certificate_free_cas (gnutls->priv->creds);
-      if (gnutls->priv->use_system_certdb)
-	{
-	  gnutls_x509_crt_t *cas;
-	  int num_cas;
+      system_certdb = g_value_get_boolean (value);
+      if (system_certdb != gnutls->priv->is_system_certdb)
+        {
+          g_clear_object (&gnutls->priv->database);
+          if (system_certdb)
+            {
+              backend = g_tls_backend_get_default ();
+              gnutls->priv->database = g_tls_backend_get_default_database (backend);
+            }
+          gnutls->priv->is_system_certdb = system_certdb;
+        }
+      break;
 
-	  g_tls_backend_gnutls_get_system_ca_list_gnutls (&cas, &num_cas);
-	  gnutls_certificate_set_x509_trust (gnutls->priv->creds, cas, num_cas);
-	}
+    case PROP_DATABASE:
+      g_clear_object (&gnutls->priv->database);
+      gnutls->priv->database = g_value_dup_object (value);
+      gnutls->priv->is_system_certdb = FALSE;
+      gnutls->priv->database_is_unset = FALSE;
       break;
 
     case PROP_CERTIFICATE:
@@ -390,6 +428,11 @@ g_tls_connection_gnutls_set_property (GObject      *object,
       gnutls->priv->certificate = g_value_dup_object (value);
       break;
 
+    case PROP_INTERACTION:
+      g_clear_object (&gnutls->priv->interaction);
+      gnutls->priv->interaction = g_value_dup_object (value);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -860,20 +903,6 @@ handshake_internal (GTlsConnectionGnutls  *gnutls,
 
   if (peer_certificate)
     {
-      int status;
-
-      status = gnutls_certificate_verify_peers (gnutls->priv->session);
-      peer_certificate_errors = g_tls_certificate_gnutls_convert_flags (status);
-      if (peer_certificate_errors)
-	{
-	  /* gnutls_certificate_verify_peers() bails out on the first
-	   * error, which may be G_TLS_CERTIFICATE_UNKNOWN_CA, but the
-	   * caller may be planning to check that part themselves. So
-	   * call g_tls_certificate_verify() to get any other errors.
-	   */
-	  peer_certificate_errors |= g_tls_certificate_verify (peer_certificate, NULL, NULL);
-	}
-
       if (!G_TLS_CONNECTION_GNUTLS_GET_CLASS (gnutls)->verify_peer (gnutls, peer_certificate, &peer_certificate_errors))
 	{
 	  g_object_unref (peer_certificate);
diff --git a/tls/gnutls/gtlsdatabase-gnutls.c b/tls/gnutls/gtlsdatabase-gnutls.c
new file mode 100644
index 0000000..3e21052
--- /dev/null
+++ b/tls/gnutls/gtlsdatabase-gnutls.c
@@ -0,0 +1,314 @@
+/* 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 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,
+                         GTlsCertificateGnutls   *chain,
+                         const gchar             *purpose,
+                         GSocketConnectable      *identity,
+                         GTlsInteraction         *interaction,
+                         GTlsDatabaseVerifyFlags  flags,
+                         GCancellable            *cancellable,
+                         GTlsCertificateGnutls  **anchor,
+                         GError                 **error)
+{
+
+  GTlsCertificateGnutls *certificate;
+  GTlsCertificate *issuer;
+
+  g_assert (anchor);
+  g_assert (chain);
+  g_assert (purpose);
+  g_assert (error);
+  g_assert (!*error);
+
+  /*
+   * Remember that the first certificate never changes in the chain.
+   * When we find a self-signed, pinned or anchored certificate, all
+   * issuers are truncated from the chain.
+   */
+
+  *anchor = NULL;
+  certificate = chain;
+
+  /* First check for pinned certificate */
+  if (g_tls_database_gnutls_lookup_assertion (self, certificate,
+                                              G_TLS_DATABASE_GNUTLS_PINNED_CERTIFICATE,
+                                              purpose, identity, cancellable, error))
+    {
+      g_tls_certificate_gnutls_set_issuer (certificate, NULL);
+      return STATUS_PINNED;
+    }
+  else if(*error)
+    {
+      return STATUS_FAILURE;
+    }
+
+  for (;;)
+    {
+      /* Was the last certificate self-signed? */
+      if (is_self_signed (certificate))
+        {
+          g_tls_certificate_gnutls_set_issuer (certificate, NULL);
+          return STATUS_SELFSIGNED;
+        }
+
+      /* 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);
+        }
+
+      /* Search for the next certificate in chain */
+      else
+        {
+          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;
+          else 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));
+        }
+
+      g_assert (issuer);
+      certificate = G_TLS_CERTIFICATE_GNUTLS (issuer);
+
+      /* Now look up whether this certificate is an anchor */
+      if (g_tls_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;
+        }
+    }
+
+  g_assert_not_reached ();
+}
+
+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,
+                                     guint                    *gnutls_chain_length)
+{
+  GTlsCertificate *cert;
+  guint i;
+
+  g_assert (gnutls_chain);
+  g_assert (gnutls_chain_length);
+
+  for (*gnutls_chain_length = 0, cert = G_TLS_CERTIFICATE (chain);
+      cert; cert = g_tls_certificate_get_issuer (cert))
+    ++(*gnutls_chain_length);
+
+  *gnutls_chain = g_new0 (gnutls_x509_crt_t, *gnutls_chain_length);
+
+  for (i = 0, cert = G_TLS_CERTIFICATE (chain);
+      cert; cert = g_tls_certificate_get_issuer (cert), ++i)
+    (*gnutls_chain)[i] = g_tls_certificate_gnutls_get_cert (G_TLS_CERTIFICATE_GNUTLS (cert));
+
+  g_assert (i == *gnutls_chain_length);
+}
+
+static GTlsCertificateFlags
+g_tls_database_gnutls_verify_chain (GTlsDatabase           *database,
+                                    GTlsCertificate        *chain,
+                                    const gchar            *purpose,
+                                    GSocketConnectable     *identity,
+                                    GTlsInteraction        *interaction,
+                                    GTlsDatabaseVerifyFlags flags,
+                                    GCancellable           *cancellable,
+                                    GError                **error)
+{
+  GTlsDatabaseGnutls *self;
+  GTlsCertificateFlags result;
+  GError *err = NULL;
+  GTlsCertificateGnutls *anchor;
+  guint gnutls_result;
+  gnutls_x509_crt_t *certs, *anchors;
+  guint certs_length, anchors_length;
+  gint status, gerr;
+
+  g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (chain),
+                        G_TLS_CERTIFICATE_GENERIC_ERROR);
+
+  self = G_TLS_DATABASE_GNUTLS (database);
+  anchor = NULL;
+
+  status = build_certificate_chain (self, G_TLS_CERTIFICATE_GNUTLS (chain), purpose,
+                                    identity, interaction, flags, cancellable, &anchor, &err);
+  if (status == STATUS_FAILURE)
+    {
+      g_propagate_error (error, err);
+      return G_TLS_CERTIFICATE_GENERIC_ERROR;
+    }
+
+  /*
+   * A pinned certificate is verified on its own, without any further
+   * verification.
+   */
+  if (status == STATUS_PINNED)
+      return 0;
+
+  convert_certificate_chain_to_gnutls (G_TLS_CERTIFICATE_GNUTLS (chain),
+                                       &certs, &certs_length);
+
+  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);
+    }
+  else
+    {
+      anchors = NULL;
+      anchors_length = 0;
+    }
+
+  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 (anchors);
+
+  if (gerr != 0)
+      return G_TLS_CERTIFICATE_GENERIC_ERROR;
+
+  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
+g_tls_database_gnutls_class_init (GTlsDatabaseGnutlsClass *klass)
+{
+  GTlsDatabaseClass *database_class = G_TLS_DATABASE_CLASS (klass);
+  database_class->verify_chain = g_tls_database_gnutls_verify_chain;
+}
+
+gboolean
+g_tls_database_gnutls_lookup_assertion (GTlsDatabaseGnutls          *self,
+                                        GTlsCertificateGnutls       *certificate,
+                                        GTlsDatabaseGnutlsAssertion  assertion,
+                                        const gchar                 *purpose,
+                                        GSocketConnectable          *identity,
+                                        GCancellable                *cancellable,
+                                        GError                     **error)
+{
+  g_return_val_if_fail (G_IS_TLS_DATABASE_GNUTLS (self), FALSE);
+  g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (certificate), FALSE);
+  g_return_val_if_fail (purpose, FALSE);
+  g_return_val_if_fail (!identity || G_IS_SOCKET_CONNECTABLE (identity), FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+  g_return_val_if_fail (!error || !*error, 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,
+                                                                   purpose,
+                                                                   identity,
+                                                                   cancellable,
+                                                                   error);
+}
diff --git a/tls/gnutls/gtlsdatabase-gnutls.h b/tls/gnutls/gtlsdatabase-gnutls.h
new file mode 100644
index 0000000..f4bf8a5
--- /dev/null
+++ b/tls/gnutls/gtlsdatabase-gnutls.h
@@ -0,0 +1,71 @@
+/* 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,
+                                           const gchar                 *purpose,
+                                           GSocketConnectable          *identity,
+                                           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,
+                                                            const gchar                 *purpose,
+                                                            GSocketConnectable          *identity,
+                                                            GCancellable                *cancellable,
+                                                            GError                     **error);
+
+G_END_DECLS
+
+#endif /* __G_TLS_DATABASE_GNUTLS_H___ */
diff --git a/tls/gnutls/gtlsfiledatabase-gnutls.c b/tls/gnutls/gtlsfiledatabase-gnutls.c
new file mode 100644
index 0000000..22abfa4
--- /dev/null
+++ b/tls/gnutls/gtlsfiledatabase-gnutls.c
@@ -0,0 +1,629 @@
+/* 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 "gtlsfiledatabase-gnutls.h"
+
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+#include <gnutls/x509.h>
+
+static void g_tls_file_database_gnutls_file_database_interface_init (GTlsFileDatabaseInterface *iface);
+
+static void g_tls_file_database_gnutls_initable_interface_init (GInitableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GTlsFileDatabaseGnutls, g_tls_file_database_gnutls, G_TYPE_TLS_DATABASE_GNUTLS,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_TLS_FILE_DATABASE,
+                                                g_tls_file_database_gnutls_file_database_interface_init);
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                g_tls_file_database_gnutls_initable_interface_init);
+);
+
+enum
+{
+  PROP_0,
+  PROP_ANCHORS,
+};
+
+struct _GTlsFileDatabaseGnutlsPrivate
+{
+  /* read-only after construct */
+  gchar *anchor_filename;
+
+  /* protected by mutex */
+  GMutex *mutex;
+
+  /*
+   * These are hash tables of GByteArray -> GPtrArray<GByteArray>. The values of
+   * the ptr array are full DER encoded certificate values. The keys are byte
+   * arrays containing either subject DNs, issuer DNs, or full DER encoded certs
+   */
+  GHashTable *subjects;
+  GHashTable *issuers;
+
+  /*
+   * This is a table of GByteArray -> GByteArray. The values and keys are
+   * DER encoded certificate values.
+   */
+  GHashTable *complete;
+
+  /*
+   * This is a table of gchar * -> GPtrArray<GByteArray>. The values of
+   * the ptr array are full DER encoded certificate values. The keys are the
+   * string handles. This array is populated on demand.
+   */
+  GHashTable *handles;
+};
+
+static guint
+byte_array_hash (gconstpointer v)
+{
+  const GByteArray *array = v;
+  const signed char *p;
+  guint32 h = 0;
+  gsize i;
+
+  g_assert (array);
+  g_assert (array->data);
+  p = (signed char*)array->data;
+
+  /* 31 bit hash function */
+  for (i = 0; i < array->len; ++i, ++p)
+    h = (h << 5) - h + *p;
+
+  return h;
+}
+
+static gboolean
+byte_array_equal (gconstpointer v1, gconstpointer v2)
+{
+  const GByteArray *array1 = v1;
+  const GByteArray *array2 = v2;
+
+  if (array1 == array2)
+    return TRUE;
+  if (!array1 || !array2)
+    return FALSE;
+
+  if (array1->len != array2->len)
+    return FALSE;
+
+  if (array1->data == array2->data)
+    return TRUE;
+  if (!array1->data || !array2->data)
+    return FALSE;
+
+  return (memcmp (array1->data, array2->data, array1->len) == 0) ? TRUE : FALSE;
+}
+
+static GHashTable *
+multi_byte_array_hash_new (void)
+{
+  return g_hash_table_new_full (byte_array_hash, byte_array_equal,
+                                (GDestroyNotify)g_byte_array_unref,
+                                (GDestroyNotify)g_ptr_array_unref);
+}
+
+static void
+multi_byte_array_hash_insert (GHashTable *table, GByteArray *key, GByteArray *value)
+{
+  GPtrArray *multi;
+
+  multi = g_hash_table_lookup (table, key);
+  if (multi == NULL)
+    {
+      multi = g_ptr_array_new_with_free_func ((GDestroyNotify)g_byte_array_unref);
+      g_hash_table_insert (table, g_byte_array_ref (key), multi);
+    }
+  g_ptr_array_add (multi, g_byte_array_ref (value));
+}
+
+static GByteArray *
+multi_byte_array_hash_lookup_one (GHashTable *table, GByteArray *key)
+{
+  GPtrArray *multi;
+
+  multi = g_hash_table_lookup (table, key);
+  if (multi == NULL)
+    return NULL;
+
+  g_assert (multi->len > 0);
+  return multi->pdata[0];
+}
+
+static GPtrArray *
+multi_byte_array_hash_lookup_all (GHashTable *table, GByteArray *key)
+{
+  return g_hash_table_lookup (table, key);
+}
+
+static gchar *
+create_handle_for_certificate (const gchar *filename,
+                               GByteArray  *der)
+{
+  gchar *bookmark;
+  gchar *uri_part;
+  gchar *uri;
+
+  /*
+   * Here we create a URI that looks like:
+   * file:///etc/ssl/certs/ca-certificates.crt#11b2641821252596420e468c275771f5e51022c121a17bd7a89a2f37b6336c8f
+   */
+
+  uri_part = g_filename_to_uri (filename, NULL, NULL);
+  if (!uri_part)
+    return NULL;
+
+  bookmark = g_compute_checksum_for_data (G_CHECKSUM_SHA256,
+                                          der->data, der->len);
+  uri = g_strconcat (uri_part, "#", bookmark, NULL);
+
+  g_free (bookmark);
+  g_free (uri_part);
+
+  return uri;
+}
+
+static GHashTable *
+create_handles_array_unlocked (const gchar *filename,
+                               GHashTable  *complete)
+{
+  GHashTable *handles;
+  GHashTableIter iter;
+  GByteArray *der;
+  gchar *handle;
+
+  handles = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+                                   (GDestroyNotify)g_byte_array_unref);
+
+  g_hash_table_iter_init (&iter, complete);
+  while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&der))
+    {
+      handle = create_handle_for_certificate (filename, der);
+      if (handle != NULL)
+        g_hash_table_insert (handles, handle, g_byte_array_ref (der));
+    }
+
+  return handles;
+}
+
+static gboolean
+load_anchor_file (const gchar *filename,
+                  GHashTable  *subjects,
+                  GHashTable  *issuers,
+                  GHashTable  *complete,
+                  GError     **error)
+{
+  GList *list, *l;
+  gnutls_x509_crt_t cert;
+  gnutls_datum_t dn;
+  GByteArray *der;
+  GByteArray *subject;
+  GByteArray *issuer;
+  gint gerr;
+
+  g_assert (error);
+
+  list = g_tls_certificate_list_new_from_file (filename, error);
+  if (*error)
+    return FALSE;
+
+
+  for (l = list; l; l = g_list_next (l))
+    {
+      cert = g_tls_certificate_gnutls_get_cert (l->data);
+      gerr = gnutls_x509_crt_get_raw_dn (cert, &dn);
+      if (gerr < 0)
+        {
+          g_warning ("failed to get subject of anchor certificate: %s",
+                     gnutls_strerror (gerr));
+          continue;
+        }
+
+      subject = g_byte_array_new ();
+      g_byte_array_append (subject, dn.data, dn.size);
+      gnutls_free (dn.data);
+
+      gerr = gnutls_x509_crt_get_raw_issuer_dn (cert, &dn);
+      if (gerr < 0)
+        {
+          g_warning ("failed to get subject of anchor certificate: %s",
+                     gnutls_strerror (gerr));
+          continue;
+        }
+
+      issuer = g_byte_array_new ();
+      g_byte_array_append (issuer, dn.data, dn.size);
+      gnutls_free (dn.data);
+
+      /* Dig out the full value of this certificate's DER encoding */
+      der = NULL;
+      g_object_get (l->data, "certificate", &der, NULL);
+      g_return_val_if_fail (der, FALSE);
+
+      /* Three different ways of looking up same certificate */
+      multi_byte_array_hash_insert (subjects, subject, der);
+      multi_byte_array_hash_insert (issuers, issuer, der);
+
+      g_hash_table_insert (complete, g_byte_array_ref (der),
+                           g_byte_array_ref (der));
+
+      g_byte_array_unref (der);
+      g_byte_array_unref (subject);
+      g_byte_array_unref (issuer);
+    }
+
+  return TRUE;
+}
+
+
+
+static void
+g_tls_file_database_gnutls_finalize (GObject *object)
+{
+  GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (object);
+
+  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_free (self->priv->mutex);
+
+  G_OBJECT_CLASS (g_tls_file_database_gnutls_parent_class)->finalize (object);
+}
+
+static void
+g_tls_file_database_gnutls_get_property (GObject    *object,
+                                         guint       prop_id,
+                                         GValue     *value,
+                                         GParamSpec *pspec)
+{
+  GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (object);
+
+  switch (prop_id)
+    {
+    case PROP_ANCHORS:
+      g_value_set_string (value, self->priv->anchor_filename);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+g_tls_file_database_gnutls_set_property (GObject      *object,
+                                         guint         prop_id,
+                                         const GValue *value,
+                                         GParamSpec   *pspec)
+{
+  GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (object);
+  gchar *anchor_path;
+
+  switch (prop_id)
+    {
+    case PROP_ANCHORS:
+      anchor_path = g_value_dup_string (value);
+      if (anchor_path && !g_path_is_absolute (anchor_path))
+        {
+          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);
+    }
+}
+
+static void
+g_tls_file_database_gnutls_init (GTlsFileDatabaseGnutls *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+                                            G_TYPE_TLS_FILE_DATABASE_GNUTLS,
+                                            GTlsFileDatabaseGnutlsPrivate);
+  self->priv->mutex = g_mutex_new ();
+}
+
+static gchar*
+g_tls_file_database_gnutls_create_certificate_handle (GTlsDatabase            *database,
+                                                      GTlsCertificate         *certificate)
+{
+  GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (database);
+  GByteArray *der;
+  gboolean contains;
+  gchar *handle = NULL;
+
+  g_object_get (certificate, "certificate", &der, NULL);
+  g_return_val_if_fail (der, FALSE);
+
+  g_mutex_lock (self->priv->mutex);
+
+  /* At the same time look up whether this certificate is in list */
+  contains = g_hash_table_lookup (self->priv->complete, der) ? TRUE : FALSE;
+
+  g_mutex_unlock (self->priv->mutex);
+
+  /* Certificate is in the database */
+  if (contains)
+    handle = create_handle_for_certificate (self->priv->anchor_filename, der);
+
+  g_byte_array_unref (der);
+  return handle;
+}
+
+static GTlsCertificate*
+g_tls_file_database_gnutls_lookup_certificate_for_handle (GTlsDatabase            *database,
+                                                          const gchar             *handle,
+                                                          GTlsInteraction         *interaction,
+                                                          GTlsDatabaseLookupFlags  flags,
+                                                          GCancellable            *cancellable,
+                                                          GError                 **error)
+{
+  GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (database);
+  GByteArray *der;
+  gnutls_datum_t datum;
+
+  if (!handle)
+    return NULL;
+
+  g_mutex_lock (self->priv->mutex);
+
+  /* Create the handles table if not already done */
+  if (!self->priv->handles)
+    self->priv->handles = create_handles_array_unlocked (self->priv->anchor_filename,
+                                                         self->priv->complete);
+
+    der = g_hash_table_lookup (self->priv->handles, handle);
+
+  g_mutex_unlock (self->priv->mutex);
+
+  if (der == NULL)
+    return NULL;
+
+  datum.data = der->data;
+  datum.size = der->len;
+
+  return g_tls_certificate_gnutls_new (&datum, NULL);
+}
+
+static gboolean
+g_tls_file_database_gnutls_lookup_assertion (GTlsDatabaseGnutls          *database,
+                                             GTlsCertificateGnutls       *certificate,
+                                             GTlsDatabaseGnutlsAssertion  assertion,
+                                             const gchar                 *purpose,
+                                             GSocketConnectable          *identity,
+                                             GCancellable                *cancellable,
+                                             GError                     **error)
+{
+  GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (database);
+  GByteArray *der = NULL;
+  gboolean contains;
+
+  /* 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.
+   */
+
+  g_object_get (certificate, "certificate", &der, NULL);
+  g_return_val_if_fail (der, FALSE);
+
+  g_mutex_lock (self->priv->mutex);
+  contains = g_hash_table_lookup (self->priv->complete, der) ? TRUE : FALSE;
+  g_mutex_unlock (self->priv->mutex);
+
+  g_byte_array_unref (der);
+
+  /* All certificates in our file are anchored certificates */
+  return contains;
+}
+
+static GTlsCertificate*
+g_tls_file_database_gnutls_lookup_certificate_issuer (GTlsDatabase           *database,
+                                                      GTlsCertificate        *certificate,
+                                                      GTlsInteraction        *interaction,
+                                                      GTlsDatabaseLookupFlags flags,
+                                                      GCancellable           *cancellable,
+                                                      GError                **error)
+{
+  GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (database);
+  gnutls_datum_t dn = { NULL, 0 };
+  GByteArray *subject, *der;
+  gnutls_datum_t datum;
+  GTlsCertificate *issuer = NULL;
+  gnutls_x509_crt_t cert;
+  int gerr;
+
+  g_return_val_if_fail (G_IS_TLS_CERTIFICATE_GNUTLS (certificate), NULL);
+
+  if (flags & G_TLS_DATABASE_LOOKUP_KEYPAIR)
+    return NULL;
+
+  /* Dig out the issuer of this certificate */
+  cert = g_tls_certificate_gnutls_get_cert (G_TLS_CERTIFICATE_GNUTLS (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;
+    }
+
+  subject = g_byte_array_new ();
+  g_byte_array_append (subject, dn.data, dn.size);
+  gnutls_free (dn.data);
+
+  /* Find the full DER value of the certificate */
+  g_mutex_lock (self->priv->mutex);
+  der = multi_byte_array_hash_lookup_one (self->priv->subjects, subject);
+  g_mutex_unlock (self->priv->mutex);
+
+  g_byte_array_unref (subject);
+
+  if (der != NULL)
+    {
+      datum.data = der->data;
+      datum.size = der->len;
+      issuer = g_tls_certificate_gnutls_new (&datum, NULL);
+    }
+
+  return issuer;
+}
+
+static GList*
+g_tls_file_database_gnutls_lookup_certificates_issued_by (GTlsDatabase           *database,
+                                                          GByteArray             *issuer_raw_dn,
+                                                          GTlsInteraction        *interaction,
+                                                          GTlsDatabaseLookupFlags flags,
+                                                          GCancellable           *cancellable,
+                                                          GError                **error)
+{
+  GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (database);
+  GByteArray *der;
+  gnutls_datum_t datum;
+  GList *issued = NULL;
+  GPtrArray *ders;
+  guint i;
+
+  /* We don't have any private keys here */
+  if (flags & G_TLS_DATABASE_LOOKUP_KEYPAIR)
+    return NULL;
+
+  /* Find the full DER value of the certificate */
+  g_mutex_lock (self->priv->mutex);
+  ders = multi_byte_array_hash_lookup_all (self->priv->issuers, issuer_raw_dn);
+  g_mutex_unlock (self->priv->mutex);
+
+  for (i = 0; ders && i < ders->len; i++)
+    {
+      der = ders->pdata[i];
+      datum.data = der->data;
+      datum.size = der->len;
+      issued = g_list_prepend (issued, g_tls_certificate_gnutls_new (&datum, NULL));
+    }
+
+  return issued;
+}
+
+static void
+g_tls_file_database_gnutls_class_init (GTlsFileDatabaseGnutlsClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GTlsDatabaseClass *database_class = G_TLS_DATABASE_CLASS (klass);
+  GTlsDatabaseGnutlsClass *gnutls_class = G_TLS_DATABASE_GNUTLS_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (GTlsFileDatabaseGnutlsPrivate));
+
+  gobject_class->get_property = g_tls_file_database_gnutls_get_property;
+  gobject_class->set_property = g_tls_file_database_gnutls_set_property;
+  gobject_class->finalize     = g_tls_file_database_gnutls_finalize;
+
+  database_class->create_certificate_handle = g_tls_file_database_gnutls_create_certificate_handle;
+  database_class->lookup_certificate_for_handle = g_tls_file_database_gnutls_lookup_certificate_for_handle;
+  database_class->lookup_certificate_issuer = g_tls_file_database_gnutls_lookup_certificate_issuer;
+  database_class->lookup_certificates_issued_by = g_tls_file_database_gnutls_lookup_certificates_issued_by;
+  gnutls_class->lookup_assertion = g_tls_file_database_gnutls_lookup_assertion;
+
+  g_object_class_override_property (gobject_class, PROP_ANCHORS, "anchors");
+}
+
+static void
+g_tls_file_database_gnutls_file_database_interface_init (GTlsFileDatabaseInterface *iface)
+{
+
+}
+
+static gboolean
+g_tls_file_database_gnutls_initable_init (GInitable    *initable,
+                                          GCancellable *cancellable,
+                                          GError      **error)
+{
+  GTlsFileDatabaseGnutls *self = G_TLS_FILE_DATABASE_GNUTLS (initable);
+  GHashTable *subjects, *issuers, *complete;
+  gboolean result;
+
+  subjects = multi_byte_array_hash_new ();
+  issuers = multi_byte_array_hash_new ();
+
+  complete = g_hash_table_new_full (byte_array_hash, byte_array_equal,
+                                    (GDestroyNotify)g_byte_array_unref,
+                                    (GDestroyNotify)g_byte_array_unref);
+
+  result = load_anchor_file (self->priv->anchor_filename, subjects, issuers,
+                             complete, error);
+
+  if (result)
+    {
+      g_mutex_lock (self->priv->mutex);
+      if (!self->priv->subjects)
+        {
+          self->priv->subjects = subjects;
+          subjects = NULL;
+        }
+      if (!self->priv->issuers)
+        {
+          self->priv->issuers = issuers;
+          issuers = NULL;
+        }
+      if (!self->priv->complete)
+        {
+          self->priv->complete = complete;
+          complete = NULL;
+        }
+      g_mutex_unlock (self->priv->mutex);
+    }
+
+  if (subjects != NULL)
+    g_hash_table_unref (subjects);
+  if (issuers != NULL)
+    g_hash_table_unref (issuers);
+  if (complete != NULL)
+    g_hash_table_unref (complete);
+  return result;
+}
+
+static void
+g_tls_file_database_gnutls_initable_interface_init (GInitableIface *iface)
+{
+  iface->init = g_tls_file_database_gnutls_initable_init;
+}
diff --git a/tls/gnutls/gtlsfiledatabase-gnutls.h b/tls/gnutls/gtlsfiledatabase-gnutls.h
new file mode 100644
index 0000000..faa2c7d
--- /dev/null
+++ b/tls/gnutls/gtlsfiledatabase-gnutls.h
@@ -0,0 +1,52 @@
+/* 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_FILE_DATABASE_GNUTLS_H__
+#define __G_TLS_FILE_DATABASE_GNUTLS_H__
+
+#include <gio/gio.h>
+
+#include "gtlsdatabase-gnutls.h"
+
+G_BEGIN_DECLS
+
+#define G_TYPE_TLS_FILE_DATABASE_GNUTLS            (g_tls_file_database_gnutls_get_type ())
+#define G_TLS_FILE_DATABASE_GNUTLS(inst)           (G_TYPE_CHECK_INSTANCE_CAST ((inst), G_TYPE_TLS_FILE_DATABASE_GNUTLS, GTlsFileDatabaseGnutls))
+#define G_TLS_FILE_DATABASE_GNUTLS_CLASS(class)    (G_TYPE_CHECK_CLASS_CAST ((class), G_TYPE_TLS_FILE_DATABASE_GNUTLS, GTlsFileDatabaseGnutlsClass))
+#define G_IS_TLS_FILE_DATABASE_GNUTLS(inst)        (G_TYPE_CHECK_INSTANCE_TYPE ((inst), G_TYPE_TLS_FILE_DATABASE_GNUTLS))
+#define G_IS_TLS_FILE_DATABASE_GNUTLS_CLASS(class) (G_TYPE_CHECK_CLASS_TYPE ((class), G_TYPE_TLS_FILE_DATABASE_GNUTLS))
+#define G_TLS_FILE_DATABASE_GNUTLS_GET_CLASS(inst) (G_TYPE_INSTANCE_GET_CLASS ((inst), G_TYPE_TLS_FILE_DATABASE_GNUTLS, GTlsFileDatabaseGnutlsClass))
+
+typedef struct _GTlsFileDatabaseGnutlsPrivate                   GTlsFileDatabaseGnutlsPrivate;
+typedef struct _GTlsFileDatabaseGnutlsClass                     GTlsFileDatabaseGnutlsClass;
+typedef struct _GTlsFileDatabaseGnutls                          GTlsFileDatabaseGnutls;
+
+struct _GTlsFileDatabaseGnutlsClass
+{
+  GTlsDatabaseGnutlsClass parent_class;
+};
+
+struct _GTlsFileDatabaseGnutls
+{
+  GTlsDatabaseGnutls parent_instance;
+  GTlsFileDatabaseGnutlsPrivate *priv;
+};
+
+GType                        g_tls_file_database_gnutls_get_type              (void) G_GNUC_CONST;
+
+GTlsDatabase*                g_tls_file_database_gnutls_new                   (const gchar *anchor_file);
+
+G_END_DECLS
+
+#endif /* __G_TLS_FILE_DATABASE_GNUTLS_H___ */
diff --git a/tls/gnutls/gtlsserverconnection-gnutls.c b/tls/gnutls/gtlsserverconnection-gnutls.c
index e374b63..b9565ae 100644
--- a/tls/gnutls/gtlsserverconnection-gnutls.c
+++ b/tls/gnutls/gtlsserverconnection-gnutls.c
@@ -174,8 +174,32 @@ g_tls_server_connection_gnutls_verify_peer (GTlsConnectionGnutls  *gnutls,
 					    GTlsCertificate       *peer_certificate,
 					    GTlsCertificateFlags  *errors)
 {
+  GTlsDatabase *database;
+  GError *error = NULL;
+
+  database = g_tls_connection_get_database (G_TLS_CONNECTION (gnutls));
+  if (database == NULL)
+    {
+      *errors |= G_TLS_CERTIFICATE_UNKNOWN_CA;
+      *errors |= g_tls_certificate_verify (peer_certificate, NULL, NULL);
+    }
+  else
+    {
+      *errors |= g_tls_database_verify_chain (database, peer_certificate,
+                                              G_TLS_DATABASE_PURPOSE_AUTHENTICATE_CLIENT, NULL,
+                                              g_tls_connection_get_interaction (G_TLS_CONNECTION (gnutls)),
+                                              G_TLS_DATABASE_VERIFY_NONE,
+                                              NULL, &error);
+      if (error)
+        {
+          g_warning ("failure verifying certificate chain: %s",
+                     error->message);
+          g_clear_error (&error);
+        }
+    }
+
   return g_tls_connection_emit_accept_certificate (G_TLS_CONNECTION (gnutls),
-						   peer_certificate, *errors);
+                                                   peer_certificate, *errors);
 }
 
 static void
diff --git a/tls/tests/Makefile.am b/tls/tests/Makefile.am
new file mode 100644
index 0000000..1e9b316
--- /dev/null
+++ b/tls/tests/Makefile.am
@@ -0,0 +1,25 @@
+NULL =
+
+include $(top_srcdir)/Makefile.decl
+
+INCLUDES =              \
+	-g                  \
+	$(GLIB_CFLAGS)      \
+	-I$(top_srcdir)/tls \
+	-DSRCDIR=\""$(srcdir)"\"
+
+noinst_PROGRAMS = $(TEST_PROGS)
+
+LDADD  = \
+	$(GLIB_LIBS)
+
+TEST_PROGS =          \
+	tls           \
+	$(NULL)
+
+EXTRA_DIST = \
+	files \
+	$(NULL)
+
+DISTCLEANFILES = \
+	$(NULL)
diff --git a/tls/tests/files/ca-roots.pem b/tls/tests/files/ca-roots.pem
new file mode 100644
index 0000000..cf9e9d5
--- /dev/null
+++ b/tls/tests/files/ca-roots.pem
@@ -0,0 +1,202 @@
+-----BEGIN CERTIFICATE-----
+MIIDxjCCAy+gAwIBAgIJAOpd4Em2fjp3MA0GCSqGSIb3DQEBBQUAMIGGMRMwEQYK
+CZImiZPyLGQBGRYDQ09NMRcwFQYKCZImiZPyLGQBGRYHRVhBTVBMRTEeMBwGA1UE
+CxMVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MRcwFQYDVQQDEw5jYS5leGFtcGxlLmNv
+bTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBsZS5jb20wHhcNMDcxMjIwMTc1NjA2
+WhcNMzUwNTA4MTc1NjA2WjCBhjETMBEGCgmSJomT8ixkARkWA0NPTTEXMBUGCgmS
+JomT8ixkARkWB0VYQU1QTEUxHjAcBgNVBAsTFUNlcnRpZmljYXRlIEF1dGhvcml0
+eTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5jb20xHTAbBgkqhkiG9w0BCQEWDmNhQGV4
+YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD5OjHuXXN2LG3s
+FHISaZZ6L1RSYgRdTenu1nvqkMn/xvzOz385oede1z/7f6BoXyM0kNWCf4SOXtXr
+EIGmQoeURhFfLCnoK8NHfNcel3IPyMPhdJUMJlc3gfpWm+QxjkyqVyMhyYxC9Pmg
+QC7zx4ZKcQrL3zVGYtg8wxmaKY2HwQIDAQABo4IBODCCATQwHQYDVR0OBBYEFNSE
+nYhMCPaaFynFeQ2R5y25+AcFMIG7BgNVHSMEgbMwgbCAFNSEnYhMCPaaFynFeQ2R
+5y25+AcFoYGMpIGJMIGGMRMwEQYKCZImiZPyLGQBGRYDQ09NMRcwFQYKCZImiZPy
+LGQBGRYHRVhBTVBMRTEeMBwGA1UECxMVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MRcw
+FQYDVQQDEw5jYS5leGFtcGxlLmNvbTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBs
+ZS5jb22CCQDqXeBJtn46dzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAZBgNVHREEEjAQgQ5jYUBleGFtcGxlLmNvbTAZBgNVHRIEEjAQgQ5jYUBleGFt
+cGxlLmNvbTANBgkqhkiG9w0BAQUFAAOBgQA6xjU2aPgMOh2yyz2KCb6d5gNNvfr4
+pLGpZWilbRkA36OOG43zxeRZoumh1ybyOvhm73cMvNihDUyOf7vQe75Qtp5koGPS
+V3mSruhsRGvOZxcV+SJnBj1exKyH3mdaZA74Xg4y5qkUkywPqnP5Y+E6UMJM7Nmw
+kHk2bKJC5vjxoA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290
+IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB
+IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA
+Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO
+BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi
+MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ
+ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ
+8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6
+zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y
+fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7
+w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc
+G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k
+epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q
+laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ
+QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU
+fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826
+YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w
+ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY
+gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe
+MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0
+IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy
+dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw
+czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0
+dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl
+aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC
+AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg
+b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB
+ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc
+nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg
+18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c
+gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl
+Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY
+sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T
+SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF
+CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum
+GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk
+zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW
+omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIBATANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290
+IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB
+IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA
+Y2FjZXJ0Lm9yZzAeFw0wNTEwMTQwNzM2NTVaFw0zMzAzMjgwNzM2NTVaMFQxFDAS
+BgNVBAoTC0NBY2VydCBJbmMuMR4wHAYDVQQLExVodHRwOi8vd3d3LkNBY2VydC5v
+cmcxHDAaBgNVBAMTE0NBY2VydCBDbGFzcyAzIFJvb3QwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQCrSTURSHzSJn5TlM9Dqd0o10Iqi/OHeBlYfA+e2ol9
+4fvrcpANdKGWZKufoCSZc9riVXbHF3v1BKxGuMO+f2SNEGwk82GcwPKQ+lHm9WkB
+Y8MPVuJKQs/iRIwlKKjFeQl9RrmK8+nzNCkIReQcn8uUBByBqBSzmGXEQ+xOgo0J
+0b2qW42S0OzekMV/CsLj6+YxWl50PpczWejDAz1gM7/30W9HxM3uYoNSbi4ImqTZ
+FRiRpoWSR7CuSOtttyHshRpocjWr//AQXcD0lKdq1TuSfkyQBX6TwSyLpI5idBVx
+bgtxA+qvFTia1NIFcm+M+SvrWnIl+TlG43IbPgTDZCciECqKT1inA62+tC4T7V2q
+SNfVfdQqe1z6RgRQ5MwOQluM7dvyz/yWk+DbETZUYjQ4jwxgmzuXVjit89Jbi6Bb
+6k6WuHzX1aCGcEDTkSm3ojyt9Yy7zxqSiuQ0e8DYbF/pCsLDpyCaWt8sXVJcukfV
+m+8kKHA4IC/VfynAskEDaJLM4JzMl0tF7zoQCqtwOpiVcK01seqFK6QcgCExqa5g
+eoAmSAC4AcCTY1UikTxW56/bOiXzjzFU6iaLgVn5odFTEcV7nQP2dBHgbbEsPyyG
+kZlxmqZ3izRg0RS0LKydr4wQ05/EavhvE/xzWfdmQnQeiuP43NJvmJzLR5iVQAX7
+6QIDAQABo4G/MIG8MA8GA1UdEwEB/wQFMAMBAf8wXQYIKwYBBQUHAQEEUTBPMCMG
+CCsGAQUFBzABhhdodHRwOi8vb2NzcC5DQWNlcnQub3JnLzAoBggrBgEFBQcwAoYc
+aHR0cDovL3d3dy5DQWNlcnQub3JnL2NhLmNydDBKBgNVHSAEQzBBMD8GCCsGAQQB
+gZBKMDMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuQ0FjZXJ0Lm9yZy9pbmRleC5w
+aHA/aWQ9MTAwDQYJKoZIhvcNAQEEBQADggIBAH8IiKHaGlBJ2on7oQhy84r3HsQ6
+tHlbIDCxRd7CXdNlafHCXVRUPIVfuXtCkcKZ/RtRm6tGpaEQU55tiKxzbiwzpvD0
+nuB1wT6IRanhZkP+VlrRekF490DaSjrxC1uluxYG5sLnk7mFTZdPsR44Q4Dvmw2M
+77inYACHV30eRBzLI++bPJmdr7UpHEV5FpZNJ23xHGzDwlVks7wU4vOkHx4y/CcV
+Bc/dLq4+gmF78CEQGPZE6lM5+dzQmiDgxrvgu1pPxJnIB721vaLbLmINQjRBvP+L
+ivVRIqqIMADisNS8vmW61QNXeZvo3MhN+FDtkaVSKKKs+zZYPumUK5FQhxvWXtaM
+zPcPEAxSTtAWYeXlCmy/F8dyRlecmPVsYGN6b165Ti/Iubm7aoW8mA3t+T6XhDSU
+rgCvoeXnkm5OvfPi2RSLXNLrAWygF6UtEOucekq9ve7O/e0iQKtwOIj1CodqwqsF
+YMlIBdpTwd5Ed2qz8zw87YC8pjhKKSRf/lk7myV6VmMAZLldpGJ9VzZPrYPvH5JT
+oI53V93lYRE9IwCQTDz6o2CTBKOvNfYOao9PSmCnhQVsRqGP9Md246FZV/dxssRu
+FFxtbUFm3xuTsdQAw+7Lzzw9IYCpX2Nl/N3gX6T0K/CFcUHUZyX7GrGXrtaZghNB
+0m6lG5kngOcLqagA
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDITCCAoqgAwIBAgIBADANBgkqhkiG9w0BAQQFADCByzELMAkGA1UEBhMC
+WkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3du
+MRowGAYDVQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlm
+aWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFBl
+cnNvbmFsIEJhc2ljIENBMSgwJgYJKoZIhvcNAQkBFhlwZXJzb25hbC1iYXNp
+Y0B0aGF3dGUuY29tMB4XDTk2MDEwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVow
+gcsxCzAJBgNVBAYTAlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNV
+BAcTCUNhcGUgVG93bjEaMBgGA1UEChMRVGhhd3RlIENvbnN1bHRpbmcxKDAm
+BgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2aXNpb24xITAfBgNV
+BAMTGFRoYXd0ZSBQZXJzb25hbCBCYXNpYyBDQTEoMCYGCSqGSIb3DQEJARYZ
+cGVyc29uYWwtYmFzaWNAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOB
+jQAwgYkCgYEAvLyTU23AUE+CFeZIlDWmWr5vQvoPR+53dXLdjUmbllegeNTK
+P1GzaQuRdhciB5dqxFGTS+CN7zeVoQxN2jSQHReJl+A1OFdKwPQIcOk8RHtQ
+fmGakOMj04gRRif1CwcOu93RfyAKiLlWCy4cgNrx454p7xS9CkT7G1sY0b8j
+kyECAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOB
+gQAt4plrsD16iddZopQBHyvdEktTwq1/qqcAXJFAVyVKOKqEcLnZgA+le1z7
+c8a914phXAPjLSeoF+CEhULcXpvGt7Jtu3Sv5D/Lp7ew4F2+eIMllNLbgQ95
+B21P9DkVWlIBe94y1k049hJcBlDfBVu9FEuh3ym6O0GN92NWod8isQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDLTCCApagAwIBAgIBADANBgkqhkiG9w0BAQQFADCB0TELMAkGA1UEBhMC
+WkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3du
+MRowGAYDVQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlm
+aWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEkMCIGA1UEAxMbVGhhd3RlIFBl
+cnNvbmFsIEZyZWVtYWlsIENBMSswKQYJKoZIhvcNAQkBFhxwZXJzb25hbC1m
+cmVlbWFpbEB0aGF3dGUuY29tMB4XDTk2MDEwMTAwMDAwMFoXDTIwMTIzMTIz
+NTk1OVowgdExCzAJBgNVBAYTAlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUx
+EjAQBgNVBAcTCUNhcGUgVG93bjEaMBgGA1UEChMRVGhhd3RlIENvbnN1bHRp
+bmcxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2aXNpb24x
+JDAiBgNVBAMTG1RoYXd0ZSBQZXJzb25hbCBGcmVlbWFpbCBDQTErMCkGCSqG
+SIb3DQEJARYccGVyc29uYWwtZnJlZW1haWxAdGhhd3RlLmNvbTCBnzANBgkq
+hkiG9w0BAQEFAAOBjQAwgYkCgYEA1GnX1LCUZFtx6UfYDFG26nKRsIRefS0N
+j3sS34UldSh0OkIsYyeflXtL734Zhx2G6qPduc6WZBrCFG5ErHzmj+hND3Ef
+QDimAKOHePb5lIZererAXnbr2RSjXW56fAylS1V/Bhkpf56aJtVquzgkCGqY
+x7Hao5iR/Xnb5VrEHLkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQQFAAOBgQDH7JJ+Tvj1lqVnYiqk8E0RYNBvjWBYYawmu1I1XAjP
+MPuoSpaKH2JCI4wXD/S6ZJwXrEcp352YXtJsYHFcoqzceePnbgBHH7UNKOgC
+neSa/RP0ptl8sfjcXyMmCZGAc9AUG95DqYMl8uacLxXK/qarigd1iwzdUYRr
+5PjRzneigQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDKTCCApKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBzzELMAkGA1UEBhMC
+WkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3du
+MRowGAYDVQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlm
+aWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEjMCEGA1UEAxMaVGhhd3RlIFBl
+cnNvbmFsIFByZW1pdW0gQ0ExKjAoBgkqhkiG9w0BCQEWG3BlcnNvbmFsLXBy
+ZW1pdW1AdGhhd3RlLmNvbTAeFw05NjAxMDEwMDAwMDBaFw0yMDEyMzEyMzU5
+NTlaMIHPMQswCQYDVQQGEwJaQTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRIw
+EAYDVQQHEwlDYXBlIFRvd24xGjAYBgNVBAoTEVRoYXd0ZSBDb25zdWx0aW5n
+MSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMSMw
+IQYDVQQDExpUaGF3dGUgUGVyc29uYWwgUHJlbWl1bSBDQTEqMCgGCSqGSIb3
+DQEJARYbcGVyc29uYWwtcHJlbWl1bUB0aGF3dGUuY29tMIGfMA0GCSqGSIb3
+DQEBAQUAA4GNADCBiQKBgQDJZtn4B0TPuYwu8KHvE0VsBd/eJxZRNkERbGw7
+7f4QfRKe5ZtCmv5gMcNmt3M6SK5O0DI3lIi1DbbZ8/JE2dWIEt12TfIa/G8j
+Hnrx2JhFTgcQ7xZC0EN1bUre4qrJMf8fAHB8Zs8QJQi6+u4A6UYDZicRFTuq
+W/KY3TZCstqIdQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
+DQEBBAUAA4GBAGk2ifc0KjNyL2071CKyuG+axTZmDhs8obF1Wub9NdP4qPIH
+b4Vnjt4rueIXsDqg8A6iAJrf8xQVbrvIhVqYgPn/vnQdPfP+MCXRNzRn+qVx
+eTBhKXLA4CxM+1bkOqhv5TJZUtt1KFBZDPgLGeSs2a+WjS9Q2wfD6h+rM+D1
+KzGJ
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMC
+WkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3du
+MR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2Vy
+dGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3Rl
+IFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNl
+cnZlckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1
+OVowgc4xCzAJBgNVBAYTAlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQ
+BgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMUVGhhd3RlIENvbnN1bHRpbmcg
+Y2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2aXNpb24x
+ITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3
+DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0B
+AQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhI
+NTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQug2SBhRz1JPL
+lyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/qgeN
+9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B
+AQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI
+hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZ
+a4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcU
+Qg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMC
+WkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3du
+MR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2Vy
+dGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3Rl
+IFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0
+ZS5jb20wHhcNOTYwODAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkG
+A1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2Fw
+ZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UE
+CxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQ
+VGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRz
+QHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I
+/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC
+6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCXL+eQbcAoQpnX
+TEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGjEzARMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG7oWD
+TSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e
+QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdni
+TCxZqdq5snUb9kLy78fyGPmJvKP/iiMucEc=
+-----END CERTIFICATE-----
diff --git a/tls/tests/files/ca.pem b/tls/tests/files/ca.pem
new file mode 100644
index 0000000..b8d6008
--- /dev/null
+++ b/tls/tests/files/ca.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDxjCCAy+gAwIBAgIJAOpd4Em2fjp3MA0GCSqGSIb3DQEBBQUAMIGGMRMwEQYK
+CZImiZPyLGQBGRYDQ09NMRcwFQYKCZImiZPyLGQBGRYHRVhBTVBMRTEeMBwGA1UE
+CxMVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MRcwFQYDVQQDEw5jYS5leGFtcGxlLmNv
+bTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBsZS5jb20wHhcNMDcxMjIwMTc1NjA2
+WhcNMzUwNTA4MTc1NjA2WjCBhjETMBEGCgmSJomT8ixkARkWA0NPTTEXMBUGCgmS
+JomT8ixkARkWB0VYQU1QTEUxHjAcBgNVBAsTFUNlcnRpZmljYXRlIEF1dGhvcml0
+eTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5jb20xHTAbBgkqhkiG9w0BCQEWDmNhQGV4
+YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD5OjHuXXN2LG3s
+FHISaZZ6L1RSYgRdTenu1nvqkMn/xvzOz385oede1z/7f6BoXyM0kNWCf4SOXtXr
+EIGmQoeURhFfLCnoK8NHfNcel3IPyMPhdJUMJlc3gfpWm+QxjkyqVyMhyYxC9Pmg
+QC7zx4ZKcQrL3zVGYtg8wxmaKY2HwQIDAQABo4IBODCCATQwHQYDVR0OBBYEFNSE
+nYhMCPaaFynFeQ2R5y25+AcFMIG7BgNVHSMEgbMwgbCAFNSEnYhMCPaaFynFeQ2R
+5y25+AcFoYGMpIGJMIGGMRMwEQYKCZImiZPyLGQBGRYDQ09NMRcwFQYKCZImiZPy
+LGQBGRYHRVhBTVBMRTEeMBwGA1UECxMVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MRcw
+FQYDVQQDEw5jYS5leGFtcGxlLmNvbTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBs
+ZS5jb22CCQDqXeBJtn46dzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAZBgNVHREEEjAQgQ5jYUBleGFtcGxlLmNvbTAZBgNVHRIEEjAQgQ5jYUBleGFt
+cGxlLmNvbTANBgkqhkiG9w0BAQUFAAOBgQA6xjU2aPgMOh2yyz2KCb6d5gNNvfr4
+pLGpZWilbRkA36OOG43zxeRZoumh1ybyOvhm73cMvNihDUyOf7vQe75Qtp5koGPS
+V3mSruhsRGvOZxcV+SJnBj1exKyH3mdaZA74Xg4y5qkUkywPqnP5Y+E6UMJM7Nmw
+kHk2bKJC5vjxoA==
+-----END CERTIFICATE-----
diff --git a/tls/tests/files/client-and-key.pem b/tls/tests/files/client-and-key.pem
new file mode 100644
index 0000000..897b5f2
--- /dev/null
+++ b/tls/tests/files/client-and-key.pem
@@ -0,0 +1,45 @@
+-----BEGIN CERTIFICATE-----
+MIIC3DCCAkUCAQkwDQYJKoZIhvcNAQEFBQAwgYYxEzARBgoJkiaJk/IsZAEZFgND
+T00xFzAVBgoJkiaJk/IsZAEZFgdFWEFNUExFMR4wHAYDVQQLExVDZXJ0aWZpY2F0
+ZSBBdXRob3JpdHkxFzAVBgNVBAMTDmNhLmV4YW1wbGUuY29tMR0wGwYJKoZIhvcN
+AQkBFg5jYUBleGFtcGxlLmNvbTAeFw0xMTAxMTgwNjA0MTFaFw0yMTAxMTUwNjA0
+MTFaMGIxEzARBgoJkiaJk/IsZAEZFgNDT00xFzAVBgoJkiaJk/IsZAEZFgdFWEFN
+UExFMQ8wDQYDVQQDEwZDbGllbnQxITAfBgkqhkiG9w0BCQEWEmNsaWVudEBleGFt
+cGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMEgNDM/dg3t
+9DHNAPz/b87LSxEaKhoZ8AcNBym3LEOdCnXGEnKf0b9lkT5caXu5GAM84ahTCJ7n
+79RVNrqGKM7jbBdSX+ZUfkqJQPhOXD2+0niYQicH92nz78kxmjlbizvd3fM1BlO+
+C/++NWf2EAhORVAjvJrHNokJp3PTNRJ1WWteHeU9PwfGmWHKVc1IgvRFMH08604I
+ZzX5CcxIg/b56g27A7CBPh/KO/qKTDLFFNGc1T2asvY/P3+PeN6y+leFHStCTu7R
+Bi/l4hczZdnwq3BGT6mnjEN7wau2s7pA067SXimNOkYi5fgwspMHi8fJWmYyBypU
+mQBRzwfm77ECAwEAATANBgkqhkiG9w0BAQUFAAOBgQA3LuElj2QB9wQvmIxk2Jmb
+IPP2/WS8dwPoCv/N3+6nTx8yRsrILf4QsnEbbsxoYO5jW4r9Kt8m8B/M7YgnBDE9
+zlm7JbXKZf2isSm5TyT627Ymzxrzs5d+7o2eS7SN1DB6PyvRh2ye7EMbyEYD8ULi
+itDUkYkssNCVivYwVvJoMg==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAwSA0Mz92De30Mc0A/P9vzstLERoqGhnwBw0HKbcsQ50KdcYS
+cp/Rv2WRPlxpe7kYAzzhqFMInufv1FU2uoYozuNsF1Jf5lR+SolA+E5cPb7SeJhC
+Jwf3afPvyTGaOVuLO93d8zUGU74L/741Z/YQCE5FUCO8msc2iQmnc9M1EnVZa14d
+5T0/B8aZYcpVzUiC9EUwfTzrTghnNfkJzEiD9vnqDbsDsIE+H8o7+opMMsUU0ZzV
+PZqy9j8/f4943rL6V4UdK0JO7tEGL+XiFzNl2fCrcEZPqaeMQ3vBq7azukDTrtJe
+KY06RiLl+DCykweLx8laZjIHKlSZAFHPB+bvsQIDAQABAoIBAQCQUI1RYnHIdPFO
+qZ+8bvDQ+g8tR30ApjM8QZsBrDRyjg579bhhWVY2jSJdFFdqseTkvoDt9KZzgGQy
+Kj9MYOZru3xRbSfmiWsaLbiUFJJPPaIvpa+BVS2oSjX8BYn2pJbF9MRfclc5CsIS
+qMNl3XUbj8mx2hKdIpJ5EvLD1adKE4Se6peqSZAmEHONNCsrMrQ0GSQqV3viInJr
+tc3kp3HcPffSROWqmc6jAJ77Cs3ApgJavL5RGjx30Kd+dKVq4PXZ+IhWM8dOSput
+wcyxEosiP/W2g0rDgNW2mGOVOwa/D5SnOolicHifdV7idjwLAjkyYgvmBMNSsECj
+yKBkE0gxAoGBAN8iHMumyvriHuj9bSLZ1bcyYFz7jIwUxpHTT7VqN/j/Y1BoBIBy
+ZZLDGMa+ID/brpRHzJQAKSNtbFQ0S1HTSKcFud5OWE8Rp3pQJU+sdeO3pCMWAD1z
+Q4ggF07JjTSSnK+4fcXgEN9P2OdfXy7Rj3HFpSahql55Kp5udoUdzUVFAoGBAN2S
+krlcEuqsEYjqsCJw5pctIwPMvCM51JgirrdETwSGquMklSrobH0PHMlR67gsA/9I
+UGShT0LL4UWYpBn/4xLrLbua5aHIBfQQZp9K6jDZddWS+EFL5JkO/Up4/qM6fUbH
+CuweVv1gd6i2Ti35K60mgx6MqVunaB1k8Q9P3Pl9AoGALSVtxha9Qv21W1bLWh3R
+C/v5W1baHQ2nD6I9omsXYB3sLjydjI+Y1ZT70lptk/4S2JWeYuOVb0GYhYD/LFMf
+hAu4i642V+kuhaTpp7ExOR3S6/ZrngNQSp6TmLFXDKgNY9BkQkEPqN8y971oOMTV
+zSM8QxC6s9q4MM4Q1OYuvjECgYEAsO2V1AW95T45Ukd1FktpFlaomyQlJ0vKgyFO
+unEFV+vhETfpFTY7SzGCHxAXVh1vo62u5Gwayo/a9qQIhepa/IRnJGNv8luyxU1D
+ZPeBQjija0PMkPd1NvNNNuafDuBpoNbX1ev0MqeRZVsN2pAZXE5gbUiNA+8NqEsu
+Yre3EFECgYEA13rXE76zZgsefx+2spjqJDUWEmTDd1460xTtxCCgL9dy4rW5bgwo
+MvINphSUXOwSkn8Oja/IvpN28zSj9W/ci5wU52P5w4blkBmuj8UoCjP2FN1b1OBa
+86mkwVsCYUyyI2apuwrHP77yeb8jXZb+reqSns3hU+HyO/nUTVmnews=
+-----END RSA PRIVATE KEY-----
diff --git a/tls/tests/files/client-future.pem b/tls/tests/files/client-future.pem
new file mode 100644
index 0000000..de1cb75
--- /dev/null
+++ b/tls/tests/files/client-future.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC3DCCAkUCAQowDQYJKoZIhvcNAQEFBQAwgYYxEzARBgoJkiaJk/IsZAEZFgND
+T00xFzAVBgoJkiaJk/IsZAEZFgdFWEFNUExFMR4wHAYDVQQLExVDZXJ0aWZpY2F0
+ZSBBdXRob3JpdHkxFzAVBgNVBAMTDmNhLmV4YW1wbGUuY29tMR0wGwYJKoZIhvcN
+AQkBFg5jYUBleGFtcGxlLmNvbTAeFw0yMDAxMTgxNzI3MDNaFw0yMTAxMTcxNzI3
+MDNaMGIxEzARBgoJkiaJk/IsZAEZFgNDT00xFzAVBgoJkiaJk/IsZAEZFgdFWEFN
+UExFMQ8wDQYDVQQDEwZDbGllbnQxITAfBgkqhkiG9w0BCQEWEmNsaWVudEBleGFt
+cGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMEgNDM/dg3t
+9DHNAPz/b87LSxEaKhoZ8AcNBym3LEOdCnXGEnKf0b9lkT5caXu5GAM84ahTCJ7n
+79RVNrqGKM7jbBdSX+ZUfkqJQPhOXD2+0niYQicH92nz78kxmjlbizvd3fM1BlO+
+C/++NWf2EAhORVAjvJrHNokJp3PTNRJ1WWteHeU9PwfGmWHKVc1IgvRFMH08604I
+ZzX5CcxIg/b56g27A7CBPh/KO/qKTDLFFNGc1T2asvY/P3+PeN6y+leFHStCTu7R
+Bi/l4hczZdnwq3BGT6mnjEN7wau2s7pA067SXimNOkYi5fgwspMHi8fJWmYyBypU
+mQBRzwfm77ECAwEAATANBgkqhkiG9w0BAQUFAAOBgQBvt8v930fQtxR7f7Vcb1Hg
+irq1CtffsBqtKYupYg6IgloiRA6U5wdU0e6faA3Ppsmd4SmNKb9ZavIgnDBfx8MP
+1/IpsNOkg0366bP/zzkAhcXspo7PU8yZIqep//wT4TOFz04N8Lshqm8HUejShFdA
+fB8C0LX5Y/2219ZVMaaEbw==
+-----END CERTIFICATE-----
diff --git a/tls/tests/files/client-past.pem b/tls/tests/files/client-past.pem
new file mode 100644
index 0000000..2dbb4d1
--- /dev/null
+++ b/tls/tests/files/client-past.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC3DCCAkUCAQswDQYJKoZIhvcNAQEFBQAwgYYxEzARBgoJkiaJk/IsZAEZFgND
+T00xFzAVBgoJkiaJk/IsZAEZFgdFWEFNUExFMR4wHAYDVQQLExVDZXJ0aWZpY2F0
+ZSBBdXRob3JpdHkxFzAVBgNVBAMTDmNhLmV4YW1wbGUuY29tMR0wGwYJKoZIhvcN
+AQkBFg5jYUBleGFtcGxlLmNvbTAeFw0wMDAxMTgxNzI3NDdaFw0wMTAxMTcxNzI3
+NDdaMGIxEzARBgoJkiaJk/IsZAEZFgNDT00xFzAVBgoJkiaJk/IsZAEZFgdFWEFN
+UExFMQ8wDQYDVQQDEwZDbGllbnQxITAfBgkqhkiG9w0BCQEWEmNsaWVudEBleGFt
+cGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMEgNDM/dg3t
+9DHNAPz/b87LSxEaKhoZ8AcNBym3LEOdCnXGEnKf0b9lkT5caXu5GAM84ahTCJ7n
+79RVNrqGKM7jbBdSX+ZUfkqJQPhOXD2+0niYQicH92nz78kxmjlbizvd3fM1BlO+
+C/++NWf2EAhORVAjvJrHNokJp3PTNRJ1WWteHeU9PwfGmWHKVc1IgvRFMH08604I
+ZzX5CcxIg/b56g27A7CBPh/KO/qKTDLFFNGc1T2asvY/P3+PeN6y+leFHStCTu7R
+Bi/l4hczZdnwq3BGT6mnjEN7wau2s7pA067SXimNOkYi5fgwspMHi8fJWmYyBypU
+mQBRzwfm77ECAwEAATANBgkqhkiG9w0BAQUFAAOBgQBC3BOULAOkRFLKLajHIIB2
+VB0tHOFWuflP/LXso3ogGA8ItqbjacqjRHdTGK79etbxSTdi7k8owMVMPavJnBYk
+TraOkf/xxHo2zWy3XES1lniTUfGgKpjYNlALB6K6DJseZorSOmGA4KllL46MYwNu
+jsLO+5HkS/uNxlKo2l+xGw==
+-----END CERTIFICATE-----
diff --git a/tls/tests/files/client.pem b/tls/tests/files/client.pem
new file mode 100644
index 0000000..04bc8ac
--- /dev/null
+++ b/tls/tests/files/client.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC3DCCAkUCAQkwDQYJKoZIhvcNAQEFBQAwgYYxEzARBgoJkiaJk/IsZAEZFgND
+T00xFzAVBgoJkiaJk/IsZAEZFgdFWEFNUExFMR4wHAYDVQQLExVDZXJ0aWZpY2F0
+ZSBBdXRob3JpdHkxFzAVBgNVBAMTDmNhLmV4YW1wbGUuY29tMR0wGwYJKoZIhvcN
+AQkBFg5jYUBleGFtcGxlLmNvbTAeFw0xMTAxMTgwNjA0MTFaFw0yMTAxMTUwNjA0
+MTFaMGIxEzARBgoJkiaJk/IsZAEZFgNDT00xFzAVBgoJkiaJk/IsZAEZFgdFWEFN
+UExFMQ8wDQYDVQQDEwZDbGllbnQxITAfBgkqhkiG9w0BCQEWEmNsaWVudEBleGFt
+cGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMEgNDM/dg3t
+9DHNAPz/b87LSxEaKhoZ8AcNBym3LEOdCnXGEnKf0b9lkT5caXu5GAM84ahTCJ7n
+79RVNrqGKM7jbBdSX+ZUfkqJQPhOXD2+0niYQicH92nz78kxmjlbizvd3fM1BlO+
+C/++NWf2EAhORVAjvJrHNokJp3PTNRJ1WWteHeU9PwfGmWHKVc1IgvRFMH08604I
+ZzX5CcxIg/b56g27A7CBPh/KO/qKTDLFFNGc1T2asvY/P3+PeN6y+leFHStCTu7R
+Bi/l4hczZdnwq3BGT6mnjEN7wau2s7pA067SXimNOkYi5fgwspMHi8fJWmYyBypU
+mQBRzwfm77ECAwEAATANBgkqhkiG9w0BAQUFAAOBgQA3LuElj2QB9wQvmIxk2Jmb
+IPP2/WS8dwPoCv/N3+6nTx8yRsrILf4QsnEbbsxoYO5jW4r9Kt8m8B/M7YgnBDE9
+zlm7JbXKZf2isSm5TyT627Ymzxrzs5d+7o2eS7SN1DB6PyvRh2ye7EMbyEYD8ULi
+itDUkYkssNCVivYwVvJoMg==
+-----END CERTIFICATE-----
diff --git a/tls/tests/files/server-and-key.pem b/tls/tests/files/server-and-key.pem
new file mode 100644
index 0000000..7bf1cff
--- /dev/null
+++ b/tls/tests/files/server-and-key.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIICJjCCAY+gAwIBAgIBBzANBgkqhkiG9w0BAQUFADCBhjETMBEGCgmSJomT8ixk
+ARkWA0NPTTEXMBUGCgmSJomT8ixkARkWB0VYQU1QTEUxHjAcBgNVBAsTFUNlcnRp
+ZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5jb20xHTAbBgkq
+hkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMB4XDTExMDExNzE5NDcxN1oXDTIxMDEx
+NDE5NDcxN1owSzETMBEGCgmSJomT8ixkARkWA0NPTTEXMBUGCgmSJomT8ixkARkW
+B0VYQU1QTEUxGzAZBgNVBAMTEnNlcnZlci5leGFtcGxlLmNvbTBcMA0GCSqGSIb3
+DQEBAQUAA0sAMEgCQQDYScTxk55XBmbDM9zzwO+grVySE4rudWuzH2PpObIonqbf
+hRoAalKVluG9jvbHI81eXxCdSObv1KBP1sbN5RzpAgMBAAGjIjAgMAkGA1UdEwQC
+MAAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEFBQADgYEAYx6fMqT1
+Gvo0jq88E8mc+bmp4LfXD4wJ7KxYeadQxt75HFRpj4FhFO3DOpVRFgzHlOEo3Fwk
+PZOKjvkT0cbcoEq5whLH25dHoQxGoVQgFyAP5s+7Vp5AlHh8Y/vAoXeEVyy/RCIH
+QkhUlAflfDMcrrYjsmwoOPSjhx6Mm/AopX4=
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOgIBAAJBANhJxPGTnlcGZsMz3PPA76CtXJITiu51a7MfY+k5siiept+FGgBq
+UpWW4b2O9scjzV5fEJ1I5u/UoE/Wxs3lHOkCAwEAAQJAURSWd9lW6ljD/TlcAyS4
+sAtNQJWC55GtJiEGW0/9savXVHPRllN5IlcOJS/L//rD47UzwmGHcxxzsg3p+s51
+YQIhAPaiU+wbUL0cQrxqwIa/TEtNtrU3T21vNvQaFLSHh+uHAiEA4IBuYuPL+xz2
+nDwSB9UWklYV5fKhwqUA96qyMNxTMA8CIGvD6h+Un+bB3nctvgoitFeDEX6FOHN8
+0OpAKyPmxIEpAiEAiwNb1wYhN9QebG3R6GGtQV3m32VXftR3feSHxDOZm0cCIEpA
+7kMFeeQQZb1qYd1PMFSDCG+BkrWh/Fud+VvFAWxI
+-----END RSA PRIVATE KEY-----
diff --git a/tls/tests/files/server-self.pem b/tls/tests/files/server-self.pem
new file mode 100644
index 0000000..20b3500
--- /dev/null
+++ b/tls/tests/files/server-self.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBiDCCATICCQDJ4QeFpYPYljANBgkqhkiG9w0BAQUFADBLMRMwEQYKCZImiZPy
+LGQBGRYDQ09NMRcwFQYKCZImiZPyLGQBGRYHRVhBTVBMRTEbMBkGA1UEAxMSc2Vy
+dmVyLmV4YW1wbGUuY29tMB4XDTExMDExOTAzMTYzOFoXDTIxMDExNjAzMTYzOFow
+SzETMBEGCgmSJomT8ixkARkWA0NPTTEXMBUGCgmSJomT8ixkARkWB0VYQU1QTEUx
+GzAZBgNVBAMTEnNlcnZlci5leGFtcGxlLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sA
+MEgCQQDYScTxk55XBmbDM9zzwO+grVySE4rudWuzH2PpObIonqbfhRoAalKVluG9
+jvbHI81eXxCdSObv1KBP1sbN5RzpAgMBAAEwDQYJKoZIhvcNAQEFBQADQQAagc2P
+/lCfDwT3max+D2M7++KMDfGqiO3gI+hMarf/jAaQpcKO/9G95AnNo4lTd6W6/7yj
+YYvUupv+0vi4CtQG
+-----END CERTIFICATE-----
diff --git a/tls/tests/files/server.der b/tls/tests/files/server.der
new file mode 100644
index 0000000..cf2de65
Binary files /dev/null and b/tls/tests/files/server.der differ
diff --git a/tls/tests/files/server.pem b/tls/tests/files/server.pem
new file mode 100644
index 0000000..d4bd526
--- /dev/null
+++ b/tls/tests/files/server.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICJjCCAY+gAwIBAgIBBzANBgkqhkiG9w0BAQUFADCBhjETMBEGCgmSJomT8ixk
+ARkWA0NPTTEXMBUGCgmSJomT8ixkARkWB0VYQU1QTEUxHjAcBgNVBAsTFUNlcnRp
+ZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAxMOY2EuZXhhbXBsZS5jb20xHTAbBgkq
+hkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMB4XDTExMDExNzE5NDcxN1oXDTIxMDEx
+NDE5NDcxN1owSzETMBEGCgmSJomT8ixkARkWA0NPTTEXMBUGCgmSJomT8ixkARkW
+B0VYQU1QTEUxGzAZBgNVBAMTEnNlcnZlci5leGFtcGxlLmNvbTBcMA0GCSqGSIb3
+DQEBAQUAA0sAMEgCQQDYScTxk55XBmbDM9zzwO+grVySE4rudWuzH2PpObIonqbf
+hRoAalKVluG9jvbHI81eXxCdSObv1KBP1sbN5RzpAgMBAAGjIjAgMAkGA1UdEwQC
+MAAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEFBQADgYEAYx6fMqT1
+Gvo0jq88E8mc+bmp4LfXD4wJ7KxYeadQxt75HFRpj4FhFO3DOpVRFgzHlOEo3Fwk
+PZOKjvkT0cbcoEq5whLH25dHoQxGoVQgFyAP5s+7Vp5AlHh8Y/vAoXeEVyy/RCIH
+QkhUlAflfDMcrrYjsmwoOPSjhx6Mm/AopX4=
+-----END CERTIFICATE-----
diff --git a/tls/tests/tls.c b/tls/tests/tls.c
new file mode 100644
index 0000000..656acdd
--- /dev/null
+++ b/tls/tests/tls.c
@@ -0,0 +1,995 @@
+/* GIO TLS tests
+ *
+ * Copyright (C) 2011 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 <gio/gio.h>
+
+#include <sys/types.h>
+#include <string.h>
+
+static gchar *source_dir = NULL;
+
+/* -----------------------------------------------------------------------------
+ * CONNECTION AND DATABASE TESTS
+ */
+
+#define TEST_DATA "You win again, gravity!\n"
+#define TEST_DATA_LENGTH 24
+
+typedef struct {
+  GMainLoop *loop;
+  GSocketService *service;
+  GTlsDatabase *database;
+  GIOStream *server_connection;
+  GIOStream *client_connection;
+  GSocketConnectable *identity;
+  GSocketAddress *address;
+  GTlsAuthenticationMode auth_mode;
+  GTlsCertificateFlags accept_flags;
+} TestConnection;
+
+static void
+setup_connection (TestConnection *test, gconstpointer data)
+{
+  GInetAddress *inet;
+  guint16 port;
+
+  test->loop = g_main_loop_new (NULL, FALSE);
+
+  test->auth_mode = G_TLS_AUTHENTICATION_NONE;
+
+  /* This is where the server listens and the client connects */
+  port = g_random_int_range (50000, 65000);
+  inet = g_inet_address_new_from_string ("127.0.0.1");
+  test->address = G_SOCKET_ADDRESS (g_inet_socket_address_new (inet, port));
+  g_object_unref (inet);
+
+  /* The identity matches the server certificate */
+  test->identity = g_network_address_new ("server.example.com", port);
+}
+
+static void
+teardown_connection (TestConnection *test, gconstpointer data)
+{
+  if (test->service)
+    g_object_unref (test->service);
+
+  if (test->server_connection)
+    {
+      g_assert (G_IS_TLS_SERVER_CONNECTION (test->server_connection));
+      g_object_unref (test->server_connection);
+      g_assert (!G_IS_TLS_SERVER_CONNECTION (test->server_connection));
+    }
+
+  if (test->client_connection)
+    {
+      g_assert (G_IS_TLS_CLIENT_CONNECTION (test->client_connection));
+      g_object_unref (test->client_connection);
+      g_assert (!G_IS_TLS_SERVER_CONNECTION (test->client_connection));
+    }
+
+  if (test->database)
+    {
+      g_assert (G_IS_TLS_DATABASE (test->database));
+      g_object_unref (test->database);
+      g_assert (!G_IS_TLS_DATABASE (test->database));
+    }
+
+  g_object_unref (test->address);
+  g_object_unref (test->identity);
+  g_main_loop_unref (test->loop);
+}
+
+static gboolean
+on_accept_certificate (GTlsClientConnection *conn, GTlsCertificate *cert,
+                       GTlsCertificateFlags errors, gpointer user_data)
+{
+  TestConnection *test = user_data;
+  return errors == test->accept_flags;
+}
+
+static void
+on_output_close_finish (GObject        *object,
+                        GAsyncResult   *res,
+                        gpointer        user_data)
+{
+  GError *error = NULL;
+  g_output_stream_close_finish (G_OUTPUT_STREAM (object), res, &error);
+  g_assert_no_error (error);
+}
+
+static void
+on_output_write_finish (GObject        *object,
+                        GAsyncResult   *res,
+                        gpointer        user_data)
+{
+  TestConnection *test = user_data;
+  GError *error = NULL;
+  g_output_stream_write_finish (G_OUTPUT_STREAM (object), res, &error);
+  g_assert_no_error (error);
+
+  g_output_stream_close_async (G_OUTPUT_STREAM (object), G_PRIORITY_DEFAULT, NULL,
+                               on_output_close_finish, test);
+}
+
+static gboolean
+on_incoming_connection (GSocketService     *service,
+                        GSocketConnection  *connection,
+                        GObject            *source_object,
+                        gpointer            user_data)
+{
+  TestConnection *test = user_data;
+  GOutputStream *stream;
+  GTlsCertificate *cert;
+  GError *error = NULL;
+  gchar *path;
+
+  path = g_build_filename (source_dir, "server-and-key.pem", NULL);
+  cert = g_tls_certificate_new_from_file (path, &error);
+  g_assert_no_error (error);
+  g_free (path);
+
+  test->server_connection = g_tls_server_connection_new (G_IO_STREAM (connection),
+                                                         cert, &error);
+  g_assert_no_error (error);
+  g_object_unref (cert);
+
+  g_object_set (test->server_connection, "authentication-mode", test->auth_mode, NULL);
+  g_signal_connect (test->server_connection, "accept-certificate",
+                    G_CALLBACK (on_accept_certificate), test);
+
+  if (test->database)
+    g_tls_connection_set_database (G_TLS_CONNECTION (test->server_connection), test->database);
+
+  stream = g_io_stream_get_output_stream (test->server_connection);
+
+  g_output_stream_write_async (stream, TEST_DATA, TEST_DATA_LENGTH,
+                               G_PRIORITY_DEFAULT, NULL,
+                               on_output_write_finish, test);
+  return FALSE;
+}
+
+static void
+start_server_service (TestConnection *test, GTlsAuthenticationMode auth_mode)
+{
+  GError *error = NULL;
+
+  test->service = g_socket_service_new ();
+  g_socket_listener_add_address (G_SOCKET_LISTENER (test->service),
+                                 G_SOCKET_ADDRESS (test->address),
+                                 G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_TCP,
+                                 NULL, NULL, &error);
+  g_assert_no_error (error);
+
+  test->auth_mode = auth_mode;
+  g_signal_connect (test->service, "incoming", G_CALLBACK (on_incoming_connection), test);
+}
+
+static GIOStream*
+start_server_and_connect_to_it (TestConnection *test, GTlsAuthenticationMode auth_mode)
+{
+  GSocketClient *client;
+  GError *error = NULL;
+  GSocketConnection *connection;
+
+  start_server_service (test, auth_mode);
+
+  client = g_socket_client_new ();
+  connection = g_socket_client_connect (client, G_SOCKET_CONNECTABLE (test->address),
+                                        NULL, &error);
+  g_assert_no_error (error);
+  g_object_unref (client);
+
+  return G_IO_STREAM (connection);
+}
+
+static void
+on_input_read_finish (GObject        *object,
+                      GAsyncResult   *res,
+                      gpointer        user_data)
+{
+  TestConnection *test = user_data;
+  GError *error = NULL;
+  gchar *line, *check;
+
+  line = g_data_input_stream_read_line_finish (G_DATA_INPUT_STREAM (object), res,
+                                               NULL, &error);
+  g_assert_no_error (error);
+  g_assert (line);
+
+  check = g_strdup (TEST_DATA);
+  g_strstrip (check);
+  g_assert_cmpstr (line, ==, check);
+  g_free (check);
+  g_free (line);
+
+  g_main_loop_quit (test->loop);
+}
+
+static void
+read_test_data_async (TestConnection *test)
+{
+  GDataInputStream *stream;
+
+  stream = g_data_input_stream_new (g_io_stream_get_input_stream (test->client_connection));
+  g_assert (stream);
+
+  g_data_input_stream_read_line_async (stream, G_PRIORITY_DEFAULT, NULL,
+                                       on_input_read_finish, test);
+  g_object_unref (stream);
+}
+
+static void
+test_basic_connection (TestConnection *test,
+                       gconstpointer   data)
+{
+  GIOStream *connection;
+  GError *error = NULL;
+
+  connection = start_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE);
+  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
+  g_assert_no_error (error);
+  g_object_unref (connection);
+
+  /* No validation at all in this test */
+  g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection),
+                                                0);
+
+  read_test_data_async (test);
+  g_main_loop_run (test->loop);
+}
+
+static void
+test_verified_connection (TestConnection *test,
+                          gconstpointer   data)
+{
+  GIOStream *connection;
+  GError *error = NULL;
+  gchar *path;
+
+  path = g_build_filename (source_dir, "ca-roots.pem", NULL);
+  test->database = g_tls_file_database_new (path, &error);
+  g_assert_no_error (error);
+  g_assert (test->database);
+  g_free (path);
+
+  connection = start_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE);
+  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
+  g_assert_no_error (error);
+  g_assert (test->client_connection);
+
+  g_tls_connection_set_database (G_TLS_CONNECTION (test->client_connection), test->database);
+
+  /* All validation in this test */
+  g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection),
+                                                G_TLS_CERTIFICATE_VALIDATE_ALL);
+
+  read_test_data_async (test);
+  g_main_loop_run (test->loop);
+}
+
+static void
+test_client_auth_connection (TestConnection *test,
+                             gconstpointer   data)
+{
+  GIOStream *connection;
+  GError *error = NULL;
+  GTlsCertificate *cert;
+  gchar *path;
+
+  path = g_build_filename (source_dir, "ca-roots.pem", NULL);
+  test->database = g_tls_file_database_new (path, &error);
+  g_assert_no_error (error);
+  g_assert (test->database);
+  g_free (path);
+
+  connection = start_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_REQUIRED);
+  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
+  g_assert_no_error (error);
+  g_assert (test->client_connection);
+
+  g_tls_connection_set_database (G_TLS_CONNECTION (test->client_connection), test->database);
+
+  path = g_build_filename (source_dir, "client-and-key.pem", NULL);
+  cert = g_tls_certificate_new_from_file (path, &error);
+  g_assert_no_error (error);
+  g_free (path);
+
+  g_tls_connection_set_certificate (G_TLS_CONNECTION (test->client_connection), cert);
+  g_object_unref (cert);
+
+  /* All validation in this test */
+  g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection),
+                                                G_TLS_CERTIFICATE_VALIDATE_ALL);
+
+  read_test_data_async (test);
+  g_main_loop_run (test->loop);
+}
+
+static void
+test_connection_no_database (TestConnection *test,
+                             gconstpointer   data)
+{
+  GIOStream *connection;
+  GError *error = NULL;
+
+  connection = start_server_and_connect_to_it (test, G_TLS_AUTHENTICATION_NONE);
+  test->client_connection = g_tls_client_connection_new (connection, test->identity, &error);
+  g_assert_no_error (error);
+  g_assert (test->client_connection);
+
+  /* Overrides loading of the default database */
+  g_tls_connection_set_database (G_TLS_CONNECTION (test->client_connection), NULL);
+
+  /* All validation in this test */
+  g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (test->client_connection),
+                                                G_TLS_CERTIFICATE_VALIDATE_ALL);
+
+  test->accept_flags = G_TLS_CERTIFICATE_UNKNOWN_CA;
+  g_signal_connect (test->client_connection, "accept-certificate",
+                    G_CALLBACK (on_accept_certificate), test);
+
+  read_test_data_async (test);
+  g_main_loop_run (test->loop);
+}
+
+/* -----------------------------------------------------------------------------
+ * CERTIFICATE TESTS
+ */
+
+typedef struct {
+  gchar *pem;
+  gsize pem_length;
+  GByteArray *der;
+} TestCertificate;
+
+static void
+setup_certificate (TestCertificate *test, gconstpointer data)
+{
+  GError *error = NULL;
+  gchar *path;
+  gchar *contents;
+  gsize length;
+
+  path = g_build_filename (source_dir, "server.pem", NULL);
+  g_file_get_contents (path, &test->pem, &test->pem_length, &error);
+  g_assert_no_error (error);
+  g_free (path);
+
+  path = g_build_filename (source_dir, "server.der", NULL);
+  g_file_get_contents (path, &contents, &length, &error);
+  g_assert_no_error (error);
+  g_free (path);
+
+  test->der = g_byte_array_new ();
+  g_byte_array_append (test->der, (guint8*)contents, length);
+  g_free (contents);
+}
+
+static void
+teardown_certificate (TestCertificate *test, gconstpointer data)
+{
+  g_free (test->pem);
+  g_byte_array_free (test->der, TRUE);
+}
+
+static void
+test_create_destroy_certificate_pem (TestCertificate *test, gconstpointer data)
+{
+  GTlsCertificate *cert;
+  gchar *pem = NULL;
+  GError *error = NULL;
+
+  cert = g_tls_certificate_new_from_pem (test->pem, test->pem_length, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (cert));
+
+  g_object_get (cert, "certificate-pem", &pem, NULL);
+  g_assert_cmpstr (pem, ==, test->pem);
+  g_free (pem);
+
+  g_object_unref (cert);
+  g_assert (!G_IS_TLS_CERTIFICATE (cert));
+}
+
+static void
+test_create_destroy_certificate_der (TestCertificate *test, gconstpointer data)
+{
+  GTlsCertificate *cert;
+  GByteArray *der = NULL;
+  GError *error = NULL;
+  GTlsBackend *backend;
+
+  backend = g_tls_backend_get_default ();
+  cert = g_initable_new (g_tls_backend_get_certificate_type (backend),
+                         NULL, &error,
+                         "certificate", test->der,
+                         NULL);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (cert));
+
+  g_object_get (cert, "certificate", &der, NULL);
+  g_assert (der);
+  g_assert_cmpuint (der->len, ==, test->der->len);
+  g_assert (memcmp (der->data, test->der->data, der->len) == 0);
+  g_byte_array_unref (der);
+
+  g_object_unref (cert);
+  g_assert (!G_IS_TLS_CERTIFICATE (cert));
+}
+
+static void
+test_create_certificate_with_issuer (TestCertificate   *test,
+                                     gconstpointer      data)
+{
+  GTlsCertificate *cert, *issuer, *check;
+  GError *error = NULL;
+  GTlsBackend *backend;
+  gchar *path;
+
+  path = g_build_filename (source_dir, "ca.pem", NULL);
+  issuer = g_tls_certificate_new_from_file (path, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (issuer));
+  g_free (path);
+
+  backend = g_tls_backend_get_default ();
+  cert = g_initable_new (g_tls_backend_get_certificate_type (backend),
+                         NULL, &error,
+                         "certificate-pem", test->pem,
+                         "issuer", issuer,
+                         NULL);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (cert));
+
+  g_object_unref (issuer);
+  g_assert (G_IS_TLS_CERTIFICATE (issuer));
+
+  check = g_tls_certificate_get_issuer (cert);
+  g_assert (check == issuer);
+
+  g_object_unref (cert);
+  g_assert (!G_IS_TLS_CERTIFICATE (cert));
+  g_assert (!G_IS_TLS_CERTIFICATE (issuer));
+}
+
+/* -----------------------------------------------------------------------------
+ * CERTIFICATE VERIFY
+ */
+
+typedef struct {
+  GTlsCertificate *cert;
+  GTlsCertificate *anchor;
+  GSocketConnectable *identity;
+  GTlsDatabase *database;
+} TestVerify;
+
+static void
+setup_verify (TestVerify     *test,
+              gconstpointer   data)
+{
+  GError *error = NULL;
+  gchar *path;
+
+  path = g_build_filename (source_dir, "server.pem", NULL);
+  test->cert = g_tls_certificate_new_from_file (path, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (test->cert));
+  g_free (path);
+
+  test->identity = g_network_address_new ("server.example.com", 80);
+
+  path = g_build_filename (source_dir, "ca.pem", NULL);
+  test->anchor = g_tls_certificate_new_from_file (path, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (test->anchor));
+  test->database = g_tls_file_database_new (path, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_DATABASE (test->database));
+  g_free (path);
+}
+
+static void
+teardown_verify (TestVerify      *test,
+                 gconstpointer    data)
+{
+  g_assert (G_IS_TLS_CERTIFICATE (test->cert));
+  g_object_unref (test->cert);
+  g_assert (!G_IS_TLS_CERTIFICATE (test->cert));
+
+  g_assert (G_IS_TLS_CERTIFICATE (test->anchor));
+  g_object_unref (test->anchor);
+  g_assert (!G_IS_TLS_CERTIFICATE (test->anchor));
+
+  g_assert (G_IS_TLS_DATABASE (test->database));
+  g_object_unref (test->database);
+  g_assert (!G_IS_TLS_DATABASE (test->database));
+}
+
+static void
+test_verify_certificate_good (TestVerify      *test,
+                              gconstpointer    data)
+{
+  GTlsCertificateFlags errors;
+
+  errors = g_tls_certificate_verify (test->cert, test->identity, test->anchor);
+  g_assert_cmpuint (errors, ==, 0);
+
+  errors = g_tls_certificate_verify (test->cert, NULL, test->anchor);
+  g_assert_cmpuint (errors, ==, 0);
+}
+
+static void
+test_verify_certificate_bad_identity (TestVerify      *test,
+                                      gconstpointer    data)
+{
+  GSocketConnectable *identity;
+  GTlsCertificateFlags errors;
+
+  identity = g_network_address_new ("other.example.com", 80);
+
+  errors = g_tls_certificate_verify (test->cert, identity, test->anchor);
+  g_assert_cmpuint (errors, ==, G_TLS_CERTIFICATE_BAD_IDENTITY);
+
+  g_object_unref (identity);
+}
+
+static void
+test_verify_certificate_bad_ca (TestVerify      *test,
+                                gconstpointer    data)
+{
+  GTlsCertificateFlags errors;
+  GTlsCertificate *cert;
+  GError *error = NULL;
+  gchar *path;
+
+  /* Use a client certificate as the CA, which is wrong */
+  path = g_build_filename (source_dir, "client.pem", NULL);
+  cert = g_tls_certificate_new_from_file (path, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (cert));
+  g_free (path);
+
+  errors = g_tls_certificate_verify (test->cert, test->identity, cert);
+  g_assert_cmpuint (errors, ==, G_TLS_CERTIFICATE_UNKNOWN_CA);
+
+  g_object_unref (cert);
+}
+
+static void
+test_verify_certificate_bad_before (TestVerify      *test,
+                                    gconstpointer    data)
+{
+  GTlsCertificateFlags errors;
+  GTlsCertificate *cert;
+  GError *error = NULL;
+  gchar *path;
+
+  /* This is a certificate in the future */
+  path = g_build_filename (source_dir, "client-future.pem", NULL);
+  cert = g_tls_certificate_new_from_file (path, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (cert));
+  g_free (path);
+
+  errors = g_tls_certificate_verify (cert, NULL, test->anchor);
+  g_assert_cmpuint (errors, ==, G_TLS_CERTIFICATE_NOT_ACTIVATED);
+
+  g_object_unref (cert);
+}
+
+static void
+test_verify_certificate_bad_expired (TestVerify      *test,
+                                     gconstpointer    data)
+{
+  GTlsCertificateFlags errors;
+  GTlsCertificate *cert;
+  GError *error = NULL;
+  gchar *path;
+
+  /* This is a certificate in the future */
+  path = g_build_filename (source_dir, "client-past.pem", NULL);
+  cert = g_tls_certificate_new_from_file (path, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (cert));
+  g_free (path);
+
+  errors = g_tls_certificate_verify (cert, NULL, test->anchor);
+  g_assert_cmpuint (errors, ==, G_TLS_CERTIFICATE_EXPIRED);
+
+  g_object_unref (cert);
+}
+
+static void
+test_verify_certificate_bad_combo (TestVerify      *test,
+                                   gconstpointer    data)
+{
+  GTlsCertificate *cert;
+  GTlsCertificate *cacert;
+  GSocketConnectable *identity;
+  GTlsCertificateFlags errors;
+  GError *error = NULL;
+  gchar *path;
+
+  path = g_build_filename (source_dir, "client-past.pem", NULL);
+  cert = g_tls_certificate_new_from_file (path, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (cert));
+  g_free (path);
+
+  /* Unrelated cert used as certificate authority */
+  path = g_build_filename (source_dir, "server-self.pem", NULL);
+  cacert = g_tls_certificate_new_from_file (path, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (cacert));
+  g_free (path);
+
+  /*
+   * - Use unrelated cert as CA
+   * - Use wrong identity.
+   * - Use expired certificate.
+   */
+
+  identity = g_network_address_new ("other.example.com", 80);
+
+  errors = g_tls_certificate_verify (cert, identity, cacert);
+  g_assert_cmpuint (errors, ==, G_TLS_CERTIFICATE_UNKNOWN_CA |
+                    G_TLS_CERTIFICATE_BAD_IDENTITY | G_TLS_CERTIFICATE_EXPIRED);
+
+  g_object_unref (cert);
+  g_object_unref (cacert);
+  g_object_unref (identity);
+}
+
+static void
+test_verify_database_good (TestVerify      *test,
+                           gconstpointer    data)
+{
+  GTlsCertificateFlags errors;
+  GError *error = NULL;
+
+  errors = g_tls_database_verify_chain (test->database, test->cert,
+                                        G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER,
+                                        test->identity, NULL, 0, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_cmpuint (errors, ==, 0);
+
+  errors = g_tls_database_verify_chain (test->database, test->cert,
+                                        G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER,
+                                        NULL, NULL, 0, NULL, &error);
+  g_assert_cmpuint (errors, ==, 0);
+}
+
+static void
+test_verify_database_bad_identity (TestVerify      *test,
+                                   gconstpointer    data)
+{
+  GSocketConnectable *identity;
+  GTlsCertificateFlags errors;
+  GError *error = NULL;
+
+  identity = g_network_address_new ("other.example.com", 80);
+
+  errors = g_tls_database_verify_chain (test->database, test->cert,
+                                        G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER,
+                                        identity, NULL, 0, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_cmpuint (errors, ==, G_TLS_CERTIFICATE_BAD_IDENTITY);
+
+  g_object_unref (identity);
+}
+
+static void
+test_verify_database_bad_ca (TestVerify      *test,
+                             gconstpointer    data)
+{
+  GTlsCertificateFlags errors;
+  GTlsCertificate *cert;
+  GError *error = NULL;
+  gchar *path;
+
+  /* Use another certificate which isn't in our CA list */
+  path = g_build_filename (source_dir, "server-self.pem", NULL);
+  cert = g_tls_certificate_new_from_file (path, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (cert));
+  g_free (path);
+
+  errors = g_tls_database_verify_chain (test->database, cert,
+                                        G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER,
+                                        test->identity, NULL, 0, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_cmpuint (errors, ==, G_TLS_CERTIFICATE_UNKNOWN_CA);
+
+  g_object_unref (cert);
+}
+
+static void
+test_verify_database_bad_before (TestVerify      *test,
+                                 gconstpointer    data)
+{
+  GTlsCertificateFlags errors;
+  GTlsCertificate *cert;
+  GError *error = NULL;
+  gchar *path;
+
+  /* This is a certificate in the future */
+  path = g_build_filename (source_dir, "client-future.pem", NULL);
+  cert = g_tls_certificate_new_from_file (path, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (cert));
+  g_free (path);
+
+  errors = g_tls_database_verify_chain (test->database, cert,
+                                        G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER,
+                                        NULL, NULL, 0, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_cmpuint (errors, ==, G_TLS_CERTIFICATE_NOT_ACTIVATED);
+
+  g_object_unref (cert);
+}
+
+static void
+test_verify_database_bad_expired (TestVerify      *test,
+                                  gconstpointer    data)
+{
+  GTlsCertificateFlags errors;
+  GTlsCertificate *cert;
+  GError *error = NULL;
+  gchar *path;
+
+  /* This is a certificate in the future */
+  path = g_build_filename (source_dir, "client-past.pem", NULL);
+  cert = g_tls_certificate_new_from_file (path, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (cert));
+  g_free (path);
+
+  errors = g_tls_database_verify_chain (test->database, cert,
+                                        G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER,
+                                        NULL, NULL, 0, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_cmpuint (errors, ==, G_TLS_CERTIFICATE_EXPIRED);
+
+  g_object_unref (cert);
+}
+
+static void
+test_verify_database_bad_combo (TestVerify      *test,
+                                gconstpointer    data)
+{
+  GTlsCertificate *cert;
+  GSocketConnectable *identity;
+  GTlsCertificateFlags errors;
+  GError *error = NULL;
+  gchar *path;
+
+  path = g_build_filename (source_dir, "server-self.pem", NULL);
+  cert = g_tls_certificate_new_from_file (path, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (cert));
+  g_free (path);
+
+  /*
+   * - Use is self signed
+   * - Use wrong identity.
+   */
+
+  identity = g_network_address_new ("other.example.com", 80);
+
+  errors = g_tls_database_verify_chain (test->database, cert,
+                                        G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER,
+                                        identity, NULL, 0, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_cmpuint (errors, ==, G_TLS_CERTIFICATE_UNKNOWN_CA |
+                    G_TLS_CERTIFICATE_BAD_IDENTITY);
+
+  g_object_unref (cert);
+  g_object_unref (identity);
+}
+
+/* -----------------------------------------------------------------------------
+ * FILE DATABASE
+ */
+
+typedef struct {
+  GTlsDatabase *database;
+  gchar *path;
+} TestFileDatabase;
+
+static void
+setup_file_database (TestFileDatabase *test,
+                     gconstpointer     data)
+{
+  GError *error = NULL;
+
+  test->path = g_build_filename (source_dir, "ca-roots.pem", NULL);
+  test->database = g_tls_file_database_new (test->path, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_DATABASE (test->database));
+}
+
+static void
+teardown_file_database (TestFileDatabase *test,
+                        gconstpointer     data)
+{
+  g_assert (G_IS_TLS_DATABASE (test->database));
+  g_object_unref (test->database);
+  g_assert (!G_IS_TLS_DATABASE (test->database));
+
+  g_free (test->path);
+}
+
+static void
+test_file_database_handle (TestFileDatabase *test,
+                           gconstpointer     unused)
+{
+  GTlsCertificate *certificate;
+  GTlsCertificate *check;
+  GError *error = NULL;
+  gchar *handle;
+  gchar *path;
+
+  /*
+   * ca.pem is in the ca-roots.pem that the test->database represents.
+   * So it should be able to create a handle for it and treat it as if it
+   * is 'in' the database.
+   */
+
+  path = g_build_filename (source_dir, "ca.pem", NULL);
+  certificate = g_tls_certificate_new_from_file (path, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (certificate));
+  g_free (path);
+
+  handle = g_tls_database_create_certificate_handle (test->database, certificate);
+  g_assert (handle != NULL);
+  g_assert (g_str_has_prefix (handle, "file:///"));
+
+  check = g_tls_database_lookup_certificate_for_handle (test->database, handle,
+                                                        NULL, G_TLS_DATABASE_LOOKUP_NONE,
+                                                        NULL, &error);
+  g_assert_no_error (error);
+  g_assert (G_IS_TLS_CERTIFICATE (check));
+
+  g_free (handle);
+  g_object_unref (check);
+  g_object_unref (certificate);
+}
+
+static void
+test_file_database_handle_invalid (TestFileDatabase *test,
+                                   gconstpointer     unused)
+{
+  GTlsCertificate *certificate;
+  GError *error = NULL;
+
+  certificate = g_tls_database_lookup_certificate_for_handle (test->database, "blah:blah",
+                                                              NULL, G_TLS_DATABASE_LOOKUP_NONE,
+                                                              NULL, &error);
+  g_assert_no_error (error);
+  g_assert (certificate == NULL);
+}
+
+/* -----------------------------------------------------------------------------
+ * BACKEND
+ */
+
+static void
+test_default_database_is_singleton (void)
+{
+  GTlsBackend *backend;
+  GTlsDatabase *database;
+  GTlsDatabase *check;
+
+  backend = g_tls_backend_get_default ();
+  g_assert (G_IS_TLS_BACKEND (backend));
+
+  database = g_tls_backend_get_default_database (backend);
+  g_assert (G_IS_TLS_DATABASE (database));
+
+  check = g_tls_backend_get_default_database (backend);
+  g_assert (database == check);
+
+  g_object_unref (database);
+  g_object_unref (check);
+}
+
+int
+main (int   argc,
+      char *argv[])
+{
+  gchar *dir;
+  int ret;
+
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  /* Use the gnutls database */
+  if (!g_getenv ("GIO_USE_TLS"))
+      g_setenv ("GIO_USE_TLS", "gnutls", TRUE);
+
+  /* Create an absolute path for test files */
+  if (g_path_is_absolute (SRCDIR))
+    {
+      source_dir = g_build_filename (SRCDIR, "files", NULL);
+    }
+  else
+    {
+      dir = g_get_current_dir ();
+      source_dir = g_build_filename (dir, SRCDIR, "files", NULL);
+      g_free (dir);
+    }
+
+  g_test_add ("/tls/connection/basic", TestConnection, NULL,
+              setup_connection, test_basic_connection, teardown_connection);
+  g_test_add ("/tls/connection/verified", TestConnection, NULL,
+              setup_connection, test_verified_connection, teardown_connection);
+  g_test_add ("/tls/connection/client-auth", TestConnection, NULL,
+              setup_connection, test_client_auth_connection, teardown_connection);
+  g_test_add ("/tls/connection/no-database", TestConnection, NULL,
+              setup_connection, test_connection_no_database, teardown_connection);
+
+  g_test_add_func ("/tls/backend/default-database-is-singleton",
+                   test_default_database_is_singleton);
+
+  g_test_add ("/tls/certificate/create-destroy-pem", TestCertificate, NULL,
+              setup_certificate, test_create_destroy_certificate_pem, teardown_certificate);
+  g_test_add ("/tls/certificate/create-destroy-der", TestCertificate, NULL,
+              setup_certificate, test_create_destroy_certificate_der, teardown_certificate);
+  g_test_add ("/tls/certificate/create-with-issuer", TestCertificate, NULL,
+              setup_certificate, test_create_certificate_with_issuer, teardown_certificate);
+
+  g_test_add ("/tls/certificate/verify-good", TestVerify, NULL,
+              setup_verify, test_verify_certificate_good, teardown_verify);
+  g_test_add ("/tls/certificate/verify-bad-identity", TestVerify, NULL,
+              setup_verify, test_verify_certificate_bad_identity, teardown_verify);
+  g_test_add ("/tls/certificate/verify-bad-ca", TestVerify, NULL,
+              setup_verify, test_verify_certificate_bad_ca, teardown_verify);
+  g_test_add ("/tls/certificate/verify-bad-before", TestVerify, NULL,
+              setup_verify, test_verify_certificate_bad_before, teardown_verify);
+  g_test_add ("/tls/certificate/verify-bad-expired", TestVerify, NULL,
+              setup_verify, test_verify_certificate_bad_expired, teardown_verify);
+  g_test_add ("/tls/certificate/verify-bad-combo", TestVerify, NULL,
+              setup_verify, test_verify_certificate_bad_combo, teardown_verify);
+  g_test_add ("/tls/database/verify-good", TestVerify, NULL,
+              setup_verify, test_verify_database_good, teardown_verify);
+  g_test_add ("/tls/database/verify-bad-identity", TestVerify, NULL,
+              setup_verify, test_verify_database_bad_identity, teardown_verify);
+  g_test_add ("/tls/database/verify-bad-ca", TestVerify, NULL,
+              setup_verify, test_verify_database_bad_ca, teardown_verify);
+  g_test_add ("/tls/database/verify-bad-before", TestVerify, NULL,
+              setup_verify, test_verify_database_bad_before, teardown_verify);
+  g_test_add ("/tls/database/verify-bad-expired", TestVerify, NULL,
+              setup_verify, test_verify_database_bad_expired, teardown_verify);
+  g_test_add ("/tls/database/verify-bad-combo", TestVerify, NULL,
+              setup_verify, test_verify_database_bad_combo, teardown_verify);
+
+  g_test_add ("/tls/file-database/test-handle", TestFileDatabase, NULL,
+              setup_file_database, test_file_database_handle, teardown_file_database);
+  g_test_add ("/tls/file-database/test-handle-invalid", TestFileDatabase, NULL,
+              setup_file_database, test_file_database_handle_invalid, teardown_file_database);
+
+  ret = g_test_run();
+
+  g_free (source_dir);
+  return ret;
+}



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