[gnome-keyring] gcr: Implement parsing of openpgp packet contents into records.



commit 46e02e0a3ef23367caebb4fb0b4c123beee346bf
Author: Stef Walter <stefw collabora co uk>
Date:   Fri Sep 16 12:29:58 2011 +0200

    gcr: Implement parsing of openpgp packet contents into records.
    
     * Also centralize our timegm() implementation for dumb OS's.
     * Add tests for openpgp packet parsing
     * Produces with-colons format in the gnupg style, slight differences
       but not incompatible.

 .gitignore                          |    1 +
 docs/reference/gcr/gcr-sections.txt |   10 +
 egg/Makefile.am                     |    1 +
 egg/egg-asn1x.c                     |   31 +-
 egg/egg-timegm.c                    |   58 ++
 egg/egg-timegm.h                    |   33 +
 gck/gck-slot.c                      |   35 +-
 gcr/gcr-gnupg-collection.c          |    2 +-
 gcr/gcr-gnupg-key.c                 |    4 +-
 gcr/gcr-gnupg-util.c                |   59 +--
 gcr/gcr-openpgp.c                   | 1125 ++++++++++++++++++++++++++++++++++-
 gcr/gcr-openpgp.h                   |    9 +
 gcr/gcr-parser.c                    |    4 +-
 gcr/gcr-record.c                    |  611 ++++++++++++++++++--
 gcr/gcr-record.h                    |  142 ++++-
 gcr/tests/Makefile.am               |    1 +
 gcr/tests/files/secring.gpg         |  Bin 0 -> 4398 bytes
 gcr/tests/frob-openpgp.c            |  122 ++++
 gcr/tests/test-openpgp.c            |  270 ++++++++-
 gcr/tests/test-record.c             |   22 -
 pkcs11/gkm/gkm-attributes.c         |   33 +-
 21 files changed, 2309 insertions(+), 264 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 35ab761..6f93373 100644
--- a/.gitignore
+++ b/.gitignore
@@ -123,6 +123,7 @@ run-tests
 /gcr/tests/frob-combo-selector
 /gcr/tests/frob-tree-selector
 /gcr/tests/frob-unlock-options
+/gcr/tests/frob-openpgp
 /gcr/tests/frob-parser
 /gcr/tests/frob-unlock
 /gcr/tests/test-certificate
diff --git a/docs/reference/gcr/gcr-sections.txt b/docs/reference/gcr/gcr-sections.txt
index 84ab8c0..a881547 100644
--- a/docs/reference/gcr/gcr-sections.txt
+++ b/docs/reference/gcr/gcr-sections.txt
@@ -539,6 +539,11 @@ GCR_RECORD_SCHEMA_SEC
 GCR_RECORD_SCHEMA_ATTRIBUTE
 GCR_RECORD_SCHEMA_FPR
 GCR_RECORD_SCHEMA_XA1
+GCR_RECORD_SCHEMA_RVK
+GCR_RECORD_SCHEMA_SIG
+GCR_RECORD_SCHEMA_SSB
+GCR_RECORD_SCHEMA_SUB
+GCR_RECORD_SCHEMA_UAT
 GCR_GNUPG_COLLECTION
 GCR_GNUPG_COLLECTION_CLASS
 GCR_GNUPG_COLLECTION_GET_CLASS
@@ -560,6 +565,10 @@ GcrRecordSecColumns
 GcrRecordAttributeColumns
 GcrRecordFprColumns
 GcrRecordXa1Columns
+GcrRecordKeyColumns
+GcrRecordRvkColumns
+GcrRecordSigColumns
+GcrRecordUatColumns
 GcrRecord
 GCR_GNUPG_PROCESS
 GCR_GNUPG_PROCESS_CLASS
@@ -601,4 +610,5 @@ GCR_TYPE_CALLBACK_OUTPUT_STREAM
 GcrCallbackOutputFunc
 GcrCallbackOutputStream
 GcrCallbackOutputStreamClass
+GcrOpenpgpParseFlags
 </SECTION>
diff --git a/egg/Makefile.am b/egg/Makefile.am
index 4b61830..feda9b2 100644
--- a/egg/Makefile.am
+++ b/egg/Makefile.am
@@ -44,6 +44,7 @@ libegg_la_SOURCES = \
 	egg-spawn.c egg-spawn.h \
 	egg-symkey.c egg-symkey.h \
 	egg-testing.c egg-testing.h \
+	egg-timegm.c egg-timegm.h \
 	egg-asn1-defs.h \
 	$(BUILT_SOURCES)
 
diff --git a/egg/egg-asn1x.c b/egg/egg-asn1x.c
index 50bc133..db64fd7 100644
--- a/egg/egg-asn1x.c
+++ b/egg/egg-asn1x.c
@@ -48,6 +48,7 @@
 #include "config.h"
 
 #include "egg-asn1x.h"
+#include "egg-timegm.h"
 
 #include <libtasn1.h>
 
@@ -1778,36 +1779,6 @@ two_to_four_digit_year (int year)
 		return century + year;
 }
 
-#ifndef HAVE_TIMEGM
-time_t timegm(struct tm *t)
-{
-	time_t tl, tb;
-	struct tm *tg;
-
-	tl = mktime (t);
-	if (tl == -1)
-	{
-		t->tm_hour--;
-		tl = mktime (t);
-		if (tl == -1)
-			return -1; /* can't deal with output from strptime */
-		tl += 3600;
-	}
-	tg = gmtime (&tl);
-	tg->tm_isdst = 0;
-	tb = mktime (tg);
-	if (tb == -1)
-	{
-		tg->tm_hour--;
-		tb = mktime (tg);
-		if (tb == -1)
-			return -1; /* can't deal with output from gmtime */
-		tb += 3600;
-	}
-	return (tl - (tb - tl));
-}
-#endif // NOT_HAVE_TIMEGM
-
 static gboolean
 parse_utc_time (const gchar *time, gsize n_time,
                 struct tm* when, gint *offset)
diff --git a/egg/egg-timegm.c b/egg/egg-timegm.c
new file mode 100644
index 0000000..b42ced9
--- /dev/null
+++ b/egg/egg-timegm.c
@@ -0,0 +1,58 @@
+/* Copyright (C) 1999, 2001-2002 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C 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 GNU C 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 GNU C 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.  */
+
+/* Extracted from misc/mkdtemp.c and sysdeps/posix/tempname.c.  */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "egg-timegm.h"
+
+#ifndef HAVE_TIMEGM
+
+time_t
+timegm(struct tm *t)
+{
+	time_t tl, tb;
+	struct tm *tg;
+
+	tl = mktime (t);
+	if (tl == -1)
+	{
+		t->tm_hour--;
+		tl = mktime (t);
+		if (tl == -1)
+			return -1;
+		tl += 3600;
+	    }
+	tg = gmtime (&tl);
+	tg->tm_isdst = 0;
+	tb = mktime (tg);
+	if (tb == -1)
+	{
+		tg->tm_hour--;
+		tb = mktime (tg);
+		if (tb == -1)
+			return -1;
+		tb += 3600;
+	}
+	return (tl - (tb - tl));
+}
+
+#endif
diff --git a/egg/egg-timegm.h b/egg/egg-timegm.h
new file mode 100644
index 0000000..37a816a
--- /dev/null
+++ b/egg/egg-timegm.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Copyright (C) 1999, 2001-2002 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C 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 GNU C 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 GNU C 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.  */
+
+/* Extracted from misc/mkdtemp.c and sysdeps/posix/tempname.c.  */
+
+#ifndef EGG_TIMEGM_H_
+#define EGG_TIMEGM_H_
+
+#include <time.h>
+
+#ifndef HAVE_TIMEGM
+
+time_t      timegm    (struct tm *t);
+
+#endif
+
+#endif /* EGG_TIMEGM_H_ */
diff --git a/gck/gck-slot.c b/gck/gck-slot.c
index ebde95c..3705b39 100644
--- a/gck/gck-slot.c
+++ b/gck/gck-slot.c
@@ -26,6 +26,8 @@
 #include "gck.h"
 #include "gck-private.h"
 
+#include "egg/egg-timegm.h"
+
 #include <string.h>
 
 /**
@@ -57,39 +59,6 @@ struct _GckSlotPrivate {
 
 G_DEFINE_TYPE (GckSlot, gck_slot, G_TYPE_OBJECT);
 
-#ifndef HAVE_TIMEGM
-
-time_t
-timegm(struct tm *t)
-{
-	time_t tl, tb;
-	struct tm *tg;
-
-	tl = mktime (t);
-	if (tl == -1)
-	{
-		t->tm_hour--;
-		tl = mktime (t);
-		if (tl == -1)
-			return -1;
-		tl += 3600;
-	    }
-	tg = gmtime (&tl);
-	tg->tm_isdst = 0;
-	tb = mktime (tg);
-	if (tb == -1)
-	{
-		tg->tm_hour--;
-		tb = mktime (tg);
-		if (tb == -1)
-			return -1;
-		tb += 3600;
-	}
-	return (tl - (tb - tl));
-}
-
-#endif
-
 /* ----------------------------------------------------------------------------
  * HELPERS
  */
diff --git a/gcr/gcr-gnupg-collection.c b/gcr/gcr-gnupg-collection.c
index 3d65ba8..bbcaf20 100644
--- a/gcr/gcr-gnupg-collection.c
+++ b/gcr/gcr-gnupg-collection.c
@@ -409,7 +409,7 @@ process_outstanding_attribute (GcrGnupgCollectionLoad *load, GcrRecord *record)
 
 	if (!_gcr_record_get_uint (record, GCR_RECORD_ATTRIBUTE_LENGTH, &length))
 		g_return_val_if_reached (FALSE);
-	fingerprint = _gcr_record_get_raw (record, GCR_RECORD_ATTRIBUTE_FINGERPRINT);
+	fingerprint = _gcr_record_get_raw (record, GCR_RECORD_ATTRIBUTE_KEY_FINGERPRINT);
 	g_return_val_if_fail (fingerprint != NULL, FALSE);
 
 	/* Do we have enough data for this attribute? */
diff --git a/gcr/gcr-gnupg-key.c b/gcr/gcr-gnupg-key.c
index 893ab27..7f269f1 100644
--- a/gcr/gcr-gnupg-key.c
+++ b/gcr/gcr-gnupg-key.c
@@ -511,10 +511,10 @@ _gcr_gnupg_key_get_keyid_for_records (GPtrArray *records)
 
 	record = _gcr_record_find (records, GCR_RECORD_SCHEMA_PUB);
 	if (record != NULL)
-		return _gcr_record_get_raw (record, GCR_RECORD_PUB_KEYID);
+		return _gcr_record_get_raw (record, GCR_RECORD_KEY_KEYID);
 	record = _gcr_record_find (records, GCR_RECORD_SCHEMA_SEC);
 	if (record != NULL)
-		return _gcr_record_get_raw (record, GCR_RECORD_SEC_KEYID);
+		return _gcr_record_get_raw (record, GCR_RECORD_KEY_KEYID);
 	return NULL;
 }
 
