/*
* 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, see .
*
* Author: Stef Walter
*/
#include "config.h"
#include "gcr-openpgp.h"
#include "gcr-internal.h"
#include "gcr-record.h"
#include "gcr-types.h"
#include "egg/egg-hex.h"
#include
#include
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,
guint8 *result)
{
g_assert (at);
if (*at == end)
*at = NULL;
if (*at == NULL)
return FALSE;
if (result)
*result = *(*at);
(*at)++;
return TRUE;
}
static gboolean
read_bytes (const guchar **at,
const guchar *end,
gpointer buffer,
gsize length)
{
g_assert (at);
if (*at + length > end)
*at = NULL;
if (*at == NULL)
return FALSE;
if (buffer != NULL)
memcpy (buffer, *at, length);
(*at) += length;
return TRUE;
}
static gboolean
read_uint32 (const guchar **at,
const guchar *end,
guint32 *value)
{
guchar buf[4];
g_assert (at);
if (!read_bytes (at, end, buf, 4))
return FALSE;
if (value)
*value = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
return TRUE;
}
static gboolean
read_uint16 (const guchar **at,
const guchar *end,
guint16 *value)
{
guchar buf[2];
g_assert (at);
if (!read_bytes (at, end, buf, 2))
return FALSE;
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;
}
static gboolean
read_new_length (const guchar **at,
const guchar *end,
gsize *pkt_len)
{
guint8 c, c1;
guint32 val;
if (!read_byte (at, end, &c))
return FALSE;
if (c < 192) {
*pkt_len = c;
} else if (c >= 192 && c <= 223) {
if (!read_byte (at, end, &c1))
return FALSE;
*pkt_len = ((c - 192) << 8) + c1 + 192;
} else if (c == 255) {
if (!read_uint32 (at, end, &val))
return FALSE;
*pkt_len = val;
} else {
/* We don't support partial length */
return FALSE;
}
return TRUE;
}
static gboolean
read_old_length (const guchar **at,
const guchar *end,
guchar ctb,
gsize *pkt_len)
{
gsize llen = ctb & 0x03;
guint16 v16;
guint32 v32;
guint8 c;
if (llen == 0) {
if (!read_byte (at, end, &c))
return FALSE;
*pkt_len = c;
} else if (llen == 1) {
if (!read_uint16 (at, end, &v16))
return FALSE;
*pkt_len = v16;
} else if (llen == 2) {
if (!read_uint32 (at, end, &v32))
return FALSE;
*pkt_len = v32;
} else {
*pkt_len = end - *at;
}
return TRUE;
}
static GcrDataError
read_openpgp_packet (const guchar **at,
const guchar *end,
guint8 *pkt_type,
gsize *length)
{
gboolean new_ctb;
guint8 ctb;
gboolean ret;
if (!read_byte (at, end, &ctb))
return GCR_ERROR_UNRECOGNIZED;
if (!(ctb & 0x80))
return GCR_ERROR_UNRECOGNIZED;
/* RFC2440 packet format. */
if (ctb & 0x40) {
*pkt_type = ctb & 0x3f;
new_ctb = TRUE;
/* the old RFC1991 packet format. */
} else {
*pkt_type = ctb & 0x3f;
*pkt_type >>= 2;
new_ctb = FALSE;
}
if (*pkt_type > 63)
return GCR_ERROR_UNRECOGNIZED;
if (new_ctb)
ret = read_new_length (at, end, length);
else
ret = read_old_length (at, end, ctb, length);
if (!ret)
return GCR_ERROR_UNRECOGNIZED;
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, NULL, 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, NULL, 0);
return TRUE;
}
static gchar *
hash_v4_keyid (const guchar *data,
const guchar *end,
gchar **fingerprint)
{
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, NULL, 0);
if (fingerprint)
*fingerprint = egg_hex_encode_full (digest, 20, TRUE, NULL, 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 GCR_OPENPGP_ALGO_RSA:
case GCR_OPENPGP_ALGO_RSA_E:
case GCR_OPENPGP_ALGO_RSA_S:
if (!read_mpi (at, end, bits, NULL) ||
!read_mpi (at, end, NULL, NULL))
return FALSE;
return TRUE;
case GCR_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 GCR_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 GCR_OPENPGP_ALGO_RSA:
return "cse";
case GCR_OPENPGP_ALGO_RSA_E:
return "e";
case GCR_OPENPGP_ALGO_RSA_S:
return "s";
case GCR_OPENPGP_ALGO_ELG_E:
return "e";
case GCR_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 *fingerprint = NULL;
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, &fingerprint);
}
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);
if (fingerprint && (schema == GCR_RECORD_SCHEMA_PUB || schema == GCR_RECORD_SCHEMA_SEC)) {
record = _gcr_record_new (GCR_RECORD_SCHEMA_FPR, GCR_RECORD_FPR_MAX, ':');
_gcr_record_take_raw (record, GCR_RECORD_FPR_FINGERPRINT, fingerprint);
g_ptr_array_add (records, record);
fingerprint = NULL;
}
g_free (fingerprint);
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_USERID, 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 GCR_OPENPGP_ALGO_RSA:
return read_mpi (at, end, NULL, NULL);
/* DSA values r and s */
case GCR_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, NULL, 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, NULL, 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, NULL, 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_USERID, 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 && schema != GCR_RECORD_SCHEMA_FPR)
_gcr_record_set_char (records->pdata[i], GCR_RECORD_TRUST, trust);
}
}
}
typedef struct {
GcrOpenpgpCallback callback;
gpointer user_data;
guint count;
GBytes *backing;
GPtrArray *records;
} openpgp_parse_closure;
static void
openpgp_parse_free (gpointer data)
{
openpgp_parse_closure *closure = data;
g_ptr_array_unref (closure->records);
g_bytes_unref (closure->backing);
g_free (closure);
}
static void
maybe_emit_openpgp_block (openpgp_parse_closure *closure,
const guchar *block,
const guchar *end)
{
GBytes *outer;
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);
outer = g_bytes_new_with_free_func (block, length, (GDestroyNotify)g_bytes_unref,
g_bytes_ref (closure->backing));
if (closure->callback)
(closure->callback) (records, outer, closure->user_data);
g_bytes_unref (outer);
g_ptr_array_unref (records);
}
guint
_gcr_openpgp_parse (GBytes *data,
GcrOpenpgpParseFlags flags,
GcrOpenpgpCallback callback,
gpointer user_data)
{
openpgp_parse_closure *closure;
const guchar *at;
const guchar *beg;
const guchar *end;
const guchar *block;
guint8 pkt_type;
GcrDataError res;
gsize length;
gboolean new_key;
guint ret;
g_return_val_if_fail (data != NULL, 0);
/* For libgcrypt */
_gcr_initialize_library ();
at = g_bytes_get_data (data, NULL);
end = at + g_bytes_get_size (data);
block = NULL;
closure = g_new0 (openpgp_parse_closure, 1);
closure->callback = callback;
closure->user_data = user_data;
closure->backing = g_bytes_ref (data);
closure->records = g_ptr_array_new_with_free_func (_gcr_record_free);
while (at != NULL && at != end) {
beg = at;
res = read_openpgp_packet (&at, end, &pkt_type, &length);
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 (block != NULL && block != beg)
maybe_emit_openpgp_block (closure, block, beg);
block = NULL;
break;
}
at += length;
}
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;
}