2004-09-30 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / gpgme / key.c
index 0443668..444f067 100644 (file)
-/* key.c - Key and keyList objects
*     Copyright (C) 2000 Werner Koch (dd9jn)
*      Copyright (C) 2001 g10 Code GmbH
- *
* This file is part of GPGME.
- *
- * GPGME 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.
- *
- * GPGME 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.
- *
* You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
- */
-
+/* key.c - Key objects.
+   Copyright (C) 2000 Werner Koch (dd9jn)
  Copyright (C) 2001, 2002, 2003 g10 Code GmbH
+
  This file is part of GPGME.
+   GPGME 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.
+   GPGME 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.
  You should have received a copy of the GNU General Public License
+   along with GPGME; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#if HAVE_CONFIG_H
 #include <config.h>
-#include <stdio.h>
+#endif
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
+#include <errno.h>
 
 #include "util.h"
 #include "ops.h"
-#include "key.h"
+#include "sema.h"
 
-#define ALLOC_CHUNK 1024
-#define my_isdigit(a) ( (a) >='0' && (a) <= '9' )
+\f
+/* Protects all reference counters in keys.  All other accesses to a
+   key are read only.  */
+DEFINE_STATIC_LOCK (key_ref_lock);
 
-#if SIZEOF_UNSIGNED_INT < 4
-#error unsigned int too short to be used as a hash value
-#endif
 
-struct key_cache_item_s {
-    struct key_cache_item_s *next;
-    GpgmeKey key;
-};
+/* Create a new key.  */
+gpgme_error_t
+_gpgme_key_new (gpgme_key_t *r_key)
+{
+  gpgme_key_t key;
 
-static int key_cache_initialized;
-static struct key_cache_item_s **key_cache;
-static size_t key_cache_size;
-static size_t key_cache_max_chain_length;
-static struct key_cache_item_s *key_cache_unused_items;
+  key = calloc (1, sizeof *key);
+  if (!key)
+    return gpg_error_from_errno (errno);
+  key->_refs = 1;
 
-static int
-hextobyte ( const byte *s )
-{
-    int c;
-
-    if ( *s >= '0' && *s <= '9' )
-       c = 16 * (*s - '0');
-    else if ( *s >= 'A' && *s <= 'F' )
-       c = 16 * (10 + *s - 'A');
-    else if ( *s >= 'a' && *s <= 'f' )
-       c = 16 * (10 + *s - 'a');
-    else
-       return -1;
-    s++;
-    if ( *s >= '0' && *s <= '9' )
-       c += *s - '0';
-    else if ( *s >= 'A' && *s <= 'F' )
-       c += 10 + *s - 'A';
-    else if ( *s >= 'a' && *s <= 'f' )
-       c += 10 + *s - 'a';
-    else
-       return -1;
-    return c;
+  *r_key = key;
+  return 0;
 }
 
-static int
-hash_key (const char *fpr, unsigned int *rhash)
+
+gpgme_error_t
+_gpgme_key_add_subkey (gpgme_key_t key, gpgme_subkey_t *r_subkey)
 {
-    unsigned int hash;
-    int c;
-
-    if ( !fpr )                         return -1;
-    if ( (c = hextobyte(fpr)) == -1 )   return -1;
-    hash = c;
-    if ( (c = hextobyte(fpr+2)) == -1 ) return -1;
-    hash |= c << 8;
-    if ( (c = hextobyte(fpr+4)) == -1 ) return -1;
-    hash |= c << 16;
-    if ( (c = hextobyte(fpr+6)) == -1 ) return -1;
-    hash |= c << 24;
-
-    *rhash = hash;
-    return 0;
+  gpgme_subkey_t subkey;
+
+  subkey = calloc (1, sizeof *subkey);
+  if (!subkey)
+    return gpg_error_from_errno (errno);
+  subkey->keyid = subkey->_keyid;
+  subkey->_keyid[16] = '\0';
+
+  if (!key->subkeys)
+    key->subkeys = subkey;
+  if (key->_last_subkey)
+    key->_last_subkey->next = subkey;
+  key->_last_subkey = subkey;
+
+  *r_subkey = subkey;
+  return 0;
 }
 
-void
-_gpgme_key_cache_init (void)
+
+static char *
+set_user_id_part (char *tail, const char *buf, size_t len)
 {
-    if (key_cache_initialized)
-        return;
-    key_cache_size = 503;
-    key_cache = xtrycalloc (key_cache_size, sizeof *key_cache);
-    if (!key_cache) {
-        key_cache_size = 0;
-        key_cache_initialized = 1;
-        return;
-    }
-    /*
-     * The upper bound for our cache size is 
-     * key_cache_max_chain_length * key_cache_size 
-     */
-    key_cache_max_chain_length = 10;
-    key_cache_initialized = 1;
+  while (len && (buf[len - 1] == ' ' || buf[len - 1] == '\t')) 
+    len--;
+  for (; len; len--)
+    *tail++ = *buf++;
+  *tail++ = 0;
+  return tail;
 }
 
 
-void
-_gpgme_key_cache_add (GpgmeKey key)
+static void
+parse_user_id (char *src, char **name, char **email,
+              char **comment, char *tail)
 {
-    struct subkey_s *k;
-
-    if (!key)
-        return;
-
-    /* FIXME: add locking */
-    if (!key_cache_initialized)
-        _gpgme_key_cache_init ();
-    if (!key_cache_size)
-        return; /* cache was not enabled */
-
-    /* put the key under each fingerprint into the cache.  We use the
-     * first 4 digits to calculate the hash */
-    for (k=&key->keys; k; k = k->next ) {
-        size_t n;
-        unsigned int hash;
-        struct key_cache_item_s *item;
-
-        if ( hash_key (k->fingerprint, &hash) )
-            continue;
-
-        hash %= key_cache_size;
-        for (item=key_cache[hash],n=0; item; item = item->next, n++) {
-            struct subkey_s *k2;
-            if (item->key == key) 
-                break; /* already in cache */
-            /* now do a deeper check */
-            for (k2=&item->key->keys; k2; k2 = k2->next ) {
-                if( k2->fingerprint
-                    && !strcmp (k->fingerprint, k2->fingerprint) ) {
-                    /* okay, replace it with the new copy */
-                    gpgme_key_unref (item->key);
-                    item->key = key;
-                    gpgme_key_ref (item->key);
-                    return;
-                }
-            }
-        }
-        if (item)
-            continue; 
-        
-        if (n > key_cache_max_chain_length ) { /* remove the last entries */
-            struct key_cache_item_s *last = NULL;
-
-            for (item=key_cache[hash];
-                 item && n < key_cache_max_chain_length;
-                 last = item, item = item->next, n++ ) {
-                ;
-            }
-            if (last) {
-                struct key_cache_item_s *next;
-
-                assert (last->next == item);
-                last->next = NULL;
-                for ( ;item; item=next) {
-                    next = item->next;
-                    gpgme_key_unref (item->key);
-                    item->key = NULL;
-                    item->next = key_cache_unused_items;
-                    key_cache_unused_items = item;
-                }
-            }
-        }
+  const char *start = NULL;
+  int in_name = 0;
+  int in_email = 0;
+  int in_comment = 0;
 
-        item = key_cache_unused_items;
-        if (item) {
-            key_cache_unused_items = item->next;
-            item->next = NULL;
-        }
-        else {
-            item = xtrymalloc (sizeof *item);
-            if (!item)
-                return; /* out of core */
-        }
-        
-        item->key = key;
-        gpgme_key_ref (key);
-        item->next = key_cache[hash];
-        key_cache[hash] = item;
+  while (*src)
+    {
+      if (in_email)
+       {
+         if (*src == '<')
+           /* Not legal but anyway.  */
+           in_email++;
+         else if (*src == '>')
+           {
+             if (!--in_email && !*email)
+               {
+                 *email = tail;
+                 tail = set_user_id_part (tail, start, src - start);
+               }
+           }
+       }
+      else if (in_comment)
+       {
+         if (*src == '(')
+           in_comment++;
+         else if (*src == ')')
+           {
+             if (!--in_comment && !*comment)
+               {
+                 *comment = tail;
+                 tail = set_user_id_part (tail, start, src - start);
+               }
+           }
+       }
+      else if (*src == '<')
+       {
+         if (in_name)
+           {
+             if (!*name)
+               {
+                 *name = tail;
+                 tail = set_user_id_part (tail, start, src - start);
+               }
+             in_name = 0;
+           }
+         in_email = 1;
+         start = src + 1;
+       }
+      else if (*src == '(')
+       {
+         if (in_name)
+           {
+             if (!*name)
+               {
+                 *name = tail;
+                 tail = set_user_id_part (tail, start, src - start);
+               }
+             in_name = 0;
+           }
+         in_comment = 1;
+         start = src + 1;
+       }
+      else if (!in_name && *src != ' ' && *src != '\t')
+       {
+         in_name = 1;
+         start = src;
+       }    
+      src++;
+    }
+  if (in_name)
+    {
+      if (!*name)
+       {
+         *name = tail;
+         tail = set_user_id_part (tail, start, src - start);
+       }
     }
+  /* Let unused parts point to an EOS.  */
+  tail--;
+  if (!*name)
+    *name = tail;
+  if (!*email)
+    *email = tail;
+  if (!*comment)
+    *comment = tail;
 }
 
 
-GpgmeKey 
-_gpgme_key_cache_get (const char *fpr)
+static void
+parse_x509_user_id (char *src, char **name, char **email,
+                   char **comment, char *tail)
 {
-    struct key_cache_item_s *item;
-    unsigned int hash;
+  if (*src == '<' && src[strlen (src) - 1] == '>')
+    *email = src;
+  
+  /* Let unused parts point to an EOS.  */
+  tail--;
+  if (!*name)
+    *name = tail;
+  if (!*email)
+    *email = tail;
+  if (!*comment)
+    *comment = tail;
+}
+
+
+/* Take a name from the --with-colon listing, remove certain escape
+   sequences sequences and put it into the list of UIDs.  */
+gpgme_error_t
+_gpgme_key_append_name (gpgme_key_t key, char *src)
+{
+  gpgme_user_id_t uid;
+  char *dst;
+  int src_len = strlen (src);
+
+  assert (key);
+  /* We can malloc a buffer of the same length, because the converted
+     string will never be larger. Actually we allocate it twice the
+     size, so that we are able to store the parsed stuff there too.  */
+  uid = malloc (sizeof (*uid) + 2 * src_len + 3);
+  if (!uid)
+    return gpg_error_from_errno (errno);
+  memset (uid, 0, sizeof *uid);
 
-    if (!key_cache_size)
-        return NULL; /* cache not (yet) enabled */
+  uid->uid = ((char *) uid) + sizeof (*uid);
+  dst = uid->uid;
+  _gpgme_decode_c_string (src, &dst, src_len + 1);
 
-    if (hash_key (fpr, &hash))
-        return NULL;
+  dst += strlen (dst) + 1;
+  if (key->protocol == GPGME_PROTOCOL_CMS)
+    parse_x509_user_id (uid->uid, &uid->name, &uid->email,
+                       &uid->comment, dst);
+  else
+    parse_user_id (uid->uid, &uid->name, &uid->email,
+                  &uid->comment, dst);
 
-    hash %= key_cache_size;
-    for (item=key_cache[hash]; item; item = item->next) {
-        struct subkey_s *k;
+  if (!key->uids)
+    key->uids = uid;
+  if (key->_last_uid)
+    key->_last_uid->next = uid;
+  key->_last_uid = uid;
 
-        for (k=&item->key->keys; k; k = k->next ) {
-            if( k->fingerprint && !strcmp (k->fingerprint, fpr) ) {
-                gpgme_key_ref (item->key);
-                return item->key;
-            }
-        }
-    }
-    return NULL;
+  return 0;
 }
 
 
-static const char *
-pkalgo_to_string ( int algo )
+gpgme_key_sig_t
+_gpgme_key_add_sig (gpgme_key_t key, char *src)
 {
-    switch (algo) {
-      case 1: 
-      case 2:
-      case 3: return "RSA";
-      case 16:
-      case 20: return "ElG";
-      case 17: return "DSA";
-      default: return "Unknown";
+  int src_len = src ? strlen (src) : 0;
+  gpgme_user_id_t uid;
+  gpgme_key_sig_t sig;
+
+  assert (key);        /* XXX */
+
+  uid = key->_last_uid;
+  assert (uid);        /* XXX */
+
+  /* We can malloc a buffer of the same length, because the converted
+     string will never be larger. Actually we allocate it twice the
+     size, so that we are able to store the parsed stuff there too.  */
+  sig = malloc (sizeof (*sig) + 2 * src_len + 3);
+  if (!sig)
+    return NULL;
+  memset (sig, 0, sizeof *sig);
+
+  sig->keyid = sig->_keyid;
+  sig->_keyid[16] = '\0';
+  sig->uid = ((char *) sig) + sizeof (*sig);
+
+  if (src)
+    {
+      char *dst = sig->uid;
+      _gpgme_decode_c_string (src, &dst, src_len + 1);
+      dst += strlen (dst) + 1;
+      if (key->protocol == GPGME_PROTOCOL_CMS)
+       parse_x509_user_id (sig->uid, &sig->name, &sig->email,
+                           &sig->comment, dst);
+      else
+       parse_user_id (sig->uid, &sig->name, &sig->email,
+                      &sig->comment, dst);
     }
-}
 
+  if (!uid->signatures)
+    uid->signatures = sig;
+  if (uid->_last_keysig)
+    uid->_last_keysig->next = sig;
+  uid->_last_keysig = sig;
 
-static GpgmeError
-key_new ( GpgmeKey *r_key, int secret )
-{
-    GpgmeKey key;
-
-    *r_key = NULL;
-    key = xtrycalloc ( 1, sizeof *key );
-    if (!key)
-        return mk_error (Out_Of_Core);
-    key->ref_count = 1;
-    *r_key = key;
-    if (secret)
-        key->secret = 1;
-    return 0;
+  return sig;
 }
 
-GpgmeError
-_gpgme_key_new ( GpgmeKey *r_key )
+\f
+/* Acquire a reference to KEY.  */
+void
+gpgme_key_ref (gpgme_key_t key)
 {
-    return key_new ( r_key, 0 );
+  LOCK (key_ref_lock);
+  key->_refs++;
+  UNLOCK (key_ref_lock);
 }
 
-GpgmeError
-_gpgme_key_new_secret ( GpgmeKey *r_key )
+
+/* gpgme_key_unref releases the key object.  Note, that this function
+   may not do an actual release if there are other shallow copies of
+   the objects.  You have to call this function for every newly
+   created key object as well as for every gpgme_key_ref() done on the
+   key object.  */
+void
+gpgme_key_unref (gpgme_key_t key)
 {
-    return key_new ( r_key, 1 );
+  gpgme_user_id_t uid;
+  gpgme_subkey_t subkey;
+
+  if (!key)
+    return;
+
+  LOCK (key_ref_lock);
+  assert (key->_refs > 0);
+  if (--key->_refs)
+    {
+      UNLOCK (key_ref_lock);
+      return;
+    }
+  UNLOCK (key_ref_lock);
+
+  subkey = key->subkeys;
+  while (subkey)
+    {
+      gpgme_subkey_t next = subkey->next;
+      if (subkey->fpr)
+       free (subkey->fpr);
+      free (subkey);
+      subkey = next;
+    }
+
+  uid = key->uids;
+  while (uid)
+    {
+      gpgme_user_id_t next_uid = uid->next;
+      gpgme_key_sig_t keysig = uid->signatures;
+
+      while (keysig)
+       {
+         gpgme_key_sig_t next = keysig->next;
+          free (keysig);
+         keysig = next;
+        }
+      free (uid);
+      uid = next_uid;
+    }
+  
+  if (key->issuer_serial)
+    free (key->issuer_serial);
+  if (key->issuer_name)
+    free (key->issuer_name);
+
+  if (key->chain_id)
+    free (key->chain_id);
+
+  free (key);
 }
 
+\f
+/* Compatibility interfaces.  */
 
-/**
- * gpgme_key_ref:
- * @key: Key object
- * 
- * To safe memory the Key objects implement reference counting.
- * Use this function to bump the reference counter.
- **/
 void
-gpgme_key_ref ( GpgmeKey key )
+gpgme_key_release (gpgme_key_t key)
 {
-    return_if_fail (key);
-    key->ref_count++;
+  gpgme_key_unref (key);
 }
 
 
-static struct subkey_s *
-add_subkey (GpgmeKey key, int secret)
+static const char *
+otrust_to_string (int otrust)
 {
-    struct subkey_s *k, *kk;
-
-    k = xtrycalloc (1, sizeof *k);
-    if (!k)
-        return NULL;
-
-    if( !(kk=key->keys.next) )
-        key->keys.next = k;
-    else {
-        while ( kk->next )
-            kk = kk->next;
-        kk->next = k;
+  switch (otrust)
+    {
+    case GPGME_VALIDITY_NEVER:
+      return "n";
+
+    case GPGME_VALIDITY_MARGINAL:
+      return "m";
+
+    case GPGME_VALIDITY_FULL:
+      return "f";
+
+    case GPGME_VALIDITY_ULTIMATE:
+      return "u";
+
+    default:
+      return "?";
     }
-    if (secret)
-        k->secret = 1;
-    return k;
 }
 
-struct subkey_s *
-_gpgme_key_add_subkey (GpgmeKey key)
-{
-    return add_subkey (key, 0);
-}
 
-struct subkey_s *
-_gpgme_key_add_secret_subkey (GpgmeKey key)
+static const char *
+validity_to_string (int validity)
 {
-    return add_subkey (key, 1);
-}
-
+  switch (validity)
+    {
+    case GPGME_VALIDITY_UNDEFINED:
+      return "q";
 
+    case GPGME_VALIDITY_NEVER:
+      return "n";
 
-/**
- * gpgme_key_release:
- * @key: Key Object or NULL
- * 
- * Release the key object. Note, that this function may not do an
- * actual release if there are other shallow copies of the objects.
- * You have to call this function for every newly created key object
- * as well as for every gpgme_key_ref() done on the key object.
- **/
-void
-gpgme_key_release ( GpgmeKey key )
-{
-    struct user_id_s *u, *u2;
-    struct subkey_s *k, *k2;
+    case GPGME_VALIDITY_MARGINAL:
+      return "m";
 
-    if (!key)
-        return;
+    case GPGME_VALIDITY_FULL:
+      return "f";
 
-    assert (key->ref_count);
-    if ( --key->ref_count )
-        return;
+    case GPGME_VALIDITY_ULTIMATE:
+      return "u";
 
-    xfree (key->keys.fingerprint);
-    for (k = key->keys.next; k; k = k2 ) {
-        k2 = k->next;
-        xfree (k->fingerprint);
-        xfree (k);
+    case GPGME_VALIDITY_UNKNOWN:
+    default:
+      return "?";
     }
-    for (u = key->uids; u; u = u2 ) {
-        u2 = u->next;
-        xfree (u);
-    }
-    xfree (key->issuer_serial);
-    xfree (key->issuer_name);
-    xfree (key->chain_id);
-    xfree (key);
 }
 
-/**
- * gpgme_key_unref:
- * @key: Key Object
- * 
- * This is an alias for gpgme_key_release().
- **/
-void
-gpgme_key_unref (GpgmeKey key)
+
+static const char *
+capabilities_to_string (gpgme_subkey_t subkey)
 {
-    gpgme_key_release (key);
+  static const char *const strings[8] =
+    {
+      "",
+      "c",
+      "s",
+      "sc",
+      "e",
+      "ec",
+      "es",
+      "esc"
+    };
+  return strings[(!!subkey->can_encrypt << 2)
+                | (!!subkey->can_sign << 1)
+                | (!!subkey->can_certify)];
 }
 
 
-static char *
-set_user_id_part ( char *tail, const char *buf, size_t len )
+/* Return the value of the attribute WHAT of ITEM, which has to be
+   representable by a string.  */
+const char *
+gpgme_key_get_string_attr (gpgme_key_t key, _gpgme_attr_t what,
+                          const void *reserved, int idx)
 {
-    while ( len && (buf[len-1] == ' ' || buf[len-1] == '\t') ) 
-        len--;
-    for ( ; len; len--)
-        *tail++ = *buf++;
-    *tail++ = 0;
-    return tail;
-}
+  gpgme_subkey_t subkey;
+  gpgme_user_id_t uid;
+  int i;
 
+  if (!key || reserved || idx < 0)
+    return NULL;
 
-static void
-parse_user_id ( struct user_id_s *uid, char *tail )
-{
-    const char *s, *start=NULL;
-    int in_name = 0;
-    int in_email = 0;
-    int in_comment = 0;
-
-    for (s=uid->name; *s; s++ ) {
-        if ( in_email ) {
-            if ( *s == '<' )
-                in_email++; /* not legal but anyway */
-            else if (*s== '>') {
-                if ( !--in_email ) {
-                    if (!uid->email_part) {
-                        uid->email_part = tail;
-                        tail = set_user_id_part ( tail, start, s-start );
-                    }
-                }
-            }
-        }
-        else if ( in_comment ) {
-            if ( *s == '(' )
-                in_comment++;
-            else if (*s== ')') {
-                if ( !--in_comment ) {
-                    if (!uid->comment_part) {
-                        uid->comment_part = tail;
-                        tail = set_user_id_part ( tail, start, s-start );
-                    }
-                }
-            }
-        }
-        else if ( *s == '<' ) {
-            if ( in_name ) {
-                if ( !uid->name_part ) {
-                    uid->name_part = tail;
-                    tail = set_user_id_part (tail, start, s-start );
-                }
-                in_name = 0;
-            }
-            in_email = 1;
-            start = s+1;
-        }
-        else if ( *s == '(' ) {
-            if ( in_name ) {
-                if ( !uid->name_part ) {
-                    uid->name_part = tail;
-                    tail = set_user_id_part (tail, start, s-start );
-                }
-                in_name = 0;
-            }
-            in_comment = 1;
-            start = s+1;
-        }
-        else if ( !in_name && *s != ' ' && *s != '\t' ) {
-            in_name = 1;
-            start = s;
-        }    
+  /* Select IDXth subkey.  */
+  subkey = key->subkeys;
+  for (i = 0; i < idx; i++)
+    {
+      subkey = subkey->next;
+      if (!subkey)
+       break;
     }
 
-    if ( in_name ) {
-        if ( !uid->name_part ) {
-            uid->name_part = tail;
-            tail = set_user_id_part (tail, start, s-start );
-        }
+  /* Select the IDXth user ID.  */
+  uid = key->uids;
+  for (i = 0; i < idx; i++)
+    {
+      uid = uid->next;
+      if (!uid)
+       break;
     }
 
-    /* let unused parts point to an EOS */ 
-    tail--;
-    if (!uid->name_part)
-        uid->name_part = tail;
-    if (!uid->email_part)
-        uid->email_part = tail;
-    if (!uid->comment_part)
-        uid->comment_part = tail;
+  switch (what)
+    {
+    case GPGME_ATTR_KEYID:
+      return subkey ? subkey->keyid : NULL;
 
-}
+    case GPGME_ATTR_FPR:
+      return subkey ? subkey->fpr : NULL;
 
-static void
-parse_x509_user_id ( struct user_id_s *uid, char *tail )
-{
-  const char *s;
+    case GPGME_ATTR_ALGO:    
+      return subkey ? gpgme_pubkey_algo_name (subkey->pubkey_algo) : NULL;
 
-  s=uid->name; 
-  if (*s == '<' && s[strlen(s)-1] == '>')
-    uid->email_part = s;
-  
-  /* let unused parts point to an EOS */ 
-  tail--;
-  if (!uid->name_part)
-    uid->name_part = tail;
-  if (!uid->email_part)
-    uid->email_part = tail;
-  if (!uid->comment_part)
-    uid->comment_part = tail;
+    case GPGME_ATTR_TYPE:
+      return key->protocol == GPGME_PROTOCOL_CMS ? "X.509" : "PGP";
+
+    case GPGME_ATTR_OTRUST:
+      return otrust_to_string (key->owner_trust);
+
+    case GPGME_ATTR_USERID:  
+      return uid ? uid->uid : NULL;
+
+    case GPGME_ATTR_NAME:   
+      return uid ? uid->name : NULL;
+
+    case GPGME_ATTR_EMAIL:
+      return uid ? uid->email : NULL;
+
+    case GPGME_ATTR_COMMENT:
+      return uid ? uid->comment : NULL;
+
+    case GPGME_ATTR_VALIDITY:
+      return uid ? validity_to_string (uid->validity) : NULL;
+
+    case GPGME_ATTR_KEY_CAPS:    
+      return subkey ? capabilities_to_string (subkey) : NULL;
+
+    case GPGME_ATTR_SERIAL:
+      return key->issuer_serial;
+
+    case GPGME_ATTR_ISSUER:
+      return idx ? NULL : key->issuer_name;
+
+    case GPGME_ATTR_CHAINID:
+      return key->chain_id;
+
+    default:
+      return NULL;
+    }
 }
 
-/* 
- * Take a name from the --with-colon listing, remove certain escape sequences
- * sequences and put it into the list of UIDs
- */
-GpgmeError
-_gpgme_key_append_name (GpgmeKey key, const char *s)
+
+unsigned long
+gpgme_key_get_ulong_attr (gpgme_key_t key, _gpgme_attr_t what,
+                         const void *reserved, int idx)
 {
-  struct user_id_s *uid;
-  char *d;
+  gpgme_subkey_t subkey;
+  gpgme_user_id_t uid;
+  int i;
 
-  assert (key);
-  /* We can malloc a buffer of the same length, because the converted
-     string will never be larger. Actually we allocate it twice the
-     size, so that we are able to store the parsed stuff there too.  */
-  uid = xtrymalloc ( sizeof *uid + 2*strlen (s)+3);
-  if (!uid)
-    return mk_error (Out_Of_Core);
-  uid->revoked = 0;
-  uid->invalid = 0;
-  uid->validity = 0;
-  uid->name_part = NULL;
-  uid->email_part = NULL;
-  uid->comment_part = NULL;
-  uid->next = NULL;
-  d = uid->name;
-
-  while (*s)
+  if (!key || reserved || idx < 0)
+    return 0;
+
+  /* Select IDXth subkey.  */
+  subkey = key->subkeys;
+  for (i = 0; i < idx; i++)
     {
-      if (*s != '\\')
-       *d++ = *s++;
-      else if (s[1] == '\\')
-       {
-         s++;
-         *d++ = *s++; 
-        }
-      else if (s[1] == 'n')
-       {
-         s += 2;
-         *d++ = '\n'; 
-        }
-      else if (s[1] == 'r')
-       {
-         s += 2;
-         *d++ = '\r'; 
-        }
-      else if (s[1] == 'v')
-       {
-         s += 2;
-         *d++ = '\v'; 
-        }
-      else if (s[1] == 'b')
-       {
-         s += 2;
-         *d++ = '\b'; 
-        }
-      else if (s[1] == '0')
-       {
-         /* Hmmm: no way to express this */
-         s += 2;
-         *d++ = '\\';
-         *d++ = '\0'; 
-        }
-      else if (s[1] == 'x' && my_isdigit (s[2]) && my_isdigit (s[3]))
-       {
-         unsigned int val = (s[2]-'0')*16 + (s[3]-'0');
-         if (!val)
-           {
-             *d++ = '\\';
-             *d++ = '\0'; 
-            }
-         else 
-           *(byte*)d++ = val;
-         s += 3;
-        }
-      else
-       {
-         /* should not happen */
-         s++;
-         *d++ = '\\'; 
-         *d++ = *s++;
-        } 
+      subkey = subkey->next;
+      if (!subkey)
+       break;
     }
-  *d++ = 0;
-  if (key->x509)
-    parse_x509_user_id (uid, d);
-  else
-    parse_user_id (uid, d);
 
-  if (key->uids)
+  /* Select the IDXth user ID.  */
+  uid = key->uids;
+  for (i = 0; i < idx; i++)
     {
-      struct user_id_s *u = key->uids;
-      while (u->next)
-       u = u->next;
-      u->next = uid;
+      uid = uid->next;
+      if (!uid)
+       break;
     }
-  else
-    key->uids = uid;
 
-  return 0;
-}
+  switch (what)
+    {
+    case GPGME_ATTR_ALGO:
+      return subkey ? (unsigned long) subkey->pubkey_algo : 0;
 
+    case GPGME_ATTR_LEN:
+      return subkey ? (unsigned long) subkey->length : 0;
 
-static void
-add_otag ( GpgmeData d, const char *tag )
-{
-    _gpgme_data_append_string ( d, "    <" );
-    _gpgme_data_append_string ( d, tag );
-    _gpgme_data_append_string ( d, ">" );
-}
+    case GPGME_ATTR_TYPE:
+      return key->protocol == GPGME_PROTOCOL_CMS ? 1 : 0;
 
-static void
-add_ctag ( GpgmeData d, const char *tag )
-{
-    _gpgme_data_append_string ( d, "</" );
-    _gpgme_data_append_string ( d, tag );
-    _gpgme_data_append_string ( d, ">\n" );
-}
+    case GPGME_ATTR_CREATED:
+      return (subkey && subkey->timestamp >= 0)
+       ? (unsigned long) subkey->timestamp : 0;
 
-static void
-add_tag_and_string ( GpgmeData d, const char *tag, const char *string )
-{
-    add_otag (d, tag);
-    _gpgme_data_append_string_for_xml ( d, string );
-    add_ctag (d, tag); 
-}
+    case GPGME_ATTR_EXPIRE: 
+      return (subkey && subkey->expires >= 0)
+       ? (unsigned long) subkey->expires : 0;
 
-static void
-add_tag_and_uint ( GpgmeData d, const char *tag, unsigned int val )
-{
-    char buf[30];
-    sprintf (buf, "%u", val );
-    add_tag_and_string ( d, tag, buf );
-}
+    case GPGME_ATTR_VALIDITY:
+      return uid ? uid->validity : 0;
 
-static void
-add_tag_and_time ( GpgmeData d, const char *tag, time_t val )
-{
-    char buf[30];
+    case GPGME_ATTR_OTRUST:
+      return key->owner_trust;
 
-    if (!val || val == (time_t)-1 )
-        return;
-    sprintf (buf, "%lu", (unsigned long)val );
-    add_tag_and_string ( d, tag, buf );
-}
+    case GPGME_ATTR_IS_SECRET:
+      return !!key->secret;
 
-static void
-one_uid_as_xml (GpgmeData d, struct user_id_s *u)
-{
-    _gpgme_data_append_string (d, "  <userid>\n");
-    if ( u->invalid )
-        _gpgme_data_append_string ( d, "    <invalid/>\n");
-    if ( u->revoked )
-        _gpgme_data_append_string ( d, "    <revoked/>\n");
-    add_tag_and_string ( d, "raw", u->name );
-    if ( *u->name_part )
-        add_tag_and_string ( d, "name", u->name_part );
-    if ( *u->email_part )
-        add_tag_and_string ( d, "email", u->email_part );
-    if ( *u->comment_part )
-        add_tag_and_string ( d, "comment", u->comment_part );
-    _gpgme_data_append_string (d, "  </userid>\n");
+    case GPGME_ATTR_KEY_REVOKED:
+      return subkey ? subkey->revoked : 0;
+
+    case GPGME_ATTR_KEY_INVALID:
+      return subkey ? subkey->invalid : 0;
+
+    case GPGME_ATTR_KEY_EXPIRED:
+      return subkey ? subkey->expired : 0;
+
+    case GPGME_ATTR_KEY_DISABLED:
+      return subkey ? subkey->disabled : 0;
+
+    case GPGME_ATTR_UID_REVOKED:
+      return uid ? uid->revoked : 0;
+
+    case GPGME_ATTR_UID_INVALID:
+      return uid ? uid->invalid : 0;
+
+    case GPGME_ATTR_CAN_ENCRYPT:
+      return key->can_encrypt;
+
+    case GPGME_ATTR_CAN_SIGN:
+      return key->can_sign;
+
+    case GPGME_ATTR_CAN_CERTIFY:
+      return key->can_certify;
+
+    default:
+      return 0;
+    }
 }
 
 
-/**
- * gpgme_key_get_as_xml:
- * @key: Key object
- * 
- * Return the key object as an XML string.  The classer has to free
- * that string.
- * 
- * Return value:  An XML string or NULL in case of a memory problem or
- *                a NULL passed as @key
- **/
-char *
-gpgme_key_get_as_xml ( GpgmeKey key )
+static gpgme_key_sig_t
+get_keysig (gpgme_key_t key, int uid_idx, int idx)
 {
-  GpgmeData d;
-  struct user_id_s *u;
-  struct subkey_s *k;
-  
-  if ( !key )
+  gpgme_user_id_t uid;
+  gpgme_key_sig_t sig;
+
+  if (!key || uid_idx < 0 || idx < 0)
     return NULL;
-  
-  if ( gpgme_data_new ( &d ) )
+
+  uid = key->uids;
+  while (uid && uid_idx > 0)
+    {
+      uid = uid->next;
+      uid_idx--;
+    }
+  if (!uid)
     return NULL;
-  
-  _gpgme_data_append_string ( d, "<GnupgKeyblock>\n"
-                              "  <mainkey>\n" );
-  if ( key->keys.secret )
-    _gpgme_data_append_string ( d, "    <secret/>\n");
-  if ( key->keys.flags.invalid )
-    _gpgme_data_append_string ( d, "    <invalid/>\n");
-  if ( key->keys.flags.revoked )
-    _gpgme_data_append_string ( d, "    <revoked/>\n");
-  if ( key->keys.flags.expired )
-    _gpgme_data_append_string ( d, "    <expired/>\n");
-  if ( key->keys.flags.disabled )
-    _gpgme_data_append_string ( d, "    <disabled/>\n");
-  add_tag_and_string (d, "keyid", key->keys.keyid );   
-  if (key->keys.fingerprint)
-    add_tag_and_string (d, "fpr", key->keys.fingerprint );
-  add_tag_and_uint (d, "algo", key->keys.key_algo );
-  add_tag_and_uint (d, "len", key->keys.key_len );
-  add_tag_and_time (d, "created", key->keys.timestamp );
-  add_tag_and_time (d, "expire", key->keys.expires_at );
-  if (key->issuer_serial)
-    add_tag_and_string (d, "serial", key->issuer_serial);
-  if (key->issuer_name)
-    add_tag_and_string (d, "issuer", key->issuer_name);
-  if (key->chain_id)
-    add_tag_and_string (d, "chainid", key->chain_id);
-  _gpgme_data_append_string (d, "  </mainkey>\n");
-  
-  /* Now the user IDs.  */
-  for (u = key->uids; u; u = u->next)
-    one_uid_as_xml (d,u);
-  
-  /* and now the subkeys */
-  for (k=key->keys.next; k; k = k->next )
+
+  sig = uid->signatures;
+  while (sig && idx > 0)
     {
-      _gpgme_data_append_string (d, "  <subkey>\n");
-      if ( k->secret )
-        _gpgme_data_append_string ( d, "    <secret/>\n");
-      if ( k->flags.invalid )
-        _gpgme_data_append_string ( d, "    <invalid/>\n");
-      if ( k->flags.revoked )
-        _gpgme_data_append_string ( d, "    <revoked/>\n");
-      if ( k->flags.expired )
-        _gpgme_data_append_string ( d, "    <expired/>\n");
-      if ( k->flags.disabled )
-        _gpgme_data_append_string ( d, "    <disabled/>\n");
-      add_tag_and_string (d, "keyid", k->keyid );   
-      if (k->fingerprint)
-        add_tag_and_string (d, "fpr", k->fingerprint );
-      add_tag_and_uint (d, "algo", k->key_algo );
-      add_tag_and_uint (d, "len", k->key_len );
-      add_tag_and_time (d, "created", k->timestamp );
-      add_tag_and_time (d, "expire", k->expires_at );
-      _gpgme_data_append_string (d, "  </subkey>\n");
+      sig = sig->next;
+      idx--;
     }
-  _gpgme_data_append_string ( d, "</GnupgKeyblock>\n" );
-  
-  return _gpgme_data_release_and_return_string (d);
+  return sig;
 }
 
 
-static const char *
-capabilities_to_string (struct subkey_s *k)
+const char *
+gpgme_key_sig_get_string_attr (gpgme_key_t key, int uid_idx,
+                              _gpgme_attr_t what,
+                              const void *reserved, int idx)
 {
-    static char *strings[8] = {
-        "",
-        "c",
-        "s",
-        "sc",
-        "e",
-        "ec",
-        "es",
-        "esc"
-    };
-    return strings[  (!!k->flags.can_encrypt << 2)
-                   | (!!k->flags.can_sign    << 1)
-                   | (!!k->flags.can_certify     ) ];
-}
+  gpgme_key_sig_t certsig = get_keysig (key, uid_idx, idx);
 
+  if (!certsig || reserved)
+    return NULL;
 
+  switch (what)
+    {
+    case GPGME_ATTR_KEYID:
+      return certsig->keyid;
 
-/**
- * gpgme_key_get_string_attr:
- * @key: Key Object
- * @what: Attribute specifier
- * @reserved: Must be 0
- * @idx: Index counter
- * 
- * Return a attribute as specified by @what and @idx.  Note that not
- * all attributes can be returned as a string, in which case NULL is
- * returned.  @idx is used to iterate through attributes which do have
- * more than one instance (e.g. user IDs or sub keys).
- * 
- * Return value: NULL or an const string which is only valid as long
- * as the key object itself is valid.
- **/
-const char *
-gpgme_key_get_string_attr ( GpgmeKey key, GpgmeAttr what,
-                            const void *reserved, int idx )
-{
-    const char *val = NULL;
-    struct subkey_s *k;
-    struct user_id_s *u;
-
-    if (!key)
-        return NULL;
-    if (reserved)
-        return NULL;
-    if (idx < 0)
-        return NULL;
-
-    switch (what) {
-      case GPGME_ATTR_KEYID:
-        for (k=&key->keys; k && idx; k=k->next, idx-- )
-            ;
-        if (k) 
-            val = k->keyid;
-        break;
-      case GPGME_ATTR_FPR:
-        for (k=&key->keys; k && idx; k=k->next, idx-- )
-            ;
-        if (k) 
-            val = k->fingerprint;
-        break;
-      case GPGME_ATTR_ALGO:    
-        for (k=&key->keys; k && idx; k=k->next, idx-- )
-            ;
-        if (k) 
-            val = pkalgo_to_string (k->key_algo);
-        break;
-      case GPGME_ATTR_LEN:     
-      case GPGME_ATTR_CREATED: 
-      case GPGME_ATTR_EXPIRE:  
-        break; /* use another get function */
-      case GPGME_ATTR_OTRUST:  
-        val = "[fixme]";
-        break;
-      case GPGME_ATTR_USERID:  
-        for (u=key->uids; u && idx; u=u->next, idx-- )
-            ;
-        val = u? u->name : NULL;
-        break;
-      case GPGME_ATTR_NAME:   
-        for (u=key->uids; u && idx; u=u->next, idx-- )
-            ;
-        val = u? u->name_part : NULL;
-        break;
-      case GPGME_ATTR_EMAIL:
-        for (u=key->uids; u && idx; u=u->next, idx-- )
-            ;
-        val = u? u->email_part : NULL;
-        break;
-      case GPGME_ATTR_COMMENT:
-        for (u=key->uids; u && idx; u=u->next, idx-- )
-            ;
-        val = u? u->comment_part : NULL;
-        break;
-      case GPGME_ATTR_VALIDITY:
-        for (u=key->uids; u && idx; u=u->next, idx-- )
-            ;
-        if (u) {
-            switch (u->validity) {
-              case GPGME_VALIDITY_UNKNOWN:   val = "?"; break;
-              case GPGME_VALIDITY_UNDEFINED: val = "q"; break;
-              case GPGME_VALIDITY_NEVER:     val = "n"; break;
-              case GPGME_VALIDITY_MARGINAL:  val = "m"; break;
-              case GPGME_VALIDITY_FULL:      val = "f"; break;
-              case GPGME_VALIDITY_ULTIMATE:  val = "u"; break;
-            }
-        }
-        break;
-      case GPGME_ATTR_LEVEL:  /* not used here */
-      case GPGME_ATTR_TYPE:
-      case GPGME_ATTR_KEY_REVOKED:
-      case GPGME_ATTR_KEY_INVALID:
-      case GPGME_ATTR_KEY_EXPIRED:
-      case GPGME_ATTR_KEY_DISABLED:
-      case GPGME_ATTR_UID_REVOKED:
-      case GPGME_ATTR_UID_INVALID:
-      case GPGME_ATTR_CAN_ENCRYPT:
-      case GPGME_ATTR_CAN_SIGN:
-      case GPGME_ATTR_CAN_CERTIFY:
-        break;
-      case GPGME_ATTR_IS_SECRET:
-        if (key->secret)
-            val = "1";
-        break;
-      case GPGME_ATTR_KEY_CAPS:    
-        for (k=&key->keys; k && idx; k=k->next, idx-- )
-            ;
-        if (k) 
-            val = capabilities_to_string (k);
-        break;
-      case GPGME_ATTR_SERIAL:
-        val = key->issuer_serial;
-        break;
-      case GPGME_ATTR_ISSUER:
-        val = key->issuer_name;
-        break;
-      case GPGME_ATTR_CHAINID:
-        val = key->chain_id;
-        break;
+    case GPGME_ATTR_ALGO:    
+      return gpgme_pubkey_algo_name (certsig->pubkey_algo);
+
+    case GPGME_ATTR_USERID:
+      return certsig->uid;
+
+    case GPGME_ATTR_NAME:   
+      return certsig->name;
+
+    case GPGME_ATTR_EMAIL:
+      return certsig->email;
+
+    case GPGME_ATTR_COMMENT:
+      return certsig->comment;
+   
+    default:
+      return NULL;
     }
-    return val;
 }
 
 
-/**
- * gpgme_key_get_ulong_attr:
- * @key: 
- * @what: 
- * @reserved: 
- * @idx: 
- * 
- * Return a attribute as specified by @what and @idx.  Note that not
- * all attributes can be returned as an integer, in which case 0 is
- * returned.  @idx is used to iterate through attributes which do have
- * more than one instance (e.g. user IDs or sub keys).
- *
- * See gpgme.h for a list of attributes.
- * 
- * Return value: 0 or the requested value.
- **/
 unsigned long
-gpgme_key_get_ulong_attr ( GpgmeKey key, GpgmeAttr what,
-                           const void *reserved, int idx )
+gpgme_key_sig_get_ulong_attr (gpgme_key_t key, int uid_idx, _gpgme_attr_t what,
+                             const void *reserved, int idx)
 {
-    unsigned long val = 0;
-    struct subkey_s *k;
-    struct user_id_s *u;
-
-    if (!key)
-        return 0;
-    if (reserved)
-        return 0;
-    if (idx < 0)
-        return 0;
-
-    switch (what) {
-      case GPGME_ATTR_ALGO:    
-        for (k=&key->keys; k && idx; k=k->next, idx-- )
-            ;
-        if (k) 
-            val = (unsigned long)k->key_algo;
-        break;
-      case GPGME_ATTR_LEN:     
-        for (k=&key->keys; k && idx; k=k->next, idx-- )
-            ;
-        if (k) 
-            val = (unsigned long)k->key_len;
-        break;
-      case GPGME_ATTR_CREATED: 
-        for (k=&key->keys; k && idx; k=k->next, idx-- )
-            ;
-        if (k) 
-            val = k->timestamp < 0? 0L:(unsigned long)k->timestamp;
-        break;
-      case GPGME_ATTR_EXPIRE: 
-        for (k=&key->keys; k && idx; k=k->next, idx-- )
-            ;
-        if (k) 
-            val = k->expires_at < 0? 0L:(unsigned long)k->expires_at;
-        break;
-      case GPGME_ATTR_VALIDITY:
-        for (u=key->uids; u && idx; u=u->next, idx-- )
-            ;
-        if (u)
-            val = u->validity;
-        break;
-      case GPGME_ATTR_IS_SECRET:
-        val = !!key->secret;
-        break;
-      case GPGME_ATTR_KEY_REVOKED:
-        for (k=&key->keys; k && idx; k=k->next, idx-- )
-            ;
-        if (k) 
-            val = k->flags.revoked;
-        break;
-      case GPGME_ATTR_KEY_INVALID:
-        for (k=&key->keys; k && idx; k=k->next, idx-- )
-            ;
-        if (k) 
-            val = k->flags.invalid;
-        break;
-      case GPGME_ATTR_KEY_EXPIRED:
-        for (k=&key->keys; k && idx; k=k->next, idx-- )
-            ;
-        if (k) 
-            val = k->flags.expired;
-        break;
-      case GPGME_ATTR_KEY_DISABLED:
-        for (k=&key->keys; k && idx; k=k->next, idx-- )
-            ;
-        if (k) 
-            val = k->flags.disabled;
-        break;
-      case GPGME_ATTR_UID_REVOKED:
-        for (u=key->uids; u && idx; u=u->next, idx-- )
-            ;
-        if (u)
-            val = u->revoked;
-        break;
-      case GPGME_ATTR_UID_INVALID:
-        for (u=key->uids; u && idx; u=u->next, idx-- )
-            ;
-        if (u)
-            val = u->invalid;
-        break;
-      case GPGME_ATTR_CAN_ENCRYPT:
-        val = key->gloflags.can_certify;
-        break;
-      case GPGME_ATTR_CAN_SIGN:
-        val = key->gloflags.can_sign;
-        break;
-      case GPGME_ATTR_CAN_CERTIFY:
-        val = key->gloflags.can_encrypt;
-        break;
-      default:
-        break;
+  gpgme_key_sig_t certsig = get_keysig (key, uid_idx, idx);
+
+  if (!certsig || reserved)
+    return 0;
+
+  switch (what)
+    {
+    case GPGME_ATTR_ALGO:    
+      return (unsigned long) certsig->pubkey_algo;
+
+    case GPGME_ATTR_CREATED: 
+      return certsig->timestamp < 0 ? 0L : (unsigned long) certsig->timestamp;
+
+    case GPGME_ATTR_EXPIRE: 
+      return certsig->expires < 0 ? 0L : (unsigned long) certsig->expires;
+
+    case GPGME_ATTR_KEY_REVOKED:
+      return certsig->revoked;
+
+    case GPGME_ATTR_KEY_INVALID:
+      return certsig->invalid;
+
+    case GPGME_ATTR_KEY_EXPIRED:
+      return certsig->expired;
+
+    case GPGME_ATTR_SIG_CLASS:
+      return certsig->sig_class;
+
+    case GPGME_ATTR_SIG_STATUS:
+      return certsig->status;
+
+    default:
+      return 0;
     }
-    return val;
 }