[gnome-keyring] Implement auto unlock in wrap-layer.



commit 92852897d420071033557bf77123a447c45b0ea1
Author: Stef Walter <stef memberwebs com>
Date:   Sat Jun 12 17:38:27 2010 +0000

    Implement auto unlock in wrap-layer.
    
     * Including tests and necessary infrastructure.

 Makefile.am                                      |    2 +-
 pkcs11/gkm/gkm-mock.c                            |  254 ++++++++---
 pkcs11/gkm/gkm-mock.h                            |   26 +
 pkcs11/gkm/gkm-secret.c                          |    7 +
 pkcs11/gkm/gkm-secret.h                          |    2 +
 pkcs11/pkcs11i.h                                 |    2 +
 pkcs11/secret-store/gkm-secret-collection.c      |   11 +
 pkcs11/wrap-layer/Makefile.am                    |    1 +
 pkcs11/wrap-layer/gkm-wrap-layer.h               |   10 +-
 pkcs11/wrap-layer/gkm-wrap-login.c               |  465 +++++++++++++++++++
 pkcs11/wrap-layer/gkm-wrap-login.h               |   42 ++
 pkcs11/wrap-layer/gkm-wrap-prompt.c              |  539 ++++++++++++++++------
 pkcs11/wrap-layer/tests/Makefile.am              |   10 +-
 pkcs11/wrap-layer/tests/mock-secret-store.c      |  286 ++++++++++++
 pkcs11/wrap-layer/tests/test-create-credential.c |    2 +
 pkcs11/wrap-layer/tests/test-login-auto.c        |  210 +++++++++
 pkcs11/wrap-layer/tests/test-login-hints.c       |   44 ++
 pkcs11/wrap-layer/tests/test-login-keyring.c     |  306 ++++++++++++
 pkcs11/wrap-layer/tests/test-login-specific.c    |    2 +
 pkcs11/wrap-layer/tests/test-login-user.c        |    2 +
 po/POTFILES.in                                   |    1 +
 ui/gku-prompt.c                                  |   31 ++-
 ui/gku-prompt.h                                  |    4 +
 23 files changed, 2044 insertions(+), 215 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 4106ac3..ca70b91 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -66,5 +66,5 @@ coverage: check
 	mkdir -p testing/coverage
 	$(LCOV) --directory . --capture --output-file testing/coverage.info
 	$(GENHTML) --output-directory testing/coverage testing/coverage.info
-	echo "Coverage info in: testing/coverage/index.html"
+	@echo "file://$(abs_top_builddir)/testing/coverage/index.html"
 endif
\ No newline at end of file
diff --git a/pkcs11/gkm/gkm-mock.c b/pkcs11/gkm/gkm-mock.c
index 2f09df5..a49c382 100644
--- a/pkcs11/gkm/gkm-mock.c
+++ b/pkcs11/gkm/gkm-mock.c
@@ -107,6 +107,138 @@ lookup_object (Session *session, CK_OBJECT_HANDLE hObject)
 	return attrs;
 }
 
+CK_OBJECT_HANDLE
+gkm_mock_module_take_object (GArray *template)
+{
+	gboolean token;
+	guint handle;
+
+	g_return_val_if_fail (the_objects, 0);
+
+	handle = ++unique_identifier;
+	if (gkm_template_find_boolean (template, CKA_TOKEN, &token))
+		g_return_val_if_fail (token == TRUE, 0);
+	else
+		gkm_template_set_boolean (template, CKA_TOKEN, CK_TRUE);
+	g_hash_table_insert (the_objects, GUINT_TO_POINTER (handle), template);
+	return handle;
+}
+
+void
+gkm_mock_module_enumerate_objects (CK_SESSION_HANDLE handle, GkmMockEnumerator func,
+                                   gpointer user_data)
+{
+	GHashTableIter iter;
+	gpointer key;
+	gpointer value;
+	Session *session;
+
+	g_assert (the_objects);
+	g_assert (func);
+
+	/* Token objects */
+	g_hash_table_iter_init (&iter, the_objects);
+	while (g_hash_table_iter_next (&iter, &key, &value)) {
+		if (!(func) (GPOINTER_TO_UINT (key), value, user_data))
+			return;
+	}
+
+	/* session objects */
+	if (handle) {
+		session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (handle));
+		if (session) {
+			g_hash_table_iter_init (&iter, session->objects);
+			while (g_hash_table_iter_next (&iter, &key, &value)) {
+				if (!(func) (GPOINTER_TO_UINT (key), value, user_data))
+					return;
+			}
+		}
+	}
+}
+
+typedef struct _FindObject {
+	CK_ATTRIBUTE_PTR attrs;
+	CK_ULONG n_attrs;
+	CK_OBJECT_HANDLE object;
+} FindObject;
+
+static gboolean
+enumerate_and_find_object (CK_OBJECT_HANDLE object, GArray *template, gpointer user_data)
+{
+	FindObject *ctx = user_data;
+	CK_ATTRIBUTE_PTR match, attr;
+	CK_ULONG i;
+
+	for (i = 0; i < ctx->n_attrs; ++i) {
+		match = ctx->attrs + i;
+		attr = gkm_template_find (template, match->type);
+		if (!attr)
+			return TRUE; /* Continue */
+
+		if (attr->ulValueLen != match->ulValueLen ||
+		    memcmp (attr->pValue, match->pValue, attr->ulValueLen) != 0)
+			return TRUE; /* Continue */
+	}
+
+	ctx->object = object;
+	return FALSE; /* Stop iteration */
+}
+
+CK_OBJECT_HANDLE
+gkm_mock_module_find_object (CK_SESSION_HANDLE session, CK_ATTRIBUTE_PTR attrs, CK_ULONG n_attrs)
+{
+	FindObject ctx;
+
+	ctx.attrs = attrs;
+	ctx.n_attrs = n_attrs;
+	ctx.object = 0;
+
+	gkm_mock_module_enumerate_objects (session, enumerate_and_find_object, &ctx);
+
+	return ctx.object;
+}
+
+static gboolean
+enumerate_and_count_objects (CK_OBJECT_HANDLE object, GArray *template, gpointer user_data)
+{
+	guint *n_objects = user_data;
+	++(*n_objects);
+	return TRUE; /* Continue */
+}
+
+guint
+gkm_mock_module_count_objects (CK_SESSION_HANDLE session)
+{
+	guint n_objects = 0;
+	gkm_mock_module_enumerate_objects (session, enumerate_and_count_objects, &n_objects);
+	return n_objects;
+}
+
+void
+gkm_mock_module_set_object (CK_OBJECT_HANDLE object, CK_ATTRIBUTE_PTR attrs,
+                            CK_ULONG n_attrs)
+{
+	CK_ULONG i;
+	GArray *template;
+
+	g_return_if_fail (object != 0);
+	g_return_if_fail (the_objects);
+
+	template = g_hash_table_lookup (the_objects, GUINT_TO_POINTER (object));
+	g_return_if_fail (template);
+
+	for (i = 0; i < n_attrs; ++i)
+		gkm_template_set (template, attrs + i);
+}
+
+void
+gkm_mock_module_set_pin (const gchar *password)
+{
+	g_free (the_pin);
+	the_pin = g_strdup (password);
+	n_the_pin = strlen (password);
+}
+
 CK_RV
 gkm_mock_C_Initialize (CK_VOID_PTR pInitArgs)
 {
@@ -153,6 +285,7 @@ gkm_mock_C_Initialize (CK_VOID_PTR pInitArgs)
 	gkm_template_set_boolean (attrs, CKA_UNWRAP, CK_TRUE);
 	gkm_template_set_boolean (attrs, CKA_DERIVE, CK_TRUE);
 	gkm_template_set_string (attrs, CKA_VALUE, "value");
+	gkm_template_set_string (attrs, CKA_GNOME_UNIQUE, "unique1");
 	g_hash_table_insert (the_objects, GUINT_TO_POINTER (PRIVATE_KEY_CAPITALIZE), attrs);
 
 	/* Public capitalize key */
@@ -164,6 +297,7 @@ gkm_mock_C_Initialize (CK_VOID_PTR pInitArgs)
 	gkm_template_set_boolean (attrs, CKA_ENCRYPT, CK_TRUE);
 	gkm_template_set_boolean (attrs, CKA_PRIVATE, CK_FALSE);
 	gkm_template_set_string (attrs, CKA_VALUE, "value");
+	gkm_template_set_string (attrs, CKA_GNOME_UNIQUE, "unique2");
 	g_hash_table_insert (the_objects, GUINT_TO_POINTER (PUBLIC_KEY_CAPITALIZE), attrs);
 
 	/* Private prefix key */
@@ -176,6 +310,7 @@ gkm_mock_C_Initialize (CK_VOID_PTR pInitArgs)
 	gkm_template_set_boolean (attrs, CKA_PRIVATE, CK_TRUE);
 	gkm_template_set_boolean (attrs, CKA_ALWAYS_AUTHENTICATE, CK_TRUE);
 	gkm_template_set_string (attrs, CKA_VALUE, "value");
+	gkm_template_set_string (attrs, CKA_GNOME_UNIQUE, "unique3");
 	g_hash_table_insert (the_objects, GUINT_TO_POINTER (PRIVATE_KEY_PREFIX), attrs);
 
 	/* Private prefix key */
@@ -187,6 +322,7 @@ gkm_mock_C_Initialize (CK_VOID_PTR pInitArgs)
 	gkm_template_set_boolean (attrs, CKA_VERIFY, CK_TRUE);
 	gkm_template_set_boolean (attrs, CKA_PRIVATE, CK_FALSE);
 	gkm_template_set_string (attrs, CKA_VALUE, "value");
+	gkm_template_set_string (attrs, CKA_GNOME_UNIQUE, "unique4");
 	g_hash_table_insert (the_objects, GUINT_TO_POINTER (PUBLIC_KEY_PREFIX), attrs);
 
 	initialized = TRUE;
@@ -448,6 +584,13 @@ gkm_mock_C_OpenSession (CK_SLOT_ID slotID, CK_FLAGS flags, CK_VOID_PTR pApplicat
 }
 
 CK_RV
+gkm_mock_fail_C_OpenSession (CK_SLOT_ID slotID, CK_FLAGS flags, CK_VOID_PTR pApplication,
+                             CK_NOTIFY Notify, CK_SESSION_HANDLE_PTR phSession)
+{
+	return CKR_GENERAL_ERROR;
+}
+
+CK_RV
 gkm_mock_C_CloseSession (CK_SESSION_HANDLE hSession)
 {
 	Session *session;
@@ -494,6 +637,18 @@ gkm_mock_C_GetSessionInfo (CK_SESSION_HANDLE hSession, CK_SESSION_INFO_PTR pInfo
 	if (!session)
 		return CKR_SESSION_HANDLE_INVALID;
 
+	if (logged_in) {
+		if (session->info.flags & CKF_RW_SESSION)
+			session->info.state = CKS_RW_USER_FUNCTIONS;
+		else
+			session->info.state = CKS_RO_USER_FUNCTIONS;
+	} else {
+		if (session->info.flags & CKF_RW_SESSION)
+			session->info.state = CKS_RW_PUBLIC_SESSION;
+		else
+			session->info.state = CKS_RO_PUBLIC_SESSION;
+	}
+
 	memcpy (pInfo, &session->info, sizeof (*pInfo));
 	return CKR_OK;
 }
@@ -672,21 +827,19 @@ gkm_mock_C_DestroyObject (CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject)
 	gboolean priv;
 
 	session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
-	g_assert (session != NULL && "No such session found");
-	if (!session)
-		return CKR_SESSION_HANDLE_INVALID;
+	g_return_val_if_fail (session, CKR_SESSION_HANDLE_INVALID);
 
 	attrs = lookup_object (session, hObject);
-	if (!attrs) {
-		g_assert_not_reached (); /* "no such object found" */
-		return CKR_OBJECT_HANDLE_INVALID;
-	}
+	g_return_val_if_fail (attrs, CKR_OBJECT_HANDLE_INVALID);
 
 	if (gkm_template_find_boolean (attrs, CKA_PRIVATE, &priv) && priv) {
 		if (!logged_in)
 			return CKR_USER_NOT_LOGGED_IN;
 	}
 
+	g_hash_table_remove (the_objects, GUINT_TO_POINTER (hObject));
+	g_hash_table_remove (session->objects, GUINT_TO_POINTER (hObject));
+
 	return CKR_OK;
 }
 
@@ -770,18 +923,40 @@ gkm_mock_C_SetAttributeValue (CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObje
 	return CKR_OK;
 }
 
+typedef struct _FindObjects {
+	CK_ATTRIBUTE_PTR template;
+	CK_ULONG count;
+	Session *session;
+} FindObjects;
+
+static gboolean
+enumerate_and_find_objects (CK_OBJECT_HANDLE object, GArray *attrs, gpointer user_data)
+{
+	FindObjects *ctx = user_data;
+	CK_ATTRIBUTE_PTR match, attr;
+	CK_ULONG i;
+
+	for (i = 0; i < ctx->count; ++i) {
+		match = ctx->template + i;
+		attr = gkm_template_find (attrs, match->type);
+		if (!attr)
+			return TRUE; /* Continue */
+
+		if (attr->ulValueLen != match->ulValueLen ||
+		    memcmp (attr->pValue, match->pValue, attr->ulValueLen) != 0)
+			return TRUE; /* Continue */
+	}
+
+	ctx->session->matches = g_list_prepend (ctx->session->matches, GUINT_TO_POINTER (object));
+	return TRUE; /* Continue */
+}
+
 CK_RV
 gkm_mock_C_FindObjectsInit (CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate,
                             CK_ULONG ulCount)
 {
-	GHashTableIter iter;
-	GArray *attrs;
-	CK_ATTRIBUTE_PTR attr;
-	CK_ATTRIBUTE_PTR match;
 	Session *session;
-	gpointer key, value;
-	gboolean matched = TRUE;
-	CK_ULONG i;
+	FindObjects ctx;
 
 	session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession));
 	g_return_val_if_fail (session != NULL, CKR_SESSION_HANDLE_INVALID);
