[gnome-keyring/trust-store] [gcr] Add GcrCertificateChain



commit 2d97fa8b25bd328c5f678412e2804c0d6bbeb071
Author: Stef Walter <stefw collabora co uk>
Date:   Fri Dec 10 21:08:55 2010 +0000

    [gcr] Add GcrCertificateChain
    
    Represents a certificate chain, and has an operation which builds
    the chain into a complete chain and identifies the type of chain.

 docs/reference/gcr/gcr-docs.sgml          |    1 +
 docs/reference/gcr/gcr-sections.txt       |   28 ++
 docs/reference/gcr/gcr.types              |    3 +-
 gcr/Makefile.am                           |    2 +
 gcr/gcr-certificate-chain.c               |  735 +++++++++++++++++++++++++++++
 gcr/gcr-certificate-chain.h               |  113 +++++
 gcr/gcr.h                                 |    1 +
 gcr/tests/Makefile.am                     |    1 +
 gcr/tests/test-certificate-chain.c        |  580 +++++++++++++++++++++++
 gcr/tests/test-data/collabora-ca.cer      |  Bin 0 -> 1536 bytes
 gcr/tests/test-data/dhansak-collabora.cer |  Bin 0 -> 1200 bytes
 11 files changed, 1463 insertions(+), 1 deletions(-)
---
diff --git a/docs/reference/gcr/gcr-docs.sgml b/docs/reference/gcr/gcr-docs.sgml
index 767fc97..d7e826c 100644
--- a/docs/reference/gcr/gcr-docs.sgml
+++ b/docs/reference/gcr/gcr-docs.sgml
@@ -16,6 +16,7 @@
 		<xi:include href="xml/gcr-certificate.xml"/>
 		<xi:include href="xml/gcr-simple-certificate.xml"/>
 		<xi:include href="xml/gcr-pkcs11-certificate.xml"/>
+		<xi:include href="xml/gcr-certificate-chain.xml"/>
 	</part>
 
 	<part id="storage">
diff --git a/docs/reference/gcr/gcr-sections.txt b/docs/reference/gcr/gcr-sections.txt
index 299ce95..1d714d1 100644
--- a/docs/reference/gcr/gcr-sections.txt
+++ b/docs/reference/gcr/gcr-sections.txt
@@ -178,4 +178,32 @@ gcr_pkcs11_get_trust_lookup_modules
 gcr_pkcs11_get_trust_store_slot
 gcr_pkcs11_get_trust_store_uri
 gcr_pkcs11_set_trust_store_uri
+</SECTION>
+
+<SECTION>
+<FILE>gcr-certificate-chain</FILE>
+GcrCertificateChain
+GcrCertificateChainType
+gcr_certificate_chain_new
+gcr_certificate_chain_add
+gcr_certificate_chain_get_certificate
+gcr_certificate_chain_get_chain_type
+gcr_certificate_chain_get_anchor
+gcr_certificate_chain_get_endpoint
+gcr_certificate_chain_get_length
+GcrCertificateChainFlags
+gcr_certificate_chain_build
+gcr_certificate_chain_build_async
+gcr_certificate_chain_build_finish
+<SUBSECTION Standard>
+gcr_certificate_chain_get_type
+GCR_CERTIFICATE_CHAIN
+GCR_TYPE_CERTIFICATE_CHAIN
+GCR_CERTIFICATE_CHAIN_CLASS
+GCR_CERTIFICATE_CHAIN_GET_CLASS
+GCR_IS_CERTIFICATE_CHAIN
+GCR_IS_CERTIFICATE_CHAIN_CLASS
+GcrCertificateChainClass
+GcrCertificateChainPrivate
+
 </SECTION>
\ No newline at end of file
diff --git a/docs/reference/gcr/gcr.types b/docs/reference/gcr/gcr.types
index dc87a0a..a54679c 100644
--- a/docs/reference/gcr/gcr.types
+++ b/docs/reference/gcr/gcr.types
@@ -2,4 +2,5 @@ gcr_parser_get_type
 gcr_certificate_get_type
 gcr_importer_get_type
 gcr_simple_certificate_get_type
-gcr_pkcs11_certificate_get_type
\ No newline at end of file
+gcr_pkcs11_certificate_get_type
+gcr_certificate_chain_get_type
\ No newline at end of file
diff --git a/gcr/Makefile.am b/gcr/Makefile.am
index 015d5e8..aad078a 100644
--- a/gcr/Makefile.am
+++ b/gcr/Makefile.am
@@ -16,6 +16,7 @@ incdir = $(includedir)/gcr GCR_VERSION_SUFFIX@/gcr
 inc_HEADERS = \
 	gcr.h \
 	gcr-certificate.h \
+	gcr-certificate-chain.h \
 	gcr-certificate-renderer.h \
 	gcr-certificate-widget.h \
 	gcr-key-renderer.h \
@@ -51,6 +52,7 @@ lib_LTLIBRARIES = libgcr GCR_VERSION_SUFFIX@.la
 
 libgcr GCR_VERSION_SUFFIX@_la_SOURCES = \
 	gcr-certificate.c gcr-certificate.h \
+	gcr-certificate-chain.c gcr-certificate-chain.h \
 	gcr-certificate-renderer.c gcr-certificate-renderer.h \
 	gcr-certificate-widget.c gcr-certificate-widget.h \
 	gcr-display-view.c gcr-display-view.h \
