* kbxutil.c: New command --import-openpgp.
authorWerner Koch <wk@gnupg.org>
Tue, 24 Aug 2004 19:55:47 +0000 (19:55 +0000)
committerWerner Koch <wk@gnupg.org>
Tue, 24 Aug 2004 19:55:47 +0000 (19:55 +0000)
(main): Updated libgcrypt initialization stuff.
(my_gcry_logger): New.
(read_file): New.  Taken from ../agent/protect-tool.
(dump_fpr, dump_openpgp_key, import_openpgp): New.

* keybox-openpgp.c: New.

kbx/ChangeLog
kbx/Makefile.am
kbx/kbxutil.c
kbx/keybox-defs.h
kbx/keybox-openpgp.c [new file with mode: 0644]

index 1c3b9e3..f13434d 100644 (file)
@@ -1,3 +1,13 @@
+2004-08-24  Werner Koch  <wk@g10code.de>
+
+       * kbxutil.c: New command --import-openpgp.
+       (main): Updated libgcrypt initialization stuff.
+       (my_gcry_logger): New.
+       (read_file): New.  Taken from ../agent/protect-tool.
+       (dump_fpr, dump_openpgp_key, import_openpgp): New.
+
+       * keybox-openpgp.c: New.
+
 2004-06-18  Werner Koch  <wk@gnupg.org>
 
        * keybox-dump.c (_keybox_dump_file): New arg STATS_ONLY.
index 4f0c400..6b6a59b 100644 (file)
@@ -37,6 +37,7 @@ common_sources = \
        keybox-file.c \
        keybox-search.c \
        keybox-update.c \
+       keybox-openpgp.c \
        keybox-dump.c 
 
 
index 37c1913..cd5da5f 100644 (file)
@@ -1,5 +1,5 @@
 /* kbxutil.c - The Keybox utility
- *     Copyright (C) 2000, 2001 Free Software Foundation, Inc.
+ *     Copyright (C) 2000, 2001, 2004 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
 #include <stdlib.h>
 #include <string.h>
 #include <ctype.h>
+#include <sys/stat.h>
 #include <unistd.h>
+#include <assert.h>
 
+#define JNLIB_NEED_LOG_LOGV
 #include "../jnlib/logging.h"
 #include "../jnlib/argparse.h"
 #include "../jnlib/stringhelp.h"
+#include "../jnlib/utf8conv.h"
 #include "../common/i18n.h"
 #include "keybox-defs.h"
 
@@ -48,6 +52,7 @@ enum cmd_and_opt_values {
   aFindByKid,
   aFindByUid,
   aStats,
+  aImportOpenPGP,
 
   oDebug,
   oDebugAll,
@@ -66,6 +71,7 @@ static ARGPARSE_OPTS opts[] = {
 /*   { aFindByKid,  "find-by-kid", 0, "|KID| find key using it's keyid" }, */
 /*   { aFindByUid,  "find-by-uid", 0, "|NAME| find key by user name" }, */
   { aStats,      "stats",       0, "show key statistics" }, 
+  { aImportOpenPGP, "import-openpgp", 0, "import OpenPGP keyblocks"},
   
   { 301, NULL, 0, N_("@\nOptions:\n ") },
   
@@ -135,6 +141,26 @@ i18n_init(void)
 #endif
 }
 
+/* Used by gcry for logging */
+static void
+my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr)
+{
+  /* Map the log levels.  */
+  switch (level)
+    {
+    case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break;
+    case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break;
+    case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break;
+    case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break;
+    case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break;
+    case GCRY_LOG_BUG:  level = JNLIB_LOG_BUG; break;
+    case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break;
+    default:            level = JNLIB_LOG_ERROR; break;  
+    }
+  log_logv (level, fmt, arg_ptr);
+}
+
+
 
 /*  static void */
 /*  wrong_args( const char *text ) */
@@ -215,6 +241,181 @@ format_keyid ( const char *s, u32 *kid )
 }
 #endif
 
