2004-09-30 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / gpgme / key.c
index 0b3b787..444f067 100644 (file)
-/* key.c - Key and keyList objects
- *     Copyright (C) 2000 Werner Koch (dd9jn)
- *      Copyright (C) 2001, 2002 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
+/* Create a new key.  */
+gpgme_error_t
+_gpgme_key_new (gpgme_key_t *r_key)
 {
-  struct key_cache_item_s *next;
-  GpgmeKey key;
-};
-
-/* Protects all key_cache_* variables.  */
-DEFINE_STATIC_LOCK (key_cache_lock);
-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;
+  gpgme_key_t key;
 
-/* Protects all reference counters in keys.  All other accesses to a
-   key are either read only or happen before the key is entered into
-   the cache.  */
-DEFINE_STATIC_LOCK (key_ref_lock);
+  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;
+  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)
 {
-  LOCK (key_cache_lock);
-  if (!key_cache_initialized)
-    {
-      key_cache_size = 503;
-      key_cache = xtrycalloc (key_cache_size, sizeof *key_cache);
-      if (!key_cache)
-       {
-         key_cache_size = 0;
-         key_cache_initialized = 1;
-       }
-      else
-       {
-         /* 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;
-       }
-    }
-  UNLOCK (key_cache_lock);
+  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;
-
-  _gpgme_key_cache_init ();
-
-  LOCK (key_cache_lock);
-  /* Check if cache was enabled.  */
-  if (!key_cache_size)
-    {
-      UNLOCK (key_cache_lock);
-      return;
-    }
+  const char *start = NULL;
+  int in_name = 0;
+  int in_email = 0;
+  int in_comment = 0;
 
-  /* 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)
+  while (*src)
     {
-      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++)
+      if (in_email)
        {
-         struct subkey_s *k2;
-         if (item->key == key) 
-           /* Already in cache.  */
-           break;
-         /* Now do a deeper check.  */
-         for (k2 = &item->key->keys; k2; k2 = k2->next)
+         if (*src == '<')
+           /* Not legal but anyway.  */
+           in_email++;
+         else if (*src == '>')
            {
-             if (k2->fingerprint && !strcmp (k->fingerprint, k2->fingerprint))
+             if (!--in_email && !*email)
                {
-                 /* Okay, replace it with the new copy.  */
-                 gpgme_key_unref (item->key);
-                 item->key = key;
-                 gpgme_key_ref (item->key);
-                 UNLOCK (key_cache_lock);
-                 return;
-                }
-            }
-        }
-      if (item)
-       continue;
-        
-      if (n > key_cache_max_chain_length)
+                 *email = tail;
+                 tail = set_user_id_part (tail, start, src - start);
+               }
+           }
+       }
+      else if (in_comment)
        {
-         /* 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)
+         if (*src == '(')
+           in_comment++;
+         else if (*src == ')')
            {
-             struct key_cache_item_s *next;
-
-             assert (last->next == item);
-             last->next = NULL;
-             for (; item; item = next)
+             if (!--in_comment && !*comment)
                {
-                 next = item->next;
-                 gpgme_key_unref (item->key);
-                 item->key = NULL;
-                 item->next = key_cache_unused_items;
-                 key_cache_unused_items = item;
-                }
-            }
-        }
-
-      item = key_cache_unused_items;
-      if (item)
+                 *comment = tail;
+                 tail = set_user_id_part (tail, start, src - start);
+               }
+           }
+       }
+      else if (*src == '<')
        {
-         key_cache_unused_items = item->next;
-         item->next = NULL;
-        }
-      else
+         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 == '(')
        {
-         item = xtrymalloc (sizeof *item);
-         if (!item)
+         if (in_name)
            {
-             UNLOCK (key_cache_lock);
-             return;
+             if (!*name)
+               {
+                 *name = tail;
+                 tail = set_user_id_part (tail, start, src - start);
+               }
+             in_name = 0;
            }
-        }
-
-      item->key = key;
-      gpgme_key_ref (key);
-      item->next = key_cache[hash];
-      key_cache[hash] = item;
-    }
-  UNLOCK (key_cache_lock);
-}
-
-
-GpgmeKey 
-_gpgme_key_cache_get (const char *fpr)
-{
-  struct key_cache_item_s *item;
-  unsigned int hash;
-
-  LOCK (key_cache_lock);
-  /* Check if cache is enabled already.  */
-  if (!key_cache_size)
-    {
-      UNLOCK (key_cache_lock);
-      return NULL;
-    }
-
-  if (hash_key (fpr, &hash))
-    {
-      UNLOCK (key_cache_lock);
-      return NULL;
+         in_comment = 1;
+         start = src + 1;
+       }
+      else if (!in_name && *src != ' ' && *src != '\t')
+       {
+         in_name = 1;
+         start = src;
+       }    
+      src++;
     }
