diff options
author | Werner Koch <wk@gnupg.org> | 2004-08-24 21:55:47 +0200 |
---|---|---|
committer | Werner Koch <wk@gnupg.org> | 2004-08-24 21:55:47 +0200 |
commit | 2a09a35ad07494747f55957286fdc5ad78871451 (patch) | |
tree | 3b961e695a97fa554a93869b739dd939211bb11e /kbx/keybox-openpgp.c | |
parent | Fixed typo in ocsp OID. (diff) | |
download | gnupg2-2a09a35ad07494747f55957286fdc5ad78871451.tar.xz gnupg2-2a09a35ad07494747f55957286fdc5ad78871451.zip |
* 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.
Diffstat (limited to '')
-rw-r--r-- | kbx/keybox-openpgp.c | 516 |
1 files changed, 516 insertions, 0 deletions
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 <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); + } +} |