diff --git a/.gitignore b/.gitignore
index cae7040..af16c8c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -106,14 +106,19 @@ run-tests
 
 /gcr/tests/frob-certificate
 /gcr/tests/frob-key
+/gcr/tests/frob-gnupg-selector
 /gcr/tests/frob-selector
 /gcr/tests/frob-unlock-options
 /gcr/tests/test-certificate
 /gcr/tests/test-certificate-chain
+/gcr/tests/test-colons
+/gcr/tests/test-gnupg-collection
+/gcr/tests/test-gnupg-key
 /gcr/tests/test-parser
 /gcr/tests/test-pkcs11-certificate
 /gcr/tests/test-simple-certificate
 /gcr/tests/test-trust
+/gcr/tests/test-util
 
 /pkcs11/gkm/tests/test-attributes
 /pkcs11/gkm/tests/test-credential
diff --git a/configure.in b/configure.in
index dc6fe19..7e145e2 100644
--- a/configure.in
+++ b/configure.in
@@ -359,6 +359,12 @@ fi
 AM_CONDITIONAL(WITH_GPG, test "$enable_gpg_agent" != "no")
 
 # --------------------------------------------------------------------
+# GPG support
+
+AC_PATH_PROGS([GNUPG], [gpg gpg2], ["gpg"])
+AC_DEFINE_UNQUOTED([GPG_EXECUTABLE], ["$GNUPG"], [Path to gpg executable.])
+
+# --------------------------------------------------------------------
 # Trusted Root Certificates Directory
 #
 
diff --git a/docs/reference/gcr/gcr-sections.txt b/docs/reference/gcr/gcr-sections.txt
index 114bfb6..2f652b9 100644
--- a/docs/reference/gcr/gcr-sections.txt
+++ b/docs/reference/gcr/gcr-sections.txt
@@ -429,3 +429,34 @@ GCR_UNLOCK_OPTIONS_WIDGET_CLASS
 GCR_UNLOCK_OPTIONS_WIDGET_GET_CLASS
 GcrUnlockOptionsWidgetPrivate
 </SECTION>
+
+<SECTION>
+<FILE>gcr-private</FILE>
+<SUBSECTION Private>
+GCR_COLONS_SCHEMA_PUB
+GCR_COLONS_SCHEMA_UID
+GCR_GNUPG_COLLECTION
+GCR_GNUPG_COLLECTION_CLASS
+GCR_GNUPG_COLLECTION_GET_CLASS
+GCR_GNUPG_KEY
+GCR_GNUPG_KEY_CLASS
+GCR_GNUPG_KEY_COLUMNS
+GCR_GNUPG_KEY_GET_CLASS
+GCR_IS_GNUPG_COLLECTION
+GCR_IS_GNUPG_COLLECTION_CLASS
+GCR_IS_GNUPG_KEY
+GCR_IS_GNUPG_KEY_CLASS
+GCR_TYPE_GNUPG_COLLECTION
+GCR_TYPE_GNUPG_KEY
+GcrColonColumns
+GcrColonPubColumns
+GcrColonUidColumns
+GcrColons
+GcrGnupgCollection
+GcrGnupgCollectionClass
+GcrGnupgCollectionPrivate
+GcrGnupgKey
+GcrGnupgKeyClass
+GcrGnupgKeyPrivate
+GcrLineCallback
+</SECTION>
\ No newline at end of file
diff --git a/gcr/Makefile.am b/gcr/Makefile.am
index 532462c..b2fe94a 100644
--- a/gcr/Makefile.am
+++ b/gcr/Makefile.am
@@ -65,10 +65,13 @@ LIB_SOURCES = \
 	gcr-certificate-widget.c gcr-certificate-widget.h \
 	gcr-collection.c gcr-collection.h \
 	gcr-collection-model.c gcr-collection-model.h \
+	gcr-colons.c gcr-colons.h \
 	gcr-debug.c gcr-debug.h \
 	gcr-display-scrolled.c gcr-display-scrolled.h \
 	gcr-comparable.c gcr-comparable.h \
 	gcr-display-view.c gcr-display-view.h \
+	gcr-gnupg-collection.c gcr-gnupg-collection.h \
+	gcr-gnupg-key.c gcr-gnupg-key.h \
 	gcr-icons.c gcr-icons.h \
 	gcr-import-dialog.c gcr-import-dialog.h \
 	gcr-importer.c gcr-importer.h  \
@@ -86,6 +89,7 @@ LIB_SOURCES = \
 	gcr-types.h \
 	gcr-unlock-options.h \
 	gcr-unlock-options-widget.c gcr-unlock-options-widget.h \
+	gcr-util.c gcr-util.h \
 	gcr-viewer.c gcr-viewer.h \
 	$(BUILT_SOURCES)
 
