[gnome-keyring] gcr: Implement parsing of openpgp packet contents into records.
- From: Stefan Walter <stefw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-keyring] gcr: Implement parsing of openpgp packet contents into records.
- Date: Tue, 27 Sep 2011 15:15:07 +0000 (UTC)
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, ×tamp))
+ 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, ×tamp))
+ _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, ×tamp))
+ _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]