-
-  hash %= key_cache_size;
-  for (item = key_cache[hash]; item; item = item->next)
+  if (in_name)
     {
-      struct subkey_s *k;
-
-      for (k = &item->key->keys; k; k = k->next)
+      if (!*name)
        {
-         if (k->fingerprint && !strcmp (k->fingerprint, fpr))
-           {
-             gpgme_key_ref (item->key);
-             UNLOCK (key_cache_lock);
-             return item->key;
-            }
-        }
+         *name = tail;
+         tail = set_user_id_part (tail, start, src - start);
+       }
     }
-  UNLOCK (key_cache_lock);
-  return NULL;
+  /* Let unused parts point to an EOS.  */
+  tail--;
+  if (!*name)
+    *name = tail;
+  if (!*email)
+    *email = tail;
+  if (!*comment)
+    *comment = tail;
 }
 
 
-static const char *
-pkalgo_to_string (int algo)
+static void
+parse_x509_user_id (char *src, char **name, char **email,
+                   char **comment, char *tail)
 {
-  switch (algo)
-    {
-    case 1: 
-    case 2:
-    case 3:
-      return "RSA";
-    case 16:
-    case 20:
-      return "ElG";
-    case 17:
-      return "DSA";
-    default:
-      return "Unknown";
-    }
+  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;
 }
 
 
-static GpgmeError
-key_new (GpgmeKey *r_key, int secret)
+/* 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)
 {
-  GpgmeKey key;
+  gpgme_user_id_t uid;
+  char *dst;
+  int src_len = strlen (src);
 
-  *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;
-}
+  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);
 
-GpgmeError
-_gpgme_key_new (GpgmeKey *r_key)
-{
-  return key_new (r_key, 0);
-}
+  uid->uid = ((char *) uid) + sizeof (*uid);
+  dst = uid->uid;
+  _gpgme_decode_c_string (src, &dst, src_len + 1);
 
-GpgmeError
-_gpgme_key_new_secret (GpgmeKey *r_key)
-{
-  return key_new (r_key, 1);
-}
+  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);
 
+  if (!key->uids)
+    key->uids = uid;
+  if (key->_last_uid)
+    key->_last_uid->next = uid;
+  key->_last_uid = uid;
 
-/**
- * gpgme_key_ref:
- * @key: Key object
- * 
- * To safe memory the Key objects implements reference counting.
- * Use this function to bump the reference counter.
- **/
-void
-gpgme_key_ref (GpgmeKey key)
-{
-  return_if_fail (key);
-  LOCK (key_ref_lock);
-  key->ref_count++;
-  UNLOCK (key_ref_lock);
+  return 0;
 }
 
 