+static char *
+read_file (const char *fname, size_t *r_length)
+{
+  FILE *fp;
+  char *buf;
+  size_t buflen;
+  
+  if (!strcmp (fname, "-"))
+    {
+      size_t nread, bufsize = 0;
+
+      fp = stdin;
+      buf = NULL;
+      buflen = 0;
+#define NCHUNK 8192
+      do 
+        {
+          bufsize += NCHUNK;
+          if (!buf)
+            buf = xtrymalloc (bufsize);
+          else
+            buf = xtryrealloc (buf, bufsize);
+          if (!buf)
+            log_fatal ("can't allocate buffer: %s\n", strerror (errno));
+
+          nread = fread (buf+buflen, 1, NCHUNK, fp);
+          if (nread < NCHUNK && ferror (fp))
+            {
+              log_error ("error reading `[stdin]': %s\n", strerror (errno));
+              xfree (buf);
+              return NULL;
+            }
+          buflen += nread;
+        }
+      while (nread == NCHUNK);
+#undef NCHUNK
+
+    }
+  else
+    {
+      struct stat st;
+
+      fp = fopen (fname, "rb");
+      if (!fp)
+        {
+          log_error ("can't open `%s': %s\n", fname, strerror (errno));
+          return NULL;
+        }
+  
+      if (fstat (fileno(fp), &st))
+        {
+          log_error ("can't stat `%s': %s\n", fname, strerror (errno));
+          fclose (fp);
+          return NULL;
+        }
+      
+      buflen = st.st_size;
+      buf = xtrymalloc (buflen+1);
+      if (!buf)
+        log_fatal ("can't allocate buffer: %s\n", strerror (errno));
+      if (fread (buf, buflen, 1, fp) != 1)
+        {
+          log_error ("error reading `%s': %s\n", fname, strerror (errno));
+          fclose (fp);
+          xfree (buf);
+          return NULL;
+        }
+      fclose (fp);
+    }
+
+  *r_length = buflen;
+  return buf;
+}
+
+
+static void
+dump_fpr (const unsigned char *buffer, size_t len)
+{
+  int i;
+
+  for (i=0; i < len; i++, buffer++)
+    {
+      if (len == 20)
+        {
+          if (i == 10)
+            putchar (' ');
+          printf (" %02X%02X", buffer[0], buffer[1]);
+          i++; buffer++;
+        }
+      else
+        {
+          if (i && !(i % 8))
+            putchar (' ');
+          printf (" %02X", buffer[0]);
+        }
+    }
+}
+
+
+static void
+dump_openpgp_key (keybox_openpgp_info_t info, const unsigned char *image)
+{
+  printf ("pub %02X%02X%02X%02X",
+          info->primary.keyid[4], info->primary.keyid[5],
+          info->primary.keyid[6], info->primary.keyid[7] );
+  dump_fpr (info->primary.fpr, info->primary.fprlen);
+  putchar ('\n');
+  if (info->nsubkeys)
+    {
+      struct _keybox_openpgp_key_info *k;
+
+      k = &info->subkeys;
+      do 
+        {
+          printf ("sub %02X%02X%02X%02X",
+                  k->keyid[4], k->keyid[5],
+                  k->keyid[6], k->keyid[7] );
+          dump_fpr (k->fpr, k->fprlen);
+          putchar ('\n');
+          k = k->next;
+        }
+      while (k);
+    }
+  if (info->nuids)
+    {
+      struct _keybox_openpgp_uid_info *u;
+
+      u = &info->uids;
+      do 
+        {
+          printf ("uid\t\t%.*s\n", u->len, image + u->off);
+          u = u->next;
+        }
+      while (u);
+    }
+}
+
+
+static void
+import_openpgp (const char *filename)
+{
+  gpg_error_t err;
+  char *buffer;
+  size_t buflen, nparsed;
+  unsigned char *p;
+  struct _keybox_openpgp_info info;
+
+  buffer = read_file (filename, &buflen);
+  if (!buffer)
+    return;
+  p = buffer;
+  for (;;)
+    {
+      err = _keybox_parse_openpgp (p, buflen, &nparsed, &info);
+      assert (nparsed <= buflen);
+      if (err)
+        {
+          if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+            break;
+          log_info ("%s: failed to parse OpenPGP keyblock: %s\n",
+                    filename, gpg_strerror (err));
+        }
+      else
+        {
+          dump_openpgp_key (&info, p);
+          _keybox_destroy_openpgp_info (&info);
+        }
+      p += nparsed;
+      buflen -= nparsed;
+    }
+  xfree (buffer);
+}
+
+
+
 
 int
 main( int argc, char **argv )