diff --git a/gcr/gcr-certificate-chain.c b/gcr/gcr-certificate-chain.c
new file mode 100644
index 0000000..89fc921
--- /dev/null
+++ b/gcr/gcr-certificate-chain.c
@@ -0,0 +1,735 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program 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 program; 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 "gcr-certificate-chain.h"
+
+#include "gcr-certificate.h"
+#include "gcr-pkcs11-certificate.h"
+#include "gcr-simple-certificate.h"
+
+#include "gcr-trust.h"
+
+/**
+ * SECTION:gcr-certificate-chain
+ * @title: GcrCertificateChain
+ * @short_description: A certificate chain
+ *
+ * #GcrCertificateChain represents a chain of certificates, normally used to
+ * validate the trust in a certificate. An X.509 certificate chain has one
+ * endpoint certificate (the one for which trust is being verified) and then
+ * in turn the certificate that issued each previous certificate in the chain.
+ *
+ * This functionality is for building of certificate chains not for validating
+ * them. Use your favorite crypto library to validate trust in a certificate
+ * chain once its built.
+ *
+ * The order of certificates in the chain should be first the endpoint
+ * certificates and then the signing certificates.
+ *
+ * Create a new certificate chain with gcr_certificate_chain_new() and then
+ * add the certificates with gcr_certificate_chain_add().
+ *
+ * You can then use gcr_certificate_chain_build() to build the remainder of
+ * the chain. This will lookup missing certificates in PKCS\#11 modules and
+ * also check that each certificate in the chain is the signer of the previous
+ * one. If a trust anchor, pinned certificate, or self-signed certificate is
+ * found, then the chain is considered built. Any extra certificates are
+ * removed from the chain.
+ *
+ * Once the certificate chain has been built, you can access its type
+ * through gcr_certificate_chain_get_chain_type(). The type signifies whether
+ * the chain is anchored on a trust root, self-signed, incomplete etc. See
+ * #GcrCertificateChainType for information on the various types.
+ *
+ * It's important to understand that the building of a certificate chain is
+ * merely the first step towards verifying trust in a certificate.
+ */
+
+/**
+ * GcrCertificateChainType:
+ * @GCR_CERTIFICATE_CHAIN_UNKNOWN: The certificate chain's type is unknown.
+ * When a chain is not yet built it has this type. If a chain is modified after
+ * being built, it has this type.
+ * @GCR_CERTIFICATE_CHAIN_INCOMPLETE: A full chain could not be loaded. The
+ * chain does not end with a self-signed certificate, a trusted anchor, or a
+ * pinned certificate.
+ * @GCR_CERTIFICATE_CHAIN_SELFSIGNED: The chain ends with a self-signed
+ * certificate. No trust anchor was found.
+ * @GCR_CERTIFICATE_CHAIN_ANCHORED: The chain ends with an anchored
+ * certificate. The anchored certificate is not necessarily self-signed.
+ * @GCR_CERTIFICATE_CHAIN_PINNED: The chain represents a pinned certificate. A
+ * pinned certificate is an exception which trusts a given certificate
+ * explicitly for a purpose and communication with a certain peer.
+ *
+ * The type of a built certificate chain. Will be set to
+ * %GCR_CERTIFICATE_CHAIN_UNKNOWN for certificate chains that have not been
+ * built.
+ */
+
+/**
+ * GcrCertificateChainFlags:
+ * @GCR_CERTIFICATE_CHAIN_FLAG_NO_LOOKUPS: If this flag is specified then no
+ * lookups for anchors or pinned certificates are done, and the resulting chain
+ * will be neither anchored or pinned. Additionally no missing certificate
+ * authorities are looked up in PKCS\#11.
+ *
+ * Flags to be used with the gcr_certificate_chain_build() operation.
+ */
+
+enum {
+	PROP_0,
+	PROP_TYPE,
+	PROP_LENGTH,
+};
+
+struct _GcrCertificateChainPrivate {
+	GPtrArray *certificates;
+	GcrCertificateChainType type;
+
+	/* Used in build operation */
+	gchar *purpose;
+	gchar *peer;
+	guint flags;
+};
+
+static GQuark Q_ORIGINAL_CERT = 0;
+static GQuark Q_OPERATION_DATA = 0;
+
+G_DEFINE_TYPE (GcrCertificateChain, gcr_certificate_chain, G_TYPE_OBJECT);
+
+/* -----------------------------------------------------------------------------
+ * INTERNAL
+ */
+
+static void
+free_chain_private (gpointer data)
+{
+	GcrCertificateChainPrivate *pv = data;
+	g_ptr_array_unref (pv->certificates);
+	g_free (pv->purpose);
+	g_free (pv->peer);
+	g_slice_free (GcrCertificateChainPrivate, pv);
+}
+
+static GcrCertificateChainPrivate*
+new_chain_private (void)
+{
+	GcrCertificateChainPrivate *pv = g_slice_new0 (GcrCertificateChainPrivate);
+	pv->certificates = g_ptr_array_new_with_free_func (g_object_unref);
+	return pv;
+}
+
+static GcrCertificateChainPrivate*
+prep_chain_private (GcrCertificateChainPrivate *orig, const gchar *purpose,
+                    const gchar *peer, guint flags)
+{
+	GcrCertificateChainPrivate *pv;
+	GcrCertificate *certificate;
+	guint i;
+
+	g_assert (orig);
+	g_assert (purpose);
+
+	pv = new_chain_private ();
+	for (i = 0; i < orig->certificates->len; ++i) {
+		certificate = g_ptr_array_index (orig->certificates, i);
+		g_ptr_array_add (pv->certificates, g_object_ref (certificate));
+	}
+
+	pv->type = orig->type;
+	pv->purpose = g_strdup (purpose);
+	pv->peer = g_strdup (peer);
+	pv->flags = flags;
+	return pv;
+}
+
+static GcrCertificateChainPrivate*
+prep_chain_private_thread_safe (GcrCertificateChainPrivate *orig, const gchar *purpose,
+                                const gchar *peer, guint flags)
+{
+	GcrCertificateChainPrivate *pv;
+	GcrCertificate *certificate, *safe;
+	gconstpointer der;
+	gsize n_der;
+	guint i;
+
+	g_assert (orig);
+
+	pv = prep_chain_private (orig, purpose, peer, flags);
+
+	for (i = 0; i < pv->certificates->len; ++i) {
+		certificate = g_ptr_array_index (pv->certificates, i);
+
+		/* We regard these types as thread safe */
+		if (GCR_IS_SIMPLE_CERTIFICATE (certificate) ||
+		    GCR_IS_PKCS11_CERTIFICATE (certificate)) {
+			safe = g_object_ref (certificate);
+
+		/* Otherwise copy the certificate data */
+		} else {
+			der = gcr_certificate_get_der_data (certificate, &n_der);
+			g_return_val_if_fail (der, NULL);
+			safe = gcr_simple_certificate_new (der, n_der);
+		}
+
+		/* Always set the original certificate onto the safe one */
+		g_object_set_qdata_full (G_OBJECT (safe), Q_ORIGINAL_CERT,
+		                         g_object_ref (certificate), g_object_unref);
+
+		g_ptr_array_index (pv->certificates, i) = safe;
+		g_object_unref (certificate);
+	}
+
+	return pv;
+}
+
+static GcrCertificateChainPrivate*
+cleanup_chain_private (GcrCertificateChainPrivate *pv)
+{
+	GcrCertificate *certificate, *orig;
+	guint i;
+
+	for (i = 0; i < pv->certificates->len; ++i) {
+		certificate = g_ptr_array_index (pv->certificates, i);
+
+		/* If there's an original certificate set, then replace it back */
+		orig = g_object_get_qdata (G_OBJECT (certificate), Q_ORIGINAL_CERT);
+		if (orig != NULL) {
+			g_ptr_array_index (pv->certificates, i) = g_object_ref (orig);
+			g_object_unref (certificate);
+		}
+	}
+
+	return pv;
+}
+
+static gboolean
+perform_build_chain (GcrCertificateChainPrivate *pv, GCancellable *cancellable,
+                     GError **rerror)
+{
+	GError *error = NULL;
+	GcrCertificate *certificate;
+	gboolean lookups;
+	gboolean ret;
+	guint length;
+
+	g_assert (pv);
+	g_assert (pv->certificates);
+
+	pv->type = GCR_CERTIFICATE_CHAIN_UNKNOWN;
+	lookups = !((pv->flags & GCR_CERTIFICATE_CHAIN_FLAG_NO_LOOKUPS) == GCR_CERTIFICATE_CHAIN_FLAG_NO_LOOKUPS);
+
+	/* This chain is built */
+	if (!pv->certificates->len)
+		return TRUE;
+
+	/* First check for pinned certificates */
+	certificate = g_ptr_array_index (pv->certificates, 0);
+	if (lookups && pv->peer) {
+		ret = gcr_trust_is_certificate_exception (certificate, pv->purpose,
+		                                          pv->peer, cancellable, &error);
+		if (!ret && error) {
+			g_propagate_error (rerror, error);
+			return FALSE;
+		}
+
+		/*
+		 * This is a pinned certificate and the rest of the chain
+		 * is irrelevant, so truncate chain and consider built.
+		 */
+		if (ret) {
+			g_ptr_array_set_size (pv->certificates, 1);
+			pv->type = GCR_CERTIFICATE_CHAIN_PINNED;
+			return TRUE;
+		}
+	}
+
+	length = 1;
+
+	/* The first certificate is always unconditionally in the chain */
+	while (pv->type == GCR_CERTIFICATE_CHAIN_UNKNOWN) {
+
+		/* Stop the chain if previous was self-signed */
+		if (gcr_certificate_is_issuer (certificate, certificate)) {
+			pv->type = GCR_CERTIFICATE_CHAIN_SELFSIGNED;
+			break;
+		}
+
+		/* Try the next certificate in the chain */
+		if (length < pv->certificates->len) {
+			certificate = g_ptr_array_index (pv->certificates, length);
+
+		/* No more in chain, try to lookup */
+		} else if (lookups) {
+			certificate = gcr_pkcs11_certificate_lookup_issuer (certificate,
+			                                                    cancellable, &error);
+			if (error != NULL) {
+				g_propagate_error (rerror, error);
+				return FALSE;
+			} else if (certificate) {
+				g_ptr_array_add (pv->certificates, certificate);
+			}
+
+		/* No more in chain, and can't lookup */
+		} else {
+			certificate = NULL;
+		}
+
+		/* Stop the chain if nothing found */
+		if (certificate == NULL) {
+			pv->type = GCR_CERTIFICATE_CHAIN_INCOMPLETE;
+			break;
+		}
+
+		++length;
+
+		/* See if this certificate is an anchor */
+		if (lookups) {
+			ret = gcr_trust_is_certificate_anchor (certificate, pv->purpose,
+			                                       cancellable, &error);
+
+			if (!ret && error) {
+				g_propagate_error (rerror, error);
+				return FALSE;
+
+			/* Stop the chain at the first anchor */
+			} else if (ret) {
+				pv->type = GCR_CERTIFICATE_CHAIN_ANCHORED;
+				break;
+			}
+		}
+	}
+
+	/* Truncate to the appropriate length */
+	g_assert (length <= pv->certificates->len);
+	g_ptr_array_set_size (pv->certificates, length);
+	return TRUE;
+}
+
+static void
+thread_build_chain (GSimpleAsyncResult *result, GObject *object,
+                    GCancellable *cancellable)
+{
+	GcrCertificateChainPrivate *pv;
+	GError *error = NULL;
+
+	pv = g_object_get_qdata (G_OBJECT (result), Q_OPERATION_DATA);
+	g_assert (pv);
+
+	if (!perform_build_chain (pv, cancellable, &error)) {
+		g_simple_async_result_set_from_error (result, error);
+		g_clear_error (&error);
+	}
+}
+
+/* -----------------------------------------------------------------------------
+ * OBJECT
+ */
+
+static void
+gcr_certificate_chain_init (GcrCertificateChain *self)
+{
+	self->pv = new_chain_private ();
+}
+
+static void
+gcr_certificate_chain_dispose (GObject *obj)
+{
+	GcrCertificateChain *self = GCR_CERTIFICATE_CHAIN (obj);
+
+	g_ptr_array_set_size (self->pv->certificates, 0);
+	self->pv->type = GCR_CERTIFICATE_CHAIN_UNKNOWN;
+
+	G_OBJECT_CLASS (gcr_certificate_chain_parent_class)->dispose (obj);
+}
+
+static void
+gcr_certificate_chain_finalize (GObject *obj)
+{
+	GcrCertificateChain *self = GCR_CERTIFICATE_CHAIN (obj);
+
+	free_chain_private (self->pv);
+	self->pv = NULL;
+
+	G_OBJECT_CLASS (gcr_certificate_chain_parent_class)->finalize (obj);
+}
+
+static void
+gcr_certificate_chain_get_property (GObject *obj, guint prop_id, GValue *value,
+                                    GParamSpec *pspec)
+{
+	GcrCertificateChain *self = GCR_CERTIFICATE_CHAIN (obj);
+
+	switch (prop_id) {
+	case PROP_TYPE:
+		g_value_set_uint (value, gcr_certificate_chain_get_chain_type (self));
+		break;
+	case PROP_LENGTH:
+		g_value_set_uint (value, gcr_certificate_chain_get_length (self));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gcr_certificate_chain_class_init (GcrCertificateChainClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	gcr_certificate_chain_parent_class = g_type_class_peek_parent (klass);
+
+	gobject_class->dispose = gcr_certificate_chain_dispose;
+	gobject_class->finalize = gcr_certificate_chain_finalize;
+	gobject_class->get_property = gcr_certificate_chain_get_property;
+
+	/**
+	 * GcrCertificateChain:type:
+	 *
+	 * The certificate chain type. See #GcrCertificateChainType
+	 */
+	g_object_class_install_property (gobject_class, PROP_TYPE,
+	           g_param_spec_uint ("type", "Type", "Type of certificate chain",
+	                              0, _GCR_CERTIFICATE_CHAIN_TYPE_MAX,
+	                              GCR_CERTIFICATE_CHAIN_UNKNOWN, G_PARAM_READABLE));
+
+	/**
+	 * GcrCertificateChain:length:
+	 *
+	 * The length of the certificate chain.
+	 */
+	g_object_class_install_property (gobject_class, PROP_LENGTH,
+	           g_param_spec_uint ("length", "Length", "Length of certificate chain",
+	                              0, G_MAXUINT, 0, G_PARAM_READABLE));
+
+	Q_ORIGINAL_CERT = g_quark_from_static_string ("gcr-certificate-chain-original-cert");
+	Q_OPERATION_DATA = g_quark_from_static_string ("gcr-certificate-chain-operation-data");
+}
+
+/* -----------------------------------------------------------------------------
+ * PUBLIC
+ */
+
+/**
+ * gcr_certificate_chain_new:
+ *
+ * Create a new #GcrCertificateChain.
+ *
+ * Returns: A newly allocated #GcrCertificateChain
+ */
+GcrCertificateChain*
+gcr_certificate_chain_new (void)
+{
+	return g_object_new (GCR_TYPE_CERTIFICATE_CHAIN, NULL);
+}
+
+/**
+ * gcr_certificate_chain_add:
+ * @self: the #GcrCertificateChain
+ * @certificate: a #GcrCertificate to add to the chain
+ *
+ * Add @certificate to the chain. The order of certificates in the chain are
+ * important. The first certificate should be the endpoint certificate, and
+ * then come the signers (certificate authorities) each in turn. If a root
+ * certificate authority is present, it should come last.
+ *
+ * Adding a certificate an already built chain (see
+ * gcr_certificate_chain_build()) resets the type of the certificate chain
+ * to %GCR_CERTIFICATE_CHAIN_UNKNOWN
+ */
+void
+gcr_certificate_chain_add (GcrCertificateChain *self, GcrCertificate *certificate)
+{
+	g_return_if_fail (GCR_IS_CERTIFICATE_CHAIN (self));
+	g_return_if_fail (GCR_IS_CERTIFICATE (certificate));
+	g_ptr_array_add (self->pv->certificates, g_object_ref (certificate));
+	self->pv->type = GCR_CERTIFICATE_CHAIN_UNKNOWN;
+	g_object_notify (G_OBJECT (self), "type");
+	g_object_notify (G_OBJECT (self), "length");
+}
+
+/**
+ * gcr_certificate_chain_get_chain_type:
+ * @self: the #GcrCertificateChain
+ *
+ * Get the type of a certificate chain. If the certificate chain has not
+ * been built, then the type will be %GCR_CERTIFICATE_CHAIN_UNKNOWN.
+ *
+ * A type of %GCR_CERTIFICATE_CHAIN_ANCHORED does not mean that the
+ * certificate chain has been verified, but merely that an anchor has been
+ * found.
+ *
+ * Returns: the type of the certificate chain.
+ */
+GcrCertificateChainType
+gcr_certificate_chain_get_chain_type (GcrCertificateChain *self)
+{
+	g_return_val_if_fail (GCR_IS_CERTIFICATE_CHAIN (self), GCR_CERTIFICATE_CHAIN_UNKNOWN);
+	return self->pv->type;
+}
+
+/**
+ * gcr_certificate_chain_get_anchor:
+ * @self: the #GcrCertificateChain
+ *
+ * If the certificate chain has been built and is of type
+ * %GCR_CERTIFICATE_CHAIN_ANCHORED, then this will return the anchor
+ * certificate that was found. This is not necessarily a root certificate
+ * authority. If an intermediate certificate authority in the chain was
+ * found to be anchored, then that certificate will be returned.
+ *
+ * If an anchor is returned it does not mean that the certificate chain has
+ * been verified, but merely that an anchor has been found.
+ *
+ * Returns: the anchor certificate, or NULL if not anchored.
+ */
+GcrCertificate*
+gcr_certificate_chain_get_anchor (GcrCertificateChain *self)
+{
+	g_return_val_if_fail (GCR_IS_CERTIFICATE_CHAIN (self), NULL);
+	if (self->pv->type != GCR_CERTIFICATE_CHAIN_ANCHORED)
+		return NULL;
+	g_assert (self->pv->certificates->len > 0);
+	return GCR_CERTIFICATE (g_ptr_array_index (self->pv->certificates,
+	                                           self->pv->certificates->len - 1));
+}
+
+/**
+ * gcr_certificate_chain_get_endpoint:
+ * @self: the #GcrCertificateChain
+ *
+ * Get the endpoint certificate in the chain. This is always the first
+ * certificate in the chain. The endpoint certificate cannot be anchored.
+ *
+ * Returns: the endpoint certificate, or NULL if the chain is empty.
+ */
+GcrCertificate*
+gcr_certificate_chain_get_endpoint (GcrCertificateChain *self)
+{
+	g_return_val_if_fail (GCR_IS_CERTIFICATE_CHAIN (self), NULL);
+	if (!self->pv->certificates->len)
+		return NULL;
+	return GCR_CERTIFICATE (g_ptr_array_index (self->pv->certificates, 0));
+}
+
+/**
+ * gcr_certificate_chain_get_length:
+ * @self: the #GcrCertificateChain
+ *
+ * Get the length of the certificate chain.
+ *
+ * Returns: the length of the certificate chain
+ */
+guint
+gcr_certificate_chain_get_length (GcrCertificateChain *self)
+{
+	g_return_val_if_fail (GCR_IS_CERTIFICATE_CHAIN (self), 0);
+	return self->pv->certificates->len;
+}
+
+/**
+ * gcr_certificate_chain_get_certificate:
+ * @self: the #GcrCertificateChain
+ * @index: index of the certificate to get
+ *
+ * Get a certificate in the chain. It is an error to call this function
+ * with an invalid index.
+ *
+ * Returns: the certificate
+ */
+GcrCertificate*
+gcr_certificate_chain_get_certificate (GcrCertificateChain *self, guint index)
+{
+	g_return_val_if_fail (GCR_IS_CERTIFICATE_CHAIN (self), NULL);
+	g_return_val_if_fail (index < self->pv->certificates->len, NULL);
+	return GCR_CERTIFICATE (g_ptr_array_index (self->pv->certificates, index));
+}
+
+/**
+ * gcr_certificate_chain_build:
+ * @self: the #GcrCertificateChain
+ * @purpose: the purpose the certificate chain will be used for
+ * @peer: the peer the certificate chain will be used with, or NULL
+ * @flags: chain completion flags
+ * @cancellable: a #GCancellable or %NULL
+ * @error: a #GError or %NULL
+ *
+ * Complete a certificate chain. Once a certificate chain has been built
+ * its type can be examined.
+ *
+ * This operation will lookup missing certificates in PKCS\#11
+ * modules and also that each certificate in the chain is the signer of the
+ * previous one. If a trust anchor, pinned certificate, or self-signed certificate
+ * is found, then the chain is considered built. Any extra certificates are
+ * removed from the chain.
+ *
+ * It's important to understand that building of a certificate chain does not
+ * constitute verifying that chain. This is merely the first step towards
+ * trust verification.
+ *
+ * The @purpose is a string like #GCR_PURPOSE_CLIENT_AUTH and is the purpose
+ * for which the certificate chain will be used. Trust anchors are looked up
+ * for this purpose. This argument is required.
+ *
+ * The @peer is usually the host name of the peer whith which this certificate
+ * chain is being used. It is used to look up pinned certificates that have
+ * been stored for this peer. If %NULL then no pinned certificates will
+ * be considered.
+ *
+ * If the #GCR_CERTIFICATE_CHAIN_FLAG_NO_LOOKUPS flag is specified then no
+ * lookups for anchors or pinned certificates are done, and the resulting chain
+ * will be neither anchored or pinned. Additionally no missing certificate
+ * authorities are looked up in PKCS\#11
+ *
+ * This call will block, see gcr_certificate_chain_build_async() for the
+ * asynchronous version.
+ *
+ * Returns: whether the operation completed successfully
+ */
+gboolean
+gcr_certificate_chain_build (GcrCertificateChain *self, const gchar *purpose,
+                             const gchar *peer, guint flags,
+                             GCancellable *cancellable, GError **error)
+{
+	GcrCertificateChainPrivate *pv;
+	gboolean ret;
+
+	g_return_val_if_fail (GCR_IS_CERTIFICATE_CHAIN (self), FALSE);
+	g_return_val_if_fail (purpose, FALSE);
+
+	pv = prep_chain_private (self->pv, purpose, peer, flags);
+
+	ret = perform_build_chain (pv, cancellable, error);
+
+	if (ret) {
+		free_chain_private (self->pv);
+		self->pv = cleanup_chain_private (pv);
+		g_object_notify (G_OBJECT (self), "type");
+		g_object_notify (G_OBJECT (self), "length");
+	}
+
+	return ret;
+}
+
+/**
+ * gcr_certificate_chain_build_async:
+ * @self: the #GcrCertificateChain
+ * @purpose: the purpose the certificate chain will be used for
+ * @peer: the peer the certificate chain will be used with, or NULL
+ * @flags: chain completion flags
+ * @cancellable: a #GCancellable or %NULL
+ * @callback: this will be called when the operation completes.
+ * @user_data: data to pass to the callback
+ *
+ * Complete a certificate chain. Once a certificate chain has been built
+ * its type can be examined.
+ *
+ * This will lookup missing certificates in PKCS\#11
+ * modules and also that each certificate in the chain is the signer of the
+ * previous one. If a trust anchor, pinned certificate, or self-signed certificate
+ * is found, then the chain is considered built. Any extra certificates are
+ * removed from the chain.
+ *
+ * It's important to understand that building of a certificate chain does not
+ * constitute verifying that chain. This is merely the first step towards
+ * trust verification.
+ *
+ * The @purpose is a string like #GCR_PURPOSE_CLIENT_AUTH and is the purpose
+ * for which the certificate chain will be used. Trust anchors are looked up
+ * for this purpose. This argument is required.
+ *
+ * The @peer is usually the host name of the peer whith which this certificate
+ * chain is being used. It is used to look up pinned certificates that have
+ * been stored for this peer. If %NULL then no pinned certificates will
+ * be considered.
+ *
+ * If the #GCR_CERTIFICATE_CHAIN_FLAG_NO_LOOKUPS flag is specified then no
+ * lookups for anchors or pinned certificates are done, and the resulting chain
+ * will be neither anchored or pinned. Additionally no missing certificate
+ * authorities are looked up in PKCS\#11
+ *
+ * When the operation is finished, @callback will be called. You can then call
+ * gcr_certificate_chain_build_finish() to get the result of the operation.
+ */
+void
+gcr_certificate_chain_build_async (GcrCertificateChain *self, const gchar *purpose,
+                                   const gchar *peer, guint flags,
+                                   GCancellable *cancellable, GAsyncReadyCallback callback,
+                                   gpointer user_data)
+{
+	GcrCertificateChainPrivate *pv;
+	GSimpleAsyncResult *result;
+
+	g_return_if_fail (GCR_IS_CERTIFICATE_CHAIN (self));
+
+	g_return_if_fail (GCR_IS_CERTIFICATE_CHAIN (self));
+	g_return_if_fail (purpose);
+
+	pv = prep_chain_private_thread_safe (self->pv, purpose, peer, flags);
+
+	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+	                                    gcr_certificate_chain_build_async);
+	g_object_set_qdata_full (G_OBJECT (result), Q_OPERATION_DATA, pv, free_chain_private);
+
+	g_simple_async_result_run_in_thread (result, thread_build_chain,
+	                                     G_PRIORITY_DEFAULT, cancellable);
+
+}
+
+/**
+ * gcr_pkcs11_certificate_lookup_issuer_finish:
+ * @result: the #GAsyncResult passed to the callback
+ * @error: a #GError, or NULL
+ *
+ * Finishes an asynchronous operation started by
+ * gcr_certificate_chain_build_async().
+ *
+ * Returns: whether the operation succeeded
+ */
+gboolean
+gcr_certificate_chain_build_finish (GcrCertificateChain *self, GAsyncResult *result,
+                                    GError **error)
+{
+	GcrCertificateChainPrivate *pv;
+
+	g_return_val_if_fail (GCR_IS_CERTIFICATE_CHAIN (self), FALSE);
+	g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
+	                      gcr_certificate_chain_build_async), FALSE);
+
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return FALSE;
+
+	pv = g_object_steal_qdata (G_OBJECT (result), Q_OPERATION_DATA);
+	g_return_val_if_fail (pv, FALSE);
+
+	free_chain_private (self->pv);
+	self->pv = cleanup_chain_private (pv);
+
+	g_object_notify (G_OBJECT (self), "type");
+	g_object_notify (G_OBJECT (self), "length");
+	return TRUE;
+}
diff --git a/gcr/gcr-certificate-chain.h b/gcr/gcr-certificate-chain.h
new file mode 100644
index 0000000..a1ed212
--- /dev/null
+++ b/gcr/gcr-certificate-chain.h
@@ -0,0 +1,113 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program 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 program; 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>
+ */
+
+#if !defined (__GCR_H_INSIDE__) && !defined (GCR_COMPILATION)
+#error "Only <gcr/gcr.h> can be included directly."
+#endif
+
+#ifndef __GCR_CERTIFICATE_CHAIN_H__
+#define __GCR_CERTIFICATE_CHAIN_H__
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "gcr-certificate.h"
+#include "gcr-types.h"
+
+G_BEGIN_DECLS
+
+typedef enum _GcrCertificateChainType {
+	GCR_CERTIFICATE_CHAIN_UNKNOWN,
+	GCR_CERTIFICATE_CHAIN_INCOMPLETE,
+	GCR_CERTIFICATE_CHAIN_SELFSIGNED,
+	GCR_CERTIFICATE_CHAIN_ANCHORED,
+	GCR_CERTIFICATE_CHAIN_PINNED,
+
+	/*< private >*/
+	_GCR_CERTIFICATE_CHAIN_TYPE_MAX
+} GcrCertificateChainType;
+
+typedef enum _GcrCertificateChainFlags {
+	GCR_CERTIFICATE_CHAIN_FLAG_NO_LOOKUPS = 1 << 0,
+} GcrCertificateChainFlags;
+
+#define GCR_TYPE_CERTIFICATE_CHAIN               (gcr_certificate_chain_get_type ())
+#define GCR_CERTIFICATE_CHAIN(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_CERTIFICATE_CHAIN, GcrCertificateChain))
+#define GCR_CERTIFICATE_CHAIN_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_CERTIFICATE_CHAIN, GcrCertificateChainClass))
+#define GCR_IS_CERTIFICATE_CHAIN(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCR_TYPE_CERTIFICATE_CHAIN))
+#define GCR_IS_CERTIFICATE_CHAIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCR_TYPE_CERTIFICATE_CHAIN))
+#define GCR_CERTIFICATE_CHAIN_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCR_TYPE_CERTIFICATE_CHAIN, GcrCertificateChainClass))
+
+typedef struct _GcrCertificateChain GcrCertificateChain;
+typedef struct _GcrCertificateChainClass GcrCertificateChainClass;
+typedef struct _GcrCertificateChainPrivate GcrCertificateChainPrivate;
+
+struct _GcrCertificateChain {
+	GObject parent;
+	GcrCertificateChainPrivate *pv;
+};
+
+struct _GcrCertificateChainClass {
+	GObjectClass parent_class;
+};
+
+GType                     gcr_certificate_chain_get_type           (void);
+
+GcrCertificateChain*      gcr_certificate_chain_new                (void);
+
+void                      gcr_certificate_chain_add                (GcrCertificateChain *self,
+                                                                    GcrCertificate *certificate);
+
+GcrCertificateChainType   gcr_certificate_chain_get_chain_type     (GcrCertificateChain *self);
+
+GcrCertificate*           gcr_certificate_chain_get_anchor         (GcrCertificateChain *self);
+
+GcrCertificate*           gcr_certificate_chain_get_endpoint       (GcrCertificateChain *self);
+
+guint                     gcr_certificate_chain_get_length         (GcrCertificateChain *self);
+
+GcrCertificate*           gcr_certificate_chain_get_certificate    (GcrCertificateChain *self,
+                                                                    guint index);
+
+gboolean                  gcr_certificate_chain_build              (GcrCertificateChain *self,
+                                                                    const gchar *purpose,
+                                                                    const gchar *peer,
+                                                                    guint flags,
+                                                                    GCancellable *cancellable,
+                                                                    GError **error);
+
+void                      gcr_certificate_chain_build_async        (GcrCertificateChain *self,
+                                                                    const gchar *purpose,
+                                                                    const gchar *peer,
+                                                                    guint flags,
+                                                                    GCancellable *cancellable,
+                                                                    GAsyncReadyCallback callback,
+                                                                    gpointer user_data);
+
+gboolean                  gcr_certificate_chain_build_finish       (GcrCertificateChain *self,
+                                                                    GAsyncResult *result,
+                                                                    GError **error);
+
+G_END_DECLS
+
+#endif /* __GCR_CERTIFICATE_CHAIN_H__ */
diff --git a/gcr/gcr.h b/gcr/gcr.h
index 84f22c9..30e3649 100644
--- a/gcr/gcr.h
+++ b/gcr/gcr.h
@@ -35,6 +35,7 @@
 #include "gcr-types.h"
 
 #include "gcr-certificate.h"
