/* keydb.c - Key database routines * Copyright (C) 2002, 2003, 2007, 2008, 2009 Free Software Foundation, Inc. * * Author: Timo Schulz * * This file is part of OpenCDK. * * The OpenCDK library 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 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA * */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include "opencdk.h" #include "main.h" #include "packet.h" #include "filters.h" #include "stream.h" #include "keydb.h" #define KEYID_CMP(a, b) ((a[0]) == (b[0]) && (a[1]) == (b[1])) #define KEYDB_CACHE_ENTRIES 8 static void keydb_cache_free (key_table_t cache); static int classify_data (const byte * buf, size_t len); static cdk_kbnode_t find_selfsig_node (cdk_kbnode_t key, cdk_pkt_pubkey_t pk); static char * keydb_idx_mkname (const char *file) { static const char *fmt = "%s.idx"; char *fname; size_t len = strlen (file) + strlen (fmt); fname = cdk_calloc (1, len + 1); if (!fname) return NULL; if (snprintf (fname, len, fmt, file) <= 0) return NULL; return fname; } /* This functions builds an index of the keyring into a separate file with the name keyring.ext.idx. It contains the offset of all public- and public subkeys. The format of the file is: -------- 4 octets offset of the packet 8 octets keyid 20 octets fingerprint -------- We store the keyid and the fingerprint due to the fact we can't get the keyid from a v3 fingerprint directly. */ static cdk_error_t keydb_idx_build (const char *file) { cdk_packet_t pkt; cdk_stream_t inp, out = NULL; byte buf[4 + 8 + KEY_FPR_LEN]; char *idx_name; u32 keyid[2]; cdk_error_t rc; if (!file) { gnutls_assert (); return CDK_Inv_Value; } rc = cdk_stream_open (file, &inp); if (rc) { gnutls_assert (); return rc; } idx_name = keydb_idx_mkname (file); if (!idx_name) { cdk_stream_close (inp); gnutls_assert (); return CDK_Out_Of_Core; } rc = cdk_stream_create (idx_name, &out); cdk_free (idx_name); if (rc) { cdk_stream_close (inp); gnutls_assert (); return rc; } cdk_pkt_new (&pkt); while (!cdk_stream_eof (inp)) { off_t pos = cdk_stream_tell (inp); rc = cdk_pkt_read (inp, pkt); if (rc) { _cdk_log_debug ("index build failed packet off=%lu\n", pos); /* FIXME: The index is incomplete */ break; } if (pkt->pkttype == CDK_PKT_PUBLIC_KEY || pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY) { _cdk_u32tobuf (pos, buf); cdk_pk_get_keyid (pkt->pkt.public_key, keyid); _cdk_u32tobuf (keyid[0], buf + 4); _cdk_u32tobuf (keyid[1], buf + 8); cdk_pk_get_fingerprint (pkt->pkt.public_key, buf + 12); cdk_stream_write (out, buf, 4 + 8 + KEY_FPR_LEN); } cdk_pkt_free (pkt); } cdk_pkt_release (pkt); cdk_stream_close (out); cdk_stream_close (inp); gnutls_assert (); return rc; } /** * cdk_keydb_idx_rebuild: * @hd: key database handle * * Rebuild the key index files for the given key database. **/ cdk_error_t cdk_keydb_idx_rebuild (cdk_keydb_hd_t db, cdk_keydb_search_t dbs) { struct stat stbuf; char *tmp_idx_name; cdk_error_t rc; int err; if (!db || !db->name || !dbs) { gnutls_assert (); return CDK_Inv_Value; } if (db->secret) return 0; tmp_idx_name = keydb_idx_mkname (db->name); if (!tmp_idx_name) { gnutls_assert (); return CDK_Out_Of_Core; } err = stat (tmp_idx_name, &stbuf); cdk_free (tmp_idx_name); /* This function expects an existing index which can be rebuild, if no index exists we do not build one and just return. */ if (err) return 0; cdk_stream_close (dbs->idx); dbs->idx = NULL; if (!dbs->idx_name) { dbs->idx_name = keydb_idx_mkname (db->name); if (!dbs->idx_name) { gnutls_assert (); return CDK_Out_Of_Core; } } rc = keydb_idx_build (db->name); if (!rc) rc = cdk_stream_open (dbs->idx_name, &dbs->idx); else gnutls_assert (); return rc; } static cdk_error_t keydb_idx_parse (cdk_stream_t inp, key_idx_t * r_idx) { key_idx_t idx; byte buf[4]; if (!inp || !r_idx) { gnutls_assert (); return CDK_Inv_Value; } idx = cdk_calloc (1, sizeof *idx); if (!idx) { gnutls_assert (); return CDK_Out_Of_Core; } while (!cdk_stream_eof (inp)) { if (cdk_stream_read (inp, buf, 4) == CDK_EOF) break; idx->offset = _cdk_buftou32 (buf); cdk_stream_read (inp, buf, 4); idx->keyid[0] = _cdk_buftou32 (buf); cdk_stream_read (inp, buf, 4); idx->keyid[1] = _cdk_buftou32 (buf); cdk_stream_read (inp, idx->fpr, KEY_FPR_LEN); break; } *r_idx = idx; return cdk_stream_eof (inp) ? CDK_EOF : 0; } static cdk_error_t keydb_idx_search (cdk_stream_t inp, u32 * keyid, const byte * fpr, off_t * r_off) { key_idx_t idx; if (!inp || !r_off) { gnutls_assert (); return CDK_Inv_Value; } if ((keyid && fpr) || (!keyid && !fpr)) { gnutls_assert (); return CDK_Inv_Mode; } /* We need an initialize the offset var with a value because it might be possible the returned offset will be 0 and then we cannot differ between the begin and an EOF. */ *r_off = 0xFFFFFFFF; cdk_stream_seek (inp, 0); while (keydb_idx_parse (inp, &idx) != CDK_EOF) { if (keyid && KEYID_CMP (keyid, idx->keyid)) { *r_off = idx->offset; break; } else if (fpr && !memcmp (idx->fpr, fpr, KEY_FPR_LEN)) { *r_off = idx->offset; break; } cdk_free (idx); idx = NULL; } cdk_free (idx); return *r_off != 0xFFFFFFFF ? 0 : CDK_EOF; } /** * cdk_keydb_new_from_mem: * @r_hd: The keydb output handle. * @data: The raw key data. * @datlen: The length of the raw data. * * Create a new keyring db handle from the contents of a buffer. */ cdk_error_t cdk_keydb_new_from_mem (cdk_keydb_hd_t * r_db, int secret, const void *data, size_t datlen) { cdk_keydb_hd_t db; cdk_error_t rc; if (!r_db) { gnutls_assert (); return CDK_Inv_Value; } *r_db = NULL; db = calloc (1, sizeof *db); rc = cdk_stream_tmp_from_mem (data, datlen, &db->fp); if (!db->fp) { cdk_free (db); gnutls_assert (); return rc; } if (cdk_armor_filter_use (db->fp)) cdk_stream_set_armor_flag (db->fp, 0); db->type = CDK_DBTYPE_DATA; db->secret = secret; *r_db = db; return 0; } /** * cdk_keydb_new_from_stream: * @r_hd: the output keydb handle * @secret: does the stream contain secret key data * @in: the input stream to use * * This function creates a new keydb handle based on the given * stream. The stream is not closed in cdk_keydb_free() and it * is up to the caller to close it. No decoding is done. */ cdk_error_t cdk_keydb_new_from_stream (cdk_keydb_hd_t * r_hd, int secret, cdk_stream_t in) { cdk_keydb_hd_t hd; if (!r_hd) { gnutls_assert (); return CDK_Inv_Value; } *r_hd = NULL; hd = calloc (1, sizeof *hd); hd->fp = in; hd->fp_ref = 1; hd->type = CDK_DBTYPE_STREAM; hd->secret = secret; *r_hd = hd; /* We do not push any filters and thus we expect that the format of the stream has the format the user wanted. */ return 0; } cdk_error_t cdk_keydb_new_from_file (cdk_keydb_hd_t * r_hd, int secret, const char *fname) { cdk_keydb_hd_t hd; if (!r_hd) { gnutls_assert (); return CDK_Inv_Value; } *r_hd = NULL; hd = calloc (1, sizeof *hd); hd->name = cdk_strdup (fname); if (!hd->name) { cdk_free (hd); gnutls_assert (); return CDK_Out_Of_Core; } hd->type = secret ? CDK_DBTYPE_SK_KEYRING : CDK_DBTYPE_PK_KEYRING; hd->secret = secret; *r_hd = hd; return 0; } /** * cdk_keydb_new: * @r_hd: handle to store the new keydb object * @type: type of the keyring * @data: data which depends on the keyring type * @count: length of the data * * Create a new keydb object **/ cdk_error_t cdk_keydb_new (cdk_keydb_hd_t * r_hd, int type, void *data, size_t count) { switch (type) { case CDK_DBTYPE_PK_KEYRING: case CDK_DBTYPE_SK_KEYRING: return cdk_keydb_new_from_file (r_hd, type == CDK_DBTYPE_SK_KEYRING, (const char *) data); case CDK_DBTYPE_DATA: return cdk_keydb_new_from_mem (r_hd, 0, data, count); case CDK_DBTYPE_STREAM: return cdk_keydb_new_from_stream (r_hd, 0, (cdk_stream_t) data); default: gnutls_assert (); return CDK_Inv_Mode; } gnutls_assert (); return CDK_Inv_Mode; } /** * cdk_keydb_free: * @hd: the keydb object * * Free the keydb object. **/ void cdk_keydb_free (cdk_keydb_hd_t hd) { if (!hd) return; if (hd->name) { cdk_free (hd->name); hd->name = NULL; } if (hd->fp && !hd->fp_ref) { cdk_stream_close (hd->fp); hd->fp = NULL; } hd->isopen = 0; hd->secret = 0; cdk_free (hd); } static cdk_error_t _cdk_keydb_open (cdk_keydb_hd_t hd, cdk_stream_t * ret_kr) { cdk_error_t rc; cdk_stream_t kr; if (!hd || !ret_kr) { gnutls_assert (); return CDK_Inv_Value; } rc = 0; if ((hd->type == CDK_DBTYPE_DATA || hd->type == CDK_DBTYPE_STREAM) && hd->fp) { kr = hd->fp; cdk_stream_seek (kr, 0); } else if (hd->type == CDK_DBTYPE_PK_KEYRING || hd->type == CDK_DBTYPE_SK_KEYRING) { rc = cdk_stream_open (hd->name, &kr); if (rc) goto leave; if (cdk_armor_filter_use (kr)) cdk_stream_set_armor_flag (kr, 0); } else { gnutls_assert (); return CDK_Inv_Mode; } leave: *ret_kr = kr; return rc; } static int find_by_keyid (cdk_kbnode_t knode, cdk_keydb_search_t ks) { cdk_kbnode_t node; u32 keyid[2]; for (node = knode; node; node = node->next) { if (node->pkt->pkttype == CDK_PKT_PUBLIC_KEY || node->pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY || node->pkt->pkttype == CDK_PKT_SECRET_KEY || node->pkt->pkttype == CDK_PKT_SECRET_SUBKEY) { _cdk_pkt_get_keyid (node->pkt, keyid); switch (ks->type) { case CDK_DBSEARCH_SHORT_KEYID: if (keyid[1] == ks->u.keyid[1]) return 1; break; case CDK_DBSEARCH_KEYID: if (KEYID_CMP (keyid, ks->u.keyid)) return 1; break; default: _cdk_log_debug ("find_by_keyid: invalid mode = %d\n", ks->type); return 0; } } } return 0; } static int find_by_fpr (cdk_kbnode_t knode, cdk_keydb_search_t ks) { cdk_kbnode_t node; byte fpr[KEY_FPR_LEN]; if (ks->type != CDK_DBSEARCH_FPR) return 0; for (node = knode; node; node = node->next) { if (node->pkt->pkttype == CDK_PKT_PUBLIC_KEY || node->pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY || node->pkt->pkttype == CDK_PKT_SECRET_KEY || node->pkt->pkttype == CDK_PKT_SECRET_SUBKEY) { _cdk_pkt_get_fingerprint (node->pkt, fpr); if (!memcmp (ks->u.fpr, fpr, KEY_FPR_LEN)) return 1; break; } } return 0; } static int find_by_pattern (cdk_kbnode_t knode, cdk_keydb_search_t ks) { cdk_kbnode_t node; size_t uidlen; char *name; for (node = knode; node; node = node->next) { if (node->pkt->pkttype != CDK_PKT_USER_ID) continue; if (node->pkt->pkt.user_id->attrib_img != NULL) continue; /* Skip attribute packets. */ uidlen = node->pkt->pkt.user_id->len; name = node->pkt->pkt.user_id->name; switch (ks->type) { case CDK_DBSEARCH_EXACT: if (name && (strlen (ks->u.pattern) == uidlen && !strncmp (ks->u.pattern, name, uidlen))) return 1; break; case CDK_DBSEARCH_SUBSTR: if (uidlen > 65536) break; if (name && strlen (ks->u.pattern) > uidlen) break; if (name && _cdk_memistr (name, uidlen, ks->u.pattern)) return 1; break; default: /* Invalid mode */ return 0; } } return 0; } static void keydb_cache_free (key_table_t cache) { key_table_t c2; while (cache) { c2 = cache->next; cache->offset = 0; cdk_free (cache); cache = c2; } } static key_table_t keydb_cache_find (cdk_keydb_search_t desc) { key_table_t cache = desc->cache; key_table_t t; for (t = cache; t; t = t->next) { switch (desc->type) { case CDK_DBSEARCH_SHORT_KEYID: case CDK_DBSEARCH_KEYID: if (KEYID_CMP (desc->u.keyid, desc->u.keyid)) return t; break; case CDK_DBSEARCH_EXACT: if (strlen (desc->u.pattern) == strlen (desc->u.pattern) && !strcmp (desc->u.pattern, desc->u.pattern)) return t; break; case CDK_DBSEARCH_SUBSTR: if (strstr (desc->u.pattern, desc->u.pattern)) return t; break; case CDK_DBSEARCH_FPR: if (!memcmp (desc->u.fpr, desc->u.fpr, KEY_FPR_LEN)) return t; break; } } return NULL; } static cdk_error_t keydb_cache_add (cdk_keydb_search_t dbs, off_t offset) { key_table_t k; if (dbs->ncache > KEYDB_CACHE_ENTRIES) return 0; /* FIXME: we should replace the last entry. */ k = cdk_calloc (1, sizeof *k); if (!k) { gnutls_assert (); return CDK_Out_Of_Core; } k->offset = offset; k->next = dbs->cache; dbs->cache = k; dbs->ncache++; _cdk_log_debug ("cache: add entry off=%d type=%d\n", (int) offset, (int) dbs->type); return 0; } static cdk_error_t idx_init (cdk_keydb_hd_t db, cdk_keydb_search_t dbs) { cdk_error_t ec, rc = 0; if (cdk_stream_get_length (db->fp) < 524288) { dbs->no_cache = 1; goto leave; } dbs->idx_name = keydb_idx_mkname (db->name); if (!dbs->idx_name) { rc = CDK_Out_Of_Core; goto leave; } ec = cdk_stream_open (dbs->idx_name, &dbs->idx); if (ec && !db->secret) { rc = keydb_idx_build (db->name); if (!rc) rc = cdk_stream_open (dbs->idx_name, &dbs->idx); if (!rc) { _cdk_log_debug ("create key index table\n"); } else { /* This is no real error, it just means we can't create the index at the given directory. maybe we've no write access. in this case, we simply disable the index. */ _cdk_log_debug ("disable key index table err=%d\n", rc); rc = 0; dbs->no_cache = 1; } } leave: return rc; } /** * cdk_keydb_search_start: * @st: search handle * @db: key database handle * @type: specifies the search type * @desc: description which depends on the type * * Create a new keydb search object. **/ cdk_error_t cdk_keydb_search_start (cdk_keydb_search_t * st, cdk_keydb_hd_t db, int type, void *desc) { u32 *keyid; char *p, tmp[3]; int i; cdk_error_t rc; if (!db) { gnutls_assert (); return CDK_Inv_Value; } if (type != CDK_DBSEARCH_NEXT && !desc) { gnutls_assert (); return CDK_Inv_Mode; } *st = cdk_calloc (1, sizeof (cdk_keydb_search_s)); if (!(*st)) { gnutls_assert (); return CDK_Out_Of_Core; } rc = idx_init (db, *st); if (rc != CDK_Success) { free (*st); gnutls_assert (); return rc; } (*st)->type = type; switch (type) { case CDK_DBSEARCH_EXACT: case CDK_DBSEARCH_SUBSTR: cdk_free ((*st)->u.pattern); (*st)->u.pattern = cdk_strdup (desc); if (!(*st)->u.pattern) { cdk_free (*st); gnutls_assert (); return CDK_Out_Of_Core; } break; case CDK_DBSEARCH_SHORT_KEYID: keyid = desc; (*st)->u.keyid[1] = keyid[0]; break; case CDK_DBSEARCH_KEYID: keyid = desc; (*st)->u.keyid[0] = keyid[0]; (*st)->u.keyid[1] = keyid[1]; break; case CDK_DBSEARCH_FPR: memcpy ((*st)->u.fpr, desc, KEY_FPR_LEN); break; case CDK_DBSEARCH_NEXT: break; case CDK_DBSEARCH_AUTO: /* Override the type with the actual db search type. */ (*st)->type = classify_data (desc, strlen (desc)); switch ((*st)->type) { case CDK_DBSEARCH_SUBSTR: case CDK_DBSEARCH_EXACT: cdk_free ((*st)->u.pattern); p = (*st)->u.pattern = cdk_strdup (desc); if (!p) { cdk_free (*st); gnutls_assert (); return CDK_Out_Of_Core; } break; case CDK_DBSEARCH_SHORT_KEYID: case CDK_DBSEARCH_KEYID: p = desc; if (!strncmp (p, "0x", 2)) p += 2; if (strlen (p) == 8) { (*st)->u.keyid[0] = 0; (*st)->u.keyid[1] = strtoul (p, NULL, 16); } else if (strlen (p) == 16) { (*st)->u.keyid[0] = strtoul (p, NULL, 16); (*st)->u.keyid[1] = strtoul (p + 8, NULL, 16); } else { /* Invalid key ID object. */ cdk_free (*st); gnutls_assert (); return CDK_Inv_Mode; } break; case CDK_DBSEARCH_FPR: p = desc; if (strlen (p) != 2 * KEY_FPR_LEN) { cdk_free (*st); gnutls_assert (); return CDK_Inv_Mode; } for (i = 0; i < KEY_FPR_LEN; i++) { tmp[0] = p[2 * i]; tmp[1] = p[2 * i + 1]; tmp[2] = 0x00; (*st)->u.fpr[i] = strtoul (tmp, NULL, 16); } break; } break; default: cdk_free (*st); _cdk_log_debug ("cdk_keydb_search_start: invalid mode = %d\n", type); gnutls_assert (); return CDK_Inv_Mode; } return 0; } static cdk_error_t keydb_pos_from_cache (cdk_keydb_hd_t hd, cdk_keydb_search_t ks, int *r_cache_hit, off_t * r_off) { key_table_t c; if (!hd || !r_cache_hit || !r_off) { gnutls_assert (); return CDK_Inv_Value; } /* Reset the values. */ *r_cache_hit = 0; *r_off = 0; c = keydb_cache_find (ks); if (c != NULL) { _cdk_log_debug ("cache: found entry in cache.\n"); *r_cache_hit = 1; *r_off = c->offset; return 0; } /* No index cache available so we just return here. */ if (!ks->idx) return 0; if (ks->idx) { if (ks->type == CDK_DBSEARCH_KEYID) { if (keydb_idx_search (ks->idx, ks->u.keyid, NULL, r_off)) { gnutls_assert (); return CDK_Error_No_Key; } _cdk_log_debug ("cache: found keyid entry in idx table.\n"); *r_cache_hit = 1; } else if (ks->type == CDK_DBSEARCH_FPR) { if (keydb_idx_search (ks->idx, NULL, ks->u.fpr, r_off)) { gnutls_assert (); return CDK_Error_No_Key; } _cdk_log_debug ("cache: found fpr entry in idx table.\n"); *r_cache_hit = 1; } } return 0; } void cdk_keydb_search_release (cdk_keydb_search_t st) { keydb_cache_free (st->cache); if (st->idx) cdk_stream_close (st->idx); if (!st) return; if (st->type == CDK_DBSEARCH_EXACT || st->type == CDK_DBSEARCH_SUBSTR) cdk_free (st->u.pattern); cdk_free (st); } /** * cdk_keydb_search: * @st: the search handle * @hd: the keydb object * @ret_key: kbnode object to store the key * * Search for a key in the given keyring. The search mode is handled * via @ks. If the key was found, @ret_key contains the key data. **/ cdk_error_t cdk_keydb_search (cdk_keydb_search_t st, cdk_keydb_hd_t hd, cdk_kbnode_t * ret_key) { cdk_stream_t kr; cdk_kbnode_t knode; cdk_error_t rc = 0; off_t pos = 0, off = 0; int key_found = 0, cache_hit = 0; if (!hd || !ret_key || !st) { gnutls_assert (); return CDK_Inv_Value; } *ret_key = NULL; kr = NULL; rc = _cdk_keydb_open (hd, &kr); if (rc) { gnutls_assert (); return rc; } if (!st->no_cache) { /* It is possible the index is not up-to-date and thus we do not find the requesed key. In this case, we reset cache hit and continue our normal search procedure. */ rc = keydb_pos_from_cache (hd, st, &cache_hit, &off); if (rc) cache_hit = 0; } knode = NULL; while (!key_found && !rc) { if (cache_hit && st->type != CDK_DBSEARCH_NEXT) cdk_stream_seek (kr, off); else if (st->type == CDK_DBSEARCH_NEXT) cdk_stream_seek (kr, st->off); pos = cdk_stream_tell (kr); rc = cdk_keydb_get_keyblock (kr, &knode); if (rc) { if (rc == CDK_EOF) break; else { gnutls_assert (); return rc; } } switch (st->type) { case CDK_DBSEARCH_SHORT_KEYID: case CDK_DBSEARCH_KEYID: key_found = find_by_keyid (knode, st); break; case CDK_DBSEARCH_FPR: key_found = find_by_fpr (knode, st); break; case CDK_DBSEARCH_EXACT: case CDK_DBSEARCH_SUBSTR: key_found = find_by_pattern (knode, st); break; case CDK_DBSEARCH_NEXT: st->off = cdk_stream_tell (kr); key_found = knode ? 1 : 0; break; } if (key_found) { if (!keydb_cache_find (st)) keydb_cache_add (st, pos); break; } cdk_kbnode_release (knode); knode = NULL; } if (key_found && rc == CDK_EOF) rc = 0; else if (rc == CDK_EOF && !key_found) { gnutls_assert (); rc = CDK_Error_No_Key; } *ret_key = key_found ? knode : NULL; return rc; } cdk_error_t cdk_keydb_get_bykeyid (cdk_keydb_hd_t hd, u32 * keyid, cdk_kbnode_t * ret_key) { cdk_error_t rc; cdk_keydb_search_t st; if (!hd || !keyid || !ret_key) { gnutls_assert (); return CDK_Inv_Value; } rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_KEYID, keyid); if (!rc) rc = cdk_keydb_search (st, hd, ret_key); cdk_keydb_search_release (st); return rc; } cdk_error_t cdk_keydb_get_byfpr (cdk_keydb_hd_t hd, const byte * fpr, cdk_kbnode_t * r_key) { cdk_error_t rc; cdk_keydb_search_t st; if (!hd || !fpr || !r_key) { gnutls_assert (); return CDK_Inv_Value; } rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_FPR, (byte *) fpr); if (!rc) rc = cdk_keydb_search (st, hd, r_key); cdk_keydb_search_release (st); return rc; } cdk_error_t cdk_keydb_get_bypattern (cdk_keydb_hd_t hd, const char *patt, cdk_kbnode_t * ret_key) { cdk_error_t rc; cdk_keydb_search_t st; if (!hd || !patt || !ret_key) { gnutls_assert (); return CDK_Inv_Value; } rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_SUBSTR, (char *) patt); if (!rc) rc = cdk_keydb_search (st, hd, ret_key); if (rc) gnutls_assert (); cdk_keydb_search_release (st); return rc; } static int keydb_check_key (cdk_packet_t pkt) { cdk_pkt_pubkey_t pk; int is_sk, valid; if (pkt->pkttype == CDK_PKT_PUBLIC_KEY || pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY) { pk = pkt->pkt.public_key; is_sk = 0; } else if (pkt->pkttype == CDK_PKT_SECRET_KEY || pkt->pkttype == CDK_PKT_SECRET_SUBKEY) { pk = pkt->pkt.secret_key->pk; is_sk = 1; } else /* No key object. */ return 0; valid = !pk->is_revoked && !pk->has_expired; if (is_sk) return valid; return valid && !pk->is_invalid; } /* Find the first kbnode with the requested packet type that represents a valid key. */ static cdk_kbnode_t kbnode_find_valid (cdk_kbnode_t root, cdk_packet_type_t pkttype) { cdk_kbnode_t n; for (n = root; n; n = n->next) { if (n->pkt->pkttype != pkttype) continue; if (keydb_check_key (n->pkt)) return n; } return NULL; } static cdk_kbnode_t keydb_find_byusage (cdk_kbnode_t root, int req_usage, int is_pk) { cdk_kbnode_t node, key; int req_type; long timestamp; req_type = is_pk ? CDK_PKT_PUBLIC_KEY : CDK_PKT_SECRET_KEY; if (!req_usage) return kbnode_find_valid (root, req_type); node = cdk_kbnode_find (root, req_type); if (node && !keydb_check_key (node->pkt)) return NULL; key = NULL; timestamp = 0; /* We iteratre over the all nodes and search for keys or subkeys which match the usage and which are not invalid. A timestamp is used to figure out the newest valid key. */ for (node = root; node; node = node->next) { if (is_pk && (node->pkt->pkttype == CDK_PKT_PUBLIC_KEY || node->pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY) && keydb_check_key (node->pkt) && (node->pkt->pkt.public_key->pubkey_usage & req_usage)) { if (node->pkt->pkt.public_key->timestamp > timestamp) key = node; } if (!is_pk && (node->pkt->pkttype == CDK_PKT_SECRET_KEY || node->pkt->pkttype == CDK_PKT_SECRET_SUBKEY) && keydb_check_key (node->pkt) && (node->pkt->pkt.secret_key->pk->pubkey_usage & req_usage)) { if (node->pkt->pkt.secret_key->pk->timestamp > timestamp) key = node; } } return key; } static cdk_kbnode_t keydb_find_bykeyid (cdk_kbnode_t root, const u32 * keyid, int search_mode) { cdk_kbnode_t node; u32 kid[2]; for (node = root; node; node = node->next) { if (!_cdk_pkt_get_keyid (node->pkt, kid)) continue; if (search_mode == CDK_DBSEARCH_SHORT_KEYID && kid[1] == keyid[1]) return node; else if (kid[0] == keyid[0] && kid[1] == keyid[1]) return node; } return NULL; } cdk_error_t _cdk_keydb_get_sk_byusage (cdk_keydb_hd_t hd, const char *name, cdk_seckey_t * ret_sk, int usage) { cdk_kbnode_t knode = NULL; cdk_kbnode_t node, sk_node, pk_node; cdk_pkt_seckey_t sk; cdk_error_t rc; const char *s; int pkttype; cdk_keydb_search_t st; if (!ret_sk || !usage) { gnutls_assert (); return CDK_Inv_Value; } if (!hd) { gnutls_assert (); return CDK_Error_No_Keyring; } *ret_sk = NULL; rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_AUTO, (char *) name); if (rc) { gnutls_assert (); return rc; } rc = cdk_keydb_search (st, hd, &knode); if (rc) { gnutls_assert (); return rc; } cdk_keydb_search_release (st); sk_node = keydb_find_byusage (knode, usage, 0); if (!sk_node) { cdk_kbnode_release (knode); gnutls_assert (); return CDK_Unusable_Key; } /* We clone the node with the secret key to avoid that the packet will be released. */ _cdk_kbnode_clone (sk_node); sk = sk_node->pkt->pkt.secret_key; for (node = knode; node; node = node->next) { if (node->pkt->pkttype == CDK_PKT_USER_ID) { s = node->pkt->pkt.user_id->name; if (sk && !sk->pk->uid && _cdk_memistr (s, strlen (s), name)) { _cdk_copy_userid (&sk->pk->uid, node->pkt->pkt.user_id); break; } } } /* To find the self signature, we need the primary public key because the selected secret key might be different from the primary key. */ pk_node = cdk_kbnode_find (knode, CDK_PKT_SECRET_KEY); if (!pk_node) { cdk_kbnode_release (knode); gnutls_assert (); return CDK_Unusable_Key; } node = find_selfsig_node (knode, pk_node->pkt->pkt.secret_key->pk); if (sk->pk->uid && node) _cdk_copy_signature (&sk->pk->uid->selfsig, node->pkt->pkt.signature); /* We only release the outer packet. */ _cdk_pkt_detach_free (sk_node->pkt, &pkttype, (void *) &sk); cdk_kbnode_release (knode); *ret_sk = sk; return rc; } cdk_error_t _cdk_keydb_get_pk_byusage (cdk_keydb_hd_t hd, const char *name, cdk_pubkey_t * ret_pk, int usage) { cdk_kbnode_t knode, node, pk_node; cdk_pkt_pubkey_t pk; const char *s; cdk_error_t rc; cdk_keydb_search_t st; if (!ret_pk || !usage) { gnutls_assert (); return CDK_Inv_Value; } if (!hd) { gnutls_assert (); return CDK_Error_No_Keyring; } *ret_pk = NULL; rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_AUTO, (char *) name); if (!rc) rc = cdk_keydb_search (st, hd, &knode); if (rc) { gnutls_assert (); return rc; } cdk_keydb_search_release (st); node = keydb_find_byusage (knode, usage, 1); if (!node) { cdk_kbnode_release (knode); gnutls_assert (); return CDK_Unusable_Key; } pk = NULL; _cdk_copy_pubkey (&pk, node->pkt->pkt.public_key); for (node = knode; node; node = node->next) { if (node->pkt->pkttype == CDK_PKT_USER_ID) { s = node->pkt->pkt.user_id->name; if (pk && !pk->uid && _cdk_memistr (s, strlen (s), name)) { _cdk_copy_userid (&pk->uid, node->pkt->pkt.user_id); break; } } } /* Same as in the sk code, the selected key can be a sub key and thus we need the primary key to find the self sig. */ pk_node = cdk_kbnode_find (knode, CDK_PKT_PUBLIC_KEY); if (!pk_node) { cdk_kbnode_release (knode); gnutls_assert (); return CDK_Unusable_Key; } node = find_selfsig_node (knode, pk_node->pkt->pkt.public_key); if (pk->uid && node) _cdk_copy_signature (&pk->uid->selfsig, node->pkt->pkt.signature); cdk_kbnode_release (knode); *ret_pk = pk; return rc; } /** * cdk_keydb_get_pk: * @hd: key db handle * @keyid: keyid of the key * @r_pk: the allocated public key * * Perform a key database search by keyid and return the raw public * key without any signatures or user id's. **/ cdk_error_t cdk_keydb_get_pk (cdk_keydb_hd_t hd, u32 * keyid, cdk_pubkey_t * r_pk) { cdk_kbnode_t knode = NULL, node; cdk_pubkey_t pk; cdk_error_t rc; size_t s_type; int pkttype; cdk_keydb_search_t st; if (!keyid || !r_pk) { gnutls_assert (); return CDK_Inv_Value; } if (!hd) { gnutls_assert (); return CDK_Error_No_Keyring; } *r_pk = NULL; s_type = !keyid[0] ? CDK_DBSEARCH_SHORT_KEYID : CDK_DBSEARCH_KEYID; rc = cdk_keydb_search_start (&st, hd, s_type, keyid); if (rc) { gnutls_assert (); return rc; } rc = cdk_keydb_search (st, hd, &knode); cdk_keydb_search_release (st); if (rc) { gnutls_assert (); return rc; } node = keydb_find_bykeyid (knode, keyid, s_type); if (!node) { cdk_kbnode_release (knode); gnutls_assert (); return CDK_Error_No_Key; } /* See comment in cdk_keydb_get_sk() */ _cdk_pkt_detach_free (node->pkt, &pkttype, (void *) &pk); *r_pk = pk; _cdk_kbnode_clone (node); cdk_kbnode_release (knode); return rc; } /** * cdk_keydb_get_sk: * @hd: key db handle * @keyid: the keyid of the key * @ret_sk: the allocated secret key * * Perform a key database search by keyid and return * only the raw secret key without the additional nodes, * like the user id or the signatures. **/ cdk_error_t cdk_keydb_get_sk (cdk_keydb_hd_t hd, u32 * keyid, cdk_seckey_t * ret_sk) { cdk_kbnode_t snode, node; cdk_seckey_t sk; cdk_error_t rc; int pkttype; if (!keyid || !ret_sk) { gnutls_assert (); return CDK_Inv_Value; } if (!hd) { gnutls_assert (); return CDK_Error_No_Keyring; } *ret_sk = NULL; rc = cdk_keydb_get_bykeyid (hd, keyid, &snode); if (rc) { gnutls_assert (); return rc; } node = keydb_find_bykeyid (snode, keyid, CDK_DBSEARCH_KEYID); if (!node) { cdk_kbnode_release (snode); gnutls_assert (); return CDK_Error_No_Key; } /* We need to release the packet itself but not its contents and thus we detach the openpgp packet and release the structure. */ _cdk_pkt_detach_free (node->pkt, &pkttype, (void *) &sk); _cdk_kbnode_clone (node); cdk_kbnode_release (snode); *ret_sk = sk; return 0; } static int is_selfsig (cdk_kbnode_t node, const u32 * keyid) { cdk_pkt_signature_t sig; if (node->pkt->pkttype != CDK_PKT_SIGNATURE) return 0; sig = node->pkt->pkt.signature; if ((sig->sig_class >= 0x10 && sig->sig_class <= 0x13) && sig->keyid[0] == keyid[0] && sig->keyid[1] == keyid[1]) return 1; return 0; } /* Find the newest self signature for the public key @pk and return the signature node. */ static cdk_kbnode_t find_selfsig_node (cdk_kbnode_t key, cdk_pkt_pubkey_t pk) { cdk_kbnode_t n, sig; unsigned int ts; u32 keyid[2]; cdk_pk_get_keyid (pk, keyid); sig = NULL; ts = 0; for (n = key; n; n = n->next) { if (is_selfsig (n, keyid) && n->pkt->pkt.signature->timestamp > ts) { ts = n->pkt->pkt.signature->timestamp; sig = n; } } return sig; } static unsigned int key_usage_to_cdk_usage (unsigned int usage) { unsigned key_usage = 0; if (usage & 0x01) /* cert + sign data */ key_usage |= CDK_KEY_USG_CERT_SIGN; if (usage & 0x02) /* cert + sign data */ key_usage |= CDK_KEY_USG_DATA_SIGN; if (usage & 0x04) /* encrypt comm. + storage */ key_usage |= CDK_KEY_USG_COMM_ENCR; if (usage & 0x08) /* encrypt comm. + storage */ key_usage |= CDK_KEY_USG_STORAGE_ENCR; if (usage & 0x10) /* encrypt comm. + storage */ key_usage |= CDK_KEY_USG_SPLIT_KEY; if (usage & 0x20) key_usage |= CDK_KEY_USG_AUTH; if (usage & 0x80) /* encrypt comm. + storage */ key_usage |= CDK_KEY_USG_SHARED_KEY; return key_usage; } static cdk_error_t keydb_merge_selfsig (cdk_kbnode_t key, u32 * keyid) { cdk_kbnode_t node, kbnode, unode; cdk_subpkt_t s = NULL; cdk_pkt_signature_t sig = NULL; cdk_pkt_userid_t uid = NULL; const byte *symalg = NULL, *hashalg = NULL, *compalg = NULL; size_t nsymalg = 0, nhashalg = 0, ncompalg = 0, n = 0; size_t key_expire = 0; if (!key) { gnutls_assert (); return CDK_Inv_Value; } for (node = key; node; node = node->next) { if (!is_selfsig (node, keyid)) continue; unode = cdk_kbnode_find_prev (key, node, CDK_PKT_USER_ID); if (!unode) { gnutls_assert (); return CDK_Error_No_Key; } uid = unode->pkt->pkt.user_id; sig = node->pkt->pkt.signature; s = cdk_subpkt_find (sig->hashed, CDK_SIGSUBPKT_PRIMARY_UID); if (s) uid->is_primary = 1; s = cdk_subpkt_find (sig->hashed, CDK_SIGSUBPKT_FEATURES); if (s && s->size == 1 && s->d[0] & 0x01) uid->mdc_feature = 1; s = cdk_subpkt_find (sig->hashed, CDK_SIGSUBPKT_KEY_EXPIRE); if (s && s->size == 4) key_expire = _cdk_buftou32 (s->d); s = cdk_subpkt_find (sig->hashed, CDK_SIGSUBPKT_PREFS_SYM); if (s) { symalg = s->d; nsymalg = s->size; n += s->size + 1; } s = cdk_subpkt_find (sig->hashed, CDK_SIGSUBPKT_PREFS_HASH); if (s) { hashalg = s->d; nhashalg = s->size; n += s->size + 1; } s = cdk_subpkt_find (sig->hashed, CDK_SIGSUBPKT_PREFS_ZIP); if (s) { compalg = s->d; ncompalg = s->size; n += s->size + 1; } if (uid->prefs != NULL) cdk_free (uid->prefs); if (!n || !hashalg || !compalg || !symalg) uid->prefs = NULL; else { uid->prefs = cdk_calloc (1, sizeof (*uid->prefs) * (n + 1)); if (!uid->prefs) { gnutls_assert (); return CDK_Out_Of_Core; } n = 0; for (; nsymalg; nsymalg--, n++) { uid->prefs[n].type = CDK_PREFTYPE_SYM; uid->prefs[n].value = *symalg++; } for (; nhashalg; nhashalg--, n++) { uid->prefs[n].type = CDK_PREFTYPE_HASH; uid->prefs[n].value = *hashalg++; } for (; ncompalg; ncompalg--, n++) { uid->prefs[n].type = CDK_PREFTYPE_ZIP; uid->prefs[n].value = *compalg++; } uid->prefs[n].type = CDK_PREFTYPE_NONE; /* end of list marker */ uid->prefs[n].value = 0; uid->prefs_size = n; } } /* Now we add the extracted information to the primary key. */ kbnode = cdk_kbnode_find (key, CDK_PKT_PUBLIC_KEY); if (kbnode) { cdk_pkt_pubkey_t pk = kbnode->pkt->pkt.public_key; if (uid && uid->prefs && n) { if (pk->prefs != NULL) cdk_free (pk->prefs); pk->prefs = _cdk_copy_prefs (uid->prefs); pk->prefs_size = n; } if (key_expire) { pk->expiredate = pk->timestamp + key_expire; pk->has_expired = pk->expiredate > (u32) time (NULL) ? 0 : 1; } pk->is_invalid = 0; } return 0; } static cdk_error_t keydb_parse_allsigs (cdk_kbnode_t knode, cdk_keydb_hd_t hd, int check) { cdk_kbnode_t node, kb; cdk_pkt_signature_t sig; cdk_pkt_pubkey_t pk; cdk_subpkt_t s = NULL; u32 expiredate = 0, curtime = (u32) time (NULL); u32 keyid[2]; if (!knode) { gnutls_assert (); return CDK_Inv_Value; } if (check && !hd) { gnutls_assert (); return CDK_Inv_Mode; } kb = cdk_kbnode_find (knode, CDK_PKT_SECRET_KEY); if (kb) return 0; /* Reset */ for (node = knode; node; node = node->next) { if (node->pkt->pkttype == CDK_PKT_USER_ID) node->pkt->pkt.user_id->is_revoked = 0; else if (node->pkt->pkttype == CDK_PKT_PUBLIC_KEY || node->pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY) node->pkt->pkt.public_key->is_revoked = 0; } kb = cdk_kbnode_find (knode, CDK_PKT_PUBLIC_KEY); if (!kb) { gnutls_assert (); return CDK_Wrong_Format; } cdk_pk_get_keyid (kb->pkt->pkt.public_key, keyid); for (node = knode; node; node = node->next) { if (node->pkt->pkttype == CDK_PKT_SIGNATURE) { sig = node->pkt->pkt.signature; /* Revocation certificates for primary keys */ if (sig->sig_class == 0x20) { kb = cdk_kbnode_find_prev (knode, node, CDK_PKT_PUBLIC_KEY); if (kb) { kb->pkt->pkt.public_key->is_revoked = 1; if (check) _cdk_pk_check_sig (hd, kb, node, NULL, NULL); } else { gnutls_assert (); return CDK_Error_No_Key; } } /* Revocation certificates for subkeys */ else if (sig->sig_class == 0x28) { kb = cdk_kbnode_find_prev (knode, node, CDK_PKT_PUBLIC_SUBKEY); if (kb) { kb->pkt->pkt.public_key->is_revoked = 1; if (check) _cdk_pk_check_sig (hd, kb, node, NULL, NULL); } else { gnutls_assert (); return CDK_Error_No_Key; } } /* Revocation certifcates for user ID's */ else if (sig->sig_class == 0x30) { if (sig->keyid[0] != keyid[0] || sig->keyid[1] != keyid[1]) continue; /* revokes an earlier signature, no userID. */ kb = cdk_kbnode_find_prev (knode, node, CDK_PKT_USER_ID); if (kb) { kb->pkt->pkt.user_id->is_revoked = 1; if (check) _cdk_pk_check_sig (hd, kb, node, NULL, NULL); } else { gnutls_assert (); return CDK_Error_No_Key; } } /* Direct certificates for primary keys */ else if (sig->sig_class == 0x1F) { kb = cdk_kbnode_find_prev (knode, node, CDK_PKT_PUBLIC_KEY); if (kb) { pk = kb->pkt->pkt.public_key; pk->is_invalid = 0; s = cdk_subpkt_find (node->pkt->pkt.signature->hashed, CDK_SIGSUBPKT_KEY_EXPIRE); if (s) { expiredate = _cdk_buftou32 (s->d); pk->expiredate = pk->timestamp + expiredate; pk->has_expired = pk->expiredate > curtime ? 0 : 1; } if (check) _cdk_pk_check_sig (hd, kb, node, NULL, NULL); } else { gnutls_assert (); return CDK_Error_No_Key; } } /* Direct certificates for subkeys */ else if (sig->sig_class == 0x18) { kb = cdk_kbnode_find_prev (knode, node, CDK_PKT_PUBLIC_SUBKEY); if (kb) { pk = kb->pkt->pkt.public_key; pk->is_invalid = 0; s = cdk_subpkt_find (node->pkt->pkt.signature->hashed, CDK_SIGSUBPKT_KEY_EXPIRE); if (s) { expiredate = _cdk_buftou32 (s->d); pk->expiredate = pk->timestamp + expiredate; pk->has_expired = pk->expiredate > curtime ? 0 : 1; } if (check) _cdk_pk_check_sig (hd, kb, node, NULL, NULL); } else { gnutls_assert (); return CDK_Error_No_Key; } } } } node = cdk_kbnode_find (knode, CDK_PKT_PUBLIC_KEY); if (node && node->pkt->pkt.public_key->version == 3) { /* v3 public keys have no additonal signatures for the key directly. we say the key is valid when we have at least a self signature. */ pk = node->pkt->pkt.public_key; for (node = knode; node; node = node->next) { if (is_selfsig (node, keyid)) { pk->is_invalid = 0; break; } } } if (node && (node->pkt->pkt.public_key->is_revoked || node->pkt->pkt.public_key->has_expired)) { /* If the primary key has been revoked, mark all subkeys as invalid because without a primary key they are not useable */ for (node = knode; node; node = node->next) { if (node->pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY) node->pkt->pkt.public_key->is_invalid = 1; } } return 0; } static void add_key_usage (cdk_kbnode_t knode, u32 keyid[2], unsigned int usage) { cdk_kbnode_t p, ctx; cdk_packet_t pkt; ctx = NULL; while ((p = cdk_kbnode_walk (knode, &ctx, 0))) { pkt = cdk_kbnode_get_packet (p); if ((pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY || pkt->pkttype == CDK_PKT_PUBLIC_KEY) && pkt->pkt.public_key->keyid[0] == keyid[0] && pkt->pkt.public_key->keyid[1] == keyid[1]) { pkt->pkt.public_key->pubkey_usage = usage; return; } } return; } cdk_error_t cdk_keydb_get_keyblock (cdk_stream_t inp, cdk_kbnode_t * r_knode) { cdk_packet_t pkt; cdk_kbnode_t knode, node; cdk_desig_revoker_t revkeys; cdk_error_t rc; u32 keyid[2], main_keyid[2]; off_t old_off; int key_seen, got_key; if (!inp || !r_knode) { gnutls_assert (); return CDK_Inv_Value; } /* Reset all values. */ keyid[0] = keyid[1] = 0; main_keyid[0] = main_keyid[1] = 0; revkeys = NULL; knode = NULL; key_seen = got_key = 0; *r_knode = NULL; rc = CDK_EOF; while (!cdk_stream_eof (inp)) { cdk_pkt_new (&pkt); old_off = cdk_stream_tell (inp); rc = cdk_pkt_read (inp, pkt); if (rc) { cdk_pkt_release (pkt); if (rc == CDK_EOF) break; else { /* Release all packets we reached so far. */ _cdk_log_debug ("keydb_get_keyblock: error %d\n", rc); cdk_kbnode_release (knode); gnutls_assert (); return rc; } } if (pkt->pkttype == CDK_PKT_PUBLIC_KEY || pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY || pkt->pkttype == CDK_PKT_SECRET_KEY || pkt->pkttype == CDK_PKT_SECRET_SUBKEY) { if (key_seen && (pkt->pkttype == CDK_PKT_PUBLIC_KEY || pkt->pkttype == CDK_PKT_SECRET_KEY)) { /* The next key starts here so set the file pointer and leave the loop. */ cdk_stream_seek (inp, old_off); cdk_pkt_release (pkt); break; } if (pkt->pkttype == CDK_PKT_PUBLIC_KEY || pkt->pkttype == CDK_PKT_SECRET_KEY) { _cdk_pkt_get_keyid (pkt, main_keyid); key_seen = 1; } else if (pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY || pkt->pkttype == CDK_PKT_SECRET_SUBKEY) { if (pkt->pkttype == CDK_PKT_PUBLIC_SUBKEY) { pkt->pkt.public_key->main_keyid[0] = main_keyid[0]; pkt->pkt.public_key->main_keyid[1] = main_keyid[1]; } else { pkt->pkt.secret_key->main_keyid[0] = main_keyid[0]; pkt->pkt.secret_key->main_keyid[1] = main_keyid[1]; } } /* We save this for the signature */ _cdk_pkt_get_keyid (pkt, keyid); got_key = 1; } else if (pkt->pkttype == CDK_PKT_USER_ID) ; else if (pkt->pkttype == CDK_PKT_SIGNATURE) { cdk_subpkt_t s; pkt->pkt.signature->key[0] = keyid[0]; pkt->pkt.signature->key[1] = keyid[1]; if (pkt->pkt.signature->sig_class == 0x1F && pkt->pkt.signature->revkeys) revkeys = pkt->pkt.signature->revkeys; s = cdk_subpkt_find (pkt->pkt.signature->hashed, CDK_SIGSUBPKT_KEY_FLAGS); if (s) { unsigned int key_usage = key_usage_to_cdk_usage (s->d[0]); add_key_usage (knode, pkt->pkt.signature->key, key_usage); } } node = cdk_kbnode_new (pkt); if (!knode) knode = node; else _cdk_kbnode_add (knode, node); } if (got_key) { keydb_merge_selfsig (knode, main_keyid); rc = keydb_parse_allsigs (knode, NULL, 0); if (revkeys) { node = cdk_kbnode_find (knode, CDK_PKT_PUBLIC_KEY); if (node) node->pkt->pkt.public_key->revkeys = revkeys; } } else cdk_kbnode_release (knode); *r_knode = got_key ? knode : NULL; /* It is possible that we are in an EOF condition after we successfully read a keyblock. For example if the requested key is the last in the file. */ if (rc == CDK_EOF && got_key) rc = 0; return rc; } /* Return the type of the given data. In case it cannot be classified, a substring search will be performed. */ static int classify_data (const byte * buf, size_t len) { int type; unsigned int i; if (buf[0] == '0' && (buf[1] == 'x' || buf[1] == 'X')) { /* Skip hex prefix. */ buf += 2; len -= 2; } /* The length of the data does not match either a keyid or a fingerprint. */ if (len != 8 && len != 16 && len != 40) return CDK_DBSEARCH_SUBSTR; for (i = 0; i < len; i++) { if (!isxdigit (buf[i])) return CDK_DBSEARCH_SUBSTR; } if (i != len) return CDK_DBSEARCH_SUBSTR; switch (len) { case 8: type = CDK_DBSEARCH_SHORT_KEYID; break; case 16: type = CDK_DBSEARCH_KEYID; break; case 40: type = CDK_DBSEARCH_FPR; break; default: type = CDK_DBSEARCH_SUBSTR; break; } return type; } /** * cdk_keydb_export: * @hd: the keydb handle * @out: the output stream * @remusr: the list of key pattern to export * * Export a list of keys to the given output stream. * Use string list with names for pattering searching. * This procedure strips local signatures. **/ cdk_error_t cdk_keydb_export (cdk_keydb_hd_t hd, cdk_stream_t out, cdk_strlist_t remusr) { cdk_kbnode_t knode, node; cdk_strlist_t r; cdk_error_t rc; int old_ctb; cdk_keydb_search_t st; for (r = remusr; r; r = r->next) { rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_AUTO, r->d); if (rc) { gnutls_assert (); return rc; } rc = cdk_keydb_search (st, hd, &knode); cdk_keydb_search_release (st); if (rc) { gnutls_assert (); return rc; } node = cdk_kbnode_find (knode, CDK_PKT_PUBLIC_KEY); if (!node) { gnutls_assert (); return CDK_Error_No_Key; } /* If the key is a version 3 key, use the old packet format for the output. */ if (node->pkt->pkt.public_key->version == 3) old_ctb = 1; else old_ctb = 0; for (node = knode; node; node = node->next) { /* No specified format; skip them */ if (node->pkt->pkttype == CDK_PKT_RING_TRUST) continue; /* We never export local signed signatures */ if (node->pkt->pkttype == CDK_PKT_SIGNATURE && !node->pkt->pkt.signature->flags.exportable) continue; /* Filter out invalid signatures */ if (node->pkt->pkttype == CDK_PKT_SIGNATURE && (!KEY_CAN_SIGN (node->pkt->pkt.signature->pubkey_algo))) continue; /* Adjust the ctb flag if needed. */ node->pkt->old_ctb = old_ctb; rc = cdk_pkt_write (out, node->pkt); if (rc) { cdk_kbnode_release (knode); gnutls_assert (); return rc; } } cdk_kbnode_release (knode); knode = NULL; } return 0; } static cdk_packet_t find_key_packet (cdk_kbnode_t knode, int *r_is_sk) { cdk_packet_t pkt; pkt = cdk_kbnode_find_packet (knode, CDK_PKT_PUBLIC_KEY); if (!pkt) { pkt = cdk_kbnode_find_packet (knode, CDK_PKT_SECRET_KEY); if (r_is_sk) *r_is_sk = pkt ? 1 : 0; } return pkt; } /* Return 1 if the is allowd in a key node. */ static int is_key_node (cdk_kbnode_t node) { switch (node->pkt->pkttype) { case CDK_PKT_SIGNATURE: case CDK_PKT_SECRET_KEY: case CDK_PKT_PUBLIC_KEY: case CDK_PKT_SECRET_SUBKEY: case CDK_PKT_PUBLIC_SUBKEY: case CDK_PKT_USER_ID: case CDK_PKT_ATTRIBUTE: return 1; default: return 0; } return 0; } cdk_error_t cdk_keydb_import (cdk_keydb_hd_t hd, cdk_kbnode_t knode) { cdk_kbnode_t node, chk; cdk_packet_t pkt; cdk_stream_t out; cdk_error_t rc; u32 keyid[2]; if (!hd || !knode) { gnutls_assert (); return CDK_Inv_Value; } pkt = find_key_packet (knode, NULL); if (!pkt) { gnutls_assert (); return CDK_Inv_Packet; } _cdk_pkt_get_keyid (pkt, keyid); chk = NULL; cdk_keydb_get_bykeyid (hd, keyid, &chk); if (chk) { /* FIXME: search for new signatures */ cdk_kbnode_release (chk); return 0; } /* We append data to the stream so we need to close the stream here to re-open it later. */ if (hd->fp) { cdk_stream_close (hd->fp); hd->fp = NULL; } rc = _cdk_stream_append (hd->name, &out); if (rc) { gnutls_assert (); return rc; } for (node = knode; node; node = node->next) { if (node->pkt->pkttype == CDK_PKT_RING_TRUST) continue; /* No uniformed syntax for this packet */ if (node->pkt->pkttype == CDK_PKT_SIGNATURE && !node->pkt->pkt.signature->flags.exportable) { _cdk_log_debug ("key db import: skip local signature\n"); continue; } if (!is_key_node (node)) { _cdk_log_debug ("key db import: skip invalid node of type %d\n", node->pkt->pkttype); continue; } rc = cdk_pkt_write (out, node->pkt); if (rc) { cdk_stream_close (out); gnutls_assert (); return rc; } } cdk_stream_close (out); hd->stats.new_keys++; return 0; } cdk_error_t _cdk_keydb_check_userid (cdk_keydb_hd_t hd, u32 * keyid, const char *id) { cdk_kbnode_t knode = NULL, unode = NULL; cdk_error_t rc; int check; cdk_keydb_search_t st; if (!hd) { gnutls_assert (); return CDK_Inv_Value; } rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_KEYID, keyid); if (rc) { gnutls_assert (); return rc; } rc = cdk_keydb_search (st, hd, &knode); cdk_keydb_search_release (st); if (rc) { gnutls_assert (); return rc; } rc = cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_EXACT, (char *) id); if (!rc) { rc = cdk_keydb_search (st, hd, &unode); cdk_keydb_search_release (st); } if (rc) { cdk_kbnode_release (knode); gnutls_assert (); return rc; } check = 0; cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_KEYID, keyid); if (unode && find_by_keyid (unode, st)) check++; cdk_keydb_search_release (st); cdk_kbnode_release (unode); cdk_keydb_search_start (&st, hd, CDK_DBSEARCH_EXACT, (char *) id); if (knode && find_by_pattern (knode, st)) check++; cdk_keydb_search_release (st); cdk_kbnode_release (knode); return check == 2 ? 0 : CDK_Inv_Value; } /** * cdk_keydb_check_sk: * @hd: the key db handle * @keyid: the 64-bit keyid * * Check if a secret key with the given key ID is available * in the key database. **/ cdk_error_t cdk_keydb_check_sk (cdk_keydb_hd_t hd, u32 * keyid) { cdk_stream_t db; cdk_packet_t pkt; cdk_error_t rc; u32 kid[2]; if (!hd || !keyid) { gnutls_assert (); return CDK_Inv_Value; } if (!hd->secret) { gnutls_assert (); return CDK_Inv_Mode; } rc = _cdk_keydb_open (hd, &db); if (rc) { gnutls_assert (); return rc; } cdk_pkt_new (&pkt); while (!cdk_pkt_read (db, pkt)) { if (pkt->pkttype != CDK_PKT_SECRET_KEY && pkt->pkttype != CDK_PKT_SECRET_SUBKEY) { cdk_pkt_free (pkt); continue; } cdk_sk_get_keyid (pkt->pkt.secret_key, kid); if (KEYID_CMP (kid, keyid)) { cdk_pkt_release (pkt); return 0; } cdk_pkt_free (pkt); } cdk_pkt_release (pkt); gnutls_assert (); return CDK_Error_No_Key; } /** * cdk_listkey_start: * @r_ctx: pointer to store the new context * @db: the key database handle * @patt: string pattern * @fpatt: recipients from a stringlist to show * * Prepare a key listing with the given parameters. Two modes are supported. * The first mode uses string pattern to determine if the key should be * returned or not. The other mode uses a string list to request the key * which should be listed. **/ cdk_error_t cdk_listkey_start (cdk_listkey_t * r_ctx, cdk_keydb_hd_t db, const char *patt, cdk_strlist_t fpatt) { cdk_listkey_t ctx; cdk_stream_t inp; cdk_error_t rc; if (!r_ctx || !db) { gnutls_assert (); return CDK_Inv_Value; } if ((patt && fpatt) || (!patt && !fpatt)) { gnutls_assert (); return CDK_Inv_Mode; } rc = _cdk_keydb_open (db, &inp); if (rc) { gnutls_assert (); return rc; } ctx = cdk_calloc (1, sizeof *ctx); if (!ctx) { gnutls_assert (); return CDK_Out_Of_Core; } ctx->db = db; ctx->inp = inp; if (patt) { ctx->u.patt = cdk_strdup (patt); if (!ctx->u.patt) { gnutls_assert (); return CDK_Out_Of_Core; } } else if (fpatt) { cdk_strlist_t l; for (l = fpatt; l; l = l->next) cdk_strlist_add (&ctx->u.fpatt, l->d); } ctx->type = patt ? 1 : 0; ctx->init = 1; *r_ctx = ctx; return 0; } /** * cdk_listkey_close: * @ctx: the list key context * * Free the list key context. **/ void cdk_listkey_close (cdk_listkey_t ctx) { if (!ctx) return; if (ctx->type) cdk_free (ctx->u.patt); else cdk_strlist_free (ctx->u.fpatt); cdk_free (ctx); } /** * cdk_listkey_next: * @ctx: list key context * @r_key: the pointer to the new key node object * * Retrieve the next key from the pattern of the key list context. **/ cdk_error_t cdk_listkey_next (cdk_listkey_t ctx, cdk_kbnode_t * ret_key) { if (!ctx || !ret_key) { gnutls_assert (); return CDK_Inv_Value; } if (!ctx->init) { gnutls_assert (); return CDK_Inv_Mode; } if (ctx->type && ctx->u.patt[0] == '*') return cdk_keydb_get_keyblock (ctx->inp, ret_key); else if (ctx->type) { cdk_kbnode_t node; struct cdk_keydb_search_s ks; cdk_error_t rc; for (;;) { rc = cdk_keydb_get_keyblock (ctx->inp, &node); if (rc) { gnutls_assert (); return rc; } memset (&ks, 0, sizeof (ks)); ks.type = CDK_DBSEARCH_SUBSTR; ks.u.pattern = ctx->u.patt; if (find_by_pattern (node, &ks)) { *ret_key = node; return 0; } cdk_kbnode_release (node); node = NULL; } } else { if (!ctx->t) ctx->t = ctx->u.fpatt; else if (ctx->t->next) ctx->t = ctx->t->next; else return CDK_EOF; return cdk_keydb_get_bypattern (ctx->db, ctx->t->d, ret_key); } gnutls_assert (); return CDK_General_Error; } int _cdk_keydb_is_secret (cdk_keydb_hd_t db) { return db->secret; }