@@ -792,54 +967,11 @@ gkm_mock_C_FindObjectsInit (CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTempla
 
 	session->operation = OP_FIND;
 
-	/* Token objects */
-	g_hash_table_iter_init (&iter, the_objects);
-	while (g_hash_table_iter_next (&iter, &key, &value)) {
-		attrs = (GArray*)value;
-		matched = TRUE;
-		for (i = 0; i < ulCount; ++i) {
-			match = pTemplate + i;
-			attr = gkm_template_find (attrs, match->type);
-			if (!attr) {
-				matched = FALSE;
-				break;
-			}
-
-			if (attr->ulValueLen != match->ulValueLen ||
-			    memcmp (attr->pValue, match->pValue, attr->ulValueLen) != 0) {
-				matched = FALSE;
-				break;
-			}
-		}
-
-		if (matched)
-			session->matches = g_list_prepend (session->matches, key);
-	}
-
-	/* session objects */
-	g_hash_table_iter_init (&iter, session->objects);
-	while (g_hash_table_iter_next (&iter, &key, &value)) {
-		attrs = (GArray*)value;
-		matched = TRUE;
-		for (i = 0; i < ulCount; ++i) {
-			match = pTemplate + i;
-			attr = gkm_template_find (attrs, match->type);
-			if (!attr) {
-				matched = FALSE;
-				break;
-			}
-
-			if (attr->ulValueLen != match->ulValueLen ||
-			    memcmp (attr->pValue, match->pValue, attr->ulValueLen) != 0) {
-				matched = FALSE;
-				break;
-			}
-		}
-
-		if (matched)
-			session->matches = g_list_prepend (session->matches, key);
-	}
+	ctx.template = pTemplate;
+	ctx.count = ulCount;
+	ctx.session = session;
 
+	gkm_mock_module_enumerate_objects (hSession, enumerate_and_find_objects, &ctx);
 	return CKR_OK;
 }
 
diff --git a/pkcs11/gkm/gkm-mock.h b/pkcs11/gkm/gkm-mock.h
index fd6c86b..dd4dd9e 100644
--- a/pkcs11/gkm/gkm-mock.h
+++ b/pkcs11/gkm/gkm-mock.h
@@ -71,6 +71,12 @@ CK_RV               gkm_mock_C_OpenSession                         (CK_SLOT_ID s
                                                                     CK_NOTIFY Notify,
                                                                     CK_SESSION_HANDLE_PTR phSession);
 
+CK_RV               gkm_mock_fail_C_OpenSession                    (CK_SLOT_ID slotID,
+                                                                    CK_FLAGS flags,
+                                                                    CK_VOID_PTR pApplication,
+                                                                    CK_NOTIFY Notify,
+                                                                    CK_SESSION_HANDLE_PTR phSession);
+
 CK_RV               gkm_mock_C_CloseSession                        (CK_SESSION_HANDLE hSession);
 
 CK_RV               gkm_mock_C_CloseAllSessions                    (CK_SLOT_ID slotID);
@@ -337,8 +343,28 @@ CK_RV               gkm_mock_unsupported_C_GenerateRandom          (CK_SESSION_H
                                                                     CK_BYTE_PTR pRandomData,
                                                                     CK_ULONG ulRandomLen);
 
+CK_OBJECT_HANDLE    gkm_mock_module_find_object                    (CK_SESSION_HANDLE session,
+                                                                    CK_ATTRIBUTE_PTR attrs,
+                                                                    CK_ULONG n_attrs);
+
+guint               gkm_mock_module_count_objects                  (CK_SESSION_HANDLE session);
+
+typedef gboolean    (*GkmMockEnumerator)                           (CK_OBJECT_HANDLE handle,
+                                                                    GArray *attrs,
+                                                                    gpointer user_data);
+
+void                gkm_mock_module_enumerate_objects              (CK_SESSION_HANDLE session,
+                                                                    GkmMockEnumerator func,
+                                                                    gpointer user_data);
+
 CK_OBJECT_HANDLE    gkm_mock_module_take_object                    (GArray *template);
 
+void                gkm_mock_module_set_object                     (CK_OBJECT_HANDLE object,
+                                                                    CK_ATTRIBUTE_PTR attrs,
+                                                                    CK_ULONG n_attrs);
+
+void                gkm_mock_module_set_pin                        (const gchar *password);
+
 /*
  * Some dumb crypto mechanisms for simple testing.
  *
diff --git a/pkcs11/gkm/gkm-secret.c b/pkcs11/gkm/gkm-secret.c
index 3bf8d05..ca9a36e 100644
--- a/pkcs11/gkm/gkm-secret.c
+++ b/pkcs11/gkm/gkm-secret.c
@@ -175,3 +175,10 @@ gkm_secret_equals (GkmSecret *self, const guchar* pin, gssize n_pin)
 	/* Compare actual memory */
 	return memcmp (pin, self->memory, n_pin) == 0;
 }
+
+gboolean
+gkm_secret_is_trivially_weak (GkmSecret *self)
+{
+	return gkm_secret_equals (self, NULL, 0) ||
+	       gkm_secret_equals (self, (const guchar*)"", 0);
+}
diff --git a/pkcs11/gkm/gkm-secret.h b/pkcs11/gkm/gkm-secret.h
index 4920b8d..e5ac404 100644
--- a/pkcs11/gkm/gkm-secret.h
+++ b/pkcs11/gkm/gkm-secret.h
@@ -64,4 +64,6 @@ gboolean            gkm_secret_equals                 (GkmSecret *self,
                                                        const guchar *data,
                                                        gssize n_data);
 
+gboolean            gkm_secret_is_trivially_weak      (GkmSecret *self);
+
 #endif /* __GKM_SECRET_H__ */
diff --git a/pkcs11/pkcs11i.h b/pkcs11/pkcs11i.h
index 08337f1..44f285a 100644
--- a/pkcs11/pkcs11i.h
+++ b/pkcs11/pkcs11i.h
@@ -86,6 +86,8 @@ typedef CK_G_APPLICATION* CK_G_APPLICATION_PTR;
 
 #define CKA_G_SCHEMA                         (CKA_GNOME + 216)
 
+#define CKA_G_LOGIN_COLLECTION               (CKA_GNOME + 218)
+
 /* -------------------------------------------------------------------
  * MECHANISMS
  */
diff --git a/pkcs11/secret-store/gkm-secret-collection.c b/pkcs11/secret-store/gkm-secret-collection.c
index 04e7718..970da45 100644
--- a/pkcs11/secret-store/gkm-secret-collection.c
+++ b/pkcs11/secret-store/gkm-secret-collection.c
@@ -398,12 +398,23 @@ static CK_RV
 gkm_secret_collection_get_attribute (GkmObject *base, GkmSession *session, CK_ATTRIBUTE_PTR attr)
 {
 	GkmSecretCollection *self = GKM_SECRET_COLLECTION (base);
+	const gchar *identifier;
+	GkmSecret *master;
 
 	switch (attr->type) {
 	case CKA_CLASS:
 		return gkm_attribute_set_ulong (attr, CKO_G_COLLECTION);
 	case CKA_G_CREDENTIAL_TEMPLATE:
 		return gkm_attribute_set_template (attr, self->template);
+	case CKA_G_LOGIN_COLLECTION:
+		identifier = gkm_secret_object_get_identifier (GKM_SECRET_OBJECT (base));
+		g_return_val_if_fail (identifier, CKR_GENERAL_ERROR);
+		return gkm_attribute_set_bool (attr, g_str_equal (identifier, "login"));
+	case CKA_TRUSTED:
+		if (self->sdata)
+			return gkm_attribute_set_bool (attr, CK_FALSE);
+		master = gkm_secret_data_get_master (self->sdata);
+		return gkm_attribute_set_bool (attr, (master && !gkm_secret_is_trivially_weak (master)));
 	}
 	return GKM_OBJECT_CLASS (gkm_secret_collection_parent_class)->get_attribute (base, session, attr);
 }
diff --git a/pkcs11/wrap-layer/Makefile.am b/pkcs11/wrap-layer/Makefile.am
index fee3a59..b12be4b 100644
--- a/pkcs11/wrap-layer/Makefile.am
+++ b/pkcs11/wrap-layer/Makefile.am
@@ -12,6 +12,7 @@ INCLUDES = -I. \
 
 libgkm_wrap_layer_la_SOURCES = \
 	gkm-wrap-layer.c gkm-wrap-layer.h \
+	gkm-wrap-login.c gkm-wrap-login.h \
 	gkm-wrap-prompt.c gkm-wrap-prompt.h
 
 libgkm_wrap_layer_la_LIBADD = \
diff --git a/pkcs11/wrap-layer/gkm-wrap-layer.h b/pkcs11/wrap-layer/gkm-wrap-layer.h
index a8d73c6..c9d0e1e 100644
--- a/pkcs11/wrap-layer/gkm-wrap-layer.h
+++ b/pkcs11/wrap-layer/gkm-wrap-layer.h
@@ -24,10 +24,14 @@
 
 #include "pkcs11/pkcs11.h"
 
-CK_FUNCTION_LIST_PTR    gkm_wrap_layer_get_functions     (void);
+CK_FUNCTION_LIST_PTR    gkm_wrap_layer_get_functions               (void);
 
-void                    gkm_wrap_layer_reset_modules     (void);
+void                    gkm_wrap_layer_reset_modules               (void);
 
-void                    gkm_wrap_layer_add_module        (CK_FUNCTION_LIST_PTR funcs);
+void                    gkm_wrap_layer_add_module                  (CK_FUNCTION_LIST_PTR funcs);
+
+void                    gkm_wrap_layer_hint_login_unlock_success   (void);
+
+void                    gkm_wrap_layer_hint_login_unlock_failure   (void);
 
 #endif /* __GKM_WRAP_LAYER_H__ */
