[gnome-keyring/gck-work: 18/18] [gck] Implement PkCS#11 URI's and enumeration



commit a9c7f91915decb9a1c326ae09f4ac64bd462d8b2
Author: Stef Walter <stef memberwebs com>
Date:   Mon Aug 9 20:16:05 2010 +0200

    [gck] Implement PkCS#11 URI's and enumeration
    
     * PKCS#11 URI spec is not completely finalized yet.
     * Removed old callback style enumeration and replaced with gio style.

 Makefile.am                     |    2 +-
 gck/Makefile.am                 |    3 +
 gck/gck-call.c                  |   18 +-
 gck/gck-enumerator.c            |  689 +++++++++++++++++++++++++++++++++++++++
 gck/gck-misc.c                  |    2 +-
 gck/gck-modules.c               |  166 +++++-----
 gck/gck-private.h               |   13 +-
 gck/gck-slot.c                  |   27 ++
 gck/gck-uri.c                   |  350 ++++++++++++++++++++
 gck/gck.h                       |  113 ++++++-
 gck/tests/Makefile.am           |    5 +-
 gck/tests/test-gck-enumerator.c |  170 ++++++++++
 gck/tests/test-gck-module.c     |    4 +
 gck/tests/test-gck-modules.c    |  145 ++++++++
 gck/tests/test-gck-slot.c       |   56 ++++
 gck/tests/test-gck-uri.c        |  229 +++++++++++++
 16 files changed, 1879 insertions(+), 113 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 88c56ce..f41c9de 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -12,9 +12,9 @@ endif
 
 SUBDIRS = \
 	. \
+	egg \
 	gck \
 	gp11 \
-	egg \
 	gcr \
 	ui \
 	pkcs11 \
diff --git a/gck/Makefile.am b/gck/Makefile.am
index a860f71..bf7533a 100644
--- a/gck/Makefile.am
+++ b/gck/Makefile.am
@@ -20,12 +20,14 @@ libgck_la_SOURCES = \
 	gck.h gck-private.h pkcs11.h \
 	gck-attributes.c \
 	gck-call.c \
+	gck-enumerator.c \
 	gck-misc.c \
 	gck-module.c \
 	gck-modules.c \
 	gck-object.c \
 	gck-session.c \
 	gck-slot.c \
+	gck-uri.c \
 	$(BUILT_SOURCES)
 
 libgck_la_LDFLAGS = \
@@ -33,6 +35,7 @@ libgck_la_LDFLAGS = \
 	-no-undefined -export-symbols-regex 'gck_*'
 
 libgck_la_LIBADD = \
+	$(top_builddir)/egg/libegg.la \
 	$(GOBJECT_LIBS) \
 	$(GTHREAD_LIBS) \
 	$(GIO_LIBS) \
diff --git a/gck/gck-call.c b/gck/gck-call.c
index 939d81b..6e46632 100644
--- a/gck/gck-call.c
+++ b/gck/gck-call.c
@@ -385,16 +385,18 @@ _gck_call_sync (gpointer object, gpointer perform, gpointer complete,
 	GckModule *module = NULL;
 	CK_RV rv;
 
-	g_assert (G_IS_OBJECT (object));
+	g_assert (!object || G_IS_OBJECT (object));
 	g_assert (perform);
 	g_assert (args);
 
-	g_object_get (object, "module", &module, "handle", &args->handle, NULL);
-	g_assert (GCK_IS_MODULE (module));
+	if (object) {
+		g_object_get (object, "module", &module, "handle", &args->handle, NULL);
+		g_assert (GCK_IS_MODULE (module));
 
-	/* We now hold a reference to module until below */
-	args->pkcs11 = gck_module_get_functions (module);
-	g_assert (args->pkcs11);
+		/* We now hold a reference to module until below */
+		args->pkcs11 = gck_module_get_functions (module);
+		g_assert (args->pkcs11);
+	}
 
 	do {
 		rv = perform_call (perform, cancellable, args);
@@ -403,7 +405,8 @@ _gck_call_sync (gpointer object, gpointer perform, gpointer complete,
 
 	} while (!complete_call (complete, args, rv));
 
-	g_object_unref (module);
+	if (module)
+		g_object_unref (module);
 
 	if (rv == CKR_OK)
 		return TRUE;
@@ -488,7 +491,6 @@ void
 _gck_call_async_go (GckCall *call)
 {
 	g_assert (GCK_IS_CALL (call));
-	g_assert (call->args->pkcs11);
 
 	/* To keep things balanced, process at one completed event */
 	process_completed(GCK_CALL_GET_CLASS (call));
diff --git a/gck/gck-enumerator.c b/gck/gck-enumerator.c
new file mode 100644
index 0000000..0a57f90
--- /dev/null
+++ b/gck/gck-enumerator.c
@@ -0,0 +1,689 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gck-enumerator.c - the GObject PKCS#11 wrapper library
+
+   Copyright (C) 2010, Stefan Walter
+
+   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 <nielsen memberwebs com>
+*/
+
+#include "config.h"
+
+#include "gck.h"
+#include "gck-private.h"
+
+#include <string.h>
+
+/**
+ * SECTION:gck-enumerator
+ * @title: GckEnumerator
+ * @short_description: Enumerates through PKCS#11 objects.
+ *
+ * Xxxxxx
+ */
+
+/**
+ * GckEnumerator:
+ *
+ * Xxxxxx
+ */
+
+typedef struct _GckEnumeratorState GckEnumeratorState;
+
+typedef gpointer (*GckEnumeratorFunc) (GckEnumeratorState *args, gboolean forward);
+
+struct _GckEnumeratorState {
+	/* For the current call */
+	gint want_objects;
+	gboolean want_password;
+
+	/* The state we're currently in */
+	GckEnumeratorFunc handler;
+
+	/* Input to enumerator */
+	GList *modules;
+	GckTokenInfo *match_token;
+	GckAttributes *match_attrs;
+	CK_FLAGS session_flags;
+	gchar *password;
+
+	/* state_module */
+	GckModule *module;
+
+	/* state_slots */
+	GList *slots;
+
+	/* state_slot */
+	GckSlot *slot;
+	GckTokenInfo *token_info;
+
+	/* state_session */
+	GckSession *session;
+
+	/* state_results */
+	GArray *objects;
+
+	/* Output from enumerator */
+	GList *results;
+};
+
+struct _GckEnumeratorPrivate {
+	/* Data here is set atomically */
+	gpointer state;
+	gint mode;
+};
+
+G_DEFINE_TYPE (GckEnumerator, gck_enumerator, G_TYPE_OBJECT);
+
+static gpointer state_start          (GckEnumeratorState *args, gboolean forward);
+static gpointer state_module         (GckEnumeratorState *args, gboolean forward);
+static gpointer state_slots          (GckEnumeratorState *args, gboolean forward);
+static gpointer state_slot           (GckEnumeratorState *args, gboolean forward);
+static gpointer state_session        (GckEnumeratorState *args, gboolean forward);
+static gpointer state_authenticated  (GckEnumeratorState *args, gboolean forward);
+static gpointer state_results        (GckEnumeratorState *args, gboolean forward);
+
+/* ----------------------------------------------------------------------------
+ * INTERNAL
+ */
+
+static gpointer
+rewind_state (GckEnumeratorState *args, GckEnumeratorFunc handler)
+{
+	g_assert (args);
+	g_assert (handler);
+	g_assert (args->handler);
+
+	while (handler != args->handler) {
+		args->handler = (args->handler) (args, FALSE);
+		g_assert (args->handler);
+	}
+
+	return handler;
+}
+
+static void
+cleanup_state (GckEnumeratorState *args)
+{
+	g_assert (args);
+
+	/* Have each state cleanup */
+	rewind_state (args, state_start);
+
+	/* state_module */
+	g_assert (!args->module);
+
+	/* state_slots */
+	g_assert (!args->slots);
+
+	/* state_slot */
+	g_assert (!args->slot);
+	g_assert (!args->token_info);
+
+	/* state_session */
+	g_assert (!args->session);
+
+	/* state_results */
+	if (args->objects)
+		g_array_free (args->objects, TRUE);
+	args->objects = NULL;
+
+	/* Other cleanup */
+	gck_list_unref_free (args->results);
+	args->results = NULL;
+
+	gck_list_unref_free (args->modules);
+	args->modules = NULL;
+
+	/* TODO: Can we use secure memory here? */
+	if (args->password) {
+		g_free (args->password);
+		args->password  = NULL;
+	}
+
+	gck_token_info_free (args->match_token);
+	args->match_token = NULL;
+
+	if (args->match_attrs) {
+		_gck_attributes_unlock (args->match_attrs);
+		gck_attributes_unref (args->match_attrs);
+	}
+}
+
+static gpointer
+state_start (GckEnumeratorState *args, gboolean forward)
+{
+	g_assert (args->module == NULL);
+
+	if (forward) {
+
+		/* There is no no more modules? */
+		if (!args->modules)
+			return NULL;
+
+		/* Pop off the current module */
+		args->module = args->modules->data;
+		g_assert (GCK_IS_MODULE (args->module));
+		args->modules = g_list_delete_link (args->modules, args->modules);
+		return state_module;
+	}
+
+	/* Should never be asked to go backward from start state */
+	g_assert_not_reached ();
+}
+
+static gpointer
+state_module (GckEnumeratorState *args, gboolean forward)
+{
+	g_assert (args->module);
+	g_assert (!args->slots);
+
+	/* module to slots state */
+	if (forward) {
+
+		args->slots = gck_module_get_slots (args->module, TRUE);
+		return state_slots;
+
+	/* module to start state */
+	} else {
+		g_object_unref (args->module);
+		args->module = NULL;
+		return state_start;
+	}
+}
+
+static gpointer
+state_slots (GckEnumeratorState *args, gboolean forward)
+{
+	GckSlot *slot;
+	GckTokenInfo *token_info;
+
+	g_assert (args->slot == NULL);
+
+	/* slots to slot state */
+	if (forward) {
+
+		/* If there are no more slots go back to start state */
+		if (!args->slots)
+			return rewind_state (args, state_start);
+
+		/* Pop the next slot off the stack */
+		slot = args->slots->data;
+		args->slots = g_list_delete_link (args->slots, args->slots);
+
+		token_info = gck_slot_get_token_info (slot);
+		if (!token_info) {
+			g_message ("couldn't get token info while enumerating");
+			g_object_unref (slot);
+			return rewind_state (args, state_start);
+		}
+
+		/* Are we trying to match the slot? */
+		if (args->match_token) {
+
+			/* No match? Go to next slot */
+			if (!_gck_token_info_match (args->match_token, token_info)) {
+				g_object_unref (slot);
+				gck_token_info_free (token_info);
+				return state_slots;
+			}
+		}
+
+		/* We have a slot */
+		args->slot = slot;
+		args->token_info = token_info;
+		return state_slot;
+
+	/* slots state to module state */
+	} else {
+
+		gck_list_unref_free (args->slots);
+		return state_module;
+	}
+}
+
+static gpointer
+state_slot (GckEnumeratorState *args, gboolean forward)
+{
+	CK_FUNCTION_LIST_PTR funcs;
+	CK_SESSION_HANDLE session;
+	CK_RV rv;
+
+	g_assert (args->slot);
+	g_assert (args->module);
+	g_assert (args->session == NULL);
+
+	/* slot to session state */
+	if (forward) {
+		funcs = gck_module_get_functions (args->module);
+		g_return_val_if_fail (funcs, NULL);
+
+		rv = (funcs->C_OpenSession) (gck_slot_get_handle (args->slot),
+		                                   args->session_flags, NULL, NULL, &session);
+		if (rv != CKR_OK) {
+			g_message ("couldn't open session on module while enumerating objects: %s",
+			           gck_message_from_rv (rv));
+			return rewind_state (args, state_slots);
+		}
+
+		args->session = gck_session_from_handle (args->slot, session);
+		return state_session;
+
+	/* slot to slots state */
+	} else {
+		g_object_unref (args->slot);
+		args->slot = NULL;
+
+		gck_token_info_free (args->token_info);
+		args->token_info = NULL;
+
+		return state_slots;
+	}
+}
+
+static gpointer
+state_session (GckEnumeratorState *args, gboolean forward)
+{
+	CK_FUNCTION_LIST_PTR funcs;
+	GckSessionInfo *sinfo;
+	CK_ULONG n_pin;
+	CK_RV rv;
+
+	g_assert (args->session);
+	g_assert (args->module);
+	g_assert (!args->want_password);
+	g_assert (args->token_info);
+
+	/* session to authenticated state */
+	if (forward) {
+
+		/* No login necessary */
+		if ((args->token_info->flags & CKF_LOGIN_REQUIRED) == 0)
+			return state_authenticated;
+
+		/* Next check if session is logged in */
+		sinfo = gck_session_get_info (args->session);
+		if (sinfo == NULL) {
+			g_message ("couldn't get session info when enumerating");
+			return rewind_state (args, state_slots);
+		}
+
+		/* Already logged in? */
+		if (sinfo->state == CKS_RW_USER_FUNCTIONS ||
+		    sinfo->state == CKS_RO_USER_FUNCTIONS ||
+		    sinfo->state == CKS_RW_SO_FUNCTIONS) {
+			gck_session_info_free (sinfo);
+			return state_authenticated;
+		}
+
+		gck_session_info_free (sinfo);
+
+		funcs = gck_module_get_functions (args->module);
+		g_return_val_if_fail (funcs, NULL);
+
+		/* Try to log in */
+		n_pin = args->password ? strlen (args->password) : 0;
+		rv = (funcs->C_Login) (gck_session_get_handle (args->session), CKU_USER,
+		                       (CK_BYTE_PTR)args->password, n_pin);
+
+		/* Authentication failed can we ask for a password? */
+		if (rv == CKR_PIN_INCORRECT && gck_module_get_options (args->module) & GCK_AUTHENTICATE_TOKENS) {
+			args->want_password = TRUE;
+			return NULL;
+
+		/* Any other failure continue without authentication */
+		} else if (rv != CKR_OK) {
+			g_message ("couldn't authenticate when enumerating: %s", gck_message_from_rv (rv));
+		}
+
+		return state_authenticated;
+
+	/* Session to slot state */
+	} else {
+		g_object_unref (args->session);
+		args->session = NULL;
+		return state_slot;
+	}
+}
+
+static gpointer
+state_authenticated (GckEnumeratorState *args, gboolean forward)
+{
+	CK_FUNCTION_LIST_PTR funcs;
+	CK_OBJECT_HANDLE objects[128];
+	CK_SESSION_HANDLE session;
+	CK_ATTRIBUTE_PTR attrs;
+	CK_ULONG n_attrs, count;
+	CK_RV rv;
+
+	/* Just go back, no logout */
+	if (!forward)
+		return state_session;
+
+	/* This is where we do the actual searching */
+
+	g_assert (args->session);
+	g_assert (!args->want_password);
+	g_assert (args->want_objects);
+
+	if (args->match_attrs) {
+		attrs = _gck_attributes_commit_out (args->match_attrs, &n_attrs);
+	} else {
+		attrs = NULL;
+		n_attrs = 0;
+	}
+
+	funcs = gck_module_get_functions (args->module);
+	g_return_val_if_fail (funcs, NULL);
+
+	session = gck_session_get_handle (args->session);
+	g_return_val_if_fail (session, NULL);
+
+	/* Get all the objects */
+	rv = (funcs->C_FindObjectsInit) (session, attrs, n_attrs);
+
+	if (rv == CKR_OK) {
+		while (rv == CKR_OK) {
+			rv = (funcs->C_FindObjects) (session, objects, G_N_ELEMENTS (objects), &count);
+
+			if (count == 0)
+				break;
+
+			if (!args->objects)
+				args->objects = g_array_new (FALSE, TRUE, sizeof (CK_OBJECT_HANDLE));
+			g_array_append_vals (args->objects, objects, count);
+		}
+
+		(funcs->C_FindObjectsFinal) (session);
+	}
+
+	return state_results;
+}
+
+static gpointer
+state_results (GckEnumeratorState *args, gboolean forward)
+{
+	CK_OBJECT_HANDLE handle;
+	GckObject *object;
+	guint have;
+
+	g_assert (args->session);
+
+	/* No cleanup, just unwind */
+	if (!forward)
+		return state_authenticated;
+
+	/* Create result objects from what we have */
+	have = g_list_length (args->results);
+
+	while (have < args->want_objects) {
+
+		/* Need more objects! */
+		if (!args->objects || args->objects->len == 0)
+			return rewind_state (args, state_slots);
+
+		handle = g_array_index (args->objects, CK_OBJECT_HANDLE, 0);
+		g_array_remove_index_fast (args->objects, 0);
+
+		object = gck_object_from_handle (args->session, handle);
+		args->results = g_list_append (args->results, object);
+		++have;
+	}
+
+	/* We got all the results we wanted */
+	return NULL;
+}
+
+/* ----------------------------------------------------------------------------
+ * OBJECT
+ */
+
+static void
+gck_enumerator_init (GckEnumerator *self)
+{
+	GckEnumeratorState *args;
+
+	self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCK_TYPE_ENUMERATOR, GckEnumeratorPrivate);
+	args = g_new0 (GckEnumeratorState, 1);
+	args->handler = state_start;
+	g_atomic_pointer_set (&self->pv->state, args);
+}
+
+static void
+gck_enumerator_finalize (GObject *obj)
+{
+	GckEnumerator *self = GCK_ENUMERATOR (obj);
+	GckEnumeratorState *state = g_atomic_pointer_get (&self->pv->state);
+
+	if (!g_atomic_pointer_compare_and_exchange (&self->pv->state, state, NULL))
+		g_assert_not_reached ();
+
+	g_assert (state);
+	cleanup_state (state);
+	g_free (state);
+
+	G_OBJECT_CLASS (gck_enumerator_parent_class)->finalize (obj);
+}
+
+
+static void
+gck_enumerator_class_init (GckEnumeratorClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	gck_enumerator_parent_class = g_type_class_peek_parent (klass);
+
+	gobject_class->finalize = gck_enumerator_finalize;
+	g_type_class_add_private (klass, sizeof (GckEnumeratorPrivate));
+}
+
+/* ----------------------------------------------------------------------------
+ * PUBLIC
+ */
+
+GckEnumerator*
+_gck_enumerator_new (GList *modules, guint session_flags, GckTokenInfo *match_token, GckAttributes *match_attrs)
+{
+	GckEnumerator *self;
+	GckEnumeratorState *state;
+
+	self = g_object_new (GCK_TYPE_ENUMERATOR, NULL);
+	state = g_atomic_pointer_get (&self->pv->state);
+
+	state->session_flags = session_flags | CKF_SERIAL_SESSION;
+	state->modules = gck_list_ref_copy (modules);
+
+	if (match_attrs) {
+		state->match_attrs = gck_attributes_ref (match_attrs);
+		_gck_attributes_lock (state->match_attrs);
+	}
+	state->match_token = match_token;
+
+	return self;
+}
+
+typedef struct _EnumerateNext {
+	GckArguments base;
+	GckEnumeratorState *state;
+} EnumerateNext;
+
+static CK_RV
+perform_enumerate_next (EnumerateNext *args)
+{
+	GckEnumeratorFunc handler;
+	GckEnumeratorState *state;
+
+	g_assert (args->state);
+	state = args->state;
+
+	g_assert (!state->want_password);
+	g_assert (state->handler);
+
+	for (;;) {
+		handler = (state->handler) (state, TRUE);
+		if (!handler)
+			break;
+		state->handler = handler;
+	}
+
+	/* TODO: In some modes, errors */
+	return CKR_OK;
+}
+
+static gboolean
+complete_enumerate_next (EnumerateNext *args, CK_RV result)
+{
+	GckEnumeratorState *state;
+	gboolean ret = TRUE;
+
+	g_assert (args->state);
+	state = args->state;
+
+	if (state->want_password) {
+		g_assert (state->module);
+		g_assert (state->slot);
+
+		/* TODO: Should we be using secure memory here? */
+		g_free (state->password);
+		state->password = NULL;
+
+		ret = _gck_module_fire_authenticate_slot (state->module, state->slot, NULL, &state->password);
+
+		/* If authenticate returns TRUE then call is not complete */
+		ret = !ret;
+	}
+
+	return ret;
+}
+
+static void
+free_enumerate_next (EnumerateNext *args)
+{
+	/* Should have been assigned back to enumerator */
+	g_assert (!args->state);
+
+	g_free (args);
+}
+
+GckObject*
+gck_enumerator_next (GckEnumerator *self, GCancellable *cancellable, GError **error)
+{
+	GckObject *result = NULL;
+	GList *results;
+
+	g_return_val_if_fail (GCK_IS_ENUMERATOR (self), NULL);
+	g_return_val_if_fail (!error || !*error, NULL);
+
+	results = gck_enumerator_next_n (self, 1, cancellable, error);
+	if (results) {
+		g_assert (GCK_IS_OBJECT (results->data));
+		result = g_object_ref (results->data);
+		gck_list_unref_free (results);
+	}
+
+	return result;
+}
+
+GList*
+gck_enumerator_next_n (GckEnumerator *self, gint max_objects, GCancellable *cancellable,
+                       GError **error)
+{
+	EnumerateNext args = { GCK_ARGUMENTS_INIT, NULL, };
+	GList *results = NULL;
+
+	g_return_val_if_fail (GCK_IS_ENUMERATOR (self), NULL);
+	g_return_val_if_fail (max_objects == -1 || max_objects > 0, NULL);
+	g_return_val_if_fail (!error || !*error, NULL);
+
+	/* Remove the state and own it ourselves */
+	args.state = g_atomic_pointer_get (&self->pv->state);
+	if (!args.state || !g_atomic_pointer_compare_and_exchange (&self->pv->state, args.state, NULL)) {
+		g_warning ("this enumerator is already running a next operation");
+		return NULL;
+	}
+
+	args.state->want_objects = max_objects <= 0 ? G_MAXINT : max_objects;
+
+	/* Run the operation and steal away the results */
+	if (_gck_call_sync (NULL, perform_enumerate_next, complete_enumerate_next, &args, cancellable, error)) {
+		results = args.state->results;
+		args.state->results = NULL;
+	}
+
+	args.state->want_objects = 0;
+
+	/* Put the state back */
+	if (!g_atomic_pointer_compare_and_exchange (&self->pv->state, NULL, args.state))
+		g_assert_not_reached ();
+
+	return results;
+}
+
+void
+gck_enumerator_next_async (GckEnumerator *self, gint max_objects, GCancellable *cancellable,
+                           GAsyncReadyCallback callback, gpointer user_data)
+{
+	GckEnumeratorState *state;
+	EnumerateNext *args;
+
+	g_return_if_fail (GCK_IS_ENUMERATOR (self));
+	g_return_if_fail (max_objects == -1 || max_objects > 0);
+
+	g_object_ref (self);
+
+	/* Remove the state and own it ourselves */
+	state = g_atomic_pointer_get (&self->pv->state);
+	if (!state || !g_atomic_pointer_compare_and_exchange (&self->pv->state, state, NULL)) {
+		g_warning ("this enumerator is already running a next operation");
+		return;
+	}
+
+	state->want_objects = max_objects <= 0 ? G_MAXINT : max_objects;
+	args =  _gck_call_async_prep (NULL, self, perform_enumerate_next, complete_enumerate_next,
+	                               sizeof (*args), free_enumerate_next);
+
+	args->state = state;
+	_gck_call_async_ready_go (args, cancellable, callback, user_data);
+	g_object_unref (self);
+}
+
+GList*
+gck_enumerator_next_finish (GckEnumerator *self, GAsyncResult *result, GError **error)
+{
+	EnumerateNext *args;
+	GckEnumeratorState *state;
+	GList *results = NULL;
+
+	g_object_ref (self);
+
+	args = _gck_call_arguments (result, EnumerateNext);
+	state = args->state;
+	args->state = NULL;
+	state->want_objects = 0;
+
+	if (_gck_call_basic_finish (result, error)) {
+		results = state->results;
+		state->results = NULL;
+	}
+
+	/* Put the state back */
+	if (!g_atomic_pointer_compare_and_exchange (&self->pv->state, NULL, state))
+		g_assert_not_reached ();
+
+	g_object_unref (self);
+
+	return results;
+}
diff --git a/gck/gck-misc.c b/gck/gck-misc.c
index ae8c66d..6fc691b 100644
--- a/gck/gck-misc.c
+++ b/gck/gck-misc.c
@@ -26,7 +26,7 @@
 #include "gck.h"
 #include "gck-private.h"
 
-#include <glib/gi18n.h>
+#include <glib/gi18n-lib.h>
 
 /**
  * SECTION:gck-error
diff --git a/gck/gck-modules.c b/gck/gck-modules.c
index c27062d..38ff1fc 100644
--- a/gck/gck-modules.c
+++ b/gck/gck-modules.c
@@ -1,7 +1,7 @@
 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
 /* gck-modules.c - the GObject PKCS#11 wrapper library
 
-   Copyright (C) 2008, Stefan Walter
+   Copyright (C) 2010, Stefan Walter
 
    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
@@ -18,7 +18,7 @@
    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.
 
-   Author: Stef Walter <nielsen memberwebs com>
+   Author: Stef Walter <stef memberwebs com>
 */
 
 #include "config.h"
@@ -120,101 +120,101 @@ gck_modules_get_slots (GList *modules, gboolean token_present)
  * @self: The module to enumerate objects.
  * @attrs: Attributes that the objects must have, or empty for all objects.
  * @session_flags: PKCS#11 flags for opening a session.
- * @cancellable: Optional cancellation object, or NULL.
- * @func: Function to call for each object.
- * @user_data: Data to pass to the function.
- * @error: Location to return error information.
  *
- * Call a function for every matching object on the module. This call may
- * block for an indefinite period.
+ * Setup an enumerator for listing matching objects on the modules.
  *
- * This function will open a session per slot. It's recommended that you
- * set the 'reuse-sessions' property on each slot if you'll be calling
- * it a lot.
+ * This call will not block but will return an enumerator immediately.
  *
- * You can access the session in which the object was found, by using the
- * gck_object_get_session() function on the resulting objects.
+ * XXX
  *
- * The function can return FALSE to stop the enumeration.
- *
- * Return value: If FALSE then an error prevented all matching objects from being enumerated.
+ * Return value: A new enumerator
  **/
-gboolean
-gck_modules_enumerate_objects (GList *modules, GckAttributes *attrs, guint session_flags,
-                               GCancellable *cancellable, GckObjectForeachFunc func,
-                               gpointer user_data, GError **err)
+GckEnumerator*
+gck_modules_enumerate_objects (GList *modules, GckAttributes *attrs, guint session_flags)
 {
-	gboolean stop = FALSE;
-	gboolean ret = TRUE;
-	GList *objects, *o;
-	GList *slots, *l, *m;
-	GError *error = NULL;
-	GckSession *session;
+	return _gck_enumerator_new (modules, session_flags, NULL, attrs);
+}
 
-	g_return_val_if_fail (attrs, FALSE);
-	g_return_val_if_fail (func, FALSE);
+GckSlot*
+gck_modules_token_for_uri (GList *modules, const gchar *uri, GError **error)
+{
+	GckTokenInfo *match, *token;
+	GckSlot *result = NULL;
+	GList *slots;
+	GList *m, *s;
 
-	gck_attributes_ref (attrs);
+	if (!gck_uri_parse (uri, &match, NULL, error))
+		return NULL;
 
-	for (m = modules; ret && !stop && m; m = g_list_next (m)) {
+	for (m = modules; result == NULL && m != NULL; m = g_list_next (m)) {
 		slots = gck_module_get_slots (m->data, TRUE);
-
-		for (l = slots; ret && !stop && l; l = g_list_next (l)) {
-
-			session = gck_slot_open_session (l->data, session_flags, &error);
-			if (!session) {
-				g_return_val_if_fail (error != NULL, FALSE);
-
-				/* Ignore these errors when enumerating */
-				if (g_error_matches (error, GCK_ERROR, CKR_USER_PIN_NOT_INITIALIZED)) {
-					g_clear_error (&error);
-
-				} else {
-					ret = FALSE;
-					g_propagate_error (err, error);
-					error = NULL;
-				}
-				continue;
-			}
-
-			objects = gck_session_find_objects (session, attrs, cancellable, &error);
-			if (error) {
-				ret = FALSE;
-				g_object_unref (session);
-				g_propagate_error (err, error);
-				error = NULL;
-				continue;
-			}
-
-			for (o = objects; !stop && o; o = g_list_next (o)) {
-				if (!(func)(o->data, user_data)) {
-					stop = TRUE;
-					break;
-				}
-			}
-
-			g_object_unref (session);
-			gck_list_unref_free (objects);
+		for (s = slots; result == NULL && s != NULL; s = g_list_next (s)) {
+			token = gck_slot_get_token_info (s->data);
+			if (token && _gck_token_info_match (match, token))
+				result = g_object_ref (s->data);
+			gck_token_info_free (token);
 		}
-
 		gck_list_unref_free (slots);
 	}
 
-	gck_attributes_unref (attrs);
+	gck_token_info_free (match);
+	return result;
+}
 
-	return ret;
+GckObject*
+gck_modules_object_for_uri (GList *modules, const gchar *uri, guint session_flags,
+                            GError **error)
+{
+	GckEnumerator *en;
+	GckObject *result;
+
+	g_return_val_if_fail (uri, NULL);
+	g_return_val_if_fail (!error || !*error, NULL);
+
+	en = gck_modules_enumerate_uri (modules, uri, session_flags, error);
+	if (en == NULL)
+		return NULL;
+
+	result = gck_enumerator_next (en, NULL, error);
+	g_object_unref (en);
+
+	return result;
 }
 
-/**
- * GckObjectForeachFunc:
- * @object: The enumerated object.
- * @user_data: Data passed to enumerate function.
- *
- * This function is passed to gck_module_enumerate_objects() or a similar function.
- * It is called once for each object matched.
- *
- * The GckSession through which the object is accessible can be retrieved by calling
- * gck_object_get_session() on object.
- *
- * Returns: TRUE to continue enumerating, FALSE to stop.
- */
+GList*
+gck_modules_objects_for_uri (GList *modules, const gchar *uri, guint session_flags,
+                             GError **error)
+{
+	GckEnumerator *en;
+	GList *results;
+
+	g_return_val_if_fail (uri, NULL);
+	g_return_val_if_fail (!error || !*error, NULL);
+
+	en = gck_modules_enumerate_uri (modules, uri, session_flags, error);
+	if (en == NULL)
+		return NULL;
+
+	results = gck_enumerator_next_n (en, -1, NULL, error);
+	g_object_unref (en);
+
+	return results;
+}
+
+GckEnumerator*
+gck_modules_enumerate_uri (GList *modules, const gchar *uri, guint session_flags,
+                           GError **error)
+{
+	GckTokenInfo *token;
+	GckAttributes *attrs;
+	GckEnumerator *en;
+
+	if (!gck_uri_parse (uri, &token, &attrs, error))
+		return NULL;
+
+	/* Takes ownership of token info */
+	en = _gck_enumerator_new (modules, session_flags, token, attrs);
+	gck_attributes_unref (attrs);
+
+	return en;
+}
diff --git a/gck/gck-private.h b/gck/gck-private.h
index 5a337cf..4a4aa9d 100644
--- a/gck/gck-private.h
+++ b/gck/gck-private.h
@@ -72,12 +72,21 @@ gboolean            _gck_module_fire_authenticate_object   (GckModule *module,
                                                              gchar *label,
                                                              gchar **password);
 
+/* -----------------------------------------------------------------------------
+ * ENUMERATOR
+ */
+
+GckEnumerator*      _gck_enumerator_new                     (GList *modules,
+                                                             guint session_flags,
+                                                             GckTokenInfo *match_token,
+                                                             GckAttributes *match_attrs);
+
 /* ----------------------------------------------------------------------------
  * SLOT
  */
 
-GckObject*         _gck_slot_object_from_handle           (GckSlot *slot,
-                                                             CK_OBJECT_HANDLE handle);
+gboolean           _gck_token_info_match                    (GckTokenInfo *match,
+                                                             GckTokenInfo *info);
 
 /* ----------------------------------------------------------------------------
  * CALL
diff --git a/gck/gck-slot.c b/gck/gck-slot.c
index 2b0aec9..48c3f17 100644
--- a/gck/gck-slot.c
+++ b/gck/gck-slot.c
@@ -300,6 +300,33 @@ gck_token_info_free (GckTokenInfo *token_info)
 	g_free (token_info);
 }
 
+static gboolean
+match_token_string (const gchar *match, const gchar *string)
+{
+	/* NULL matches anything */
+	if (match == NULL)
+		return TRUE;
+
+	if (string == NULL)
+		return FALSE;
+
+	return g_str_equal (match, string);
+}
+
+gboolean
+_gck_token_info_match (GckTokenInfo *match, GckTokenInfo *info)
+{
+	/* Matches two GckTokenInfo for use in PKCS#11 URI's */
+
+	g_return_val_if_fail (match, FALSE);
+	g_return_val_if_fail (info, FALSE);
+
+	return (match_token_string (match->label, info->label) &&
+	        match_token_string (match->manufacturer_id, info->manufacturer_id) &&
+	        match_token_string (match->model, info->model) &&
+	        match_token_string (match->serial_number, info->serial_number));
+}
+
 /**
  * GckMechanismInfo:
  * @min_key_size: The minimum key size that can be used with this mechanism.
diff --git a/gck/gck-uri.c b/gck/gck-uri.c
new file mode 100644
index 0000000..be6e6a1
--- /dev/null
+++ b/gck/gck-uri.c
@@ -0,0 +1,350 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gck-uri.c - the GObject PKCS#11 wrapper library
+
+   Copyright (C) 2010, Stefan Walter
+
+   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 <stef memberwebs com>
+*/
+
+#include "config.h"
+
+#include "gck.h"
+#include "gck-private.h"
+#include "gck-marshal.h"
+
+#include <glib/gi18n-lib.h>
+
+#include <string.h>
+
+#include "egg/egg-hex.h"
+
+/**
+ * SECTION:gck-modules
+ * @title: GckModule lists
+ * @short_description: Dealing with lists of PKCS#11 modules.
+ *
+ * Xxxxx
+ */
+
+#define URI_PREFIX "pkcs11:"
+#define N_URI_PREFIX 7
+
+GQuark
+gck_uri_get_error_quark (void)
+{
+	static GQuark domain = 0;
+	static volatile gsize quark_inited = 0;
+
+	if (g_once_init_enter (&quark_inited)) {
+		domain = g_quark_from_static_string ("gck-uri-error");
+		g_once_init_leave (&quark_inited, 1);
+	}
+
+	return domain;
+}
+
+static gint
+parse_string_attribute (const gchar *name, const gchar *start, const gchar *end,
+                        GckAttributes *attrs, GError **error)
+{
+	gchar *value;
+	gint res = 0;
+
+	g_assert (name);
+	g_assert (start);
+	g_assert (end);
+
+	if (!g_str_equal (name, "object") && !g_str_equal (name, "objecttype"))
+		return 0;
+
+	value = g_uri_unescape_segment (start, end, "");
+	if (value == NULL) {
+		g_set_error (error, GCK_URI_ERROR, GCK_URI_BAD_ENCODING,
+		             _("The URI has invalid syntax. The '%s' field encoding is invalid."), name);
+		return -1;
+	}
+
+	if (g_str_equal (name, "object")) {
+		gck_attributes_add_string (attrs, CKA_LABEL, value);
+		res = 1;
+
+	} else if (g_str_equal (name, "objecttype")) {
+
+		res = 1;
+		if (g_str_equal (value, "cert"))
+			gck_attributes_add_ulong (attrs, CKA_CLASS, CKO_CERTIFICATE);
+		else if (g_str_equal (value, "public"))
+			gck_attributes_add_ulong (attrs, CKA_CLASS, CKO_PUBLIC_KEY);
+		else if (g_str_equal (value, "private"))
+			gck_attributes_add_ulong (attrs, CKA_CLASS, CKO_PRIVATE_KEY);
+		else if (g_str_equal (value, "secretkey"))
+			gck_attributes_add_ulong (attrs, CKA_CLASS, CKO_SECRET_KEY);
+		else if (g_str_equal (value, "data"))
+			gck_attributes_add_ulong (attrs, CKA_CLASS, CKO_DATA);
+		else {
+			g_message ("ignoring unsupported value for '%s'", value);
+			res = 0;
+		}
+	} else {
+		g_assert_not_reached ();
+	}
+
+	g_free (value);
+	return res;
+}
+
+static gint
+parse_binary_attribute (const gchar *name, const gchar *start, const gchar *end,
+                        GckAttributes *attrs, GError **error)
+{
+	guchar *data;
+	gsize n_data;
+
+	g_assert (name);
+	g_assert (start);
+	g_assert (end);
+	g_assert (attrs);
+
+	if (!g_str_equal (name, "id"))
+		return 0;
+
+	/*
+	 * TODO: This requires some work. We're not yet sure about the actual
+	 * encoding that's supported here.
+	 */
+
+	g_assert (end >= start);
+	data = egg_hex_decode_full (start, end - start, ':', 1, &n_data);
+	if (data == NULL) {
+		g_set_error (error, GCK_URI_ERROR, GCK_URI_BAD_ENCODING,
+		             _("The URI has invalid syntax. The '%s' field encoding is invalid."), name);
+		return -1;
+	}
+
+	gck_attributes_add_data (attrs, CKA_ID, data, n_data);
+	g_free (data);
+	return 1;
+}
+
+static gint
+parse_token_attribute (const gchar *name, const gchar *start, const gchar *end,
+                       GckTokenInfo *token, GError **error)
+{
+	gchar **value;
+	gchar *string;
+
+	g_assert (name);
+	g_assert (start);
+	g_assert (end);
+	g_assert (token);
+
+	if (g_str_equal (name, "model"))
+		value = &(token->model);
+	else if (g_str_equal (name, "manufacturer"))
+		value = &(token->manufacturer_id);
+	else if (g_str_equal (name, "serial"))
+		value = &(token->serial_number);
+	else if (g_str_equal (name, "token"))
+		value = &(token->label);
+	else
+		return 0;
+
+	string = g_uri_unescape_segment (start, end, "");
+	if (string == NULL) {
+		g_set_error (error, GCK_URI_ERROR, GCK_URI_BAD_ENCODING,
+		             _("The URI has invalid syntax. The '%s' field encoding is invalid."), name);
+		return -1;
+	}
+
+	g_free (*value);
+	*value = string;
+
+	return 1;
+}
+
+gboolean
+gck_uri_parse (const gchar *uri, GckTokenInfo **token, GckAttributes **attrs, GError **error)
+{
+	GckAttributes *rattrs = NULL;
+	GckTokenInfo *rtoken = NULL;
+	const gchar *spos, *epos;
+	gchar *key = NULL;
+	gboolean ret = FALSE;
+	gint res;
+
+	g_return_val_if_fail (uri, FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+
+	if (!g_str_has_prefix (uri, URI_PREFIX)) {
+		g_set_error_literal (error, GCK_URI_ERROR, GCK_URI_BAD_PREFIX,
+		                     _("The URI has does not have the 'pkcs11' scheme."));
+		goto cleanup;
+	}
+
+	uri += N_URI_PREFIX;
+	rattrs = gck_attributes_new ();
+	rtoken = g_new0 (GckTokenInfo, 1);
+
+	for (;;) {
+		spos = strchr (uri, ';');
+		if (spos == NULL) {
+			spos = uri + strlen (uri);
+			g_assert (*spos == '\0');
+			if (spos == uri)
+				break;
+		}
+
+		epos = strchr (uri, '=');
+		if (epos == NULL || spos == uri || epos == uri || epos >= spos) {
+			g_set_error_literal (error, GCK_URI_ERROR, GCK_URI_BAD_SYNTAX,
+			                     "The URI has invalid syntax. It must consist of key=value pairs.");
+			goto cleanup;
+		}
+
+		g_free (key);
+		key = g_strndup (uri, epos - uri);
+		epos++;
+
+		res = parse_string_attribute (key, epos, spos, rattrs, error);
+		if (res == 0)
+			res = parse_binary_attribute (key, epos, spos, rattrs, error);
+		if (res == 0)
+			res = parse_token_attribute (key, epos, spos, rtoken, error);
+		if (res < 0)
+			goto cleanup;
+		if (res == 0)
+			g_message ("Ignoring unsupported field '%s'", key);
+
+		if (*spos == '\0')
+			break;
+		uri = spos + 1;
+	}
+
+	ret = TRUE;
+
+cleanup:
+	if (ret && token) {
+		*token = rtoken;
+		rtoken = NULL;
+	}
+	if (ret && attrs) {
+		*attrs = rattrs;
+		rattrs = NULL;
+	}
+
+	gck_token_info_free (rtoken);
+	if (rattrs)
+		gck_attributes_unref (rattrs);
+
+	g_free (key);
+	return ret;
+}
+
+static void
+build_string_attribute (const gchar *name, const gchar *value,
+                        GString *result, gboolean *first)
+{
+	gchar *segment;
+
+	g_assert (first);
+	g_assert (result);
+	g_assert (name);
+
+	if (!value)
+		return;
+	if (!value[0])
+		return;
+
+	segment = g_uri_escape_string (value, "", FALSE);
+	if (!*first)
+		g_string_append_c (result, ';');
+	*first = FALSE;
+
+	g_string_append (result, name);
+	g_string_append_c (result, '=');
+	g_string_append (result, segment);
+	g_free (segment);
+}
+
+static void
+build_binary_attribute (const gchar *name, gconstpointer data, gsize n_data,
+                        GString *result, gboolean *first)
+{
+	gchar *segment;
+
+	g_assert (first);
+	g_assert (result);
+	g_assert (name);
+
+	if (!n_data)
+		return;
+	g_assert (data);
+
+	segment = egg_hex_encode_full (data, n_data, FALSE, ':', 1);
+	if (!*first)
+		g_string_append_c (result, ';');
+	*first = FALSE;
+
+	g_string_append (result, name);
+	g_string_append_c (result, '=');
+	g_string_append (result, segment);
+	g_free (segment);
+}
+
+gchar*
+gck_uri_build (GckTokenInfo *token, GckAttributes *attrs)
+{
+	GckAttribute *attr;
+	GString *result;
+	gchar *value;
+	gulong klass;
+	gboolean first = TRUE;
+
+	result = g_string_new (URI_PREFIX);
+
+	if (token) {
+		build_string_attribute ("model", token->model, result, &first);
+		build_string_attribute ("manufacturer", token->manufacturer_id, result, &first);
+		build_string_attribute ("serial", token->serial_number, result, &first);
+		build_string_attribute ("token", token->label, result, &first);
+	}
+
+	if (attrs) {
+		if (gck_attributes_find_string (attrs, CKA_LABEL, &value)) {
+			build_string_attribute ("object", value, result, &first);
+			g_free (value);
+		}
+		if (gck_attributes_find_ulong (attrs, CKA_CLASS, &klass)) {
+			if (klass == CKO_CERTIFICATE)
+				build_string_attribute ("objecttype", "cert", result, &first);
+			else if (klass == CKO_PUBLIC_KEY)
+				build_string_attribute ("objecttype", "public", result, &first);
+			else if (klass == CKO_PRIVATE_KEY)
+				build_string_attribute ("objecttype", "private", result, &first);
+			else if (klass == CKO_SECRET_KEY)
+				build_string_attribute ("objecttype", "secretkey", result, &first);
+			else if (klass == CKO_DATA)
+				build_string_attribute ("objecttype", "data", result, &first);
+		}
+		attr = gck_attributes_find (attrs, CKA_ID);
+		if (attr != NULL)
+			build_binary_attribute ("id", attr->value, attr->length, result, &first);
+	}
+
+	return g_string_free (result, FALSE);
+}
diff --git a/gck/gck.h b/gck/gck.h
index d804071..a24cad7 100644
--- a/gck/gck.h
+++ b/gck/gck.h
@@ -226,8 +226,7 @@ typedef struct _GckSlot GckSlot;
 typedef struct _GckModule GckModule;
 typedef struct _GckSession GckSession;
 typedef struct _GckObject GckObject;
-
-typedef gboolean    (*GckObjectForeachFunc)                (GckObject *object, gpointer user_data);
+typedef struct _GckEnumerator GckEnumerator;
 
 /* -------------------------------------------------------------------------
  * MODULE
@@ -306,31 +305,75 @@ GList*                gck_modules_initialize_registered       (guint options);
 GList*                gck_modules_get_slots                   (GList *modules,
                                                                gboolean token_present);
 
-gboolean              gck_modules_enumerate_objects           (GList *modules,
+GckEnumerator*        gck_modules_enumerate_objects           (GList *modules,
                                                                GckAttributes *attrs,
+                                                               guint session_flags);
+
+GckSlot*              gck_modules_token_for_uri               (GList *modules,
+                                                               const gchar *uri,
+                                                               GError **error);
+
+GckObject*            gck_modules_object_for_uri              (GList *modules,
+                                                               const gchar *uri,
                                                                guint session_flags,
-                                                               GCancellable *cancellable,
-                                                               GckObjectForeachFunc func,
-                                                               gpointer user_data,
                                                                GError **error);
 
-#ifdef UNIMPLEMENTED
-void                  gck_modules_enumerate_objects_async     (GList *modules,
-                                                               GckAttributes *attrs,
+GList*                gck_modules_objects_for_uri             (GList *modules,
+                                                               const gchar *uri,
                                                                guint session_flags,
-                                                               GckObjectForeachFunc func,
+                                                               GError **error);
+
+GckEnumerator*        gck_modules_enumerate_uri               (GList *modules,
+                                                               const gchar *uri,
+                                                               guint session_flags,
+                                                               GError **error);
+
+
+/* ------------------------------------------------------------------------
+ * ENUMERATOR
+ */
+
+#define GCK_TYPE_ENUMERATOR             (gck_enumerator_get_type())
+#define GCK_ENUMERATOR(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), GCK_TYPE_ENUMERATOR, GckEnumerator))
+#define GCK_ENUMERATOR_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass), GCK_TYPE_ENUMERATOR, GckEnumerator))
+#define GCK_IS_ENUMERATOR(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj), GCK_TYPE_ENUMERATOR))
+#define GCK_IS_ENUMERATOR_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), GCK_TYPE_ENUMERATOR))
+#define GCK_ENUMERATOR_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS((obj), GCK_TYPE_ENUMERATOR, GckEnumeratorClass))
+
+typedef struct _GckEnumeratorClass GckEnumeratorClass;
+typedef struct _GckEnumeratorPrivate GckEnumeratorPrivate;
+
+struct _GckEnumerator {
+	GObject parent;
+	GckEnumeratorPrivate *pv;
+	gpointer reserved[2];
+};
+
+struct _GckEnumeratorClass {
+	GObjectClass parent;
+	gpointer reserved[2];
+};
+
+GType                 gck_enumerator_get_type                 (void) G_GNUC_CONST;
+
+GckObject*            gck_enumerator_next                     (GckEnumerator *self,
+                                                               GCancellable *cancellable,
+                                                               GError **err);
+
+GList*                gck_enumerator_next_n                   (GckEnumerator *self,
+                                                               gint max_objects,
+                                                               GCancellable *cancellable,
+                                                               GError **err);
+
+void                  gck_enumerator_next_async               (GckEnumerator *self,
+                                                               gint max_objects,
                                                                GCancellable *cancellable,
                                                                GAsyncReadyCallback callback,
                                                                gpointer user_data);
 
-GckObject*            gck_modules_enumerate_objects_next      (GList *modules,
+GList*                gck_enumerator_next_finish              (GckEnumerator *self,
                                                                GAsyncResult *res,
-                                                               GError **error);
-
-void                  gck_modules_enumerate_objects_finish    (GList *modules,
-                                                               GAsyncResult *res,
-                                                               GError **error);
-#endif
+                                                               GError **err);
 
 /* ------------------------------------------------------------------------
  * SLOT
@@ -1042,6 +1085,20 @@ CK_OBJECT_HANDLE    gck_object_get_handle                   (GckObject *self);
 
 GckSession*         gck_object_get_session                  (GckObject *self);
 
+gchar*              gck_object_build_uri                    (GckObject *self,
+                                                             guint options,
+                                                             GCancellable *cancellable,
+                                                             GError **err);
+
+void                gck_object_build_uri_async              (GckObject *self,
+                                                             guint options,
+                                                             GCancellable *cancellable,
+                                                             GError **err);
+
+gchar*              gck_object_build_uri_finish             (GckObject *self,
+                                                             GAsyncResult *result,
+                                                             GError **err);
+
 #ifdef UNIMPLEMENTED
 
 GckObject*          gck_object_copy                         (GckObject *self,
@@ -1198,6 +1255,28 @@ GckAttributes*      gck_object_get_template_finish          (GckObject *self,
                                                              GAsyncResult *result,
                                                              GError **err);
 
+/* ----------------------------------------------------------------------------
+ * URI
+ */
+
+enum {
+	GCK_URI_BAD_PREFIX = 1,
+	GCK_URI_BAD_ENCODING = 2,
+	GCK_URI_BAD_SYNTAX = 3
+};
+
+#define             GCK_URI_ERROR                           (gck_uri_get_error_quark ())
+
+GQuark              gck_uri_get_error_quark                 (void);
+
+gchar*              gck_uri_build                           (GckTokenInfo *token,
+                                                             GckAttributes *attrs);
+
+gboolean            gck_uri_parse                           (const gchar *uri,
+                                                             GckTokenInfo **token,
+                                                             GckAttributes **attrs,
+                                                             GError **err);
+
 G_END_DECLS
 
 #endif /* GCK_H */
diff --git a/gck/tests/Makefile.am b/gck/tests/Makefile.am
index 482f6b2..b2a319b 100644
--- a/gck/tests/Makefile.am
+++ b/gck/tests/Makefile.am
@@ -7,7 +7,10 @@ TESTING_FILES = \
 	test-gck-slot.c \
 	test-gck-session.c \
 	test-gck-object.c \
-	test-gck-crypto.c
+	test-gck-crypto.c \
+	test-gck-uri.c \
+	test-gck-enumerator.c \
+	test-gck-modules.c
 
 TESTING_FLAGS = \
 	-I$(top_srcdir)/gck/ \
diff --git a/gck/tests/test-gck-enumerator.c b/gck/tests/test-gck-enumerator.c
new file mode 100644
index 0000000..bcae887
--- /dev/null
+++ b/gck/tests/test-gck-enumerator.c
@@ -0,0 +1,170 @@
+
+#include <glib.h>
+#include <string.h>
+
+#include "test-suite.h"
+#include "gck-test.h"
+#include "gck-private.h"
+
+static GList *modules = NULL;
+
+DEFINE_SETUP(enumerator)
+{
+	GckModule *module;
+	GError *err = NULL;
+
+	/* Successful load */
+	module = gck_module_initialize (".libs/libgck-test-module.so", NULL, 0, &err);
+	SUCCESS_RES (module, err);
+
+	modules = g_list_append (NULL, module);
+}
+
+DEFINE_TEARDOWN(enumerator)
+{
+	gck_list_unref_free (modules);
+	modules = NULL;
+}
+
+DEFINE_TEST(enumerator_create)
+{
+	GckEnumerator *en;
+
+	en = _gck_enumerator_new (modules, 0, NULL, NULL);
+	g_assert (GCK_IS_ENUMERATOR (en));
+	g_object_unref (en);
+}
+
+DEFINE_TEST(enumerator_next)
+{
+	GError *error = NULL;
+	GckEnumerator *en;
+	GckObject *obj;
+
+	en = _gck_enumerator_new (modules, 0, NULL, NULL);
+	g_assert (GCK_IS_ENUMERATOR (en));
+
+	obj = gck_enumerator_next (en, NULL, &error);
+	g_assert (GCK_IS_OBJECT (obj));
+
+	g_object_unref (obj);
+	g_object_unref (en);
+}
+
+DEFINE_TEST(enumerator_next_and_resume)
+{
+	GError *error = NULL;
+	GckEnumerator *en;
+	GckObject *obj, *obj2;
+
+	en = _gck_enumerator_new (modules, 0, NULL, NULL);
+	g_assert (GCK_IS_ENUMERATOR (en));
+
+	obj = gck_enumerator_next (en, NULL, &error);
+	SUCCESS_RES (obj, error);
+	g_assert (GCK_IS_OBJECT (obj));
+
+	obj2 = gck_enumerator_next (en, NULL, &error);
+	SUCCESS_RES (obj2, error);
+	g_assert (GCK_IS_OBJECT (obj2));
+
+	g_assert (!gck_object_equal (obj, obj2));
+
+	g_object_unref (obj);
+	g_object_unref (obj2);
+	g_object_unref (en);
+}
+
+DEFINE_TEST(enumerator_next_n)
+{
+	GError *error = NULL;
+	GckEnumerator *en;
+	GList *objects, *l;
+
+	en = _gck_enumerator_new (modules, 0, NULL, NULL);
+	g_assert (GCK_IS_ENUMERATOR (en));
+
+	objects = gck_enumerator_next_n (en, -1, NULL, &error);
+	SUCCESS_RES (objects, error);
+	g_assert_cmpint (g_list_length (objects), ==, 5);
+	for (l = objects; l; l = g_list_next (l))
+		g_assert (GCK_IS_OBJECT (l->data));
+
+	gck_list_unref_free (objects);
+	g_object_unref (en);
+}
+
+static void
+fetch_async_result (GObject *source, GAsyncResult *result, gpointer user_data)
+{
+	*((GAsyncResult**)user_data) = result;
+	g_object_ref (result);
+	testing_wait_stop ();
+}
+
+DEFINE_TEST(enumerator_next_async)
+{
+	GAsyncResult *result = NULL;
+	GError *error = NULL;
+	GckEnumerator *en;
+	GList *objects, *l;
+
+	en = _gck_enumerator_new (modules, 0, NULL, NULL);
+	g_assert (GCK_IS_ENUMERATOR (en));
+
+	gck_enumerator_next_async (en, -1, NULL, fetch_async_result, &result);
+	testing_wait_until (500);
+	g_assert (result);
+
+	objects = gck_enumerator_next_finish (en, result, &error);
+	SUCCESS_RES (objects, error);
+	g_assert_cmpint (g_list_length (objects), ==, 5);
+	for (l = objects; l; l = g_list_next (l))
+		g_assert (GCK_IS_OBJECT (l->data));
+
+	g_object_unref (result);
+	gck_list_unref_free (objects);
+	g_object_unref (en);
+}
+
+DEFINE_TEST(enumerator_attributes)
+{
+	GckAttributes *attrs;
+	GError *error = NULL;
+	GckEnumerator *en;
+	GList *objects;
+
+	attrs = gck_attributes_new ();
+	gck_attributes_add_string (attrs, CKA_LABEL, "Private Capitalize Key");
+	en = _gck_enumerator_new (modules, 0, NULL, attrs);
+	g_assert (GCK_IS_ENUMERATOR (en));
+	gck_attributes_unref (attrs);
+
+	objects = gck_enumerator_next_n (en, -1, NULL, &error);
+	SUCCESS_RES (objects, error);
+	g_assert_cmpint (g_list_length (objects), ==, 1);
+	g_assert (GCK_IS_OBJECT (objects->data));
+
+	gck_list_unref_free (objects);
+	g_object_unref (en);
+}
+
+DEFINE_TEST(enumerator_token_match)
+{
+	GckTokenInfo *token;
+	GError *error = NULL;
+	GckEnumerator *en;
+	GList *objects;
+
+	token = g_new0 (GckTokenInfo, 1);
+	token->label = g_strdup ("Invalid token name");
+	en = _gck_enumerator_new (modules, 0, token, NULL);
+	g_assert (GCK_IS_ENUMERATOR (en));
+
+	objects = gck_enumerator_next_n (en, -1, NULL, &error);
+	g_assert_cmpint (g_list_length (objects), ==, 0);
+	g_assert (error == NULL);
+
+	gck_list_unref_free (objects);
+	g_object_unref (en);
+}
diff --git a/gck/tests/test-gck-module.c b/gck/tests/test-gck-module.c
index 67b2be3..7ad2356 100644
--- a/gck/tests/test-gck-module.c
+++ b/gck/tests/test-gck-module.c
@@ -86,6 +86,7 @@ DEFINE_TEST(module_info)
 	gck_module_info_free (info);
 }
 
+#if 0
 static int n_objects = 0;
 static GckObject *last_object = NULL;
 
@@ -120,9 +121,11 @@ for_first_object (GckObject *object, gpointer user_data)
 
 	return FALSE;
 }
+#endif
 
 DEFINE_TEST(module_enumerate)
 {
+#if 0
 	GckSession *session;
 	GckAttributes *attrs;
 	gboolean ret;
@@ -162,4 +165,5 @@ DEFINE_TEST(module_enumerate)
 	n_objects = 0;
 
 	gck_list_unref_free (modules);
+#endif
 }
diff --git a/gck/tests/test-gck-modules.c b/gck/tests/test-gck-modules.c
new file mode 100644
index 0000000..64ac992
--- /dev/null
+++ b/gck/tests/test-gck-modules.c
@@ -0,0 +1,145 @@
+
+#include <glib.h>
+#include <string.h>
+
+#include "test-suite.h"
+#include "gck-test.h"
+
+static GList *modules = NULL;
+
+DEFINE_SETUP(modules)
+{
+	GckModule *module;
+	GError *err = NULL;
+
+	/* Successful load */
+	module = gck_module_initialize (".libs/libgck-test-module.so", NULL, 0, &err);
+	SUCCESS_RES (module, err);
+
+	modules = g_list_append (NULL, module);
+}
+
+DEFINE_TEARDOWN(modules)
+{
+	gck_list_unref_free (modules);
+	modules = NULL;
+}
+
+DEFINE_TEST(modules_enumerate_objects)
+{
+	GckAttributes *attrs;
+	GError *error = NULL;
+	GckEnumerator *en;
+	GList *objects;
+
+	attrs = gck_attributes_new ();
+	gck_attributes_add_string (attrs, CKA_LABEL, "Private Capitalize Key");
+	en = gck_modules_enumerate_objects (modules, attrs, 0);
+	g_assert (GCK_IS_ENUMERATOR (en));
+	gck_attributes_unref (attrs);
+
+	objects = gck_enumerator_next_n (en, -1, NULL, &error);
+	SUCCESS_RES (objects, error);
+	g_assert_cmpint (g_list_length (objects), ==, 1);
+	g_assert (GCK_IS_OBJECT (objects->data));
+
+	gck_list_unref_free (objects);
+	g_object_unref (en);
+}
+
+
+DEFINE_TEST(modules_token_for_uri)
+{
+	GckSlot *slot;
+	GError *error = NULL;
+
+	slot = gck_modules_token_for_uri (modules, "pkcs11:token=TEST%20LABEL", &error);
+	g_assert (GCK_IS_SLOT (slot));
+
+	g_object_unref (slot);
+}
+
+DEFINE_TEST(modules_token_for_uri_not_found)
+{
+	GckSlot *slot;
+	GError *error = NULL;
+
+	slot = gck_modules_token_for_uri (modules, "pkcs11:token=UNKNOWN", &error);
+	g_assert (slot == NULL);
+	g_assert (error == NULL);
+}
+
+DEFINE_TEST(modules_token_for_uri_error)
+{
+	GckSlot *slot;
+	GError *error = NULL;
+
+	slot = gck_modules_token_for_uri (modules, "http://invalid.uri";, &error);
+	g_assert (slot == NULL);
+	g_assert (error != NULL);
+	g_assert (g_error_matches (error, GCK_URI_ERROR, GCK_URI_BAD_PREFIX));
+	g_error_free (error);
+}
+
+DEFINE_TEST(modules_object_for_uri)
+{
+	GckObject *object;
+	GError *error = NULL;
+
+	object = gck_modules_object_for_uri (modules, "pkcs11:object=Public%20Capitalize%20Key;objecttype=public", 0, &error);
+	g_assert (GCK_IS_OBJECT (object));
+	g_object_unref (object);
+}
+
+DEFINE_TEST(modules_object_for_uri_not_found)
+{
+	GckObject *object;
+	GError *error = NULL;
+
+	object = gck_modules_object_for_uri (modules, "pkcs11:object=Unknown%20Label", 0, &error);
+	g_assert (object == NULL);
+	g_assert (error == NULL);
+}
+
+DEFINE_TEST(modules_object_for_uri_error)
+{
+	GckObject *object;
+	GError *error = NULL;
+
+	object = gck_modules_object_for_uri (modules, "http://invalid.uri";, 0, &error);
+	g_assert (object == NULL);
+	g_assert (error != NULL);
+	g_assert (g_error_matches (error, GCK_URI_ERROR, GCK_URI_BAD_PREFIX));
+	g_error_free (error);
+}
+
+DEFINE_TEST(modules_objects_for_uri)
+{
+	GList *objects;
+	GError *error = NULL;
+
+	objects = gck_modules_objects_for_uri (modules, "pkcs11:token=TEST%20LABEL", 0, &error);
+	g_assert (objects);
+	g_assert (!error);
+	g_assert_cmpint (g_list_length (objects), ==, 5);
+
+	gck_list_unref_free (objects);
+}
+
+DEFINE_TEST(modules_enumerate_uri)
+{
+	GckEnumerator *en;
+	GList *objects;
+	GError *error = NULL;
+
+	en = gck_modules_enumerate_uri (modules, "pkcs11:token=TEST%20LABEL", 0, &error);
+	g_assert (GCK_IS_ENUMERATOR (en));
+	g_assert (!error);
+
+	objects = gck_enumerator_next_n (en, -1, NULL, &error);
+	g_assert_cmpint (g_list_length (objects), ==, 5);
+	g_assert (!error);
+
+	g_object_unref (en);
+	gck_list_unref_free (objects);
+}
diff --git a/gck/tests/test-gck-slot.c b/gck/tests/test-gck-slot.c
index 3e4bd64..8ea75f0 100644
--- a/gck/tests/test-gck-slot.c
+++ b/gck/tests/test-gck-slot.c
@@ -4,6 +4,7 @@
 
 #include "test-suite.h"
 #include "gck-test.h"
+#include "gck-private.h"
 
 static GckModule *module = NULL;
 static GckSlot *slot = NULL;
@@ -145,3 +146,58 @@ DEFINE_TEST(slot_mechanisms)
 
 	gck_mechanisms_free (mechs);
 }
+
+DEFINE_TEST(token_info_match_null)
+{
+	GckTokenInfo *match;
+	GckTokenInfo *token;
+	gboolean ret;
+
+	token = gck_slot_get_token_info (slot);
+	match = g_new0 (GckTokenInfo, 1);
+
+	/* Should match, since no fields are set */
+	ret = _gck_token_info_match (match, token);
+	g_assert (ret);
+
+	gck_token_info_free (match);
+	gck_token_info_free (token);
+}
+
+DEFINE_TEST(token_info_match_label)
+{
+	GckTokenInfo *match;
+	GckTokenInfo *token;
+	gboolean ret;
+
+	token = gck_slot_get_token_info (slot);
+	match = g_new0 (GckTokenInfo, 1);
+
+	/* Should match since the label and serial are matching */
+	match->label = g_strdup (token->label);
+	match->serial_number = g_strdup (token->serial_number);
+	ret = _gck_token_info_match (match, token);
+	g_assert (ret);
+
+	gck_token_info_free (match);
+	gck_token_info_free (token);
+}
+
+DEFINE_TEST(token_info_match_different)
+{
+	GckTokenInfo *match;
+	GckTokenInfo *token;
+	gboolean ret;
+
+	token = gck_slot_get_token_info (slot);
+	match = g_new0 (GckTokenInfo, 1);
+
+	/* Should not match since serial is different */
+	match->label = g_strdup (token->label);
+	match->serial_number = g_strdup ("393939393939393");
+	ret = _gck_token_info_match (match, token);
+	g_assert (!ret);
+
+	gck_token_info_free (match);
+	gck_token_info_free (token);
+}
diff --git a/gck/tests/test-gck-uri.c b/gck/tests/test-gck-uri.c
new file mode 100644
index 0000000..b129f13
--- /dev/null
+++ b/gck/tests/test-gck-uri.c
@@ -0,0 +1,229 @@
+
+#include <glib.h>
+#include <string.h>
+
+#include "test-suite.h"
+#include "gck-test.h"
+#include "gck-private.h"
+
+DEFINE_SETUP(uri)
+{
+
+}
+
+DEFINE_TEARDOWN(uri)
+{
+
+}
+
+DEFINE_TEST(uri_parse)
+{
+	GError *error = NULL;
+	if (!gck_uri_parse ("pkcs11:", NULL, NULL, &error))
+		g_assert_not_reached ();
+}
+
+DEFINE_TEST(uri_parse_bad_scheme)
+{
+	GError *error = NULL;
+	if (gck_uri_parse ("http:\\example.com\test", NULL, NULL, &error))
+		g_assert_not_reached ();
+	g_assert (g_error_matches (error, GCK_URI_ERROR, GCK_URI_BAD_PREFIX));
+	g_error_free (error);
+}
+
+DEFINE_TEST(uri_parse_with_label)
+{
+	GError *error = NULL;
+	GckAttributes *attrs;
+	gchar *value;
+
+	if (!gck_uri_parse ("pkcs11:object=Test%20Label", NULL, &attrs, &error))
+		g_assert_not_reached ();
+
+	if (!gck_attributes_find_string (attrs, CKA_LABEL, &value))
+		g_assert_not_reached ();
+
+	g_assert_cmpstr (value, ==, "Test Label");
+	g_free (value);
+}
+DEFINE_TEST(uri_parse_with_label_and_klass)
+{
+	GError *error = NULL;
+	GckAttributes *attrs;
+	gchar *value;
+	gulong klass;
+
+	if (!gck_uri_parse ("pkcs11:object=Test%20Label;objecttype=cert", NULL, &attrs, &error))
+		g_assert_not_reached ();
+
+	if (!gck_attributes_find_string (attrs, CKA_LABEL, &value))
+		g_assert_not_reached ();
+
+	if (!gck_attributes_find_ulong (attrs, CKA_CLASS, &klass))
+		g_assert_not_reached ();
+
+	g_assert_cmpstr (value, ==, "Test Label");
+	g_assert (klass == CKO_CERTIFICATE);
+	g_free (value);
+}
+
+DEFINE_TEST(uri_parse_with_id)
+{
+	GError *error = NULL;
+	GckAttributes *attrs;
+	GckAttribute *attr;
+
+	if (!gck_uri_parse ("pkcs11:id=54:45:53:54", NULL, &attrs, &error))
+		g_assert_not_reached ();
+
+	attr = gck_attributes_find (attrs, CKA_ID);
+	g_assert (attr);
+	g_assert (attr->value);
+	g_assert (attr->length == 4);
+	g_assert (memcmp (attr->value, "TEST", 4) == 0);
+
+	gck_attributes_unref (attrs);
+}
+
+DEFINE_TEST(uri_parse_with_bad_string_encoding)
+{
+	GError *error = NULL;
+	if (gck_uri_parse ("pkcs11:object=Test%", NULL, NULL, &error))
+		g_assert_not_reached ();
+	g_assert (g_error_matches (error, GCK_URI_ERROR, GCK_URI_BAD_ENCODING));
+	g_error_free (error);
+}
+
+DEFINE_TEST(uri_parse_with_bad_binary_encoding)
+{
+	GError *error = NULL;
+	if (gck_uri_parse ("pkcs11:id=xxxxx", NULL, NULL, &error))
+		g_assert_not_reached ();
+	g_assert (g_error_matches (error, GCK_URI_ERROR, GCK_URI_BAD_ENCODING));
+	g_error_free (error);
+}
+
+DEFINE_TEST(uri_parse_with_token)
+{
+	GError *error = NULL;
+	GckTokenInfo *token = NULL;
+
+	if (!gck_uri_parse ("pkcs11:token=Token%20Label;serial=3333;model=Deluxe;manufacturer=Me",
+	                    &token, NULL, &error))
+		g_assert_not_reached ();
+
+	g_assert (token);
+	g_assert_cmpstr (token->label, ==, "Token Label");
+	g_assert_cmpstr (token->serial_number, ==, "3333");
+	g_assert_cmpstr (token->model, ==, "Deluxe");
+	g_assert_cmpstr (token->manufacturer_id, ==, "Me");
+	gck_token_info_free (token);
+}
+
+DEFINE_TEST(uri_parse_with_token_bad_encoding)
+{
+	GError *error = NULL;
+
+	if (gck_uri_parse ("pkcs11:token=Token%", NULL, NULL, &error))
+		g_assert_not_reached ();
+
+	g_assert (g_error_matches (error, GCK_URI_ERROR, GCK_URI_BAD_ENCODING));
+	g_error_free (error);
+}
+
+DEFINE_TEST(uri_parse_with_bad_syntax)
+{
+	GError *error = NULL;
+
+	if (gck_uri_parse ("pkcs11:token", NULL, NULL, &error))
+		g_assert_not_reached ();
+
+	g_assert (g_error_matches (error, GCK_URI_ERROR, GCK_URI_BAD_SYNTAX));
+	g_error_free (error);
+}
+
+DEFINE_TEST(uri_build_empty)
+{
+	gchar *uri = NULL;
+
+	uri = gck_uri_build (NULL, NULL);
+	g_assert_cmpstr (uri, ==, "pkcs11:");
+	g_free (uri);
+}
+
+DEFINE_TEST(uri_build_with_token_info)
+{
+	gchar *uri = NULL;
+	GckTokenInfo *token;
+	GckTokenInfo *check;
+
+	token = g_new0 (GckTokenInfo, 1);
+	token->label = g_strdup ("The Label");
+	token->serial_number = g_strdup ("44444");
+	token->manufacturer_id = g_strdup ("Me");
+	token->model = g_strdup ("Deluxe");
+
+	uri = gck_uri_build (token, NULL);
+	g_assert (uri);
+
+	if (!gck_uri_parse (uri, &check, NULL, NULL))
+		g_assert_not_reached ();
+
+	g_assert (_gck_token_info_match (token, check));
+
+	gck_token_info_free (check);
+	gck_token_info_free (token);
+
+	g_assert (g_str_has_prefix (uri, "pkcs11:"));
+	g_assert (strstr (uri, "token=The%20Label"));
+	g_assert (strstr (uri, "serial=44444"));
+	g_assert (strstr (uri, "manufacturer=Me"));
+	g_assert (strstr (uri, "model=Deluxe"));
+
+	g_free (uri);
+}
+
+DEFINE_TEST(uri_build_with_attributes)
+{
+	gchar *uri = NULL;
+	GckAttributes *attrs;
+	gchar *string;
+	gulong value;
+	GckAttribute *attr;
+
+	attrs = gck_attributes_new ();
+	gck_attributes_add_string (attrs, CKA_LABEL, "The Label");
+	gck_attributes_add_ulong (attrs, CKA_CLASS, CKO_DATA);
+	gck_attributes_add_data (attrs, CKA_ID, "TEST", 4);
+
+	uri = gck_uri_build (NULL, attrs);
+	g_assert (uri);
+
+	gck_attributes_unref (attrs);
+
+	if (!gck_uri_parse (uri, NULL, &attrs, NULL))
+		g_assert_not_reached ();
+
+	if (!gck_attributes_find_string (attrs, CKA_LABEL, &string))
+		g_assert_not_reached ();
+	g_assert_cmpstr (string, ==, "The Label");
+
+	if (!gck_attributes_find_ulong (attrs, CKA_CLASS, &value))
+		g_assert_not_reached ();
+	g_assert (value == CKO_DATA);
+
+	attr = gck_attributes_find (attrs, CKA_ID);
+	g_assert (attr);
+	g_assert (attr->length == 4);
+	g_assert (memcmp (attr->value, "TEST", 4) == 0);
+
+	gck_attributes_unref (attrs);
+
+	g_assert (g_str_has_prefix (uri, "pkcs11:"));
+	g_assert (strstr (uri, "object=The%20Label"));
+	g_assert (strstr (uri, "objecttype=data"));
+	g_assert (strstr (uri, "id=54:45:53:54"));
+
+	g_free (uri);
+}



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