Add new features to kbxutil.
authorWerner Koch <wk@gnupg.org>
Thu, 23 Aug 2007 17:41:22 +0000 (17:41 +0000)
committerWerner Koch <wk@gnupg.org>
Thu, 23 Aug 2007 17:41:22 +0000 (17:41 +0000)
Fixed bug 829 (can't encrypt if duplicated certs are in the keybox)

kbx/ChangeLog
kbx/kbxutil.c
kbx/keybox-defs.h
kbx/keybox-dump.c
sm/ChangeLog
sm/certlist.c
sm/export.c
sm/gpgsm.h

index b36746d..edcf917 100644 (file)
@@ -1,3 +1,12 @@
+2007-08-23  Werner Koch  <wk@g10code.com>
+
+       * kbxutil.c: New commands --find-dups and --cut.  New options
+       --from an --to.
+       * keybox-dump.c (hash_blob_rawdata): New.
+       (_keybox_dump_find_dups): New.
+       (open_file): Factor some code out to this.
+       (_keybox_dump_cut_records): New.
+
 2007-06-26  Werner Koch  <wk@g10code.com>
 
        * kbxutil.c: Include init.h
index 8bc545d..cadc067 100644 (file)
@@ -1,5 +1,5 @@
 /* kbxutil.c - The Keybox utility
- *     Copyright (C) 2000, 2001, 2004 Free Software Foundation, Inc.
+ * Copyright (C) 2000, 2001, 2004, 2007 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -25,6 +25,7 @@
 #include <ctype.h>
 #include <sys/stat.h>
 #include <unistd.h>
+#include <limits.h>
 #include <assert.h>
 
 #define JNLIB_NEED_LOG_LOGV
@@ -52,12 +53,15 @@ enum cmd_and_opt_values {
   aFindByUid,
   aStats,
   aImportOpenPGP,
+  aFindDups,
+  aCut,
 
   oDebug,
   oDebugAll,
 
   oNoArmor,
-  
+  oFrom,
+  oTo,
 
   aTest
 };
@@ -71,9 +75,13 @@ static ARGPARSE_OPTS opts[] = {
 /*   { aFindByUid,  "find-by-uid", 0, "|NAME| find key by user name" }, */
   { aStats,      "stats",       0, "show key statistics" }, 
   { aImportOpenPGP, "import-openpgp", 0, "import OpenPGP keyblocks"},
+  { aFindDups,    "find-dups",   0, "find duplicates" },
+  { aCut,         "cut",         0, "export records" },
   
   { 301, NULL, 0, N_("@\nOptions:\n ") },
   
+  { oFrom, "from", 4, "|N|first record to export" },
+  { oTo,   "to",   4, "|N|last record to export" },
 /*   { oArmor, "armor",     0, N_("create ascii armored output")}, */
 /*   { oArmor, "armour",     0, "@" }, */
 /*   { oOutput, "output",    2, N_("use as output file")}, */
@@ -402,6 +410,7 @@ main( int argc, char **argv )
 {
   ARGPARSE_ARGS pargs;
   enum cmd_and_opt_values cmd = 0;
+  unsigned long from = 0, to = ULONG_MAX;
   
   set_strusage( my_strusage );
   gcry_control (GCRYCTL_DISABLE_SECMEM);
@@ -452,14 +461,24 @@ main( int argc, char **argv )
         case aFindByUid:
         case aStats:
         case aImportOpenPGP:
+        case aFindDups:
+        case aCut:
           cmd = pargs.r_opt;
           break;
 
+        case oFrom: from = pargs.r.ret_ulong; break;
+        case oTo: to = pargs.r.ret_ulong; break;
+
         default:
           pargs.err = 2;
           break;
        }
     }
+  
+  if (to < from)
+    log_error ("record number of \"--to\" is lower than \"--from\" one\n");
+
+
   if (log_get_errorcount(0) )
     myexit(2);
   
@@ -483,6 +502,26 @@ main( int argc, char **argv )
             _keybox_dump_file (*argv, 1, stdout);
         }
     }