diff --git a/pkcs11/wrap-layer/gkm-wrap-login.c b/pkcs11/wrap-layer/gkm-wrap-login.c
new file mode 100644
index 0000000..0e4518b
--- /dev/null
+++ b/pkcs11/wrap-layer/gkm-wrap-login.c
@@ -0,0 +1,465 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2010 Stefan Walter
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include "gkm-wrap-layer.h"
+#include "gkm-wrap-login.h"
+
+#include "gkm/gkm-attributes.h"
+#include "gkm/gkm-util.h"
+
+#include "egg/egg-secure-memory.h"
+
+#include "pkcs11/pkcs11.h"
+#include "pkcs11/pkcs11i.h"
+
+#include <glib/gi18n.h>
+
+#include <string.h>
+
+static gint unlock_failures = 0;
+
+void
+gkm_wrap_layer_hint_login_unlock_success (void)
+{
+	g_atomic_int_set (&unlock_failures, 0);
+}
+
+void
+gkm_wrap_layer_hint_login_unlock_failure (void)
+{
+	g_atomic_int_inc (&unlock_failures);
+}
+
+gboolean
+gkm_wrap_login_did_unlock_fail (void)
+{
+	return g_atomic_int_get (&unlock_failures) ? TRUE : FALSE;
+}
+
+static gboolean
+prepare_template_for_storage (CK_FUNCTION_LIST_PTR module,
+                              CK_SESSION_HANDLE session,
+                              CK_OBJECT_HANDLE collection,
+                              GArray *template)
+{
+	CK_ATTRIBUTE attr;
+	CK_RV rv;
+
+	g_assert (module);
+	g_assert (session);
+	g_assert (template);
+
+	/* Lookup the ID attribute */
+	attr.type = CKA_ID;
+	attr.pValue = NULL;
+	attr.ulValueLen = 0;
+
+	rv = (module->C_GetAttributeValue) (session, collection, &attr, 1);
+	if (rv != CKR_OK || attr.ulValueLen == (CK_ULONG)-1)
+		return FALSE;
+
+	attr.pValue = g_malloc0 (attr.ulValueLen);
+
+	rv = (module->C_GetAttributeValue) (session, collection, &attr, 1);
+	g_return_val_if_fail (rv == CKR_OK, FALSE);
+
+	/* Use the ID as the collection attribute */
+	attr.type = CKA_G_COLLECTION;
+	gkm_template_set (template, &attr);
+	g_free (attr.pValue);
+
+	gkm_template_set_ulong (template, CKA_CLASS, CKO_SECRET_KEY);
+	gkm_template_set_boolean (template, CKA_TOKEN, TRUE);
+	return TRUE;
+}
+
+static gboolean
+prepare_module_session_and_collection (CK_FUNCTION_LIST_PTR_PTR module,
+                                       CK_SESSION_HANDLE_PTR session,
+                                       CK_OBJECT_HANDLE_PTR object)
+{
+	CK_OBJECT_CLASS klass = CKO_G_COLLECTION;
+	CK_BBOOL trueval = CK_TRUE;
+	CK_BBOOL falseval = CK_FALSE;
+	CK_ATTRIBUTE attrs[] = {
+		{ CKA_G_LOGIN_COLLECTION, &trueval, sizeof (trueval) },
+		{ CKA_G_LOCKED, &falseval, sizeof (falseval) },
+		{ CKA_CLASS, &klass, sizeof (klass) },
+		{ CKA_TOKEN, &trueval, sizeof (trueval) },
+		{ CKA_TRUSTED, &trueval, sizeof (trueval) },
+	};
+
+	CK_SESSION_INFO sinfo;
+	CK_SLOT_ID_PTR slots = NULL;
+	CK_FUNCTION_LIST_PTR funcs;
+	CK_ULONG n_slots = 0, i;
+	gboolean ret = FALSE;
+	CK_ULONG n_objects = 0;
+	CK_RV rv;
+
+	g_assert (module);
+	g_assert (session);
+
+	funcs = gkm_wrap_layer_get_functions ();
+	g_return_val_if_fail (funcs, FALSE);
+
+	rv = (funcs->C_GetSlotList) (CK_TRUE, NULL, &n_slots);
+	g_return_val_if_fail (rv == CKR_OK, FALSE);
+	slots = g_new0 (CK_SLOT_ID, n_slots);
+	rv = (funcs->C_GetSlotList) (CK_TRUE, slots, &n_slots);
+	g_return_val_if_fail (rv == CKR_OK, FALSE);
+
+	for (i = 0; !ret && i < n_slots; ++i) {
+		/* Open a session with this module */
+		rv = (funcs->C_OpenSession) (slots[i], CKF_SERIAL_SESSION, NULL, NULL, session);
+		if (rv != CKR_OK)
+			continue;
+
+		rv = (funcs->C_GetSessionInfo) (*session, &sinfo);
+		if (rv != CKR_OK)
+			continue;
+
+		/* Log into the session with no password, in case its needed */
+		if (sinfo.state == CKS_RO_PUBLIC_SESSION || sinfo.state == CKS_RW_PUBLIC_SESSION)
+			(funcs->C_Login) (*session, CKU_USER, (guchar*)"", 0);
+
+		rv = (funcs->C_FindObjectsInit) (*session, attrs, G_N_ELEMENTS (attrs));
+		if (rv == CKR_OK) {
+			rv = (funcs->C_FindObjects) (*session, object, 1, &n_objects);
+			(funcs->C_FindObjectsFinal) (*session);
+			if (rv == CKR_OK && n_objects == 1)
+				ret = TRUE;
+		}
+
+		if (!ret)
+			(funcs->C_CloseSession) (*session);
+	}
+
+	g_free (slots);
+
+	*module = funcs;
+	return ret;
+}
+
+static void
+string_fields_to_template_va (va_list args, const gchar *name,
+                              GArray *template)
+{
+	GString *fields = g_string_sized_new (128);
+	const gchar *last = NULL;
+	gint cmp;
+
+	g_assert (name);
+	g_assert (template);
+
+	while (name != NULL) {
+		g_string_append (fields, name);
+		g_string_append_c (fields, '\0');
+		g_string_append (fields, va_arg (args, const gchar*));
+		g_string_append_c (fields, '\0');
+
+		/* Names must be in alphabetical order */
+		if (name && last) {
+			cmp = strcmp (last, name);
+			if (cmp == 0)
+				g_warning ("duplicate names in attributes not allowed: %s %s", last, name);
+			else if (cmp > 0)
+				g_warning ("names in attributes must in alphabetical order: %s %s", last, name);
+		}
+
+		last = name;
+		name = va_arg (args, const gchar*);
+	}
+
+	gkm_template_set_value (template, CKA_G_FIELDS, fields->str, fields->len);
+	g_string_free (fields, TRUE);
+}
+
+static CK_OBJECT_HANDLE
+find_login_keyring_item (CK_FUNCTION_LIST_PTR module, CK_SESSION_HANDLE session,
+                         GArray *template)
+{
+	CK_OBJECT_HANDLE item = 0;
+	CK_ATTRIBUTE_PTR attr;
+	CK_ATTRIBUTE matched;
+	CK_OBJECT_HANDLE search;
+	GArray *attrs;
+	CK_RV rv;
+
+	g_assert (module);
+	g_assert (template);
+
+	/* Template for search object */
+	attrs = gkm_template_new (NULL, 0);
+	gkm_template_set_ulong (attrs, CKA_CLASS, CKO_G_SEARCH);
+	gkm_template_set_boolean (attrs, CKA_TOKEN, CK_FALSE);
+
+	attr = gkm_template_find (template, CKA_G_COLLECTION);
+	if (attr != NULL)
+		gkm_template_set (attrs, attr);
+
+	attr = gkm_template_find (template, CKA_G_FIELDS);
+	g_return_val_if_fail (attr, 0);
+	gkm_template_set (attrs, attr);
+
+	/* Create new search object */
+	rv = (module->C_CreateObject) (session, (CK_ATTRIBUTE_PTR)attrs->data, attrs->len, &search);
+	gkm_template_free (attrs);
+
+	if (rv != CKR_OK) {
+		g_warning ("couldn't create search for login keyring: %s", gkm_util_rv_stringize (rv));
+		return 0;
+	}
+
+	matched.type = CKA_G_MATCHED;
+	matched.pValue = NULL;
+	matched.ulValueLen = 0;
+
+	rv = (module->C_GetAttributeValue) (session, search, &matched, 1);
+	g_return_val_if_fail (rv == CKR_OK, 0);
+	g_return_val_if_fail (matched.ulValueLen != (CK_ULONG)-1, 0);
+
+	if (matched.ulValueLen >= sizeof (CK_OBJECT_HANDLE)) {
+		matched.pValue = g_malloc (matched.ulValueLen);
+		rv = (module->C_GetAttributeValue) (session, search, &matched, 1);
+		g_return_val_if_fail (rv == CKR_OK, 0);
+
+		item = *((CK_OBJECT_HANDLE_PTR)matched.pValue);
+		g_return_val_if_fail (item != 0, 0);
+		g_free (matched.pValue);
+	}
+
+	/* Destroy the search object */
+	(module->C_DestroyObject) (session, search);
+
+	return item;
+}
+
+void
+gkm_wrap_login_attach_secret (const gchar *label, const gchar *secret,
+                              const gchar *first, ...)
+{
+	CK_FUNCTION_LIST_PTR module;
+	CK_SESSION_HANDLE session;
+	CK_OBJECT_HANDLE collection;
+	CK_OBJECT_HANDLE item;
+	CK_ATTRIBUTE attr;
+	gchar *display_name;
+	GArray *template;
+	gsize original_len;
+	va_list va;
+	CK_RV rv;
+
+	if (first == NULL)
+		return;
+
+	if (secret == NULL)
+		secret = "";
+
+	/* We only support storing utf-8 strings */
+	g_return_if_fail (g_utf8_validate (secret, -1, NULL));
+
+	if (!prepare_module_session_and_collection (&module, &session, &collection))
+		return;
+
+	template = gkm_template_new (NULL, 0);
+	if (!prepare_template_for_storage (module, session, collection, template)) {
+		gkm_template_free (template);
+		return;
+	}
+
+	va_start(va, first);
+	string_fields_to_template_va (va, first, template);
+	va_end(va);
+
+	/*
+	 * If there already is such an item, then include its identifier.
+	 * What this does is overwrite that item, rather than creating new.
+	 */
+	item = find_login_keyring_item (module, session, template);
+	if (item != 0) {
+
+		attr.type = CKA_ID;
+		attr.ulValueLen = 0;
+		attr.pValue = NULL;
+
+		rv = (module->C_GetAttributeValue) (session, item, &attr, 1);
+		if (rv == CKR_OK && attr.ulValueLen != (CK_ULONG)-1) {
+			attr.pValue = g_malloc (attr.ulValueLen);
+			rv = (module->C_GetAttributeValue) (session, item, &attr, 1);
+			if (rv == CKR_OK)
+				gkm_template_set (template, &attr);
+			g_free (attr.pValue);
+		}
+	}
+
+	/* Get the label ready */
+	display_name = g_strdup_printf (_("Unlock password for: %s"), label ? label : _("Unnamed"));
+	gkm_template_set_string (template, CKA_LABEL, display_name);
+	g_free (display_name);
+
+	/* Instead of duplicating the password, we tack it into the template */
+	original_len = template->len;
+	attr.type = CKA_VALUE;
+	attr.pValue = (CK_VOID_PTR)secret;
+	attr.ulValueLen = strlen (secret);
+	g_array_append_val (template, attr);
+
+	/* Actually make the object */
+	rv = (module->C_CreateObject) (session, ((CK_ATTRIBUTE_PTR)template->data),
+	                               template->len, &item);
+	if (rv != CKR_OK)
+		g_warning ("couldn't store secret in login keyring: %s", gkm_util_rv_stringize (rv));
+
+	/* Before freeing, truncate our password attribute we tacked on the end */
+	g_array_set_size (template, original_len);
+	gkm_template_free (template);
+
+	(module->C_CloseSession) (session);
+}
+
+gchar*
+gkm_wrap_login_lookup_secret (const gchar *first, ...)
+{
+	CK_FUNCTION_LIST_PTR module;
+	CK_SESSION_HANDLE session;
+	CK_OBJECT_HANDLE collection;
+	CK_OBJECT_HANDLE item;
+	CK_ATTRIBUTE attr;
+	GArray *template;
+	gchar *password = NULL;
+	va_list va;
+	CK_RV rv;
+
+	if (first == NULL)
+		return NULL;
+
+	if (!prepare_module_session_and_collection (&module, &session, &collection))
+		return NULL;
+
+	template = gkm_template_new (NULL, 0);
+	gkm_template_set_ulong (template, CKA_CLASS, CKO_SECRET_KEY);
+	gkm_template_set_boolean (template, CKA_G_LOCKED, FALSE);
+
+	va_start(va, first);
+	string_fields_to_template_va (va, first, template);
+	va_end(va);
+
+	item = find_login_keyring_item (module, session, template);
+	gkm_template_free (template);
+
+	if (item != 0) {
+
+		attr.type = CKA_VALUE;
+		attr.pValue = NULL;
+		attr.ulValueLen = 0;
+
+		rv = (module->C_GetAttributeValue) (session, item, &attr, 1);
+		if (rv == CKR_OK && attr.ulValueLen != (CK_ULONG)-1) {
+
+			/* Allocate memory for password. Note we're null terminating */
+			password = attr.pValue = egg_secure_alloc (attr.ulValueLen + 1);
+			rv = (module->C_GetAttributeValue) (session, item, &attr, 1);
+
+			/* Double check that this is a password */
+			if (rv == CKR_OK) {
+				if (!g_utf8_validate (password, -1, NULL)) {
+					g_message ("expected string, but found binary secret in login keyring");
+					egg_secure_strfree (password);
+					password = NULL;
+				}
+
+			/* Couldn't read the value. Remember object can go away due to race */
+			} else {
+				if (rv != CKR_OBJECT_HANDLE_INVALID)
+					g_warning ("couldn't read stored secret from login keyring: %s",
+					           gkm_util_rv_stringize (rv));
+				egg_secure_free (password);
+				password = NULL;
+			}
+
+		/* Failure. Remember object can go away due to race */
+		} else if (rv != CKR_OK && rv != CKR_OBJECT_HANDLE_INVALID) {
+			g_warning ("couldn't get stored secret from login keyring: %s",
+			           gkm_util_rv_stringize (rv));
+		}
+	}
+
+	(module->C_CloseSession) (session);
+
+	return password;
+}
+
+void
+gkm_wrap_login_remove_secret (const gchar *first, ...)
+{
+	CK_FUNCTION_LIST_PTR module;
+	CK_SESSION_HANDLE session;
+	CK_OBJECT_HANDLE collection;
+	CK_OBJECT_HANDLE item;
+	GArray *template;
+	va_list va;
+	CK_RV rv;
+
+	if (first == NULL)
+		return;
+
+	if (!prepare_module_session_and_collection (&module, &session, &collection))
+		return;
+
+	template = gkm_template_new (NULL, 0);
+	if (!prepare_template_for_storage (module, session, collection, template)) {
+		gkm_template_free (template);
+		return;
+	}
+
+	va_start(va, first);
+	string_fields_to_template_va (va, first, template);
+	va_end(va);
+
+	item = find_login_keyring_item (module, session, template);
+	gkm_template_free (template);
+
+	if (item != 0) {
+		rv = (module->C_DestroyObject) (session, item);
+		if (rv != CKR_OK && rv != CKR_OBJECT_HANDLE_INVALID)
+			g_warning ("couldn't remove stored secret from login keyring: %s",
+			           gkm_util_rv_stringize (rv));
+	}
+
+	(module->C_CloseSession) (session);
+}
+
+gboolean
+gkm_wrap_login_is_usable (void)
+{
+	CK_FUNCTION_LIST_PTR module;
+	CK_OBJECT_HANDLE collection;
+	CK_SESSION_HANDLE session;
+
+	if (!prepare_module_session_and_collection (&module, &session, &collection))
+		return FALSE;
+
+	(module->C_CloseSession) (session);
+	return TRUE;
+}
diff --git a/pkcs11/wrap-layer/gkm-wrap-login.h b/pkcs11/wrap-layer/gkm-wrap-login.h
new file mode 100644
index 0000000..148a1b9
--- /dev/null
+++ b/pkcs11/wrap-layer/gkm-wrap-login.h
@@ -0,0 +1,42 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2010 Stefan Walter
+ *
+ * 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.
+ */
+
+#ifndef __GKM_WRAP_LOGIN_H__
+#define __GKM_WRAP_LOGIN_H__
+
+#include <glib.h>
+
+gboolean      gkm_wrap_login_is_usable                (void);
+
+gboolean      gkm_wrap_login_did_unlock_fail          (void);
+
+void          gkm_wrap_login_attach_secret            (const gchar *label,
+                                                       const gchar *secret,
+                                                       const gchar *first,
+                                                       ...);
+
+gchar*        gkm_wrap_login_lookup_secret            (const gchar *first,
+                                                       ...);
+
+void          gkm_wrap_login_remove_secret            (const gchar *first,
+                                                       ...);
+
+#endif /* __GKM_WRAP_LOGIN_H__ */
diff --git a/pkcs11/wrap-layer/gkm-wrap-prompt.c b/pkcs11/wrap-layer/gkm-wrap-prompt.c
index db2ffa3..f788ac0 100644
--- a/pkcs11/wrap-layer/gkm-wrap-prompt.c
+++ b/pkcs11/wrap-layer/gkm-wrap-prompt.c
@@ -21,6 +21,7 @@
 
 #include "config.h"
 
+#include "gkm-wrap-login.h"
 #include "gkm-wrap-prompt.h"
 
 #include "egg/egg-secure-memory.h"