-static struct subkey_s *
-add_subkey (GpgmeKey key, int secret)
+gpgme_key_sig_t
+_gpgme_key_add_sig (gpgme_key_t key, char *src)
 {
-  struct subkey_s *k, *kk;
+  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 */
 
-  k = xtrycalloc (1, sizeof *k);
-  if (!k)
+  /* 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);
 
-  if(!(kk = key->keys.next))
-    key->keys.next = k;
-  else
+  sig->keyid = sig->_keyid;
+  sig->_keyid[16] = '\0';
+  sig->uid = ((char *) sig) + sizeof (*sig);
+
+  if (src)
     {
-      while (kk->next)
-       kk = kk->next;
-      kk->next = k;
+      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 (secret)
-    k->secret = 1;
-  return k;
-}
 
+  if (!uid->signatures)
+    uid->signatures = sig;
+  if (uid->_last_keysig)
+    uid->_last_keysig->next = sig;
+  uid->_last_keysig = sig;
 
-struct subkey_s *
-_gpgme_key_add_subkey (GpgmeKey key)
-{
-  return add_subkey (key, 0);
+  return sig;
 }
 
-
-struct subkey_s *
-_gpgme_key_add_secret_subkey (GpgmeKey key)
+\f
+/* Acquire a reference to KEY.  */
+void
+gpgme_key_ref (gpgme_key_t key)
 {
-  return add_subkey (key, 1);
+  LOCK (key_ref_lock);
+  key->_refs++;
+  UNLOCK (key_ref_lock);
 }
 
 
-/**
- * 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.
- **/
+/* 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_release (GpgmeKey key)
+gpgme_key_unref (gpgme_key_t key)
 {
-  struct user_id_s *u, *u2;
-  struct subkey_s *k, *k2;
+  gpgme_user_id_t uid;
+  gpgme_subkey_t subkey;
 
   if (!key)
     return;
 
   LOCK (key_ref_lock);
-  assert (key->ref_count);
-  if (--key->ref_count)
+  assert (key->_refs > 0);
+  if (--key->_refs)
     {
       UNLOCK (key_ref_lock);
       return;
     }
   UNLOCK (key_ref_lock);
 
-  xfree (key->keys.fingerprint);
-  for (k = key->keys.next; k; k = k2)
+  subkey = key->subkeys;
+  while (subkey)
     {
-      k2 = k->next;
-      xfree (k->fingerprint);
-      xfree (k);
+      gpgme_subkey_t next = subkey->next;
+      if (subkey->fpr)
+       free (subkey->fpr);
+      free (subkey);
+      subkey = next;
     }
-  for (u = key->uids; u; u = u2)
+
+  uid = key->uids;
+  while (uid)
     {
-      u2 = u->next;
-      xfree (u);
-    }
-  xfree (key->issuer_serial);
-  xfree (key->issuer_name);
-  xfree (key->chain_id);
-  xfree (key);
-}
+      gpgme_user_id_t next_uid = uid->next;
+      gpgme_key_sig_t keysig = uid->signatures;
 
-/**
- * gpgme_key_unref:
- * @key: Key Object
- * 
- * This is an alias for gpgme_key_release().
- **/
-void
-gpgme_key_unref (GpgmeKey key)
-{
-  gpgme_key_release (key);
-}
+      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);
 
-static char *
-set_user_id_part (char *tail, const char *buf, size_t len)
-{
-  while (len && (buf[len-1] == ' ' || buf[len-1] == '\t')) 
-    len--;
-  for (; len; len--)
-    *tail++ = *buf++;
-  *tail++ = 0;
-  return tail;
+  free (key);
 }
 
+\f
+/* Compatibility interfaces.  */
 