+  else if (cmd == aFindDups )
+    {
+      if (!argc) 
+        _keybox_dump_find_dups (NULL, 0, stdout);
+      else
+        {
+          for (; argc; argc--, argv++) 
+            _keybox_dump_find_dups (*argv, 0, stdout);
+        }
+    }
+  else if (cmd == aCut )
+    {
+      if (!argc) 
+        _keybox_dump_cut_records (NULL, from, to, stdout);
+      else
+        {
+          for (; argc; argc--, argv++) 
+            _keybox_dump_cut_records (*argv, from, to, stdout);
+        }
+    }
   else if (cmd == aImportOpenPGP)
     {
       if (!argc)
index ffdca2b..c425cdd 100644 (file)
@@ -169,6 +169,9 @@ gpg_err_code_t _keybox_get_flag_location (const unsigned char *buffer,
 /*-- keybox-dump.c --*/
 int _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp);
 int _keybox_dump_file (const char *filename, int stats_only, FILE *outfp);
+int _keybox_dump_find_dups (const char *filename, int print_them, FILE *outfp);
+int _keybox_dump_cut_records (const char *filename, unsigned long from,
+                              unsigned long to, FILE *outfp);
 
 
 /*-- keybox-util.c --*/
@@ -186,7 +189,7 @@ void  _keybox_free (void *p);
 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
 #define DIMof(type,member)   DIM(((type *)0)->member)
 #ifndef STR
-  #define STR(v) #v
+#  define STR(v) #v
 #endif
 #define STR2(v) STR(v)
 
index 1525a03..fe68bf1 100644 (file)
 #include <errno.h>
 
 #include "keybox-defs.h"
+#include <gcrypt.h>
+
+/* Argg, we can't include ../common/util.h */
+char *bin2hexcolon (const void *buffer, size_t length, char *stringbuf);
+
 
 static ulong
 get32 (const byte *buffer)
@@ -183,6 +188,9 @@ _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp)
 
   fprintf( fp, "Data-Offset: %lu\n", rawdata_off );
   fprintf( fp, "Data-Length: %lu\n", rawdata_len );
+  if (rawdata_off > length || rawdata_len > length 
+      || rawdata_off+rawdata_off > length)
+    fprintf (fp, "[Error: raw data larger than blob]\n");
 
   nkeys = get16 (buffer + 16);
   fprintf (fp, "Key-Count: %lu\n", nkeys );
@@ -322,6 +330,53 @@ _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp)
 }
 
 