+#include "gcr-certificate-chain.h"
 #include "gcr-certificate-renderer.h"
 #include "gcr-certificate-widget.h"
 #include "gcr-key-renderer.h"
diff --git a/gcr/tests/Makefile.am b/gcr/tests/Makefile.am
index ba06b60..3454af5 100644
--- a/gcr/tests/Makefile.am
+++ b/gcr/tests/Makefile.am
@@ -2,6 +2,7 @@
 # Test files should be listed in order they need to run
 TESTING_FILES = \
 	test-certificate.c \
+	test-certificate-chain.c \
 	test-simple-certificate.c \
 	test-pkcs11-certificate.c \
 	test-trust.c \
diff --git a/gcr/tests/test-certificate-chain.c b/gcr/tests/test-certificate-chain.c
new file mode 100644
index 0000000..7aeaca6
--- /dev/null
+++ b/gcr/tests/test-certificate-chain.c
@@ -0,0 +1,580 @@
+
+#include "config.h"
+#include "test-suite.h"
+
+#include "egg/egg-asn1x.h"
+#include "egg/egg-asn1-defs.h"
+
+#include "gcr/gcr.h"
+
+#include "gck/gck-mock.h"
+#include "gck/gck-test.h"
+
+#include <glib.h>
+
+#include <string.h>
+
+/* ---------------------------------------------------------------------------
+ * A Mock certificate that checks that it's always called on the
+ * same thread. A GcrCertificate implemented on top of a non-thread-safe
+ * crypto library would require this behavior.
+ */
+
+GType               mock_certificate_get_type               (void);
+
+#define MOCK_CERTIFICATE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST ((obj), mock_certificate_get_type (), MockCertificate))
+
+typedef struct _MockCertificate {
+	GObject parent;
+	GThread *created_on;
+	gpointer data;
+	gsize n_data;
+} MockCertificate;
+
+typedef struct _MockCertificateClass {
+	GObjectClass parent_class;
+} MockCertificateClass;
+
+static void mock_certificate_iface (GcrCertificateIface *iface);
+G_DEFINE_TYPE_WITH_CODE (MockCertificate, mock_certificate, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GCR_TYPE_CERTIFICATE, mock_certificate_iface));
+
+static void
+mock_certificate_init (MockCertificate *self)
+{
+	self->created_on = g_thread_self ();
+}
+
+static void
+mock_certificate_finalize (GObject *obj)
+{
+	MockCertificate *self = MOCK_CERTIFICATE (obj);
+	g_assert (self->created_on == g_thread_self ());
+	g_free (self->data);
+	G_OBJECT_CLASS (mock_certificate_parent_class)->finalize (obj);
+}
+
+static void
+mock_certificate_class_init (MockCertificateClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	gobject_class->finalize = mock_certificate_finalize;
+}
+
+static gconstpointer
+mock_certificate_real_get_der_data (GcrCertificate *base, gsize *n_data)
+{
+	MockCertificate *self = MOCK_CERTIFICATE (base);
+	g_assert (self->created_on == g_thread_self ());
+	*n_data = self->n_data;
+	return self->data;
+}
+
+static void
+mock_certificate_iface (GcrCertificateIface *iface)
+{
+	iface->get_der_data = (gpointer)mock_certificate_real_get_der_data;
+}
+
+static GcrCertificate*
+mock_certificate_new (gconstpointer data, gsize n_data)
+{
+	MockCertificate *self = g_object_new (mock_certificate_get_type (), NULL);
+	self->data = g_memdup (data, n_data);
+	self->n_data = n_data;
+	g_assert (self->created_on == g_thread_self ());
+	return GCR_CERTIFICATE (self);
+}
+
+/* ----------------------------------------------------------------------------
+ * TESTS
+ */
+
+static GcrCertificate *cert_self = NULL;
+static GcrCertificate *cert_ca = NULL;
+static GcrCertificate *cert_signed = NULL;
+static CK_FUNCTION_LIST funcs;
+
+TESTING_SETUP (certificate_chain)
+{
+	GList *modules = NULL;
+	CK_FUNCTION_LIST_PTR f;
+	guchar *contents;
+	gsize n_contents;
+	CK_RV rv;
+	GckModule *module;
+
+	rv = gck_mock_C_GetFunctionList (&f);
+	gck_assert_cmprv (rv, ==, CKR_OK);
+	memcpy (&funcs, f, sizeof (funcs));
+
+	/* Open a session */
+	rv = (funcs.C_Initialize) (NULL);
+	gck_assert_cmprv (rv, ==, CKR_OK);
+
+	g_assert (!modules);
+	module = gck_module_new (&funcs, 0);
+	modules = g_list_prepend (modules, module);
+	gcr_pkcs11_set_modules (modules);
+	gcr_pkcs11_set_trust_store_uri (GCK_MOCK_SLOT_ONE_URI);
+	gck_list_unref_free (modules);
+
+	/* A self-signed certificate */
+	contents = testing_data_read ("der-certificate.crt", &n_contents);
+	cert_self = gcr_simple_certificate_new (contents, n_contents);
+	g_free (contents);
+
+	/* A signed certificate */
+	contents = testing_data_read ("dhansak-collabora.cer", &n_contents);
+	cert_signed = mock_certificate_new (contents, n_contents);
+	g_free (contents);
+
+	/* The signer for the above certificate */
+	contents = testing_data_read ("collabora-ca.cer", &n_contents);
+	cert_ca = mock_certificate_new (contents, n_contents);
+	g_free (contents);
+}
+
+static void
+add_certificate_to_module (GcrCertificate *certificate)
+{
+	GckAttributes *attrs;
+	gconstpointer data;
+	gsize n_data, n_subject;
+	gpointer subject;
+
+	data = gcr_certificate_get_der_data (certificate, &n_data);
+	g_assert (data);
+
+	subject = gcr_certificate_get_subject_raw (certificate, &n_subject);
+	g_assert (subject);
+
+	/* Add a certificate to the module */
+	attrs = gck_attributes_new ();
+	gck_attributes_add_data (attrs, CKA_VALUE, data, n_data);
+	gck_attributes_add_ulong (attrs, CKA_CLASS, CKO_CERTIFICATE);
+	gck_attributes_add_ulong (attrs, CKA_CERTIFICATE_TYPE, CKC_X_509);
+	gck_attributes_add_data (attrs, CKA_SUBJECT, subject, n_subject);
+	gck_mock_module_take_object (attrs);
+
+	g_free (subject);
+}
+
+static void
+add_anchor_to_module (GcrCertificate *certificate, const gchar *purpose)
+{
+	GckAttributes *attrs;
+	gconstpointer data;
+	gsize n_data;
+
+	data = gcr_certificate_get_der_data (certificate, &n_data);
+	g_assert (data);
+
+	/* And add a certificate exception for the signed certificate */
+	attrs = gck_attributes_new ();
+	gck_attributes_add_data (attrs, CKA_G_CERTIFICATE_VALUE, data, n_data);
+	gck_attributes_add_ulong (attrs, CKA_CLASS, CKO_G_TRUST_ASSERTION);
+	gck_attributes_add_ulong (attrs, CKA_G_ASSERTION_TYPE, CKT_G_CERTIFICATE_TRUST_ANCHOR);
+	gck_attributes_add_string (attrs, CKA_G_PURPOSE, purpose);
+	gck_mock_module_take_object (attrs);
+}
+
+static void
+add_pinned_to_module (GcrCertificate *certificate, const gchar *purpose, const gchar *host)
+{
+	GckAttributes *attrs;
+	gconstpointer data;
+	gsize n_data;
+
+	data = gcr_certificate_get_der_data (certificate, &n_data);
+	g_assert (data);
+
+	/* And add a certificate exception for the signed certificate */
+	attrs = gck_attributes_new ();
+	gck_attributes_add_data (attrs, CKA_G_CERTIFICATE_VALUE, data, n_data);
+	gck_attributes_add_ulong (attrs, CKA_CLASS, CKO_G_TRUST_ASSERTION);
+	gck_attributes_add_ulong (attrs, CKA_G_ASSERTION_TYPE, CKT_G_CERTIFICATE_TRUST_EXCEPTION);
+	gck_attributes_add_string (attrs, CKA_G_PURPOSE, purpose);
+	gck_attributes_add_string (attrs, CKA_G_PEER, host);
+	gck_mock_module_take_object (attrs);
+}
+
+TESTING_TEARDOWN (certificate_chain)
+{
+	CK_RV rv;
+
+	g_object_unref (cert_self);
+	cert_self = NULL;
+
+	g_object_unref (cert_signed);
+	cert_signed = NULL;
+
+	g_object_unref (cert_ca);
+	cert_signed = NULL;
+
+	rv = (funcs.C_Finalize) (NULL);
+	gck_assert_cmprv (rv, ==, CKR_OK);
+}
+
+TESTING_TEST (certificate_chain_new)
+{
+	GcrCertificateChain *chain;
+
+	chain = gcr_certificate_chain_new ();
+
+	g_assert_cmpuint (gcr_certificate_chain_get_chain_type (chain), ==,
+	                  GCR_CERTIFICATE_CHAIN_UNKNOWN);
+	g_assert_cmpuint (gcr_certificate_chain_get_length (chain), ==, 0);
+
+	g_assert (gcr_certificate_chain_get_endpoint (chain) == NULL);
+
+	g_object_unref (chain);
+}
+
+TESTING_TEST (certificate_chain_new_with_cert)
+{
+	GcrCertificateChain *chain;
+	GcrCertificate *check;
+	guint type, length;
+
+	chain = gcr_certificate_chain_new ();
+	gcr_certificate_chain_add (chain, cert_signed);
+	gcr_certificate_chain_add (chain, cert_ca);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_chain_type (chain), ==,
+	                  GCR_CERTIFICATE_CHAIN_UNKNOWN);
+	g_assert_cmpuint (gcr_certificate_chain_get_length (chain), ==, 2);
+
+	type = G_MAXUINT;
+	length = 0;
+	g_object_get (chain, "type", &type, "length", &length, NULL);
+	g_assert_cmpuint (type, ==, GCR_CERTIFICATE_CHAIN_UNKNOWN);
+	g_assert_cmpuint (length, ==, 2);
+
+	check = gcr_certificate_chain_get_certificate (chain, 1);
+	g_assert (check == cert_ca);
+
+	/* Not yet completed */
+	check = gcr_certificate_chain_get_anchor (chain);
+	g_assert (check == NULL);
+
+	check = gcr_certificate_chain_get_endpoint (chain);
+	g_assert (check == cert_signed);
+
+	g_object_unref (chain);
+}
+
+TESTING_TEST (certificate_chain_selfsigned)
+{
+	GcrCertificateChain *chain;
+	GError *error = NULL;
+
+	chain = gcr_certificate_chain_new ();
+
+	/* Add a self-signed certificate */
+	gcr_certificate_chain_add (chain, cert_self);
+
+	if (!gcr_certificate_chain_build (chain, GCR_PURPOSE_CLIENT_AUTH,
+	                                  NULL, 0, NULL, &error))
+		g_assert_not_reached ();
+	g_assert_no_error (error);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_chain_type (chain), ==,
+	                  GCR_CERTIFICATE_CHAIN_SELFSIGNED);
+
+	g_object_unref (chain);
+}
+
+TESTING_TEST (certificate_chain_incomplete)
+{
+	GcrCertificateChain *chain;
+	GError *error = NULL;
+
+	chain = gcr_certificate_chain_new ();
+
+	/* Add a signed certificate */
+	gcr_certificate_chain_add (chain, cert_signed);
+
+	if (!gcr_certificate_chain_build (chain, GCR_PURPOSE_CLIENT_AUTH,
+	                                  NULL, 0, NULL, &error))
+		g_assert_not_reached ();
+	g_assert_no_error (error);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_chain_type (chain), ==,
+	                  GCR_CERTIFICATE_CHAIN_INCOMPLETE);
+
+	g_object_unref (chain);
+}
+
+TESTING_TEST (certificate_chain_empty)
+{
+	GcrCertificateChain *chain;
+	GError *error = NULL;
+
+	chain = gcr_certificate_chain_new ();
+
+	/* Add no certificate */
+
+	if (!gcr_certificate_chain_build (chain, GCR_PURPOSE_CLIENT_AUTH,
+	                                  NULL, 0, NULL, &error))
+		g_assert_not_reached ();
+	g_assert_no_error (error);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_chain_type (chain), ==,
+	                  GCR_CERTIFICATE_CHAIN_UNKNOWN);
+
+	g_object_unref (chain);
+}
+
+TESTING_TEST (certificate_chain_trim_extras)
+{
+	GcrCertificateChain *chain;
+	GError *error = NULL;
+
+	chain = gcr_certificate_chain_new ();
+
+	/* Add two unrelated certificates */
+	gcr_certificate_chain_add (chain, cert_self);
+	gcr_certificate_chain_add (chain, cert_signed);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_length (chain), ==, 2);
+
+	if (!gcr_certificate_chain_build (chain, GCR_PURPOSE_CLIENT_AUTH,
+	                                  NULL, 0, NULL, &error))
+		g_assert_not_reached ();
+	g_assert_no_error (error);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_chain_type (chain), ==,
+	                  GCR_CERTIFICATE_CHAIN_SELFSIGNED);
+	g_assert_cmpuint (gcr_certificate_chain_get_length (chain), ==, 1);
+
+	g_object_unref (chain);
+}
+
+static void
+fetch_async_result (GObject *source, GAsyncResult *result, gpointer user_data)
+{
+	*((GAsyncResult**)user_data) = result;
+	g_object_ref (result);
+	testing_wait_stop ();
+}
+
+TESTING_TEST (certificate_chain_complete_async)
+{
+	GcrCertificateChain *chain;
+	GError *error = NULL;
+	GAsyncResult *result;
+
+	chain = gcr_certificate_chain_new ();
+
+	/* Add a whole bunch of certificates */
+	gcr_certificate_chain_add (chain, cert_signed);
+	gcr_certificate_chain_add (chain, cert_ca);
+	gcr_certificate_chain_add (chain, cert_self);
+
+	gcr_certificate_chain_build_async (chain, GCR_PURPOSE_CLIENT_AUTH,
+	                                   NULL, 0, NULL, fetch_async_result, &result);
+	testing_wait_until (500);
+	if (!gcr_certificate_chain_build_finish (chain, result, &error))
+		g_assert_not_reached ();
+	g_assert_no_error (error);
+	g_object_unref (result);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_chain_type (chain), ==,
+	                  GCR_CERTIFICATE_CHAIN_SELFSIGNED);
+	g_assert_cmpuint (gcr_certificate_chain_get_length (chain), ==, 2);
+
+	g_object_unref (chain);
+}
+
+TESTING_TEST (certificate_chain_with_anchor)
+{
+	GcrCertificateChain *chain;
+	GError *error = NULL;
+
+	chain = gcr_certificate_chain_new ();
+
+	/* Two certificates in chain with ca trust anchor */
+	gcr_certificate_chain_add (chain, cert_signed);
+	gcr_certificate_chain_add (chain, cert_ca);
+	add_anchor_to_module (cert_ca, GCR_PURPOSE_CLIENT_AUTH);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_length (chain), ==, 2);
+
+	if (!gcr_certificate_chain_build (chain, GCR_PURPOSE_CLIENT_AUTH,
+	                                  NULL, 0, NULL, &error))
+		g_assert_not_reached ();
+	g_assert_no_error (error);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_chain_type (chain), ==,
+	                  GCR_CERTIFICATE_CHAIN_ANCHORED);
+	g_assert_cmpuint (gcr_certificate_chain_get_length (chain), ==, 2);
+	g_assert (gcr_certificate_chain_get_anchor (chain) == cert_ca);
+
+	g_object_unref (chain);
+}
+
+TESTING_TEST (certificate_chain_with_anchor_and_lookup_ca)
+{
+	GcrCertificateChain *chain;
+	GError *error = NULL;
+
+	chain = gcr_certificate_chain_new ();
+
+	/* One signed certificate, with CA in pkcs11, and trust anchor */
+	gcr_certificate_chain_add (chain, cert_signed);
+	add_certificate_to_module (cert_ca);
+	add_anchor_to_module (cert_ca, GCR_PURPOSE_CLIENT_AUTH);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_length (chain), ==, 1);
+
+	if (!gcr_certificate_chain_build (chain, GCR_PURPOSE_CLIENT_AUTH,
+	                                  NULL, 0, NULL, &error))
+		g_assert_not_reached ();
+	g_assert_no_error (error);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_chain_type (chain), ==,
+	                  GCR_CERTIFICATE_CHAIN_ANCHORED);
+	g_assert_cmpuint (gcr_certificate_chain_get_length (chain), ==, 2);
+	g_assert (gcr_certificate_chain_get_anchor (chain) != NULL);
+
+	g_object_unref (chain);
+}
+
+TESTING_TEST (certificate_chain_with_pinned)
+{
+	GcrCertificateChain *chain;
+	GError *error = NULL;
+
+	chain = gcr_certificate_chain_new ();
+
+	/* One certificate, and add CA to pkcs11 */
+	gcr_certificate_chain_add (chain, cert_signed);
+	gcr_certificate_chain_add (chain, cert_ca);
+	add_pinned_to_module (cert_signed, GCR_PURPOSE_CLIENT_AUTH, "pinned.example.com");
+
+	g_assert_cmpuint (gcr_certificate_chain_get_length (chain), ==, 2);
+
+	/* But we don't allow the lookup to happen */
+	if (!gcr_certificate_chain_build (chain, GCR_PURPOSE_CLIENT_AUTH,
+	                                  "pinned.example.com", 0, NULL, &error))
+		g_assert_not_reached ();
+	g_assert_no_error (error);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_chain_type (chain), ==,
+	                  GCR_CERTIFICATE_CHAIN_PINNED);
+	g_assert_cmpuint (gcr_certificate_chain_get_length (chain), ==, 1);
+	g_assert (gcr_certificate_chain_get_anchor (chain) == NULL);
+
+	g_object_unref (chain);
+}
+
+TESTING_TEST (certificate_chain_without_lookups)
+{
+	GcrCertificateChain *chain;
+	GError *error = NULL;
+
+	chain = gcr_certificate_chain_new ();
+
+	/* One certificate, and add CA to pkcs11 */
+	gcr_certificate_chain_add (chain, cert_signed);
+	add_certificate_to_module (cert_ca);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_length (chain), ==, 1);
+
+	/* But we don't allow the lookup to happen */
+	if (!gcr_certificate_chain_build (chain, GCR_PURPOSE_CLIENT_AUTH,
+	                                  NULL, GCR_CERTIFICATE_CHAIN_FLAG_NO_LOOKUPS,
+	                                  NULL, &error))
+		g_assert_not_reached ();
+	g_assert_no_error (error);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_chain_type (chain), ==,
+	                  GCR_CERTIFICATE_CHAIN_INCOMPLETE);
+	g_assert_cmpuint (gcr_certificate_chain_get_length (chain), ==, 1);
+	g_assert (gcr_certificate_chain_get_anchor (chain) == NULL);
+
+	g_object_unref (chain);
+}
+
+TESTING_TEST (certificate_chain_with_lookup_error)
+{
+	GcrCertificateChain *chain;
+	GError *error = NULL;
+
+	/* Make the lookup fail */
+	funcs.C_GetAttributeValue = gck_mock_fail_C_GetAttributeValue;
+
+	chain = gcr_certificate_chain_new ();
+
+	/* Two certificates in chain with ca trust anchor */
+	gcr_certificate_chain_add (chain, cert_signed);
+	add_certificate_to_module (cert_ca);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_length (chain), ==, 1);
+
+	if (gcr_certificate_chain_build (chain, GCR_PURPOSE_CLIENT_AUTH,
+	                                 NULL, 0, NULL, &error))
+		g_assert_not_reached ();
+	g_assert_error (error, GCK_ERROR, CKR_FUNCTION_FAILED);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_chain_type (chain), ==,
+	                  GCR_CERTIFICATE_CHAIN_UNKNOWN);
+
+	g_object_unref (chain);
+}
+
+TESTING_TEST (certificate_chain_with_anchor_error)
+{
+	GcrCertificateChain *chain;
+	GError *error = NULL;
+
+	/* Make the lookup fail */
+	funcs.C_GetAttributeValue = gck_mock_fail_C_GetAttributeValue;
+
+	chain = gcr_certificate_chain_new ();
+
+	/* Two certificates in chain with ca trust anchor */
+	gcr_certificate_chain_add (chain, cert_signed);
+	add_certificate_to_module (cert_ca);
+
+	if (gcr_certificate_chain_build (chain, GCR_PURPOSE_CLIENT_AUTH,
+	                                 NULL, 0, NULL, &error))
+		g_assert_not_reached ();
+	g_assert_error (error, GCK_ERROR, CKR_FUNCTION_FAILED);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_chain_type (chain), ==,
+	                  GCR_CERTIFICATE_CHAIN_UNKNOWN);
+
+	g_object_unref (chain);
+}
+
+TESTING_TEST (certificate_chain_with_anchor_error_async)
+{
+	GcrCertificateChain *chain;
+	GError *error = NULL;
+	GAsyncResult *result;
+
+	/* Make the lookup fail */
+	funcs.C_GetAttributeValue = gck_mock_fail_C_GetAttributeValue;
+
+	chain = gcr_certificate_chain_new ();
+
+	/* Two certificates in chain with ca trust anchor */
+	gcr_certificate_chain_add (chain, cert_signed);
+	add_certificate_to_module (cert_ca);
+
+	gcr_certificate_chain_build_async (chain, GCR_PURPOSE_CLIENT_AUTH,
+	                                   NULL, 0, NULL, fetch_async_result, &result);
+	testing_wait_until (500);
+	if (gcr_certificate_chain_build_finish (chain, result, &error))
+		g_assert_not_reached ();
+	g_assert_error (error, GCK_ERROR, CKR_FUNCTION_FAILED);
+	g_object_unref (result);
+
+	g_assert_cmpuint (gcr_certificate_chain_get_chain_type (chain), ==,
+	                  GCR_CERTIFICATE_CHAIN_UNKNOWN);
+
+	g_object_unref (chain);
+}
diff --git a/gcr/tests/test-data/collabora-ca.cer b/gcr/tests/test-data/collabora-ca.cer
new file mode 100644
index 0000000..2842c69
Binary files /dev/null and b/gcr/tests/test-data/collabora-ca.cer differ
diff --git a/gcr/tests/test-data/dhansak-collabora.cer b/gcr/tests/test-data/dhansak-collabora.cer
new file mode 100644
index 0000000..c411e7d
Binary files /dev/null and b/gcr/tests/test-data/dhansak-collabora.cer differ



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