@@ -223,19 +424,22 @@ main( int argc, char **argv )
   enum cmd_and_opt_values cmd = 0;
   
   set_strusage( my_strusage );
-  /*log_set_name("kbxutil"); fixme */
-#if 0
-  /* check that the libraries are suitable.  Do it here because
-   * the option parse may need services of the library */
-  if ( !gcry_check_version ( "1.1.4" ) ) 
+  gcry_control (GCRYCTL_DISABLE_SECMEM);
+  log_set_prefix ("kbxutil", 1); 
+  set_native_charset (NULL); 
+  i18n_init ();
+
+  /* Check that the libraries are suitable.  Do it here because
+     the option parsing may need services of the library.  */
+  if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
     {
-      log_fatal(_("libgcrypt is too old (need %s, have %s)\n"),
-                "1.1.4", gcry_check_version(NULL) );
+      log_fatal( _("libgcrypt is too old (need %s, have %s)\n"),
+                 NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
     }
-#endif
+
+  gcry_set_log_handler (my_gcry_logger, NULL);
 
   /*create_dotlock(NULL); register locking cleanup */
-  i18n_init();
 
   /* We need to use the gcry malloc function because jnlib does use them */
   keybox_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free);
@@ -264,9 +468,10 @@ main( int argc, char **argv )
         case aFindByKid:
         case aFindByUid:
         case aStats:
+        case aImportOpenPGP:
           cmd = pargs.r_opt;
           break;
-          
+
         default:
           pargs.err = 2;
           break;
@@ -276,24 +481,34 @@ main( int argc, char **argv )
     myexit(2);
   
   if (!cmd)
-      { /* default is to list a KBX file */
-       if (!argc) 
-          _keybox_dump_file (NULL, 0, stdout);
-       else
-          {
-           for (; argc; argc--, argv++) 
-              _keybox_dump_file (*argv, 0, stdout);
-          }
-      }
+    { /* Default is to list a KBX file */
+      if (!argc) 
+        _keybox_dump_file (NULL, 0, stdout);
+      else
+        {
+          for (; argc; argc--, argv++) 
+            _keybox_dump_file (*argv, 0, stdout);
+        }
+    }
   else if (cmd == aStats )
     {
-       if (!argc) 
-          _keybox_dump_file (NULL, 1, stdout);
-       else
-          {
-           for (; argc; argc--, argv++) 
-              _keybox_dump_file (*argv, 1, stdout);
-          }
+      if (!argc) 
+        _keybox_dump_file (NULL, 1, stdout);
+      else
+        {
+          for (; argc; argc--, argv++) 
+            _keybox_dump_file (*argv, 1, stdout);
+        }
+    }
+  else if (cmd == aImportOpenPGP)
+    {
+      if (!argc)
+        import_openpgp ("-");
+      else
+        {
+          for (; argc; argc--, argv++) 
+            import_openpgp (*argv);
+        }
     }
 #if 0
   else if ( cmd == aFindByFpr ) 
index 759289a..4906a38 100644 (file)
@@ -82,6 +82,40 @@ struct keybox_handle {
 };
 
 
+/* Openpgp helper structures. */
+struct _keybox_openpgp_key_info
+{
+  struct _keybox_openpgp_key_info *next;
+  unsigned char keyid[8];
+  int fprlen;  /* Either 16 or 20 */
+  unsigned char fpr[20];
+};
+
+struct _keybox_openpgp_uid_info
+{
+  struct _keybox_openpgp_uid_info *next;
+  size_t off;
+  size_t len;
+};
+
+struct _keybox_openpgp_info
+{
+  int is_secret;        /* True if this is a secret key. */
+  unsigned int nsubkeys;/* Total number of subkeys.  */
+  unsigned int nuids;   /* Total number of user IDs in the keyblock. */
+  unsigned int nsigs;   /* Total number of signatures in the keyblock. */
+
+  /* Note, we use 2 structs here to better cope with the most common
+     use of having one primary and one subkey - this allows us to
+     statically allocate this structure and only malloc stuff for more
+     than one subkey. */
+  struct _keybox_openpgp_key_info primary;
+  struct _keybox_openpgp_key_info subkeys;
+  struct _keybox_openpgp_uid_info uids;
+};
+typedef struct _keybox_openpgp_info *keybox_openpgp_info_t;
+
+
 /* Don't know whether this is needed: */
 /*  static struct { */
 /*    const char *homedir; */
@@ -108,6 +142,13 @@ const char *_keybox_get_blob_image (KEYBOXBLOB blob, size_t *n);
 off_t _keybox_get_blob_fileoffset (KEYBOXBLOB blob);
 void _keybox_update_header_blob (KEYBOXBLOB blob);
 
+/*-- keybox-openpgp.c --*/
+gpg_error_t _keybox_parse_openpgp (const unsigned char *image, size_t imagelen,
+                                   size_t *nparsed,
+                                   keybox_openpgp_info_t info);
+void _keybox_destroy_openpgp_info (keybox_openpgp_info_t info);
+
+
 /*-- keybox-file.c --*/
 int _keybox_read_blob (KEYBOXBLOB *r_blob, FILE *fp);
 int _keybox_read_blob2 (KEYBOXBLOB *r_blob, FILE *fp, int *skipped_deleted);
diff --git a/kbx/keybox-openpgp.c b/kbx/keybox-openpgp.c
new file mode 100644 (file)
index 0000000..7e6e0e4
--- /dev/null
@@ -0,0 +1,516 @@
+/* keybox-openpgp.c - OpenPGP key parsing
+ *     Copyright (C) 2001, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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
+ */
+
+/* This is a simple OpenPGP parser suitable for all OpenPGP key
+   material.  It just provides the functionality required to build and
+   parse an KBX OpenPGP key blob.  Thus it is not a complete parser.
+   However it is self-contained and optimized for fast in-memory
+   parsing.  Note that we don't support old ElGamal v3 keys
+   anymore. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "keybox-defs.h"
+
+#include <gcrypt.h>
+
+
+enum packet_types
+  {
+    PKT_NONE              =0,
+    PKT_PUBKEY_ENC        =1, /* public key encrypted packet */
+    PKT_SIGNATURE         =2, /* secret key encrypted packet */
+    PKT_SYMKEY_ENC        =3, /* session key packet (OpenPGP)*/
+    PKT_ONEPASS_SIG        =4, /* one pass sig packet (OpenPGP)*/
+    PKT_SECRET_KEY        =5, /* secret key */
+    PKT_PUBLIC_KEY        =6, /* public key */
+    PKT_SECRET_SUBKEY      =7, /* secret subkey (OpenPGP) */
+    PKT_COMPRESSED        =8, /* compressed data packet */
+    PKT_ENCRYPTED         =9, /* conventional encrypted data */
+    PKT_MARKER           =10, /* marker packet (OpenPGP) */
+    PKT_PLAINTEXT        =11, /* plaintext data with filename and mode */
+    PKT_RING_TRUST       =12, /* keyring trust packet */
+    PKT_USER_ID                  =13, /* user id packet */
+    PKT_PUBLIC_SUBKEY     =14, /* public subkey (OpenPGP) */
+    PKT_OLD_COMMENT       =16, /* comment packet from an OpenPGP draft */
+    PKT_ATTRIBUTE         =17, /* PGP's attribute packet */
+    PKT_ENCRYPTED_MDC     =18, /* integrity protected encrypted data */
+    PKT_MDC              =19, /* manipulation detection code packet */
+    PKT_COMMENT                  =61, /* new comment packet (private) */
+    PKT_GPG_CONTROL       =63  /* internal control packet */
+  };
+
+
+
+/* Assume a valid OpenPGP packet at the address pointed to by BUFBTR
+   which is of amaximum length as stored at BUFLEN.  Return the header
+   information of that packet and advance the pointer stored at BUFPTR
+   to the next packet; also adjust the length stored at BUFLEN to
+   match the remaining bytes. If there are no more packets, store NULL
+   at BUFPTR.  Return an non-zero error code on failure or the
+   follwing data on success:
+
+   R_DATAPKT = Pointer to the begin of the packet data.
+   R_DATALEN = Length of this data.  This has already been checked to fit 
+               into the buffer. 
+   R_PKTTYPE = The packet type.
+   R_NTOTAL  = The total number of bytes of this packet
+
+   Note that these values are only updated on success.
+*/
+static gpg_error_t
+next_packet (unsigned char const **bufptr, size_t *buflen,
+             unsigned char const **r_data, size_t *r_datalen, int *r_pkttype,
+             size_t *r_ntotal)
+{
+  const unsigned char *buf = *bufptr;
+  size_t len = *buflen;
+  int c, ctb, pkttype;
+  unsigned long pktlen;
+
+  if (!len)
+    return gpg_error (GPG_ERR_NO_DATA);
+  
+  ctb = *buf++; len--;
+  if ( !(ctb & 0x80) )
+    return gpg_error (GPG_ERR_INV_PACKET); /* Invalid CTB. */
+  
+  pktlen = 0;
+  if ((ctb & 0x40))  /* New style (OpenPGP) CTB.  */
+    {
+      pkttype = (ctb & 0x3f);
+      if (!len)
+        return gpg_error (GPG_ERR_INV_PACKET); /* No 1st length byte. */
+      c = *buf++; len--;
+      if (pkttype == PKT_COMPRESSED)
+        return gpg_error (GPG_ERR_UNEXPECTED); /* ... packet in a keyblock. */
+      if ( c < 192 )
+        pktlen = c;
+      else if ( c < 224 )
+        { 
+          pktlen = (c - 192) * 256;
+          if (!len)
+            return gpg_error (GPG_ERR_INV_PACKET); /* No 2nd length byte. */
+          c = *buf++; len--;
+          pktlen += c + 192;
+        }
+      else if (c == 255)
+        {
+          if (len <4 )
+            return gpg_error (GPG_ERR_INV_PACKET); /* No length bytes. */
+          pktlen  = (*buf++) << 24;
+          pktlen |= (*buf++) << 16;
+          pktlen |= (*buf++) << 8;
+          pktlen |= (*buf++);
+          len -= 4;
+      }
+      else /* Partial length encoding is not allowed for key packets. */
+        return gpg_error (GPG_ERR_UNEXPECTED);
+    }
+  else /* Old style CTB.  */
+    {
+      int lenbytes;
+
+      pktlen = 0;
+      pkttype = (ctb>>2)&0xf;
+      lenbytes = ((ctb&3)==3)? 0 : (1<<(ctb & 3));
+      if (!lenbytes) /* Not allowed in key packets.  */
+        return gpg_error (GPG_ERR_UNEXPECTED);
+      if (len < lenbytes)
+        return gpg_error (GPG_ERR_INV_PACKET); /* Not enough length bytes.  */
+      for (; lenbytes; lenbytes--)
+        {
+          pktlen <<= 8;
+          pktlen |= *buf++; len--;
+       }
+    }
+
+  /* Do some basic sanity check.  */
+  switch (pkttype)
+    {
+    case PKT_SIGNATURE:
+    case PKT_SECRET_KEY:       
+    case PKT_PUBLIC_KEY:
+    case PKT_SECRET_SUBKEY:
+    case PKT_MARKER:
+    case PKT_RING_TRUST:
+    case PKT_USER_ID:
+    case PKT_PUBLIC_SUBKEY:
+    case PKT_OLD_COMMENT:
+    case PKT_ATTRIBUTE:
+    case PKT_COMMENT:
+    case PKT_GPG_CONTROL:
+      break; /* Okay these are allowed packets. */
+    default:
+      return gpg_error (GPG_ERR_UNEXPECTED);
+    }
+
+  if (pktlen == 0xffffffff) 
+      return gpg_error (GPG_ERR_INV_PACKET);
+  
+  if (pktlen > len)
+    return gpg_error (GPG_ERR_INV_PACKET); /* Packet length header too long. */
+
+  *r_data = buf;
+  *r_datalen = pktlen;
+  *r_pkttype = pkttype;
+  *r_ntotal = (buf - *bufptr) + pktlen;
+
+  *bufptr = buf + pktlen;
+  *buflen = len - pktlen;
+  if (!*buflen)
+    *bufptr = NULL;
+
+  return 0;
+}
+
+
+/* Parse a key packet and store the ionformation in KI. */
+static gpg_error_t
+parse_key (const unsigned char *data, size_t datalen,
+           struct _keybox_openpgp_key_info *ki)
+{
+  gpg_error_t err;
+  const unsigned char *data_start = data;
+  int i, version, algorithm;
+  size_t n;
+  unsigned long timestamp, expiredate;
+  int npkey;
+  unsigned char hashbuffer[768];
+  const unsigned char *mpi_n = NULL;
+  size_t mpi_n_len = 0, mpi_e_len = 0;
+  gcry_md_hd_t md;
+
+  if (datalen < 5)
+    return gpg_error (GPG_ERR_INV_PACKET);
+  version = *data++; datalen--;
+  if (version < 2 || version > 4 )
+    return gpg_error (GPG_ERR_INV_PACKET); /* Invalid version. */
+  
+  timestamp = ((data[0]<<24)|(data[1]<<16)|(data[2]<<8)|(data[3]));
+  data +=4; datalen -=4;
+
+  if (version < 4)
+    {
+      unsigned short ndays;
+      
+      if (datalen < 2)
+        return gpg_error (GPG_ERR_INV_PACKET);
+      ndays = ((data[0]<<8)|(data[1]));
+      data +=2; datalen -= 2;
+      if (ndays)
+      expiredate = ndays? (timestamp + ndays * 86400L) : 0;
+    }
+  else
+    expiredate = 0; /* This is stored in the self-signature. */
+
+  if (!datalen)
+    return gpg_error (GPG_ERR_INV_PACKET);
+  algorithm = *data++; datalen--;
+
+  switch (algorithm)
+    {
+    case 1:
+    case 2:
+    case 3: /* RSA */
+      npkey = 2; 
+      break;
+    case 16:
+    case 20: /* Elgamal */
+      npkey = 3;
+      break;
+    case 17: /* DSA */
+      npkey = 4;
+      break;
+    default: /* Unknown algorithm. */
+      return gpg_error (GPG_ERR_UNKNOWN_ALGORITHM);
+    }
+
+  for (i=0; i < npkey; i++ )
+    {
+      unsigned int nbits, nbytes;
+      
+      if (datalen < 2)
+        return gpg_error (GPG_ERR_INV_PACKET);
+      nbits = ((data[0]<<8)|(data[1]));
+      data += 2; datalen -=2;
+      nbytes = (nbits+7) / 8;
+      if (datalen < nbytes)
+        return gpg_error (GPG_ERR_INV_PACKET);
+      /* For use by v3 fingerprint calculation we need to know the RSA
+         modulus and exponent. */
+      if (i==0) 
+        {
+          mpi_n = data; 
+          mpi_n_len = nbytes;
+        }
+      else if (i==1)
+        mpi_e_len = nbytes;
+        
+      data += nbytes; datalen -= nbytes;
+    }
+  n = data - data_start;
+
+  if (version < 4)
+    {
+      /* We do not support any other algorithm than RSA in v3
+         packets. */
+      if (algorithm < 1 || algorithm > 3)
+        return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+
+      err = gcry_md_open (&md, GCRY_MD_MD5, 0);
+      if (err)
+        return err; /* Oops */
+      gcry_md_write (md, mpi_n, mpi_n_len);
+      gcry_md_write (md, mpi_n+mpi_n_len+2, mpi_e_len);
+      memcpy (ki->fpr, gcry_md_read (md, 0), 16);
+      gcry_md_close (md);
+      ki->fprlen = 16;
+      
+      if (mpi_n_len < 8)
+        {
+          /* Moduli less than 64 bit are out of the specs scope.  Zero
+             them out becuase this is what gpg does too. */
+          memset (ki->keyid, 0, 8); 
+        }
+      else
+        memcpy (ki->keyid, mpi_n + mpi_n_len - 8, 8);
+    }
+  else
+    {
+      /* Its a pitty that we need to prefix the buffer with the tag
+         and a length header: We can't simply psss it to the fast
+         hashing fucntion for that reason.  It might be a good idea to
+         have a scatter-gather enabled hash function. What we do here
+         is to use a static buffer if this one is large enough and
+         only use the regular hash fucntions if this buffer is not
+         large enough. */
+      if ( 3 + n < sizeof hashbuffer )
+        {
+          hashbuffer[0] = 0x99;     /* CTB */
+          hashbuffer[1] = (n >> 8); /* 2 byte length header. */
+          hashbuffer[2] = n;
+          memcpy (hashbuffer + 3, data_start, n);
+          gcry_md_hash_buffer (GCRY_MD_SHA1, ki->fpr, hashbuffer, 3 + n);
+        }
+      else
+        {
+          err = gcry_md_open (&md, GCRY_MD_SHA1, 0);
+          if (err)
+            return err; /* Oops */
+          gcry_md_putc (md, 0x99 );     /* CTB */
+          gcry_md_putc (md, (n >> 8) ); /* 2 byte length header. */
+          gcry_md_putc (md, n );
+          gcry_md_write (md, data_start, n);
+          memcpy (ki->fpr, gcry_md_read (md, 0), 20);
+          gcry_md_close (md);
+        }
+      ki->fprlen = 20;
+      memcpy (ki->keyid, ki->fpr+12, 8);
+    }
+
+  return 0;
+}
+
+
+
+/* The caller must pass the address of an INFO structure which will
+   get filled on success with information pertaining to the OpenPGP
+   keyblock IMAGE of length IMAGELEN.  Note that a caller does only
+   need to release this INFO structure when the function returns
+   success.  If NPARSED is not NULL the actual number of bytes parsed
+   will be stored at this address.  */
+gpg_error_t
+_keybox_parse_openpgp (const unsigned char *image, size_t imagelen,
+                       size_t *nparsed,
+                       keybox_openpgp_info_t info)
+{
+  gpg_error_t err = 0;
+  const unsigned char *image_start, *data;
+  size_t n, datalen;
+  int pkttype;
+  int first = 1;
+  struct _keybox_openpgp_key_info *k, **ktail = NULL;
+  struct _keybox_openpgp_uid_info *u, **utail = NULL;
+  
+  memset (info, 0, sizeof *info);
+  if (nparsed)
+    *nparsed = 0;
+
+  image_start = image;
+  while (image)
+    {
+      err = next_packet (&image, &imagelen, &data, &datalen, &pkttype, &n);
+      if (err)
+        break;
+
+      if (first)
+        {
+          if (pkttype == PKT_PUBLIC_KEY)
+            ;
+          else if (pkttype == PKT_SECRET_KEY)
+            info->is_secret = 1;
+          else
+            {
+              err = gpg_error (GPG_ERR_UNEXPECTED);
+              break;
+            }
+          first = 0;
+        }
+      else if (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY)
+        break; /* Next keyblock encountered - ready. */
+      
+      if (nparsed)
+        *nparsed += n;
+
+      if (pkttype == PKT_SIGNATURE)
+        {
+          /* For now we only count the total number of signatures. */
+          info->nsigs++;
+        }
+      else if (pkttype == PKT_USER_ID)
+        {
+          info->nuids++;
+          if (info->nuids == 1)
+            {
+              info->uids.off = data - image_start;
+              info->uids.len = datalen;
+              utail = &info->uids.next;
+            }
+          else
+            {
+              u = xtrycalloc (1, sizeof *u);
+              if (!u)
+                {
+                  err = gpg_error_from_errno (errno);
+                  break;
+                }
+              u->off = data - image_start;
+              u->len = datalen;
+              *utail = u;
+              utail = &u->next;
+            }
+        }
+      else if (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY)
+        {
+          err = parse_key (data, datalen, &info->primary);
+          if (err)
+            break;
+        }
+      else if( pkttype == PKT_PUBLIC_SUBKEY && datalen && *data == '#' ) 
+        {
+          /* Early versions of GnuPG used old PGP comment packets;
+           * luckily all those comments are prefixed by a hash
+           * sign - ignore these packets. */
+        }
+      else if (pkttype == PKT_PUBLIC_SUBKEY || pkttype == PKT_SECRET_SUBKEY)
+        {
+          info->nsubkeys++;
+          if (info->nsubkeys == 1)
+            {
+              err = parse_key (data, datalen, &info->subkeys);
+              if (err)
+                {
+                  info->nsubkeys--;
+                  if (gpg_err_code (err) != GPG_ERR_UNKNOWN_ALGORITHM)
+                    break;
+                  /* We ignore subkeys with unknown algorithms. */
+                }
+              else
+                ktail = &info->subkeys.next;
+            }
+          else
+            {
+              k = xtrycalloc (1, sizeof *k);
+              if (!k)
+                {
+                  err = gpg_error_from_errno (errno);
+                  break;
+                }
+              err = parse_key (data, datalen, k);
+              if (err)
+                {
+                  xfree (k);
+                  info->nsubkeys--;
+                  if (gpg_err_code (err) != GPG_ERR_UNKNOWN_ALGORITHM)
+                    break;
+                  /* We ignore subkeys with unknown algorithms. */
+                }
+              else
+                {
+                  *ktail = k;
+                  ktail = &k->next;
+                }
+            }
+        }
+    }
+
+  if (err)
+    {
+      _keybox_destroy_openpgp_info (info);
+      if (!first
+          && (gpg_err_code (err) == GPG_ERR_UNSUPPORTED_ALGORITHM
+              || gpg_err_code (err) == GPG_ERR_UNKNOWN_ALGORITHM))
+        {
+          /* We are able to skip to the end of this keyblock. */
+          while (image)
+            {
+              if (next_packet (&image, &imagelen,
+                               &data, &datalen, &pkttype, &n) )
+                break; /* Another error - stop here. */
+
+              if (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY)
+                break; /* Next keyblock encountered - ready. */
+              
+              if (nparsed)
+                *nparsed += n;
+            }
+        }
+    }
+
+  return err;
+}
+
+
+/* Release any malloced data in INFO but not INFO itself! */
+void
+_keybox_destroy_openpgp_info (keybox_openpgp_info_t info)
+{
+  struct _keybox_openpgp_key_info *k, *k2;
+  struct _keybox_openpgp_uid_info *u, *u2;
+
+  assert (!info->primary.next);
+  for (k=info->subkeys.next; k; k = k2)
+    {
+      k2 = k->next;
+      xfree (k);
+    }
+
+  for (u=info->uids.next; u; u = u2)
+    {
+      u2 = u->next;
+      xfree (u);
+    }
+}