+/* Compute the SHA_1 checksum of teh rawdata in BLOB and aput it into
+   DIGEST. */
+static int
+hash_blob_rawdata (KEYBOXBLOB blob, unsigned char *digest)
+{
+  const unsigned char *buffer;
+  size_t n, length;
+  int type;
+  ulong rawdata_off, rawdata_len;
+
+  buffer = _keybox_get_blob_image (blob, &length);
+  
+  if (length < 32)
+    return -1;
+  n = get32 (buffer);
+  if (n < length) 
+    length = n;  /* Blob larger than length in header - ignore the rest. */
+
+  type = buffer[4];
+  switch (type)
+    {
+    case BLOBTYPE_PGP:
+    case BLOBTYPE_X509:
+      break;
+
+    case BLOBTYPE_EMPTY:
+    case BLOBTYPE_HEADER:
+    default:
+      memset (digest, 0, 20);
+      return 0;
+    }
+
+  if (length < 40)
+    return -1;
+  
+  rawdata_off = get32 (buffer + 8);
+  rawdata_len = get32 (buffer + 12);
+
+  if (rawdata_off > length || rawdata_len > length 
+      || rawdata_off+rawdata_off > length)
+    return -1; /* Out of bounds.  */
+
+  gcry_md_hash_buffer (GCRY_MD_SHA1, digest, buffer+rawdata_off, rawdata_len);
+  return 0;
+}
+
+
 struct file_stats_s
 {
   unsigned long too_short_blobs;
@@ -401,6 +456,29 @@ update_stats (KEYBOXBLOB blob, struct file_stats_s *s)
 
 
 \f
+static FILE *
+open_file (const char **filename, FILE *outfp)
+{
+  FILE *fp;
+
+  if (!*filename)
+    {
+      *filename = "-";
+      fp = stdin;
+    }
+  else
+    fp = fopen (*filename, "rb");
+  if (!fp)
+    {
+      int save_errno = errno;
+      fprintf (outfp, "can't open `%s': %s\n", *filename, strerror(errno));
+      errno = save_errno;
+    }
+  return fp;
+}
+
+
+
 int
 _keybox_dump_file (const char *filename, int stats_only, FILE *outfp)
 {
@@ -412,19 +490,8 @@ _keybox_dump_file (const char *filename, int stats_only, FILE *outfp)
 
   memset (&stats, 0, sizeof stats);
 
-  if (!filename)
-    {
-      filename = "-";
-      fp = stdin;
-    }
-  else
-    fp = fopen (filename, "rb");
-  if (!fp)
-    {
-      gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
-      fprintf (outfp, "can't open `%s': %s\n", filename, strerror(errno));
-      return tmperr;
-    }
+  if (!(fp = open_file (&filename, outfp)))
+    return gpg_error_from_syserror ();
 
   while ( !(rc = _keybox_read_blob (&blob, fp)) )
     {
@@ -481,3 +548,150 @@ _keybox_dump_file (const char *filename, int stats_only, FILE *outfp)
 
   return rc;
 }
+
+
+\f
+struct dupitem_s 
+{
+  unsigned long recno; 
+  unsigned char digest[20];
+};
+
+
+static int
+cmp_dupitems (const void *arg_a, const void *arg_b)
+{
+  struct dupitem_s *a = (struct dupitem_s *)arg_a;
+  struct dupitem_s *b = (struct dupitem_s *)arg_b;
+  
+  return memcmp (a->digest, b->digest, 20);
+}
+
+
+int
+_keybox_dump_find_dups (const char *filename, int print_them, FILE *outfp)
+{
+  FILE *fp;
+  KEYBOXBLOB blob;
+  int rc;
+  unsigned long recno = 0;
+  unsigned char zerodigest[20];
+  struct dupitem_s *dupitems;
+  size_t dupitems_size, dupitems_count, lastn, n;
+  char fprbuf[3*20+1];
+  
+  memset (zerodigest, 0, sizeof zerodigest);
+
+  if (!(fp = open_file (&filename, outfp)))
+    return gpg_error_from_syserror ();
+
+  dupitems_size = 1000;
+  dupitems = malloc (dupitems_size * sizeof *dupitems);
+  if (!dupitems)
+    {
+      gpg_error_t tmperr = gpg_error_from_syserror ();
+      fprintf (outfp, "error allocating array for `%s': %s\n",
+               filename, strerror(errno));
+      return tmperr;
+    }
+  dupitems_count = 0;
+
+  while ( !(rc = _keybox_read_blob (&blob, fp)) )
+    {
+      unsigned char digest[20];
+      
+      if (hash_blob_rawdata (blob, digest))
+        fprintf (outfp, "error in blob %ld of `%s'\n", recno, filename);
+      else if (memcmp (digest, zerodigest, 20))
+        {
+          if (dupitems_count >= dupitems_size)
+            {
+              struct dupitem_s *tmp;
+
+              dupitems_size += 1000;
+              tmp = realloc (dupitems, dupitems_size * sizeof *dupitems);
+              if (!tmp)
+                {
+                  gpg_error_t tmperr = gpg_error_from_syserror ();
+                  fprintf (outfp, "error reallocating array for `%s': %s\n",
+                           filename, strerror(errno));
+                  free (dupitems);
+                  return tmperr;
+                }
+              dupitems = tmp;
+            }
+          dupitems[dupitems_count].recno = recno;
+          memcpy (dupitems[dupitems_count].digest, digest, 20);
+          dupitems_count++;
+        }
+      _keybox_release_blob (blob);
+      recno++;
+    }
+  if (rc == -1)
+    rc = 0;
+  if (rc)
+    fprintf (outfp, "error reading `%s': %s\n", filename, gpg_strerror (rc));
+  if (fp != stdin)
+    fclose (fp);
+
+  qsort (dupitems, dupitems_count, sizeof *dupitems, cmp_dupitems);
+
+  for (lastn=0, n=1; n < dupitems_count; lastn=n, n++)
+    {
+      if (!memcmp (dupitems[lastn].digest, dupitems[n].digest, 20))
+        {
+          bin2hexcolon (dupitems[lastn].digest, 20, fprbuf);
+          fprintf (outfp, "fpr=%s recno=%lu", fprbuf, dupitems[lastn].recno);
+          do
+            fprintf (outfp, " %lu", dupitems[n].recno);
+          while (++n < dupitems_count
+                 && !memcmp (dupitems[lastn].digest, dupitems[n].digest, 20));
+          putc ('\n', outfp);
+          n--;
+        }
+    }
+
+  free (dupitems);
+
+  return rc;
+}
+
+
+/* Print records with record numbers FROM to TO to OUTFP.  */
+int
+_keybox_dump_cut_records (const char *filename, unsigned long from,
+                          unsigned long to, FILE *outfp)
+{
+  FILE *fp;
+  KEYBOXBLOB blob;
+  int rc;
+  unsigned long recno = 0;
+  
+  if (!(fp = open_file (&filename, stderr)))
+    return gpg_error_from_syserror ();
+
+  while ( !(rc = _keybox_read_blob (&blob, fp)) )
+    {
+      if (recno > to)
+        break; /* Ready.  */
+      if (recno >= from)
+        {
+          if ((rc = _keybox_write_blob (blob, outfp)))
+            {
+              fprintf (stderr, "error writing output: %s\n",
+                       gpg_strerror (rc));
+              goto leave;
+            }
+        }
+      _keybox_release_blob (blob);
+      recno++;
+    }
+  if (rc == -1)
+    rc = 0;
+  if (rc)
+    fprintf (stderr, "error reading `%s': %s\n", filename, gpg_strerror (rc));
+ leave:
+  if (fp != stdin)
+    fclose (fp);
+  return rc;
+}
index 8bcc22a..6805969 100644 (file)
@@ -1,3 +1,11 @@
+2007-08-23  Werner Koch  <wk@g10code.com>
+
+       * certlist.c (gpgsm_certs_identical_p): New.
+       (gpgsm_add_to_certlist): Ignore duplicate certificates in
+       ambigious name detection.
+       (gpgsm_find_cert): Ditto.
+       * export.c (gpgsm_p12_export): Ditto.
+
 2007-08-22  Werner Koch  <wk@g10code.com>
 
        * certreqgen.c (create_request): Replace open coding by bin2hex.
index 5c08e31..3afdbc3 100644 (file)
@@ -1,5 +1,5 @@
 /* certlist.c - build list of certificates
- *     Copyright (C) 2001, 2003, 2004, 2005 Free Software Foundation, Inc.
+ * Copyright (C) 2001, 2003, 2004, 2005, 2007 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -225,6 +225,25 @@ same_subject_issuer (const char *subject, const char *issuer, ksba_cert_t cert)
   return tmp;
 }
 
+
+/* Return true if CERT_A is the same as CERT_B.  */
+int
+gpgsm_certs_identical_p (ksba_cert_t cert_a, ksba_cert_t cert_b)
+{
+  const unsigned char *img_a, *img_b;
+  size_t len_a, len_b;
+
+  img_a = ksba_cert_get_image (cert_a, &len_a);
+  if (img_a)
+    {
+      img_b = ksba_cert_get_image (cert_b, &len_b);
+      if (img_b && len_a == len_b && !memcmp (img_a, img_b, len_a))
+        return 1; /* Identical. */
+    }
+  return 0;
+}
+
+
 /* Return true if CERT is already contained in CERTLIST. */
 static int
 is_cert_in_certlist (ksba_cert_t cert, certlist_t certlist)
@@ -330,6 +349,8 @@ gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret,
           
           if (!rc)
             {
+              certlist_t dup_certs = NULL;
+
             next_ambigious:
               rc = keydb_search (kh, &desc, 1);
               if (rc == -1)
@@ -337,23 +358,45 @@ gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret,
               else if (!rc)
                 {
                   ksba_cert_t cert2 = NULL;
+                  
+                  /* If this is the first possible duplicate, add thye orginal 
+                     certificate to our list of duplicates.  */
+                  if (!dup_certs)
+                    gpgsm_add_cert_to_certlist (ctrl, cert, &dup_certs, 0);
 
                   /* We have to ignore ambigious names as long as
-                     there only fault is a bad key usage */
+                     there only fault is a bad key usage.  This is
+                     required to support encryption and signing
+                     certifciates of the same subject.
+
+                     Further we ignore them if they are due to an
+                     identical certificate (which may happen if a
+                     certificate is accidential duplicated in the
+                     keybox).  */
                   if (!keydb_get_cert (kh, &cert2))
                     {
                       int tmp = (same_subject_issuer (subject, issuer, cert2)
                                  && ((gpg_err_code (
                                       secret? gpgsm_cert_use_sign_p (cert2)
-                                            : gpgsm_cert_use_encrypt_p (cert2)
+                                      : gpgsm_cert_use_encrypt_p (cert2)
                                       )
                                      )  == GPG_ERR_WRONG_KEY_USAGE));
+                      if (tmp)
+                        gpgsm_add_cert_to_certlist (ctrl, cert2,
+                                                    &dup_certs, 0);
+                      else
+                        {
+                          if (is_cert_in_certlist (cert2, dup_certs))
+                            tmp = 1;
+                        }
+                      
                       ksba_cert_release (cert2);
                       if (tmp)
                         goto next_ambigious;
                     }
                   rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
                 }
+              gpgsm_release_certlist (dup_certs);
             }
           xfree (subject);
           xfree (issuer);
@@ -464,13 +507,27 @@ gpgsm_find_cert (const char *name, ksba_sexp_t keyid, ksba_cert_t *r_cert)
              won't lead to ambiguous names. */
           if (!rc && !keyid)
             {
+            next_ambiguous:
               rc = keydb_search (kh, &desc, 1);
               if (rc == -1)
                 rc = 0;
               else 
                 {
                   if (!rc)
-                    rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
+                    {
+                      ksba_cert_t cert2 = NULL;
+
+                      if (!keydb_get_cert (kh, &cert2))
+                        {
+                          if (gpgsm_certs_identical_p (*r_cert, cert2))
+                            {
+                              ksba_cert_release (cert2);
+                              goto next_ambiguous;
+                            }
+                          ksba_cert_release (cert2);
+                        }
+                      rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
+                    }
                   ksba_cert_release (*r_cert);
                   *r_cert = NULL;
                 }
index 2685d67..e6c29ef 100644 (file)
@@ -1,5 +1,5 @@
-/* export.c
- * Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc.
+/* export.c - Export certificates and private keys.
+ * Copyright (C) 2002, 2003, 2004, 2007 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -379,10 +379,24 @@ gpgsm_p12_export (ctrl_t ctrl, const char *name, FILE *fp)
           log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc));
           goto leave;
         }
-      
+
+    next_ambiguous:      
       rc = keydb_search (hd, desc, 1);
       if (!rc)
-        rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
+        {
+          ksba_cert_t cert2 = NULL;
+
+          if (!keydb_get_cert (hd, &cert2))
+            {
+              if (gpgsm_certs_identical_p (cert, cert2))
+                {
+                  ksba_cert_release (cert2);
+                  goto next_ambiguous;
+                }
+              ksba_cert_release (cert2);
+            }
+          rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
+        }
       else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
         rc = 0;
       if (rc)
index dffd126..7c90665 100644 (file)
@@ -1,5 +1,5 @@
 /* gpgsm.h - Global definitions for GpgSM
- *     Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc.
+ * Copyright (C) 2001, 2003, 2004, 2007 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -287,6 +287,7 @@ int gpgsm_cert_use_verify_p (ksba_cert_t cert);
 int gpgsm_cert_use_decrypt_p (ksba_cert_t cert);
 int gpgsm_cert_use_cert_p (ksba_cert_t cert);
 int gpgsm_cert_use_ocsp_p (ksba_cert_t cert);
+int gpgsm_certs_identical_p (ksba_cert_t cert_a, ksba_cert_t cert_b);
 int gpgsm_add_cert_to_certlist (ctrl_t ctrl, ksba_cert_t cert,
                                 certlist_t *listaddr, int is_encrypt_to);
 int gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret,