2004-09-30 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / gpgme / key.c
index f00f8df..444f067 100644 (file)
-/* key.c - Key and keyList objects
- *     Copyright (C) 2000 Werner Koch (dd9jn)
- *
- * 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);
 
 
-static const char *
-pkalgo_to_string ( int algo )
+/* Create a new key.  */
+gpgme_error_t
+_gpgme_key_new (gpgme_key_t *r_key)
 {
-    switch (algo) {
-      case 1: 
-      case 2:
-      case 3: return "RSA";
-      case 16:
-      case 20: return "ElG";
-      case 17: return "DSA";
-      default: return "Unknown";
-    }
-}
+  gpgme_key_t key;
 
+  key = calloc (1, sizeof *key);
+  if (!key)
+    return gpg_error_from_errno (errno);
+  key->_refs = 1;
 
+  *r_key = key;
+  return 0;
+}
 
 
-GpgmeError
-_gpgme_key_new( GpgmeKey *r_key )
+gpgme_error_t
+_gpgme_key_add_subkey (gpgme_key_t key, gpgme_subkey_t *r_subkey)
 {
-    GpgmeKey key;
+  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;
+}
 
-    *r_key = NULL;
-    key = xtrycalloc ( 1, sizeof *key );
-    if (!key)
-        return mk_error (Out_Of_Core);
 
-    *r_key = key;
-    return 0;
+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;
 }
 
 
-struct subkey_s *
-_gpgme_key_add_subkey (GpgmeKey key)
+static void
+parse_user_id (char *src, char **name, char **email,
+              char **comment, char *tail)
 {
-    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;
+  const char *start = NULL;
+  int in_name = 0;
+  int in_email = 0;
+  int in_comment = 0;
+
+  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);
+       }
     }
-    return k;
+  /* Let unused parts point to an EOS.  */
+  tail--;
+  if (!*name)
+    *name = tail;
+  if (!*email)
+    *email = tail;
+  if (!*comment)
+    *comment = tail;
 }
 
 
-void
-_gpgme_key_release ( GpgmeKey key )
+static void
+parse_x509_user_id (char *src, char **name, char **email,
+                   char **comment, char *tail)
 {
-    struct user_id_s *u, *u2;
-    struct subkey_s *k, *k2;
+  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;
+}
 
-    if (!key)
-        return;
 
-    xfree (key->keys.fingerprint);
-    for (k = key->keys.next; k; k = k2 ) {
-        k2 = k->next;
-        xfree (k->fingerprint);
-        xfree (k);
-    }
-    for (u = key->uids; u; u = u2 ) {
-        u2 = u->next;
-        xfree (u);
+/* 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);
+
+  uid->uid = ((char *) uid) + sizeof (*uid);
+  dst = uid->uid;
+  _gpgme_decode_c_string (src, &dst, src_len + 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;
+
+  return 0;
+}
+
+
+gpgme_key_sig_t
+_gpgme_key_add_sig (gpgme_key_t key, char *src)
+{
+  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);
     }
-    xfree (key);
+
+  if (!uid->signatures)
+    uid->signatures = sig;
+  if (uid->_last_keysig)
+    uid->_last_keysig->next = sig;
+  uid->_last_keysig = sig;
+
+  return sig;
 }
 
-static char *
-set_user_id_part ( char *tail, const char *buf, size_t len )
+\f
+/* Acquire a reference to KEY.  */
+void
+gpgme_key_ref (gpgme_key_t key)
 {
-    while ( len && (buf[len-1] == ' ' || buf[len-1] == '\t') ) 
-        len--;
-    for ( ; len; len--)
-        *tail++ = *buf++;
-    *tail++ = 0;
-    return tail;
+  LOCK (key_ref_lock);
+  key->_refs++;
+  UNLOCK (key_ref_lock);
 }
 
 
-static void
-parse_user_id ( struct user_id_s *uid, char *tail )
+/* 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)
 {
-    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;
-        }    
+  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;
     }
 
-    if ( in_name ) {
-        if ( !uid->name_part ) {
-            uid->name_part = tail;
-            tail = set_user_id_part (tail, start, s-start );
+  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);
 
-    /* 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;
+  if (key->chain_id)
+    free (key->chain_id);
 
+  free (key);
 }
 
-/* 
- * 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 )
-{
-    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->validity = 0;
-    uid->name_part = NULL;
-    uid->email_part = NULL;
-    uid->comment_part = 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;
-    parse_user_id ( uid, d );
+\f
+/* Compatibility interfaces.  */
 
-    uid->next = key->uids;
-    key->uids = uid;
-    return 0;
+void
+gpgme_key_release (gpgme_key_t key)
+{
+  gpgme_key_unref (key);
 }
 
 
-static void
-add_otag ( GpgmeData d, const char *tag )
+static const char *
+otrust_to_string (int otrust)
 {
-    _gpgme_data_append_string ( d, "    <" );
-    _gpgme_data_append_string ( d, tag );
-    _gpgme_data_append_string ( d, ">" );
+  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 "?";
+    }
 }
 
-static void
-add_ctag ( GpgmeData d, const char *tag )
+
+static const char *
+validity_to_string (int validity)
 {
-    _gpgme_data_append_string ( d, "</" );
-    _gpgme_data_append_string ( d, tag );
-    _gpgme_data_append_string ( d, ">\n" );
+  switch (validity)
+    {
+    case GPGME_VALIDITY_UNDEFINED:
+      return "q";
+
+    case GPGME_VALIDITY_NEVER:
+      return "n";
+
+    case GPGME_VALIDITY_MARGINAL:
+      return "m";
+
+    case GPGME_VALIDITY_FULL:
+      return "f";
+
+    case GPGME_VALIDITY_ULTIMATE:
+      return "u";
+
+    case GPGME_VALIDITY_UNKNOWN:
+    default:
+      return "?";
+    }
 }
 
-static void
-add_tag_and_string ( GpgmeData d, const char *tag, const char *string )
+
+static const char *
+capabilities_to_string (gpgme_subkey_t subkey)
 {
-    add_otag (d, tag);
-    _gpgme_data_append_string_for_xml ( d, string );
-    add_ctag (d, tag); 
+  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 void
-add_tag_and_uint ( GpgmeData d, const char *tag, unsigned int val )
+
+/* 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)
 {
-    char buf[30];
-    sprintf (buf, "%u", val );
-    add_tag_and_string ( d, tag, buf );
+  gpgme_subkey_t subkey;
+  gpgme_user_id_t uid;
+  int i;
+
+  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:
+      return subkey ? subkey->keyid : NULL;
+
+    case GPGME_ATTR_FPR:
+      return subkey ? subkey->fpr : NULL;
+
+    case GPGME_ATTR_ALGO:    
+      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:
+      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;
+    }
 }
 
-static void
-add_tag_and_time ( GpgmeData d, const char *tag, time_t val )
+
+unsigned long
+gpgme_key_get_ulong_attr (gpgme_key_t key, _gpgme_attr_t what,
+                         const void *reserved, int idx)
 {
-    char buf[30];
+  gpgme_subkey_t subkey;
+  gpgme_user_id_t uid;
+  int i;
+
+  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:
+      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: 
+      return (subkey && subkey->expires >= 0)
+       ? (unsigned long) subkey->expires : 0;
+
+    case GPGME_ATTR_VALIDITY:
+      return uid ? uid->validity : 0;
+
+    case GPGME_ATTR_OTRUST:
+      return key->owner_trust;
+
+    case GPGME_ATTR_IS_SECRET:
+      return !!key->secret;
 
-    if (!val || val == (time_t)-1 )
-        return;
-    sprintf (buf, "%lu", (unsigned long)val );
-    add_tag_and_string ( d, tag, buf );
+    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;
+    }
 }
 
-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 )
-        return NULL;
-    
-    if ( gpgme_data_new ( &d ) )
-        return NULL;
-    
-    _gpgme_data_append_string ( d, "<GnupgKeyblock>\n"
-                                   "  <mainkey>\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, "expires", key->expires );*/
-    _gpgme_data_append_string (d, "  </mainkey>\n");
-
-    /* Now the user IDs */
-    for ( u = key->uids; u; u = u->next ) {
-        _gpgme_data_append_string (d, "  <userid>\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");
+  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--;
     }
-    
-    for (k=key->keys.next; k; k = k->next ) {
-        _gpgme_data_append_string (d, "  <subkey>\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 );
-        _gpgme_data_append_string (d, "  </subkey>\n");
+  if (!uid)
+    return NULL;
+
+  sig = uid->signatures;
+  while (sig && idx > 0)
+    {
+      sig = sig->next;
+      idx--;
     }
-    _gpgme_data_append_string ( d, "</GnupgKeyblock>\n" );
-
-    return _gpgme_data_release_and_return_string (d);
+  return sig;
 }
 
 
 const char *
-gpgme_key_get_string_attr ( GpgmeKey key, GpgmeAttr what,
-                            const void *reserved, int idx )
+gpgme_key_sig_get_string_attr (gpgme_key_t key, int uid_idx,
+                              _gpgme_attr_t what,
+                              const void *reserved, int idx)
 {
-    const char *val = NULL;
-    struct user_id_s *u;
-
-    if (!key)
-        return NULL;
-    if (reserved)
-        return NULL;
-    if (idx < 0)
-        return NULL;
-
-    switch (what) {
-      case GPGME_ATTR_KEYID:
-        val = key->keys.keyid;
-        break;
-      case GPGME_ATTR_FPR:
-        val = key->keys.fingerprint;
-        break;
-      case GPGME_ATTR_ALGO:    
-        val = pkalgo_to_string (key->keys.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:
-        val = "[foxme]";
-        break;
-      case GPGME_ATTR_LEVEL:  /* not used here */
-      case GPGME_ATTR_TYPE:
-        break;
+  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;
     }
-    return val;
 }
 
 
 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;
-
-    if (!key)
-        return 0;
-    if (reserved)
-        return 0;
-    if (idx < 0)
-        return 0;
-
-    switch (what) {
-      case GPGME_ATTR_ALGO:    
-        val = (unsigned long)key->keys.key_algo;
-        break;
-      case GPGME_ATTR_LEN:     
-        val = (unsigned long)key->keys.key_len;
-        break;
-      case GPGME_ATTR_CREATED: 
-        val = key->keys.timestamp < 0? 0L:(unsigned long)key->keys.timestamp;
-        break;
-      default:
-        break;
-    }
-    return val;
-}
+  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;
+    }
+}