-static void
-parse_user_id (struct user_id_s *uid, char *tail)
+void
+gpgme_key_release (gpgme_key_t key)
 {
-  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 == '<')
-             /* Not legal but anyway.  */
-             in_email++;
-            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;
-         }    
-      }
-    if (in_name)
-      {
-        if (!uid->name_part)
-         {
-            uid->name_part = tail;
-            tail = set_user_id_part (tail, start, s-start);
-         }
-      }
-    /* 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;
+  gpgme_key_unref (key);
 }
 
-static void
-parse_x509_user_id (struct user_id_s *uid, char *tail)
-{
-  const char *s;
-
-  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;
-}
 
-/* 
- * 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)
+static const char *
+otrust_to_string (int otrust)
 {
-  struct user_id_s *uid;
-  char *d;
-
-  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 (*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++;
-        } 
-    }
-  *d++ = 0;
-  if (key->x509)
-    parse_x509_user_id (uid, d);
-  else
-    parse_user_id (uid, d);
-
-  if (key->uids)
+  switch (otrust)
     {
-      struct user_id_s *u = key->uids;
-      while (u->next)
-       u = u->next;
-      u->next = uid;
-    }
-  else
-    key->uids = uid;
-
-  return 0;
-}
+    case GPGME_VALIDITY_NEVER:
+      return "n";
 
+    case GPGME_VALIDITY_MARGINAL:
+      return "m";
 
-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_VALIDITY_FULL:
+      return "f";
 
-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_VALIDITY_ULTIMATE:
+      return "u";
 
-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); 
+    default:
+      return "?";
+    }
 }
 
-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);
-}
 
-static void
-add_tag_and_time (GpgmeData d, const char *tag, time_t val)
+static const char *
+validity_to_string (int validity)
 {
-  char buf[30];
+  switch (validity)
+    {
+    case GPGME_VALIDITY_UNDEFINED:
+      return "q";
 
-  if (!val || val == (time_t) - 1)
-    return;
-  sprintf (buf, "%lu", (unsigned long) val);
-  add_tag_and_string (d, tag, buf);
-}
+    case GPGME_VALIDITY_NEVER:
+      return "n";
 
-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_VALIDITY_MARGINAL:
+      return "m";
 
+    case GPGME_VALIDITY_FULL:
+      return "f";
 
-/**
- * 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)
-{
-  GpgmeData d;
-  struct user_id_s *u;
-  struct subkey_s *k;
-  
-  if (!key)
-    return NULL;
-  
-  if (gpgme_data_new (&d))
-    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");
+    case GPGME_VALIDITY_ULTIMATE:
+      return "u";
 
-  /* 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)
-    {
-      _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");
+    case GPGME_VALIDITY_UNKNOWN:
+    default:
+      return "?";
     }
-  _gpgme_data_append_string (d, "</GnupgKeyblock>\n");
-  
-  return _gpgme_data_release_and_return_string (d);
 }
 
 
 static const char *
-capabilities_to_string (struct subkey_s *k)
+capabilities_to_string (gpgme_subkey_t subkey)
 {
   static const char *const strings[8] =
     {
@@ -826,271 +429,283 @@ capabilities_to_string (struct subkey_s *k)
       "es",
       "esc"
     };
-  return strings[(!!k->flags.can_encrypt << 2)
-                | (!!k->flags.can_sign    << 1)
-                | (!!k->flags.can_certify     )];
+  return strings[(!!subkey->can_encrypt << 2)
+                | (!!subkey->can_sign << 1)
+                | (!!subkey->can_certify)];
 }
 
 
-/**
- * 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.
- **/
+/* Return the value of the attribute WHAT of ITEM, which has to be
+   representable by a string.  */
 const char *
