[gnome-keyring/dbus-api] [secrets] Port over some parts of the keyring parsing.



commit 6a9310cc435346b75147743dec9e80ab133457e8
Author: Stef Walter <stef memberwebs com>
Date:   Sun Jul 26 15:07:14 2009 +0000

    [secrets] Port over some parts of the keyring parsing.
    
    The code compiles, but does not run, and is not tested.

 egg/egg-buffer.c                            |    8 +-
 egg/egg-buffer.h                            |   15 +-
 pkcs11/gck/gck-login.h                      |    4 +-
 pkcs11/secret-store/Makefile.am             |    4 +-
 pkcs11/secret-store/gck-secret-binary.c     |  929 +++++++++++++++++++++++++++
 pkcs11/secret-store/gck-secret-binary.h     |   37 ++
 pkcs11/secret-store/gck-secret-collection.c |   64 ++-
 pkcs11/secret-store/gck-secret-collection.h |   24 +-
 pkcs11/secret-store/gck-secret-compat.h     |   41 ++
 pkcs11/secret-store/gck-secret-fields.c     |   31 +
 pkcs11/secret-store/gck-secret-fields.h     |   12 +-
 pkcs11/secret-store/gck-secret-item.h       |    3 +-
 pkcs11/secret-store/gck-secret-module.c     |  356 ++++++++++
 pkcs11/secret-store/gck-secret-module.h     |   44 ++
 pkcs11/secret-store/gck-secret-object.h     |   11 +-
 pkcs11/secret-store/gck-secret-search.h     |    7 +-
 pkcs11/secret-store/gck-secret-store.h      |   29 +
 pkcs11/secret-store/gck-secret-textual.c    |  545 ++++++++++++++++
 pkcs11/secret-store/gck-secret-textual.h    |   37 ++
 pkcs11/secret-store/gck-secret-types.h      |   31 +
 20 files changed, 2207 insertions(+), 25 deletions(-)
---
diff --git a/egg/egg-buffer.c b/egg/egg-buffer.c
index 16eb12f..960d01a 100644
--- a/egg/egg-buffer.c
+++ b/egg/egg-buffer.c
@@ -60,15 +60,15 @@ egg_buffer_init_full (EggBuffer *buffer, size_t reserve, EggBufferAllocator allo
 }
 
 void
-egg_buffer_init_static (EggBuffer* buffer, unsigned char *buf, size_t len)
+egg_buffer_init_static (EggBuffer* buffer, const unsigned char *buf, size_t len)
 {
 	memset (buffer, 0, sizeof (*buffer));
-		
-	buffer->buf = buf;
+
+	buffer->buf = (unsigned char*)buf;
 	buffer->len = len;
 	buffer->allocated_len = len;
 	buffer->failures = 0;
-	
+
 	/* A null allocator, and the buffer can't change in size */
 	buffer->allocator = NULL;	
 }