diff --git a/gcr/gcr-colons.c b/gcr/gcr-colons.c
new file mode 100644
index 0000000..4d9c01c
--- /dev/null
+++ b/gcr/gcr-colons.c
@@ -0,0 +1,152 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 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-colons.h"
+#define DEBUG_FLAG GCR_DEBUG_PARSE
+#include "gcr-debug.h"
+
+#include <string.h>
+
+#define MAX_COLUMNS 32
+
+struct _GcrColons {
+	gchar *data;
+	gpointer columns[MAX_COLUMNS];
+	guint n_columns;
+};
+
+GcrColons*
+_gcr_colons_parse (const gchar *line, gssize n_line)
+{
+	GcrColons *result;
+	gchar *p;
+
+	g_return_val_if_fail (line, NULL);
+	if (n_line < 0)
+		n_line = strlen (line);
+
+	result = g_slice_new0 (GcrColons);
+	result->data = g_strndup (line, n_line);
+
+	p = result->data;
+	for (;;) {
+		if (result->n_columns >= MAX_COLUMNS) {
+			_gcr_debug ("too many colons in gnupg line: %.*s", n_line, line);
+			_gcr_colons_free (result);
+			return NULL;
+		}
+
+		result->columns[result->n_columns] = p;
+		result->n_columns++;
+
+		p = strchr (p, ':');
+		if (p == NULL)
+			break;
+		p[0] = '\0';
+		p++;
+	}
+
+	_gcr_debug ("parsed line %.*s into %d columns", n_line, line, result->n_columns);
+	return result;
+}
+
+GcrColons*
+_gcr_colons_find (GPtrArray *dataset, GQuark schema)
+{
+	guint i;
+
+	g_return_val_if_fail (dataset, NULL);
+	g_return_val_if_fail (schema, NULL);
+
+	for (i = 0; i < dataset->len; i++) {
+		if (schema == _gcr_colons_get_schema (dataset->pdata[i]))
+			return dataset->pdata[i];
+	}
+
+	return NULL;
+}
+
+gchar*
+_gcr_colons_get_string (GcrColons *colons, guint column)
+{
+	const gchar *value;
+	gchar *text;
+	gchar *converted;
+
+	g_return_val_if_fail (colons, NULL);
+
+	value = _gcr_colons_get_raw (colons, column);
+	if (!value)
+		return NULL;
+	text = g_strcompress (value);
+	if (g_utf8_validate (text, -1, NULL))
+		return text;
+
+	/* If it's not UTF-8, we guess that it's latin1 */
+	converted = g_convert (text, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL);
+	g_free (text);
+
+	if (!converted) {
+		_gcr_debug ("failed to convert value from latin1 to utf-8: %s", text);
+		return NULL;
+	}
+
+	return converted;
+}
+
+const gchar*
+_gcr_colons_get_raw (GcrColons *colons, guint column)
+{
+	g_return_val_if_fail (colons, NULL);
+
+	if (column >= colons->n_columns) {
+		_gcr_debug ("only %d columns exist, tried to access %d",
+		            colons->n_columns, column);
+		return NULL;
+	}
+
+	return colons->columns[column];
+}
+
+void
+_gcr_colons_free (gpointer colons)
+{
+	if (!colons)
+		return;
+
+	g_free (((GcrColons*)colons)->data);
+	g_slice_free (GcrColons, colons);
+}
+
+GQuark
+_gcr_colons_get_schema (GcrColons *colons)
+{
+	const gchar *value;
+
+	value = _gcr_colons_get_raw (colons, GCR_COLONS_SCHEMA);
+	if (value != NULL)
+		return g_quark_try_string (value);
+	return 0;
+}
diff --git a/gcr/gcr-colons.h b/gcr/gcr-colons.h
new file mode 100644
index 0000000..5333998
--- /dev/null
+++ b/gcr/gcr-colons.h
@@ -0,0 +1,97 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 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_GNUPG_COLONS_H
+#define GCR_GNUPG_COLONS_H
+
+#include <glib.h>
+
+/*
+ * Gnupg's official format for listing keys is in the '--with-colons' format.
+ * This is documented in doc/DETAILS in the gnupg distribution. Looks like:
+ *
+ * pub:f:1024:17:6C7EE1B8621CC013:899817715:1055898235::m:::scESC:
+ * fpr:::::::::ECAF7590EB3443B5C7CF3ACB6C7EE1B8621CC013:
+ * uid:f::::::::Werner Koch <wk@g10code.com>:
+ * uid:f::::::::Werner Koch <wk@gnupg.org>:
+ * sub:f:1536:16:06AD222CADF6A6E1:919537416:1036177416:::::e:
+ * fpr:::::::::CF8BCC4B18DE08FCD8A1615906AD222CADF6A6E1:
+ * sub:r:1536:20:5CE086B5B5A18FF4:899817788:1025961788:::::esc:
+ * fpr:::::::::AB059359A3B81F410FCFF97F5CE086B5B5A18FF4:
+ *
+ * Each row is colon delimeted, and has a certain 'schema'. The first item
+ * in the row tells us the schema. Then the various columns are numbered,
+ * (schema is zero).
+ */
+
+G_BEGIN_DECLS
+
+#define GCR_COLONS_SCHEMA_UID  (g_quark_from_static_string ("uid"))
+#define GCR_COLONS_SCHEMA_PUB  (g_quark_from_static_string ("pub"))
+
+/* Common columns for all schemas */
+typedef enum {
+	GCR_COLONS_SCHEMA = 0
+} GcrColonColumns;
+
+/*
+ * Columns for pub schema, add them as they're used. eg:
+ * pub:f:1024:17:6C7EE1B8621CC013:899817715:1055898235::m:::scESC:
+ */
+typedef enum {
+	GCR_COLONS_PUB_KEYID = 4
+} GcrColonPubColumns;
+
+/*
+ * Columns for uid schema, add them as they're used. eg:
+ * pub:f:1024:17:6C7EE1B8621CC013:899817715:1055898235::m:::scESC:
+ */
+typedef enum {
+	GCR_COLONS_UID_NAME = 9
+} GcrColonUidColumns;
+
+typedef struct _GcrColons GcrColons;
+
+GcrColons*     _gcr_colons_parse                (const gchar *line,
+                                                 gssize n_line);
+
+void           _gcr_colons_free                 (gpointer colons);
+
+GcrColons*     _gcr_colons_find                 (GPtrArray *dataset,
+                                                 GQuark schema);
+
+gchar*         _gcr_colons_get_string           (GcrColons *colons,
+                                                 guint column);
+
+const gchar*   _gcr_colons_get_raw              (GcrColons *colons,
+                                                 guint column);
+
+GQuark         _gcr_colons_get_schema           (GcrColons *colons);
+
+G_END_DECLS
+
+#endif /* GCR_GNUPG_COLONS_H */
diff --git a/gcr/gcr-debug.c b/gcr/gcr-debug.c
index 04a3530..6df2341 100644
--- a/gcr/gcr-debug.c
+++ b/gcr/gcr-debug.c
@@ -37,6 +37,7 @@ static GcrDebugFlags current_flags = 0;
 
 static GDebugKey keys[] = {
 	{ "certificate-chain", GCR_DEBUG_CERTIFICATE_CHAIN },
+	{ "parse", GCR_DEBUG_PARSE },
 	{ 0, }
 };
 
diff --git a/gcr/gcr-debug.h b/gcr/gcr-debug.h
index 46de32c..200c939 100644
--- a/gcr/gcr-debug.h
+++ b/gcr/gcr-debug.h
@@ -30,6 +30,7 @@ G_BEGIN_DECLS
 typedef enum {
 	GCR_DEBUG_LIBRARY = 1 << 1,
 	GCR_DEBUG_CERTIFICATE_CHAIN = 1 << 2,
+	GCR_DEBUG_PARSE = 1 << 3,
 } GcrDebugFlags;
 
 gboolean           _gcr_debug_flag_is_set              (GcrDebugFlags flag);