@@ -47,6 +48,7 @@ struct _GkmWrapPrompt {
 	gpointer prompt_data;
 	GDestroyNotify destroy_data;
 
+	guint iteration;
 	GQueue pool;
 };
 
@@ -54,122 +56,308 @@ typedef struct _CredentialPrompt {
 	GArray *template;
 	CK_ULONG n_template;
 	gchar *password;
-	guint iteration;
 } CredentialPrompt;
 
 G_DEFINE_TYPE (GkmWrapPrompt, gkm_wrap_prompt, GKU_TYPE_PROMPT);
 
 /* -----------------------------------------------------------------------------
- * INTERNAL
+ * UTILITIES
+ */
+
+static gpointer
+pool_alloc (GkmWrapPrompt *self, gsize length)
+{
+	gpointer memory = g_malloc0 (length);
+
+	g_assert (GKM_WRAP_IS_PROMPT (self));
+	g_queue_push_tail (&self->pool, memory);
+	return memory;
+}
+
+static gpointer
+pool_dup (GkmWrapPrompt *self, gconstpointer original, gsize length)
+{
+	gpointer memory = pool_alloc (self, length);
+	memcpy (memory, original, length);
+	return memory;
+}
+
+/* -----------------------------------------------------------------------------
+ * AUTO UNLOCK
  */
 
 #if 0
+static void
+set_warning_wrong (GkdSecretUnlock *self)
+{
+	g_assert (GKD_SECRET_IS_UNLOCK (self));
+	gku_prompt_set_warning (GKU_PROMPT (self), _("The unlock password was incorrect"));
+}
+#endif
+
 static gchar*
-location_string_for_attributes (GP11Attributes *attrs)
+auto_unlock_keyring_location (CK_ATTRIBUTE_PTR attrs, CK_ULONG n_attrs)
 {
-	gchar *identifier;
-	gchar *location;
+	CK_ATTRIBUTE_PTR attr;
+	gboolean is_login = FALSE;
+
+	if (gkm_attributes_find_boolean (attrs, n_attrs, CKA_G_LOGIN_COLLECTION, &is_login) && is_login)
+		return NULL;
 
-	identifier = identifier_string_for_attributes (attrs);
-	if (identifier == NULL)
+	attr = gkm_attributes_find (attrs, n_attrs, CKA_ID);
+	if (attr == NULL)
 		return NULL;
 
 	/*
 	 * COMPAT: Format it into a string. This is done this way for compatibility
 	 * with old gnome-keyring releases. In the future this may change.
-	 *
-	 * FYI: gp11_object_get_data() null terminates
 	 */
-	location = g_strdup_printf ("LOCAL:/keyrings/%s.keyring", (gchar*)identifier);
-	g_free (identifier);
-	return location;
+
+	return g_strdup_printf ("LOCAL:/keyrings/%s.keyring", (gchar*)attr->pValue);
 }
 
-static void
-set_warning_wrong (GkdSecretUnlock *self)
+static gchar*
+auto_unlock_object_unique (CK_ATTRIBUTE_PTR attrs, CK_ULONG n_attrs)
 {
-	g_assert (GKD_SECRET_IS_UNLOCK (self));
-	gku_prompt_set_warning (GKU_PROMPT (self), _("The unlock password was incorrect"));
+	CK_ATTRIBUTE_PTR attr;
+
+	attr = gkm_attributes_find (attrs, n_attrs, CKA_GNOME_UNIQUE);
+	if (attr == NULL)
+		return NULL;
+
+	return g_strndup (attr->pValue, attr->ulValueLen);
+}
+
+static gchar*
+auto_unlock_lookup_keyring (CK_ATTRIBUTE_PTR attrs, CK_ULONG n_attrs)
+{
+	gchar *location;
+	gchar *password;
+
+	location = auto_unlock_keyring_location (attrs, n_attrs);
+	if (location == NULL)
+		return NULL;
+
+	password = gkm_wrap_login_lookup_secret ("keyring", location, NULL);
+	g_free (location);
+	return password;
+}
+
+
+static gchar*
+auto_unlock_lookup_object (CK_ATTRIBUTE_PTR attrs, CK_ULONG n_attrs)
+{
+	CK_OBJECT_CLASS klass;
+	gchar *unique;
+	gchar *password;
+
+	if (!gkm_attributes_find_ulong (attrs, n_attrs, CKA_CLASS, &klass))
+		return NULL;
+
+	if (klass == CKO_G_COLLECTION)
+		return auto_unlock_lookup_keyring (attrs, n_attrs);
+
+	unique = auto_unlock_object_unique (attrs, n_attrs);
+	if (unique == NULL)
+		return NULL;
+
+	password = gkm_wrap_login_lookup_secret ("unique", unique, NULL);
+	g_free (unique);
+	return password;
+}
+
+static gchar*
+auto_unlock_lookup_token (CK_TOKEN_INFO_PTR info)
+{
+	gchar *password = NULL;
+	gchar *manufacturer;
+	gchar *serial;
+
+	g_assert (info);
+
+	manufacturer = g_strndup ((gchar*)info->manufacturerID, sizeof (info->manufacturerID));
+	g_strchomp (manufacturer);
+
+	serial = g_strndup ((gchar*)info->serialNumber, sizeof (info->serialNumber));
+	g_strchomp (serial);
+
+	if (!g_str_equal (manufacturer, "") && !g_str_equal (serial, ""))
+		password = gkm_wrap_login_lookup_secret ("manufacturer", manufacturer,
+		                                         "serial-number", serial,
+		                                         NULL);
+
+	g_free (manufacturer);
+	g_free (serial);
+
+	return password;
+}
+
+static gboolean
+auto_unlock_should_attach (GkmWrapPrompt *self)
+{
+	GkuPrompt *prompt = GKU_PROMPT (self);
+	gint value = 0;
+	return gku_prompt_has_response (prompt) &&
+	       gku_prompt_get_unlock_option (prompt, GKU_UNLOCK_AUTO, &value) &&
+	       value != 0;
 }
 
 static void
-attach_unlock_to_login (GP11Object *collection, GkdSecretSecret *master)
+auto_unlock_attach_keyring (CK_ATTRIBUTE_PTR attrs, CK_ULONG n_attrs, const gchar *password)
 {
-	DBusError derr = DBUS_ERROR_INIT;
-	GP11Attributes *attrs;
-	GP11Object *cred;
 	gchar *location;
 	gchar *label;
 
-	g_assert (GP11_IS_OBJECT (collection));
+	if (!password)
+		return;
+
+	location = auto_unlock_keyring_location (attrs, n_attrs);
+	if (location == NULL)
+		return;
 
-	/* Relevant information for the unlock item */
-	attrs = attributes_for_collection (collection);
-	g_return_if_fail (attrs);
-	location = location_string_for_attributes (attrs);
-	label = label_string_for_attributes (attrs);
-	gp11_attributes_unref (attrs);
+	if (!gkm_attributes_find_string (attrs, n_attrs, CKA_LABEL, &label))
+		if (!gkm_attributes_find_string (attrs, n_attrs, CKA_ID, &label))
+			label = g_strdup (location);
 
-	attrs = gkd_login_attach_make_attributes (label, "keyring", location, NULL);
+	gkm_wrap_login_attach_secret (label, password, "keyring", location, NULL);
 	g_free (location);
 	g_free (label);
+}
 
