From 698ba5ae3cc6ceee476188ad794b295e664793bf Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 23 Aug 2007 17:41:22 +0000 Subject: [PATCH] Add new features to kbxutil. Fixed bug 829 (can't encrypt if duplicated certs are in the keybox) --- kbx/ChangeLog | 9 ++ kbx/kbxutil.c | 43 ++++++++- kbx/keybox-defs.h | 5 +- kbx/keybox-dump.c | 240 +++++++++++++++++++++++++++++++++++++++++++--- sm/ChangeLog | 8 ++ sm/certlist.c | 65 ++++++++++++- sm/export.c | 22 ++++- sm/gpgsm.h | 3 +- 8 files changed, 370 insertions(+), 25 deletions(-) diff --git a/kbx/ChangeLog b/kbx/ChangeLog index b36746d0b..edcf917fd 100644 --- a/kbx/ChangeLog +++ b/kbx/ChangeLog @@ -1,3 +1,12 @@ +2007-08-23 Werner Koch + + * 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 * kbxutil.c: Include init.h diff --git a/kbx/kbxutil.c b/kbx/kbxutil.c index 8bc545dc9..cadc06775 100644 --- a/kbx/kbxutil.c +++ b/kbx/kbxutil.c @@ -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 #include #include +#include #include #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) diff --git a/kbx/keybox-defs.h b/kbx/keybox-defs.h index ffdca2b9b..c425cdd40 100644 --- a/kbx/keybox-defs.h +++ b/kbx/keybox-defs.h @@ -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) diff --git a/kbx/keybox-dump.c b/kbx/keybox-dump.c index 1525a033f..fe68bf1fc 100644 --- a/kbx/keybox-dump.c +++ b/kbx/keybox-dump.c @@ -24,6 +24,11 @@ #include #include "keybox-defs.h" +#include + +/* 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) +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; } + + + +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; +} diff --git a/sm/ChangeLog b/sm/ChangeLog index 8bcc22a0d..6805969de 100644 --- a/sm/ChangeLog +++ b/sm/ChangeLog @@ -1,3 +1,11 @@ +2007-08-23 Werner Koch + + * 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 * certreqgen.c (create_request): Replace open coding by bin2hex. diff --git a/sm/certlist.c b/sm/certlist.c index 5c08e317c..3afdbc30b 100644 --- a/sm/certlist.c +++ b/sm/certlist.c @@ -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; } diff --git a/sm/export.c b/sm/export.c index 2685d67da..e6c29ef17 100644 --- a/sm/export.c +++ b/sm/export.c @@ -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) diff --git a/sm/gpgsm.h b/sm/gpgsm.h index dffd12618..7c9066577 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -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,