diff --git a/kbx/ChangeLog b/kbx/ChangeLog index 1c3b9e3a1..f13434de8 100644 --- a/kbx/ChangeLog +++ b/kbx/ChangeLog @@ -1,3 +1,13 @@ +2004-08-24 Werner Koch + + * 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 * keybox-dump.c (_keybox_dump_file): New arg STATS_ONLY. diff --git a/kbx/Makefile.am b/kbx/Makefile.am index 4f0c40043..6b6a59b26 100644 --- a/kbx/Makefile.am +++ b/kbx/Makefile.am @@ -37,6 +37,7 @@ common_sources = \ keybox-file.c \ keybox-search.c \ keybox-update.c \ + keybox-openpgp.c \ keybox-dump.c diff --git a/kbx/kbxutil.c b/kbx/kbxutil.c index 37c19130b..cd5da5f77 100644 --- a/kbx/kbxutil.c +++ b/kbx/kbxutil.c @@ -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. * @@ -24,11 +24,15 @@ #include #include #include +#include #include +#include +#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 ) diff --git a/kbx/keybox-defs.h b/kbx/keybox-defs.h index 759289a0e..4906a386e 100644 --- a/kbx/keybox-defs.h +++ b/kbx/keybox-defs.h @@ -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 index 000000000..7e6e0e412 --- /dev/null +++ b/kbx/keybox-openpgp.c @@ -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 +#include +#include +#include +#include +#include + +#include "keybox-defs.h" + +#include + + +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); + } +}