diff --git a/egg/egg-buffer.h b/egg/egg-buffer.h
index 1bc0f80..9249574 100644
--- a/egg/egg-buffer.h
+++ b/egg/egg-buffer.h
@@ -72,19 +72,22 @@ int             egg_buffer_init                 (EggBuffer *buffer, size_t reser
 int             egg_buffer_init_full            (EggBuffer *buffer, 
                                                  size_t reserve, 
                                                  EggBufferAllocator allocator);
-                                                 
-void            egg_buffer_init_static          (EggBuffer *buffer, 
-                                                 unsigned char *buf, 
+
+void            egg_buffer_init_static          (EggBuffer *buffer,
+                                                 const unsigned char *buf,
                                                  size_t len);
 
-void            egg_buffer_init_allocated       (EggBuffer *buffer, 
-                                                 unsigned char *buf, 
+void            egg_buffer_init_allocated       (EggBuffer *buffer,
+                                                 unsigned char *buf,
                                                  size_t len,
                                                  EggBufferAllocator allocator);
                                                  
 void            egg_buffer_uninit               (EggBuffer *buffer);
 
-int             egg_buffer_set_allocator        (EggBuffer *buffer, 
+unsigned char*  egg_buffer_uninit_steal         (EggBuffer *buffer,
+                                                 size_t *n_result);
+
+int             egg_buffer_set_allocator        (EggBuffer *buffer,
                                                  EggBufferAllocator allocator);
 
 void 		egg_buffer_reset		(EggBuffer *buffer);
diff --git a/pkcs11/gck/gck-login.h b/pkcs11/gck/gck-login.h
index c6fc1f9..8f83451 100644
--- a/pkcs11/gck/gck-login.h
+++ b/pkcs11/gck/gck-login.h
@@ -46,7 +46,9 @@ GType               gck_login_get_type               (void);
 GckLogin*           gck_login_new                    (CK_UTF8CHAR_PTR pin, 
                                                       CK_ULONG n_pin);
 
-const gchar*        gck_login_get_password           (GckLogin *self, 
+GckLogin*           gck_login_new_from_password      (const gchar *password);
+
+const gchar*        gck_login_get_password           (GckLogin *self,
                                                       gsize *n_pin);
 
 gboolean            gck_login_equals                 (GckLogin *self,
diff --git a/pkcs11/secret-store/Makefile.am b/pkcs11/secret-store/Makefile.am
index e3fdd33..1c1224a 100644
--- a/pkcs11/secret-store/Makefile.am
+++ b/pkcs11/secret-store/Makefile.am
@@ -13,10 +13,12 @@ noinst_LTLIBRARIES = \
 	libgck-secret-store.la
 
 libgck_secret_store_la_SOURCES = \
+	gck-secret-binary.c gck-secret-binary.h \
 	gck-secret-collection.h gck-secret-collection.c \
 	gck-secret-fields.h gck-secret-fields.c \
 	gck-secret-item.h gck-secret-item.c \
 	gck-secret-object.h gck-secret-object.c \
-	gck-secret-search.h gck-secret-search.c
+	gck-secret-search.h gck-secret-search.c \
+	gck-secret-textual.c gck-secret-textual.h
 
 # -------------------------------------------------------------------------------
diff --git a/pkcs11/secret-store/gck-secret-binary.c b/pkcs11/secret-store/gck-secret-binary.c
new file mode 100644
index 0000000..073b6c2
--- /dev/null
+++ b/pkcs11/secret-store/gck-secret-binary.c
@@ -0,0 +1,929 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gck-secret-binary.c - The binary encrypted format of a keyring
+
+   Copyright (C) 2003 Red Hat, Inc
+   Copyright (C) 2007, 2009 Stefan Walter
+
+   Gnome keyring is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+  
+   Gnome keyring 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
+   General Public License for more details.
+  
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Author: Alexander Larsson <alexl redhat com>
+   Author: Stef Walter <stef memberwebs com>
+*/
+
+#include "config.h"
+
+#include "gck-secret-binary.h"
+#include "gck-secret-collection.h"
+#include "gck-secret-compat.h"
+#include "gck-secret-fields.h"
+#include "gck-secret-item.h"
+
+#include "egg/egg-buffer.h"
+#include "egg/egg-symkey.h"
+#include "egg/egg-secure-memory.h"
+
+#include "gck/gck-login.h"
+
+#include <glib.h>
+
+#include <gcrypt.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+/* -----------------------------------------------------------------------------
+ * DECLARATIONS
+ */
+
+#define LOCK_ON_IDLE_FLAG (1<<0)
+
+typedef struct {
+	/* unencrypted: */
+	guint32 id;
+	gchar *identifier;
+	guint32 type;
+	GHashTable *hashed_attributes;
+
+	/* encrypted: */
+	char *display_name;
+	char *secret;
+	time_t ctime;
+	time_t mtime;
+	GHashTable *attributes;
+	GList *acl;
+} ItemInfo;
+
+#define KEYRING_FILE_HEADER "GnomeKeyring\n\r\0\n"
+#define KEYRING_FILE_HEADER_LEN 16
+
+/* -----------------------------------------------------------------------------
+ * BUFFER UTILITY FUNCTIONS
+ */
+
+static gboolean
+buffer_get_bytes (EggBuffer *buffer, gsize offset, gsize *next_offset, 
+                  guchar *out, gsize n_bytes)
+{
+	if (buffer->len < n_bytes || offset > buffer->len - n_bytes) 
+		return FALSE;
+	memcpy (out, buffer->buf + offset, n_bytes);
+	*next_offset = offset + n_bytes;
+	return TRUE;
+}
+
+static gboolean
+buffer_add_time (EggBuffer *buffer, glong time)
+{
+	guint64 val = time;
+	return egg_buffer_add_uint32 (buffer, ((val >> 32) & 0xffffffff)) && 
+	       egg_buffer_add_uint32 (buffer, (val & 0xffffffff));
+}
+
+static gboolean
+buffer_get_time (EggBuffer *buffer, gsize offset, gsize *next_offset, glong *time)
+{
+	guint32 a, b;
+	guint64 val;
+
+	if (!egg_buffer_get_uint32 (buffer, offset, &offset, &a) || 
+	    !egg_buffer_get_uint32 (buffer, offset, &offset, &b))
+		return FALSE;
+
+	val = ((guint64)a) << 32 | b;
+	*next_offset = offset;
+	*time = (time_t) val;
+	return TRUE;
+}
+
+static gboolean
+buffer_add_utf8_string (EggBuffer *buffer, const char *str)
+{
+	if (str && !g_utf8_validate (str, -1, NULL))
+		return FALSE;
+	return egg_buffer_add_string (buffer, str);
+}
+
+static gboolean
+buffer_get_utf8_string (EggBuffer *buffer, gsize offset, gsize *next_offset,
+                        char **str_ret)
+{
+	gsize len;
+	char *str;
+	
+	if (!egg_buffer_get_string (buffer, offset, &offset, &str, 
+	                            (EggBufferAllocator)g_realloc))
+		return FALSE;
+	len = str ? strlen (str) : 0;
+
+	if (str != NULL) {
+		if (!g_utf8_validate (str, len, NULL)) {
+			g_free (str);
+			return FALSE;
+		}
+	}
+
+	if (next_offset != NULL) {
+		*next_offset = offset;
+	}
+	if (str_ret != NULL) {
+		*str_ret = str;
+	} else {
+		g_free (str);
+	}
+	return TRUE;
+}
+
+static gboolean
+buffer_get_raw_secret (EggBuffer *buffer, gsize offset, gsize *next_offset,
+                       guchar **secret, gsize *n_secret)
+{
+	const guchar* ptr;
+	if (!egg_buffer_get_byte_array (buffer, offset, next_offset, &ptr, n_secret))
+		return FALSE;
+
+	if (ptr == NULL || *n_secret == 0) {
+		*secret = NULL;
+		*n_secret = 0;
+		return TRUE;
+	}
+
+	*secret = egg_secure_alloc (*n_secret + 1);
+	memcpy (*secret, ptr, *n_secret);
+	(*secret)[*n_secret] = 0;
+	return TRUE;
+}
+
+typedef struct _AttributesCtx {
+	EggBuffer *buffer;
+	GHashTable *attributes;
+} AttributesCtx;
+
+static void
+add_each_attribute (gpointer key, gpointer value, gpointer user_data)
+{
+	AttributesCtx *ctx = user_data;
+	guint32 number;
+	gchar *end;
+	
+	buffer_add_utf8_string (ctx->buffer, key);
+	
+	/* 
+	 * COMPATIBILITY:
+	 * 
+	 * Our new Secrets API doesn't support integer attributes. However, to have 
+	 * compatibility with old keyring code reading this file, we need to set 
+	 * the uint32 type attribute appropriately where expected. 
+	 * 
+	 * If there's an extra compat-uint32 attribute and the name of this attribute
+	 * is contained in that list, then write as a uint32.
+	 */
+	
+	/* Determine if it's a uint32 compatible value, and store as such if it is */
+	if (gck_secret_fields_has_word (ctx->attributes, "compat-uint32", key)) {
+		number = strtoul (value, &end, 10);
+		if (!*end) {
+			egg_buffer_add_uint32 (ctx->buffer, 1);
+			egg_buffer_add_uint32 (ctx->buffer, number);
+			return;
+		}
+	}
+	
+	/* A standard string attribute */
+	egg_buffer_add_uint32 (ctx->buffer, 0);
+	buffer_add_utf8_string (ctx->buffer, value);
+}
+
+
+static gboolean
+buffer_add_attributes (EggBuffer *buffer, GHashTable *attributes)
+{
+	AttributesCtx ctx;
+	if (attributes == NULL) {
+		egg_buffer_add_uint32 (buffer, 0);
+	} else {
+		ctx.buffer = buffer;
+		ctx.attributes = attributes;
+		egg_buffer_add_uint32 (buffer, g_hash_table_size (attributes));
+		g_hash_table_foreach (attributes, add_each_attribute, &ctx);
+	}
+	
+	return !egg_buffer_has_error (buffer);
+}
+
+static gboolean
+buffer_get_attributes (EggBuffer *buffer, gsize offset, gsize *next_offset,
+                       GHashTable **attributes_out)
+{
+	guint32 list_size;
+	GHashTable *attributes;
+	GString *compat_uint32;
+	char *name;
+	guint32 type;
+	char *str;
+	guint32 val;
+	int i;
+
+	attributes = NULL;
+	compat_uint32 = g_string_new ("");
+	
+	if (!egg_buffer_get_uint32 (buffer, offset, &offset, &list_size))
+		goto bail;
+
+	attributes = gck_secret_fields_new ();
+	for (i = 0; i < list_size; i++) {
+		if (!buffer_get_utf8_string (buffer, offset, &offset, &name))
+			goto bail;
+		if (!egg_buffer_get_uint32 (buffer, offset, &offset, &type)) {
+			g_free (name);
+			goto bail;
+		}
+		switch (type) {
+		case 0: /* A string */
+			if (!buffer_get_utf8_string (buffer, offset, &offset, &str)) {
+				g_free (name);
+				goto bail;
+			}
+			g_hash_table_replace (attributes, name, str);
+			break;
+		case 1: /* A uint32 */
+			if (!egg_buffer_get_uint32 (buffer, offset, 
+			                            &offset, &val)) {
+				g_free (name);
+				goto bail;
+			}
+			g_string_append_c (compat_uint32, ' ');
+			g_string_append (compat_uint32, name);
+			str = g_strdup_printf ("%u", val);
+			g_hash_table_replace (attributes, name, str);
+			break;
+		default:
+			g_free (name);
+			goto bail;
+		}
+	}
+	
+	if (compat_uint32->len)
+		g_hash_table_replace (attributes, "compat-uint32", g_string_free (compat_uint32, FALSE));
+	else
+		g_string_free (compat_uint32, TRUE);
+
+	*attributes_out = attributes;
+	*next_offset = offset;
+	
+	return TRUE;
+	
+bail:
+	g_string_free (compat_uint32, TRUE);
+	g_hash_table_unref (attributes);
+	return FALSE;
+}
+
+static gboolean
+convert_to_integer (const gchar *string, guint32 *result)
+{
+	gchar *end;
+	*result = strtoul (string, &end, 10);
+	return *end == 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * BINARY ENCRYPTED FILE FORMAT
+ */
+
+static gboolean
+encrypt_buffer (EggBuffer *buffer,
+		const char *password,
+		guchar salt[8],
+		int iterations)
+{
+	gcry_cipher_hd_t cih;
+	gcry_error_t gerr;
+        guchar *key, *iv;
+	size_t pos;
+
+	g_assert (buffer->len % 16 == 0);
+	g_assert (16 == gcry_cipher_get_algo_blklen (GCRY_CIPHER_AES128));
+	g_assert (16 == gcry_cipher_get_algo_keylen (GCRY_CIPHER_AES128));
+	
+	if (!egg_symkey_generate_simple (GCRY_CIPHER_AES128, GCRY_MD_SHA256, 
+	                                 password, -1, salt, 8, iterations, &key, &iv))
+		return FALSE;
+
+	gerr = gcry_cipher_open (&cih, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CBC, 0);
+	if (gerr) {
+		g_warning ("couldn't create aes cipher context: %s", 
+			   gcry_strerror (gerr));
+		egg_secure_free (key);
+		g_free (iv);
+		return FALSE;
+	}
+
+	/* 16 = 128 bits */
+	gerr = gcry_cipher_setkey (cih, key, 16);
+	g_return_val_if_fail (!gerr, FALSE);
+	egg_secure_free (key);
+
+	/* 16 = 128 bits */
+	gerr = gcry_cipher_setiv (cih, iv, 16);
+	g_return_val_if_fail (!gerr, FALSE);
+	g_free (iv);
+
+	for (pos = 0; pos < buffer->len; pos += 16) {
+		/* In place encryption */
+		gerr = gcry_cipher_encrypt (cih, buffer->buf + pos, 16, NULL, 0);
+		g_return_val_if_fail (!gerr, FALSE);
+	}
+
+	gcry_cipher_close (cih);
+	
+	return TRUE;
+}
+
+static gboolean
+decrypt_buffer (EggBuffer *buffer,
+		const char *password,
+		guchar salt[8],
+		int iterations)
+{
+	gcry_cipher_hd_t cih;
+	gcry_error_t gerr;
+        guchar *key, *iv;
+	size_t pos;
+
+	g_assert (buffer->len % 16 == 0);
+	g_assert (16 == gcry_cipher_get_algo_blklen (GCRY_CIPHER_AES128));
+	g_assert (16 == gcry_cipher_get_algo_keylen (GCRY_CIPHER_AES128));
+	
+	if (!egg_symkey_generate_simple (GCRY_CIPHER_AES128, GCRY_MD_SHA256, 
+	                                 password, -1, salt, 8, iterations, &key, &iv))
+		return FALSE;
+	
+	gerr = gcry_cipher_open (&cih, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CBC, 0);
+	if (gerr) {
+		g_warning ("couldn't create aes cipher context: %s", 
+			   gcry_strerror (gerr));
+		egg_secure_free (key);
+		g_free (iv);
+		return FALSE;
+	}
+
+	/* 16 = 128 bits */
+	gerr = gcry_cipher_setkey (cih, key, 16);
+	g_return_val_if_fail (!gerr, FALSE);
+	egg_secure_free (key);
+
+	/* 16 = 128 bits */
+	gerr = gcry_cipher_setiv (cih, iv, 16);
+	g_return_val_if_fail (!gerr, FALSE);
+	g_free (iv);
+
+	for (pos = 0; pos < buffer->len; pos += 16) {
+		/* In place encryption */
+		gerr = gcry_cipher_decrypt (cih, buffer->buf + pos, 16, NULL, 0);
+		g_return_val_if_fail (!gerr, FALSE);
+	}
+
+	gcry_cipher_close (cih);
+	
+	return TRUE;
+}
+
+static gboolean
+verify_decrypted_buffer (EggBuffer *buffer)
+{
+        guchar digest[16];
+	
+	/* In case the world changes on us... */
+	g_return_val_if_fail (gcry_md_get_algo_dlen (GCRY_MD_MD5) == sizeof (digest), 0);
+	
+	gcry_md_hash_buffer (GCRY_MD_MD5, (void*)digest, 
+			     (guchar*)buffer->buf + 16, buffer->len - 16);
+	
+	return memcmp (buffer->buf, digest, 16) == 0;
+}
+
+static gboolean 
+generate_acl_data (EggBuffer *buffer, GList *acl)
+{
+	GList *l;
+	GckSecretAccess *ac;
+	
+	egg_buffer_add_uint32 (buffer, g_list_length (acl));
+
+	for (l = acl; l != NULL; l = l->next) {
+		ac = l->data;
+		
+		egg_buffer_add_uint32 (buffer, ac->types_allowed);
+		if (!buffer_add_utf8_string (buffer, ac->display_name) || 
+		    !buffer_add_utf8_string (buffer, ac->pathname))
+			return FALSE;
+
+		/* Reserved: */
+		if (!buffer_add_utf8_string (buffer, NULL))
+			return FALSE;
+
+		egg_buffer_add_uint32 (buffer, 0);
+	}
+	
+	return TRUE;
+}
+
+static gboolean
+generate_encrypted_data (EggBuffer *buffer, GckSecretCollection *collection)
+{
+	GckSecretObject *obj;
+	GckSecretItem *item;
+	GList *items, *l;
+	GHashTable *attributes;
+	const gchar *label;
+	GckLogin *secret;
+	const gchar *password;
+	gsize n_password;
+	GList *acl;
+	int i;
+	
+	/* Make sure we're using non-pageable memory */
+	egg_buffer_set_allocator (buffer, egg_secure_realloc);
+	
+	items = gck_secret_collection_get_items (collection);
+	for (l = items; l && !egg_buffer_has_error(buffer); l = g_list_next (l)) {
+		item = GCK_SECRET_ITEM (l->data);
+		obj = GCK_SECRET_OBJECT (l->data);
+		
+		label = gck_secret_object_get_label (obj);
+		buffer_add_utf8_string (buffer, label);
+
+		secret = gck_secret_item_get_secret (item);
+		password = NULL;
+		if (secret != NULL)
+			password = gck_login_get_password (secret, &n_password);
+		/* TODO: Need to support binary secrets somehow */
+		buffer_add_utf8_string (buffer, password);
+
+		if (!buffer_add_time (buffer, gck_secret_object_get_created (obj)) || 
+		    !buffer_add_time (buffer, gck_secret_object_get_modified (obj)))
+			break;
+
+		/* reserved: */
+		if (!buffer_add_utf8_string (buffer, NULL))
+			break;
+		for (i = 0; i < 4; i++)
+			egg_buffer_add_uint32 (buffer, 0);
+
+		/* Null attributes = empty attribute array */
+		attributes = gck_secret_item_get_fields (item);
+		if (!buffer_add_attributes (buffer, attributes))
+			break;
+
+		acl = g_object_get_data (G_OBJECT (item), "compat-acl");
+		if (!generate_acl_data (buffer, acl))
+			break;
+	}
+	
+	g_list_free (items);
+	
+	/* Iteration completed prematurely == fail */
+	return (l == NULL); 
+}
+
+static gboolean
+generate_hashed_attributes (GckSecretCollection *collection, EggBuffer *buffer)
+{
+	GHashTable *attributes;
+	GHashTable *hashed;
+	const gchar *value;
+	GList *items, *l;
+	guint32 id, type;
+	
+	items = gck_secret_collection_get_items (collection);
+	egg_buffer_add_uint32 (buffer, g_list_length (items));
+
+	for (l = items; l; l = g_list_next (l)) {
+		
+		value = gck_secret_object_get_identifier (l->data);
+		if (!convert_to_integer (value, &id))
+			continue;
+		egg_buffer_add_uint32 (buffer, id);
+		
+		attributes = gck_secret_item_get_fields (l->data);
+		value = g_hash_table_lookup (attributes, "compat-item-type");
+		if (!value || !convert_to_integer (value, &type))
+			type = 0;
+		egg_buffer_add_uint32 (buffer, type);
+		
+		hashed = gck_secret_fields_hash (attributes);
+		buffer_add_attributes (buffer, hashed);
+		g_hash_table_unref (hashed);
+	}
+	
+	g_list_free (items);
+	return !egg_buffer_has_error (buffer);
+}
+
+GckDataResult 
+gck_secret_binary_write (GckSecretCollection *collection, guchar **data, gsize *n_data)
+{
+	guint flags;
+	const gchar *password;
+	GckSecretObject *obj;
+	EggBuffer to_encrypt;
+        guchar digest[16];
+        EggBuffer buffer;
+        gint hash_iterations;
+        gint lock_timeout;
+        guchar salt[8];
+	int i;
+
+	/* In case the world changes on us... */
+	g_return_val_if_fail (gcry_md_get_algo_dlen (GCRY_MD_MD5) == sizeof (digest), GCK_DATA_FAILURE);
+	g_return_val_if_fail (gck_secret_collection_get_state (collection) == GCK_SECRET_COMPLETE, GCK_DATA_FAILURE);
+	
+	egg_buffer_init_full (&buffer, 256, g_realloc);
+	obj = GCK_SECRET_OBJECT (collection);
+	
+	/* Prepare the keyring for encryption */
+	hash_iterations = 1000 + (int) (1000.0 * rand() / (RAND_MAX + 1.0));
+	gcry_create_nonce (salt, sizeof (salt));
+		
+	egg_buffer_append (&buffer, (guchar*)KEYRING_FILE_HEADER, KEYRING_FILE_HEADER_LEN);
+	egg_buffer_add_byte (&buffer, 0); /* Major version */
+	egg_buffer_add_byte (&buffer, 0); /* Minor version */
+	egg_buffer_add_byte (&buffer, 0); /* crypto (0 == AEL) */
+	egg_buffer_add_byte (&buffer, 0); /* hash (0 == MD5) */
+
+	buffer_add_utf8_string (&buffer, gck_secret_object_get_label (obj));
+	buffer_add_time (&buffer, gck_secret_object_get_modified (obj));
+	buffer_add_time (&buffer, gck_secret_object_get_created (obj));
+	
+	flags = 0;
+	if (g_object_get_data (G_OBJECT (collection), "lock-on-idle")) 
+		flags |= 1;
+	egg_buffer_add_uint32 (&buffer, flags);
+	
+	lock_timeout = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (collection), "lock-on-idle"));
+	egg_buffer_add_uint32 (&buffer, lock_timeout);
+	egg_buffer_add_uint32 (&buffer, hash_iterations);
+	egg_buffer_append (&buffer, salt, 8);
+
+	/* Reserved: */
+	for (i = 0; i < 4; i++)
+		egg_buffer_add_uint32 (&buffer, 0);
+
+	/* Hashed items: */
+	generate_hashed_attributes (collection, &buffer);
+
+	/* Encrypted data. Use non-pageable memory */
+	egg_buffer_init_full (&to_encrypt, 4096, egg_secure_realloc);
+	
+	egg_buffer_append (&to_encrypt, (guchar*)digest, 16); /* Space for hash */
+
+	if (!generate_encrypted_data (&to_encrypt, collection)) {
+		egg_buffer_uninit (&to_encrypt);
+		egg_buffer_uninit (&buffer);
+		return GCK_DATA_FAILURE;
+	}
+
+	/* Pad with zeros to multiple of 16 bytes */
+	while (to_encrypt.len % 16 != 0)
+		egg_buffer_add_byte (&to_encrypt, 0);
+
+	gcry_md_hash_buffer (GCRY_MD_MD5, (void*)digest, 
+			     (guchar*)to_encrypt.buf + 16, to_encrypt.len - 16);
+	memcpy (to_encrypt.buf, digest, 16);
+	
+	password = gck_secret_collection_get_master_password (collection);
+	if (!encrypt_buffer (&to_encrypt, password, salt, hash_iterations)) {
+		egg_buffer_uninit (&buffer);
+		egg_buffer_uninit (&to_encrypt);
+		return GCK_DATA_FAILURE;
+	}
+	
+	if (egg_buffer_has_error (&to_encrypt) || egg_buffer_has_error (&buffer)) {
+		egg_buffer_uninit (&buffer);
+		egg_buffer_uninit (&to_encrypt);
+		return GCK_DATA_FAILURE;
+	}
+
+	egg_buffer_add_uint32 (&buffer, to_encrypt.len);
+	egg_buffer_append (&buffer, to_encrypt.buf, to_encrypt.len);
+	egg_buffer_uninit (&to_encrypt);
+	*data = egg_buffer_uninit_steal (&buffer, n_data);
+	
+	return GCK_DATA_SUCCESS;
+}
+
+static gboolean
+decode_acl (EggBuffer *buffer, gsize offset, gsize *offset_out, GList **out)
+{
+	GList *acl;
+	guint32 num_acs;
+	guint32 x, y;
+	int i;
+	GckSecretAccess *ac;
+	char *name, *path, *reserved;
+	
+	acl = NULL;
+
+	if (!egg_buffer_get_uint32 (buffer, offset, &offset, &num_acs))
+		return FALSE;
+	for (i = 0; i < num_acs; i++) {
+		if (!egg_buffer_get_uint32 (buffer, offset, &offset, &x)) {
+			goto bail;
+		}
+		if (!buffer_get_utf8_string (buffer, offset, &offset, &name)) {
+			goto bail;
+		}
+		if (!buffer_get_utf8_string (buffer, offset, &offset, &path)) {
+			g_free (name);
+			goto bail;
+		}
+		reserved = NULL;
+		if (!buffer_get_utf8_string (buffer, offset, &offset, &reserved)) {
+			g_free (name);
+			g_free (path);
+			goto bail;
+		}
+		g_free (reserved);
+		if (!egg_buffer_get_uint32 (buffer, offset, &offset, &y)) {
+			g_free (name);
+			g_free (path);
+			goto bail;
+		}
+
+		ac = g_new0 (GckSecretAccess, 1);
+		ac->display_name = name;
+		ac->pathname = path;
+		ac->types_allowed = x;
+		
+		acl = g_list_prepend (acl, ac);
+	}
+
+	*offset_out = offset;
+	*out = g_list_reverse (acl);
+	return TRUE;
+	
+bail:
+	gck_secret_acl_free (acl);
+	return FALSE;
+}
+
+static void 
+remove_unavailable_item (gpointer key, gpointer dummy, gpointer user_data)
+{
+	/* Called to remove items from a keyring that no longer exist */
+	
+	GckSecretCollection *collection = user_data;
+	GckSecretItem *item;
+	
+	g_assert (GCK_IS_SECRET_COLLECTION (collection));
+	
+	item = gck_secret_collection_get_item (collection, key);
+	if (item != NULL)
+		gck_secret_collection_remove_item (collection, item);
+}
+
+static void
+setup_item_from_info (GckSecretItem *item, gboolean locked, ItemInfo *info)
+{
+	GckSecretObject *obj = GCK_SECRET_OBJECT (item);
+	GckLogin *secret;
+	gchar *type;
+	
+	gck_secret_object_set_label (obj, info->display_name);
+	gck_secret_object_set_created (obj, info->ctime);
+	gck_secret_object_set_modified (obj, info->mtime);
+	
+	type = g_strdup_printf ("%u", info->type);
+	
+	if (locked) {
+		gck_secret_fields_add (info->hashed_attributes, "compat-item-type", type);
+		gck_secret_item_set_fields (item, info->hashed_attributes);
+		g_object_set_data (G_OBJECT (item), "compat-acl", NULL);
+		gck_secret_item_set_secret (item, NULL);
+		
+	} else {
+		gck_secret_fields_add (info->attributes, "compat-item-type", type);
+		gck_secret_item_set_fields (item, info->attributes);
+		secret = gck_login_new_from_password (info->secret);
+		gck_secret_item_set_secret (item, secret);
+		g_object_unref (secret);
+		g_object_set_data_full (G_OBJECT (item), "compat-acl", info->acl, gck_secret_acl_free);
+		info->acl = NULL;
+	}
+
+	g_free (type);
+}
+
+static void
+free_item_info (ItemInfo *info)
+{
+	g_free (info->identifier);
+	g_hash_table_unref (info->hashed_attributes);
+	g_free (info->display_name);
+	egg_secure_free (info->secret);
+	g_hash_table_unref (info->attributes);
+	gck_secret_acl_free (info->acl);
+}
+
+gint
+gck_secret_binary_read (GckSecretCollection *collection, const guchar *data, gsize n_data)
+{
+	gsize offset;
+	guchar major, minor, crypto, hash;
+	guint32 flags;
+	guint32 lock_timeout;
+	time_t mtime, ctime;
+	char *display_name;
+	gsize n_secret;
+	int i, j;
+	guint32 tmp;
+	guint32 num_items;
+	guint32 crypto_size;
+	guint32 hash_iterations;
+	guchar salt[8];
+	ItemInfo *items;
+	const gchar *password;
+	GckSecretObject *obj;
+	EggBuffer to_decrypt = EGG_BUFFER_EMPTY;
+	GckDataResult res = GCK_DATA_FAILURE;
+	GHashTable *checks = NULL;
+	GckSecretItem *item;
+	EggBuffer buffer;
+	char *reserved;
+	gchar *identifier;
+	GList *l, *iteml;
+
+	display_name = NULL;
+	items = 0;
+	obj = GCK_SECRET_OBJECT (collection);
+
+	/* The buffer we read from */
+	egg_buffer_init_static (&buffer, data, n_data);
+
+	if (buffer.len < KEYRING_FILE_HEADER_LEN || 
+	    memcmp (buffer.buf, KEYRING_FILE_HEADER, KEYRING_FILE_HEADER_LEN) != 0) {
+		egg_buffer_uninit (&buffer);
+		return GCK_DATA_UNRECOGNIZED;
+	}
+	
+	offset = KEYRING_FILE_HEADER_LEN;
+	major = buffer.buf[offset++];
+	minor = buffer.buf[offset++];
+	crypto = buffer.buf[offset++];
+	hash = buffer.buf[offset++];
+
+	if (major != 0 || minor != 0 || crypto != 0 || hash != 0) {
+		egg_buffer_uninit (&buffer);
+		return GCK_DATA_UNRECOGNIZED;
+	}
+	
+	/* We're decrypting this, so use secure memory */
+	egg_buffer_set_allocator (&to_decrypt, egg_secure_realloc);
+
+	if (!buffer_get_utf8_string (&buffer, offset, &offset, &display_name) || 
+	    !buffer_get_time (&buffer, offset, &offset, &ctime) ||		
+	    !buffer_get_time (&buffer, offset, &offset, &mtime) ||
+	    !egg_buffer_get_uint32 (&buffer, offset, &offset, &flags) ||
+	    !egg_buffer_get_uint32 (&buffer, offset, &offset, &lock_timeout) ||
+	    !egg_buffer_get_uint32 (&buffer, offset, &offset, &hash_iterations) ||
+	    !buffer_get_bytes (&buffer, offset, &offset, salt, 8))
+		goto bail;
+	
+	for (i = 0; i < 4; i++) {
+		if (!egg_buffer_get_uint32 (&buffer, offset, &offset, &tmp))
+			goto bail;
+	}
+
+	if (!egg_buffer_get_uint32 (&buffer, offset, &offset, &num_items))
+		goto bail;
+
+	items = g_new0 (ItemInfo, num_items);
+
+	for (i = 0; i < num_items; i++) {
+		if (!egg_buffer_get_uint32 (&buffer, offset, &offset, &items[i].id) ||
+		    !egg_buffer_get_uint32 (&buffer, offset, &offset, &items[i].type) ||
+		    !buffer_get_attributes (&buffer, offset, &offset, &items[i].hashed_attributes))
+			goto bail;
+		identifier = g_strdup_printf ("%u", items[i].id);
+	}
+
+	if (!egg_buffer_get_uint32 (&buffer, offset, &offset, &crypto_size))
+		goto bail;
+
+	/* Make the crypted part is the right size */
+	if (crypto_size % 16 != 0)
+		goto bail;
+	
+	/* Copy the data into to_decrypt into non-pageable memory */
+	egg_buffer_init_static (&to_decrypt, buffer.buf + offset, crypto_size);
+
+	password = gck_secret_collection_get_master_password (collection);
+	if (password != NULL) {
+		
+		if (!decrypt_buffer (&to_decrypt, password, salt, hash_iterations))
+			goto bail;
+		if (!verify_decrypted_buffer (&to_decrypt)) {
+			res = GCK_DATA_LOCKED;
+			goto bail;
+		} else {
+			offset += 16; /* Skip hash */
+			for (i = 0; i < num_items; i++) {
+				if (!buffer_get_utf8_string (&buffer, offset, &offset,
+				                             &items[i].display_name)) {
+					goto bail;
+				}
+				if (!buffer_get_raw_secret (&buffer, offset, &offset,
+				                            (guchar**)(&items[i].secret), &n_secret)) {
+					goto bail;
+				}
+				/* We don't support binary secrets yet, skip */
+				if (!g_utf8_validate ((gchar*)items[i].secret, n_secret, NULL)) {
+					g_message ("discarding item with unsupported non-textual secret: %s", 
+					           items[i].display_name);
+					free (items[i].display_name);
+					free (items[i].secret);
+					continue;
+				}
+				if (!buffer_get_time (&buffer, offset, &offset, &items[i].ctime) ||
+				    !buffer_get_time (&buffer, offset, &offset, &items[i].mtime)) 
+					goto bail;
+				reserved = NULL;
+				if (!buffer_get_utf8_string (&buffer, offset, &offset, &reserved))
+					goto bail;
+				g_free (reserved);
+				for (j = 0; j < 4; j++) {
+					guint32 tmp;
+					if (!egg_buffer_get_uint32 (&buffer, offset, &offset, &tmp))
+						goto bail;
+				}
+				if (!buffer_get_attributes (&buffer, offset, &offset, &items[i].attributes))
+					goto bail;
+				if (!decode_acl (&buffer, offset, &offset, &items[i].acl))
+					goto bail;
+			}
+		}
+	}
+
+	/* Correctly read all data, possibly including the decrypted data.
+	 * Now update the keyring and items: */
+
+	gck_secret_object_set_label (obj, display_name);
+	gck_secret_object_set_modified (obj, mtime);
+	gck_secret_object_set_created (obj, ctime);
+	g_object_set_data (G_OBJECT (collection), "lock-on-idle", GINT_TO_POINTER (!!(flags & LOCK_ON_IDLE_FLAG)));
+	g_object_set_data (G_OBJECT (collection), "lock-timeout", GINT_TO_POINTER (lock_timeout));
+	
+	/* Build a Hash table where we can track ids we haven't yet seen */
+	checks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	iteml = gck_secret_collection_get_items (collection);
+	for (l = iteml; l; l = g_list_next (l))
+		g_hash_table_insert (checks, g_strdup (gck_secret_object_get_identifier (l->data)), "unused");
+	g_list_free (iteml);
+
+	for (i = 0; i < num_items; i++) {
+		
+		/* We've seen this id */
+		g_hash_table_remove (checks, items[i].identifier);
+		
+		item = gck_secret_collection_get_item (collection, items[i].identifier);
+		if (item == NULL)
+			item = gck_secret_collection_create_item (collection, items[i].identifier);
+		
+		setup_item_from_info (item, password == NULL, &items[i]);
+	}
+	
+	g_hash_table_foreach (checks, remove_unavailable_item, collection);
+	res = GCK_DATA_SUCCESS;
+
+bail:
+	egg_buffer_uninit (&to_decrypt);
+	if (checks)
+		g_hash_table_destroy (checks);
+	g_free (display_name);
+
+	for (i = 0; items && i < num_items; i++)
+		free_item_info (&items[i]);
+	g_free (items);
+	
+	return res;
+}
diff --git a/pkcs11/secret-store/gck-secret-binary.h b/pkcs11/secret-store/gck-secret-binary.h
new file mode 100644
index 0000000..f87c1d7
--- /dev/null
+++ b/pkcs11/secret-store/gck-secret-binary.h
@@ -0,0 +1,37 @@
+/* 
+ * gnome-keyring
+ * 
+ * Copyright (C) 2009 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 __GCK_SECRET_BINARY_H__
+#define __GCK_SECRET_BINARY_H__
+
+#include "gck-secret-collection.h"
+
+#include "gck/gck-data-types.h"
+
+GckDataResult          gck_secret_binary_read        (GckSecretCollection *collection, 
+                                                      const guchar *data,
+                                                      gsize n_data);
+
+GckDataResult          gck_secret_binary_write       (GckSecretCollection *collection, 
+                                                      guchar **data,
+                                                      gsize *n_data);
+
+#endif /* __GCK_SECRET_BINARY_H__ */
diff --git a/pkcs11/secret-store/gck-secret-collection.c b/pkcs11/secret-store/gck-secret-collection.c
index d4bf163..5c41849 100644
--- a/pkcs11/secret-store/gck-secret-collection.c
+++ b/pkcs11/secret-store/gck-secret-collection.c
@@ -21,7 +21,13 @@
 
 #include "config.h"
 
+#include "gck-secret-binary.h"
 #include "gck-secret-collection.h"
+#include "gck-secret-textual.h"
+
+#include "egg/egg-buffer.h"
+
+#include "gck/gck-serializable.h"
 
 #include <glib/gi18n.h>
 
@@ -32,17 +38,23 @@ enum {
 struct _GckSecretCollection {
 	GckSecretObject parent;
 	GHashTable *secrets;
+	GList *items;
+	gchar *password;
 };
 
-G_DEFINE_TYPE (GckSecretCollection, gck_secret_collection, GCK_TYPE_SECRET_OBJECT);
+static void gck_secret_collection_serializable (GckSerializableIface *iface);
+
+G_DEFINE_TYPE_EXTENDED (GckSecretCollection, gck_secret_collection, GCK_TYPE_SECRET_OBJECT, 0,
+               G_IMPLEMENT_INTERFACE (GCK_TYPE_SERIALIZABLE, gck_secret_collection_serializable));
 
 /* -----------------------------------------------------------------------------
- * INTERNAL 
+ * INTERNAL
  */
 
 
+
 /* -----------------------------------------------------------------------------
- * OBJECT 
+ * OBJECT
  */
 
 static CK_RV
@@ -139,6 +151,52 @@ gck_secret_collection_class_init (GckSecretCollectionClass *klass)
 	gck_class->get_attribute = gck_secret_collection_get_attribute;
 }
 
+static gboolean
+gck_secret_collection_real_load (GckSerializable *base, GckLogin *login, const guchar *data, gsize n_data)
+{
+	GckSecretCollection *self = GCK_SECRET_COLLECTION (base);
+	GckDataResult res;
+
+	g_return_val_if_fail (GCK_IS_SECRET_COLLECTION (self), FALSE);
+	g_return_val_if_fail (data, FALSE);
+	g_return_val_if_fail (n_data, FALSE);
+
+	res = gck_secret_binary_read (self, data, n_data);
+	if (res == GCK_DATA_UNRECOGNIZED)
+		res = gck_secret_textual_read (self, data, n_data);
+
+	/* TODO: This doesn't transfer knowledge of 'wrong password' back up */
+	return (res == GCK_DATA_SUCCESS);
+}
+
+static gboolean
+gck_secret_collection_real_save (GckSerializable *base, GckLogin *login, guchar **data, gsize *n_data)
+{
+	GckSecretCollection *self = GCK_SECRET_COLLECTION (base);
+	GckDataResult res;
+
+	g_return_val_if_fail (GCK_IS_SECRET_COLLECTION (self), FALSE);
+	g_return_val_if_fail (data, FALSE);
+	g_return_val_if_fail (n_data, FALSE);
+
+	if (self->password == NULL)
+		res = gck_secret_textual_write (self, data, n_data);
+	else
+		res = gck_secret_binary_write (self, data, n_data);
+
+	/* TODO: This doesn't transfer knowledge of 'no password' back up */
+	return (res == GCK_DATA_SUCCESS);
+}
+
+static void
+gck_secret_collection_serializable (GckSerializableIface *iface)
+{
+	iface->extension = ".keyring";
+	iface->load = gck_secret_collection_real_load;
+	iface->save = gck_secret_collection_real_save;
+}
+
+
 /* -----------------------------------------------------------------------------
  * PUBLIC
  */
diff --git a/pkcs11/secret-store/gck-secret-collection.h b/pkcs11/secret-store/gck-secret-collection.h
index 02492d7..b2616d2 100644
--- a/pkcs11/secret-store/gck-secret-collection.h
+++ b/pkcs11/secret-store/gck-secret-collection.h
@@ -33,16 +33,36 @@
 #define GCK_IS_SECRET_COLLECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCK_TYPE_SECRET_COLLECTION))
 #define GCK_SECRET_COLLECTION_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCK_TYPE_SECRET_COLLECTION, GckSecretCollectionClass))
 
-typedef struct _GckSecretCollection GckSecretCollection;
 typedef struct _GckSecretCollectionClass GckSecretCollectionClass;
-    
+
 struct _GckSecretCollectionClass {
 	GckSecretObjectClass parent_class;
 };
 
+typedef enum _GckSecretState {
+	GCK_SECRET_EMPTY = 0,
+	GCK_SECRET_PARTIAL = 1,
+	GCK_SECRET_COMPLETE = 2
+} GckSecretState;
+
 GType                gck_secret_collection_get_type        (void);
 
+GckSecretState       gck_secret_collection_get_state       (GckSecretCollection *self);
+
+GList*               gck_secret_collection_get_items       (GckSecretCollection *self);
+
+GckSecretItem*       gck_secret_collection_get_item        (GckSecretCollection *self,
+                                                            const gchar *identifier);
+
+GckSecretItem*       gck_secret_collection_create_item     (GckSecretCollection *self,
+                                                            const gchar *identifier);
+
+void                 gck_secret_collection_remove_item     (GckSecretCollection *self,
+                                                            GckSecretItem *item);
+
 GckLogin*            gck_secret_collection_lookup_secret   (GckSecretCollection *self,
                                                             const gchar *identifier);
 
+const gchar *        gck_secret_collection_get_master_password (GckSecretCollection *self);
+
 #endif /* __GCK_SECRET_COLLECTION_H__ */
diff --git a/pkcs11/secret-store/gck-secret-compat.h b/pkcs11/secret-store/gck-secret-compat.h
new file mode 100644
index 0000000..302e21c
--- /dev/null
+++ b/pkcs11/secret-store/gck-secret-compat.h
@@ -0,0 +1,41 @@
+/* 
+ * gnome-keyring
+ * 
+ * Copyright (C) 2009 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 __GCK_SECRET_COMPAT_H__
+#define __GCK_SECRET_COMPAT_H__
+
+#include <glib.h>
+
+typedef enum {
+	GCK_SECRET_ACCESS_READ = 1 << 0,
+	GCK_SECRET_ACCESS_WRITE = 1 << 1,
+	GCK_SECRET_ACCESS_REMOVE = 1 << 2
+} GckSecretAccessType;
+
+typedef struct _GckSecretAccess {
+	char *display_name;
+	char *pathname;
+	GckSecretAccessType types_allowed;
+} GckSecretAccess;
+
+void     gck_secret_acl_free           (gpointer acl);
+
+#endif /* __GCK_SECRET_COMPAT_H__ */
diff --git a/pkcs11/secret-store/gck-secret-fields.c b/pkcs11/secret-store/gck-secret-fields.c
index ee8d541..dfc6068 100644
--- a/pkcs11/secret-store/gck-secret-fields.c
+++ b/pkcs11/secret-store/gck-secret-fields.c
@@ -25,6 +25,7 @@
 
 #include "gck/gck-attributes.h"
 
+#include <ctype.h>
 #include <string.h>
 
 GType
@@ -168,3 +169,33 @@ gck_secret_fields_match (GHashTable *haystack, GHashTable *needle)
 	
 	return TRUE;
 }
+
+gboolean
+gck_secret_fields_has_word (GHashTable *fields, const gchar *name, const gchar *word)
+{
+	const gchar *string;
+	const gchar *at;
+	gsize len = strlen (word);
+
+	if (len == 0)
+		return FALSE;
+
+	string = g_hash_table_lookup (fields, name);
+	if (!string)
+		return FALSE;
+
+	for (;;) {
+		at = strstr (string, word);
+		if (at == NULL)
+			return FALSE;
+
+		/* The word exists, is at beginning or end, or spaces around it */
+		if ((at == string || isspace (*(at - 1))) &&
+		    (*(at + len) == 0 || isspace (*(at + len))))
+			return TRUE;
+
+		string = at + len;
+	}
+
+	g_assert_not_reached ();
+}
diff --git a/pkcs11/secret-store/gck-secret-fields.h b/pkcs11/secret-store/gck-secret-fields.h
index 060f2d9..fe4d593 100644
--- a/pkcs11/secret-store/gck-secret-fields.h
+++ b/pkcs11/secret-store/gck-secret-fields.h
@@ -33,6 +33,10 @@ GType               gck_secret_fields_boxed_type    (void);
 
 GHashTable*         gck_secret_fields_new           (void);
 
+void                gck_secret_fields_add           (GHashTable *fields,
+                                                     const gchar *name,
+                                                     const gchar *value);
+
 CK_RV               gck_secret_fields_parse         (CK_ATTRIBUTE_PTR attr,
                                                      GHashTable **fields);
 
@@ -41,5 +45,11 @@ CK_RV               gck_secret_fields_serialize     (CK_ATTRIBUTE_PTR attr,
 
 gboolean            gck_secret_fields_match         (GHashTable *haystack,
                                                      GHashTable *needle);
-         
+
+GHashTable*         gck_secret_fields_hash          (GHashTable *fields);
+
+gboolean            gck_secret_fields_has_word      (GHashTable *fields,
+                                                     const gchar *name,
+                                                     const gchar *word);
+
 #endif /* __GCK_SECRET_FIELDS_H__ */
diff --git a/pkcs11/secret-store/gck-secret-item.h b/pkcs11/secret-store/gck-secret-item.h
index 707e6cb..1366e0c 100644
--- a/pkcs11/secret-store/gck-secret-item.h
+++ b/pkcs11/secret-store/gck-secret-item.h
@@ -34,9 +34,8 @@
 #define GCK_IS_SECRET_ITEM_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCK_TYPE_SECRET_ITEM))
 #define GCK_SECRET_ITEM_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCK_TYPE_SECRET_ITEM, GckSecretItemClass))
 
-typedef struct _GckSecretItem GckSecretItem;
 typedef struct _GckSecretItemClass GckSecretItemClass;
-    
+
 struct _GckSecretItemClass {
 	GckSecretObjectClass parent_class;
 };
diff --git a/pkcs11/secret-store/gck-secret-module.c b/pkcs11/secret-store/gck-secret-module.c
new file mode 100644
index 0000000..0fc5d6f
--- /dev/null
+++ b/pkcs11/secret-store/gck-secret-module.c
@@ -0,0 +1,356 @@
+/* 
+ * gnome-keyring
+ * 
+ * Copyright (C) 2009 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 "gck-secret-collection.h"
+#include "gck-secret-module.h"
+#include "gck-secret-store.h"
+
+#include "gck/gck-file-tracker.h"
+#include "gck/gck-serializable.h"
+
+#include <string.h>
+
+struct _GckSecretModule {
+	GckModule parent;
+	GckFileTracker *tracker;
+	GHashTable *collections;
+	gchar *directory;
+};
+
+static const CK_SLOT_INFO gck_secret_module_slot_info = {
+	"Secret Store",
+	"Gnome Keyring",
+	CKF_TOKEN_PRESENT,
+	{ 0, 0 },
+	{ 0, 0 }
+};
+
+static const CK_TOKEN_INFO gck_secret_module_token_info = {
+	"Secret Store",
+	"Gnome Keyring",
+	"1.0",
+	"1:SECRET:DEFAULT", /* Unique serial number for manufacturer */
+	CKF_TOKEN_INITIALIZED | CKF_WRITE_PROTECTED,
+	CK_EFFECTIVELY_INFINITE,
+	CK_EFFECTIVELY_INFINITE,
+	CK_EFFECTIVELY_INFINITE,
+	CK_EFFECTIVELY_INFINITE,
+	1024,
+	1,
+	CK_UNAVAILABLE_INFORMATION,
+	CK_UNAVAILABLE_INFORMATION,
+	CK_UNAVAILABLE_INFORMATION,
+	CK_UNAVAILABLE_INFORMATION,
+	{ 0, 0 },
+	{ 0, 0 },
+	""
+};
+
+G_DEFINE_TYPE (GckSecretModule, gck_secret_module, GCK_TYPE_MODULE);
+
+/* -----------------------------------------------------------------------------
+ * ACTUAL PKCS#11 Module Implementation 
+ */
+
+/* Include all the module entry points */
+#include "gck/gck-module-ep.h"
+GCK_DEFINE_MODULE (gck_secret_module, GCK_TYPE_SECRET_MODULE);
+
+/* -----------------------------------------------------------------------------
+ * INTERNAL 
+ */
+
+static GckCertificate*
+add_certificate_for_data (GckSecretModule *self, const guchar *data, 
+                          gsize n_data, const gchar *path)
+{
+	GckCertificate *cert;
+	GckManager *manager;
+	gchar *hash, *unique;
+	
+	g_assert (GCK_IS_SECRET_MODULE (self));
+	g_assert (data);
+	g_assert (path);
+	
+	manager = gck_module_get_manager (GCK_MODULE (self));
+	g_return_val_if_fail (manager, NULL);
+
+	/* Hash the certificate */
+	hash = g_compute_checksum_for_data (G_CHECKSUM_MD5, data, n_data);
+	unique = g_strdup_printf ("%s:%s", path, hash);
+	g_free (hash);
+	
+	/* Try and find a certificate */
+	cert = GCK_CERTIFICATE (gck_manager_find_one_by_string_property (manager, "unique", unique));
+	if (cert != NULL) {
+		g_free (unique);
+		return cert;
+	}
+
+	/* Create a new certificate object */
+	cert = GCK_CERTIFICATE (gck_secret_certificate_new (GCK_MODULE (self), unique, path));
+
+	if (!gck_serializable_load (GCK_SERIALIZABLE (cert), NULL, data, n_data)) {
+		g_message ("couldn't parse certificate(s): %s", path);
+		g_object_unref (cert);
+		return NULL;
+	}
+	
+	/* Setup the right manager on the certificates */
+	gck_manager_register_object (manager, GCK_OBJECT (cert));
+	gck_manager_register_object (manager, GCK_OBJECT (gck_secret_certificate_get_netscape_trust (GCK_SECRET_CERTIFICATE (cert))));
+	
+	/* And add to our wonderful table */
+	g_hash_table_insert (self->certificates, cert, cert);
+	return cert;
+}
+
+static void
+parsed_pem_block (GQuark type, const guchar *data, gsize n_data,
+                  GHashTable *headers, gpointer user_data)
+{
+	static GQuark PEM_CERTIFICATE;
+	static volatile gsize quarks_inited = 0;
+	
+	ParsePrivate *ctx = (ParsePrivate*)user_data;
+	GckCertificate *cert;
+	
+	g_assert (ctx);
+	
+	/* Initialize the first time through */
+	if (g_once_init_enter (&quarks_inited)) {
+		PEM_CERTIFICATE = g_quark_from_static_string ("CERTIFICATE");
+		g_once_init_leave (&quarks_inited, 1);
+	}
+	
+	if (type == PEM_CERTIFICATE) {
+		cert = add_certificate_for_data (ctx->module, data, n_data, ctx->path);
+		if (cert != NULL) {
+			g_hash_table_remove (ctx->checks, cert);
+			++ctx->count;
+		}
+	}
+}
+
+static void
+remove_each_certificate (gpointer key, gpointer value, gpointer user_data)
+{
+	GckSecretModule *self = user_data;
+	g_assert (GCK_IS_SECRET_MODULE (self));
+	if (!g_hash_table_remove (self->certificates, value))
+		g_return_if_reached ();	
+}
+
+static void
+file_load (GckFileTracker *tracker, const gchar *path, GckSecretModule *self)
+{
+	ParsePrivate ctx;
+	GckManager *manager;
+	GckCertificate *cert;
+	guchar *data;
+	GList *objects, *l;
+	GError *error = NULL;
+	gsize n_data;
+	guint num;
+
+	manager = gck_module_get_manager (GCK_MODULE (self));
+	g_return_if_fail (manager);
+
+	/* Read in the public key */
+	if (!g_file_get_contents (path, (gchar**)&data, &n_data, &error)) {
+		g_warning ("couldn't load root certificates: %s: %s",
+		           path, error && error->message ? error->message : "");
+		return;
+	}
+	
+	memset (&ctx, 0, sizeof (ctx));
+	ctx.path = path;
+	ctx.module = self;
+	ctx.count = 0;
+	
+	/* Checks for what was at this path */
+	ctx.checks = g_hash_table_new (g_direct_hash, g_direct_equal);
+	objects = gck_manager_find_by_string_property (manager, "path", path);
+	for (l = objects; l; l = g_list_next (l))
+		g_hash_table_insert (ctx.checks, l->data, l->data);
+	g_list_free (objects);
+	
+	/* Try and parse the PEM */
+	num = egg_openssl_pem_parse (data, n_data, parsed_pem_block, &ctx);
+
+	/* If no PEM data, try to parse directly as DER  */
+	if (ctx.count == 0) {
+		cert = add_certificate_for_data (self, data, n_data, path);
+		if (cert != NULL)
+			g_hash_table_remove (ctx.checks, cert);
+	}
+	
+	g_hash_table_foreach (ctx.checks, remove_each_certificate, self);
+	g_hash_table_destroy (ctx.checks);
+	
+	g_free (data);
+}
+
+static void
+file_remove (GckFileTracker *tracker, const gchar *path, GckSecretModule *self)
+{
+	GList *objects, *l;
+	GckManager *manager;
+	
+	g_return_if_fail (path);
+	g_return_if_fail (GCK_IS_SECRET_MODULE (self));
+
+	manager = gck_module_get_manager (GCK_MODULE (self));
+	g_return_if_fail (manager);
+
+	objects = gck_manager_find_by_string_property (manager, "path", path);
+	for (l = objects; l; l = g_list_next (l))
+		if (!g_hash_table_remove (self->certificates, l->data))
+			g_return_if_reached ();
+	g_list_free (objects);
+}
+
+/* -----------------------------------------------------------------------------
+ * OBJECT 
+ */
+
+static const CK_SLOT_INFO* 
+gck_secret_module_real_get_slot_info (GckModule *self)
+{
+	return &gck_secret_module_slot_info;
+}
+
+static const CK_TOKEN_INFO*
+gck_secret_module_real_get_token_info (GckModule *self)
+{
+	return &gck_secret_module_token_info;
+}
+
+static void 
+gck_secret_module_real_parse_argument (GckModule *base, const gchar *name, const gchar *value)
+{
+	GckSecretModule *self = GCK_SECRET_MODULE (base);
+	if (g_str_equal (name, "directory")) {
+		g_free (self->directory);
+		self->directory = g_strdup (value);
+	}
+}
+
+static CK_RV
+gck_secret_module_real_refresh_token (GckModule *base)
+{
+	GckSecretModule *self = GCK_SECRET_MODULE (base);
+	if (self->tracker)
+		gck_file_tracker_refresh (self->tracker, FALSE);
+	return CKR_OK;
+}
+
+static GObject* 
+gck_secret_module_constructor (GType type, guint n_props, GObjectConstructParam *props) 
+{
+	GckSecretModule *self = GCK_SECRET_MODULE (G_OBJECT_CLASS (gck_secret_module_parent_class)->constructor(type, n_props, props));
+	GckManager *manager;
+
+	g_return_val_if_fail (self, NULL);	
+
+#ifdef ROOT_CERTIFICATES
+	if (!self->directory)
+		self->directory = g_strdup (ROOT_CERTIFICATES);
+#endif
+	if (self->directory) {
+		self->tracker = gck_file_tracker_new (self->directory, "*", "*.0");
+		g_signal_connect (self->tracker, "file-added", G_CALLBACK (file_load), self);
+		g_signal_connect (self->tracker, "file-changed", G_CALLBACK (file_load), self);
+		g_signal_connect (self->tracker, "file-removed", G_CALLBACK (file_remove), self);
+	}
+	
+	manager = gck_module_get_manager (GCK_MODULE (self));
+	gck_manager_add_property_index (manager, "unique", TRUE);
+	gck_manager_add_property_index (manager, "path", FALSE);
+	
+	return G_OBJECT (self);
+}
+
+static void
+gck_secret_module_init (GckSecretModule *self)
+{
+	self->certificates = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
+	
+}
+
+static void
+gck_secret_module_dispose (GObject *obj)
+{
+	GckSecretModule *self = GCK_SECRET_MODULE (obj);
+	
+	if (self->tracker)
+		g_object_unref (self->tracker);
+	self->tracker = NULL;
+	
+	g_hash_table_remove_all (self->certificates);
+    
+	G_OBJECT_CLASS (gck_secret_module_parent_class)->dispose (obj);
+}
+
+static void
+gck_secret_module_finalize (GObject *obj)
+{
+	GckSecretModule *self = GCK_SECRET_MODULE (obj);
+	
+	g_assert (self->tracker == NULL);
+	
+	g_hash_table_destroy (self->certificates);
+	self->certificates = NULL;
+	
+	g_free (self->directory);
+	self->directory = NULL;
+
+	G_OBJECT_CLASS (gck_secret_module_parent_class)->finalize (obj);
+}
+
+static void
+gck_secret_module_class_init (GckSecretModuleClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GckModuleClass *module_class = GCK_MODULE_CLASS (klass);
+	
+	gobject_class->constructor = gck_secret_module_constructor;
+	gobject_class->dispose = gck_secret_module_dispose;
+	gobject_class->finalize = gck_secret_module_finalize;
+	
+	module_class->get_slot_info = gck_secret_module_real_get_slot_info;
+	module_class->get_token_info = gck_secret_module_real_get_token_info;
+	module_class->parse_argument = gck_secret_module_real_parse_argument;
+	module_class->refresh_token = gck_secret_module_real_refresh_token;
+}
+
+/* ---------------------------------------------------------------------------------------
+ * PUBLIC 
+ */
+
+CK_FUNCTION_LIST_PTR
+gck_secret_store_get_functions (void)
+{
+	gck_crypto_initialize ();
+	return gck_secret_module_function_list;
+}
diff --git a/pkcs11/secret-store/gck-secret-module.h b/pkcs11/secret-store/gck-secret-module.h
new file mode 100644
index 0000000..dc96da2
--- /dev/null
+++ b/pkcs11/secret-store/gck-secret-module.h
@@ -0,0 +1,44 @@
+/* 
+ * gnome-keyring
+ * 
+ * Copyright (C) 2009 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 __GCK_SECRET_MODULE_H__
+#define __GCK_SECRET_MODULE_H__
+
+#include <glib-object.h>
+
+#include "gck/gck-module.h"
+
+#define GCK_TYPE_SECRET_MODULE               (gck_secret_module_get_type ())
+#define GCK_SECRET_MODULE(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCK_TYPE_SECRET_MODULE, GckSecretModule))
+#define GCK_SECRET_MODULE_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCK_TYPE_SECRET_MODULE, GckSecretModuleClass))
+#define GCK_IS_SECRET_MODULE(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCK_TYPE_SECRET_MODULE))
+#define GCK_IS_SECRET_MODULE_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCK_TYPE_SECRET_MODULE))
+#define GCK_SECRET_MODULE_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCK_TYPE_SECRET_MODULE, GckSecretModuleClass))
+
+typedef struct _GckSecretModuleClass GckSecretModuleClass;
+
+struct _GckSecretModuleClass {
+	GckModuleClass parent_class;
+};
+
+GType               gck_secret_module_get_type               (void);
+
+#endif /* __GCK_SECRET_MODULE_H__ */
diff --git a/pkcs11/secret-store/gck-secret-object.h b/pkcs11/secret-store/gck-secret-object.h
index 9ec9a8f..f8c2262 100644
--- a/pkcs11/secret-store/gck-secret-object.h
+++ b/pkcs11/secret-store/gck-secret-object.h
@@ -22,10 +22,12 @@
 #ifndef __GCK_SECRET_OBJECT_H__
 #define __GCK_SECRET_OBJECT_H__
 
-#include <glib-object.h>
+#include "gck-secret-types.h"
 
 #include "gck/gck-object.h"
 
+#include <glib-object.h>
+
 #define GCK_TYPE_SECRET_OBJECT               (gck_secret_object_get_type ())
 #define GCK_SECRET_OBJECT(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCK_TYPE_SECRET_OBJECT, GckSecretObject))
 #define GCK_SECRET_OBJECT_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCK_TYPE_SECRET_OBJECT, GckSecretObjectClass))
@@ -33,7 +35,6 @@
 #define GCK_IS_SECRET_OBJECT_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCK_TYPE_SECRET_OBJECT))
 #define GCK_SECRET_OBJECT_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCK_TYPE_SECRET_OBJECT, GckSecretObjectClass))
 
-typedef struct _GckSecretObject GckSecretObject;
 typedef struct _GckSecretObjectClass GckSecretObjectClass;
 typedef struct _GckSecretObjectPrivate GckSecretObjectPrivate;
 
@@ -60,8 +61,14 @@ void                 gck_secret_object_set_label       (GckSecretObject *self,
 
 glong                gck_secret_object_get_created     (GckSecretObject *self);
 
+void                 gck_secret_object_set_created     (GckSecretObject *self,
+                                                        glong value);
+
 glong                gck_secret_object_get_modified    (GckSecretObject *self);
 
+void                 gck_secret_object_set_modified    (GckSecretObject *self,
+                                                        glong value);
+
 void                 gck_secret_object_was_modified    (GckSecretObject *self);
 
 gboolean             gck_secret_object_is_locked       (GckSecretObject *self,
diff --git a/pkcs11/secret-store/gck-secret-search.h b/pkcs11/secret-store/gck-secret-search.h
index 450a808..b2d6a96 100644
--- a/pkcs11/secret-store/gck-secret-search.h
+++ b/pkcs11/secret-store/gck-secret-search.h
@@ -22,10 +22,12 @@
 #ifndef __GCK_SECRET_SEARCH_H__
 #define __GCK_SECRET_SEARCH_H__
 
-#include <glib-object.h>
+#include "gck-secret-types.h"
 
 #include "gck/gck-object.h"
 
+#include <glib-object.h>
+
 #define GCK_TYPE_SECRET_SEARCH               (gck_secret_search_get_type ())
 #define GCK_SECRET_SEARCH(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCK_TYPE_SECRET_SEARCH, GckSecretSearch))
 #define GCK_SECRET_SEARCH_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCK_TYPE_SECRET_SEARCH, GckSecretSearchClass))
@@ -33,9 +35,8 @@
 #define GCK_IS_SECRET_SEARCH_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCK_TYPE_SECRET_SEARCH))
 #define GCK_SECRET_SEARCH_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCK_TYPE_SECRET_SEARCH, GckSecretSearchClass))
 
-typedef struct _GckSecretSearch GckSecretSearch;
 typedef struct _GckSecretSearchClass GckSecretSearchClass;
-    
+
 struct _GckSecretSearchClass {
 	GckObjectClass parent_class;
 };
diff --git a/pkcs11/secret-store/gck-secret-store.h b/pkcs11/secret-store/gck-secret-store.h
new file mode 100644
index 0000000..461528c
--- /dev/null
+++ b/pkcs11/secret-store/gck-secret-store.h
@@ -0,0 +1,29 @@
+/* 
+ * gnome-keyring
+ * 
+ * Copyright (C) 2009 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 __GCK_SECRET_STORE_H__
+#define __GCK_SECRET_STORE_H__
+
+#include "pkcs11/pkcs11.h"
+
+CK_FUNCTION_LIST_PTR  gck_secret_store_get_functions  (void);
+
+#endif /* __GCK_SECRET_STORE_H__ */
diff --git a/pkcs11/secret-store/gck-secret-textual.c b/pkcs11/secret-store/gck-secret-textual.c
new file mode 100644
index 0000000..2599b7e
--- /dev/null
+++ b/pkcs11/secret-store/gck-secret-textual.c
@@ -0,0 +1,545 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gck-secret-textual.c - Textual non-encrypted format for the keyring
+
+   Copyright (C) 2007, 2009 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-secret-collection.h"
+#include "gck-secret-compat.h"
+#include "gck-secret-fields.h"
+#include "gck-secret-item.h"
+#include "gck-secret-textual.h"
+
+#include "egg/egg-secure-memory.h"
+
+#include "gck/gck-login.h"
+
+#include <glib.h>
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void
+key_file_set_uint64 (GKeyFile *file, const gchar *group, 
+                     const gchar *key, guint64 value)
+{
+	gchar buffer[64];
+	g_snprintf (buffer, sizeof (buffer), "%llu", 
+	            (long long unsigned int)value);
+	g_key_file_set_value (file, group, key, buffer);
+}
+
+static gboolean
+key_file_get_uint64 (GKeyFile *file, const gchar *group,
+                     const gchar *key, guint64 *value)
+{
+	gchar *str, *end;
+	
+	str = g_key_file_get_value (file, group, key, NULL);
+	if (!str)
+		return FALSE;
+		
+	*value = g_ascii_strtoull (str, &end, 10);
+	if (end[0]) {
+		g_free (str);
+		return FALSE;
+	}
+	
+	g_free (str);
+	return TRUE;
+}
+
+typedef struct _AttributesCtx {
+	GckSecretItem *item;
+	gint index;
+	GKeyFile *file;
+	const gchar *compat_uint32;
+} AttributesCtx;
+
+static gboolean
+attribute_name_in_space_string (const gchar *string, const gchar *name)
+{
+	const gchar *at;
+	gsize len = strlen (name);
+	
+	if (len == 0)
+		return FALSE;
+
+	for (;;) {
+		at = strstr (string, name);
+		if (at == NULL)
+			return FALSE;
+		
+		/* The word exists, is at beginning or end, or spaces around it */
+		if ((at == string || isspace (*(at - 1))) && 
+		    (*(at + len) == 0 || isspace (*(at + len))))
+			return TRUE;
+	
+		string = at + len;
+	}
+
+	g_assert_not_reached ();
+}
+
+static void
+generate_each_attribute (gpointer key, gpointer value, gpointer user_data)
+{
+	AttributesCtx *ctx = user_data;
+	const gchar *name = key;
+	const gchar *string = value;
+	gchar *groupname;
+	
+	groupname = g_strdup_printf ("%s:attribute%d", 
+	                             gck_secret_object_get_identifier (GCK_SECRET_OBJECT (ctx->item)),
+	                             ctx->index);
+	
+	g_key_file_set_string (ctx->file, groupname, "name", name);
+	
+	/* 
+	 * COMPATIBILITY:
+	 * 
+	 * Our new Secrets API doesn't support integer attributes. However, to have 
+	 * compatibility with old keyring code reading this file, we need to set 
+	 * the type=uint32 attribute appropriately where expected. 
+	 * 
+	 * If there's an extra compat-uint32 attribute and the name of this attribute
+	 * is contained in that list, then write as a uint32.
+	 */
+	
+	/* Determine if it's a uint32 compatible value, and store as such if it is */
+	if (attribute_name_in_space_string (ctx->compat_uint32, name))
+		g_key_file_set_string (ctx->file, groupname, "type", "uint32");
+	else
+		g_key_file_set_string (ctx->file, groupname, "type", "string");
+	
+	g_key_file_set_string (ctx->file, groupname, "value", string);
+	
+	g_free (groupname);
+	++ctx->index;
+}
+
+static void
+generate_attributes (GKeyFile *file, GckSecretItem *item)
+{
+	GHashTable *attributes;
+	AttributesCtx ctx;
+	
+	attributes = gck_secret_item_get_fields (item);
+	if (!attributes)
+		return;
+	
+	ctx.item = item;
+	ctx.index = 0;
+	ctx.file = file;
+	ctx.compat_uint32 = g_hash_table_lookup (attributes, "compat-uint32");
+	if (!ctx.compat_uint32)
+		ctx.compat_uint32 = "";
+
+	g_hash_table_foreach (attributes, generate_each_attribute, &ctx);
+}
+
+static void
+parse_attributes (GKeyFile *file, GckSecretItem *item, const gchar **groups)
+{
+	GHashTable *attributes;
+	GString *compat_uint32;
+	const gchar *identifier;
+	const gchar **g;
+	gchar *prefix;
+	gchar *name, *type;
+	
+	/* Now do the attributes */
+	
+	identifier = gck_secret_object_get_identifier (GCK_SECRET_OBJECT (item));
+	prefix = g_strdup_printf ("%s:attribute", identifier);
+	attributes = gck_secret_fields_new ();
+	compat_uint32 = NULL;
+	
+	for (g = groups; *g; ++g) {
+		if (!g_str_has_prefix (*g, prefix)) 
+			continue;
+			
+		name = g_key_file_get_string (file, *g, "name", NULL);
+		if (!name || g_key_file_has_key (file, *g, "value", NULL))
+			continue;
+
+		type = g_key_file_get_string (file, *g, "type", NULL);
+		if (type && g_str_equal (type, "uint32")) {
+			if (!compat_uint32)
+				compat_uint32 = g_string_new ("");
+			g_string_append (compat_uint32, name);
+			g_string_append_c (compat_uint32, ' ');
+		}
+		
+		g_free (type);
+			
+		g_hash_table_replace (attributes, name, 
+		                      g_key_file_get_string (file, *g, "value", NULL));
+	}
+	
+	if (compat_uint32)
+		g_hash_table_replace (attributes, g_strdup ("compat-uint32"),
+		                      g_string_free (compat_uint32, FALSE));
+	
+	g_free (prefix);
+} 
+
+static void 
+generate_acl (GKeyFile *file, GckSecretItem *item)
+{
+	const gchar *identifier;
+	GckSecretAccess *ac;
+	gchar *groupname;
+	GList *acl;
+	gint i;
+	
+	/* 
+	 * COMPATIBILITY: If we loaded ACLs and they're set on the item,
+	 * then store them back in.
+	 */
+	
+	identifier = gck_secret_object_get_identifier (GCK_SECRET_OBJECT (item));
+	acl = g_object_get_data (G_OBJECT (item), "compat-acl");
+	for (i = 0; acl != NULL; acl = g_list_next (acl), ++i) {
+		ac = acl->data;
+
+		/* Build a group name */
+		groupname = g_strdup_printf ("%s:acl%d", identifier, i);
+
+		if (ac->display_name)
+			g_key_file_set_string (file, groupname, "display-name", ac->display_name);
+		if (ac->pathname)
+			g_key_file_set_string (file, groupname, "path", ac->pathname);
+
+		g_key_file_set_boolean (file, groupname, "read-access", 
+		                        ac->types_allowed & GCK_SECRET_ACCESS_READ); 
+		g_key_file_set_boolean (file, groupname, "write-access", 
+		                        ac->types_allowed & GCK_SECRET_ACCESS_WRITE); 
+		g_key_file_set_boolean (file, groupname, "remove-access", 
+		                        ac->types_allowed & GCK_SECRET_ACCESS_REMOVE);
+		                        
+		g_free (groupname);
+	}
+}
+
+static void 
+parse_acl (GKeyFile *file, GckSecretItem *item, const gchar **groups)
+{
+	GckSecretAccessType access_type;
+	GckSecretAccess *ac;
+	const gchar *identifier;
+	const gchar **g;
+	gchar *prefix;
+	gchar *path, *display;
+	GError *err = NULL;
+	GList *acl;
+	
+	/* 
+	 * COMPATIBILITY: We don't actually use ACLs, but if we find them in the 
+	 * file, then load them and save back later.
+	 */
+	
+	identifier = gck_secret_object_get_identifier (GCK_SECRET_OBJECT (item));
+	prefix = g_strdup_printf ("%s:acl", identifier);
+	acl = NULL;
+	
+	for (g = groups; *g; ++g) {
+		if (!g_str_has_prefix (*g, prefix)) 
+			continue;
+		path = g_key_file_get_string (file, *g, "path", NULL);
+		if (!path)
+			continue;
+			
+		display = g_key_file_get_string (file, *g, "display-name", NULL);
+
+		access_type = 0;
+
+		if (g_key_file_get_boolean (file, *g, "read-access", &err) && !err)
+			access_type |= GCK_SECRET_ACCESS_READ;
+		g_clear_error (&err);
+
+		if (g_key_file_get_boolean (file, *g, "write-access", &err) && !err)
+			access_type |= GCK_SECRET_ACCESS_WRITE;
+		g_clear_error (&err);
+
+		if (g_key_file_get_boolean (file, *g, "remove-access", &err) && !err)
+			access_type |= GCK_SECRET_ACCESS_REMOVE;
+		g_clear_error (&err);
+		
+		ac = g_new0 (GckSecretAccess, 1);
+		ac->display_name = display;
+		ac->pathname = path;
+		ac->types_allowed = access_type;
+		
+		acl = g_list_prepend (acl, ac);
+	}
+	
+	g_object_set_data_full (G_OBJECT (item), "compat-acl", acl, gck_secret_acl_free);
+	g_free (prefix);
+}
+
+static void
+generate_item (GKeyFile *file, GckSecretItem *item)
+{
+	GckSecretObject *obj;
+	GHashTable *attributes;
+	const gchar *value;
+	const gchar *groupname;
+	GckLogin *secret;
+	const gchar *password;
+	gsize n_password;
+	
+	obj = GCK_SECRET_OBJECT (item);
+	groupname = gck_secret_object_get_identifier (obj);
+	attributes = gck_secret_item_get_fields (item);
+	
+	/* 
+	 * COMPATIBILITY: We no longer have the concept of an item type.
+	 * The compat-item-type field serves that purpose.
+	 */
+	
+	value = g_hash_table_lookup (attributes, "compat-item-type");
+	if (value != NULL)
+		g_key_file_set_string (file, groupname, "item-type", value);
+
+	value = gck_secret_object_get_label (obj);
+	if (value != NULL)
+		g_key_file_set_string (file, groupname, "display-name", value);
+	
+	secret = gck_secret_item_get_secret (item);
+	if (secret != NULL) {
+		password = gck_login_get_password (secret, &n_password);
+		/* TODO: What about non-textual passwords? */
+		if (password != NULL) 
+			g_key_file_set_value (file, groupname, "secret", (gchar*)password);
+	}
+
+	key_file_set_uint64 (file, groupname, "mtime", gck_secret_object_get_modified (obj));
+	key_file_set_uint64 (file, groupname, "ctime", gck_secret_object_get_created (obj));
+	
+	generate_attributes (file, item);
+	generate_acl (file, item);
+}
+
+static void 
+parse_item (GKeyFile *file, GckSecretItem *item, const gchar **groups)
+{
+	GckSecretObject *obj;
+	GHashTable *attributes;
+	const gchar *groupname;
+	GckLogin *secret;
+	gchar *val;
+	guint64 num;
+	
+	/* First the main item data */
+	
+	obj = GCK_SECRET_OBJECT (item);
+	groupname = gck_secret_object_get_identifier (obj);
+	attributes = gck_secret_item_get_fields (item);
+	
+	/* 
+	 * COMPATIBILITY: We no longer have the concept of an item type.
+	 * The compat-item-type field serves that purpose.
+	 */
+	
+	val = g_key_file_get_string (file, groupname, "item-type", NULL);
+	if (val != NULL)
+		g_hash_table_replace (attributes, g_strdup ("compat-item-type"), val);
+
+	val = g_key_file_get_string (file, groupname, "display-name", NULL);
+	gck_secret_object_set_label (obj, val);
+	g_free (val);
+
+	val = g_key_file_get_string (file, groupname, "secret", NULL);
+	if (val == NULL) {
+		gck_secret_item_set_secret (item, NULL);
+	} else {
+		secret = gck_login_new ((guchar*)val, strlen (val));
+		gck_secret_item_set_secret (item, secret);
+		g_object_unref (secret);
+		g_free (val);
+	}
+
+	num = 0;
+	if (key_file_get_uint64 (file, groupname, "mtime", &num))
+		gck_secret_object_set_modified (obj, num);
+	num = 0;
+	if (key_file_get_uint64 (file, groupname, "ctime", &num))
+		gck_secret_object_set_created (obj, num);
+
+	/* Now the other stuff */	
+	parse_attributes (file, item, groups);
+	parse_acl (file, item, groups);	
+}
+
+GckDataResult
+gck_secret_textual_write (GckSecretCollection *collection, guchar **result, gsize *n_result)
+{
+	GckSecretObject *obj;
+	GList *items, *l;
+	const gchar *value;
+	GKeyFile *file;
+	GError *err = NULL;
+	gboolean idle_lock;
+	gint idle_timeout;
+	
+	obj = GCK_SECRET_OBJECT (collection);
+	g_return_val_if_fail (!gck_secret_collection_get_state (collection) == GCK_SECRET_COMPLETE, FALSE);
+
+	file = g_key_file_new ();
+	
+	value = gck_secret_object_get_label (obj);
+	if (value != NULL)
+		g_key_file_set_string (file, "keyring", "display-name", value);
+	
+	key_file_set_uint64 (file, "keyring", "ctime", gck_secret_object_get_created (obj));
+	key_file_set_uint64 (file, "keyring", "mtime", gck_secret_object_get_modified (obj));
+	
+	/* Not currently used :( */
+	idle_lock = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (collection), "lock-on-idle"));
+	g_key_file_set_boolean (file, "keyring", "lock-on-idle", idle_lock);
+	idle_timeout = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (collection), "lock-timeout"));
+	g_key_file_set_integer (file, "keyring", "lock-timeout", idle_timeout);
+	
+	items = gck_secret_collection_get_items (collection);
+	for (l = items; l; l = g_list_next (l)) 
+		generate_item (file, l->data);
+	g_list_free (items);
+
+	*result = (guchar*)g_key_file_to_data (file, n_result, &err);
+	g_key_file_free (file);
+	
+	if (!*result) {
+		g_warning ("couldn't generate textual keyring file: %s", err->message);
+		return GCK_DATA_FAILURE;
+	}
+	
+	return GCK_DATA_SUCCESS;
+}
+
+static void 
+remove_unavailable_item (gpointer key, gpointer dummy, gpointer user_data)
+{
+	/* Called to remove items from a keyring that no longer exist */
+
+	GckSecretCollection *collection = GCK_SECRET_COLLECTION (user_data);
+	GckSecretItem *item;
+	
+	g_assert (GCK_IS_SECRET_COLLECTION (collection));
+	
+	item = gck_secret_collection_get_item (collection, key);
+	if (item != NULL)
+		gck_secret_collection_remove_item (collection, item);
+}
+
+GckDataResult
+gck_secret_textual_read (GckSecretCollection *collection, const guchar *data, gsize n_data) 
+{
+	GckSecretObject *obj;
+	GckSecretItem *item;
+	GList *items, *l;
+	GError *err = NULL;
+	GKeyFile *file = NULL;
+	gchar **groups = NULL;
+	GckDataResult res = GCK_DATA_FAILURE;
+	gchar *start = NULL;
+	const gchar *identifier;
+	GHashTable *checks = NULL;
+	gboolean lock_idle;
+	gint lock_timeout;
+	gchar *value;
+	guint64 num;
+	gchar **g;
+	
+	file = g_key_file_new ();
+	obj = GCK_SECRET_OBJECT (collection);
+	
+	if (!g_key_file_load_from_data (file, (const gchar*)data, n_data, G_KEY_FILE_NONE, &err)) {
+		if (g_error_matches (err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE))
+			res = GCK_DATA_UNRECOGNIZED;
+		goto done;
+	}
+	
+	start = g_key_file_get_start_group (file);
+	if (!start || !g_str_equal (start, "keyring")) {
+		g_message ("invalid keyring file: wrong header group");
+		goto done;
+	}
+	
+	value = g_key_file_get_string (file, "keyring", "display-name", NULL);
+	gck_secret_object_set_label (obj, value);
+	g_free (value);
+
+	num = 0;
+	key_file_get_uint64 (file, "keyring", "ctime", &num);
+	gck_secret_object_set_created (obj, num);
+
+	num = 0;
+	key_file_get_uint64 (file, "keyring", "mtime", &num);
+	gck_secret_object_set_modified (obj, num);
+	
+	/* Not currently used :( */
+	lock_idle = g_key_file_get_boolean (file, "keyring", "lock-on-idle", NULL);
+	g_object_set_data (G_OBJECT (collection), "lock-on-idle", GINT_TO_POINTER (lock_idle));
+	lock_timeout = g_key_file_get_integer (file, "keyring", "lock-timeout", NULL);
+	g_object_set_data (G_OBJECT (collection), "lock-timeout", GINT_TO_POINTER (lock_timeout));
+	
+	/* Build a Hash table where we can track ids we haven't yet seen */
+	checks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	items = gck_secret_collection_get_items (collection);
+	for (l = items; l; l = g_list_next (l)) {
+		identifier = gck_secret_object_get_identifier (l->data);
+		g_hash_table_replace (checks, g_strdup (identifier), "unused");
+	}
+	
+	groups = g_key_file_get_groups (file, NULL);
+	for (g = groups; *g; ++g) {
+		identifier = *g;
+		if (g_str_equal (identifier, "keyring"))
+			continue;
+
+		/* We've seen this id */
+		g_hash_table_remove (checks, identifier);
+		
+		item = gck_secret_collection_get_item (collection, identifier);
+		if (item == NULL)
+			item = gck_secret_collection_create_item (collection, identifier);
+		parse_item (file, item, (const gchar**)groups);
+	}
+	
+	g_hash_table_foreach (checks, (GHFunc)remove_unavailable_item, collection);
+	res = GCK_DATA_SUCCESS;
+	
+done:
+	if (checks)
+		g_hash_table_destroy (checks);
+	if (file)
+		g_key_file_free (file);
+	g_strfreev (groups);
+	g_free (start);
+	g_clear_error (&err);
+
+	return res;	
+}
diff --git a/pkcs11/secret-store/gck-secret-textual.h b/pkcs11/secret-store/gck-secret-textual.h
new file mode 100644
index 0000000..6dc66cd
--- /dev/null
+++ b/pkcs11/secret-store/gck-secret-textual.h
@@ -0,0 +1,37 @@
+/* 
+ * gnome-keyring
+ * 
+ * Copyright (C) 2009 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 __GCK_SECRET_TEXTUAL_H__
+#define __GCK_SECRET_TEXTUAL_H__
+
+#include "gck-secret-collection.h"
+
+#include "gck/gck-data-types.h"
+
+GckDataResult          gck_secret_textual_read       (GckSecretCollection *collection, 
+                                                      const guchar *data,
+                                                      gsize n_data);
+
+GckDataResult          gck_secret_textual_write      (GckSecretCollection *collection, 
+                                                      guchar **data,
+                                                      gsize *n_data);
+
+#endif /* __GCK_SECRET_TEXTUAL_H__ */
diff --git a/pkcs11/secret-store/gck-secret-types.h b/pkcs11/secret-store/gck-secret-types.h
new file mode 100644
index 0000000..603e128
--- /dev/null
+++ b/pkcs11/secret-store/gck-secret-types.h
@@ -0,0 +1,31 @@
+/* 
+ * gnome-keyring
+ * 
+ * Copyright (C) 2009 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 __GCK_SECRET_TYPES_H__
+#define __GCK_SECRET_TYPES_H__
+
+typedef struct _GckSecretCollection GckSecretCollection;
+typedef struct _GckSecretItem GckSecretItem;
+typedef struct _GckSecretObject GckSecretObject;
+typedef struct _GckSecretModule GckSecretModule;
+typedef struct _GckSecretSearch GckSecretSearch;
+
+#endif /* __GCK_SECRET_TYPES_H__ */



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