diff --git a/gcr/gcr-gnupg-util.c b/gcr/gcr-gnupg-util.c
index 93677b0..b85836b 100644
--- a/gcr/gcr-gnupg-util.c
+++ b/gcr/gcr-gnupg-util.c
@@ -44,71 +44,44 @@ _gcr_gnupg_build_xa1_record (GcrRecord *meta, gpointer attribute,
 {
 	gchar hash[20];
 	gchar *hex;
-	gchar *status = "";
-	gint state, save;
-	gsize length;
-	gsize n_prefix, estimate;
-	GString *output;
+	gchar status = 0;
 	GcrRecord *record;
 	guint flags, type;
-	const gchar *fingerprint, *created, *expiry;
+	const gchar *created, *expiry;
 
 	g_return_val_if_fail (meta, NULL);
 
+	record = _gcr_record_new (GCR_RECORD_SCHEMA_XA1, GCR_RECORD_XA1_MAX, ':');
+
 	gcry_md_hash_buffer (GCRY_MD_RMD160, hash, attribute, n_attribute);
 	hex = egg_hex_encode_full (hash, sizeof (hash), TRUE, 0, 1);
+	_gcr_record_take_raw (record, GCR_RECORD_XA1_FINGERPRINT, hex);
 
 	if (!_gcr_record_get_uint (meta, GCR_RECORD_ATTRIBUTE_FLAGS, &flags))
 		flags = 0;
 
-	if (!_gcr_record_get_uint (meta, GCR_RECORD_ATTRIBUTE_TYPE, &type))
-		type = 0;
-
-	fingerprint = _gcr_record_get_raw (meta, GCR_RECORD_ATTRIBUTE_FINGERPRINT);
-	if (fingerprint == NULL)
-		fingerprint = "";
+	if (_gcr_record_get_uint (meta, GCR_RECORD_ATTRIBUTE_TYPE, &type))
+		_gcr_record_set_uint (record, GCR_RECORD_XA1_TYPE, type);
 
 	created = _gcr_record_get_raw (meta, GCR_RECORD_ATTRIBUTE_TIMESTAMP);
 	if (created == NULL)
-		created = "0";
+		_gcr_record_set_raw (record, GCR_RECORD_XA1_TIMESTAMP, created);
 
 	expiry = _gcr_record_get_raw (meta, GCR_RECORD_ATTRIBUTE_EXPIRY);
-	if (expiry == NULL)
-		expiry = "";
+	if (expiry != NULL)
+		_gcr_record_set_raw (record, GCR_RECORD_XA1_EXPIRY, expiry);
 
 	/* These values are from gnupg doc/DETAILS */
 	if (flags & 0x02)
-		status = "r";
+		status = 'r';
 	else if (flags & 0x04)
-		status = "e";
+		status = 'e';
 	else if (flags & 0x01)
-		status = "P";
-
-	/* Algorithm from Glib reference */
-	estimate = n_attribute * 4 / 3 + n_attribute * 4 / (3 * 65) + 7;
-
-	output = g_string_sized_new (64 + estimate);
-	g_string_append_printf (output, "xa1::%u:%u:%s:%s:%s:%s:%s:",
-	                        (guint)n_attribute, type, fingerprint,
-	                        created, expiry, hex, status);
-
-	g_free (hex);
-
-	/* Resize string to fit the base64 data. */
-	n_prefix = output->len;
-	g_string_set_size (output, n_prefix + estimate);
-
-	/* The actual base64 data, without line breaks */
-	state = save = 0;
-	length = g_base64_encode_step ((guchar*)attribute, n_attribute, FALSE,
-	                               output->str + n_prefix, &state, &save);
-	length += g_base64_encode_close (TRUE, output->str + n_prefix + length,
-	                                 &state, &save);
+		status = 'P';
+	if (status != 0)
+		_gcr_record_set_char (record, GCR_RECORD_XA1_TRUST, status);
 
-	g_assert (length <= estimate);
-	g_string_set_size (output, n_prefix + length);
-	record = _gcr_record_take_colons (g_string_free (output, FALSE));
-	g_assert (record);
+	_gcr_record_set_base64 (record, GCR_RECORD_XA1_DATA, attribute, n_attribute);
 
 	return record;
 }
diff --git a/gcr/gcr-openpgp.c b/gcr/gcr-openpgp.c
index 073ec7d..abf6800 100644
--- a/gcr/gcr-openpgp.c
+++ b/gcr/gcr-openpgp.c
@@ -28,21 +28,78 @@
 #include "gcr-record.h"
 #include "gcr-types.h"
 
-#include "pkcs11/pkcs11.h"
+#include "egg/egg-hex.h"
+
+#include <gcrypt.h>
 
 #include <string.h>
 
+typedef enum {
+	OPENPGP_ALGO_RSA = 1,
+	OPENPGP_ALGO_RSA_E = 2,
+	OPENPGP_ALGO_RSA_S = 3,
+	OPENPGP_ALGO_ELG_E = 16,
+	OPENPGP_ALGO_DSA = 17
+} OpenpgpPkAlgo;
+
+typedef enum {
+	OPENPGP_PKT_RESERVED = 0,
+	OPENPGP_PKT_PUBKEY_ENC = 1,
+	OPENPGP_PKT_SIGNATURE = 2,
+	OPENPGP_PKT_ONEPASS_SIG = 4,
+	OPENPGP_PKT_SECRET_KEY = 5,
+	OPENPGP_PKT_PUBLIC_KEY = 6,
+	OPENPGP_PKT_SECRET_SUBKEY = 7,
+	OPENPGP_PKT_COMPRESSED = 8,
+	OPENPGP_PKT_MARKER = 10,
+	OPENPGP_PKT_LITERAL = 11,
+	OPENPGP_PKT_RING_TRUST = 12,
+	OPENPGP_PKT_USER_ID = 13,
+	OPENPGP_PKT_PUBLIC_SUBKEY = 14,
+	OPENPGP_PKT_OLD_COMMENT = 16,
+	OPENPGP_PKT_ATTRIBUTE = 17,
+	OPENPGP_PKT_MDC = 19
+} OpenpgpPktType;
+
+typedef enum {
+	OPENPGP_SIG_CREATION = 2,
+	OPENPGP_SIG_EXPIRY = 3,
+	OPENPGP_SIG_EXPORTABLE = 4,
+	OPENPGP_SIG_TRUST = 5,
+	OPENPGP_SIG_REGULAR_EXPRESSION = 6,
+	OPENPGP_SIG_REVOCABLE = 7,
+	OPENPGP_SIG_KEY_EXPIRY = 9,
+	OPENPGP_SIG_SYMMETRIC_ALGOS = 11,
+	OPENPGP_SIG_REVOCATION_KEY = 12,
+	OPENPGP_SIG_ISSUER = 16,
+	OPENPGP_SIG_NOTATION_DATA = 20,
+	OPENPGP_SIG_HASH_ALGOS = 21,
+	OPENPGP_SIG_COMPRESSION_ALGOS = 22,
+	OPENPGP_SIG_KEYSERVER_PREFS = 23,
+	OPENPGP_SIG_PREFERRED_KEYSERVER = 24,
+	OPENPGP_SIG_PRIMARY_USERID = 25,
+	OPENPGP_SIG_POLICY_URI = 26,
+	OPENPGP_SIG_KEY_FLAGS = 27,
+	OPENPGP_SIG_SIGNER_USERID = 28,
+	OPENPGP_SIG_REVOCATION_REASON = 29,
+	OPENPGP_SIG_FEATURES = 30,
+	OPENPGP_SIG_TARGET = 31,
+	OPENPGP_SIG_EMBEDDED_SIGNATURE = 32,
+} OpenpgpSigPacket;
+
 static gboolean
 read_byte (const guchar **at,
            const guchar *end,
-           guchar *result)
+           guint8 *result)
 {
 	g_assert (at);
 	if (*at == end)
 		*at = NULL;
 	if (*at == NULL)
 		return FALSE;
-	*result = *((*at)++);
+	if (result)
+		*result = *(*at);
+	(*at)++;
 	return TRUE;
 }
 
@@ -53,11 +110,12 @@ read_bytes (const guchar **at,
             gsize length)
 {
 	g_assert (at);
-	if (*at + length >= end)
+	if (*at + length > end)
 		*at = NULL;
 	if (*at == NULL)
 		return FALSE;
-	memcpy (buffer, *at, length);
+	if (buffer != NULL)
+		memcpy (buffer, *at, length);
 	(*at) += length;
 	return TRUE;
 }
@@ -68,9 +126,11 @@ read_uint32 (const guchar **at,
              guint32 *value)
 {
 	guchar buf[4];
+	g_assert (at);
 	if (!read_bytes (at, end, buf, 4))
 		return FALSE;
-	*value = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
+	if (value)
+		*value = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
 	return TRUE;
 }
 
@@ -80,9 +140,37 @@ read_uint16 (const guchar **at,
              guint16 *value)
 {
 	guchar buf[2];
+	g_assert (at);
 	if (!read_bytes (at, end, buf, 2))
 		return FALSE;
-	*value = buf[0] << 8 | buf[1];
+	if (value)
+		*value = buf[0] << 8 | buf[1];
+	return TRUE;
+}
+
+static gboolean
+read_mpi (const guchar **at,
+          const guchar *end,
+          guint16 *bits,
+          guchar **value)
+{
+	gsize bytes;
+	guint16 b;
+	g_assert (at);
+	if (!bits)
+		bits = &b;
+	if (!read_uint16 (at, end, bits))
+		return FALSE;
+	bytes = (*bits + 7) / 8;
+	if (bytes == 0)
+		return FALSE;
+	if (value)
+		*value = g_malloc (bytes);
+	if (!read_bytes (at, end, value ? *value : NULL, bytes)) {
+		if (value)
+			g_free (*value);
+		return FALSE;
+	}
 	return TRUE;
 }
 
