--- trunk/Src/wptKeyCache.cpp 2006/01/09 09:15:29 133 +++ trunk/Src/wptKeyCache.cpp 2006/06/27 10:16:41 234 @@ -1,14 +1,14 @@ /* wptKeyCache.cpp- Caching for the pub- and the secring * Copyright (C) 2001-2006 Timo Schulz * - * This file is part of MyGPGME. + * This file is part of WinPT. * - * MyGPGME is free software; you can redistribute it and/or modify + * WinPT is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * - * MyGPGME is distributed in the hope that it will be useful, + * WinPT is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -36,8 +35,15 @@ #include "wptErrors.h" #include "wptW32API.h" #include "wptGPG.h" +#include "wptTypes.h" +#include "wptCommonCtl.h" +#include "wptContext.h" +#include "wptKeyEdit.h" +#include "wptUTF8.h" +gpgme_error_t parse_keyserver_url (char **r_keyserver, unsigned short *r_port); + /* Attribute list which holds the image data. */ struct attr_list_s { struct attr_list_s *next; @@ -49,14 +55,28 @@ typedef struct attr_list_s *attr_list_t; +/* XXX: convert it to an inline function and place it in a header file. */ +static unsigned char* +safe_uchar_alloc (size_t n) +{ + unsigned char *p = new unsigned char[n]; + if (!p) + BUG (0); + return p; +} + + +/* Free attribute list @ctx. */ void free_attr_list (attr_list_t ctx) { attr_list_t n; + while (ctx) { n = ctx->next; - free (ctx->fpr); - free (ctx->d); + free_if_alloc (ctx->fpr); + free_if_alloc (ctx->d); + free_if_alloc (ctx); ctx = n; } } @@ -80,12 +100,16 @@ continue; buffer = buf+9+10; pos = 0; - c = (attr_list_t)calloc (1, sizeof *c); + c = new attr_list_s; + if (!c) + BUG (0); + memset (c, 0, sizeof *c); + p = strtok (buffer, " "); while (p != NULL) { switch (pos) { case 0: - c->fpr = strdup (p); + c->fpr = m_strdup (p); break; case 1: @@ -102,7 +126,6 @@ pos++; p = strtok (NULL, " "); } - /*printf ("id=%s octets=%d flags=%d\n", c->fpr, c->octets, c->flags);*/ if (!*ctx) *ctx = c; else { @@ -110,7 +133,7 @@ ; t->next = c; } - c->d = (unsigned char*)malloc (c->octets); + c->d = safe_uchar_alloc (c->octets); memcpy (c->d, data, c->octets); data += c->octets; datlen -= c->octets; @@ -125,23 +148,25 @@ parse_attr_data (const char *keyid, attr_list_t *list) { gpgme_error_t err; - FILE *tmp; - char *status; + FILE *tmp; BYTE *data; - DWORD ndata; + char *status, tmpnam[MAX_PATH+1]; + DWORD ndata = 0; err = gpg_get_photoid_data (keyid, &status, &data, &ndata); if (err) return err; - if (ndata > 0) { - tmp = tmpfile (); + get_temp_name (tmpnam, MAX_PATH, NULL); + tmp = fopen (tmpnam, "w+b"); + if (ndata > 0 && tmp != NULL) { fwrite (status, 1, strlen (status), tmp); fflush (tmp); rewind (tmp); ndata = parse_attr_list (tmp, data, ndata, list); fclose (tmp); + DeleteFile (tmpnam); } else *list = NULL; @@ -171,6 +196,8 @@ gpg_iobuf_ioctl (inp, 3, 1, NULL); pkt = (PACKET*)calloc (1, sizeof *pkt); + if (!pkt) + BUG (0); gpg_init_packet (pkt); while (gpg_parse_packet (inp, pkt) != -1) { if (pkt->pkttype == PKT_SECRET_KEY) { @@ -185,8 +212,8 @@ goto next; c->gloflags.is_protected = sk->is_protected; c->gloflags.divert_to_card = sk->protect.s2k.mode==1002? 1 : 0; - if (c->pubpart != NULL) { - c->pubpart->gloflags.is_protected = sk->is_protected; + if (c->pubpart != NULL) { + c->pubpart->gloflags.is_protected = sk->is_protected; c->pubpart->gloflags.divert_to_card = sk->protect.s2k.mode==1002? 1 : 0; } } @@ -210,10 +237,10 @@ gpg_keycache_find_key2 (ctx, fpr, 0, &key, &fnd); if (!fnd) return gpg_error (GPG_ERR_NOT_FOUND); - safe_free (fnd->attrib.d); + free_if_alloc (fnd->attrib.d); fnd->attrib.flags = dat->flags; fnd->attrib.len = dat->octets; - fnd->attrib.d = (unsigned char*)malloc (dat->octets); + fnd->attrib.d = safe_uchar_alloc (dat->octets); memcpy (fnd->attrib.d, dat->d, dat->octets); return 0; } @@ -239,8 +266,83 @@ } +void +keycache_decode_uid (struct keycache_s *ctx) +{ + gpgme_user_id_t u; + struct native_uid_s *n, *t; + + for (u = ctx->key->uids; u; u = u->next) { + n = new native_uid_s; + if (!n) + BUG (0); + memset (n, 0, sizeof *n); + if (is_8bit_string (u->uid)) { + n->malloced = 1; + n->uid = utf8_to_native (u->uid); + if (u->name != NULL) + n->name = utf8_to_native (u->name); + if (u->email != NULL) + n->email = m_strdup (u->email); + if (u->comment != NULL) + n->comment = utf8_to_native (u->comment); + } + else { + n->malloced = 0; + n->uid = u->uid; + n->name = u->name; + n->comment = u->comment; + n->email = u->email; + } + n->signatures = u->signatures; + n->validity = u->validity; + n->revoked = u->revoked; + if (!ctx->uids) + ctx->uids = n; + else { + for (t = ctx->uids; t->next; t=t->next) + ; + t->next = n; + } + } +} + + +/* Store utf8 decoded user IDs in the code to avoid in-place decoding. */ +static void +keycache_decode_uids (gpg_keycache_t ctx) +{ + struct keycache_s *c; + + for (c = ctx->item; c; c = c->next) + keycache_decode_uid (c); +} + + +static void +free_native_uids (struct native_uid_s **r_n) +{ + struct native_uid_s *t; + struct native_uid_s *n = *r_n; + + while (n != NULL) { + t = n->next; + if (n->malloced) { + free_if_alloc (n->uid); + free_if_alloc (n->name); + free_if_alloc (n->comment); + free_if_alloc (n->email); + } + free_if_alloc (n); + n = t; + } + *r_n = NULL; +} + + + /* Merge the information from the keyrings into the key cache structure. */ -gpgme_error_t +static gpgme_error_t keycache_prepare2 (gpg_keycache_t ctx, const char *kid, const char *pubring, const char *secring) { @@ -249,7 +351,7 @@ gpg_iobuf_t inp; PACKET *pkt; struct keycache_s *c; - const byte *sym_prefs; + const BYTE *sym_prefs; char keyid[16+1]; int key_seen = 0; size_t nsym =0; @@ -265,13 +367,14 @@ gpg_iobuf_ioctl (inp, 3, 1, NULL); /* disable cache */ pkt = (PACKET*)calloc (1, sizeof * pkt); + if (!pkt) + BUG (0); gpg_init_packet (pkt); while (gpg_parse_packet (inp, pkt) != -1) { if (pkt->pkttype == PKT_PUBLIC_KEY) { strcpy (keyid, ""); key_seen = 1; } - if (pkt->pkttype == PKT_SIGNATURE && pkt->pkt.signature->sig_class == 0x1F) { if (pkt->pkt.signature->numrevkeys == 0) @@ -285,7 +388,7 @@ goto next; c->gloflags.has_desig_rev = 1; } - if (pkt->pkttype == PKT_SIGNATURE && key_seen == 1 ) { + if (pkt->pkttype == PKT_SIGNATURE && key_seen == 1 && c != NULL) { sym_prefs = gpg_parse_sig_subpkt (pkt->pkt.signature->hashed, SIGSUBPKT_PREF_SYM, &nsym); if (!sym_prefs) @@ -295,12 +398,13 @@ if (kid && strcmp (kid, keyid) != 0) goto next; err = gpg_keycache_find_key2 (ctx, keyid, 0, &key, &c); - if (err) + if (err || !c) + goto next; + if (c->sym_prefs) // only use the prefs from the primary uid. goto next; else if (nsym > 0) { - c->sym_prefs = (unsigned char*)calloc (1, nsym+1); - if (!c->sym_prefs) - return gpg_error (GPG_ERR_ENOMEM); + c->sym_prefs = safe_uchar_alloc (nsym+1); + memset (c->sym_prefs, 0, nsym+1); memcpy (c->sym_prefs, sym_prefs, nsym); } } @@ -340,9 +444,10 @@ if (!r_ctx) return gpg_error (GPG_ERR_INV_ARG); - ctx = (gpg_keycache_t)calloc (1, sizeof *ctx); + ctx = new gpg_keycache_s; if (!ctx) - return gpg_error (GPG_ERR_ENOMEM); + BUG (0); + memset (ctx, 0, sizeof *ctx); ctx->secret = 0; ctx->pos = 0; *r_ctx = ctx; @@ -350,6 +455,24 @@ } +void +gpg_keycache_item_release (struct keycache_s *c) +{ + if (c->key) + gpgme_key_release (c->key); + c->key = NULL; + if (c->rev != NULL) + gpg_desig_rev_release (c->rev); + c->rev = NULL; + free_if_alloc (c->pref_keyserver); + free_if_alloc (c->sym_prefs); + free_if_alloc (c->attrib.d); + free_if_alloc (c->card_type); + free_native_uids (&c->uids); + free_if_alloc (c); +} + + /* Release keycache object @ctx. */ void gpg_keycache_release (gpg_keycache_t ctx) @@ -361,14 +484,9 @@ for (c = ctx->item; c; c = c2) { c2 = c->next; - gpgme_key_release (c->key); - c->key = NULL; - safe_free (c->sym_prefs); - safe_free (c->attrib.d); - safe_free (c->card_type); - free (c); + gpg_keycache_item_release (c); } - safe_free (ctx); + free_if_alloc (ctx); } @@ -400,9 +518,10 @@ if (!ctx) return gpg_error (GPG_ERR_INV_ARG); - c = (struct keycache_s*)calloc (1, sizeof *c); + c = new keycache_s; if (!c) - return gpg_error (GPG_ERR_ENOMEM); + BUG (0); + memset (c, 0, sizeof *c); c->gloflags.is_protected = 1; /*default: assume protection. */ c->key = key; if (!ctx->item) @@ -501,21 +620,88 @@ } +/* Return the next key which was updated. Before it is + returned the update flag is cleared. + @r_status is 1 for a new key and 2 for an updated key. + Return value: 0 on success. */ +gpgme_error_t +gpg_keycache_next_updated_key (gpg_keycache_t ctx, + struct keycache_s **r_obj, + int *r_status) +{ + struct keycache_s *c; + + for (c = ctx->item; c; c = c->next) { + if (c->flags != 0) { + *r_status = c->flags; + *r_obj = c; + c->flags = 0; /* reset update flag. */ + return 0; + } + } + return gpg_error (GPG_ERR_NOT_FOUND); +} + + +/* Helper to retrieve a GPG key. */ +static gpgme_error_t +get_gpg_key (const char *keyid, int is_sec, gpgme_key_t *r_key) +{ + gpgme_ctx_t ctx; + gpgme_error_t err; + + err = gpgme_new (&ctx); + if (err) + return err; + gpgme_set_keylist_mode (ctx, GPGME_KEYLIST_MODE_SIGS); + err = gpgme_get_key (ctx, keyid, r_key, is_sec); + gpgme_release (ctx); + return err; +} + + +/* Fetch a key directly from gpg but without adding + it to the key cache. Caller must free @r_ctx. */ +gpgme_error_t +gpg_keycache_fetch_key (const char *keyid, int is_sec, + gpgme_key_t *r_key, struct keycache_s **r_c) +{ + gpgme_error_t err; + gpgme_key_t key; + struct keycache_s *c; + + *r_key = NULL; + *r_c = NULL; + err = get_gpg_key (keyid, is_sec, &key); + if (err) + return err; + + c = new keycache_s; + if (!c) + BUG (0); + memset (c, 0, sizeof *c); + c->gloflags.is_protected = 1; /*default: assume protection. */ + c->key = key; + keycache_decode_uid (c); + *r_key = key; + *r_c = c; + return 0; +} + + +/* Update the key with the keyid @key in the key cache. + If the key does not exist, it is added otherwise all + parts are first freed and then replaced with the updated data. */ gpgme_error_t gpg_keycache_update_key (gpg_keycache_t ctx, int is_sec, void *opaque, const char *keyid) -{ - struct keycache_s *c = NULL, *c_new=NULL; +{ gpgme_key_t key=NULL, fndkey=NULL; gpgme_error_t err; - gpgme_ctx_t gctx; + struct keycache_s *c = NULL, *c_new=NULL; gpg_keycache_t pub = (gpg_keycache_t)opaque; - err = gpgme_new (&gctx); - if (err) - return err; - err = gpgme_get_key (gctx, keyid, &key, is_sec); - gpgme_release (gctx); + err = get_gpg_key (keyid, is_sec, &key); if (err) return err; err = gpg_keycache_find_key2 (ctx, keyid, 0, &fndkey, &c); @@ -523,7 +709,7 @@ log_debug ("keycache update: keyid=%s %p\r\n", keyid, pub); gpgme_key_release (fndkey); c->key = key; - c->flags = 0; + c->flags = KC_FLAG_UPD; if (is_sec && pub != NULL && !gpg_keycache_find_key (pub, keyid, 0, &fndkey)) { log_debug ("keycache update: set public part %p\r\n", fndkey); @@ -546,7 +732,16 @@ c->gloflags.divert_to_card = c_new->gloflags.divert_to_card; } } + if (c != NULL) + c->flags = KC_FLAG_ADD; + } + + /* refresh utf8 user ID list. */ + if (c != NULL && c->key) { + free_native_uids (&c->uids); + keycache_decode_uid (c); } + return 0; } @@ -568,18 +763,22 @@ c = ctx->item; if (c->next == NULL) { - gpgme_key_release (itm->key); - if (itm) - free (itm); - ctx->item = NULL; + if (itm->key) + gpgme_key_release (itm->key); + itm->key = NULL; + free_if_alloc (itm); } else { - while (c && c->next != itm) - c = c->next; + for (; c != NULL; c = c->next) { + if (c->next == itm) + break; + } + assert (c != NULL); /* XXX: sometimes access violation. */ c->next = c->next->next; - gpgme_key_release (itm->key); - if (itm) - free (itm); + if (itm->key) + gpgme_key_release (itm->key); + itm->key = NULL; + free_if_alloc (itm); } return 0; } @@ -603,6 +802,7 @@ if (err) return err; + /* XXX: GPGME_KEYLIST_MODE_SIG_NOTATIONS causes an internal error! */ gpgme_set_keylist_mode (c, GPGME_KEYLIST_MODE_SIGS); err = gpgme_op_keylist_start (c, pattern, secret); while(!err) { @@ -616,19 +816,22 @@ if (gpgme_err_code (err) == GPG_ERR_EOF) err = gpg_error (GPG_ERR_NO_ERROR); keycache_update_photos (ctx); - /* XXX: make sure the progress dialog is closed. */ + keycache_decode_uids (ctx); gpgme_op_keylist_end (c); gpgme_release (c); return err; } -/* XXX: kludge to see if the key is stored on a card. */ +/* Return 1 if we can assume that the actual private key is + stored on a smart card. This is not bullet proof, but the + card provides 3 keys (RSA) and each key for a different purpose. */ static int key_divert_to_card (gpgme_key_t key) { gpgme_subkey_t k; - int n=0, n_alg=0, can_auth = 0; + int n=0, n_alg=0; + int can_auth = 0, can_encr = 0; for (k = key->subkeys; k; k = k->next) { n++; @@ -636,8 +839,10 @@ n_alg++; if (k->can_authenticate) can_auth++; + if (k->can_encrypt) + can_encr++; } - if (n == 3 && n_alg == 3 && can_auth == 1) + if (n == 3 && n_alg == 3 && can_auth == 1 && can_encr == 1) return 1; return 0; } @@ -651,14 +856,14 @@ while (prefs[pos] != 0) pos++; - p = (unsigned char*)calloc (1, pos+1); - if (!p) - abort (); + p = safe_uchar_alloc (pos+1); + memset (p, 0, pos+1); memcpy (p, prefs, pos); return p; } +/* Sync the secret and the public key cache information. */ gpgme_error_t gpg_keycache_sync (gpg_keycache_t pub, gpg_keycache_t sec) { @@ -668,13 +873,15 @@ if (!pub || !sec) return gpg_error (GPG_ERR_INV_ARG); - for (c=sec->item; c; c=c->next) { - if (!gpg_keycache_find_key2 (pub, c->key->subkeys->keyid, 0, &key, &c_sec)) { + for (c=sec->item; c; c=c->next) { + if (!gpg_keycache_find_key2 (pub, c->key->subkeys->keyid, 0, + &key, &c_sec)) { c_sec->gloflags.is_protected = c->gloflags.is_protected; c_sec->gloflags.divert_to_card = c->gloflags.divert_to_card; if (!c->gloflags.divert_to_card) c->gloflags.divert_to_card = key_divert_to_card (key); - c->sym_prefs = copy_uid_prefs (c_sec->sym_prefs); + if (c_sec->sym_prefs) + c->sym_prefs = copy_uid_prefs (c_sec->sym_prefs); c->pubpart = c_sec; c->pubpart->key = key; } @@ -723,9 +930,15 @@ *r_key = NULL; return gpg_error (GPG_ERR_EOF); } - + if (ctx->tmp->flags != 0) + ctx->tmp->flags = 0; /* reset the 'updated' status. */ + + /* it might be possible there is no public key. */ + if (flags && ctx->tmp->pubpart == NULL) + flags = 0; *r_key = flags? ctx->tmp->pubpart->key : ctx->tmp->key; - *c = ctx->tmp = ctx->tmp->next; + *c = ctx->tmp; + ctx->tmp = ctx->tmp->next; ctx->pos++; return 0; @@ -739,8 +952,144 @@ gpg_keycache_next_key (gpg_keycache_t ctx, int flags, gpgme_key_t *r_key) { struct keycache_s *c=NULL; - gpgme_error_t err = 0; + gpgme_error_t err; err = keycache_next_key (ctx, flags, &c, r_key); return err; } + + +gpgme_error_t +gpg_keycache_next_key2 (gpg_keycache_t ctx, int flags, + struct keycache_s **c, gpgme_key_t *r_key) +{ + return keycache_next_key (ctx, flags, c, r_key); +} + + +/* Search for a key with the pattern @pattern and mark + this key as the default signing key if found. + Return value: 0 on success. */ +gpgme_error_t +gpg_keycache_set_default_key (gpg_keycache_t ctx, + const char *pattern) +{ + gpgme_error_t err; + gpgme_key_t key; + struct keycache_s *itm; + + err = gpg_keycache_find_key2 (ctx, pattern, 0, &key, &itm); + if (err) + return err; + + if (itm) + itm->default_key = 1; + return 0; +} + +/* Return the default key from the cache. If no was + marked before, NULL is returned in @r_key. + Return value: 0 on success. */ +gpgme_error_t +gpg_keycache_get_default_key (gpg_keycache_t ctx, + gpgme_key_t *r_key) +{ + struct keycache_s *itm; + + *r_key = NULL; + for (itm = ctx->item; itm; itm = itm->next) { + if (itm->default_key) { + *r_key = itm->key; + break; + } + } + if (!*r_key) + return gpgme_error (GPG_ERR_NOT_FOUND); + return 0; +} + + +static gpgme_error_t +decode_subpacket (const char *subpkt_data, int *type, + char **out, WORD *outlen) +{ + char tmp[128], *val; + char *enc = NULL; + size_t pos = 0; + + /* example: spk:24:1:21:http%3A//subkeys.pgp.de */ + *outlen = 0; + *out = NULL; + + if (strncmp (subpkt_data, "spk:", 4)) + return gpg_error (GPG_ERR_NO_DATA); + + /* XXX: do not use static buffer sizes. */ + strncpy (tmp, subpkt_data, DIM (tmp)-4); + val = strtok (tmp, ":"); + while (val != NULL) { + switch (pos++) { + case 0: + break; + + case 1: + if (type) + *type = atoi (val); + break; + + case 2: + break; + + case 3: + *outlen = atoi (val); + break; + + case 4: + enc = m_strdup (val); + break; + } + val = strtok (NULL, ":"); + } + if (!enc) + return gpg_error (GPG_ERR_NO_DATA); + unhexify_buffer (enc, out); + free_if_alloc (enc); + return 0; +} + + +/* If the attribute given in @attr is not set in the + key cache object, try to update it. */ +gpgme_error_t +gpg_keycache_update_attr (struct keycache_s *item, + int attr, int force) +{ + gpgme_error_t err = gpg_error (GPG_ERR_NO_ERROR); + char *val = NULL; + WORD n = 0; + + switch (attr) { + case KC_ATTR_PREFSYM: + if (!force && item->sym_prefs) + break; + free_if_alloc (item->sym_prefs); + err = gpg_find_key_subpacket (item->key->subkeys->keyid+8, attr, &val); + if (!err && val != NULL) + err = decode_subpacket (val, NULL, (char**)&item->sym_prefs, &n); + break; + + case KC_ATTR_PREFKSERV: + if (!force && item->pref_keyserver) + break; + free_if_alloc (item->pref_keyserver); + err = gpg_find_key_subpacket (item->key->subkeys->keyid+8, attr, &val); + if (!err && val != NULL) + err = decode_subpacket (val, NULL, &item->pref_keyserver, &n); + if (!err && item->pref_keyserver) + err = parse_keyserver_url (&item->pref_keyserver, + &item->pref_keyserver_port); + break; + } + safe_free (val); + return err; +}