-	cred = gkd_secret_session_create_credential (master->session, NULL, attrs, master, &derr);
-	gp11_attributes_unref (attrs);
-	g_object_unref (cred);
+static void
+auto_unlock_attach_object (CK_ATTRIBUTE_PTR attrs, CK_ULONG n_attrs, const gchar *password)
+{
+	CK_OBJECT_CLASS klass;
+	gchar *label;
+	gchar *unique;
+
+	if (!password)
+		return;
 
-	if (!cred) {
-		g_warning ("couldn't save unlock password in login collection: %s", derr.message);
-		dbus_error_free (&derr);
+	if (!gkm_attributes_find_ulong (attrs, n_attrs, CKA_CLASS, &klass))
+		return;
+
+	if (klass == CKO_G_COLLECTION) {
+		auto_unlock_attach_keyring (attrs, n_attrs, password);
+		return;
 	}
+
+	unique = auto_unlock_object_unique (attrs, n_attrs);
+	if (unique == NULL)
+		return;
+
+	if (!gkm_attributes_find_string (attrs, n_attrs, CKA_LABEL, &label))
+		label = g_strdup (unique);
+
+	gkm_wrap_login_attach_secret (label, password, "unique", unique, NULL);
+	g_free (unique);
+	g_free (label);
 }
 
-/* Save it to the login keyring */
-if (!transient)
-	attach_unlock_to_login (collection, master);
+static void
+auto_unlock_attach_token (CK_TOKEN_INFO_PTR info, const gchar *password)
+{
+	gchar *manufacturer;
+	gchar *serial;
+	gchar *label;
 
-/* Or try to use login keyring's passwords */
-} else {
-	attrs = attributes_for_collection (coll);
-	location = location_string_for_attributes (attrs);
-	gp11_attributes_unref (attrs);
+	g_assert (info);
 
-	if (location) {
-		password = gkd_login_lookup_secret ("keyring", location, NULL);
-		g_free (location);
+	if (!password)
+		return;
 
-		if (password) {
-			if (gkd_secret_unlock_with_password (coll, NULL, 0, NULL))
-				locked = FALSE;
-			egg_secure_strfree (password);
-		}
+	manufacturer = g_strndup ((gchar*)info->manufacturerID, sizeof (info->manufacturerID));
+	g_strchomp (manufacturer);
+
+	serial = g_strndup ((gchar*)info->serialNumber, sizeof (info->serialNumber));
+	g_strchomp (serial);
+
+	label = g_strndup ((gchar*)info->label, sizeof (info->label));
+	g_strchomp (label);
+
+	if (g_str_equal (label, "")) {
+		g_free (label);
+		label = g_strdup (manufacturer);
 	}
 
-#endif
+	if (!g_str_equal (manufacturer, "") && !g_str_equal (serial, ""))
+		gkm_wrap_login_attach_secret (label, password,
+		                              "manufacturer", manufacturer,
+		                              "serial-number", serial,
+		                              NULL);
 
-static GkuPrompt*
-on_prompt_attention (gpointer user_data)
+	g_free (manufacturer);
+	g_free (serial);
+	g_free (label);
+}
+
+static void
+auto_unlock_remove_keyring (CK_ATTRIBUTE_PTR attrs, CK_ULONG n_attrs)
 {
-	/* We passed the prompt as the argument */
-	return g_object_ref (user_data);
+	gchar *location;
+
+	location = auto_unlock_keyring_location (attrs, n_attrs);
+	if (location == NULL)
+		return;
+
+	gkm_wrap_login_remove_secret ("keyring", location, NULL);
+	g_free (location);
 }
 
-static gpointer
-pool_alloc (GkmWrapPrompt *self, gsize length)
+static void
+auto_unlock_remove_object (CK_ATTRIBUTE_PTR attrs, CK_ULONG n_attrs)
 {
-	gpointer memory = g_malloc0 (length);
+	CK_OBJECT_CLASS klass;
+	gchar *unique;
 
-	g_assert (GKM_WRAP_IS_PROMPT (self));
-	g_queue_push_tail (&self->pool, memory);
-	return memory;
+	if (!gkm_attributes_find_ulong (attrs, n_attrs, CKA_CLASS, &klass))
+		return;
+
+	if (klass == CKO_G_COLLECTION) {
+		auto_unlock_remove_keyring (attrs, n_attrs);
+		return;
+	}
+
+	unique = auto_unlock_object_unique (attrs, n_attrs);
+	if (unique == NULL)
+		return;
+
+	gkm_wrap_login_remove_secret ("unique", unique, NULL);
+	g_free (unique);
 }
 
-static gpointer
-pool_dup (GkmWrapPrompt *self, gconstpointer original, gsize length)
+static void
+auto_unlock_remove_token (CK_TOKEN_INFO_PTR info)
 {
-	gpointer memory = pool_alloc (self, length);
-	memcpy (memory, original, length);
-	return memory;
+	gchar *manufacturer;
+	gchar *serial;
+
+	g_assert (info);
+
+	manufacturer = g_strndup ((gchar*)info->manufacturerID, sizeof (info->manufacturerID));
+	g_strchomp (manufacturer);
+
+	serial = g_strndup ((gchar*)info->serialNumber, sizeof (info->serialNumber));
+	g_strchomp (serial);
+
+	if (!g_str_equal (manufacturer, "") && !g_str_equal (serial, ""))
+		gkm_wrap_login_remove_secret ("manufacturer", manufacturer,
+		                              "serial-number", serial,
+		                              NULL);
+
+	g_free (manufacturer);
+	g_free (serial);
+}
+
+/* -----------------------------------------------------------------------------------------
+ * PROMPTING
+ */
+
+static GkuPrompt*
+on_prompt_attention (gpointer user_data)
+{
+	/* We passed the prompt as the argument */
+	return g_object_ref (user_data);
 }
 
 static CK_ATTRIBUTE_PTR
@@ -260,6 +448,9 @@ get_unlock_options_from_prompt (GkmWrapPrompt *self, CK_ULONG_PTR n_options)
 	g_assert (GKM_WRAP_IS_PROMPT (self));
 	g_assert (n_options);
 
+	if (!gku_prompt_has_response (GKU_PROMPT (self)))
+		return NULL;
+
 	*n_options = 4;
 	options = pool_alloc (self, sizeof (CK_ATTRIBUTE) * (*n_options));
 
@@ -316,8 +507,8 @@ set_unlock_options_on_prompt (GkmWrapPrompt *self, CK_ATTRIBUTE_PTR options, CK_
 static CK_ATTRIBUTE_PTR
 get_attributes_from_object (GkmWrapPrompt *self, CK_ULONG *n_attrs)
 {
-	CK_ATTRIBUTE attrs[3];
-	CK_ULONG i, count = 3;
+	CK_ATTRIBUTE attrs[5];
+	CK_ULONG i;
 	CK_RV rv;
 
 	g_assert (GKM_WRAP_IS_PROMPT (self));
@@ -328,8 +519,10 @@ get_attributes_from_object (GkmWrapPrompt *self, CK_ULONG *n_attrs)
 	attrs[0].type = CKA_LABEL;
 	attrs[1].type = CKA_ID;
 	attrs[2].type = CKA_CLASS;
+	attrs[3].type = CKA_G_LOGIN_COLLECTION;
+	attrs[4].type = CKA_GNOME_UNIQUE;
 
-	rv = (self->module->C_GetAttributeValue) (self->session, self->object, attrs, count);
+	rv = (self->module->C_GetAttributeValue) (self->session, self->object, attrs, G_N_ELEMENTS (attrs));
 	if (rv != CKR_OK && rv != CKR_ATTRIBUTE_TYPE_INVALID) {
 		g_warning ("Couldn't retrieve information about object to unlock: %s",
 		           gkm_util_rv_to_string (rv));
@@ -337,24 +530,33 @@ get_attributes_from_object (GkmWrapPrompt *self, CK_ULONG *n_attrs)
 	}
 
 	/* Allocate for each value, note we're null terminating values */
-	for (i = 0; i < count; ++i) {
+	for (i = 0; i < G_N_ELEMENTS (attrs); ++i) {
 		if (attrs[i].ulValueLen != (CK_ULONG)-1)
 			attrs[i].pValue = pool_alloc (self, attrs[i].ulValueLen + 1);
 	}
 
 	/* Now get the actual values */
-	rv = (self->module->C_GetAttributeValue) (self->session, self->object, attrs, count);
+	rv = (self->module->C_GetAttributeValue) (self->session, self->object, attrs, G_N_ELEMENTS (attrs));
 	if (rv != CKR_OK && rv != CKR_ATTRIBUTE_TYPE_INVALID) {
 		g_warning ("couldn't retrieve credential template for prompt: %s",
 		           gkm_util_rv_to_string (rv));
 		return NULL;
 	}
 
-	*n_attrs = count;
+	*n_attrs = G_N_ELEMENTS (attrs);
 	return pool_dup (self, attrs, sizeof (attrs));
 
 }
 
+static gboolean
+get_info_for_token (GkmWrapPrompt *self, CK_TOKEN_INFO_PTR tinfo)
+{
+	CK_SESSION_INFO sinfo;
+
+	return (self->module->C_GetSessionInfo) (self->session, &sinfo) == CKR_OK &&
+	       (self->module->C_GetTokenInfo) (sinfo.slotID, tinfo) == CKR_OK;
+}
+
 static void
 prepare_unlock_keyring_login (GkmWrapPrompt *self)
 {
@@ -370,12 +572,9 @@ prepare_unlock_keyring_login (GkmWrapPrompt *self)
 	text = _("Enter password for to unlock your login keyring");
 	gku_prompt_set_primary_text (prompt, text);
 
-#if 0
-	/* TODO: Reimplement this */
-	if (gkd_login_did_unlock_fail ())
+	if (gkm_wrap_login_did_unlock_fail ())
 		text = _("The password you use to log in to your computer no longer matches that of your login keyring.");
 	else
-#endif
 		text = _("The login keyring did not get unlocked when you logged into your computer.");
 	gku_prompt_set_secondary_text (prompt, text);
 
@@ -384,8 +583,6 @@ prepare_unlock_keyring_login (GkmWrapPrompt *self)
 	gku_prompt_show_widget (prompt, "password_area");
 }
 
-
-
 static void
 prepare_unlock_keyring_other (GkmWrapPrompt *self, const gchar *label)
 {
@@ -413,11 +610,9 @@ prepare_unlock_keyring_other (GkmWrapPrompt *self, const gchar *label)
 	gku_prompt_show_widget (prompt, "lock_area");
 	gku_prompt_show_widget (prompt, "options_area");
 
-#if 0 /* TODO: Implement */
-	if (gkd_login_is_usable ())
+	if (gkm_wrap_login_is_usable ())
 		gku_prompt_show_widget (prompt, "auto_unlock_check");
 	else
-#endif
 		gku_prompt_hide_widget (prompt, "auto_unlock_check");
 }
 
@@ -498,14 +693,14 @@ prepare_unlock_object (GkmWrapPrompt *self, const gchar *label, CK_OBJECT_CLASS
 }
 
 static void
-prepare_unlock_prompt (GkmWrapPrompt *self, gboolean first)
+prepare_unlock_prompt (GkmWrapPrompt *self, CK_ATTRIBUTE_PTR attrs,
+                       CK_ULONG n_attrs, gboolean first)
 {
-	CK_ATTRIBUTE_PTR attrs;
 	CK_ATTRIBUTE_PTR attr;
-	CK_ULONG n_attrs;
 	GkuPrompt *prompt;
 	const gchar *label = NULL;
 	CK_OBJECT_CLASS klass;
+	gboolean is_login = FALSE;
 
 	g_assert (GKM_WRAP_IS_PROMPT (self));
 
@@ -514,10 +709,6 @@ prepare_unlock_prompt (GkmWrapPrompt *self, gboolean first)
 	/* Hard reset on first prompt, soft on later */
 	gku_prompt_reset (GKU_PROMPT (prompt), first);
 
-	/* Load up all the values, note they're null terminated */
-	attrs = get_attributes_from_object (self, &n_attrs);
-	g_return_if_fail (attrs);
-
 	/* Load up the object class */
 	if (!gkm_attributes_find_ulong (attrs, n_attrs, CKA_CLASS, &klass))
 		klass = (CK_ULONG)-1;
@@ -536,12 +727,10 @@ prepare_unlock_prompt (GkmWrapPrompt *self, gboolean first)
 		label = _("Unnamed");
 
 	if (klass == CKO_G_COLLECTION) {
-		if (attr && attr->pValue && attr->ulValueLen == 5 &&
-		    memcmp (attr->pValue, "login", 5)) {
+		if (gkm_attributes_find_boolean (attrs, n_attrs, CKA_G_LOGIN_COLLECTION, &is_login) && is_login)
 			prepare_unlock_keyring_login (self);
-		} else {
+		else
 			prepare_unlock_keyring_other (self, label);
-		}
 	} else {
 		prepare_unlock_object (self, label, klass);
 	}
@@ -576,13 +765,11 @@ prepare_unlock_token (GkmWrapPrompt *self, CK_TOKEN_INFO_PTR tinfo)
 	gku_prompt_set_secondary_text (prompt, text);
 	g_free (text);
 
-#if 0
-	if (gkd_login_is_usable ()) {
+	if (gkm_wrap_login_is_usable ()) {
 		gku_prompt_show_widget (prompt, "details_area");
 		gku_prompt_show_widget (prompt, "lock_area");
 		gku_prompt_hide_widget (prompt, "options_area");
 	}
-#endif
 
 	g_free (label);
 }
@@ -693,8 +880,9 @@ gkm_wrap_prompt_do_credential (GkmWrapPrompt *self, CK_ATTRIBUTE_PTR *template,
                                CK_ULONG *n_template)
 {
 	CK_ATTRIBUTE_PTR options;
+	CK_ATTRIBUTE_PTR attrs;
 	CK_ATTRIBUTE_PTR attr;
-	CK_ULONG n_options, i;
+	CK_ULONG n_attrs, n_options, i;
 	CredentialPrompt *data;
 
 	g_return_val_if_fail (GKM_WRAP_IS_PROMPT (self), FALSE);
@@ -704,18 +892,33 @@ gkm_wrap_prompt_do_credential (GkmWrapPrompt *self, CK_ATTRIBUTE_PTR *template,
 	g_assert (self->destroy_data == credential_prompt_free);
 	data = self->prompt_data;
 
-	prepare_unlock_prompt (self, data->iteration == 0);
-	++(data->iteration);
+	attrs = get_attributes_from_object (self, &n_attrs);
+	g_return_val_if_fail (attrs, FALSE);
 
-	gku_prompt_request_attention_sync (NULL, on_prompt_attention,
-	                                   g_object_ref (self), g_object_unref);
+	egg_secure_free (data->password);
+	data->password = NULL;
 
-	if (gku_prompt_get_response (GKU_PROMPT (self)) != GKU_RESPONSE_OK)
-		return FALSE;
+	if (self->iteration == 0) {
+		++(self->iteration);
+		data->password = auto_unlock_lookup_object (attrs, n_attrs);
 
-	egg_secure_strfree (data->password);
-	data->password = gku_prompt_get_password (GKU_PROMPT (self), "password");
-	g_return_val_if_fail (data->password, FALSE);
+	} else if (self->iteration == 1) {
+		auto_unlock_remove_object (attrs, n_attrs);
+	}
+
+	if (!data->password) {
+		prepare_unlock_prompt (self, attrs, n_attrs, self->iteration == 1);
+		++(self->iteration);
+
+		gku_prompt_request_attention_sync (NULL, on_prompt_attention,
+		                                   g_object_ref (self), g_object_unref);
+
+		if (gku_prompt_get_response (GKU_PROMPT (self)) != GKU_RESPONSE_OK)
+			return FALSE;
+
+		data->password = gku_prompt_get_password (GKU_PROMPT (self), "password");
+		g_return_val_if_fail (data->password, FALSE);
+	}
 
 	/* Truncate any extra options off the end of template */
 	g_assert (data->n_template > 0);
@@ -742,24 +945,25 @@ void
 gkm_wrap_prompt_done_credential (GkmWrapPrompt *self, CK_RV call_result)
 {
 	CK_ATTRIBUTE_PTR options;
-	CK_ULONG n_options;
-	gint value = 0;
+	CK_ATTRIBUTE_PTR attrs;
+	CK_ULONG n_options, n_attrs;
+	CredentialPrompt *data;
 
 	g_return_if_fail (GKM_WRAP_IS_PROMPT (self));
 
+	g_assert (self->destroy_data == credential_prompt_free);
+	data = self->prompt_data;
+
 	/* Save the options, and possibly auto unlock */
 	if (call_result == CKR_OK) {
 		options = get_unlock_options_from_prompt (self, &n_options);
 		if (options != NULL)
 			set_unlock_options_on_object (self, options, n_options);
 
-		if (gku_prompt_get_unlock_option (GKU_PROMPT (self), GKU_UNLOCK_AUTO, &value) && value) {
-			g_assert_not_reached (); /* TODO: Need to implement */
+		if (auto_unlock_should_attach (self)) {
+			attrs = get_attributes_from_object (self, &n_attrs);
+			auto_unlock_attach_object (attrs, n_attrs, data->password);
 		}
-
-	/* Make sure to remove any auto-unlock when we fail */
-	} else if (call_result == CKR_PIN_INCORRECT) {
-		/* TODO: Implement removal from login keyring */
 	}
 }
 
@@ -853,29 +1057,62 @@ static gboolean
 login_prompt_do_specific (GkmWrapPrompt *self, CK_RV last_result,
                           CK_UTF8CHAR_PTR *pin, CK_ULONG *n_pin)
 {
+	CK_ATTRIBUTE_PTR attrs;
+	CK_ULONG n_attrs;
+
 	g_assert (GKM_WRAP_IS_PROMPT (self));
 	g_assert (pin);
 	g_assert (n_pin);
 
-	prepare_unlock_prompt (self, *pin == NULL);
+	g_assert (self->destroy_data == (GDestroyNotify)egg_secure_strfree);
+	egg_secure_strfree (self->prompt_data);
+	self->prompt_data = NULL;
 
-	gku_prompt_request_attention_sync (NULL, on_prompt_attention,
-	                                   g_object_ref (self), g_object_unref);
+	attrs = get_attributes_from_object (self, &n_attrs);
+	g_return_val_if_fail (attrs, FALSE);
 
-	if (gku_prompt_get_response (GKU_PROMPT (self)) != GKU_RESPONSE_OK)
-		return FALSE;
+	if (self->iteration == 0) {
+		++(self->iteration);
+		self->prompt_data = auto_unlock_lookup_object (attrs, n_attrs);
 
-	g_assert (self->destroy_data == (GDestroyNotify)egg_secure_strfree);
-	egg_secure_strfree (self->prompt_data);
+	} else if (self->iteration == 1 && last_result == CKR_PIN_INCORRECT) {
+		auto_unlock_remove_object (attrs, n_attrs);
+	}
+
+	if (!self->prompt_data) {
+		prepare_unlock_prompt (self, attrs, n_attrs, self->iteration == 1);
 
-	self->prompt_data = gku_prompt_get_password (GKU_PROMPT (self), "password");
-	g_return_val_if_fail (self->prompt_data, FALSE);
+		gku_prompt_request_attention_sync (NULL, on_prompt_attention,
+		                                   g_object_ref (self), g_object_unref);
+
+		if (gku_prompt_get_response (GKU_PROMPT (self)) != GKU_RESPONSE_OK)
+			return FALSE;
+
+		self->prompt_data = gku_prompt_get_password (GKU_PROMPT (self), "password");
+		g_return_val_if_fail (self->prompt_data, FALSE);
+	}
 
 	*pin = self->prompt_data;
 	*n_pin = strlen (self->prompt_data);
 	return TRUE;
 }
 
+static void
+login_prompt_done_specific (GkmWrapPrompt *self, CK_RV call_result)
+{
+	CK_ATTRIBUTE_PTR attrs;
+	CK_ULONG n_attrs;
+
+	g_assert (GKM_WRAP_IS_PROMPT (self));
+	g_assert (self->destroy_data == (GDestroyNotify)egg_secure_strfree);
+
+	/* Possibly save away auto unlock */
+	if (call_result == CKR_OK && auto_unlock_should_attach (self)) {
+		attrs = get_attributes_from_object (self, &n_attrs);
+		auto_unlock_attach_object (attrs, n_attrs, self->prompt_data);
+	}
+}
+
 static GkmWrapPrompt*
 login_prompt_for_user (CK_FUNCTION_LIST_PTR module, CK_SESSION_HANDLE session)
 {
@@ -896,45 +1133,65 @@ static gboolean
 login_prompt_do_user (GkmWrapPrompt *self, CK_RV last_result,
                        CK_UTF8CHAR_PTR *pin, CK_ULONG *n_pin)
 {
-	CK_SESSION_INFO sinfo;
 	CK_TOKEN_INFO tinfo;
-	CK_RV rv;
 
 	g_assert (GKM_WRAP_IS_PROMPT (self));
 	g_assert (self->module);
 	g_assert (pin);
 	g_assert (n_pin);
 
-	rv = (self->module->C_GetSessionInfo) (self->session, &sinfo);
-	if (rv != CKR_OK)
-		return FALSE;
+	g_assert (self->destroy_data == (GDestroyNotify)egg_secure_strfree);
+	egg_secure_strfree (self->prompt_data);
+	self->prompt_data = NULL;
 
-	rv = (self->module->C_GetTokenInfo) (sinfo.slotID, &tinfo);
-	if (rv != CKR_OK)
+	if (!get_info_for_token (self, &tinfo))
 		return FALSE;
 
-	/* Hard reset on first prompt, soft on later */
-	gku_prompt_reset (GKU_PROMPT (self), last_result == CKR_OK);
+	if (self->iteration == 0) {
+		++(self->iteration);
+		self->prompt_data = auto_unlock_lookup_token (&tinfo);
 
-	prepare_unlock_token (self, &tinfo);
+	} else if (self->iteration == 1 && last_result == CKR_PIN_INCORRECT) {
+		auto_unlock_remove_token (&tinfo);
+	}
 
-	gku_prompt_request_attention_sync (NULL, on_prompt_attention,
-	                                   g_object_ref (self), g_object_unref);
+	if (!self->prompt_data) {
+		/* Hard reset on first prompt, soft on later */
+		gku_prompt_reset (GKU_PROMPT (self), last_result == CKR_OK);
 
-	if (gku_prompt_get_response (GKU_PROMPT (self)) != GKU_RESPONSE_OK)
-		return FALSE;
+		prepare_unlock_token (self, &tinfo);
 
-	g_assert (self->destroy_data == (GDestroyNotify)egg_secure_strfree);
-	egg_secure_strfree (self->prompt_data);
+		gku_prompt_request_attention_sync (NULL, on_prompt_attention,
+		                                   g_object_ref (self), g_object_unref);
+
+		if (gku_prompt_get_response (GKU_PROMPT (self)) != GKU_RESPONSE_OK)
+			return FALSE;
 
-	self->prompt_data = gku_prompt_get_password (GKU_PROMPT (self), "password");
-	g_return_val_if_fail (self->prompt_data, FALSE);
+		self->prompt_data = gku_prompt_get_password (GKU_PROMPT (self), "password");
+		g_return_val_if_fail (self->prompt_data, FALSE);
+	}
 
 	*pin = self->prompt_data;
 	*n_pin = strlen (self->prompt_data);
 	return TRUE;
 }
 
+static void
+login_prompt_done_user (GkmWrapPrompt *self, CK_RV call_result)
+{
+	CK_TOKEN_INFO tinfo;
+
+	g_assert (GKM_WRAP_IS_PROMPT (self));
+	g_assert (self->destroy_data == (GDestroyNotify)egg_secure_strfree);
+
+	/* Save the options, and possibly auto unlock */
+	if (call_result == CKR_OK && auto_unlock_should_attach (self)) {
+		if (get_info_for_token (self, &tinfo))
+			auto_unlock_attach_token (&tinfo, self->prompt_data);
+	}
+}
+
+
 GkmWrapPrompt*
 gkm_wrap_prompt_for_login (CK_FUNCTION_LIST_PTR module, CK_USER_TYPE user_type,
                            CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object,
@@ -978,7 +1235,6 @@ gkm_wrap_prompt_done_login (GkmWrapPrompt *self, CK_USER_TYPE user_type, CK_RV c
 {
 	g_return_if_fail (GKM_WRAP_IS_PROMPT (self));
 
-#if 0
 	switch (user_type) {
 	case CKU_CONTEXT_SPECIFIC:
 		login_prompt_done_specific (self, call_result);
@@ -987,5 +1243,4 @@ gkm_wrap_prompt_done_login (GkmWrapPrompt *self, CK_USER_TYPE user_type, CK_RV c
 		login_prompt_done_user (self, call_result);
 		break;
 	}
-#endif
 }
diff --git a/pkcs11/wrap-layer/tests/Makefile.am b/pkcs11/wrap-layer/tests/Makefile.am
index ae531a6..bcc73cc 100644
--- a/pkcs11/wrap-layer/tests/Makefile.am
+++ b/pkcs11/wrap-layer/tests/Makefile.am
@@ -1,10 +1,12 @@
 
 TESTING_FILES = \
+	mock-secret-store.c \
 	test-create-credential.c \
-	test-login-user.c \
-	test-login-specific.c
-
-UNIT_PROMPT =
+	test-login-auto.c \
+	test-login-hints.c \
+	test-login-keyring.c \
+	test-login-specific.c \
+	test-login-user.c
 
 TESTING_LIBS = \
 	$(top_builddir)/pkcs11/wrap-layer/libgkm-wrap-layer.la \
diff --git a/pkcs11/wrap-layer/tests/mock-secret-store.c b/pkcs11/wrap-layer/tests/mock-secret-store.c
new file mode 100644
index 0000000..d724919
--- /dev/null
+++ b/pkcs11/wrap-layer/tests/mock-secret-store.c
@@ -0,0 +1,286 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2010 Stefan Walter
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include "test-suite.h"
+
+#include "gkm/gkm-attributes.h"
+#include "gkm/gkm-mock.h"
+#include "gkm/gkm-test.h"
+
+#include "wrap-layer/gkm-wrap-layer.h"
+
+#include "ui/gku-prompt.h"
+
+static guint secret_identifier = 8800;
+
+static CK_RV
+mock_secret_C_Initialize (CK_VOID_PTR pInitArgs)
+{
+	GArray *attrs;
+	CK_RV rv;
+
+	rv = gkm_mock_C_Initialize (pInitArgs);
+	if (rv != CKR_OK)
+		return rv;
+
+	attrs = gkm_template_new (NULL, 0);
+	gkm_template_set_ulong (attrs, CKA_CLASS, CKO_G_COLLECTION);
+	gkm_template_set_boolean (attrs, CKA_G_LOGIN_COLLECTION, CK_TRUE);
+	gkm_template_set_string (attrs, CKA_ID, "login");
+	gkm_template_set_string (attrs, CKA_LABEL, "Login Keyring");
+	gkm_template_set_boolean (attrs, CKA_TRUSTED, CK_TRUE);
+	gkm_template_set_boolean (attrs, CKA_G_LOCKED, CK_FALSE);
+	gkm_template_set_boolean (attrs, CKA_TOKEN, CK_TRUE);
+	gkm_mock_module_take_object (attrs);
+
+	attrs = gkm_template_new (NULL, 0);
+	gkm_template_set_ulong (attrs, CKA_CLASS, CKO_G_COLLECTION);
+	gkm_template_set_boolean (attrs, CKA_G_LOGIN_COLLECTION, CK_FALSE);
+	gkm_template_set_string (attrs, CKA_ID, "other");
+	gkm_template_set_string (attrs, CKA_LABEL, "Other Keyring");
+	gkm_template_set_boolean (attrs, CKA_TRUSTED, CK_TRUE);
+	gkm_template_set_boolean (attrs, CKA_G_LOCKED, CK_TRUE);
+	gkm_template_set_boolean (attrs, CKA_TOKEN, CK_TRUE);
+	gkm_mock_module_take_object (attrs);
+
+	attrs = gkm_template_new (NULL, 0);
+	gkm_template_set_string (attrs, CKA_G_COLLECTION, "login");
+	gkm_template_set_string (attrs, CKA_ID, "23");
+	gkm_template_set_string (attrs, CKA_LABEL, "Unlock password for: Mock");
+	gkm_template_set_ulong (attrs, CKA_CLASS, CKO_SECRET_KEY);
+	gkm_template_set_value (attrs, CKA_G_FIELDS, "one\0" "1\0" "two\0" "2\0", 12);
+	gkm_template_set_string (attrs, CKA_VALUE, "mock");
+	gkm_template_set_boolean (attrs, CKA_TOKEN, CK_TRUE);
+	gkm_template_set_boolean (attrs, CKA_G_LOCKED, CK_FALSE);
+	gkm_mock_module_take_object (attrs);
+
+	return CKR_OK;
+}
+
+typedef struct {
+	GArray *template;
+	GArray *objects;
+} FieldSearch;
+
+static gboolean
+match_fields (gconstpointer fields, gsize n_fields, gconstpointer all, gsize n_all)
+{
+	const guchar *field;
+	gsize n_field;
+	const guchar *ptr;
+	const guchar *last;
+
+	g_assert (all);
+	g_assert (fields);
+
+	ptr = fields;
+	last = ptr + n_fields;
+
+	g_assert (ptr || last == ptr);
+	while (ptr && ptr != last) {
+		g_assert (ptr < last);
+
+		field = ptr;
+		ptr = memchr (ptr, 0, last - ptr);
+		g_assert (ptr);
+		++ptr;
+
+		ptr = memchr (ptr, 0, last - ptr);
+		g_assert (ptr);
+		++ptr;
+
+		n_field = ptr - field;
+		if (!memmem (all, n_all, field, n_field))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+enumerate_field_search (CK_OBJECT_HANDLE handle, GArray *attrs, gpointer user_data)
+{
+	FieldSearch *ctx = user_data;
+	CK_ATTRIBUTE_PTR tattr;
+	CK_ATTRIBUTE_PTR oattr;
+
+	tattr = gkm_template_find (ctx->template, CKA_G_FIELDS);
+	g_assert (tattr);
+
+	oattr = gkm_template_find (attrs, CKA_G_FIELDS);
+	if (!oattr)
+		return TRUE; /* Continue */
+	if (!match_fields (tattr->pValue, tattr->ulValueLen, oattr->pValue, oattr->ulValueLen))
+		return TRUE; /* Continue */
+
+	tattr = gkm_template_find (ctx->template, CKA_G_COLLECTION);
+	if (tattr) {
+		oattr = gkm_template_find (attrs, CKA_G_COLLECTION);
+		if (!oattr || oattr->ulValueLen != tattr->ulValueLen)
+			return TRUE; /* Continue */
+		if (memcmp (oattr->pValue, tattr->pValue, oattr->ulValueLen) != 0)
+			return TRUE; /* Continue */
+	}
+
+	/* Add it to the set */
+	g_array_append_val (ctx->objects, handle);
+	return TRUE; /* Continue */
+}
+
+static CK_RV
+mock_secret_C_CreateObject (CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate,
+                            CK_ULONG ulCount, CK_OBJECT_HANDLE_PTR phObject)
+{
+	GArray *template = NULL;
+	CK_OBJECT_CLASS klass;
+	CK_ATTRIBUTE_PTR attr;
+	gchar *text;
+	CK_RV rv;
+
+	g_return_val_if_fail (phObject, CKR_ARGUMENTS_BAD);
+
+	if (!gkm_attributes_find_ulong (pTemplate, ulCount, CKA_CLASS, &klass))
+		klass = (CK_ULONG)-1;
+
+	/* Is it a search object? */
+	if (klass == CKO_G_SEARCH) {
+
+		FieldSearch ctx;
+		ctx.template = template = gkm_template_new (pTemplate, ulCount);
+		ctx.objects = g_array_new (FALSE, FALSE, sizeof (CK_OBJECT_HANDLE));
+
+		/* Find all the fields */
+		gkm_mock_module_enumerate_objects (hSession, enumerate_field_search, &ctx);
+
+		gkm_template_set_value (template, CKA_G_MATCHED, ctx.objects->data,
+		                        ctx.objects->len * sizeof (CK_OBJECT_HANDLE));
+		g_array_free (ctx.objects, TRUE);
+
+	} else if (klass == CKO_SECRET_KEY) {
+
+		/* If it's a secret key and id set, just overwrite */
+		attr = gkm_attributes_find (pTemplate, ulCount, CKA_ID);
+		if (attr) {
+			CK_ATTRIBUTE attrs[2];
+
+			memcpy (attrs, attr, sizeof (CK_ATTRIBUTE));
+			attrs[1].type = CKA_CLASS;
+			attrs[1].ulValueLen = sizeof (klass);
+			attrs[1].pValue = &klass;
+
+			*phObject = gkm_mock_module_find_object (hSession, attrs, 2);
+			g_return_val_if_fail (*phObject, CKR_TEMPLATE_INCONSISTENT);
+			return gkm_mock_C_SetAttributeValue (hSession, *phObject, pTemplate, ulCount);
+		}
+
+		/* Otherwise add a unique identifier */
+		template = gkm_template_new (pTemplate, ulCount);
+		text = g_strdup_printf ("%d", ++secret_identifier);
+		gkm_template_set_string (template, CKA_ID, text);
+		g_free (text);
+	}
+
+	if (template) {
+		pTemplate = (CK_ATTRIBUTE_PTR)template->data;
+		ulCount = template->len;
+	}
+
+	rv = gkm_mock_C_CreateObject (hSession, pTemplate, ulCount, phObject);
+
+	if (template)
+		g_array_free (template, TRUE);
+
+	return rv;
+}
+
+CK_FUNCTION_LIST mock_secret_store = {
+	{ 2, 11 },	/* version */
+	mock_secret_C_Initialize,
+	gkm_mock_C_Finalize,
+	gkm_mock_C_GetInfo,
+	gkm_mock_C_GetFunctionList,
+	gkm_mock_C_GetSlotList,
+	gkm_mock_C_GetSlotInfo,
+	gkm_mock_C_GetTokenInfo,
+	gkm_mock_C_GetMechanismList,
+	gkm_mock_C_GetMechanismInfo,
+	gkm_mock_C_InitToken,
+	gkm_mock_C_InitPIN,
+	gkm_mock_C_SetPIN,
+	gkm_mock_C_OpenSession,
+	gkm_mock_C_CloseSession,
+	gkm_mock_C_CloseAllSessions,
+	gkm_mock_C_GetSessionInfo,
+	gkm_mock_unsupported_C_GetOperationState,
+	gkm_mock_unsupported_C_SetOperationState,
+	gkm_mock_C_Login,
+	gkm_mock_C_Logout,
+	mock_secret_C_CreateObject,
+	gkm_mock_unsupported_C_CopyObject,
+	gkm_mock_C_DestroyObject,
+	gkm_mock_unsupported_C_GetObjectSize,
+	gkm_mock_C_GetAttributeValue,
+	gkm_mock_C_SetAttributeValue,
+	gkm_mock_C_FindObjectsInit,
+	gkm_mock_C_FindObjects,
+	gkm_mock_C_FindObjectsFinal,
+	gkm_mock_C_EncryptInit,
+	gkm_mock_C_Encrypt,
+	gkm_mock_unsupported_C_EncryptUpdate,
+	gkm_mock_unsupported_C_EncryptFinal,
+	gkm_mock_C_DecryptInit,
+	gkm_mock_C_Decrypt,
+	gkm_mock_unsupported_C_DecryptUpdate,
+	gkm_mock_unsupported_C_DecryptFinal,
+	gkm_mock_unsupported_C_DigestInit,
+	gkm_mock_unsupported_C_Digest,
+	gkm_mock_unsupported_C_DigestUpdate,
+	gkm_mock_unsupported_C_DigestKey,
+	gkm_mock_unsupported_C_DigestFinal,
+	gkm_mock_C_SignInit,
+	gkm_mock_C_Sign,
+	gkm_mock_unsupported_C_SignUpdate,
+	gkm_mock_unsupported_C_SignFinal,
+	gkm_mock_unsupported_C_SignRecoverInit,
+	gkm_mock_unsupported_C_SignRecover,
+	gkm_mock_C_VerifyInit,
+	gkm_mock_C_Verify,
+	gkm_mock_unsupported_C_VerifyUpdate,
+	gkm_mock_unsupported_C_VerifyFinal,
+	gkm_mock_unsupported_C_VerifyRecoverInit,
+	gkm_mock_unsupported_C_VerifyRecover,
+	gkm_mock_unsupported_C_DigestEncryptUpdate,
+	gkm_mock_unsupported_C_DecryptDigestUpdate,
+	gkm_mock_unsupported_C_SignEncryptUpdate,
+	gkm_mock_unsupported_C_DecryptVerifyUpdate,
+	gkm_mock_unsupported_C_GenerateKey,
+	gkm_mock_unsupported_C_GenerateKeyPair,
+	gkm_mock_unsupported_C_WrapKey,
+	gkm_mock_unsupported_C_UnwrapKey,
+	gkm_mock_unsupported_C_DeriveKey,
+	gkm_mock_unsupported_C_SeedRandom,
+	gkm_mock_unsupported_C_GenerateRandom,
+	gkm_mock_C_GetFunctionStatus,
+	gkm_mock_C_CancelFunction,
+	gkm_mock_unsupported_C_WaitForSlotEvent
+};
diff --git a/pkcs11/wrap-layer/tests/test-create-credential.c b/pkcs11/wrap-layer/tests/test-create-credential.c
index 2b89bb4..17a0309 100644
--- a/pkcs11/wrap-layer/tests/test-create-credential.c
+++ b/pkcs11/wrap-layer/tests/test-create-credential.c
@@ -86,6 +86,8 @@ DEFINE_TEARDOWN (create_credential)
 {
 	CK_RV rv;
 
+	g_assert (!gku_prompt_dummy_have_response ());
+
 	object = 0;
 
 	rv = (module->C_CloseSession) (session);
diff --git a/pkcs11/wrap-layer/tests/test-login-auto.c b/pkcs11/wrap-layer/tests/test-login-auto.c
new file mode 100644
index 0000000..9b21649
--- /dev/null
+++ b/pkcs11/wrap-layer/tests/test-login-auto.c
@@ -0,0 +1,210 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2010 Stefan Walter
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include "test-suite.h"
+
+#include "gkm/gkm-mock.h"
+#include "gkm/gkm-test.h"
+
+#include "wrap-layer/gkm-wrap-layer.h"
+
+#include "ui/gku-prompt.h"
+
+extern CK_FUNCTION_LIST mock_secret_store;
+static CK_FUNCTION_LIST functions;
+static CK_FUNCTION_LIST_PTR module = NULL;
+static CK_SESSION_HANDLE session = 0;
+static CK_OBJECT_HANDLE key = 0;
+static CK_OBJECT_HANDLE collection = 0;
+static CK_MECHANISM mech = { CKM_MOCK_PREFIX, NULL, 0 };
+
+DEFINE_SETUP (login_auto)
+{
+	CK_SLOT_ID slot_id;
+	CK_ULONG n_slots = 1;
+	CK_ULONG count;
+	CK_RV rv;
+
+	CK_BBOOL always = TRUE;
+	CK_ATTRIBUTE kattrs[] = {
+		{ CKA_ALWAYS_AUTHENTICATE, &always, sizeof (always) }
+	};
+
+	CK_OBJECT_CLASS fklass = CKO_G_COLLECTION;
+	CK_ATTRIBUTE fattrs[] = {
+		{ CKA_CLASS, &fklass, sizeof (fklass) },
+		{ CKA_ID, "other", 5 },
+	};
+
+	/* Always start off with test functions */
+	memcpy (&functions, &mock_secret_store, sizeof (functions));
+
+	gkm_wrap_layer_reset_modules ();
+	gkm_wrap_layer_add_module (&functions);
+	module = gkm_wrap_layer_get_functions ();
+
+	gku_prompt_dummy_prepare_response ();
+
+	/* Open a session */
+	rv = (module->C_Initialize) (NULL);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+
+	rv = (module->C_GetSlotList) (CK_TRUE, &slot_id, &n_slots);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+
+	rv = (module->C_OpenSession) (slot_id, CKF_SERIAL_SESSION, NULL, NULL, &session);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+
+	/* Find keyring object */
+	rv = (module->C_FindObjectsInit) (session, fattrs, 1);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+	rv = (module->C_FindObjects) (session, &collection, 1, &count);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+	gkm_assert_cmpulong (count, ==, 1);
+	gkm_assert_cmpulong (collection, !=, 0);
+	rv = (module->C_FindObjectsFinal) (session);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+
+	/* Find the key object */
+	rv = (module->C_FindObjectsInit) (session, kattrs, 1);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+	rv = (module->C_FindObjects) (session, &key, 1, &count);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+	gkm_assert_cmpulong (count, ==, 1);
+	gkm_assert_cmpulong (key, !=, 0);
+	rv = (module->C_FindObjectsFinal) (session);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+
+	/* Start a signing operation, that needs to be authenticated */
+	rv = (module->C_SignInit) (session, &mech, key);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+}
+
+DEFINE_TEARDOWN (login_auto)
+{
+	CK_RV rv;
+
+	g_assert (!gku_prompt_dummy_have_response ());
+
+	key = 0;
+	collection = 0;
+
+	rv = (module->C_CloseSession) (session);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+	session = 0;
+
+	rv = (module->C_Finalize) (NULL);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+	module = NULL;
+}
+
+DEFINE_TEST (login_auto_specific)
+{
+	CK_RV rv;
+
+	/* Login with prompt */
+	gku_prompt_dummy_queue_auto_password ("booo");
+	rv = (module->C_Login) (session, CKU_CONTEXT_SPECIFIC, NULL, 0);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+
+	/* Start a signing operation, that needs to be authenticated */
+	rv = (module->C_SignInit) (session, &mech, key);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+
+	/* No further prompting should be shown, uses stored password */
+	gku_prompt_dummy_prepare_response ();
+	rv = (module->C_Login) (session, CKU_CONTEXT_SPECIFIC, NULL, 0);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+
+	/* Change the password */
+	gkm_mock_module_set_pin ("other");
+
+	/* Start a signing operation, that needs to be authenticated */
+	rv = (module->C_SignInit) (session, &mech, key);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+
+	/* This should prompt again, as stored password is now wrong */
+	gku_prompt_dummy_queue_ok_password ("other");
+	rv = (module->C_Login) (session, CKU_CONTEXT_SPECIFIC, NULL, 0);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+}
+
+DEFINE_TEST (login_auto_user_token)
+{
+	CK_RV rv;
+
+	/* Login with prompt */
+	gku_prompt_dummy_queue_auto_password ("booo");
+	rv = (module->C_Login) (session, CKU_USER, NULL, 0);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+	rv = (module->C_Logout) (session);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+
+	/* No further prompting should be shown, uses stored password */
+	gku_prompt_dummy_prepare_response ();
+	rv = (module->C_Login) (session, CKU_USER, NULL, 0);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+	rv = (module->C_Logout) (session);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+
+	/* Change the password */
+	gkm_mock_module_set_pin ("other");
+
+	/* This should prompt again, as stored password is now wrong */
+	gku_prompt_dummy_queue_ok_password ("other");
+	rv = (module->C_Login) (session, CKU_USER, NULL, 0);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+}
+
+DEFINE_TEST (login_auto_unlock_keyring)
+{
+	CK_OBJECT_HANDLE credential;
+	CK_RV rv;
+
+	CK_OBJECT_CLASS klass = CKO_G_CREDENTIAL;
+	CK_ATTRIBUTE attrs[] = {
+		{ CKA_CLASS, &klass, sizeof (klass) },
+		{ CKA_VALUE, NULL, 0 },
+		{ CKA_G_OBJECT, &collection, sizeof (collection) },
+	};
+
+	/* Create credential with prompt */
+	gku_prompt_dummy_queue_auto_password ("booo");
+	rv = (module->C_CreateObject) (session, attrs, G_N_ELEMENTS (attrs), &credential);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+	rv = (module->C_DestroyObject) (session, credential);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+
+	/* No further prompting should be shown, uses stored password */
+	gku_prompt_dummy_prepare_response ();
+	rv = (module->C_CreateObject) (session, attrs, G_N_ELEMENTS (attrs), &credential);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+
+	/* Change the password */
+	gkm_mock_module_set_pin ("other");
+
+	/* This should prompt again, as stored password is now wrong */
+	gku_prompt_dummy_queue_ok_password ("other");
+	rv = (module->C_CreateObject) (session, attrs, G_N_ELEMENTS (attrs), &credential);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+}
diff --git a/pkcs11/wrap-layer/tests/test-login-hints.c b/pkcs11/wrap-layer/tests/test-login-hints.c
new file mode 100644
index 0000000..d649c21
--- /dev/null
+++ b/pkcs11/wrap-layer/tests/test-login-hints.c
@@ -0,0 +1,44 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2010 Stefan Walter
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include "test-suite.h"
+
+#include "wrap-layer/gkm-wrap-layer.h"
+#include "wrap-layer/gkm-wrap-login.h"
+
+DEFINE_TEST (login_did_unlock_fail)
+{
+	gboolean ret;
+
+	gkm_wrap_layer_hint_login_unlock_failure ();
+
+	ret = gkm_wrap_login_did_unlock_fail ();
+	g_assert (ret == TRUE);
+
+	gkm_wrap_layer_hint_login_unlock_failure ();
+	gkm_wrap_layer_hint_login_unlock_failure ();
+	gkm_wrap_layer_hint_login_unlock_success ();
+
+	ret = gkm_wrap_login_did_unlock_fail ();
+	g_assert (ret == FALSE);
+}
diff --git a/pkcs11/wrap-layer/tests/test-login-keyring.c b/pkcs11/wrap-layer/tests/test-login-keyring.c
new file mode 100644
index 0000000..43c4f43
--- /dev/null
+++ b/pkcs11/wrap-layer/tests/test-login-keyring.c
@@ -0,0 +1,306 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2010 Stefan Walter
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include "test-suite.h"
+
+#include "egg/egg-secure-memory.h"
+
+#include "gkm/gkm-attributes.h"
+#include "gkm/gkm-mock.h"
+#include "gkm/gkm-test.h"
+
+#include "wrap-layer/gkm-wrap-layer.h"
+#include "wrap-layer/gkm-wrap-login.h"
+
+extern CK_FUNCTION_LIST mock_secret_store;
+static CK_FUNCTION_LIST functions;
+static CK_FUNCTION_LIST_PTR module = NULL;
+
+DEFINE_SETUP (login_keyring)
+{
+	CK_RV rv;
+
+	/* Always start off with test functions */
+	memcpy (&functions, &mock_secret_store, sizeof (functions));
+	gkm_wrap_layer_reset_modules ();
+	gkm_wrap_layer_add_module (&functions);
+	module = gkm_wrap_layer_get_functions ();
+
+	/* Initialize */
+	rv = (module->C_Initialize) (NULL);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+}
+
+DEFINE_TEARDOWN (login_keyring)
+{
+	CK_RV rv;
+
+	rv = (module->C_Finalize) (NULL);
+	gkm_assert_cmprv (rv, ==, CKR_OK);
+	module = NULL;
+}
+
+DEFINE_TEST (login_is_usable)
+{
+	gboolean ret;
+
+	ret = gkm_wrap_login_is_usable ();
+	g_assert (ret == TRUE);
+}
+
+DEFINE_TEST (login_usable_fail_open_session)
+{
+	gboolean ret;
+
+	functions.C_OpenSession = gkm_mock_fail_C_OpenSession;
+	ret = gkm_wrap_login_is_usable ();
+	g_assert (ret == FALSE);
+}
+
+DEFINE_TEST (login_usable_fail_not_trusted)
+{
+	CK_OBJECT_HANDLE object;
+	CK_ATTRIBUTE attr;
+	CK_BBOOL bval;
+	gboolean ret;
+
+	ret = gkm_wrap_login_is_usable ();
+	g_assert (ret == TRUE);
+
+	bval = CK_TRUE;
+	attr.type = CKA_G_LOGIN_COLLECTION;
+	attr.pValue = &bval;
+	attr.ulValueLen = sizeof (bval);
+
+	object = gkm_mock_module_find_object (0, &attr, 1);
+	gkm_assert_cmpulong (object, !=, 0);
+
+	bval = CK_FALSE;
+	attr.type = CKA_TRUSTED;
+	attr.pValue = &bval;
+	attr.ulValueLen = sizeof (bval);
+
+	gkm_mock_module_set_object (object, &attr, 1);
+
+	/* Not trusted, so no longer usable */
+	ret = gkm_wrap_login_is_usable ();
+	g_assert (ret == FALSE);
+}
+
+DEFINE_TEST (login_usable_fail_locked)
+{
+	CK_OBJECT_HANDLE object;
+	CK_ATTRIBUTE attr;
+	CK_BBOOL bval;
+	gboolean ret;
+
+	ret = gkm_wrap_login_is_usable ();
+	g_assert (ret == TRUE);
+
+	bval = CK_TRUE;
+	attr.type = CKA_G_LOGIN_COLLECTION;
+	attr.pValue = &bval;
+	attr.ulValueLen = sizeof (bval);
+
+	object = gkm_mock_module_find_object (0, &attr, 1);
+	gkm_assert_cmpulong (object, !=, 0);
+
+	bval = CK_TRUE;
+	attr.type = CKA_G_LOCKED;
+	attr.pValue = &bval;
+	attr.ulValueLen = sizeof (bval);
+
+	gkm_mock_module_set_object (object, &attr, 1);
+
+	/* Not unlocked, so no longer usable */
+	ret = gkm_wrap_login_is_usable ();
+	g_assert (ret == FALSE);
+}
+
+DEFINE_TEST (login_lookup_secret_no_match)
+{
+	gchar *password;
+
+	password = gkm_wrap_login_lookup_secret ("invalid", "attribute",
+	                                         "second", "attribute", NULL);
+	g_assert_cmpstr (password, ==, NULL);
+}
+
+DEFINE_TEST (login_lookup_secret_and_match)
+{
+	gchar *password;
+
+	/* Secret stored in mock-secret-store.c */
+	password = gkm_wrap_login_lookup_secret ("one", "1",
+	                                         "two", "2", NULL);
+	g_assert_cmpstr (password, ==, "mock");
+
+	egg_secure_free (password);
+}
+
+DEFINE_TEST (login_lookup_store_secret)
+{
+	CK_OBJECT_CLASS klass = CKO_SECRET_KEY;
+	CK_BBOOL tval = CK_TRUE;
+	CK_ATTRIBUTE attrs[] = {
+		{ CKA_LABEL, "Unlock password for: The label", 30 },
+		{ CKA_CLASS, &klass, sizeof (klass) },
+		{ CKA_VALUE, "the password", 12 },
+		{ CKA_TOKEN, &tval, sizeof (tval) },
+		{ CKA_G_COLLECTION, "login", 5 },
+		{ CKA_G_FIELDS, "one\0" "1\0" "three\0" "3\0", 14 },
+	};
+
+	CK_OBJECT_HANDLE object;
+
+	/* Secret stored in mock-secret-store.c */
+	gkm_wrap_login_attach_secret ("The label", "the password",
+	                              "one", "1",
+	                              "three", "3", NULL);
+
+	object = gkm_mock_module_find_object (0, attrs, G_N_ELEMENTS (attrs));
+	gkm_assert_cmpulong (object, !=, 0);
+}
+
+DEFINE_TEST (login_lookup_store_secret_overwrite)
+{
+	CK_OBJECT_CLASS klass = CKO_SECRET_KEY;
+	CK_BBOOL tval = CK_TRUE;
+	CK_ATTRIBUTE attrs[] = {
+		{ CKA_VALUE, "the password", 12 },
+		{ CKA_LABEL, "Unlock password for: The label", 30 },
+		{ CKA_CLASS, &klass, sizeof (klass) },
+		{ CKA_TOKEN, &tval, sizeof (tval) },
+		{ CKA_G_COLLECTION, "login", 5 },
+		{ CKA_G_FIELDS, "one\0" "1\0" "three\0" "3\0", 14 },
+	};
+
+	CK_OBJECT_HANDLE object1, object2;
+
+	/* Secret stored in mock-secret-store.c */
+	gkm_wrap_login_attach_secret ("The label", "the password",
+	                              "one", "1",
+	                              "three", "3", NULL);
+
+	object1 = gkm_mock_module_find_object (0, attrs, G_N_ELEMENTS (attrs));
+	gkm_assert_cmpulong (object1, !=, 0);
+
+	/* Secret stored in mock-secret-store.c */
+	gkm_wrap_login_attach_secret ("The label", "other",
+	                              "one", "1",
+	                              "three", "3", NULL);
+
+	attrs[0].pValue = "other";
+	attrs[0].ulValueLen = 5;
+
+	object2 = gkm_mock_module_find_object (0, attrs, G_N_ELEMENTS (attrs));
+	gkm_assert_cmpulong (object2, !=, 0);
+
+	/* Should have been stored on same object */
+	gkm_assert_cmpulong (object1, ==, object2);
+}
+
+DEFINE_TEST (login_lookup_store_null_secret)
+{
+	CK_OBJECT_CLASS klass = CKO_SECRET_KEY;
+	CK_BBOOL tval = CK_TRUE;
+	CK_ATTRIBUTE attrs[] = {
+		{ CKA_LABEL, "Unlock password for: The label", 30 },
+		{ CKA_CLASS, &klass, sizeof (klass) },
+		{ CKA_VALUE, "", 0 },
+		{ CKA_TOKEN, &tval, sizeof (tval) },
+		{ CKA_G_COLLECTION, "login", 5 },
+		{ CKA_G_FIELDS, "one\0" "1\0" "three\0" "3\0", 14 },
+	};
+
+	CK_OBJECT_HANDLE object;
+
+	gkm_wrap_login_attach_secret ("The label", NULL,
+	                              "one", "1",
+	                              "three", "3", NULL);
+
+	object = gkm_mock_module_find_object (0, attrs, G_N_ELEMENTS (attrs));
+	gkm_assert_cmpulong (object, !=, 0);
+}
+
+DEFINE_TEST (login_lookup_store_no_attributes_not_stored)
+{
+	CK_OBJECT_CLASS klass = CKO_SECRET_KEY;
+	CK_BBOOL tval = CK_TRUE;
+	CK_ATTRIBUTE attrs[] = {
+		{ CKA_LABEL, "Unlock password for: The label", 30 },
+		{ CKA_CLASS, &klass, sizeof (klass) },
+		{ CKA_VALUE, "the password", 0 },
+		{ CKA_TOKEN, &tval, sizeof (tval) },
+		{ CKA_G_COLLECTION, "login", 5 },
+		{ CKA_G_FIELDS, "", 0 },
+	};
+
+	CK_OBJECT_HANDLE object;
+
+	gkm_wrap_login_attach_secret ("The label", "the password", NULL);
+
+	object = gkm_mock_module_find_object (0, attrs, G_N_ELEMENTS (attrs));
+	gkm_assert_cmpulong (object, ==, 0);
+}
+
+
+DEFINE_TEST (login_lookup_remove_present)
+{
+	CK_OBJECT_CLASS klass = CKO_SECRET_KEY;
+	CK_BBOOL tval = CK_TRUE;
+	CK_ATTRIBUTE attrs[] = {
+		{ CKA_LABEL, "Unlock password for: Mock", 25 },
+		{ CKA_CLASS, &klass, sizeof (klass) },
+		{ CKA_VALUE, "mock", 4 },
+		{ CKA_TOKEN, &tval, sizeof (tval) },
+		{ CKA_G_COLLECTION, "login", 5 },
+		{ CKA_G_FIELDS, "one\0" "1\0" "two\0" "2\0", 12 },
+	};
+
+	CK_OBJECT_HANDLE object;
+
+	/* This object is created in mock-secret-store.c */
+	object = gkm_mock_module_find_object (0, attrs, G_N_ELEMENTS (attrs));
+	gkm_assert_cmpulong (object, !=, 0);
+
+	gkm_wrap_login_remove_secret ("one", "1", "two", "2", NULL);
+
+	/* No longer there */
+	object = gkm_mock_module_find_object (0, attrs, G_N_ELEMENTS (attrs));
+	gkm_assert_cmpulong (object, ==, 0);
+}
+
+DEFINE_TEST (login_lookup_remove_no_attributes)
+{
+	guint n_objects, check;
+
+	n_objects = gkm_mock_module_count_objects (0);
+	g_assert_cmpuint (n_objects, >, 0);
+
+	/* Shouldn't remove anything if no attributes */
+	gkm_wrap_login_remove_secret (NULL);
+
+	check = gkm_mock_module_count_objects (0);
+	g_assert_cmpuint (check, ==, n_objects);
+}
diff --git a/pkcs11/wrap-layer/tests/test-login-specific.c b/pkcs11/wrap-layer/tests/test-login-specific.c
index 2637c5b..234076d 100644
--- a/pkcs11/wrap-layer/tests/test-login-specific.c
+++ b/pkcs11/wrap-layer/tests/test-login-specific.c
@@ -92,6 +92,8 @@ DEFINE_TEARDOWN (login_specific)
 {
 	CK_RV rv;
 
+	g_assert (!gku_prompt_dummy_have_response ());
+
 	rv = (module->C_CloseSession) (session);
 	gkm_assert_cmprv (rv, ==, CKR_OK);
 	session = 0;
diff --git a/pkcs11/wrap-layer/tests/test-login-user.c b/pkcs11/wrap-layer/tests/test-login-user.c
index 3fde9c6..f709d0a 100644
--- a/pkcs11/wrap-layer/tests/test-login-user.c
+++ b/pkcs11/wrap-layer/tests/test-login-user.c
@@ -67,6 +67,8 @@ DEFINE_TEARDOWN (login_user)
 {
 	CK_RV rv;
 
+	g_assert (!gku_prompt_dummy_have_response ());
+
 	rv = (module->C_CloseSession) (session);
 	gkm_assert_cmprv (rv, ==, CKR_OK);
 	session = 0;
diff --git a/po/POTFILES.in b/po/POTFILES.in
index c90f557..5c2cd7c 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -24,6 +24,7 @@ gp11/gp11-misc.c
 pkcs11/gkm/gkm-certificate.c
 pkcs11/ssh-store/gkm-ssh-private-key.c
 pkcs11/secret-store/gkm-secret-collection.c
+pkcs11/wrap-layer/gkm-wrap-login.c
 pkcs11/wrap-layer/gkm-wrap-prompt.c
 ui/gku-prompt-tool.c
 [type: gettext/glade]ui/gku-prompt.ui
diff --git a/ui/gku-prompt.c b/ui/gku-prompt.c
index ce473cf..dc93acb 100644
--- a/ui/gku-prompt.c
+++ b/ui/gku-prompt.c
@@ -39,7 +39,7 @@
 
 #include <sys/wait.h>
 
-#define DEBUG_PROMPT 1
+#define DEBUG_PROMPT 0
 #define DEBUG_STDERR 0
 
 enum {
@@ -86,9 +86,9 @@ static void display_async_prompt (GkuPrompt *);
 
 /* User choices we transfer over during a soft prompt reset */
 const struct { const gchar *section; const gchar *name; } SOFT_RESET[] = {
-	{ "unlock-options", "unlock-auto"},
-	{ "unlock-options", "unlock-idle"},
-	{ "unlock-options", "unlock-timeout"},
+	{ "unlock-options", "unlock-auto" },
+	{ "unlock-options", "unlock-idle" },
+	{ "unlock-options", "unlock-timeout" },
 	{ "details", "expanded" },
 };
 
@@ -1172,6 +1172,16 @@ gku_prompt_dummy_prepare_response (void)
 	g_static_mutex_unlock (&attention_mutex);
 }
 
+gboolean
+gku_prompt_dummy_have_response (void)
+{
+	gboolean ret;
+	g_static_mutex_lock (&attention_mutex);
+		ret = !g_queue_is_empty (&queued_responses);
+	g_static_mutex_unlock (&attention_mutex);
+	return ret;
+}
+
 static void
 queue_dummy_response (gchar *response)
 {
@@ -1202,6 +1212,19 @@ gku_prompt_dummy_queue_ok_password (const gchar *password)
 }
 
 void
+gku_prompt_dummy_queue_auto_password (const gchar *password)
+{
+	const static gchar *RESPONSE = "[password]\nparameter=\nvalue=%s\n[prompt]\nresponse=ok\n"
+	                               "[unlock-options]\nunlock-auto=1\n";
+	gchar *value;
+
+	g_return_if_fail (password);
+	value = egg_hex_encode ((const guchar*)password, strlen (password));
+	queue_dummy_response (g_strdup_printf (RESPONSE, value));
+	g_free (value);
+}
+
+void
 gku_prompt_dummy_queue_no (void)
 {
 	const static gchar *RESPONSE = "[prompt]\nresponse=no\n";
diff --git a/ui/gku-prompt.h b/ui/gku-prompt.h
index 075a209..1f82eb5 100644
--- a/ui/gku-prompt.h
+++ b/ui/gku-prompt.h
@@ -143,10 +143,14 @@ void                gku_prompt_request_attention_sync     (const gchar *window_i
 
 void                gku_prompt_dummy_prepare_response      (void);
 
+gboolean            gku_prompt_dummy_have_response         (void);
+
 void                gku_prompt_dummy_queue_response        (const gchar *response);
 
 void                gku_prompt_dummy_queue_ok_password     (const gchar *password);
 
+void                gku_prompt_dummy_queue_auto_password   (const gchar *password);
+
 void                gku_prompt_dummy_queue_no              (void);
 
 #endif



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