@@ -91,7 +179,7 @@ read_new_length (const guchar **at,
                  const guchar *end,
                  gsize *pkt_len)
 {
-	guchar c, c1;
+	guint8 c, c1;
 	guint32 val;
 
 	if (!read_byte (at, end, &c))
@@ -123,7 +211,7 @@ read_old_length (const guchar **at,
 	gsize llen = ctb & 0x03;
 	guint16 v16;
 	guint32 v32;
-	guchar c;
+	guint8 c;
 
 	if (llen == 0) {
 		if (!read_byte (at, end, &c))
@@ -147,12 +235,11 @@ read_old_length (const guchar **at,
 static GcrDataError
 read_openpgp_packet (const guchar **at,
                      const guchar *end,
-                     GPtrArray *records,
+                     guint8 *pkt_type,
                      gsize *length)
 {
-	guchar pkt_type;
 	gboolean new_ctb;
-	guchar ctb;
+	guint8 ctb;
 	gboolean ret;
 
 	if (!read_byte (at, end, &ctb))
@@ -162,17 +249,17 @@ read_openpgp_packet (const guchar **at,
 
 	/* RFC2440 packet format. */
 	if (ctb & 0x40) {
-		pkt_type = ctb & 0x3f;
+		*pkt_type = ctb & 0x3f;
 		new_ctb = TRUE;
 
 	/* the old RFC1991 packet format. */
 	} else {
-		pkt_type = ctb & 0x3f;
-		pkt_type >>= 2;
+		*pkt_type = ctb & 0x3f;
+		*pkt_type >>= 2;
 		new_ctb = FALSE;
 	}
 
-	if (pkt_type > 63)
+	if (*pkt_type > 63)
 		return GCR_ERROR_UNRECOGNIZED;
 
 	if (new_ctb)
@@ -184,43 +271,1029 @@ read_openpgp_packet (const guchar **at,
 
 	if ((*at) + *length > end)
 		return GCR_ERROR_FAILURE;
+
 	return GCR_SUCCESS;
 }
 
+static gchar *
+hash_user_id_or_attribute (const guchar *beg,
+                           const guchar *end)
+{
+	guint8 digest[20] = { 0, };
+
+	g_assert (beg != NULL);
+	g_assert (end > beg);
+
+	gcry_md_hash_buffer (GCRY_MD_RMD160, digest, beg, end - beg);
+	return egg_hex_encode_full (digest, sizeof (digest), TRUE, 0, 0);
+}
+
+static gboolean
+parse_v3_rsa_bits_and_keyid (const guchar **at,
+                             const guchar *end,
+                             guint16 *bits,
+                             gchar **keyid)
+{
+	guchar *n;
+	gsize bytes;
+
+	g_assert (bits);
+	g_assert (keyid);
+
+	/* Read in the modulus */
+	if (!read_mpi (at, end, bits, &n))
+		return FALSE;
+
+	/* Last 64-bits of modulus are keyid */
+	bytes = (*bits + 7) / 8;
+	if (bytes < 8) {
+		g_free (n);
+		return FALSE;
+	}
+
+	*keyid = egg_hex_encode_full (n + (bytes - 8), 8, TRUE, 0, 0);
+	return TRUE;
+}
+
+static gchar *
+hash_v4_keyid (const guchar *data,
+               const guchar *end)
+{
+	gcry_md_hd_t mdh;
+	gcry_error_t gcry;
+	guchar header[3];
+	guint8 *digest;
+	gchar *keyid;
+	gsize len;
+
+	/*
+	 * Both primary and subkeys use the public key tag byte
+	 * 0x99 to construct the hash. So we skip over that here.
+	 */
+
+	g_assert (data != NULL);
+	g_assert (end > data);
+
+	len = end - data;
+	g_return_val_if_fail (len < G_MAXUSHORT, NULL);
+
+	header[0] = 0x99;
+	header[1] = len >> 8 & 0xff;
+	header[2] = len & 0xff;
+
+	gcry = gcry_md_open (&mdh, GCRY_MD_SHA1, 0);
+	g_return_val_if_fail (gcry == 0, NULL);
+
+	gcry_md_write (mdh, header, 3);
+	gcry_md_write (mdh, data, len);
+
+	digest = gcry_md_read (mdh, 0);
+	keyid = egg_hex_encode_full (digest + 12, 8, TRUE, 0, 0);
+	gcry_md_close (mdh);
+
+	return keyid;
+}
+
+static gboolean
+parse_v4_algo_bits (const guchar **at,
+                    const guchar *end,
+                    guint8 algo,
+                    guint16 *bits)
+{
+	switch (algo) {
+	case OPENPGP_ALGO_RSA:
+	case OPENPGP_ALGO_RSA_E:
+	case OPENPGP_ALGO_RSA_S:
+		if (!read_mpi (at, end, bits, NULL) ||
+		    !read_mpi (at, end, NULL, NULL))
+			return FALSE;
+		return TRUE;
+	case OPENPGP_ALGO_DSA:
+		if (!read_mpi (at, end, bits, NULL) ||
+		    !read_mpi (at, end, NULL, NULL) ||
+		    !read_mpi (at, end, NULL, NULL) ||
+		    !read_mpi (at, end, NULL, NULL))
+			return FALSE;
+		return TRUE;
+	case OPENPGP_ALGO_ELG_E:
+		if (!read_mpi (at, end, bits, NULL) ||
+		    !read_mpi (at, end, NULL, NULL) ||
+		    !read_mpi (at, end, NULL, NULL))
+			return FALSE;
+		return TRUE;
+	default: /* Unsupported key */
+		return FALSE;
+	}
+}
+
+static const gchar *
+default_caps_for_algo (guint8 algo)
+{
+	switch (algo) {
+	case OPENPGP_ALGO_RSA:
+		return "cse";
+	case OPENPGP_ALGO_RSA_E:
+		return "e";
+	case OPENPGP_ALGO_RSA_S:
+		return "s";
+	case OPENPGP_ALGO_ELG_E:
+		return "e";
+	case OPENPGP_ALGO_DSA:
+		return "sca";
+	default:
+		return "";
+	}
+}
+
+static gboolean
+parse_public_key_or_subkey (GQuark schema,
+                            guint n_columns,
+                            const guchar *beg,
+                            const guchar **at,
+                            const guchar *end,
+                            GcrOpenpgpParseFlags flags,
+                            GPtrArray *records)
+{
+	gchar *keyid;
+	GcrRecord *record;
+	guint8 version;
+	guint32 timestamp;
+	guint16 ndays = 0;
+	guint8 algo;
+	guint16 bits;
+	gulong expiry;
+	const guchar *data;
+
+	/* Start of actual key data in packet */
+	data = *at;
+
+	/* First byte is version */
+	if (!read_byte (at, end, &version))
+		return FALSE;
+	if (version < 2 || version > 4)
+		return FALSE;
+
+	/* Next a 4 byte create date */
+	if (!read_uint32 (at, end, &timestamp))
+		return FALSE;
+	/* If version 2 or 3, validity days comes next */
+	if (version < 4) {
+		if (!read_uint16 (at, end, &ndays))
+			return FALSE;
+	}
+
+	/* Algorithm */
+	if (!read_byte (at, end, &algo))
+		return FALSE;
+
+	/* For version 2 and 3, only RSA, keyid is low 64-bits of modulus */
+	if (version < 4) {
+		if (!parse_v3_rsa_bits_and_keyid (at, end, &bits, &keyid))
+			return FALSE;
+
+	/* For version 4 */
+	} else {
+		if (!parse_v4_algo_bits (at, end, algo, &bits))
+			return FALSE;
+		keyid = hash_v4_keyid (data, *at);
+	}
+
+	record = _gcr_record_new (schema, n_columns, ':');
+	_gcr_record_set_uint (record, GCR_RECORD_KEY_BITS, bits);
+	_gcr_record_set_uint (record, GCR_RECORD_KEY_ALGO, algo);
+	_gcr_record_take_raw (record, GCR_RECORD_KEY_KEYID, keyid);
+	_gcr_record_set_ulong (record, GCR_RECORD_KEY_TIMESTAMP, timestamp);
+	if (schema != GCR_RECORD_SCHEMA_SEC && schema != GCR_RECORD_SCHEMA_SSB)
+		_gcr_record_set_raw (record, GCR_RECORD_PUB_CAPS, default_caps_for_algo (algo));
+
+	if (ndays > 0) {
+		expiry = (gulong)timestamp + ((gulong)ndays * 86400);
+		_gcr_record_set_ulong (record, GCR_RECORD_KEY_EXPIRY, expiry);
+	}
+
+	g_ptr_array_add (records, record);
+	return TRUE;
+}
+
+static gboolean
+parse_secret_key_or_subkey (GQuark schema,
+                            const guchar *beg,
+                            const guchar **at,
+                            const guchar *end,
+                            GcrOpenpgpParseFlags flags,
+                            GPtrArray *records)
+{
+	/*
+	 * Identical to a public key, with extra crap after it. The
+	 * extra crap is hard to parse and doesn't add anything to
+	 * the records, so just skip over it.
+	 *
+	 * Also don't print out trust, that doesn't make sense for
+	 * secret keys.
+	 */
+
+	if (!parse_public_key_or_subkey (schema, GCR_RECORD_SEC_MAX,
+	                                 beg, at, end, flags, records))
+		return FALSE;
+
+	*at = end;
+	return TRUE;
+}
+
+static gboolean
+parse_user_id (const guchar *beg,
+               const guchar **at,
+               const guchar *end,
+               GcrOpenpgpParseFlags flags,
+               GPtrArray *records)
+{
+	gchar *string;
+	GcrRecord *record;
+	gchar *fingerprint;
+
+	g_assert (at);
+	if (!*at || !end || *at > end)
+		return FALSE;
+
+	string = g_strndup ((gchar *)*at, end - *at);
+
+	fingerprint = hash_user_id_or_attribute (*at, end);
+	record = _gcr_record_new (GCR_RECORD_SCHEMA_UID, GCR_RECORD_UID_MAX, ':');
+	_gcr_record_take_raw (record, GCR_RECORD_UID_FINGERPRINT, fingerprint);
+	_gcr_record_set_string (record, GCR_RECORD_UID_NAME, string);
+	g_free (string);
+
+	g_ptr_array_add (records, record);
+
+	*at = end;
+	return TRUE;
+}
+
+static gboolean
+parse_user_attribute_packet (const guchar *beg,
+                             const guchar **at,
+                             const guchar *end,
+                             guchar subpkt_type,
+                             GPtrArray *records)
+{
+	GcrRecord *record;
+	gchar *fingerprint;
+
+	record = _gcr_record_new (GCR_RECORD_SCHEMA_XA1, GCR_RECORD_XA1_MAX, ':');
+	_gcr_record_set_uint (record, GCR_RECORD_XA1_LENGTH, end - *at);
+	_gcr_record_set_uint (record, GCR_RECORD_XA1_TYPE, subpkt_type);
+	fingerprint = hash_user_id_or_attribute (*at, end);
+	_gcr_record_take_raw (record, GCR_RECORD_XA1_FINGERPRINT, fingerprint);
+	_gcr_record_set_base64 (record, GCR_RECORD_XA1_DATA, *at, end - *at);
+
+	g_ptr_array_add (records, record);
+
+	*at = end;
+	return TRUE;
+}
+
+static gboolean
+parse_user_attribute (const guchar *beg,
+                      const guchar **at,
+                      const guchar *end,
+                      GcrOpenpgpParseFlags flags,
+                      GPtrArray *records)
+{
+	gsize subpkt_len;
+	guint count = 0;
+	const guchar *start;
+	const guchar *subpkt_beg;
+	guint8 subpkt_type;
+	gchar *fingerprint;
+	gchar *string;
+	GcrRecord *record;
+
+	start = *at;
+	while (*at != end) {
+		subpkt_beg = *at;
+
+		if (!read_new_length (at, end, &subpkt_len) ||
+		    !read_byte (at, end, &subpkt_type))
+			return FALSE;
+
+		count++;
+
+		if (flags & GCR_OPENPGP_PARSE_ATTRIBUTES) {
+			if (!parse_user_attribute_packet (subpkt_beg, at,
+			                                  *at + (subpkt_len - 1),
+			                                  subpkt_type, records))
+				return FALSE;
+
+		/* We already progressed one extra byte for the subpkt_type */
+		} else {
+			*at += (subpkt_len - 1);
+		}
+	}
+
+	fingerprint = hash_user_id_or_attribute (start, end);
+	string = g_strdup_printf ("%d %d", count, (guint)(*at - start));
+	record = _gcr_record_new (GCR_RECORD_SCHEMA_UAT, GCR_RECORD_UAT_MAX, ':');
+	_gcr_record_take_raw (record, GCR_RECORD_UAT_FINGERPRINT, fingerprint);
+	_gcr_record_take_raw (record, GCR_RECORD_UAT_COUNT_SIZE, string);
+
+	g_ptr_array_add (records, record);
+	return TRUE;
+}
+
+static gboolean
+skip_signature_mpis (const guchar **at,
+                     const guchar *end,
+                     guint8 algo)
+{
+	switch (algo) {
+
+	/* RSA signature value */
+	case OPENPGP_ALGO_RSA:
+		return read_mpi (at, end, NULL, NULL);
+
+	/* DSA values r and s */
+	case OPENPGP_ALGO_DSA:
+		return read_mpi (at, end, NULL, NULL) &&
+		       read_mpi (at, end, NULL, NULL);
+	default:
+		return FALSE;
+	}
+}
+
+static gboolean
+parse_v3_signature (const guchar **at,
+                    const guchar *end,
+                    GcrOpenpgpParseFlags flags,
+                    GPtrArray *records)
+{
+	guchar keyid[8];
+	guint8 sig_type;
+	guint8 sig_len;
+	guint32 sig_time;
+	guint8 key_algo;
+	guint8 hash_algo;
+	guint16 left_bits;
+	GcrRecord *record;
+	gchar *value;
+
+	if (!read_byte (at, end, &sig_len) || sig_len != 5)
+		return FALSE;
+
+	if (!read_byte (at, end, &sig_type) ||
+	    !read_uint32 (at, end, &sig_time) ||
+	    !read_bytes (at, end, keyid, 8) ||
+	    !read_byte (at, end, &key_algo) ||
+	    !read_byte (at, end, &hash_algo) ||
+	    !read_uint16 (at, end, &left_bits) ||
+	    !skip_signature_mpis (at, end, key_algo))
+		return FALSE;
+
+	if (flags & GCR_OPENPGP_PARSE_SIGNATURES) {
+		record = _gcr_record_new (GCR_RECORD_SCHEMA_SIG, GCR_RECORD_SIG_MAX, ':');
+		_gcr_record_set_uint (record, GCR_RECORD_SIG_ALGO, key_algo);
+		value = egg_hex_encode_full (keyid, sizeof (keyid), TRUE, 0, 0);
+		_gcr_record_take_raw (record, GCR_RECORD_SIG_KEYID, value);
+		_gcr_record_set_ulong (record, GCR_RECORD_SIG_TIMESTAMP, sig_time);
+		value = g_strdup_printf ("%02xx", (guint)sig_type);
+		_gcr_record_take_raw (record, GCR_RECORD_SIG_CLASS, value);
+		g_ptr_array_add (records, record);
+	}
+
+	return TRUE;
+}
+
+typedef struct {
+	gulong key_expiry;
+	gboolean exportable;
+	gboolean primary;
+	guint8 key_flags;
+	GcrRecord *revocation;
+} SigSubpacket;
+
+static gboolean
+parse_v4_signature_revocation (const guchar **at,
+                               const guchar *end,
+                               GcrRecord *revocation)
+{
+	guchar fingerprint[20];
+	gchar *value;
+	guint8 klass;
+	guint8 algo;
+
+	if (!read_byte (at, end, &klass) ||
+	    !read_byte (at, end, &algo) ||
+	    !read_bytes (at, end, fingerprint, 20))
+		return FALSE;
+
+	_gcr_record_set_uint (revocation, GCR_RECORD_RVK_ALGO, algo);
+	value = egg_hex_encode_full (fingerprint, 20, TRUE, 0, 0);
+	_gcr_record_take_raw (revocation, GCR_RECORD_RVK_FINGERPRINT, value);
+	value = g_strdup_printf ("%02X", (guint)klass);
+	_gcr_record_take_raw (revocation, GCR_RECORD_RVK_CLASS, value);
+
+	return TRUE;
+}
+
+static gboolean
+parse_v4_signature_subpacket (const guchar **at,
+                              const guchar *end,
+                              guint8 sub_type,
+                              GcrRecord *record,
+                              SigSubpacket *subpkt)
+{
+	guchar keyid[8];
+	guint32 when;
+	guint8 byte;
+	gboolean critical;
+	gchar *value;
+
+	critical = (sub_type & 0x80) ? TRUE : FALSE;
+	sub_type &= ~0xC0;
+
+	switch (sub_type) {
+	case OPENPGP_SIG_CREATION:
+		if (!read_uint32 (at, end, &when))
+			return FALSE;
+		_gcr_record_set_ulong (record, GCR_RECORD_SIG_TIMESTAMP, when);
+		return TRUE;
+	case OPENPGP_SIG_ISSUER:
+		if (!read_bytes (at, end, keyid, 8))
+			return FALSE;
+		value = egg_hex_encode_full (keyid, 8, TRUE, 0, 0);
+		_gcr_record_take_raw (record, GCR_RECORD_SIG_KEYID, value);
+		return TRUE;
+	case OPENPGP_SIG_KEY_EXPIRY:
+		if (!read_uint32 (at, end, &when))
+			return FALSE;
+		subpkt->key_expiry = when;
+		return TRUE;
+	case OPENPGP_SIG_EXPIRY:
+		if (!read_uint32 (at, end, &when))
+			return FALSE;
+		_gcr_record_set_ulong (record, GCR_RECORD_SIG_EXPIRY, when);
+		return TRUE;
+	case OPENPGP_SIG_EXPORTABLE:
+		if (!read_byte (at, end, &byte))
+			return FALSE;
+		if (byte != 0 && byte != 1)
+			return FALSE;
+		subpkt->exportable = (byte == 0 ? FALSE : TRUE);
+		return TRUE;
+
+	case OPENPGP_SIG_PRIMARY_USERID:
+		if (!read_byte (at, end, &byte))
+			return FALSE;
+		if (byte != 0 && byte != 1)
+			return FALSE;
+		subpkt->primary = byte;
+		return TRUE;
+
+	case OPENPGP_SIG_KEY_FLAGS:
+		if (!read_byte (at, end, &byte))
+			return FALSE;
+		*at = end; /* N octets of flags */
+		subpkt->key_flags = byte;
+		return TRUE;
+
+	case OPENPGP_SIG_SIGNER_USERID:
+		value = g_strndup ((gchar *)*at, end - *at);
+		_gcr_record_set_string (record, GCR_RECORD_SIG_NAME, value);
+		g_free (value);
+		return TRUE;
+
+	case OPENPGP_SIG_REVOCATION_KEY:
+		_gcr_record_free (subpkt->revocation);
+		subpkt->revocation = _gcr_record_new (GCR_RECORD_SCHEMA_RVK, GCR_RECORD_RVK_MAX, ':');
+		return parse_v4_signature_revocation (at, end, subpkt->revocation);
+
+	/* Ignored */
+	case OPENPGP_SIG_SYMMETRIC_ALGOS:
+	case OPENPGP_SIG_HASH_ALGOS:
+	case OPENPGP_SIG_COMPRESSION_ALGOS:
+	case OPENPGP_SIG_REVOCABLE:
+	case OPENPGP_SIG_TRUST:
+	case OPENPGP_SIG_REGULAR_EXPRESSION:
+	case OPENPGP_SIG_NOTATION_DATA:
+	case OPENPGP_SIG_KEYSERVER_PREFS:
+	case OPENPGP_SIG_PREFERRED_KEYSERVER:
+	case OPENPGP_SIG_POLICY_URI:
+	case OPENPGP_SIG_REVOCATION_REASON:
+	case OPENPGP_SIG_FEATURES:
+	case OPENPGP_SIG_TARGET:
+	case OPENPGP_SIG_EMBEDDED_SIGNATURE:
+		*at = end;
+		return TRUE;
+
+	/* Unrecognized */
+	default:
+		/* Critical, but not recognized */
+		if (critical)
+			return FALSE;
+		*at = end;
+		return TRUE;
+	}
+
+}
+
+static gboolean
+parse_v4_signature_subpackets (const guchar **at,
+                               const guchar *end,
+                               GcrRecord *record,
+                               SigSubpacket *subpkt)
+{
+	gsize length;
+	guint8 sub_type;
+	const guchar *stop;
+
+	while (*at != end) {
+		if (!read_new_length (at, end, &length) ||
+		    !read_byte (at, end, &sub_type) ||
+		    length == 0)
+			return FALSE;
+
+		/* The length includes the sub_type */
+		length--;
+		stop = *at + length;
+		if (stop > end)
+			return FALSE;
+
+		/* Actually parse the sub packets */
+		if (!parse_v4_signature_subpacket (at, stop, sub_type, record, subpkt))
+			return FALSE;
+		if (*at != stop)
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+static GcrRecord *
+uid_or_uat_find_for_self_signature (GPtrArray *records,
+                                    guint8 sig_type)
+{
+	GcrRecord *record;
+	GQuark schema;
+
+	if (records->len == 0)
+		return NULL;
+
+	switch (sig_type) {
+	/* Generic certification of a key or userid */
+	case 0x10: case 0x11: case 0x12: case 0x13:
+		record = records->pdata[records->len - 1];
+		schema = _gcr_record_get_schema (record);
+		if (schema == GCR_RECORD_SCHEMA_UID ||
+		    schema == GCR_RECORD_SCHEMA_UAT)
+			return record;
+		return NULL;
+
+	default:
+		return NULL;
+	}
+
+}
+
+static GcrRecord *
+key_or_sub_find_for_self_signature (GPtrArray *records,
+                                    guint8 sig_type,
+                                    const gchar *keyid)
+{
+	GcrRecord *record;
+	const gchar *check;
+	GQuark schema;
+	gint i;
+
+	if (records->len == 0)
+		return NULL;
+
+	switch (sig_type) {
+	/* Generic certification of a key or userid */
+	case 0x10: case 0x11: case 0x12: case 0x13:
+		for (i = records->len - 1; i >= 0; i--) {
+			record = records->pdata[i];
+			schema = _gcr_record_get_schema (record);
+			if (schema == GCR_RECORD_SCHEMA_PUB || schema == GCR_RECORD_SCHEMA_SEC) {
+				check = _gcr_record_get_raw (record, GCR_RECORD_KEY_KEYID);
+				return (check != NULL && g_str_equal (check, keyid)) ? record : NULL;
+			}
+		}
+		return NULL;
+
+	/* (Primary) Subkey Binding Signature */
+	case 0x18: case 0x19:
+		record = records->pdata[records->len - 1];
+		schema = _gcr_record_get_schema (record);
+		if (schema == GCR_RECORD_SCHEMA_SUB)
+			return record;
+		return NULL;
+
+	default:
+		return NULL;
+	}
+}
+
+static void
+pub_or_sub_set_key_caps (GcrRecord *record,
+                         guint8 key_flags)
+{
+	GString *string;
+	GQuark schema;
+
+	schema = _gcr_record_get_schema (record);
+	if (schema == GCR_RECORD_SCHEMA_SEC || schema == GCR_RECORD_SCHEMA_SSB)
+		return;
+
+	string = g_string_sized_new (8);
+	if (key_flags & 0x02)
+		g_string_append_c (string, 's');
+	if (key_flags & 0x01)
+		g_string_append_c (string, 'c');
+	if (key_flags & 0x04 || key_flags & 0x08)
+		g_string_append_c (string, 'e');
+	if (key_flags & 0x20)
+		g_string_append_c (string, 'a');
+
+	_gcr_record_take_raw (record, GCR_RECORD_PUB_CAPS,
+	                      g_string_free (string, FALSE));
+}
+
+static gboolean
+parse_v4_signature (const guchar **at,
+                    const guchar *end,
+                    GcrOpenpgpParseFlags flags,
+                    GPtrArray *records)
+{
+	guint8 sig_type;
+	guint8 key_algo;
+	guint8 hash_algo;
+	guint16 hashed_len;
+	guint16 unhashed_len;
+	guint16 left_bits;
+	GcrRecord *record;
+	GcrRecord *key, *uid;
+	const gchar *keyid;
+	gchar *value;
+	const guchar *stop;
+	gulong timestamp;
+
+	/* Information to transfer back onto the key record */
+	SigSubpacket subpkt = { 0, };
+	subpkt.exportable = 1;
+
+	if (!read_byte (at, end, &sig_type) ||
+	    !read_byte (at, end, &key_algo) ||
+	    !read_byte (at, end, &hash_algo) ||
+	    !read_uint16 (at, end, &hashed_len))
+		return FALSE;
+
+	/* Hashed subpackets which we use */
+	record = _gcr_record_new (GCR_RECORD_SCHEMA_SIG, GCR_RECORD_SIG_MAX, ':');
+	stop = *at + hashed_len;
+	if (stop > end ||
+	    !parse_v4_signature_subpackets (at, stop, record, &subpkt)) {
+		_gcr_record_free (record);
+		_gcr_record_free (subpkt.revocation);
+		return FALSE;
+	}
+
+	/* Includes unhashed subpackets, which we skip over */
+	if (!read_uint16 (at, end, &unhashed_len)) {
+		_gcr_record_free (record);
+		_gcr_record_free (subpkt.revocation);
+		return FALSE;
+	}
+
+	stop = *at + unhashed_len;
+	if (stop > end ||
+	    !parse_v4_signature_subpackets (at, stop, record, &subpkt) ||
+	    !read_uint16 (at, end, &left_bits) ||
+	    !skip_signature_mpis (at, end, key_algo)) {
+		_gcr_record_free (record);
+		_gcr_record_free (subpkt.revocation);
+		return FALSE;
+	}
+
+	if (subpkt.revocation) {
+		g_ptr_array_add (records, subpkt.revocation);
+		subpkt.revocation = NULL;
+	}
+
+	/* Fill in information on previous key or subkey */
+	keyid = _gcr_record_get_raw (record, GCR_RECORD_SIG_KEYID);
+	key = key_or_sub_find_for_self_signature (records, sig_type, keyid);
+	if (key != NULL) {
+		if (subpkt.key_expiry != 0) {
+			if (_gcr_record_get_ulong (key, GCR_RECORD_KEY_TIMESTAMP, &timestamp))
+				_gcr_record_set_ulong (key, GCR_RECORD_KEY_EXPIRY, timestamp + subpkt.key_expiry);
+		}
+		if (subpkt.key_flags != 0)
+			pub_or_sub_set_key_caps (key, subpkt.key_flags);
+	}
+
+	if (key && _gcr_record_get_schema (key) == GCR_RECORD_SCHEMA_PUB) {
+		uid = uid_or_uat_find_for_self_signature (records, sig_type);
+		if (uid != NULL) {
+			if (_gcr_record_get_ulong (record, GCR_RECORD_SIG_TIMESTAMP, &timestamp))
+				_gcr_record_set_ulong (uid, GCR_RECORD_UID_TIMESTAMP, timestamp);
+		}
+	}
+
+	if (flags & GCR_OPENPGP_PARSE_SIGNATURES) {
+		_gcr_record_set_uint (record, GCR_RECORD_SIG_ALGO, key_algo);
+		value = g_strdup_printf ("%02x%s", (guint)sig_type,
+		                         subpkt.exportable ? "x" : "l");
+		_gcr_record_take_raw (record, GCR_RECORD_SIG_CLASS, value);
+		g_ptr_array_add (records, record);
+	} else {
+		_gcr_record_free (record);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+parse_signature (const guchar *beg,
+                 const guchar **at,
+                 const guchar *end,
+                 GcrOpenpgpParseFlags flags,
+                 GPtrArray *records)
+{
+	guint8 version;
+
+	if (!read_byte (at, end, &version))
+		return FALSE;
+
+	if (version == 3)
+		return parse_v3_signature (at, end, flags, records);
+	else if (version == 4)
+		return parse_v4_signature (at, end, flags, records);
+	else
+		return FALSE;
+}
+
+static GcrDataFormat
+parse_openpgp_packet (const guchar *beg,
+                      const guchar *at,
+                      const guchar *end,
+                      guint8 pkt_type,
+                      GcrOpenpgpParseFlags flags,
+                      GPtrArray *records)
+{
+	gboolean ret;
+
+	switch (pkt_type) {
+	case OPENPGP_PKT_PUBLIC_KEY:
+		ret = parse_public_key_or_subkey (GCR_RECORD_SCHEMA_PUB, GCR_RECORD_PUB_MAX,
+		                                  beg, &at, end, flags, records);
+		break;
+	case OPENPGP_PKT_PUBLIC_SUBKEY:
+		ret = parse_public_key_or_subkey (GCR_RECORD_SCHEMA_SUB, GCR_RECORD_PUB_MAX,
+		                                  beg, &at, end, flags, records);
+		break;
+	case OPENPGP_PKT_USER_ID:
+		ret = parse_user_id (beg, &at, end, flags, records);
+		break;
+	case OPENPGP_PKT_ATTRIBUTE:
+		ret = parse_user_attribute (beg, &at, end, flags, records);
+		break;
+	case OPENPGP_PKT_SIGNATURE:
+		ret = parse_signature (beg, &at, end, flags, records);
+		break;
+	case OPENPGP_PKT_SECRET_KEY:
+		ret = parse_secret_key_or_subkey (GCR_RECORD_SCHEMA_SEC,
+		                                  beg, &at, end, flags, records);
+		break;
+	case OPENPGP_PKT_SECRET_SUBKEY:
+		ret = parse_secret_key_or_subkey (GCR_RECORD_SCHEMA_SSB,
+		                                  beg, &at, end, flags, records);
+		break;
+
+	/* Stuff we don't want to be meddling with right now */
+	case OPENPGP_PKT_RING_TRUST:
+		return GCR_SUCCESS;
+
+	/* Ignore packets we don't understand */
+	default:
+		return GCR_SUCCESS;
+	}
+
+	/* Key packet had extra data */
+	if (ret == TRUE && at != end)
+		ret = FALSE;
+
+	return ret ? GCR_SUCCESS : GCR_ERROR_FAILURE;
+}
+
+static void
+append_key_capabilities (GString *string,
+                         const gchar *caps)
+{
+	guint i;
+	gchar cap;
+
+	for (i = 0; caps[i] != 0; i++) {
+		cap = g_ascii_toupper (caps[i]);
+		if (!strchr (string->str, cap))
+			g_string_append_c (string, cap);
+	}
+}
+
+static void
+normalize_capabilities (GPtrArray *records)
+{
+	GString *string;
+	GQuark schema;
+	const gchar *caps;
+	guint i;
+
+	/* Gather the capabilities of all subkeys into the primary key */
+	string = g_string_new (_gcr_record_get_raw (records->pdata[0], GCR_RECORD_PUB_CAPS));
+	for (i = 0; i < records->len; i++) {
+		schema = _gcr_record_get_schema (records->pdata[i]);
+		if (schema == GCR_RECORD_SCHEMA_PUB || schema == GCR_RECORD_SCHEMA_SUB) {
+			caps = _gcr_record_get_raw (records->pdata[i], GCR_RECORD_PUB_CAPS);
+			append_key_capabilities (string, caps);
+		}
+	}
+	_gcr_record_take_raw (records->pdata[0], GCR_RECORD_PUB_CAPS,
+	                      g_string_free (string, FALSE));
+}
+
+static gboolean
+check_key_expiry (GcrRecord *record)
+{
+	gulong expiry;
+	time_t current;
+
+	if (_gcr_record_get_ulong (record, GCR_RECORD_KEY_EXPIRY, &expiry)) {
+		if (expiry == 0)
+			return FALSE;
+		current = time (NULL);
+		if (current > expiry)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void
+normalize_key_records (GPtrArray *records)
+{
+	GQuark schema;
+	guchar trust = 0;
+	const gchar *prev;
+	gboolean force = FALSE;
+	guint i;
+
+	if (records->len == 0)
+		return;
+
+	schema = _gcr_record_get_schema (records->pdata[0]);
+	if (schema == GCR_RECORD_SCHEMA_PUB) {
+
+		if (check_key_expiry (records->pdata[0])) {
+			trust = 'e';
+			force = TRUE;
+
+		/* Mark public keys as unknown trust */
+		} else {
+			normalize_capabilities (records);
+			trust = 'o';
+			force = FALSE;
+		}
+
+		/* Ownertrust unknown, new to system */
+		_gcr_record_set_char (records->pdata[0], GCR_RECORD_KEY_OWNERTRUST, 'o');
+
+	} else if (schema == GCR_RECORD_SCHEMA_SEC) {
+
+		/* Trust doesn't make sense for secret keys */
+		trust = 0;
+		force = FALSE;
+	}
+
+
+	/* Setup default trust if necessary */
+	if (trust != 0) {
+		for (i = 0; i < records->len; i++) {
+			if (!force) {
+				prev = _gcr_record_get_raw (records->pdata[i], GCR_RECORD_TRUST);
+				if (prev != NULL && prev[0])
+					continue;
+			}
+			schema = _gcr_record_get_schema (records->pdata[i]);
+			if (schema != GCR_RECORD_SCHEMA_SIG)
+				_gcr_record_set_char (records->pdata[i], GCR_RECORD_TRUST, trust);
+		}
+	}
+}
+
+typedef struct {
+	GcrOpenpgpCallback callback;
+	gpointer user_data;
+	guint count;
+	GPtrArray *records;
+} openpgp_parse_closure;
+
+static void
+openpgp_parse_free (gpointer data)
+{
+	openpgp_parse_closure *closure = data;
+	g_ptr_array_unref (closure->records);
+	g_free (closure);
+}
+
+static void
+maybe_emit_openpgp_block (openpgp_parse_closure *closure,
+                          const guchar *block,
+                          const guchar *end)
+{
+	gsize length;
+	GPtrArray *records;
+
+	if (block == NULL || block == end)
+		return;
+
+	g_assert (end != NULL);
+	g_assert (end > block);
+
+	length = end - block;
+	closure->count++;
+
+	records = closure->records;
+	closure->records = g_ptr_array_new_with_free_func (_gcr_record_free);
+
+	if (closure->callback)
+		(closure->callback) (records, block, length, closure->user_data);
+
+	g_ptr_array_unref (records);
+}
+
 guint
 _gcr_openpgp_parse (gconstpointer data,
                     gsize n_data,
+                    GcrOpenpgpParseFlags flags,
                     GcrOpenpgpCallback callback,
                     gpointer user_data)
 {
+	openpgp_parse_closure *closure;
 	const guchar *at;
 	const guchar *beg;
 	const guchar *end;
-	GPtrArray *records;
+	const guchar *block;
+	guint8 pkt_type;
 	GcrDataError res;
 	gsize length;
-	guint num_packets = 0;
+	gboolean new_key;
+	guint ret;
 
 	g_return_val_if_fail (data != NULL, 0);
 
+	/* For libgcrypt */
+	_gcr_initialize_library ();
+
 	at = data;
 	end = at + n_data;
+	block = NULL;
+
+	closure = g_new0 (openpgp_parse_closure, 1);
+	closure->callback = callback;
+	closure->user_data = user_data;
+	closure->records = g_ptr_array_new_with_free_func (_gcr_record_free);
 
 	while (at != NULL && at != end) {
 		beg = at;
-		records = g_ptr_array_new_with_free_func (_gcr_record_free);
-		res = read_openpgp_packet (&at, end, records, &length);
-		if (res == GCR_SUCCESS && callback != NULL)
-			(callback) (records, beg, (at - beg) + length, user_data);
+		res = read_openpgp_packet (&at, end, &pkt_type, &length);
 
-		g_ptr_array_unref (records);
+		if (res == GCR_SUCCESS) {
+			new_key = (pkt_type == OPENPGP_PKT_PUBLIC_KEY ||
+			           pkt_type == OPENPGP_PKT_SECRET_KEY);
+			if (flags & GCR_OPENPGP_PARSE_KEYS && new_key)
+				normalize_key_records (closure->records);
+			/* Start of a new set of packets, per key */
+			if (!(flags & GCR_OPENPGP_PARSE_KEYS) || new_key) {
+				maybe_emit_openpgp_block (closure, block, beg);
+				block = beg;
+			}
+			if (!(flags & GCR_OPENPGP_PARSE_NO_RECORDS))
+				parse_openpgp_packet (beg, at, at + length, pkt_type,
+				                      flags, closure->records);
+		}
 
-		if (res != GCR_SUCCESS)
+		if (res != GCR_SUCCESS) {
+			if (block != NULL && block != beg)
+				maybe_emit_openpgp_block (closure, block, beg);
+			block = NULL;
 			break;
+		}
 
 		at += length;
-		num_packets++;
 	}
 
-	return num_packets;
+	if (flags & GCR_OPENPGP_PARSE_KEYS)
+		normalize_key_records (closure->records);
+	maybe_emit_openpgp_block (closure, block, at);
+	ret = closure->count;
+	openpgp_parse_free (closure);
+	return ret;
 }
diff --git a/gcr/gcr-openpgp.h b/gcr/gcr-openpgp.h
index 3575921..5ba4267 100644
--- a/gcr/gcr-openpgp.h
+++ b/gcr/gcr-openpgp.h
@@ -32,6 +32,14 @@
 
 #include <gck/gck.h>
 
+typedef enum {
+	GCR_OPENPGP_PARSE_NONE = 0,
+	GCR_OPENPGP_PARSE_KEYS = 1 << 1,
+	GCR_OPENPGP_PARSE_NO_RECORDS = 1 << 2,
+	GCR_OPENPGP_PARSE_SIGNATURES = 1 << 3,
+	GCR_OPENPGP_PARSE_ATTRIBUTES = 1 << 4,
+} GcrOpenpgpParseFlags;
+
 G_BEGIN_DECLS
 
 typedef void             (*GcrOpenpgpCallback)             (GPtrArray *records,
@@ -41,6 +49,7 @@ typedef void             (*GcrOpenpgpCallback)             (GPtrArray *records,
 
 guint                    _gcr_openpgp_parse                (gconstpointer data,
                                                             gsize n_data,
+                                                            GcrOpenpgpParseFlags flags,
                                                             GcrOpenpgpCallback callback,
                                                             gpointer user_data);
 
diff --git a/gcr/gcr-parser.c b/gcr/gcr-parser.c
index d18034b..96aedfe 100644
--- a/gcr/gcr-parser.c
+++ b/gcr/gcr-parser.c
@@ -1424,7 +1424,9 @@ parse_openpgp_packets (GcrParser *self,
 {
 	gint num_parsed;
 
-	num_parsed = _gcr_openpgp_parse (data, n_data, on_openpgp_packet, self);
+	num_parsed = _gcr_openpgp_parse (data, n_data,
+	                                 GCR_OPENPGP_PARSE_KEYS,
+	                                 on_openpgp_packet, self);
 
 	if (num_parsed == 0)
 		return GCR_ERROR_UNRECOGNIZED;
diff --git a/gcr/gcr-record.c b/gcr/gcr-record.c
index 0ff6503..612dc86 100644
--- a/gcr/gcr-record.c
+++ b/gcr/gcr-record.c
@@ -27,55 +27,185 @@
 #define DEBUG_FLAG GCR_DEBUG_PARSE
 #include "gcr-debug.h"
 
+#include "egg/egg-timegm.h"
+
 #include <stdlib.h>
 #include <string.h>
+#include <time.h>
 
 #define MAX_COLUMNS 32
 
+typedef struct {
+	gpointer next;
+	gsize n_value;
+	gchar value[1];
+	/* Hangs off the end */
+} GcrRecordBlock;
+
 struct _GcrRecord {
-	gchar *data;
-	gsize n_data;
-	gchar *columns[MAX_COLUMNS];
+	GcrRecordBlock *block;
+	const gchar *columns[MAX_COLUMNS];
 	guint n_columns;
+	gchar delimiter;
 };
 
 G_DEFINE_BOXED_TYPE (GcrRecord, _gcr_record, _gcr_record_copy, _gcr_record_free);
 
-GcrRecord*
-_gcr_record_copy (GcrRecord *record)
+static GcrRecordBlock *
+record_block_new (const gchar *value,
+                  gsize length)
+{
+	GcrRecordBlock *block;
+
+	block = g_malloc (sizeof (GcrRecordBlock) + length);
+	block->next = NULL;
+	block->n_value = length;
+
+	if (value != NULL) {
+		memcpy (block->value, value, length);
+		block->value[length] = 0;
+	} else {
+		block->value[0] = 0;
+	}
+
+	return block;
+}
+
+static GcrRecordBlock *
+record_block_take (gchar *value,
+                   gsize length)
+{
+	GcrRecordBlock *block;
+
+	g_assert (value);
+
+	block = g_realloc (value, sizeof (GcrRecordBlock) + length);
+	memmove (((gchar*)block) + G_STRUCT_OFFSET (GcrRecordBlock, value),
+	         block, length);
+	block->next = NULL;
+	block->n_value = length;
+	block->value[length] = 0;
+
+	return block;
+}
+
+static GcrRecord *
+record_flatten (GcrRecord *record)
 {
 	GcrRecord *result;
-	gchar *column;
+	GcrRecordBlock *block;
+	gsize total;
+	gsize at;
+	gsize len;
 	guint i;
 
+	/* Calculate the length of what we need */
+	total = 0;
+	for (i = 0; i < record->n_columns; i++)
+		total += strlen (record->columns[i]) + 1;
+
+	/* Allocate a new GcrRecordData which will hold all that */
 	result = g_slice_new0 (GcrRecord);
-	result->data = g_memdup (record->data, record->n_data);
-	result->n_data = record->n_data;
+	result->block = block = record_block_new (NULL, total);
 
+	at = 0;
 	for (i = 0; i < record->n_columns; i++) {
-		column = (gchar*)record->columns[i];
-		g_assert (column >= record->data);
-		result->columns[i] = result->data + (column - record->data);
+		len = strlen (record->columns[i]);
+		result->columns[i] = block->value + at;
+		memcpy ((gchar *)result->columns[i], record->columns[i], len + 1);
+		at += len + 1;
 	}
+
 	result->n_columns = record->n_columns;
+	result->delimiter = record->delimiter;
+	g_assert (at == total);
+
 	return result;
 }
 
-static GcrRecord*
-take_and_parse_internal (gchar *line, gchar delimiter, gboolean allow_empty)
+static void
+print_record_to_string (GcrRecord *record,
+                        GString *string)
+{
+	guint i;
+
+	for (i = 0; i < record->n_columns; i++) {
+		g_string_append (string, record->columns[i]);
+		g_string_append_c (string, record->delimiter);
+	}
+}
+
+gchar *
+_gcr_record_format (GcrRecord *record)
+{
+	GString *string;
+
+	g_return_val_if_fail (record, NULL);
+
+	string = g_string_new ("");
+	print_record_to_string (record, string);
+	return g_string_free (string, FALSE);
+}
+
+gchar *
+_gcr_records_format (GPtrArray *records)
+{
+	GString *string;
+	guint i;
+
+	g_return_val_if_fail (records, NULL);
+
+	string = g_string_new ("");
+	for (i = 0; i < records->len; i++) {
+		print_record_to_string (records->pdata[i], string);
+		g_string_append_c (string, '\n');
+	}
+	return g_string_free (string, FALSE);
+}
+
+GcrRecord *
+_gcr_record_new (GQuark schema,
+                 guint n_columns,
+                 gchar delimiter)
+{
+	GcrRecord *result;
+	guint i;
+
+	result = g_slice_new0 (GcrRecord);
+	result->block = NULL;
+	result->delimiter = delimiter;
+
+	for (i = 0; i < n_columns; i++)
+		result->columns[i] = "";
+	result->columns[0] = g_quark_to_string (schema);
+	result->n_columns = n_columns;
+
+	return result;
+}
+
+GcrRecord *
+_gcr_record_copy (GcrRecord *record)
+{
+	return record_flatten (record);
+}
+
+static GcrRecord *
+take_and_parse_internal (GcrRecordBlock *block,
+                         gchar delimiter,
+                         gboolean allow_empty)
 {
 	GcrRecord *result;
 	gchar *at, *beg, *end;
 
-	g_assert (line);
+	g_assert (block);
 
 	result = g_slice_new0 (GcrRecord);
-	result->data = line;
-	result->n_data = strlen (line) + 1;
+	result->block = block;
+	result->delimiter = delimiter;
 
-	_gcr_debug ("parsing line %s", line);
+	_gcr_debug ("parsing line %s", block->value);
 
-	at = result->data;
+	at = block->value;
 	for (;;) {
 		if (result->n_columns >= MAX_COLUMNS) {
 			_gcr_debug ("too many record (%d) in gnupg line", MAX_COLUMNS);
@@ -88,7 +218,7 @@ take_and_parse_internal (gchar *line, gchar delimiter, gboolean allow_empty)
 
 		at = strchr (beg, delimiter);
 		if (at == NULL) {
-			end = (result->data + result->n_data) - 1;
+			end = (block->value + block->n_value) - 1;
 		} else {
 			at[0] = '\0';
 			end = at;
@@ -111,19 +241,7 @@ _gcr_record_parse_colons (const gchar *line, gssize n_line)
 	g_return_val_if_fail (line, NULL);
 	if (n_line < 0)
 		n_line = strlen (line);
-	return take_and_parse_internal (g_strndup (line, n_line), ':', TRUE);
-}
-
-GcrRecord*
-_gcr_record_take_colons (gchar *line)
-{
-	GcrRecord *record;
-
-	g_return_val_if_fail (line, NULL);
-	record = take_and_parse_internal (line, ':', TRUE);
-	if (record == NULL)
-		g_warning ("internal parsing of colons format failed");
-	return record;
+	return take_and_parse_internal (record_block_new (line, n_line), ':', TRUE);
 }
 
 GcrRecord*
@@ -132,10 +250,9 @@ _gcr_record_parse_spaces (const gchar *line, gssize n_line)
 	g_return_val_if_fail (line, NULL);
 	if (n_line < 0)
 		n_line = strlen (line);
-	return take_and_parse_internal (g_strndup (line, n_line), ' ', FALSE);
+	return take_and_parse_internal (record_block_new (line, n_line), ' ', FALSE);
 }
 
-
 GcrRecord*
 _gcr_record_find (GPtrArray *records, GQuark schema)
 {
@@ -159,32 +276,259 @@ _gcr_record_get_count (GcrRecord *record)
 	return record->n_columns;
 }
 
+static void
+record_take_column (GcrRecord *record,
+                    guint column,
+                    GcrRecordBlock *block)
+{
+	g_assert (block->next == NULL);
+	block->next = record->block;
+	record->block = block;
+
+	g_assert (column < record->n_columns);
+	record->columns[column] = block->value;
+}
+
+static const char HEXC_LOWER[] = "0123456789abcdef";
+
+/* Will return NULL if unescaping failed or not needed */
+static gchar *
+c_colons_unescape (const gchar *source,
+                   gsize *length)
+{
+	const gchar *p = source, *octal, *hex;
+	gchar *dest = NULL;
+	gchar *q = dest;
+	gchar *pos;
+
+	while (*p) {
+		if (*p == '\\') {
+			if (dest == NULL) {
+				dest = 	g_malloc (strlen (source) + 1);
+				memcpy (dest, source, (p - source));
+				q = dest + (p - source);
+			}
+
+			p++;
+			switch (*p) {
+			case '\0': /* invalid trailing backslash */
+				g_free (dest);
+				return NULL;
+			case '0':  case '1':  case '2':  case '3':  case '4':
+			case '5':  case '6':  case '7':
+				*q = 0;
+				octal = p;
+				while ((p < octal + 3) && (*p >= '0') && (*p <= '7')) {
+					*q = (*q * 8) + (*p - '0');
+					p++;
+				}
+				q++;
+				p--;
+				break;
+			case 'x':
+				*q = 0;
+				hex = p;
+				while (p < hex + 2) {
+					pos = strchr (HEXC_LOWER, g_ascii_tolower (*p));
+					if (pos == 0) { /* invalid bad hex character */
+						g_free (dest);
+						return NULL;
+					}
+					*q = (*q * 16) + (pos - HEXC_LOWER);
+					p++;
+				}
+				q++;
+				p--;
+				break;
+			case 'b':
+				*q++ = '\b';
+				break;
+			case 'f':
+				*q++ = '\f';
+				break;
+			case 'n':
+				*q++ = '\n';
+				break;
+			case 'r':
+				*q++ = '\r';
+				break;
+			case 't':
+				*q++ = '\t';
+				break;
+			default:            /* Also handles \" and \\ */
+				*q++ = *p;
+				break;
+			}
+		} else if (q != NULL) {
+			*q++ = *p;
+		}
+		p++;
+	}
+
+	if (q != NULL) {
+		*q = 0;
+		if (length)
+			*length = q - dest;
+	}
+
+	return dest;
+}
+
+/* Will return NULL if no escaping needed */
+static gchar *
+c_colons_escape (const gchar *source,
+                 const gchar extra,
+                 gsize *length)
+{
+	const guchar *p;
+	gchar *dest = NULL;
+	gchar *q = NULL;
+	gchar escape;
+	gsize off;
+
+	g_return_val_if_fail (source != NULL, NULL);
+
+	p = (guchar *) source;
+
+	while (*p) {
+		escape = 0;
+		switch (*p) {
+		case '\b':
+			escape = 'b';
+			break;
+		case '\f':
+			escape = 'f';
+			break;
+		case '\n':
+			escape = 'n';
+			break;
+		case '\r':
+			escape = 'r';
+			break;
+		case '\t':
+			escape = 't';
+			break;
+		case '\\':
+			escape = '\\';
+			break;
+		case '"':
+			escape = '"';
+			break;
+		}
+
+		if (escape != 0 || *p < ' ' || *p >= 0x127 || *p == extra) {
+			if (dest == NULL) {
+				/* Each source byte needs maximally four destination chars (\xff) */
+				dest = g_malloc (strlen (source) * 4 + 1);
+				off = (gchar *)p - source;
+				memcpy (dest, source, off);
+				q = dest + off;
+			}
+
+			if (escape) {
+				*q++ = '\\';
+				*q++ = escape;
+			} else {
+				*q++ = '\\';
+				*q++ = 'x';
+				*q++ = HEXC_LOWER[*p >> 4 & 0xf];
+				*q++ = HEXC_LOWER[*p & 0xf];
+			}
+		} else if (q != NULL) {
+			*q++ = *p;
+		}
+		p++;
+	}
+
+	if (q != NULL) {
+		*q = 0;
+		if (length)
+			*length = q - dest;
+	}
+
+	return dest;
+}
+
 gchar*
 _gcr_record_get_string (GcrRecord *record, guint column)
 {
 	const gchar *value;
-	gchar *text;
-	gchar *converted;
+	gchar *text = NULL;
 
 	g_return_val_if_fail (record, NULL);
 
 	value = _gcr_record_get_raw (record, column);
 	if (!value)
 		return NULL;
-	text = g_strcompress (value);
-	if (g_utf8_validate (text, -1, NULL))
-		return text;
+
+	text = c_colons_unescape (value, NULL);
+	if (text != NULL)
+		value = text;
 
 	/* If it's not UTF-8, we guess that it's latin1 */
-	converted = g_convert (text, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL);
-	g_free (text);
+	if (!g_utf8_validate (value, -1, NULL)) {
+		gchar *conv = g_convert (value, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL);
+		g_free (text);
+		value = text = conv;
+	}
 
 	/*
 	 * latin1 to utf-8 conversion can't really fail, just produce
 	 * garbage... so there's no need to check here.
 	 */
 
-	return converted;
+	return (text == value) ? text : g_strdup (value);
+}
+
+void
+_gcr_record_set_string (GcrRecord *record,
+                        guint column,
+                        const gchar *string)
+{
+	GcrRecordBlock *block;
+	gchar *escaped;
+
+	g_return_if_fail (record != NULL);
+	g_return_if_fail (string != NULL);
+	g_return_if_fail (column < record->n_columns);
+
+	escaped = c_colons_escape (string, record->delimiter, NULL);
+	if (escaped != NULL)
+		block = record_block_take (escaped, strlen (escaped));
+	else
+		block = record_block_new (string, strlen (string));
+
+	record_take_column (record, column, block);
+}
+
+gchar
+_gcr_record_get_char (GcrRecord *record,
+                      guint column)
+{
+	const gchar *value;
+
+	g_return_val_if_fail (record, 0);
+
+	value = _gcr_record_get_raw (record, column);
+	if (!value)
+		return 0;
+
+	if (value[0] != 0 && value[1] == 0)
+		return value[0];
+
+	return 0;
+}
+
+void
+_gcr_record_set_char (GcrRecord *record,
+                      guint column,
+                      gchar value)
+{
+	g_return_if_fail (record != NULL);
+	g_return_if_fail (column < record->n_columns);
+	g_return_if_fail (value != 0);
+
+	record_take_column (record, column, record_block_new (&value, 1));
 }
 
 gboolean
@@ -216,6 +560,124 @@ _gcr_record_get_uint (GcrRecord *record, guint column, guint *value)
 	return TRUE;
 }
 
+void
+_gcr_record_set_uint (GcrRecord *record,
+                      guint column,
+                      guint value)
+{
+	gchar *escaped;
+
+	g_return_if_fail (record != NULL);
+	g_return_if_fail (column < record->n_columns);
+
+	escaped = g_strdup_printf ("%u", value);
+	record_take_column (record, column,
+	                    record_block_take (escaped, strlen (escaped)));
+}
+
+gboolean
+_gcr_record_get_ulong (GcrRecord *record,
+                       guint column,
+                       gulong *value)
+{
+	const gchar *raw;
+	gint64 result;
+	gchar *end = NULL;
+
+	g_return_val_if_fail (record, FALSE);
+
+	raw = _gcr_record_get_raw (record, column);
+	if (raw == NULL)
+		return FALSE;
+
+	result = g_ascii_strtoull (raw, &end, 10);
+	if (!end || end[0]) {
+		_gcr_debug ("invalid unsigned long value: %s", raw);
+		return FALSE;
+	}
+
+	if (result < 0 || result > G_MAXULONG) {
+		_gcr_debug ("unsigned long value is out of range: %s", raw);
+		return FALSE;
+	}
+
+	if (value)
+		*value = (guint)result;
+	return TRUE;
+
+}
+
+void
+_gcr_record_set_ulong (GcrRecord *record,
+                       guint column,
+                       gulong value)
+{
+	gchar *escaped;
+
+	g_return_if_fail (record != NULL);
+	g_return_if_fail (column < record->n_columns);
+
+	escaped = g_strdup_printf ("%lu", value);
+	record_take_column (record, column,
+	                    record_block_take (escaped, strlen (escaped)));
+}
+
+gboolean
+_gcr_record_get_date (GcrRecord *record,
+                      guint column,
+                      gulong *value)
+{
+	const gchar *raw;
+	gulong result;
+	gchar *end = NULL;
+	struct tm tm;
+
+	g_return_val_if_fail (record, FALSE);
+
+	raw = _gcr_record_get_raw (record, column);
+	if (raw == NULL)
+		return FALSE;
+
+	/* Try to parse as a number */
+	result = strtoul (raw, &end, 10);
+	if (!end || end[0]) {
+		/* Try to parse as a date */
+		memset (&tm, 0, sizeof (tm));
+		end = strptime (raw, "%Y-%m-%d", &tm);
+		if (!end || end[0]) {
+			_gcr_debug ("invalid date value: %s", raw);
+			return FALSE;
+		}
+		result = timegm (&tm);
+	}
+
+	if (value)
+		*value = result;
+	return TRUE;
+}
+
+void
+_gcr_record_set_date (GcrRecord *record,
+                      guint column,
+                      gulong value)
+{
+	GcrRecordBlock *block;
+	time_t time;
+	struct tm tm;
+	gsize len;
+
+	g_return_if_fail (record != NULL);
+	g_return_if_fail (column < record->n_columns);
+
+	time = value;
+	gmtime_r (&time, &tm);
+	block = record_block_new (NULL, 20);
+	len = strftime (block->value, 20, "%Y-%m-%d", &tm);
+	g_assert (len < 20);
+
+	record_take_column (record, column, block);
+}
+
 /**
  * _gcr_record_get_base64:
  * @record: The record
@@ -240,6 +702,36 @@ _gcr_record_get_base64 (GcrRecord *record, guint column, gsize *n_data)
 	return g_base64_decode (raw, n_data);
 }
 
+void
+_gcr_record_set_base64 (GcrRecord *record,
+                        guint column,
+                        gconstpointer data,
+                        gsize n_data)
+{
+	GcrRecordBlock *block;
+	gint state, save;
+	gsize estimate;
+	gsize length;
+
+	g_return_if_fail (record != NULL);
+	g_return_if_fail (column < record->n_columns);
+
+	estimate = n_data * 4 / 3 + n_data * 4 / (3 * 65) + 7;
+	block = record_block_new (NULL, estimate);
+
+	/* The actual base64 data, without line breaks */
+	state = save = 0;
+	length = g_base64_encode_step ((guchar *)data, n_data, FALSE,
+	                               block->value, &state, &save);
+	length += g_base64_encode_close (TRUE, block->value + length,
+	                                 &state, &save);
+	g_strchomp (block->value);
+	g_assert (length < estimate);
+	block->value[length] = 0;
+
+	record_take_column (record, column, block);
+}
+
 const gchar*
 _gcr_record_get_raw (GcrRecord *record, guint column)
 {
@@ -255,12 +747,45 @@ _gcr_record_get_raw (GcrRecord *record, guint column)
 }
 
 void
+_gcr_record_set_raw (GcrRecord *record,
+                     guint column,
+                     const gchar *value)
+{
+	g_return_if_fail (record != NULL);
+	g_return_if_fail (value != NULL);
+	g_return_if_fail (column < record->n_columns);
+
+	record_take_column (record, column,
+	                    record_block_new (value, strlen (value)));
+}
+
+void
+_gcr_record_take_raw (GcrRecord *record,
+                      guint column,
+                      gchar *value)
+{
+	g_return_if_fail (record != NULL);
+	g_return_if_fail (value != NULL);
+	g_return_if_fail (column < record->n_columns);
+
+	record_take_column (record, column,
+	                    record_block_take (value, strlen (value)));
+}
+
+void
 _gcr_record_free (gpointer record)
 {
+	GcrRecordBlock *block, *next;
+	GcrRecord *rec = record;
+
 	if (!record)
 		return;
 
-	g_free (((GcrRecord*)record)->data);
+	for (block = rec->block; block != NULL; block = next) {
+		next = block->next;
+		g_free (block);
+	}
+
 	g_slice_free (GcrRecord, record);
 }
 
diff --git a/gcr/gcr-record.h b/gcr/gcr-record.h
index 1fe2bce..a9bf6e0 100644
--- a/gcr/gcr-record.h
+++ b/gcr/gcr-record.h
@@ -54,13 +54,19 @@ G_BEGIN_DECLS
 #define GCR_RECORD_SCHEMA_ATTRIBUTE  (g_quark_from_static_string ("ATTRIBUTE"))
 #define GCR_RECORD_SCHEMA_FPR  (g_quark_from_static_string ("fpr"))
 #define GCR_RECORD_SCHEMA_PUB  (g_quark_from_static_string ("pub"))
+#define GCR_RECORD_SCHEMA_SUB  (g_quark_from_static_string ("sub"))
 #define GCR_RECORD_SCHEMA_SEC  (g_quark_from_static_string ("sec"))
+#define GCR_RECORD_SCHEMA_SSB  (g_quark_from_static_string ("ssb"))
 #define GCR_RECORD_SCHEMA_UID  (g_quark_from_static_string ("uid"))
+#define GCR_RECORD_SCHEMA_UAT  (g_quark_from_static_string ("uat"))
 #define GCR_RECORD_SCHEMA_XA1  (g_quark_from_static_string ("xa1"))
+#define GCR_RECORD_SCHEMA_SIG  (g_quark_from_static_string ("sig"))
+#define GCR_RECORD_SCHEMA_RVK  (g_quark_from_static_string ("rvk"))
 
-/* Common columns for all schemas */
+/* Common columns for schemas */
 typedef enum {
-	GCR_RECORD_SCHEMA = 0
+	GCR_RECORD_SCHEMA = 0,
+	GCR_RECORD_TRUST = 1,
 } GcrRecordColumns;
 
 /*
@@ -68,7 +74,7 @@ typedef enum {
  * [GNUPG:] ATTRIBUTE FBAFC70D60AE13D560764062B547B5580EEB5A80 10604 1 1 1 1227936754 0 1
  */
 typedef enum {
-	GCR_RECORD_ATTRIBUTE_FINGERPRINT = 1,
+	GCR_RECORD_ATTRIBUTE_KEY_FINGERPRINT = 1,
 	GCR_RECORD_ATTRIBUTE_LENGTH = 2,
 	GCR_RECORD_ATTRIBUTE_TYPE = 3,
 	GCR_RECORD_ATTRIBUTE_TIMESTAMP = 6,
@@ -84,45 +90,90 @@ typedef enum {
 	GCR_RECORD_FPR_FINGERPRINT = 9
 } GcrRecordFprColumns;
 
-
 /*
- * Columns for pub schema, add them as they're used. eg:
+ * Columns for pub, sec, sub, and ssb schemas. eg:
  * pub:f:1024:17:6C7EE1B8621CC013:899817715:1055898235::m:::scESC:
  */
 typedef enum {
-	GCR_RECORD_PUB_KEYID = 4
+	GCR_RECORD_KEY_BITS = 2,
+	GCR_RECORD_KEY_ALGO = 3,
+	GCR_RECORD_KEY_KEYID = 4,
+	GCR_RECORD_KEY_TIMESTAMP = 5,
+	GCR_RECORD_KEY_EXPIRY = 6,
+	GCR_RECORD_KEY_OWNERTRUST = 8,
+} GcrRecordKeyColumns;
+
+typedef enum {
+	GCR_RECORD_PUB_CAPS = 11,
+	GCR_RECORD_PUB_MAX = 12
 } GcrRecordPubColumns;
 
-/*
- * Columns for sec schema, add them as they're used. eg:
- * sec::2048:1:293FC71A513189BD:1299771018::::::::::
- */
 typedef enum {
-	GCR_RECORD_SEC_KEYID = 4
+	GCR_RECORD_SEC_MAX = 15
 } GcrRecordSecColumns;
 
 /*
  * Columns for uid schema, add them as they're used. eg:
- * pub:f:1024:17:6C7EE1B8621CC013:899817715:1055898235::m:::scESC:
+ * uid:u::::1024442705::7A5C6648DAA1F5D12BD80BBED538439ABAFEE203::Test <test example com>:
  */
 typedef enum {
-	GCR_RECORD_UID_NAME = 9
+	GCR_RECORD_UID_TIMESTAMP = 5,
+	GCR_RECORD_UID_FINGERPRINT = 7,
+	GCR_RECORD_UID_NAME = 9,
+	GCR_RECORD_UID_MAX = 10,
 } GcrRecordUidColumns;
 
 /*
+ * Columns for sig schema. eg:
+ * sig:::17:FAD3A86D2505A4D5:1291829838::::Stef Walter <stefw servingtfi com>:10x:
+ */
+typedef enum {
+	GCR_RECORD_SIG_STATUS = 1,
+	GCR_RECORD_SIG_ALGO = 3,
+	GCR_RECORD_SIG_KEYID = 4,
+	GCR_RECORD_SIG_TIMESTAMP = 5,
+	GCR_RECORD_SIG_EXPIRY = 6,
+	GCR_RECORD_SIG_NAME = 9,
+	GCR_RECORD_SIG_CLASS = 10,
+	GCR_RECORD_SIG_MAX = 11,
+} GcrRecordSigColumns;
+
+/*
+ * Columns for rvk schema. eg:
+ * rvk:::17::::::3FC732041D23E9EA66DDB5009C9DBC21DF74DC61:80:
+ */
+typedef enum {
+	GCR_RECORD_RVK_ALGO = 3,
+	GCR_RECORD_RVK_FINGERPRINT = 9,
+	GCR_RECORD_RVK_CLASS = 10,
+	GCR_RECORD_RVK_MAX = 11,
+} GcrRecordRvkColumns;
+
+/*
+ * Columns for uat schema, add them as they're used. eg:
+ * uat:u::::1024442705::7A5C6648DAA1F5D12BD80BBED538439ABAFEE203::1 3233:
+ */
+typedef enum {
+	GCR_RECORD_UAT_TRUST = 1,
+	GCR_RECORD_UAT_FINGERPRINT = 7,
+	GCR_RECORD_UAT_COUNT_SIZE = 9,
+	GCR_RECORD_UAT_MAX = 10,
+} GcrRecordUatColumns;
+
+/*
  * Columns for xa1 schema. This is a schema that we've invented ourselves
  * for representing the actual data of openpgp attribute packets. eg:
- * xa1::10838:1:ECAF7590EB3443B5C7CF3ACB6C7EE1B8621CC013:1998-02-02:0:ECAF7590EB3443B5C7CF3ACB6C7EE1B8621CC013:P:...
+ * xa1:e:10838:1:::1998-02-02:0:ECAF7590EB3443B5C7CF3ACB6C7EE1B8621CC013::...
  */
 typedef enum {
+	GCR_RECORD_XA1_TRUST = 1,
 	GCR_RECORD_XA1_LENGTH = 2,
 	GCR_RECORD_XA1_TYPE = 3,
-	GCR_RECORD_XA1_FINGERPRINT = 4,
 	GCR_RECORD_XA1_TIMESTAMP = 5,
 	GCR_RECORD_XA1_EXPIRY = 6,
-	GCR_RECORD_XA1_HASH = 7,
-	GCR_RECORD_XA1_STATUS = 8,
+	GCR_RECORD_XA1_FINGERPRINT = 7,
 	GCR_RECORD_XA1_DATA = 9,
+	GCR_RECORD_XA1_MAX = 11,
 } GcrRecordXa1Columns;
 
 typedef struct _GcrRecord GcrRecord;
@@ -131,37 +182,90 @@ typedef struct _GcrRecord GcrRecord;
 
 GType          _gcr_record_get_type             (void) G_GNUC_CONST;
 
+GcrRecord *    _gcr_record_new                  (GQuark schema,
+                                                 guint n_columns,
+                                                 gchar delimiter);
+
 GcrRecord*     _gcr_record_copy                 (GcrRecord *record);
 
 GcrRecord*     _gcr_record_parse_colons         (const gchar *line,
                                                  gssize n_line);
 
-GcrRecord*     _gcr_record_take_colons          (gchar *line);
-
 GcrRecord*     _gcr_record_parse_spaces         (const gchar *line,
                                                  gssize n_line);
 
+gchar *        _gcr_record_format               (GcrRecord *record);
+
+gchar *        _gcr_records_format              (GPtrArray *records);
+
 void           _gcr_record_free                 (gpointer record);
 
 GcrRecord*     _gcr_record_find                 (GPtrArray *records,
                                                  GQuark schema);
 
+GcrRecord*     _gcr_record_rfind                (GPtrArray *records,
+                                                 GQuark schema);
+
 guint          _gcr_record_get_count            (GcrRecord *record);
 
+gchar          _gcr_record_get_char             (GcrRecord *record,
+                                                 guint column);
+
+void           _gcr_record_set_char             (GcrRecord *record,
+                                                 guint column,
+                                                 gchar value);
+
 gchar*         _gcr_record_get_string           (GcrRecord *record,
                                                  guint column);
 
+void           _gcr_record_set_string           (GcrRecord *record,
+                                                 guint column,
+                                                 const gchar *value);
+
 gboolean       _gcr_record_get_uint             (GcrRecord *record,
                                                  guint column,
                                                  guint *value);
 
+void           _gcr_record_set_uint             (GcrRecord *record,
+                                                 guint column,
+                                                 guint value);
+
+gboolean       _gcr_record_get_ulong            (GcrRecord *record,
+                                                 guint column,
+                                                 gulong *value);
+
+void           _gcr_record_set_ulong            (GcrRecord *record,
+                                                 guint column,
+                                                 gulong value);
+
+gboolean       _gcr_record_get_date             (GcrRecord *record,
+                                                 guint column,
+                                                 gulong *value);
+
+void           _gcr_record_set_date             (GcrRecord *record,
+                                                 guint column,
+                                                 gulong value);
+
 gpointer       _gcr_record_get_base64           (GcrRecord *record,
                                                  guint column,
                                                  gsize *n_data);
 
+void           _gcr_record_set_base64           (GcrRecord *record,
+                                                 guint column,
+                                                 gconstpointer data,
+                                                 gsize n_data);
+
 const gchar*   _gcr_record_get_raw              (GcrRecord *record,
                                                  guint column);
 
+void           _gcr_record_set_raw              (GcrRecord *record,
+                                                 guint column,
+                                                 const gchar *value);
+
+void           _gcr_record_take_raw             (GcrRecord *record,
+                                                 guint column,
+                                                 gchar *value);
+
 GQuark         _gcr_record_get_schema           (GcrRecord *record);
 
 G_END_DECLS
diff --git a/gcr/tests/Makefile.am b/gcr/tests/Makefile.am
index aef09b0..15c0cb8 100644
--- a/gcr/tests/Makefile.am
+++ b/gcr/tests/Makefile.am
@@ -57,6 +57,7 @@ noinst_PROGRAMS = \
 	frob-gnupg-selector \
 	frob-key \
 	frob-tree-selector \
+	frob-openpgp \
 	frob-parser \
 	frob-unlock \
 	frob-unlock-options
diff --git a/gcr/tests/files/secring.gpg b/gcr/tests/files/secring.gpg
new file mode 100644
index 0000000..4a21e26
Binary files /dev/null and b/gcr/tests/files/secring.gpg differ
diff --git a/gcr/tests/frob-openpgp.c b/gcr/tests/frob-openpgp.c
new file mode 100644
index 0000000..b83305a
--- /dev/null
+++ b/gcr/tests/frob-openpgp.c
@@ -0,0 +1,122 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#include "config.h"
+
+#include "gcr/gcr.h"
+#include "gcr/gcr-openpgp.h"
+#include "gcr/gcr-record.h"
+
+#include "egg/egg-armor.h"
+
+static void
+on_packet_print_records (GPtrArray *records,
+                         const guchar *packet,
+                         gsize n_packet,
+                         gpointer user_data)
+{
+	gchar *string;
+	guint i;
+
+	for (i = 0; i < records->len; i++) {
+		string = _gcr_record_format (records->pdata[i]);
+		g_print ("%s\n", string);
+		g_free (string);
+	}
+}
+
+static gboolean
+parse_binary (gconstpointer contents,
+              gsize length)
+{
+	guint packets;
+
+	packets = _gcr_openpgp_parse (contents, length,
+	                              GCR_OPENPGP_PARSE_KEYS |
+	                              GCR_OPENPGP_PARSE_ATTRIBUTES,
+	                              on_packet_print_records, NULL);
+
+	return (packets > 0);
+}
+
+static void
+on_armor_parsed (GQuark type,
+                 const guchar *data,
+                 gsize n_data,
+                 const gchar *outer,
+                 gsize n_outer,
+                 GHashTable *headers,
+                 gpointer user_data)
+{
+	const gchar *value;
+	gboolean *result = user_data;
+
+	value = g_hash_table_lookup (headers, "Version");
+	g_assert_cmpstr (value, ==, "GnuPG v1.4.11 (GNU/Linux)");
+
+	*result = parse_binary (data, n_data);
+}
+
+static gboolean
+parse_armor_or_binary (gconstpointer contents,
+                       gsize length)
+{
+	gboolean result;
+	guint parts;
+
+	parts = egg_armor_parse (contents, length, on_armor_parsed, &result);
+	if (parts == 0)
+		result = parse_binary (contents, length);
+	return result;
+}
+
+int
+main(int argc, char *argv[])
+{
+	GError *error = NULL;
+	gchar *contents;
+	gsize length;
+	int ret;
+
+	g_set_prgname ("frob-openpgp");
+
+	if (argc != 2) {
+		g_printerr ("usage: frob-openpgp filename\n");
+		return 2;
+	}
+
+	if (!g_file_get_contents (argv[1], &contents, &length, &error)) {
+		g_printerr ("frob-openpgp: couldn't read file: %s: %s", argv[1], error->message);
+		g_error_free (error);
+		return 1;
+	}
+
+	ret = 0;
+	if (!parse_armor_or_binary (contents, length)) {
+		g_printerr ("frob-openpgp: no openpgp data found in data");
+		ret = 1;
+	}
+
+	g_free (contents);
+	return ret;
+}
diff --git a/gcr/tests/test-openpgp.c b/gcr/tests/test-openpgp.c
index 7d4fd5e..4c8c1d0 100644
--- a/gcr/tests/test-openpgp.c
+++ b/gcr/tests/test-openpgp.c
@@ -24,6 +24,7 @@
 
 #include "gcr/gcr.h"
 #include "gcr/gcr-openpgp.h"
+#include "gcr/gcr-record.h"
 
 #include "egg/egg-armor.h"
 #include "egg/egg-testing.h"
@@ -32,17 +33,216 @@
 #include <glib.h>
 #include <string.h>
 
+typedef struct {
+	const gchar *name;
+	const gchar **records;
+	const gchar *filename;
+	const gchar *version;
+	GcrOpenpgpParseFlags flags;
+} Fixture;
+
+static const gchar *werner_koch_records[] = {
+	"pub:e:1024:17:68B7AB8957548DCD:899816990:1136043547::o:::sca:\n"
+	"uid:e::::1102866526::B712A25DC2ABEF1579696C2925859931078C2C3E::Werner Koch (gnupg sig) <dd9jn gnu org>:\n",
+
+	"pub:e:1024:17:5DE249965B0358A2:921520361:1247335656::o:::sc:\n"
+	"uid:e::::1113145458::F5B5738FAFB7543A01BAB31A6D767FBC789FF8A8::Werner Koch <wk gnupg org>:\n"
+	"uid:e::::1113145466::60095F7DAD08129CCE39E15BEB6BBE21937E3AA6::Werner Koch <wk g10code com>:\n"
+	"uid:e::::921520362::392B892CF897AD0F03EB26343C4C20A48B36513E::Werner Koch:\n"
+	"uid:e::::1113145466::3E000C0F7D13A3C57C633C16ABDC97F12EAF16C1::Werner Koch <werner fsfe org>:\n"
+	"sub:e:1024:17:60784E94010A57ED:1079892559:1199124559:::::s:\n"
+	"sub:e:2048:1:7299C628B604F148:1079892777:1136052777:::::e:\n"
+	"sub:e:2048:1:35E52D69C3680A6E:1136137762:1199123362:::::e:\n",
+
+	"pub:e:1024:1:53B620D01CE0C630:1136130759:1230738759::o:::sc:\n"
+	"uid:e::::1136130760::142B958D9816ECF810DBB83BD257E5C7DB36C99A::Werner Koch (dist sig) <dd9jn gnu org>:\n",
+
+	NULL
+};
+
+static const gchar *werner_sig_records[] = {
+	"pub:e:1024:17:68B7AB8957548DCD:899816990:1136043547::o:::sca:\n"
+	"uid:e::::1102866526::B712A25DC2ABEF1579696C2925859931078C2C3E::Werner Koch (gnupg sig) <dd9jn gnu org>:\n"
+	"sig:::17:68B7AB8957548DCD:1102866526:::::13x:\n",
+
+	"pub:e:1024:17:5DE249965B0358A2:921520361:1247335656::o:::sc:\n"
+	"uid:e::::1113145458::F5B5738FAFB7543A01BAB31A6D767FBC789FF8A8::Werner Koch <wk gnupg org>:\n"
+	"sig:::17:5DE249965B0358A2:1113145458:::::13x:\n"
+	"uid:e::::1113145466::60095F7DAD08129CCE39E15BEB6BBE21937E3AA6::Werner Koch <wk g10code com>:\n"
+	"sig:::17:5DE249965B0358A2:1113145466:::::13x:\n"
+	"uid:e::::921520362::392B892CF897AD0F03EB26343C4C20A48B36513E::Werner Koch:\n"
+	"sig:::17:5DE249965B0358A2:921520362:::::13x:\n"
+	"uid:e::::1113145466::3E000C0F7D13A3C57C633C16ABDC97F12EAF16C1::Werner Koch <werner fsfe org>:\n"
+	"sig:::17:5DE249965B0358A2:1113145466:::::13x:\n"
+	"sub:e:1024:17:60784E94010A57ED:1079892559:1199124559:::::s:\n"
+	"sig:::17:5DE249965B0358A2:1148562461:::::18x:\n"
+	"sub:e:2048:1:7299C628B604F148:1079892777:1136052777:::::e:\n"
+	"sig:::17:5DE249965B0358A2:1079892777:::::18x:\n"
+	"sub:e:2048:1:35E52D69C3680A6E:1136137762:1199123362:::::e:\n"
+	"sig:::17:5DE249965B0358A2:1136137762:::::18x:\n",
+
+	"pub:e:1024:1:53B620D01CE0C630:1136130759:1230738759::o:::sc:\n"
+	"uid:e::::1136130760::142B958D9816ECF810DBB83BD257E5C7DB36C99A::Werner Koch (dist sig) <dd9jn gnu org>:\n"
+	"sig:::1:53B620D01CE0C630:1136130760:::::13x:\n",
+
+	NULL
+};
+
+static const gchar *pubring_records[] = {
+	"pub:o:2048:1:4842D952AFC000FD:1305189489:::o:::scSCE:\n"
+	"uid:o::::1305189489::D449F1605254754B0BBFA424FC34E50609103BBB::Test Number 1 (unlimited) <test-number-1 example com>:\n"
+	"uid:o::::1305189849::D0A8FA7B15DC4BE3F8F03A49C372F2718C78AFC0::Dr. Strangelove <lovingbomb example com>:\n"
+	"sub:o:2048:1:4852132BBED15014:1305189489::::::e:\n",
+
+	"pub:e:1024:1:268FEE686262C395:1305189628:1305276028::o:::sc:\n"
+	"uid:e::::1305189628::2E9D48BD771DA765D2B48A0233D0E8F393F6E839::Test Number 2 (all gone) <test-number-2 example com>:\n"
+	"sub:e:1024:1:C5877FABF4772E4F:1305189628:1305276028:::::e:\n",
+
+	"pub:e:1024:17:68B7AB8957548DCD:899816990:1136043547::o:::sca:\n"
+	"uid:e::::1102866526::B712A25DC2ABEF1579696C2925859931078C2C3E::Werner Koch (gnupg sig) <dd9jn gnu org>:\n",
+
+	"pub:e:1024:17:5DE249965B0358A2:921520361:1247335656::o:::sc:\n"
+	"uid:e::::1113145458::F5B5738FAFB7543A01BAB31A6D767FBC789FF8A8::Werner Koch <wk gnupg org>:\n"
+	"uid:e::::1113145466::60095F7DAD08129CCE39E15BEB6BBE21937E3AA6::Werner Koch <wk g10code com>:\n"
+	"uid:e::::921520362::392B892CF897AD0F03EB26343C4C20A48B36513E::Werner Koch:\n"
+	"uid:e::::1113145466::3E000C0F7D13A3C57C633C16ABDC97F12EAF16C1::Werner Koch <werner fsfe org>:\n"
+	"sub:e:1024:17:60784E94010A57ED:1079892559:1199124559:::::s:\n"
+	"sub:e:2048:1:7299C628B604F148:1079892777:1136052777:::::e:\n"
+	"sub:e:2048:1:35E52D69C3680A6E:1136137762:1199123362:::::e:\n",
+
+	"pub:o:1024:17:C7463639B2D7795E:978642983:::o:::scSCE:\n"
+	"rvk:o::17::::::3FC732041D23E9EA66DDB5009C9DBC21DF74DC61:80:\n"
+	"uid:o::::978642983::44C6F00AAE524A8955CAB76F2BB16126530BB203::Philip R. Zimmermann <prz mit edu>:\n"
+	"uid:o::::978643127::BD93DF0D0D564E85F73ECBECFFB1B5BA5FF2838D::Philip R. Zimmermann <prz acm org>:\n"
+	"uat:o::::978751266::E0F87F37495D4ED247BB66A08D7360D8D81F9976::1 3391:\n"
+	"uat:o::::1013326898::10A2C49F62C540090ECD679C518AACAA8E960BA5::1 3479:\n"
+	"uid:o::::1052692250::09D1F68A1C44AC42E7FCC5615EEDBB0FD581DCDE::Philip R. Zimmermann <prz philzimmermann com>:\n"
+	"sub:o:3072:16:C4EB1C56A8E92834:978642983::::::e:\n",
+
+	"pub:o:4096:1:DB698D7199242560:1012189561:::o:::scSCEA:\n"
+	"uid:o::::1012189561::0E5FC22DD5518890217F20F1FF832597932B46C1::David M. Shaw <dshaw jabberwocky com>:\n"
+	"sub:o:2048:16:AE2827D11643B926:1012189956:1327549956:::::e:\n"
+	"sub:o:1024:17:E2665C8749E1CBC9:1012190171:1327550171:::::sca:\n",
+
+	"pub:o:2048:1:9710B89BCA57AD7C:1102303986:::o:::scSC:\n"
+	"uid:o::::1112650864::A96F758EFD5D67EA9450860C7D15A96DAA1B40E2::PGP Global Directory Verification Key:\n"
+	"uat:o::::1112650864::83B0B68B95892BBCE32F04BA0FBAC6CEAD4EDE49::1 3422:\n",
+
+	"pub:e:1024:1:53B620D01CE0C630:1136130759:1230738759::o:::sc:\n"
+	"uid:e::::1136130760::142B958D9816ECF810DBB83BD257E5C7DB36C99A::Werner Koch (dist sig) <dd9jn gnu org>:\n",
+
+	NULL
+};
+
+static const gchar *secring_records[] = {
+	"sec::2048:1:4842D952AFC000FD:1305189489::::::::::\n"
+	"uid:::::::D449F1605254754B0BBFA424FC34E50609103BBB::Test Number 1 (unlimited) <test-number-1 example com>:\n"
+	"uid:::::::D0A8FA7B15DC4BE3F8F03A49C372F2718C78AFC0::Dr. Strangelove <lovingbomb example com>:\n"
+	"ssb::2048:1:4852132BBED15014:1305189489::::::::::\n",
+
+	"sec::1024:1:268FEE686262C395:1305189628:1305276028:::::::::\n"
+	"uid:::::::2E9D48BD771DA765D2B48A0233D0E8F393F6E839::Test Number 2 (all gone) <test-number-2 example com>:\n"
+	"ssb::1024:1:C5877FABF4772E4F:1305189628::::::::::\n",
+
+	NULL
+};
+
+static Fixture fixtures[] = {
+	{
+	  "werner_koch",
+	  werner_koch_records,
+	  SRCDIR "/files/werner-koch.asc",
+	  "GnuPG v1.4.11 (GNU/Linux)",
+	  GCR_OPENPGP_PARSE_KEYS
+	},
+	{
+	  "werner_koch_with_sigs",
+	  werner_sig_records,
+	  SRCDIR "/files/werner-koch.asc",
+	  "GnuPG v1.4.11 (GNU/Linux)",
+	  GCR_OPENPGP_PARSE_KEYS | GCR_OPENPGP_PARSE_SIGNATURES
+	},
+	{
+	  "pubring",
+	  pubring_records,
+	  SRCDIR "/files/pubring.gpg",
+	  NULL,
+	  GCR_OPENPGP_PARSE_KEYS
+	},
+	{
+	  "secring",
+	  secring_records,
+	  SRCDIR "/files/secring.gpg",
+	  NULL,
+	  GCR_OPENPGP_PARSE_KEYS
+	}
+};
+
+typedef struct {
+	const gchar **at;
+	const Fixture *fixture;
+} Test;
+
+static void
+setup (Test *test,
+       gconstpointer data)
+{
+	const Fixture *fixture = data;
+	test->fixture = fixture;
+	test->at = fixture->records;
+}
+
+static void
+teardown (Test *test,
+          gconstpointer data)
+{
+
+}
+
+static void
+compare_fixture_with_records (const gchar *fixture,
+                              GPtrArray *records)
+{
+	gchar *record;
+	gchar **lines;
+	guint i;
+
+	lines = g_strsplit (fixture, "\n", -1);
+	for (i = 0; i < records->len; i++) {
+		record = _gcr_record_format (records->pdata[i]);
+		g_assert_cmpstr (record, ==, lines[i]);
+		g_free (record);
+	}
+
+	if (lines[i] == NULL) {
+		g_test_message ("more openpgp records parsed than in fixture");
+		g_assert_not_reached ();
+	}
+
+	g_strfreev (lines);
+}
+
 static void
 on_openpgp_packet  (GPtrArray *records,
                     const guchar *outer,
                     gsize n_outer,
                     gpointer user_data)
 {
-	guint num_packets;
+	Test *test = user_data;
+	guint seen;
 
 	/* Should be parseable again */
-	num_packets = _gcr_openpgp_parse (outer, n_outer, NULL, NULL);
-	g_assert_cmpuint (num_packets, ==, 1);
+	seen = _gcr_openpgp_parse (outer, n_outer, test->fixture->flags |
+	                           GCR_OPENPGP_PARSE_NO_RECORDS, NULL, NULL);
+	g_assert_cmpuint (seen, ==, 1);
+
+	if (*(test->at) == NULL) {
+		g_test_message ("more openpgp packets parsed than in fixture");
+		g_assert_not_reached ();
+	}
+
+	compare_fixture_with_records (*(test->at), records);
+	test->at++;
 }
 
 static void
@@ -54,41 +254,85 @@ on_armor_parsed (GQuark type,
                  GHashTable *headers,
                  gpointer user_data)
 {
+	Test *test = user_data;
 	const gchar *value;
-	guint num_packets;
+	guint seen;
 
-	value = g_hash_table_lookup (headers, "Version");
-	g_assert_cmpstr (value, ==, "GnuPG v1.4.11 (GNU/Linux)");
+	if (test->fixture->version) {
+		value = g_hash_table_lookup (headers, "Version");
+		g_assert_cmpstr (value, ==, test->fixture->version);
+	}
 
-	num_packets = _gcr_openpgp_parse (data, n_data, on_openpgp_packet, NULL);
-	g_assert_cmpuint (num_packets, ==, 21);
+	seen = _gcr_openpgp_parse (data, n_data, test->fixture->flags,
+	                           on_openpgp_packet, test);
+	g_assert_cmpuint (seen, >, 0);
+
+	if (*(test->at) != NULL) {
+		g_test_message ("less openpgp packets parsed than in fixture");
+		g_assert_not_reached ();
+	}
 }
 
 static void
-test_armor_parse (void)
+test_openpgp_armor (Test *test,
+                    gconstpointer data)
 {
 	GError *error = NULL;
 	gchar *armor;
 	gsize length;
 	guint parts;
 
-	g_file_get_contents (SRCDIR "/files/werner-koch.asc", &armor, &length, &error);
+	g_file_get_contents (test->fixture->filename, &armor, &length, &error);
 	g_assert_no_error (error);
 
-	parts = egg_armor_parse (armor, length, on_armor_parsed, NULL);
+	parts = egg_armor_parse (armor, length, on_armor_parsed, test);
 	g_assert_cmpuint (parts, ==, 1);
 
 	g_free (armor);
 }
 
+static void
+test_openpgp_binary (Test *test,
+                     gconstpointer data)
+{
+	GError *error = NULL;
+	gchar *binary;
+	gsize length;
+	guint seen;
+
+	g_file_get_contents (test->fixture->filename, &binary, &length, &error);
+	g_assert_no_error (error);
+
+	seen = _gcr_openpgp_parse (binary, length, test->fixture->flags,
+	                           on_openpgp_packet, test);
+	g_assert_cmpuint (seen, >, 0);
+
+	if (*(test->at) != NULL) {
+		g_test_message ("less openpgp packets parsed than in fixture");
+		g_assert_not_reached ();
+	}
+
+	g_free (binary);
+}
+
 int
 main (int argc, char **argv)
 {
+	guint i;
+	gchar *test_path;
+
 	g_type_init ();
 	g_test_init (&argc, &argv, NULL);
-	g_set_prgname ("test-gnupg-process");
+	g_set_prgname ("test-openpgp");
 
-	g_test_add_func ("/gcr/openpgp/armor_parse", test_armor_parse);
+	for (i = 0; i < G_N_ELEMENTS (fixtures); i++) {
+		test_path = g_strdup_printf ("/gcr/openpgp/%s", fixtures[i].name);
+		if (g_str_has_suffix (fixtures[i].filename, ".asc"))
+			g_test_add (test_path, Test, fixtures + i, setup, test_openpgp_armor, teardown);
+		else
+			g_test_add (test_path, Test, fixtures + i, setup, test_openpgp_binary, teardown);
+		g_free (test_path);
+	}
 
 	return g_test_run ();
 }
diff --git a/gcr/tests/test-record.c b/gcr/tests/test-record.c
index 4c132ff..4a18d44 100644
--- a/gcr/tests/test-record.c
+++ b/gcr/tests/test-record.c
@@ -70,27 +70,6 @@ test_parse_colons (void)
 }
 
 static void
-test_take_colons (void)
-{
-	GcrRecord *record;
-
-	record = _gcr_record_take_colons (g_strdup ("one:two::four::six"));
-	g_assert (record);
-
-	g_assert_cmpstr (_gcr_record_get_raw (record, 0), ==, "one");
-	g_assert_cmpstr (_gcr_record_get_raw (record, 1), ==, "two");
-	g_assert_cmpstr (_gcr_record_get_raw (record, 2), ==, "");
-	g_assert_cmpstr (_gcr_record_get_raw (record, 3), ==, "four");
-	g_assert_cmpstr (_gcr_record_get_raw (record, 4), ==, "");
-	g_assert_cmpstr (_gcr_record_get_raw (record, 5), ==, "six");
-	g_assert (_gcr_record_get_raw (record, 6) == NULL);
-	g_assert_cmpuint (_gcr_record_get_count (record), ==, 6);
-
-	_gcr_record_free (record);
-}
-
-
-static void
 test_parse_spaces (void)
 {
 	GcrRecord *record;
@@ -294,7 +273,6 @@ main (int argc, char **argv)
 	g_test_init (&argc, &argv, NULL);
 
 	g_test_add_func ("/gcr/record/parse_colons", test_parse_colons);
-	g_test_add_func ("/gcr/record/take_colons", test_take_colons);
 	g_test_add_func ("/gcr/record/parse_colons", test_parse_spaces);
 	g_test_add_func ("/gcr/record/parse_part", test_parse_part);
 	g_test_add_func ("/gcr/record/parse_too_long", test_parse_too_long);
diff --git a/pkcs11/gkm/gkm-attributes.c b/pkcs11/gkm/gkm-attributes.c
index cb4b03c..99c2f09 100644
--- a/pkcs11/gkm/gkm-attributes.c
+++ b/pkcs11/gkm/gkm-attributes.c
@@ -24,6 +24,8 @@
 #include "gkm-attributes.h"
 #include "gkm-util.h"
 
+#include "egg/egg-timegm.h"
+
 #include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
@@ -61,37 +63,6 @@ gkm_attribute_get_ulong (CK_ATTRIBUTE_PTR attr, CK_ULONG *value)
 	return CKR_OK;
 }
 
-#ifndef HAVE_TIMEGM
-static time_t
-timegm (struct tm *t)
-{
-	time_t tl, tb;
-	struct tm *tg;
-
-	tl = mktime (t);
-	if (tl == -1)
-	{
-		t->tm_hour--;
-		tl = mktime (t);
-		if (tl == -1)
-			return -1; /* can't deal with output from strptime */
-		tl += 3600;
-	}
-	tg = gmtime (&tl);
-	tg->tm_isdst = 0;
-	tb = mktime (tg);
-	if (tb == -1)
-	{
-		tg->tm_hour--;
-		tb = mktime (tg);
-		if (tb == -1)
-			return -1; /* can't deal with output from gmtime */
-		tb += 3600;
-	}
-	return (tl - (tb - tl));
-}
-#endif // NOT_HAVE_TIMEGM
-
 CK_RV
 gkm_attribute_get_time (CK_ATTRIBUTE_PTR attr, glong *when)
 {



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