-gpgme_key_get_string_attr (GpgmeKey key, GpgmeAttr what,
+gpgme_key_get_string_attr (gpgme_key_t key, _gpgme_attr_t what,
                           const void *reserved, int idx)
 {
-  const char *val = NULL;
-  struct subkey_s *k;
-  struct user_id_s *u;
+  gpgme_subkey_t subkey;
+  gpgme_user_id_t uid;
+  int i;
 
-  if (!key)
-    return NULL;
-  if (reserved)
-    return NULL;
-  if (idx < 0)
+  if (!key || reserved || idx < 0)
     return NULL;
 
+  /* Select IDXth subkey.  */
+  subkey = key->subkeys;
+  for (i = 0; i < idx; i++)
+    {
+      subkey = subkey->next;
+      if (!subkey)
+       break;
+    }
+
+  /* Select the IDXth user ID.  */
+  uid = key->uids;
+  for (i = 0; i < idx; i++)
+    {
+      uid = uid->next;
+      if (!uid)
+       break;
+    }
+
   switch (what)
     {
     case GPGME_ATTR_KEYID:
-      for (k = &key->keys; k && idx; k = k->next, idx--)
-       ;
-      if (k) 
-       val = k->keyid;
-      break;
+      return subkey ? subkey->keyid : NULL;
+
     case GPGME_ATTR_FPR:
-      for (k = &key->keys; k && idx; k = k->next, idx--)
-       ;
-      if (k) 
-       val = k->fingerprint;
-      break;
+      return subkey ? subkey->fpr : NULL;
+
     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:  
-      /* Use another get function.  */
-      break;
+      return subkey ? gpgme_pubkey_algo_name (subkey->pubkey_algo) : NULL;
+
+    case GPGME_ATTR_TYPE:
+      return key->protocol == GPGME_PROTOCOL_CMS ? "X.509" : "PGP";
+
     case GPGME_ATTR_OTRUST:
-      val = "[fixme]";
-      break;
+      return otrust_to_string (key->owner_trust);
+
     case GPGME_ATTR_USERID:  
-      for (u = key->uids; u && idx; u = u->next, idx--)
-       ;
-      val = u ? u->name : NULL;
-      break;
+      return uid ? uid->uid : NULL;
+
     case GPGME_ATTR_NAME:   
-      for (u = key->uids; u && idx; u = u->next, idx--)
-       ;
-      val = u ? u->name_part : NULL;
-      break;
+      return uid ? uid->name : NULL;
+
     case GPGME_ATTR_EMAIL:
-      for (u = key->uids; u && idx; u = u->next, idx--)
-       ;
-      val = u ? u->email_part : NULL;
-      break;
+      return uid ? uid->email : NULL;
+
     case GPGME_ATTR_COMMENT:
-      for (u = key->uids; u && idx; u = u->next, idx--)
-       ;
-      val = u ? u->comment_part : NULL;
-      break;
+      return uid ? uid->comment : NULL;
+
     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:
-    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:
-      /* Not used here.  */
-      break;
-    case GPGME_ATTR_IS_SECRET:
-      if (key->secret)
-       val = "1";
-      break;
+      return uid ? validity_to_string (uid->validity) : NULL;
+
     case GPGME_ATTR_KEY_CAPS:    
-      for (k = &key->keys; k && idx; k = k->next, idx--)
-       ;
-      if (k) 
-       val = capabilities_to_string (k);
-      break;
+      return subkey ? capabilities_to_string (subkey) : NULL;
+
     case GPGME_ATTR_SERIAL:
-      val = key->issuer_serial;
-      break;
+      return key->issuer_serial;
+
     case GPGME_ATTR_ISSUER:
-      val = key->issuer_name;
-      break;
+      return idx ? NULL : key->issuer_name;
+
     case GPGME_ATTR_CHAINID:
-      val = key->chain_id;
-      break;
-    case GPGME_ATTR_SIG_STATUS:
-      /* Not of any use here.  */
-      break;
+      return key->chain_id;
+
+    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,
+gpgme_key_get_ulong_attr (gpgme_key_t key, _gpgme_attr_t what,
                          const void *reserved, int idx)
 {
-  unsigned long val = 0;
-  struct subkey_s *k;
-  struct user_id_s *u;
+  gpgme_subkey_t subkey;
+  gpgme_user_id_t uid;
+  int i;
 
-  if (!key)
-    return 0;
-  if (reserved)
-    return 0;
-  if (idx < 0)
+  if (!key || reserved || idx < 0)
     return 0;
 
+  /* Select IDXth subkey.  */
+  subkey = key->subkeys;
+  for (i = 0; i < idx; i++)
+    {
+      subkey = subkey->next;
+      if (!subkey)
+       break;
+    }
+
+  /* Select the IDXth user ID.  */
+  uid = key->uids;
+  for (i = 0; i < idx; i++)
+    {
+      uid = uid->next;
+      if (!uid)
+       break;
+    }
+
   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_ALGO:
+      return subkey ? (unsigned long) subkey->pubkey_algo : 0;
+
+    case GPGME_ATTR_LEN:
+      return subkey ? (unsigned long) subkey->length : 0;
+
+    case GPGME_ATTR_TYPE:
+      return key->protocol == GPGME_PROTOCOL_CMS ? 1 : 0;
+
+    case GPGME_ATTR_CREATED:
+      return (subkey && subkey->timestamp >= 0)
+       ? (unsigned long) subkey->timestamp : 0;
+
     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;
+      return (subkey && subkey->expires >= 0)
+       ? (unsigned long) subkey->expires : 0;
+
     case GPGME_ATTR_VALIDITY:
-      for (u = key->uids; u && idx; u = u->next, idx--)
-       ;
-      if (u)
-       val = u->validity;
-      break;
+      return uid ? uid->validity : 0;
+
+    case GPGME_ATTR_OTRUST:
+      return key->owner_trust;
+
     case GPGME_ATTR_IS_SECRET:
-      val = !!key->secret;
-      break;
+      return !!key->secret;
+
     case GPGME_ATTR_KEY_REVOKED:
-      for (k = &key->keys; k && idx; k = k->next, idx--)
-       ;
-      if (k) 
-       val = k->flags.revoked;
-      break;
+      return subkey ? subkey->revoked : 0;
+
     case GPGME_ATTR_KEY_INVALID:
-      for (k = &key->keys; k && idx; k = k->next, idx--)
-       ;
-      if (k) 
-       val = k->flags.invalid;
-      break;
+      return subkey ? subkey->invalid : 0;
+
     case GPGME_ATTR_KEY_EXPIRED:
-      for (k = &key->keys; k && idx; k = k->next, idx--)
-       ;
-      if (k) 
-       val = k->flags.expired;
-      break;
+      return subkey ? subkey->expired : 0;
+
     case GPGME_ATTR_KEY_DISABLED:
-      for (k = &key->keys; k && idx; k = k->next, idx--)
-       ;
-      if (k) 
-       val = k->flags.disabled;
-      break;
+      return subkey ? subkey->disabled : 0;
+
     case GPGME_ATTR_UID_REVOKED:
-      for (u = key->uids; u && idx; u = u->next, idx--)
-       ;
-      if (u)
-       val = u->revoked;
-      break;
+      return uid ? uid->revoked : 0;
+
     case GPGME_ATTR_UID_INVALID:
-      for (u = key->uids; u && idx; u = u->next, idx--)
-       ;
-      if (u)
-       val = u->invalid;
-      break;
+      return uid ? uid->invalid : 0;
+
     case GPGME_ATTR_CAN_ENCRYPT:
-      val = key->gloflags.can_encrypt;
-      break;
+      return key->can_encrypt;
+
     case GPGME_ATTR_CAN_SIGN:
-      val = key->gloflags.can_sign;
-      break;
+      return key->can_sign;
+
     case GPGME_ATTR_CAN_CERTIFY:
-      val = key->gloflags.can_certify;
-      break;
+      return key->can_certify;
+
+    default:
+      return 0;
+    }
+}
+
+
+static gpgme_key_sig_t
+get_keysig (gpgme_key_t key, int uid_idx, int idx)
+{
+  gpgme_user_id_t uid;
+  gpgme_key_sig_t sig;
+
+  if (!key || uid_idx < 0 || idx < 0)
+    return NULL;
+
+  uid = key->uids;
+  while (uid && uid_idx > 0)
+    {
+      uid = uid->next;
+      uid_idx--;
+    }
+  if (!uid)
+    return NULL;
+
+  sig = uid->signatures;
+  while (sig && idx > 0)
+    {
+      sig = sig->next;
+      idx--;
+    }
+  return sig;
+}
+
+
+const char *
+gpgme_key_sig_get_string_attr (gpgme_key_t key, int uid_idx,
+                              _gpgme_attr_t what,
+                              const void *reserved, int idx)
+{
+  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;
+
+    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;
+    }
+}
+
+
+unsigned long
+gpgme_key_sig_get_ulong_attr (gpgme_key_t key, int uid_idx, _gpgme_attr_t what,
+                             const void *reserved, int idx)
+{
+  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:
-      break;
+      return 0;
     }
-  return val;
 }