diff --git a/gcr/gcr-gnupg-collection.c b/gcr/gcr-gnupg-collection.c
new file mode 100644
index 0000000..c6accf4
--- /dev/null
+++ b/gcr/gcr-gnupg-collection.c
@@ -0,0 +1,526 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 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-colons.h"
+#include "gcr-collection.h"
+#include "gcr-gnupg-collection.h"
+#include "gcr-gnupg-key.h"
+#include "gcr-internal.h"
+#include "gcr-util.h"
+
+#include "egg/egg-spawn.h"
+
+#include <sys/wait.h>
+#include <string.h>
+
+enum {
+	PROP_0,
+	PROP_DIRECTORY,
+	PROP_MAIN_CONTEXT
+};
+
+struct _GcrGnupgCollectionPrivate {
+	GHashTable *items;          /* Map of char *keyid -> GcrGnupgKey *key */
+	gchar *directory;
+};
+
+static void _gcr_collection_iface (GcrCollectionIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GcrGnupgCollection, _gcr_gnupg_collection, G_TYPE_OBJECT,
+	G_IMPLEMENT_INTERFACE (GCR_TYPE_COLLECTION, _gcr_collection_iface)
+);
+
+/* -----------------------------------------------------------------------------
+ * OBJECT
+ */
+
+static void
+_gcr_gnupg_collection_init (GcrGnupgCollection *self)
+{
+	self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_GNUPG_COLLECTION,
+	                                        GcrGnupgCollectionPrivate);
+
+	self->pv->items = g_hash_table_new_full (g_str_hash, g_str_equal,
+	                                         g_free, g_object_unref);
+}
+
+static void
+_gcr_gnupg_collection_set_property (GObject *obj, guint prop_id, const GValue *value,
+                                    GParamSpec *pspec)
+{
+	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (obj);
+
+	switch (prop_id) {
+	case PROP_DIRECTORY:
+		g_return_if_fail (!self->pv->directory);
+		self->pv->directory = g_value_dup_string (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+_gcr_gnupg_collection_get_property (GObject *obj, guint prop_id, GValue *value,
+                                    GParamSpec *pspec)
+{
+	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (obj);
+
+	switch (prop_id) {
+	case PROP_DIRECTORY:
+		g_value_set_string (value, self->pv->directory);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+_gcr_gnupg_collection_dispose (GObject *obj)
+{
+	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (obj);
+
+	g_hash_table_remove_all (self->pv->items);
+
+	G_OBJECT_CLASS (_gcr_gnupg_collection_parent_class)->dispose (obj);
+}
+
+static void
+_gcr_gnupg_collection_finalize (GObject *obj)
+{
+	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (obj);
+
+	g_assert (self->pv->items);
+	g_assert (g_hash_table_size (self->pv->items) == 0);
+	g_hash_table_destroy (self->pv->items);
+	self->pv->items = NULL;
+
+	g_free (self->pv->directory);
+	self->pv->directory = NULL;
+
+	G_OBJECT_CLASS (_gcr_gnupg_collection_parent_class)->finalize (obj);
+}
+
+static void
+_gcr_gnupg_collection_class_init (GcrGnupgCollectionClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	gobject_class->get_property = _gcr_gnupg_collection_get_property;
+	gobject_class->set_property = _gcr_gnupg_collection_set_property;
+	gobject_class->dispose = _gcr_gnupg_collection_dispose;
+	gobject_class->finalize = _gcr_gnupg_collection_finalize;
+
+	g_object_class_install_property (gobject_class, PROP_DIRECTORY,
+	           g_param_spec_string ("directory", "Directory", "Gnupg Directory",
+	                                NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_type_class_add_private (gobject_class, sizeof (GcrGnupgCollectionPrivate));
+	_gcr_initialize ();
+}
+
+static guint
+gcr_gnupg_collection_real_get_length (GcrCollection *coll)
+{
+	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (coll);
+	return g_hash_table_size (self->pv->items);
+}
+
+static GList*
+gcr_gnupg_collection_real_get_objects (GcrCollection *coll)
+{
+	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (coll);
+	return g_hash_table_get_keys (self->pv->items);
+}
+
+static void
+_gcr_collection_iface (GcrCollectionIface *iface)
+{
+	iface->get_length = gcr_gnupg_collection_real_get_length;
+	iface->get_objects = gcr_gnupg_collection_real_get_objects;
+}
+
+/**
+ * _gcr_gnupg_collection_new:
+ * @directory: The gnupg home directory, or %NULL
+ *
+ * Create a new GcrGnupgCollection.
+ *
+ * The gnupg home directory is where the keyring files live. If directory is
+ * %NULL then the default gnupg home directory is used.
+ *
+ * Returns: A newly allocated collection, which should be released with
+ *     g_object_unref().
+ */
+GcrCollection*
+_gcr_gnupg_collection_new (const gchar *directory)
+{
+	return g_object_new (GCR_TYPE_GNUPG_COLLECTION,
+	                     "directory", directory,
+	                     NULL);
+}
+
+/*
+ * We use @difference to track the keys that were in the collection before
+ * the load process, and then remove any not found, at the end of the load
+ * process. Strings are directly used from collection->pv->items keys.
+ */
+
+typedef struct {
+	GcrGnupgCollection *collection;       /* reffed pointer back to collection */
+	GPtrArray *dataset;                   /* GcrColons* not yet made into a key */
+	guint spawn_sig;
+	guint child_sig;
+	GPid gnupg_pid;
+	GString *out_data;                    /* Pending output not yet parsed into colons */
+	GString *err_data;                    /* Pending errors not yet printed */
+	GHashTable *difference;               /* Hashset gchar *keyid -> gchar *keyid */
+} GcrGnupgCollectionLoad;
+
+static void
+_gcr_gnupg_collection_load_free (gpointer data)
+{
+	GcrGnupgCollectionLoad *load = data;
+	g_assert (load);
+
+	g_ptr_array_unref (load->dataset);
+	g_string_free (load->err_data, TRUE);
+	g_string_free (load->out_data, TRUE);
+	g_hash_table_destroy (load->difference);
+	g_object_unref (load->collection);
+
+	if (load->spawn_sig)
+		g_source_remove (load->spawn_sig);
+	if (load->child_sig)
+		g_source_remove (load->child_sig);
+	if (load->gnupg_pid) {
+		kill (load->gnupg_pid, SIGTERM);
+		g_spawn_close_pid (load->gnupg_pid);
+	}
+	g_slice_free (GcrGnupgCollectionLoad, load);
+}
+
+static void
+process_dataset_as_key (GcrGnupgCollectionLoad *load)
+{
+	const gchar *keyid;
+	GPtrArray *dataset;
+	GcrGnupgKey *key;
+
+	g_assert (load->dataset->len);
+
+	dataset = load->dataset;
+	load->dataset = g_ptr_array_new_with_free_func (_gcr_colons_free);
+
+	keyid = _gcr_gnupg_key_get_keyid_for_colons (dataset);
+	if (keyid) {
+		/* Note that we've seen this keyid */
+		g_hash_table_remove (load->difference, keyid);
+
+		key = g_hash_table_lookup (load->collection->pv->items, keyid);
+
+		/* Already have this key, just update */
+		if (key) {
+			_gcr_gnupg_key_set_dataset (key, dataset);
+
+		/* Add a new key */
+		} else {
+			key = _gcr_gnupg_key_new (dataset);
+			g_hash_table_insert (load->collection->pv->items, g_strdup (keyid), key);
+			gcr_collection_emit_added (GCR_COLLECTION (load->collection), G_OBJECT (key));
+		}
+
+	} else {
+		g_warning ("parsed gnupg data had no keyid");
+	}
+
+	g_ptr_array_unref (dataset);
+}
+
+static void
+on_line_parse_output (const gchar *line, gpointer user_data)
+{
+	GcrGnupgCollectionLoad *load = user_data;
+	GcrColons *colons;
+	GQuark schema;
+
+	colons = _gcr_colons_parse (line, -1);
+	if (!colons) {
+		g_warning ("invalid gnupg output line: %s", line);
+		return;
+	}
+
+	schema = _gcr_colons_get_schema (colons);
+
+	/*
+	 * Each time we see a line with 'pub' schema we assume that it's
+	 * a new key being listed.
+	 */
+	if (schema == GCR_COLONS_SCHEMA_PUB) {
+		if (load->dataset->len)
+			process_dataset_as_key (load);
+		g_assert (!load->dataset->len);
+		g_ptr_array_add (load->dataset, colons);
+		colons = NULL;
+
+	/*
+	 * 'uid' schema lines get added to the key that came before.
+	 */
+	} else if (schema == GCR_COLONS_SCHEMA_UID) {
+		if (load->dataset->len) {
+			g_ptr_array_add (load->dataset, colons);
+			colons = NULL;
+		}
+	}
+
+	if (colons != NULL)
+		_gcr_colons_free (colons);
+}
+
+
+static gboolean
+on_spawn_standard_output (int fd, gpointer user_data)
+{
+	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
+	gchar buffer[1024];
+	gssize ret;
+
+	ret = egg_spawn_read_output (fd, buffer, sizeof (buffer));
+	if (ret < 0) {
+		g_warning ("couldn't read output data from prompt process");
+		return FALSE;
+	}
+
+	g_string_append_len (load->out_data, buffer, ret);
+	_gcr_util_parse_lines (load->out_data, FALSE, on_line_parse_output, load);
+
+	return (ret > 0);
+}
+
+static void
+on_line_print_error (const gchar *line, gpointer unused)
+{
+	g_printerr ("%s\n", line);
+}
+
+static gboolean
+on_spawn_standard_error (int fd, gpointer user_data)
+{
+	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
+	gchar buffer[1024];
+	gssize ret;
+
+	ret = egg_spawn_read_output (fd, buffer, sizeof (buffer));
+	if (ret < 0) {
+		g_warning ("couldn't read error data from prompt process");
+		return FALSE;
+	}
+
+	g_string_append_len (load->err_data, buffer, ret);
+	_gcr_util_parse_lines (load->err_data, FALSE, on_line_print_error, NULL);
+
+	return ret > 0;
+}
+
+static void
+on_spawn_completed (gpointer user_data)
+{
+	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
+	GHashTableIter iter;
+	GObject *object;
+	gpointer keyid;
+
+	/* Should be the last call we receive */
+	g_assert (load->spawn_sig != 0);
+	load->spawn_sig = 0;
+
+	/* Print out any remaining errors */
+	_gcr_util_parse_lines (load->err_data, TRUE, on_line_print_error, NULL);
+
+	/* Process any remaining output */
+	_gcr_util_parse_lines (load->out_data, TRUE, on_line_parse_output, load);
+
+	/* Process last bit as a key, if any */
+	if (load->dataset->len)
+		process_dataset_as_key (load);
+
+	/* Remove any keys that we still have in the difference */
+	g_hash_table_iter_init (&iter, load->difference);
+	while (g_hash_table_iter_next (&iter, &keyid, NULL)) {
+		object = g_hash_table_lookup (load->collection->pv->items, keyid);
+		if (object != NULL) {
+			g_object_ref (object);
+			g_hash_table_remove (load->collection->pv->items, keyid);
+			gcr_collection_emit_removed (GCR_COLLECTION (load->collection), object);
+			g_object_unref (object);
+		}
+	}
+
+	g_simple_async_result_complete (res);
+}
+
+static void
+on_child_exited (GPid pid, gint status, gpointer user_data)
+{
+	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
+	gint code;
+
+	g_return_if_fail (pid == load->gnupg_pid);
+
+	g_spawn_close_pid (load->gnupg_pid);
+	load->gnupg_pid = 0;
+	load->child_sig = 0;
+
+	if (WIFEXITED (status)) {
+		code = WEXITSTATUS (status);
+		if (code != 0) {
+			g_message ("gnupg process exited with failure code: %d", code);
+		} else if (WIFSIGNALED (status)) {
+			code = WTERMSIG (status);
+			g_message ("gnupg process was killed with signal: %d", code);
+		}
+	}
+}
+
+static EggSpawnCallbacks spawn_callbacks = {
+	NULL,
+	on_spawn_standard_output,
+	on_spawn_standard_error,
+	on_spawn_completed,
+	g_object_unref,
+	NULL
+};
+
+/**
+ * _gcr_gnupg_collection_load_async:
+ * @self: The collection
+ * @cancellable: Cancellation object or %NULL
+ * @callback: Callback to call when result is ready
+ * @user_data: Data for callback
+ *
+ * Start an operation to load or reload the list of gnupg keys in this
+ * collection.
+ */
+void
+_gcr_gnupg_collection_load_async (GcrGnupgCollection *self, GCancellable *cancellable,
+                                  GAsyncReadyCallback callback, gpointer user_data)
+{
+	GSimpleAsyncResult *res;
+	GcrGnupgCollectionLoad *load;
+	GError *error = NULL;
+	GPtrArray *argv;
+	GHashTableIter iter;
+	gpointer keyid;
+
+	g_return_if_fail (GCR_IS_GNUPG_COLLECTION (self));
+
+	/* TODO: Cancellation not yet implemented */
+
+	argv = g_ptr_array_new ();
+	g_ptr_array_add (argv, (gpointer)GPG_EXECUTABLE);
+	g_ptr_array_add (argv, (gpointer)"--list-keys");
+	g_ptr_array_add (argv, (gpointer)"--fixed-list-mode");
+	g_ptr_array_add (argv, (gpointer)"--with-colons");
+	if (self->pv->directory) {
+		g_ptr_array_add (argv, (gpointer)"--homedir");
+		g_ptr_array_add (argv, (gpointer)self->pv->directory);
+	}
+	g_ptr_array_add (argv, NULL);
+
+	res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+	                                 _gcr_gnupg_collection_load_async);
+
+	load = g_slice_new0 (GcrGnupgCollectionLoad);
+	load->dataset = g_ptr_array_new_with_free_func (_gcr_colons_free);
+	load->err_data = g_string_sized_new (128);
+	load->out_data = g_string_sized_new (1024);
+	load->collection = g_object_ref (self);
+
+	/*
+	 * Track all the keys we currently have, at end remove those that
+	 * didn't get listed by the gpg process.
+	 */
+	load->difference = g_hash_table_new (g_str_hash, g_str_equal);
+	g_hash_table_iter_init (&iter, self->pv->items);
+	while (g_hash_table_iter_next (&iter, &keyid, NULL))
+		g_hash_table_insert (load->difference, keyid, keyid);
+
+	g_simple_async_result_set_op_res_gpointer (res, load,
+	                                           _gcr_gnupg_collection_load_free);
+
+	load->spawn_sig = egg_spawn_async_with_callbacks (self->pv->directory,
+	                                                  (gchar**)argv->pdata, NULL,
+	                                                  G_SPAWN_DO_NOT_REAP_CHILD,
+	                                                  &load->gnupg_pid,
+	                                                  &spawn_callbacks,
+	                                                  g_object_ref (res),
+	                                                  NULL, &error);
+
+	if (error) {
+		g_simple_async_result_set_from_error (res, error);
+		g_simple_async_result_complete_in_idle (res);
+		g_clear_error (&error);
+	} else {
+		load->child_sig = g_child_watch_add_full (G_PRIORITY_DEFAULT,
+		                                          load->gnupg_pid,
+		                                          on_child_exited,
+		                                          g_object_ref (res),
+		                                          g_object_unref);
+	}
+
+	g_object_unref (res);
+	g_ptr_array_unref (argv);
+}
+
+/**
+ * _gcr_gnupg_collection_load_finish:
+ * @self: The collection
+ * @result: The result passed to the callback
+ * @error: Location to raise an error on failure.
+ *
+ * Get the result of an operation to load or reload the list of gnupg keys
+ * in this collection.
+ */
+gboolean
+_gcr_gnupg_collection_load_finish (GcrGnupgCollection *self, GAsyncResult *result,
+                                   GError **error)
+{
+	g_return_val_if_fail (GCR_IS_GNUPG_COLLECTION (self), FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
+	                      _gcr_gnupg_collection_load_async), FALSE);
+
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return FALSE;
+
+	return TRUE;
+}
diff --git a/gcr/gcr-gnupg-collection.h b/gcr/gcr-gnupg-collection.h
new file mode 100644
index 0000000..b2a5d25
--- /dev/null
+++ b/gcr/gcr-gnupg-collection.h
@@ -0,0 +1,69 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 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>
+ */
+
+#ifndef GCR_GNUPG_COLLECTION_H
+#define GCR_GNUPG_COLLECTION_H
+
+#include "gcr.h"
+#include "gcr-collection.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GCR_TYPE_GNUPG_COLLECTION               (_gcr_gnupg_collection_get_type ())
+#define GCR_GNUPG_COLLECTION(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_COLLECTION, GcrGnupgCollection))
+#define GCR_GNUPG_COLLECTION_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_COLLECTION, GcrGnupgCollectionClass))
+#define GCR_IS_GNUPG_COLLECTION(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCR_TYPE_COLLECTION))
+#define GCR_IS_GNUPG_COLLECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCR_TYPE_COLLECTION))
+#define GCR_GNUPG_COLLECTION_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCR_TYPE_COLLECTION, GcrGnupgCollectionClass))
+
+typedef struct _GcrGnupgCollection GcrGnupgCollection;
+typedef struct _GcrGnupgCollectionClass GcrGnupgCollectionClass;
+typedef struct _GcrGnupgCollectionPrivate GcrGnupgCollectionPrivate;
+
+struct _GcrGnupgCollection {
+	GObject parent;
+	GcrGnupgCollectionPrivate *pv;
+};
+
+struct _GcrGnupgCollectionClass {
+	GObjectClass parent_class;
+};
+
+GType               _gcr_gnupg_collection_get_type                (void);
+
+GcrCollection*      _gcr_gnupg_collection_new                     (const gchar *directory);
+
+void                _gcr_gnupg_collection_load_async              (GcrGnupgCollection *self,
+                                                                   GCancellable *cancellable,
+                                                                   GAsyncReadyCallback callback,
+                                                                   gpointer user_data);
+
+gboolean            _gcr_gnupg_collection_load_finish             (GcrGnupgCollection *self,
+                                                                   GAsyncResult *result,
+                                                                   GError **error);
+
+G_END_DECLS
+
+#endif /* GCR_GNUPG_COLLECTION_H */
diff --git a/gcr/gcr-gnupg-key.c b/gcr/gcr-gnupg-key.c
new file mode 100644
index 0000000..874a3ba
--- /dev/null
+++ b/gcr/gcr-gnupg-key.c
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2011 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-colons.h"
+#include "gcr-gnupg-key.h"
+
+#include "gck/gck.h"
+
+#include <gdk/gdk.h>
+#include <glib/gi18n-lib.h>
+
+enum {
+	PROP_0,
+	PROP_DATASET,
+	PROP_LABEL,
+	PROP_MARKUP,
+	PROP_DESCRIPTION,
+	PROP_KEYID
+};
+
+struct _GcrGnupgKeyPrivate {
+	GPtrArray *dataset;
+};
+
+G_DEFINE_TYPE (GcrGnupgKey, _gcr_gnupg_key, G_TYPE_OBJECT);
+
+/* -----------------------------------------------------------------------------
+ * INTERNAL
+ */
+
+static gchar *
+calculate_name (GcrGnupgKey *self)
+{
+	GcrColons* colons;
+
+	colons = _gcr_colons_find (self->pv->dataset, GCR_COLONS_SCHEMA_UID);
+	g_return_val_if_fail (colons, NULL);
+
+	return _gcr_colons_get_string (colons, GCR_COLONS_UID_NAME);
+}
+
+static gchar *
+calculate_markup (GcrGnupgKey *self)
+{
+	gchar *result = NULL;
+	gchar *name;
+
+	name = calculate_name (self);
+	if (name)
+		result = g_markup_escape_text (name, -1);
+	g_free (name);
+
+	return result;
+}
+
+static const gchar *
+calculate_keyid (GcrGnupgKey *self)
+{
+	const gchar *keyid;
+	gsize length;
+
+	keyid = _gcr_gnupg_key_get_keyid_for_colons (self->pv->dataset);
+	if (keyid == NULL)
+		return NULL;
+
+	length = strlen (keyid);
+	if (length > 8)
+		keyid += (length - 8);
+
+	return keyid;
+}
+
+static void
+_gcr_gnupg_key_init (GcrGnupgKey *self)
+{
+	self->pv = (G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_GNUPG_KEY, GcrGnupgKeyPrivate));
+}
+
+static void
+_gcr_gnupg_key_finalize (GObject *obj)
+{
+	GcrGnupgKey *self = GCR_GNUPG_KEY (obj);
+
+	if (self->pv->dataset)
+		g_ptr_array_free (self->pv->dataset, TRUE);
+
+	G_OBJECT_CLASS (_gcr_gnupg_key_parent_class)->finalize (obj);
+}
+
+static void
+_gcr_gnupg_key_set_property (GObject *obj, guint prop_id, const GValue *value,
+                             GParamSpec *pspec)
+{
+	GcrGnupgKey *self = GCR_GNUPG_KEY (obj);
+
+	switch (prop_id) {
+	case PROP_DATASET:
+		_gcr_gnupg_key_set_dataset (self, g_value_get_boxed (value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+_gcr_gnupg_key_get_property (GObject *obj, guint prop_id, GValue *value,
+                             GParamSpec *pspec)
+{
+	GcrGnupgKey *self = GCR_GNUPG_KEY (obj);
+
+	switch (prop_id) {
+	case PROP_DATASET:
+		g_value_set_boxed (value, self->pv->dataset);
+		break;
+	case PROP_LABEL:
+		g_value_take_string (value, calculate_name (self));
+		break;
+	case PROP_DESCRIPTION:
+		g_value_set_string (value, _("PGP Key"));
+		break;
+	case PROP_MARKUP:
+		g_value_take_string (value, calculate_markup (self));
+		break;
+	case PROP_KEYID:
+		g_value_set_string (value, calculate_keyid (self));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+_gcr_gnupg_key_class_init (GcrGnupgKeyClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	_gcr_gnupg_key_parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (GcrGnupgKeyPrivate));
+
+	gobject_class->finalize = _gcr_gnupg_key_finalize;
+	gobject_class->set_property = _gcr_gnupg_key_set_property;
+	gobject_class->get_property = _gcr_gnupg_key_get_property;
+
+	g_object_class_install_property (gobject_class, PROP_DATASET,
+	         g_param_spec_boxed ("dataset", "Dataset", "Colon Dataset",
+	                             G_TYPE_PTR_ARRAY, G_PARAM_READWRITE));
+
+	g_object_class_install_property (gobject_class, PROP_LABEL,
+	         g_param_spec_string ("label", "Label", "Key label",
+	                              "", G_PARAM_READABLE));
+
+	g_object_class_install_property (gobject_class, PROP_DESCRIPTION,
+	         g_param_spec_string ("description", "Description", "Description of object type",
+	                              "", G_PARAM_READABLE));
+
+	g_object_class_install_property (gobject_class, PROP_MARKUP,
+	         g_param_spec_string ("markup", "Markup", "Markup which describes key",
+	                              "", G_PARAM_READABLE));
+
+	g_object_class_install_property (gobject_class, PROP_KEYID,
+	         g_param_spec_string ("keyid", "Key ID", "Display key identifier",
+	                              "", G_PARAM_READABLE));
+}
+
+/**
+ * _gcr_gnupg_key_new:
+ * @dataset: array of GcrColons*
+ *
+ * Create a new GcrGnupgKey for the colons data passed.
+ *
+ * Returns: A newly allocated key, which should be released with
+ *     g_object_unref().
+ */
+GcrGnupgKey*
+_gcr_gnupg_key_new (GPtrArray *dataset)
+{
+	g_return_val_if_fail (dataset, NULL);
+	return g_object_new (GCR_TYPE_GNUPG_KEY, "dataset", dataset, NULL);
+}
+
+/**
+ * _gcr_gnupg_key_get_dataset:
+ * @self: The key
+ *
+ * Get the colons data this key is based on.
+ *
+ * Returns: An array of GcrColons*, owned by the key.
+ */
+GPtrArray*
+_gcr_gnupg_key_get_dataset (GcrGnupgKey *self)
+{
+	g_return_val_if_fail (GCR_IS_GNUPG_KEY (self), NULL);
+	return self->pv->dataset;
+}
+
+/**
+ * _gcr_gnupg_key_set_dataset:
+ * @self: The key
+ * @dataset: The new array of GcrColons*
+ *
+ * Change the colons data that this key is based on.
+ */
+void
+_gcr_gnupg_key_set_dataset (GcrGnupgKey *self, GPtrArray *dataset)
+{
+	GObject *obj;
+
+	g_return_if_fail (GCR_IS_GNUPG_KEY (self));
+	g_return_if_fail (dataset);
+
+	g_ptr_array_ref (dataset);
+	if (self->pv->dataset)
+		g_ptr_array_unref (self->pv->dataset);
+	self->pv->dataset = dataset;
+
+	obj = G_OBJECT (self);
+	g_object_freeze_notify (obj);
+	g_object_notify (obj, "dataset");
+	g_object_notify (obj, "label");
+	g_object_notify (obj, "markup");
+	g_object_notify (obj, "keyid");
+	g_object_thaw_notify (obj);
+}
+
+/**
+ * _gcr_gnupg_key_get_keyid_for_colons:
+ * @dataset: Array of GcrColons*
+ *
+ * Get the keyid for some colons data.
+ *
+ * Returns: The keyid, owned by the colons data.
+ */
+const gchar*
+_gcr_gnupg_key_get_keyid_for_colons (GPtrArray *dataset)
+{
+	GcrColons *colons;
+
+	colons = _gcr_colons_find (dataset, GCR_COLONS_SCHEMA_PUB);
+	if (colons == NULL)
+		return NULL;
+
+	return _gcr_colons_get_raw (colons, GCR_COLONS_PUB_KEYID);
+}
+
+/**
+ * _gcr_gnupg_key_get_columns:
+ *
+ * Get the columns that we should display for gnupg keys.
+ *
+ * Returns: The columns, NULL terminated, should not be freed.
+ */
+const GcrColumn*
+_gcr_gnupg_key_get_columns (void)
+{
+	static GcrColumn columns[] = {
+		{ "label", G_TYPE_STRING, G_TYPE_STRING, NC_("column", "Name"),
+		  GCR_COLUMN_SORTABLE },
+		{ "keyid", G_TYPE_STRING, G_TYPE_STRING, NC_("column", "Key ID"),
+		  GCR_COLUMN_SORTABLE },
+		{ NULL }
+	};
+
+	return columns;
+}
diff --git a/gcr/gcr-gnupg-key.h b/gcr/gcr-gnupg-key.h
new file mode 100644
index 0000000..b1b8765
--- /dev/null
+++ b/gcr/gcr-gnupg-key.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 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_GNUPG_KEY_H
+#define GCR_GNUPG_KEY_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "gcr-column.h"
+#include "gcr-types.h"
+
+G_BEGIN_DECLS
+
+#define GCR_GNUPG_KEY_COLUMNS            (_gcr_gnupg_key_get_columns ())
+#define GCR_TYPE_GNUPG_KEY               (_gcr_gnupg_key_get_type ())
+#define GCR_GNUPG_KEY(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_GNUPG_KEY, GcrGnupgKey))
+#define GCR_GNUPG_KEY_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_GNUPG_KEY, GcrGnupgKeyClass))
+#define GCR_IS_GNUPG_KEY(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCR_TYPE_GNUPG_KEY))
+#define GCR_IS_GNUPG_KEY_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCR_TYPE_GNUPG_KEY))
+#define GCR_GNUPG_KEY_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCR_TYPE_GNUPG_KEY, GcrGnupgKeyClass))
+
+typedef struct _GcrGnupgKey GcrGnupgKey;
+typedef struct _GcrGnupgKeyClass GcrGnupgKeyClass;
+typedef struct _GcrGnupgKeyPrivate GcrGnupgKeyPrivate;
+
+struct _GcrGnupgKey {
+	GObject parent;
+
+	/*< private >*/
+	GcrGnupgKeyPrivate *pv;
+};
+
+struct _GcrGnupgKeyClass {
+	GObjectClass parent_class;
+};
+
+GType               _gcr_gnupg_key_get_type                      (void);
+
+const GcrColumn*    _gcr_gnupg_key_get_columns                   (void);
+
+GcrGnupgKey*        _gcr_gnupg_key_new                           (GPtrArray *dataset);
+
+GPtrArray*          _gcr_gnupg_key_get_dataset                   (GcrGnupgKey *self);
+
+void                _gcr_gnupg_key_set_dataset                   (GcrGnupgKey *self,
+                                                                  GPtrArray *dataset);
+
+const gchar*        _gcr_gnupg_key_get_keyid_for_colons          (GPtrArray *dataset);
+
+G_END_DECLS
+
+#endif /* __GCR_GNUPG_KEY_H__ */
diff --git a/gcr/gcr-library.c b/gcr/gcr-library.c
index c52ba38..41cedf7 100644
--- a/gcr/gcr-library.c
+++ b/gcr/gcr-library.c
@@ -62,6 +62,14 @@
  */
 
 /**
+ * SECTION:gcr-private
+ * @title: Private declarations
+ * @short_description: private declarations to supress warnings.
+ *
+ * This section is only here to supress warnings, and should not be displayed.
+ */
+
+/**
  * GCR_DATA_ERROR:
  *
  * The #GError domain for data parsing errors.
diff --git a/gcr/gcr-util.c b/gcr/gcr-util.c
new file mode 100644
index 0000000..418b73c
--- /dev/null
+++ b/gcr/gcr-util.c
@@ -0,0 +1,66 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 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-util.h"
+
+#include <string.h>
+
+/**
+ * _gcr_util_parse_lines:
+ * @string: The string to parse lines from, will be modified
+ * @last_line: Whether or not we should run for last line or not
+ * @callback: Call for each line
+ * @user_data: Data for callback
+ *
+ * Calls callback for each line. If last_line, also sends the remainder
+ * data that comes after the last line break. \n and \r\n are line separators.
+ * Neither are included in data passed to callback.
+ */
+void
+_gcr_util_parse_lines (GString *string, gboolean last_line,
+                       GcrLineCallback callback, gpointer user_data)
+{
+	gchar *ptr;
+	gchar *prev;
+
+	g_return_if_fail (string);
+	g_return_if_fail (callback);
+
+	/* Print all stderr lines as messages */
+	while ((ptr = strchr (string->str, '\n')) != NULL) {
+		*ptr = '\0';
+		prev = ptr - 1;
+		if (*prev == '\r')
+			*prev = '\0';
+
+		(callback) (string->str, user_data);
+		g_string_erase (string, 0, ptr - string->str + 1);
+	}
+
+	if (last_line && string->len) {
+		(callback) (string->str, user_data);
+		g_string_erase (string, 0, string->len);
+	}
+}
diff --git a/gcr/gcr-util.h b/gcr/gcr-util.h
new file mode 100644
index 0000000..a104a8e
--- /dev/null
+++ b/gcr/gcr-util.h
@@ -0,0 +1,41 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 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>
+ */
+
+#ifndef GCR_UTIL_H
+#define GCR_UTIL_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef void  (*GcrLineCallback)              (const gchar *line,
+                                               gpointer user_data);
+
+void           _gcr_util_parse_lines          (GString *string,
+                                               gboolean last_line,
+                                               GcrLineCallback callback,
+                                               gpointer user_data);
+
+G_END_DECLS
+
+#endif /* __GCR_TOKEN_MANAGER_H__ */
diff --git a/gcr/tests/Makefile.am b/gcr/tests/Makefile.am
index b5ff961..9105937 100644
--- a/gcr/tests/Makefile.am
+++ b/gcr/tests/Makefile.am
@@ -5,6 +5,7 @@ INCLUDES = \
 	-DSRCDIR=$(srcdir) \
 	-DGCR_API_SUBJECT_TO_CHANGE \
 	-DGCK_API_SUBJECT_TO_CHANGE \
+	-DGCR_COMPILATION \
 	$(GLIB_CFLAGS) \
 	$(GTK_CFLAGS) \
 	$(LIBGCRYPT_CFLAGS)
@@ -19,12 +20,16 @@ LDADD = \
 	$(LIBGCRYPT_LIBS)
 
 TEST_PROGS = \
+	test-util \
+	test-simple-certificate \
 	test-certificate \
 	test-certificate-chain \
 	test-pkcs11-certificate \
-	test-simple-certificate \
 	test-trust \
-	test-parser
+	test-parser \
+	test-colons \
+	test-gnupg-key \
+	test-gnupg-collection
 
 check_PROGRAMS = $(TEST_PROGS)
 
@@ -44,6 +49,7 @@ EXTRA_DIST = \
 
 noinst_PROGRAMS = \
 	frob-certificate \
+	frob-gnupg-selector \
 	frob-key \
 	frob-selector \
 	frob-unlock-options
diff --git a/gcr/tests/files/gnupg-homedir/pubring.gpg b/gcr/tests/files/gnupg-homedir/pubring.gpg
new file mode 100644
index 0000000..a642536
Binary files /dev/null and b/gcr/tests/files/gnupg-homedir/pubring.gpg differ
diff --git a/gcr/tests/files/gnupg-homedir/secring.gpg b/gcr/tests/files/gnupg-homedir/secring.gpg
new file mode 100644
index 0000000..e69de29
diff --git a/gcr/tests/files/gnupg-homedir/trustdb.gpg b/gcr/tests/files/gnupg-homedir/trustdb.gpg
new file mode 100644
index 0000000..0377c97
Binary files /dev/null and b/gcr/tests/files/gnupg-homedir/trustdb.gpg differ
diff --git a/gcr/tests/frob-gnupg-selector.c b/gcr/tests/frob-gnupg-selector.c
new file mode 100644
index 0000000..02fd837
--- /dev/null
+++ b/gcr/tests/frob-gnupg-selector.c
@@ -0,0 +1,79 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 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.h"
+#include "gcr-gnupg-collection.h"
+#include "gcr-gnupg-key.h"
+
+#include <gtk/gtk.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+static void
+on_collection_loaded (GObject *source, GAsyncResult *result, gpointer unused)
+{
+	GError *error = NULL;
+
+	_gcr_gnupg_collection_load_finish (GCR_GNUPG_COLLECTION (source), result, &error);
+	if (error) {
+		g_warning ("collection load failed: %s", error->message);
+		g_clear_error (&error);
+	}
+}
+
+int
+main (int argc, char *argv[])
+{
+	GcrCollection *collection;
+	GcrSelector *selector;
+	GtkDialog *dialog;
+
+	gtk_init (&argc, &argv);
+
+	dialog = GTK_DIALOG (gtk_dialog_new ());
+	g_object_ref_sink (dialog);
+
+	collection = _gcr_gnupg_collection_new (NULL);
+	selector = gcr_selector_new (collection, GCR_GNUPG_KEY_COLUMNS, GCR_SELECTOR_MODE_MULTIPLE);
+
+	gtk_widget_show (GTK_WIDGET (selector));
+	gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (dialog)), GTK_WIDGET (selector));
+
+	_gcr_gnupg_collection_load_async (GCR_GNUPG_COLLECTION (collection), NULL,
+	                                  on_collection_loaded, NULL);
+
+	gtk_window_set_default_size (GTK_WINDOW (dialog), 550, 400);
+	gtk_container_set_border_width (GTK_CONTAINER (dialog), 20);
+
+	g_object_unref (collection);
+
+	gtk_dialog_run (dialog);
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+	g_object_unref (dialog);
+
+	return 0;
+}
diff --git a/gcr/tests/test-colons.c b/gcr/tests/test-colons.c
new file mode 100644
index 0000000..4e153d5
--- /dev/null
+++ b/gcr/tests/test-colons.c
@@ -0,0 +1,180 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+   Copyright (C) 2011 Collabora Ltd.
+
+   The Gnome Keyring Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Keyring 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  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/gcr-colons.h"
+
+#include "egg/egg-testing.h"
+
+#include <glib.h>
+
+typedef struct {
+	GcrColons *colons;
+} Test;
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+	test->colons = _gcr_colons_parse ("one:tab\\there::four:f\xfc""nf:", -1);
+}
+
+static void
+teardown (Test *test, gconstpointer unused)
+{
+	_gcr_colons_free (test->colons);
+}
+
+static void
+test_parse (void)
+{
+	GcrColons *colons;
+
+	colons = _gcr_colons_parse ("one:two::four::six", -1);
+	g_assert (colons);
+
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 0), ==, "one");
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 1), ==, "two");
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 2), ==, "");
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 3), ==, "four");
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 4), ==, "");
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 5), ==, "six");
+	g_assert (_gcr_colons_get_raw (colons, 6) == NULL);
+
+	_gcr_colons_free (colons);
+}
+
+static void
+test_parse_part (void)
+{
+	GcrColons *colons;
+
+	colons = _gcr_colons_parse ("one:two::four::six", 8);
+	g_assert (colons);
+
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 0), ==, "one");
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 1), ==, "two");
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 2), ==, "");
+	g_assert (_gcr_colons_get_raw (colons, 3) == NULL);
+
+	_gcr_colons_free (colons);
+}
+
+static void
+test_parse_too_long (void)
+{
+	GcrColons *colons;
+
+	/* Too many columns */
+	colons = _gcr_colons_parse (":::::::::::::::::::::::::::::::::::::::::::::::::::::", -1);
+	g_assert (colons == NULL);
+}
+
+static void
+test_find (void)
+{
+	GcrColons *uid, *pub, *one, *check;
+	GPtrArray *dataset;
+
+	dataset = g_ptr_array_new_with_free_func (_gcr_colons_free);
+
+	one = _gcr_colons_parse ("one:two::four::six", -1);
+	g_ptr_array_add (dataset, one);
+	pub = _gcr_colons_parse ("pub:two", -1);
+	g_ptr_array_add (dataset, pub);
+	uid = _gcr_colons_parse ("uid:two", -1);
+	g_ptr_array_add (dataset, uid);
+
+	check = _gcr_colons_find (dataset, GCR_COLONS_SCHEMA_PUB);
+	g_assert (check == pub);
+
+	check = _gcr_colons_find (dataset, GCR_COLONS_SCHEMA_UID);
+	g_assert (check == uid);
+
+	g_ptr_array_unref (dataset);
+}
+
+static void
+test_get_string (Test *test, gconstpointer unused)
+{
+	gchar *value = _gcr_colons_get_string (test->colons, 1);
+	g_assert (value);
+
+	g_assert_cmpstr (value, ==, "tab\there");
+	g_free (value);
+}
+
+static void
+test_get_string_null (Test *test, gconstpointer unused)
+{
+	gchar *value = _gcr_colons_get_string (test->colons, 35);
+	g_assert (value == NULL);
+}
+
+static void
+test_get_string_latin1 (Test *test, gconstpointer unused)
+{
+	gchar *value = _gcr_colons_get_string (test->colons, 4);
+	g_assert (value);
+
+	g_assert_cmpstr (value, ==, "f\xc3\xbc""nf");
+	g_assert (g_utf8_validate (value, -1, NULL));
+	g_free (value);
+}
+
+static void
+test_free_null (void)
+{
+	_gcr_colons_free (NULL);
+}
+
+static void
+test_get_schema (Test *test, gconstpointer unused)
+{
+	GQuark schema;
+	GQuark check;
+
+	/* Initialize this quark */
+	check = g_quark_from_static_string ("one");
+
+	schema = _gcr_colons_get_schema (test->colons);
+	g_assert (check == schema);
+	g_assert_cmpstr (g_quark_to_string (schema), ==, "one");
+}
+
+int
+main (int argc, char **argv)
+{
+	g_test_init (&argc, &argv, NULL);
+
+	g_test_add_func ("/gcr/colons/parse", test_parse);
+	g_test_add_func ("/gcr/colons/parse_part", test_parse_part);
+	g_test_add_func ("/gcr/colons/parse_too_long", test_parse_too_long);
+	g_test_add_func ("/gcr/colons/free_null", test_free_null);
+	g_test_add_func ("/gcr/colons/find", test_find);
+	g_test_add ("/gcr/colons/get_string", Test, NULL, setup, test_get_string, teardown);
+	g_test_add ("/gcr/colons/get_string_null", Test, NULL, setup, test_get_string_null, teardown);
+	g_test_add ("/gcr/colons/get_string_latin1", Test, NULL, setup, test_get_string_latin1, teardown);
+	g_test_add ("/gcr/colons/get_schema", Test, NULL, setup, test_get_schema, teardown);
+
+	return g_test_run ();
+}
diff --git a/gcr/tests/test-gnupg-collection.c b/gcr/tests/test-gnupg-collection.c
new file mode 100644
index 0000000..d156a82
--- /dev/null
+++ b/gcr/tests/test-gnupg-collection.c
@@ -0,0 +1,82 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+   Copyright (C) 2010 Collabora Ltd
+
+   The Gnome Keyring Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Keyring 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  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/gcr.h"
+#include "gcr/gcr-gnupg-collection.h"
+
+#include "egg/egg-testing.h"
+
+#include <glib.h>
+
+#include <errno.h>
+#include <string.h>
+
+#if 0
+typedef struct {
+
+} Test;
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+
+}
+
+static void
+teardown (Test *test, gconstpointer unused)
+{
+
+}
+#endif
+
+static void
+test_create (void)
+{
+	GcrCollection *collection;
+
+	collection = _gcr_gnupg_collection_new ("files/gnupg-homedir/");
+
+	g_object_unref (collection);
+}
+
+
+int
+main (int argc, char **argv)
+{
+	const gchar *srcdir;
+
+	g_type_init ();
+	g_test_init (&argc, &argv, NULL);
+
+	srcdir = g_getenv ("SRCDIR");
+	if (srcdir && chdir (srcdir) < 0)
+		g_error ("couldn't change directory to: %s: %s", srcdir, g_strerror (errno));
+
+	g_test_add_func ("/gcr/gnupg-collection/create", test_create);
+#if 0
+	g_test_add ("/gcr/certificate/issuer_dn", Test, NULL, setup, test_issuer_dn, teardown);
+#endif
+
+	return g_test_run ();
+}
diff --git a/gcr/tests/test-gnupg-key.c b/gcr/tests/test-gnupg-key.c
new file mode 100644
index 0000000..5f1efac
--- /dev/null
+++ b/gcr/tests/test-gnupg-key.c
@@ -0,0 +1,132 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+   Copyright (C) 2011 Collabora Ltd
+
+   The Gnome Keyring Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Keyring 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  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/gcr-colons.h"
+#include "gcr/gcr-gnupg-key.h"
+
+#include "egg/egg-testing.h"
+
+#include <glib.h>
+
+#include <errno.h>
+#include <string.h>
+
+typedef struct {
+	GPtrArray *dataset;
+	GcrGnupgKey *key;
+} Test;
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+	GPtrArray *dataset;
+
+	dataset = g_ptr_array_new_with_free_func (_gcr_colons_free);
+	g_ptr_array_add (dataset, _gcr_colons_parse ("pub:f:1024:17:6C7EE1B8621CC013:899817715:1055898235::m:::scESC:", -1));
+	g_ptr_array_add (dataset, _gcr_colons_parse ("fpr:::::::::ECAF7590EB3443B5C7CF3ACB6C7EE1B8621CC013:", -1));
+	g_ptr_array_add (dataset, _gcr_colons_parse ("uid:f::::::::Werner Koch <wk@g10code.com>:\n", -1));
+	g_ptr_array_add (dataset, _gcr_colons_parse ("uid:f::::::::Werner Koch <wk@gnupg.org>:\n", -1));
+	g_ptr_array_add (dataset, _gcr_colons_parse ("sub:f:1536:16:06AD222CADF6A6E1:919537416:1036177416:::::e:\n", -1));
+	g_ptr_array_add (dataset, _gcr_colons_parse ("fpr:::::::::CF8BCC4B18DE08FCD8A1615906AD222CADF6A6E1:\n", -1));
+	g_ptr_array_add (dataset, _gcr_colons_parse ("sub:r:1536:20:5CE086B5B5A18FF4:899817788:1025961788:::::esc:\n", -1));
+	g_ptr_array_add (dataset, _gcr_colons_parse ("fpr:::::::::AB059359A3B81F410FCFF97F5CE086B5B5A18FF4:", -1));
+
+	test->key = _gcr_gnupg_key_new (dataset);
+	test->dataset = dataset;
+}
+
+static void
+teardown (Test *test, gconstpointer unused)
+{
+	g_object_unref (test->key);
+	g_ptr_array_unref (test->dataset);
+}
+
+static void
+test_label (Test *test, gconstpointer unused)
+{
+	gchar *label;
+
+	g_object_get (test->key, "label", &label, NULL);
+	g_assert_cmpstr (label, ==, "Werner Koch <wk@g10code.com>");
+
+	g_free (label);
+}
+
+static void
+test_markup (Test *test, gconstpointer unused)
+{
+	gchar *markup;
+
+	g_object_get (test->key, "markup", &markup, NULL);
+	g_assert_cmpstr (markup, ==, "Werner Koch &lt;wk@g10code.com&gt;");
+
+	g_free (markup);
+}
+
+static void
+test_description (Test *test, gconstpointer unused)
+{
+	gchar *description;
+
+	g_object_get (test->key, "description", &description, NULL);
+	g_assert_cmpstr (description, ==, "PGP Key");
+
+	g_free (description);
+}
+
+static void
+test_dataset (Test *test, gconstpointer unused)
+{
+	GPtrArray *dataset;
+
+	g_object_get (test->key, "dataset", &dataset, NULL);
+	g_assert (dataset == test->dataset);
+
+	g_ptr_array_unref (dataset);
+}
+
+static void
+test_keyid_for_colons (Test *test, gconstpointer unused)
+{
+	const gchar *keyid;
+
+	keyid = _gcr_gnupg_key_get_keyid_for_colons (test->dataset);
+	g_assert_cmpstr (keyid, ==, "6C7EE1B8621CC013");
+}
+
+int
+main (int argc, char **argv)
+{
+	g_type_init ();
+	g_test_init (&argc, &argv, NULL);
+
+	g_test_add ("/gcr/gnupg-key/label", Test, NULL, setup, test_label, teardown);
+	g_test_add ("/gcr/gnupg-key/description", Test, NULL, setup, test_description, teardown);
+	g_test_add ("/gcr/gnupg-key/markup", Test, NULL, setup, test_markup, teardown);
+	g_test_add ("/gcr/gnupg-key/dataset", Test, NULL, setup, test_dataset, teardown);
+	g_test_add ("/gcr/gnupg-key/keyid_for_colons", Test, NULL, setup, test_keyid_for_colons, teardown);
+
+	return g_test_run ();
+}
diff --git a/gcr/tests/test-util.c b/gcr/tests/test-util.c
new file mode 100644
index 0000000..015424f
--- /dev/null
+++ b/gcr/tests/test-util.c
@@ -0,0 +1,112 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+   Copyright (C) 2011 Collabora Ltd.
+
+   The Gnome Keyring Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Keyring 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  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.h"
+#include "gcr/gcr-util.h"
+
+#include <errno.h>
+
+static void
+on_line_parsed_match_template (const gchar *line, gpointer user_data)
+{
+	const gchar ***matching = user_data;
+
+	g_assert (matching);
+	g_assert (*matching);
+
+	/* Must be another line to match */
+	g_assert ((*matching)[0]);
+
+	/* Match this line against expected, and increment to next */
+	g_assert_cmpstr ((*matching)[0], ==, line);
+	(*matching)++;
+}
+
+static void
+test_parse_lines (void)
+{
+	GString *string = g_string_new ("first line\nsecond line\n\nlast line");
+	const gchar *matches[] = { "first line", "second line", "", NULL };
+	const gchar **matching = matches;
+
+	_gcr_util_parse_lines (string, FALSE, on_line_parsed_match_template, &matching);
+
+	/* All lines should have matched */
+	g_assert (*matching == NULL);
+
+	/* The last line should still be here */
+	g_assert_cmpstr (string->str, ==, "last line");
+	g_string_free (string, TRUE);
+}
+
+static void
+test_parse_lines_and_last (void)
+{
+	GString *string = g_string_new ("first line\nsecond line\n\nlast line");
+	const gchar *matches[] = { "first line", "second line", "", "last line", NULL };
+	const gchar **matching = matches;
+
+	_gcr_util_parse_lines (string, FALSE, on_line_parsed_match_template, &matching);
+	_gcr_util_parse_lines (string, TRUE, on_line_parsed_match_template, &matching);
+
+	/* All lines should have matched */
+	g_assert (*matching == NULL);
+
+	/* No more data */
+	g_assert_cmpstr (string->str, ==, "");
+	g_assert_cmpuint (string->len, ==, 0);
+	g_string_free (string, TRUE);
+}
+
+static void
+test_parse_lines_dos (void)
+{
+	GString *string = g_string_new ("first line\r\nsecond line\r\n\r\nlast line");
+	const gchar *matches[] = { "first line", "second line", "", "last line", NULL };
+	const gchar **matching = matches;
+
+	_gcr_util_parse_lines (string, FALSE, on_line_parsed_match_template, &matching);
+	_gcr_util_parse_lines (string, TRUE, on_line_parsed_match_template, &matching);
+
+	/* All lines should have matched */
+	g_assert (*matching == NULL);
+
+	/* No more data */
+	g_assert_cmpstr (string->str, ==, "");
+	g_assert_cmpuint (string->len, ==, 0);
+	g_string_free (string, TRUE);
+}
+
+int
+main (int argc, char **argv)
+{
+	g_type_init ();
+	g_test_init (&argc, &argv, NULL);
+
+	g_test_add_func ("/gcr/util/test_parse_lines", test_parse_lines);
+	g_test_add_func ("/gcr/util/test_parse_lines_and_last", test_parse_lines_and_last);
+	g_test_add_func ("/gcr/util/test_parse_lines_dos", test_parse_lines_dos);
+
+	return g_test_run ();
+}
