mirror of
git://git.gnupg.org/gnupg.git
synced 2024-10-29 19:48:43 +01:00
d528d0b065
* g10/gpg.c (aAddRecipients, aChangeRecipients): New consts. (opts): Add --add-recipients and --change-recipients. (main): Handle them. * g10/gpg.h (struct server_control_s): Add fields modify_recipients, clear_recipients, and last_read_ctb. * g10/armor.c (was_armored): New. * g10/decrypt.c (decrypt_message): Add optional arg 'remusr'. Handle re-encryption if desired. * g10/encrypt.c (write_pubkey_enc): Factor info printing out to ... (show_encrypted_for_user_info): new. (reencrypt_to_new_recipients): New. * g10/packet.h (struct parse_packet_ctx_s): Add fields only_fookey_enc and last_ctb. (init_parse_packet): Clear them. * g10/parse-packet.c (parse): Store CTB in the context. Early return on pubkey_enc and symkey_enc packets if requested. * g10/mainproc.c (proc_encrypted): Allow for PKT being NULL. Return early in modify-recipients mode. (proc_encryption_packets): Add two optional args 'r_dek' and 'r_list'. Adjust callers. Call do_proc_packets in modify-recipients mode depending on the optional args. (do_proc_packets): Add arg 'keep_dek_and_list'. Adjust callers. Save the last read CTB in CTRL and return after the last fooenc_enc packets. -- This basically works but does not yet handle symmetric encrypted packets (symkey_enc). GnuPG-bug-id: 1825 (Yes, this is an at least 9 year old feature request)
3970 lines
106 KiB
C
3970 lines
106 KiB
C
/* parse-packet.c - read packets
|
|
* Copyright (C) 1998-2007, 2009-2010 Free Software Foundation, Inc.
|
|
* Copyright (C) 2014, 2018 Werner Koch
|
|
* Copyright (C) 2015 g10 Code GmbH
|
|
*
|
|
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
|
* SPDX-License-Identifier: GPL-3.0+
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "gpg.h"
|
|
#include "../common/util.h"
|
|
#include "packet.h"
|
|
#include "../common/iobuf.h"
|
|
#include "filter.h"
|
|
#include "photoid.h"
|
|
#include "options.h"
|
|
#include "main.h"
|
|
#include "../common/i18n.h"
|
|
#include "../common/host2net.h"
|
|
#include "../common/mbox-util.h"
|
|
|
|
|
|
static int mpi_print_mode;
|
|
static int list_mode;
|
|
static estream_t listfp;
|
|
|
|
/* A linked list of known notation names. Note that the FLAG is used
|
|
* to store the length of the name to speed up the check. */
|
|
static strlist_t known_notations_list;
|
|
|
|
|
|
static int parse (parse_packet_ctx_t ctx, PACKET *pkt, int onlykeypkts,
|
|
off_t * retpos, int *skip, IOBUF out, int do_skip
|
|
#if DEBUG_PARSE_PACKET
|
|
, const char *dbg_w, const char *dbg_f, int dbg_l
|
|
#endif
|
|
);
|
|
static int copy_packet (IOBUF inp, IOBUF out, int pkttype,
|
|
unsigned long pktlen, int partial);
|
|
static void skip_packet (IOBUF inp, int pkttype,
|
|
unsigned long pktlen, int partial);
|
|
static void *read_rest (IOBUF inp, size_t pktlen);
|
|
static int parse_marker (IOBUF inp, int pkttype, unsigned long pktlen);
|
|
static int parse_symkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * packet);
|
|
static int parse_pubkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * packet);
|
|
static int parse_onepass_sig (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PKT_onepass_sig * ops);
|
|
static int parse_key (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
byte * hdr, int hdrlen, PACKET * packet);
|
|
static int parse_user_id (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * packet);
|
|
static int parse_attribute (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * packet);
|
|
static int parse_comment (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * packet);
|
|
static gpg_error_t parse_ring_trust (parse_packet_ctx_t ctx,
|
|
unsigned long pktlen);
|
|
static int parse_plaintext (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * packet, int new_ctb, int partial);
|
|
static int parse_compressed (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * packet, int new_ctb);
|
|
static int parse_encrypted (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * packet, int new_ctb, int partial);
|
|
static gpg_error_t parse_encrypted_aead (IOBUF inp, int pkttype,
|
|
unsigned long pktlen, PACKET *packet,
|
|
int partial);
|
|
static int parse_mdc (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * packet, int new_ctb);
|
|
static int parse_gpg_control (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * packet, int partial);
|
|
|
|
/* Read a 16-bit value in MSB order (big endian) from an iobuf. */
|
|
static unsigned short
|
|
read_16 (IOBUF inp)
|
|
{
|
|
unsigned short a;
|
|
a = (unsigned short)iobuf_get_noeof (inp) << 8;
|
|
a |= iobuf_get_noeof (inp);
|
|
return a;
|
|
}
|
|
|
|
|
|
/* Read a 32-bit value in MSB order (big endian) from an iobuf. */
|
|
static unsigned long
|
|
read_32 (IOBUF inp)
|
|
{
|
|
unsigned long a;
|
|
a = (unsigned long)iobuf_get_noeof (inp) << 24;
|
|
a |= iobuf_get_noeof (inp) << 16;
|
|
a |= iobuf_get_noeof (inp) << 8;
|
|
a |= iobuf_get_noeof (inp);
|
|
return a;
|
|
}
|
|
|
|
|
|
/* Read an external representation of an MPI and return the MPI. The
|
|
external format is a 16-bit unsigned value stored in network byte
|
|
order giving the number of bits for the following integer. The
|
|
integer is stored MSB first and is left padded with zero bits to
|
|
align on a byte boundary.
|
|
|
|
The caller must set *RET_NREAD to the maximum number of bytes to
|
|
read from the pipeline INP. This function sets *RET_NREAD to be
|
|
the number of bytes actually read from the pipeline.
|
|
|
|
If SECURE is true, the integer is stored in secure memory
|
|
(allocated using gcry_xmalloc_secure). */
|
|
static gcry_mpi_t
|
|
mpi_read (iobuf_t inp, unsigned int *ret_nread, int secure)
|
|
{
|
|
int c, c1, c2, i;
|
|
unsigned int nmax = *ret_nread;
|
|
unsigned int nbits, nbytes;
|
|
size_t nread = 0;
|
|
gcry_mpi_t a = NULL;
|
|
byte *buf = NULL;
|
|
byte *p;
|
|
|
|
if (!nmax)
|
|
goto overflow;
|
|
|
|
if ((c = c1 = iobuf_get (inp)) == -1)
|
|
goto leave;
|
|
if (++nread == nmax)
|
|
goto overflow;
|
|
nbits = c << 8;
|
|
if ((c = c2 = iobuf_get (inp)) == -1)
|
|
goto leave;
|
|
++nread;
|
|
nbits |= c;
|
|
if (nbits > MAX_EXTERN_MPI_BITS)
|
|
{
|
|
log_error ("mpi too large (%u bits)\n", nbits);
|
|
goto leave;
|
|
}
|
|
|
|
nbytes = (nbits + 7) / 8;
|
|
buf = secure ? gcry_xmalloc_secure (nbytes + 2) : gcry_xmalloc (nbytes + 2);
|
|
p = buf;
|
|
p[0] = c1;
|
|
p[1] = c2;
|
|
for (i = 0; i < nbytes; i++)
|
|
{
|
|
if (nread == nmax)
|
|
goto overflow;
|
|
|
|
c = iobuf_get (inp);
|
|
if (c == -1)
|
|
goto leave;
|
|
|
|
p[i + 2] = c;
|
|
nread ++;
|
|
}
|
|
|
|
if (gcry_mpi_scan (&a, GCRYMPI_FMT_PGP, buf, nread, &nread))
|
|
a = NULL;
|
|
|
|
*ret_nread = nread;
|
|
gcry_free(buf);
|
|
return a;
|
|
|
|
overflow:
|
|
log_error ("mpi larger than indicated length (%u bits)\n", 8*nmax);
|
|
leave:
|
|
*ret_nread = nread;
|
|
gcry_free(buf);
|
|
return a;
|
|
}
|
|
|
|
|
|
/* If NLENGTH is zero read an octet string of length NBYTES from INP
|
|
* and return it at R_DATA.
|
|
*
|
|
* If NLENGTH is either 1, 2, or 4 and NLENGTH is zero read an
|
|
* NLENGTH-octet count and use this count number octets from INP and
|
|
* return it at R_DATA.
|
|
*
|
|
* On error return an error code and store NULL at R_DATA. PKTLEN
|
|
* shall give the current length of the packet and is updated with
|
|
* each read. If SECURE is true, the integer is stored in secure
|
|
* memory (allocated using gcry_xmalloc_secure).
|
|
*/
|
|
static gpg_error_t
|
|
read_octet_string (iobuf_t inp, unsigned long *pktlen,
|
|
unsigned int nlength, unsigned int nbytes,
|
|
int secure, gcry_mpi_t *r_data)
|
|
{
|
|
gpg_error_t err;
|
|
int c, i;
|
|
byte *buf = NULL;
|
|
byte *p;
|
|
|
|
*r_data = NULL;
|
|
|
|
if ((nbytes && nlength)
|
|
|| (!nbytes && !(nlength == 1 || nlength == 2 || nlength == 4)))
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_ARG);
|
|
goto leave;
|
|
}
|
|
|
|
if (nlength)
|
|
{
|
|
for (i = 0; i < nlength; i++)
|
|
{
|
|
if (!*pktlen)
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
c = iobuf_readbyte (inp);
|
|
if (c < 0)
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
--*pktlen;
|
|
nbytes <<= 8;
|
|
nbytes |= c;
|
|
}
|
|
|
|
if (!nbytes)
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
}
|
|
|
|
if (nbytes*8 > (nbytes==4? MAX_EXTERN_KEYPARM_BITS:MAX_EXTERN_MPI_BITS)
|
|
|| (nbytes*8 < nbytes))
|
|
{
|
|
log_error ("octet string too large (%u octets)\n", nbytes);
|
|
err = gpg_error (GPG_ERR_TOO_LARGE);
|
|
goto leave;
|
|
}
|
|
|
|
if (nbytes > *pktlen)
|
|
{
|
|
log_error ("octet string larger than packet (%u octets)\n", nbytes);
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
|
|
buf = secure ? gcry_malloc_secure (nbytes) : gcry_malloc (nbytes);
|
|
if (!buf)
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
goto leave;
|
|
}
|
|
p = buf;
|
|
for (i = 0; i < nbytes; i++)
|
|
{
|
|
c = iobuf_get (inp);
|
|
if (c == -1)
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
|
|
p[i] = c;
|
|
--*pktlen;
|
|
}
|
|
|
|
*r_data = gcry_mpi_set_opaque (NULL, buf, nbytes*8);
|
|
gcry_mpi_set_flag (*r_data, GCRYMPI_FLAG_USER2);
|
|
return 0;
|
|
|
|
leave:
|
|
gcry_free (buf);
|
|
return err;
|
|
}
|
|
|
|
|
|
/* Read an external representation of an SOS and return the opaque MPI
|
|
with GCRYMPI_FLAG_USER2. The external format is a 16-bit unsigned
|
|
value stored in network byte order giving information for the
|
|
following octets.
|
|
|
|
The caller must set *RET_NREAD to the maximum number of bytes to
|
|
read from the pipeline INP. This function sets *RET_NREAD to be
|
|
the number of bytes actually read from the pipeline.
|
|
|
|
If SECURE is true, the integer is stored in secure memory
|
|
(allocated using gcry_xmalloc_secure). */
|
|
static gcry_mpi_t
|
|
sos_read (iobuf_t inp, unsigned int *ret_nread, int secure)
|
|
{
|
|
int c, c1, c2, i;
|
|
unsigned int nmax = *ret_nread;
|
|
unsigned int nbits, nbytes;
|
|
size_t nread = 0;
|
|
gcry_mpi_t a = NULL;
|
|
byte *buf = NULL;
|
|
byte *p;
|
|
|
|
if (!nmax)
|
|
goto overflow;
|
|
|
|
if ((c = c1 = iobuf_get (inp)) == -1)
|
|
goto leave;
|
|
if (++nread == nmax)
|
|
goto overflow;
|
|
nbits = c << 8;
|
|
if ((c = c2 = iobuf_get (inp)) == -1)
|
|
goto leave;
|
|
++nread;
|
|
nbits |= c;
|
|
if (nbits > MAX_EXTERN_MPI_BITS)
|
|
{
|
|
log_error ("mpi too large (%u bits)\n", nbits);
|
|
goto leave;
|
|
}
|
|
|
|
nbytes = (nbits + 7) / 8;
|
|
buf = secure ? gcry_xmalloc_secure (nbytes) : gcry_xmalloc (nbytes);
|
|
p = buf;
|
|
for (i = 0; i < nbytes; i++)
|
|
{
|
|
if (nread == nmax)
|
|
goto overflow;
|
|
|
|
c = iobuf_get (inp);
|
|
if (c == -1)
|
|
goto leave;
|
|
|
|
p[i] = c;
|
|
nread ++;
|
|
}
|
|
|
|
a = gcry_mpi_set_opaque (NULL, buf, nbits);
|
|
gcry_mpi_set_flag (a, GCRYMPI_FLAG_USER2);
|
|
*ret_nread = nread;
|
|
return a;
|
|
|
|
overflow:
|
|
log_error ("mpi larger than indicated length (%u bits)\n", 8*nmax);
|
|
leave:
|
|
*ret_nread = nread;
|
|
gcry_free(buf);
|
|
return a;
|
|
}
|
|
|
|
|
|
/* Register STRING as a known critical notation name. */
|
|
void
|
|
register_known_notation (const char *string)
|
|
{
|
|
strlist_t sl;
|
|
|
|
if (!known_notations_list)
|
|
{
|
|
sl = add_to_strlist (&known_notations_list,
|
|
"preferred-email-encoding@pgp.com");
|
|
sl->flags = 32; /* Length of the string. */
|
|
}
|
|
if (!string)
|
|
return; /* Only initialized the default known notations. */
|
|
|
|
/* In --set-notation we use an exclamation mark to indicate a
|
|
* critical notation. As a convenience skip this here. */
|
|
if (*string == '!')
|
|
string++;
|
|
|
|
if (!*string || strlist_find (known_notations_list, string))
|
|
return; /* Empty string or already registered. */
|
|
|
|
sl = add_to_strlist (&known_notations_list, string);
|
|
sl->flags = strlen (string);
|
|
}
|
|
|
|
|
|
int
|
|
set_packet_list_mode (int mode)
|
|
{
|
|
int old = list_mode;
|
|
list_mode = mode;
|
|
|
|
/* We use stdout only if invoked by the --list-packets command
|
|
but switch to stderr in all other cases. This breaks the
|
|
previous behaviour but that seems to be more of a bug than
|
|
intentional. I don't believe that any application makes use of
|
|
this long standing annoying way of printing to stdout except when
|
|
doing a --list-packets. If this assumption fails, it will be easy
|
|
to add an option for the listing stream. Note that we initialize
|
|
it only once; mainly because there is code which switches
|
|
opt.list_mode back to 1 and we want to have all output to the
|
|
same stream. The MPI_PRINT_MODE will be enabled if the
|
|
corresponding debug flag is set or if we are in --list-packets
|
|
and --verbose is given.
|
|
|
|
Using stderr is not actually very clean because it bypasses the
|
|
logging code but it is a special thing anyway. I am not sure
|
|
whether using log_stream() would be better. Perhaps we should
|
|
enable the list mode only with a special option. */
|
|
if (!listfp)
|
|
{
|
|
if (opt.list_packets)
|
|
{
|
|
listfp = es_stdout;
|
|
if (opt.verbose)
|
|
mpi_print_mode = 1;
|
|
}
|
|
else
|
|
listfp = es_stderr;
|
|
|
|
if (DBG_MPI)
|
|
mpi_print_mode = 1;
|
|
}
|
|
return old;
|
|
}
|
|
|
|
|
|
/* If OPT.VERBOSE is set, print a warning that the algorithm ALGO is
|
|
not suitable for signing and encryption. */
|
|
static void
|
|
unknown_pubkey_warning (int algo)
|
|
{
|
|
static byte unknown_pubkey_algos[256];
|
|
|
|
/* First check whether the algorithm is usable but not suitable for
|
|
encryption/signing. */
|
|
if (pubkey_get_npkey (algo))
|
|
{
|
|
if (opt.verbose && !glo_ctrl.silence_parse_warnings)
|
|
{
|
|
if (!pubkey_get_nsig (algo))
|
|
log_info ("public key algorithm %s not suitable for %s\n",
|
|
openpgp_pk_algo_name (algo), "signing");
|
|
if (!pubkey_get_nenc (algo))
|
|
log_info ("public key algorithm %s not suitable for %s\n",
|
|
openpgp_pk_algo_name (algo), "encryption");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
algo &= 0xff;
|
|
if (!unknown_pubkey_algos[algo])
|
|
{
|
|
if (opt.verbose && !glo_ctrl.silence_parse_warnings)
|
|
log_info (_("can't handle public key algorithm %d\n"), algo);
|
|
unknown_pubkey_algos[algo] = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#if DEBUG_PARSE_PACKET
|
|
int
|
|
dbg_parse_packet (parse_packet_ctx_t ctx, PACKET *pkt,
|
|
const char *dbg_f, int dbg_l)
|
|
{
|
|
int skip, rc;
|
|
|
|
do
|
|
{
|
|
rc = parse (ctx, pkt, 0, NULL, &skip, NULL, 0, "parse", dbg_f, dbg_l);
|
|
}
|
|
while (skip && ! rc);
|
|
return rc;
|
|
}
|
|
#else /*!DEBUG_PARSE_PACKET*/
|
|
int
|
|
parse_packet (parse_packet_ctx_t ctx, PACKET *pkt)
|
|
{
|
|
int skip, rc;
|
|
|
|
do
|
|
{
|
|
rc = parse (ctx, pkt, 0, NULL, &skip, NULL, 0);
|
|
}
|
|
while (skip && ! rc);
|
|
return rc;
|
|
}
|
|
#endif /*!DEBUG_PARSE_PACKET*/
|
|
|
|
|
|
/*
|
|
* Like parse packet, but only return secret or public (sub)key
|
|
* packets.
|
|
*/
|
|
#if DEBUG_PARSE_PACKET
|
|
int
|
|
dbg_search_packet (parse_packet_ctx_t ctx, PACKET *pkt,
|
|
off_t * retpos, int with_uid,
|
|
const char *dbg_f, int dbg_l)
|
|
{
|
|
int skip, rc;
|
|
|
|
do
|
|
{
|
|
rc = parse (ctx, pkt, with_uid ? 2 : 1, retpos, &skip, NULL, 0, "search",
|
|
dbg_f, dbg_l);
|
|
}
|
|
while (skip && ! rc);
|
|
return rc;
|
|
}
|
|
#else /*!DEBUG_PARSE_PACKET*/
|
|
int
|
|
search_packet (parse_packet_ctx_t ctx, PACKET *pkt,
|
|
off_t * retpos, int with_uid)
|
|
{
|
|
int skip, rc;
|
|
|
|
do
|
|
{
|
|
rc = parse (ctx, pkt, with_uid ? 2 : 1, retpos, &skip, NULL, 0);
|
|
}
|
|
while (skip && ! rc);
|
|
return rc;
|
|
}
|
|
#endif /*!DEBUG_PARSE_PACKET*/
|
|
|
|
|
|
/*
|
|
* Copy all packets from INP to OUT, thereby removing unused spaces.
|
|
*/
|
|
#if DEBUG_PARSE_PACKET
|
|
int
|
|
dbg_copy_all_packets (iobuf_t inp, iobuf_t out, const char *dbg_f, int dbg_l)
|
|
{
|
|
PACKET pkt;
|
|
struct parse_packet_ctx_s parsectx;
|
|
int skip, rc = 0;
|
|
|
|
if (! out)
|
|
log_bug ("copy_all_packets: OUT may not be NULL.\n");
|
|
|
|
init_parse_packet (&parsectx, inp);
|
|
|
|
do
|
|
{
|
|
init_packet (&pkt);
|
|
}
|
|
while (!
|
|
(rc =
|
|
parse (&parsectx, &pkt, 0, NULL, &skip, out, 0, "copy",
|
|
dbg_f, dbg_l)));
|
|
|
|
deinit_parse_packet (&parsectx);
|
|
|
|
return rc;
|
|
}
|
|
#else /*!DEBUG_PARSE_PACKET*/
|
|
int
|
|
copy_all_packets (iobuf_t inp, iobuf_t out)
|
|
{
|
|
PACKET pkt;
|
|
struct parse_packet_ctx_s parsectx;
|
|
int skip, rc = 0;
|
|
|
|
if (! out)
|
|
log_bug ("copy_all_packets: OUT may not be NULL.\n");
|
|
|
|
init_parse_packet (&parsectx, inp);
|
|
|
|
do
|
|
{
|
|
init_packet (&pkt);
|
|
}
|
|
while (!(rc = parse (&parsectx, &pkt, 0, NULL, &skip, out, 0)));
|
|
|
|
deinit_parse_packet (&parsectx);
|
|
|
|
return rc;
|
|
}
|
|
#endif /*!DEBUG_PARSE_PACKET*/
|
|
|
|
|
|
/*
|
|
* Copy some packets from INP to OUT, thereby removing unused spaces.
|
|
* Stop at offset STOPoff (i.e. don't copy packets at this or later
|
|
* offsets)
|
|
*/
|
|
#if DEBUG_PARSE_PACKET
|
|
int
|
|
dbg_copy_some_packets (iobuf_t inp, iobuf_t out, off_t stopoff,
|
|
const char *dbg_f, int dbg_l)
|
|
{
|
|
int rc = 0;
|
|
PACKET pkt;
|
|
int skip;
|
|
struct parse_packet_ctx_s parsectx;
|
|
|
|
init_parse_packet (&parsectx, inp);
|
|
|
|
do
|
|
{
|
|
if (iobuf_tell (inp) >= stopoff)
|
|
{
|
|
deinit_parse_packet (&parsectx);
|
|
return 0;
|
|
}
|
|
init_packet (&pkt);
|
|
}
|
|
while (!(rc = parse (&parsectx, &pkt, 0, NULL, &skip, out, 0,
|
|
"some", dbg_f, dbg_l)));
|
|
|
|
deinit_parse_packet (&parsectx);
|
|
|
|
return rc;
|
|
}
|
|
#else /*!DEBUG_PARSE_PACKET*/
|
|
int
|
|
copy_some_packets (iobuf_t inp, iobuf_t out, off_t stopoff)
|
|
{
|
|
int rc = 0;
|
|
PACKET pkt;
|
|
struct parse_packet_ctx_s parsectx;
|
|
int skip;
|
|
|
|
init_parse_packet (&parsectx, inp);
|
|
|
|
do
|
|
{
|
|
if (iobuf_tell (inp) >= stopoff)
|
|
{
|
|
deinit_parse_packet (&parsectx);
|
|
return 0;
|
|
}
|
|
init_packet (&pkt);
|
|
}
|
|
while (!(rc = parse (&parsectx, &pkt, 0, NULL, &skip, out, 0)));
|
|
|
|
deinit_parse_packet (&parsectx);
|
|
|
|
return rc;
|
|
}
|
|
#endif /*!DEBUG_PARSE_PACKET*/
|
|
|
|
|
|
/*
|
|
* Skip over N packets
|
|
*/
|
|
#if DEBUG_PARSE_PACKET
|
|
int
|
|
dbg_skip_some_packets (iobuf_t inp, unsigned n, const char *dbg_f, int dbg_l)
|
|
{
|
|
int rc = 0;
|
|
int skip;
|
|
PACKET pkt;
|
|
struct parse_packet_ctx_s parsectx;
|
|
|
|
init_parse_packet (&parsectx, inp);
|
|
|
|
for (; n && !rc; n--)
|
|
{
|
|
init_packet (&pkt);
|
|
rc = parse (&parsectx, &pkt, 0, NULL, &skip, NULL, 1, "skip",
|
|
dbg_f, dbg_l);
|
|
}
|
|
|
|
deinit_parse_packet (&parsectx);
|
|
|
|
return rc;
|
|
}
|
|
#else /*!DEBUG_PARSE_PACKET*/
|
|
int
|
|
skip_some_packets (iobuf_t inp, unsigned int n)
|
|
{
|
|
int rc = 0;
|
|
int skip;
|
|
PACKET pkt;
|
|
struct parse_packet_ctx_s parsectx;
|
|
|
|
init_parse_packet (&parsectx, inp);
|
|
|
|
for (; n && !rc; n--)
|
|
{
|
|
init_packet (&pkt);
|
|
rc = parse (&parsectx, &pkt, 0, NULL, &skip, NULL, 1);
|
|
}
|
|
|
|
deinit_parse_packet (&parsectx);
|
|
|
|
return rc;
|
|
}
|
|
#endif /*!DEBUG_PARSE_PACKET*/
|
|
|
|
|
|
/* Parse a packet and save it in *PKT.
|
|
|
|
If OUT is not NULL and the packet is valid (its type is not 0),
|
|
then the header, the initial length field and the packet's contents
|
|
are written to OUT. In this case, the packet is not saved in *PKT.
|
|
|
|
ONLYKEYPKTS is a simple packet filter. If ONLYKEYPKTS is set to 1,
|
|
then only public subkey packets, public key packets, private subkey
|
|
packets and private key packets are parsed. The rest are skipped
|
|
(i.e., the header and the contents are read from the pipeline and
|
|
discarded). If ONLYKEYPKTS is set to 2, then in addition to the
|
|
above 4 types of packets, user id packets are also accepted.
|
|
|
|
DO_SKIP is a more coarse grained filter. Unless ONLYKEYPKTS is set
|
|
to 2 and the packet is a user id packet, all packets are skipped.
|
|
|
|
Finally, if a packet is invalid (it's type is 0), it is skipped.
|
|
|
|
If a packet is skipped and SKIP is not NULL, then *SKIP is set to
|
|
1.
|
|
|
|
Note: ONLYKEYPKTS and DO_SKIP are only respected if OUT is NULL,
|
|
i.e., the packets are not simply being copied.
|
|
|
|
If RETPOS is not NULL, then the position of CTX->INP (as returned by
|
|
iobuf_tell) is saved there before any data is read from CTX->INP.
|
|
*/
|
|
static int
|
|
parse (parse_packet_ctx_t ctx, PACKET *pkt, int onlykeypkts, off_t * retpos,
|
|
int *skip, IOBUF out, int do_skip
|
|
#if DEBUG_PARSE_PACKET
|
|
, const char *dbg_w, const char *dbg_f, int dbg_l
|
|
#endif
|
|
)
|
|
{
|
|
int rc = 0;
|
|
iobuf_t inp;
|
|
int c, ctb, pkttype, lenbytes;
|
|
unsigned long pktlen;
|
|
byte hdr[8];
|
|
int hdrlen;
|
|
int new_ctb = 0, partial = 0;
|
|
int with_uid = (onlykeypkts == 2);
|
|
off_t pos;
|
|
|
|
*skip = 0;
|
|
inp = ctx->inp;
|
|
|
|
again:
|
|
log_assert (!pkt->pkt.generic);
|
|
if (retpos || list_mode)
|
|
{
|
|
pos = iobuf_tell (inp);
|
|
if (retpos)
|
|
*retpos = pos;
|
|
}
|
|
else
|
|
pos = 0; /* (silence compiler warning) */
|
|
|
|
/* The first byte of a packet is the so-called tag. The highest bit
|
|
must be set. */
|
|
if ((ctb = iobuf_get (inp)) == -1)
|
|
{
|
|
rc = -1;
|
|
goto leave;
|
|
}
|
|
ctx->last_ctb = ctb;
|
|
hdrlen = 0;
|
|
hdr[hdrlen++] = ctb;
|
|
|
|
if (!(ctb & 0x80))
|
|
{
|
|
log_error ("%s: invalid packet (ctb=%02x)\n", iobuf_where (inp), ctb);
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
|
|
/* Immediately following the header is the length. There are two
|
|
* formats: the old format and the new format. If bit 6 (where the
|
|
* least significant bit is bit 0) is set in the tag, then we are
|
|
* dealing with a new format packet. Otherwise, it is an old format
|
|
* packet. In the new format the packet's type is encoded in the 6
|
|
* least significant bits of the tag; in the old format it is
|
|
* encoded in bits 2-5. */
|
|
pktlen = 0;
|
|
new_ctb = !!(ctb & 0x40);
|
|
if (new_ctb)
|
|
pkttype = ctb & 0x3f;
|
|
else
|
|
pkttype = (ctb >> 2) & 0xf;
|
|
|
|
if (ctx->only_fookey_enc
|
|
&& !(pkttype == PKT_SYMKEY_ENC || pkttype == PKT_PUBKEY_ENC))
|
|
{
|
|
rc = gpg_error (GPG_ERR_TRUE);
|
|
goto leave;
|
|
}
|
|
|
|
if (new_ctb)
|
|
{
|
|
/* Extract the packet's length. New format packets have 4 ways
|
|
to encode the packet length. The value of the first byte
|
|
determines the encoding and partially determines the length.
|
|
See section 4.2.2 of RFC 4880 for details. */
|
|
if ((c = iobuf_get (inp)) == -1)
|
|
{
|
|
log_error ("%s: 1st length byte missing\n", iobuf_where (inp));
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
|
|
|
|
hdr[hdrlen++] = c;
|
|
if (c < 192)
|
|
pktlen = c;
|
|
else if (c < 224)
|
|
{
|
|
pktlen = (c - 192) * 256;
|
|
if ((c = iobuf_get (inp)) == -1)
|
|
{
|
|
log_error ("%s: 2nd length byte missing\n",
|
|
iobuf_where (inp));
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
hdr[hdrlen++] = c;
|
|
pktlen += c + 192;
|
|
}
|
|
else if (c == 255)
|
|
{
|
|
int i;
|
|
char value[4];
|
|
|
|
for (i = 0; i < 4; i ++)
|
|
{
|
|
if ((c = iobuf_get (inp)) == -1)
|
|
{
|
|
log_error ("%s: 4 byte length invalid\n", iobuf_where (inp));
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
value[i] = hdr[hdrlen++] = c;
|
|
}
|
|
|
|
pktlen = buf32_to_ulong (value);
|
|
}
|
|
else /* Partial body length. */
|
|
{
|
|
switch (pkttype)
|
|
{
|
|
case PKT_PLAINTEXT:
|
|
case PKT_ENCRYPTED:
|
|
case PKT_ENCRYPTED_MDC:
|
|
case PKT_ENCRYPTED_AEAD:
|
|
case PKT_COMPRESSED:
|
|
iobuf_set_partial_body_length_mode (inp, c & 0xff);
|
|
pktlen = 0; /* To indicate partial length. */
|
|
partial = 1;
|
|
break;
|
|
|
|
default:
|
|
log_error ("%s: partial length invalid for"
|
|
" packet type %d\n", iobuf_where (inp), pkttype);
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
}
|
|
|
|
}
|
|
else /* This is an old format packet. */
|
|
{
|
|
/* The type of length encoding is encoded in bits 0-1 of the
|
|
tag. */
|
|
lenbytes = ((ctb & 3) == 3) ? 0 : (1 << (ctb & 3));
|
|
if (!lenbytes)
|
|
{
|
|
pktlen = 0; /* Don't know the value. */
|
|
/* This isn't really partial, but we can treat it the same
|
|
in a "read until the end" sort of way. */
|
|
partial = 1;
|
|
if (pkttype != PKT_ENCRYPTED && pkttype != PKT_PLAINTEXT
|
|
&& pkttype != PKT_COMPRESSED)
|
|
{
|
|
log_error ("%s: indeterminate length for invalid"
|
|
" packet type %d\n", iobuf_where (inp), pkttype);
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (; lenbytes; lenbytes--)
|
|
{
|
|
pktlen <<= 8;
|
|
c = iobuf_get (inp);
|
|
if (c == -1)
|
|
{
|
|
log_error ("%s: length invalid\n", iobuf_where (inp));
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
pktlen |= hdr[hdrlen++] = c;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Sometimes the decompressing layer enters an error state in which
|
|
it simply outputs 0xff for every byte read. If we have a stream
|
|
of 0xff bytes, then it will be detected as a new format packet
|
|
with type 63 and a 4-byte encoded length that is 4G-1. Since
|
|
packets with type 63 are private and we use them as a control
|
|
packet, which won't be 4 GB, we reject such packets as
|
|
invalid. */
|
|
if (pkttype == 63 && pktlen == 0xFFFFFFFF)
|
|
{
|
|
/* With some probability this is caused by a problem in the
|
|
* the uncompressing layer - in some error cases it just loops
|
|
* and spits out 0xff bytes. */
|
|
log_error ("%s: garbled packet detected\n", iobuf_where (inp));
|
|
g10_exit (2);
|
|
}
|
|
|
|
if (out && pkttype)
|
|
{
|
|
/* This type of copying won't work if the packet uses a partial
|
|
body length. (In other words, this only works if HDR is
|
|
actually the length.) Currently, no callers require this
|
|
functionality so we just log this as an error. */
|
|
if (partial)
|
|
{
|
|
log_error ("parse: Can't copy partial packet. Aborting.\n");
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
|
|
rc = iobuf_write (out, hdr, hdrlen);
|
|
if (!rc)
|
|
rc = copy_packet (inp, out, pkttype, pktlen, partial);
|
|
goto leave;
|
|
}
|
|
|
|
if (with_uid && pkttype == PKT_USER_ID)
|
|
/* If ONLYKEYPKTS is set to 2, then we never skip user id packets,
|
|
even if DO_SKIP is set. */
|
|
;
|
|
else if (do_skip
|
|
/* type==0 is not allowed. This is an invalid packet. */
|
|
|| !pkttype
|
|
/* When ONLYKEYPKTS is set, we don't skip keys. */
|
|
|| (onlykeypkts && pkttype != PKT_PUBLIC_SUBKEY
|
|
&& pkttype != PKT_PUBLIC_KEY
|
|
&& pkttype != PKT_SECRET_SUBKEY && pkttype != PKT_SECRET_KEY))
|
|
{
|
|
iobuf_skip_rest (inp, pktlen, partial);
|
|
*skip = 1;
|
|
rc = 0;
|
|
goto leave;
|
|
}
|
|
|
|
if (DBG_PACKET)
|
|
{
|
|
#if DEBUG_PARSE_PACKET
|
|
log_debug ("parse_packet(iob=%d): type=%d length=%lu%s (%s.%s.%d)\n",
|
|
iobuf_id (inp), pkttype, pktlen, new_ctb ? " (new_ctb)" : "",
|
|
dbg_w, dbg_f, dbg_l);
|
|
#else
|
|
log_debug ("parse_packet(iob=%d): type=%d length=%lu%s\n",
|
|
iobuf_id (inp), pkttype, pktlen,
|
|
new_ctb ? " (new_ctb)" : "");
|
|
#endif
|
|
}
|
|
|
|
if (list_mode)
|
|
es_fprintf (listfp, "# off=%lu ctb=%02x tag=%d hlen=%d plen=%lu%s%s\n",
|
|
(unsigned long)pos, ctb, pkttype, hdrlen, pktlen,
|
|
partial? (new_ctb ? " partial" : " indeterminate") :"",
|
|
new_ctb? " new-ctb":"");
|
|
|
|
/* Count it. */
|
|
ctx->n_parsed_packets++;
|
|
|
|
pkt->pkttype = pkttype;
|
|
rc = GPG_ERR_UNKNOWN_PACKET; /* default error */
|
|
switch (pkttype)
|
|
{
|
|
case PKT_PUBLIC_KEY:
|
|
case PKT_PUBLIC_SUBKEY:
|
|
case PKT_SECRET_KEY:
|
|
case PKT_SECRET_SUBKEY:
|
|
pkt->pkt.public_key = xmalloc_clear (sizeof *pkt->pkt.public_key);
|
|
rc = parse_key (inp, pkttype, pktlen, hdr, hdrlen, pkt);
|
|
break;
|
|
case PKT_SYMKEY_ENC:
|
|
rc = parse_symkeyenc (inp, pkttype, pktlen, pkt);
|
|
break;
|
|
case PKT_PUBKEY_ENC:
|
|
rc = parse_pubkeyenc (inp, pkttype, pktlen, pkt);
|
|
break;
|
|
case PKT_SIGNATURE:
|
|
pkt->pkt.signature = xmalloc_clear (sizeof *pkt->pkt.signature);
|
|
rc = parse_signature (inp, pkttype, pktlen, pkt->pkt.signature);
|
|
break;
|
|
case PKT_ONEPASS_SIG:
|
|
pkt->pkt.onepass_sig = xmalloc_clear (sizeof *pkt->pkt.onepass_sig);
|
|
rc = parse_onepass_sig (inp, pkttype, pktlen, pkt->pkt.onepass_sig);
|
|
break;
|
|
case PKT_USER_ID:
|
|
rc = parse_user_id (inp, pkttype, pktlen, pkt);
|
|
break;
|
|
case PKT_ATTRIBUTE:
|
|
pkt->pkttype = pkttype = PKT_USER_ID; /* we store it in the userID */
|
|
rc = parse_attribute (inp, pkttype, pktlen, pkt);
|
|
break;
|
|
case PKT_OLD_COMMENT:
|
|
case PKT_COMMENT:
|
|
rc = parse_comment (inp, pkttype, pktlen, pkt);
|
|
break;
|
|
case PKT_RING_TRUST:
|
|
{
|
|
rc = parse_ring_trust (ctx, pktlen);
|
|
if (!rc)
|
|
goto again; /* Directly read the next packet. */
|
|
}
|
|
break;
|
|
case PKT_PLAINTEXT:
|
|
rc = parse_plaintext (inp, pkttype, pktlen, pkt, new_ctb, partial);
|
|
break;
|
|
case PKT_COMPRESSED:
|
|
rc = parse_compressed (inp, pkttype, pktlen, pkt, new_ctb);
|
|
break;
|
|
case PKT_ENCRYPTED:
|
|
case PKT_ENCRYPTED_MDC:
|
|
rc = parse_encrypted (inp, pkttype, pktlen, pkt, new_ctb, partial);
|
|
break;
|
|
case PKT_MDC:
|
|
rc = parse_mdc (inp, pkttype, pktlen, pkt, new_ctb);
|
|
break;
|
|
case PKT_ENCRYPTED_AEAD:
|
|
rc = parse_encrypted_aead (inp, pkttype, pktlen, pkt, partial);
|
|
break;
|
|
case PKT_GPG_CONTROL:
|
|
rc = parse_gpg_control (inp, pkttype, pktlen, pkt, partial);
|
|
break;
|
|
case PKT_MARKER:
|
|
rc = parse_marker (inp, pkttype, pktlen);
|
|
break;
|
|
default:
|
|
/* Unknown packet. Skip it. */
|
|
skip_packet (inp, pkttype, pktlen, partial);
|
|
break;
|
|
}
|
|
|
|
/* Store a shallow copy of certain packets in the context. */
|
|
free_packet (NULL, ctx);
|
|
if (!rc && (pkttype == PKT_PUBLIC_KEY
|
|
|| pkttype == PKT_SECRET_KEY
|
|
|| pkttype == PKT_USER_ID
|
|
|| pkttype == PKT_ATTRIBUTE
|
|
|| pkttype == PKT_SIGNATURE))
|
|
{
|
|
ctx->last_pkt = *pkt;
|
|
}
|
|
|
|
leave:
|
|
/* FIXME: We leak in case of an error (see the xmalloc's above). */
|
|
if (!rc && iobuf_error (inp))
|
|
rc = GPG_ERR_INV_KEYRING;
|
|
|
|
/* FIXME: We use only the error code for now to avoid problems with
|
|
callers which have not been checked to always use gpg_err_code()
|
|
when comparing error codes. */
|
|
return rc == -1? -1 : gpg_err_code (rc);
|
|
}
|
|
|
|
|
|
static void
|
|
dump_hex_line (int c, int *i)
|
|
{
|
|
if (*i && !(*i % 8))
|
|
{
|
|
if (*i && !(*i % 24))
|
|
es_fprintf (listfp, "\n%4d:", *i);
|
|
else
|
|
es_putc (' ', listfp);
|
|
}
|
|
if (c == -1)
|
|
es_fprintf (listfp, " EOF");
|
|
else
|
|
es_fprintf (listfp, " %02x", c);
|
|
++*i;
|
|
}
|
|
|
|
|
|
/* Copy the contents of a packet from the pipeline IN to the pipeline
|
|
OUT.
|
|
|
|
The header and length have already been read from INP and the
|
|
decoded values are given as PKGTYPE and PKTLEN.
|
|
|
|
If the packet is a partial body length packet (RFC 4880, Section
|
|
4.2.2.4), then iobuf_set_partial_block_modeiobuf_set_partial_block_mode
|
|
should already have been called on INP and PARTIAL should be set.
|
|
|
|
If PARTIAL is set or PKTLEN is 0 and PKTTYPE is PKT_COMPRESSED,
|
|
copy until the first EOF is encountered on INP.
|
|
|
|
Returns 0 on success and an error code if an error occurs. */
|
|
static int
|
|
copy_packet (IOBUF inp, IOBUF out, int pkttype,
|
|
unsigned long pktlen, int partial)
|
|
{
|
|
int rc;
|
|
int n;
|
|
char buf[100];
|
|
|
|
if (partial)
|
|
{
|
|
while ((n = iobuf_read (inp, buf, sizeof (buf))) != -1)
|
|
if ((rc = iobuf_write (out, buf, n)))
|
|
return rc; /* write error */
|
|
}
|
|
else if (!pktlen && pkttype == PKT_COMPRESSED)
|
|
{
|
|
log_debug ("copy_packet: compressed!\n");
|
|
/* compressed packet, copy till EOF */
|
|
while ((n = iobuf_read (inp, buf, sizeof (buf))) != -1)
|
|
if ((rc = iobuf_write (out, buf, n)))
|
|
return rc; /* write error */
|
|
}
|
|
else
|
|
{
|
|
for (; pktlen; pktlen -= n)
|
|
{
|
|
n = pktlen > sizeof (buf) ? sizeof (buf) : pktlen;
|
|
n = iobuf_read (inp, buf, n);
|
|
if (n == -1)
|
|
return gpg_error (GPG_ERR_EOF);
|
|
if ((rc = iobuf_write (out, buf, n)))
|
|
return rc; /* write error */
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Skip an unknown packet. PKTTYPE is the packet's type, PKTLEN is
|
|
the length of the packet's content and PARTIAL is whether partial
|
|
body length encoding in used (in this case PKTLEN is ignored). */
|
|
static void
|
|
skip_packet (IOBUF inp, int pkttype, unsigned long pktlen, int partial)
|
|
{
|
|
if (list_mode)
|
|
{
|
|
es_fprintf (listfp, ":unknown packet: type %2d, length %lu\n",
|
|
pkttype, pktlen);
|
|
if (pkttype)
|
|
{
|
|
int c, i = 0;
|
|
es_fputs ("dump:", listfp);
|
|
if (partial)
|
|
{
|
|
while ((c = iobuf_get (inp)) != -1)
|
|
dump_hex_line (c, &i);
|
|
}
|
|
else
|
|
{
|
|
for (; pktlen; pktlen--)
|
|
{
|
|
dump_hex_line ((c = iobuf_get (inp)), &i);
|
|
if (c == -1)
|
|
break;
|
|
}
|
|
}
|
|
es_putc ('\n', listfp);
|
|
return;
|
|
}
|
|
}
|
|
iobuf_skip_rest (inp, pktlen, partial);
|
|
}
|
|
|
|
|
|
/* Read PKTLEN bytes from INP and return them in a newly allocated
|
|
* buffer. In case of an error (including reading fewer than PKTLEN
|
|
* bytes from INP before EOF is returned), NULL is returned and an
|
|
* error message is logged. */
|
|
static void *
|
|
read_rest (IOBUF inp, size_t pktlen)
|
|
{
|
|
int c;
|
|
byte *buf, *p;
|
|
|
|
buf = xtrymalloc (pktlen);
|
|
if (!buf)
|
|
{
|
|
gpg_error_t err = gpg_error_from_syserror ();
|
|
log_error ("error reading rest of packet: %s\n", gpg_strerror (err));
|
|
return NULL;
|
|
}
|
|
for (p = buf; pktlen; pktlen--)
|
|
{
|
|
c = iobuf_get (inp);
|
|
if (c == -1)
|
|
{
|
|
log_error ("premature eof while reading rest of packet\n");
|
|
xfree (buf);
|
|
return NULL;
|
|
}
|
|
*p++ = c;
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
/* Read a special size+body from INP. On success store an opaque MPI
|
|
* with it at R_DATA. The caller shall store the remaining size of
|
|
* the packet at PKTLEN. On error return an error code and store NULL
|
|
* at R_DATA. Even in the error case store the number of read bytes
|
|
* at PKTLEN is updated. */
|
|
static gpg_error_t
|
|
read_sized_octet_string (iobuf_t inp, unsigned long *pktlen, gcry_mpi_t *r_data)
|
|
{
|
|
char buffer[256];
|
|
char *tmpbuf;
|
|
int i, c, nbytes;
|
|
|
|
*r_data = NULL;
|
|
|
|
if (!*pktlen)
|
|
return gpg_error (GPG_ERR_INV_PACKET);
|
|
c = iobuf_readbyte (inp);
|
|
if (c < 0)
|
|
return gpg_error (GPG_ERR_INV_PACKET);
|
|
--*pktlen;
|
|
nbytes = c;
|
|
if (nbytes < 2 || nbytes > 254)
|
|
return gpg_error (GPG_ERR_INV_PACKET);
|
|
if (nbytes > *pktlen)
|
|
return gpg_error (GPG_ERR_INV_PACKET);
|
|
|
|
buffer[0] = nbytes;
|
|
|
|
for (i = 0; i < nbytes; i++)
|
|
{
|
|
c = iobuf_get (inp);
|
|
if (c < 0)
|
|
return gpg_error (GPG_ERR_INV_PACKET);
|
|
--*pktlen;
|
|
buffer[1+i] = c;
|
|
}
|
|
|
|
tmpbuf = xtrymalloc (1 + nbytes);
|
|
if (!tmpbuf)
|
|
return gpg_error_from_syserror ();
|
|
memcpy (tmpbuf, buffer, 1 + nbytes);
|
|
*r_data = gcry_mpi_set_opaque (NULL, tmpbuf, 8 * (1 + nbytes));
|
|
if (!*r_data)
|
|
{
|
|
xfree (tmpbuf);
|
|
return gpg_error_from_syserror ();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Parse a marker packet. */
|
|
static int
|
|
parse_marker (IOBUF inp, int pkttype, unsigned long pktlen)
|
|
{
|
|
(void) pkttype;
|
|
|
|
if (pktlen != 3)
|
|
goto fail;
|
|
|
|
if (iobuf_get (inp) != 'P')
|
|
{
|
|
pktlen--;
|
|
goto fail;
|
|
}
|
|
|
|
if (iobuf_get (inp) != 'G')
|
|
{
|
|
pktlen--;
|
|
goto fail;
|
|
}
|
|
|
|
if (iobuf_get (inp) != 'P')
|
|
{
|
|
pktlen--;
|
|
goto fail;
|
|
}
|
|
|
|
if (list_mode)
|
|
es_fputs (":marker packet: PGP\n", listfp);
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
log_error ("invalid marker packet\n");
|
|
if (list_mode)
|
|
es_fputs (":marker packet: [invalid]\n", listfp);
|
|
iobuf_skip_rest (inp, pktlen, 0);
|
|
return GPG_ERR_INV_PACKET;
|
|
}
|
|
|
|
|
|
static int
|
|
parse_symkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * packet)
|
|
{
|
|
PKT_symkey_enc *k;
|
|
int rc = 0;
|
|
int i, version, s2kmode, cipher_algo, aead_algo, hash_algo, seskeylen, minlen;
|
|
|
|
if (pktlen < 4)
|
|
goto too_short;
|
|
version = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
if (version == 4)
|
|
;
|
|
else if (version == 5)
|
|
;
|
|
else
|
|
{
|
|
log_error ("packet(%d) with unknown version %d\n", pkttype, version);
|
|
if (list_mode)
|
|
es_fprintf (listfp, ":symkey enc packet: [unknown version]\n");
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
if (pktlen > 200)
|
|
{ /* (we encode the seskeylen in a byte) */
|
|
log_error ("packet(%d) too large\n", pkttype);
|
|
if (list_mode)
|
|
es_fprintf (listfp, ":symkey enc packet: [too large]\n");
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
cipher_algo = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
if (version == 5)
|
|
{
|
|
aead_algo = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
}
|
|
else
|
|
aead_algo = 0;
|
|
if (pktlen < 2)
|
|
goto too_short;
|
|
s2kmode = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
hash_algo = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
switch (s2kmode)
|
|
{
|
|
case 0: /* Simple S2K. */
|
|
minlen = 0;
|
|
break;
|
|
case 1: /* Salted S2K. */
|
|
minlen = 8;
|
|
break;
|
|
case 3: /* Iterated+salted S2K. */
|
|
minlen = 9;
|
|
break;
|
|
default:
|
|
log_error ("unknown S2K mode %d\n", s2kmode);
|
|
if (list_mode)
|
|
es_fprintf (listfp, ":symkey enc packet: [unknown S2K mode]\n");
|
|
goto leave;
|
|
}
|
|
if (minlen > pktlen)
|
|
{
|
|
log_error ("packet with S2K %d too short\n", s2kmode);
|
|
if (list_mode)
|
|
es_fprintf (listfp, ":symkey enc packet: [too short]\n");
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
seskeylen = pktlen - minlen;
|
|
k = packet->pkt.symkey_enc = xmalloc_clear (sizeof *packet->pkt.symkey_enc
|
|
+ seskeylen - 1);
|
|
k->version = version;
|
|
k->cipher_algo = cipher_algo;
|
|
k->aead_algo = aead_algo;
|
|
k->s2k.mode = s2kmode;
|
|
k->s2k.hash_algo = hash_algo;
|
|
if (s2kmode == 1 || s2kmode == 3)
|
|
{
|
|
for (i = 0; i < 8 && pktlen; i++, pktlen--)
|
|
k->s2k.salt[i] = iobuf_get_noeof (inp);
|
|
}
|
|
if (s2kmode == 3)
|
|
{
|
|
k->s2k.count = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
}
|
|
k->seskeylen = seskeylen;
|
|
if (k->seskeylen)
|
|
{
|
|
for (i = 0; i < seskeylen && pktlen; i++, pktlen--)
|
|
k->seskey[i] = iobuf_get_noeof (inp);
|
|
|
|
/* What we're watching out for here is a session key decryptor
|
|
with no salt. The RFC says that using salt for this is a
|
|
MUST. */
|
|
if (s2kmode != 1 && s2kmode != 3)
|
|
log_info (_("WARNING: potentially insecure symmetrically"
|
|
" encrypted session key\n"));
|
|
}
|
|
log_assert (!pktlen);
|
|
|
|
if (list_mode)
|
|
{
|
|
es_fprintf (listfp,
|
|
":symkey enc packet: version %d, cipher %d, aead %d,"
|
|
" s2k %d, hash %d",
|
|
version, cipher_algo, aead_algo, s2kmode, hash_algo);
|
|
if (seskeylen)
|
|
{
|
|
/* To compute the size of the session key we need to know
|
|
* the size of the AEAD nonce which we may not know. Thus
|
|
* we show only the size of the entire encrypted session
|
|
* key. */
|
|
if (aead_algo)
|
|
es_fprintf (listfp, ", encrypted seskey %d bytes", seskeylen);
|
|
else
|
|
es_fprintf (listfp, ", seskey %d bits", (seskeylen - 1) * 8);
|
|
}
|
|
es_fprintf (listfp, "\n");
|
|
if (s2kmode == 1 || s2kmode == 3)
|
|
{
|
|
es_fprintf (listfp, "\tsalt ");
|
|
es_write_hexstring (listfp, k->s2k.salt, 8, 0, NULL);
|
|
if (s2kmode == 3)
|
|
es_fprintf (listfp, ", count %lu (%lu)",
|
|
S2K_DECODE_COUNT ((ulong) k->s2k.count),
|
|
(ulong) k->s2k.count);
|
|
es_fprintf (listfp, "\n");
|
|
}
|
|
}
|
|
|
|
leave:
|
|
iobuf_skip_rest (inp, pktlen, 0);
|
|
return rc;
|
|
|
|
too_short:
|
|
log_error ("packet(%d) too short\n", pkttype);
|
|
if (list_mode)
|
|
es_fprintf (listfp, ":symkey enc packet: [too short]\n");
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
|
|
|
|
/* Parse a public key encrypted packet (Tag 1). */
|
|
static int
|
|
parse_pubkeyenc (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * packet)
|
|
{
|
|
int rc = 0;
|
|
int i, ndata;
|
|
unsigned int n;
|
|
PKT_pubkey_enc *k;
|
|
|
|
k = packet->pkt.pubkey_enc = xmalloc_clear (sizeof *packet->pkt.pubkey_enc);
|
|
if (pktlen < 12)
|
|
{
|
|
log_error ("packet(%d) too short\n", pkttype);
|
|
if (list_mode)
|
|
es_fputs (":pubkey enc packet: [too short]\n", listfp);
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
k->version = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
if (k->version != 2 && k->version != 3)
|
|
{
|
|
log_error ("packet(%d) with unknown version %d\n", pkttype, k->version);
|
|
if (list_mode)
|
|
es_fputs (":pubkey enc packet: [unknown version]\n", listfp);
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
k->keyid[0] = read_32 (inp);
|
|
pktlen -= 4;
|
|
k->keyid[1] = read_32 (inp);
|
|
pktlen -= 4;
|
|
k->pubkey_algo = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
k->throw_keyid = 0; /* Only used as flag for build_packet. */
|
|
if (list_mode)
|
|
es_fprintf (listfp,
|
|
":pubkey enc packet: version %d, algo %d, keyid %08lX%08lX\n",
|
|
k->version, k->pubkey_algo, (ulong) k->keyid[0],
|
|
(ulong) k->keyid[1]);
|
|
|
|
ndata = pubkey_get_nenc (k->pubkey_algo);
|
|
if (!ndata)
|
|
{
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tunsupported algorithm %d\n", k->pubkey_algo);
|
|
unknown_pubkey_warning (k->pubkey_algo);
|
|
k->data[0] = NULL; /* No need to store the encrypted data. */
|
|
}
|
|
else if (k->pubkey_algo == PUBKEY_ALGO_ECDH)
|
|
{
|
|
log_assert (ndata == 2);
|
|
/* Get the ephemeral public key. */
|
|
n = pktlen;
|
|
k->data[0] = sos_read (inp, &n, 0);
|
|
pktlen -= n;
|
|
if (!k->data[0])
|
|
{
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
/* Get the wrapped symmetric key. */
|
|
rc = read_sized_octet_string (inp, &pktlen, k->data + 1);
|
|
if (rc)
|
|
goto leave;
|
|
}
|
|
else if (k->pubkey_algo == PUBKEY_ALGO_KYBER)
|
|
{
|
|
log_assert (ndata == 3);
|
|
/* Get the ephemeral public key. */
|
|
n = pktlen;
|
|
k->data[0] = sos_read (inp, &n, 0);
|
|
pktlen -= n;
|
|
if (!k->data[0])
|
|
{
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
/* Get the Kyber ciphertext. */
|
|
rc = read_octet_string (inp, &pktlen, 4, 0, 0, k->data + 1);
|
|
if (rc)
|
|
goto leave;
|
|
/* Get the algorithm id for the session key. */
|
|
if (!pktlen)
|
|
{
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
k->seskey_algo = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
/* Get the encrypted symmetric key. */
|
|
rc = read_octet_string (inp, &pktlen, 1, 0, 0, k->data + 2);
|
|
if (rc)
|
|
goto leave;
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < ndata; i++)
|
|
{
|
|
n = pktlen;
|
|
k->data[i] = mpi_read (inp, &n, 0);
|
|
pktlen -= n;
|
|
if (!k->data[i])
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
}
|
|
if (rc)
|
|
goto leave;
|
|
}
|
|
if (list_mode)
|
|
{
|
|
if (k->seskey_algo)
|
|
es_fprintf (listfp, "\tsession key algo: %d\n", k->seskey_algo);
|
|
for (i = 0; i < ndata; i++)
|
|
{
|
|
es_fprintf (listfp, "\tdata: ");
|
|
mpi_print (listfp, k->data[i], mpi_print_mode);
|
|
es_putc ('\n', listfp);
|
|
}
|
|
}
|
|
|
|
|
|
leave:
|
|
iobuf_skip_rest (inp, pktlen, 0);
|
|
return rc;
|
|
}
|
|
|
|
|
|
/* Dump a subpacket to LISTFP. BUFFER contains the subpacket in
|
|
* question and points to the type field in the subpacket header (not
|
|
* the start of the header). TYPE is the subpacket's type with the
|
|
* critical bit cleared. CRITICAL is the value of the CRITICAL bit.
|
|
* BUFLEN is the length of the buffer and LENGTH is the length of the
|
|
* subpacket according to the subpacket's header. DIGEST_ALGO is the
|
|
* digest algo of the signature. */
|
|
static void
|
|
dump_sig_subpkt (int hashed, int type, int critical,
|
|
const byte * buffer, size_t buflen, size_t length,
|
|
int digest_algo)
|
|
{
|
|
const char *p = NULL;
|
|
int i;
|
|
int nprinted;
|
|
|
|
/* The CERT has warning out with explains how to use GNUPG to detect
|
|
* the ARRs - we print our old message here when it is a faked ARR
|
|
* and add an additional notice. */
|
|
if (type == SIGSUBPKT_ARR && !hashed)
|
|
{
|
|
es_fprintf (listfp,
|
|
"\tsubpkt %d len %u (additional recipient request)\n"
|
|
"WARNING: PGP versions > 5.0 and < 6.5.8 will automagically "
|
|
"encrypt to this key and thereby reveal the plaintext to "
|
|
"the owner of this ARR key. Detailed info follows:\n",
|
|
type, (unsigned) length);
|
|
}
|
|
|
|
buffer++;
|
|
length--;
|
|
|
|
nprinted = es_fprintf (listfp, "\t%s%ssubpkt %d len %u (", /*) */
|
|
critical ? "critical " : "",
|
|
hashed ? "hashed " : "", type, (unsigned) length);
|
|
if (nprinted < 1)
|
|
nprinted = 1; /*(we use (nprinted-1) later.)*/
|
|
if (length > buflen)
|
|
{
|
|
es_fprintf (listfp, "too short: buffer is only %u)\n", (unsigned) buflen);
|
|
return;
|
|
}
|
|
switch (type)
|
|
{
|
|
case SIGSUBPKT_SIG_CREATED:
|
|
if (length >= 4)
|
|
es_fprintf (listfp, "sig created %s",
|
|
strtimestamp (buf32_to_u32 (buffer)));
|
|
break;
|
|
case SIGSUBPKT_SIG_EXPIRE:
|
|
if (length >= 4)
|
|
{
|
|
if (buf32_to_u32 (buffer))
|
|
es_fprintf (listfp, "sig expires after %s",
|
|
strtimevalue (buf32_to_u32 (buffer)));
|
|
else
|
|
es_fprintf (listfp, "sig does not expire");
|
|
}
|
|
break;
|
|
case SIGSUBPKT_EXPORTABLE:
|
|
if (length)
|
|
es_fprintf (listfp, "%sexportable", *buffer ? "" : "not ");
|
|
break;
|
|
case SIGSUBPKT_TRUST:
|
|
if (length != 2)
|
|
p = "[invalid trust subpacket]";
|
|
else
|
|
es_fprintf (listfp, "trust signature of depth %d, value %d", buffer[0],
|
|
buffer[1]);
|
|
break;
|
|
case SIGSUBPKT_REGEXP:
|
|
if (!length)
|
|
p = "[invalid regexp subpacket]";
|
|
else
|
|
{
|
|
es_fprintf (listfp, "regular expression: \"");
|
|
es_write_sanitized (listfp, buffer, length, "\"", NULL);
|
|
p = "\"";
|
|
}
|
|
break;
|
|
case SIGSUBPKT_REVOCABLE:
|
|
if (length)
|
|
es_fprintf (listfp, "%srevocable", *buffer ? "" : "not ");
|
|
break;
|
|
case SIGSUBPKT_KEY_EXPIRE:
|
|
if (length >= 4)
|
|
{
|
|
if (buf32_to_u32 (buffer))
|
|
es_fprintf (listfp, "key expires after %s",
|
|
strtimevalue (buf32_to_u32 (buffer)));
|
|
else
|
|
es_fprintf (listfp, "key does not expire");
|
|
}
|
|
break;
|
|
case SIGSUBPKT_PREF_SYM:
|
|
es_fputs ("pref-sym-algos:", listfp);
|
|
for (i = 0; i < length; i++)
|
|
es_fprintf (listfp, " %d", buffer[i]);
|
|
break;
|
|
case SIGSUBPKT_PREF_AEAD:
|
|
es_fputs ("pref-aead-algos:", listfp);
|
|
for (i = 0; i < length; i++)
|
|
es_fprintf (listfp, " %d", buffer[i]);
|
|
break;
|
|
case SIGSUBPKT_REV_KEY:
|
|
es_fputs ("revocation key: ", listfp);
|
|
if (length < 22)
|
|
p = "[too short]";
|
|
else
|
|
{
|
|
es_fprintf (listfp, "c=%02x a=%d f=", buffer[0], buffer[1]);
|
|
for (i = 2; i < length; i++)
|
|
es_fprintf (listfp, "%02X", buffer[i]);
|
|
}
|
|
break;
|
|
case SIGSUBPKT_ISSUER:
|
|
if (length >= 8)
|
|
es_fprintf (listfp, "issuer key ID %08lX%08lX",
|
|
(ulong) buf32_to_u32 (buffer),
|
|
(ulong) buf32_to_u32 (buffer + 4));
|
|
break;
|
|
case SIGSUBPKT_ISSUER_FPR:
|
|
if (length >= 21)
|
|
{
|
|
char *tmp;
|
|
es_fprintf (listfp, "issuer fpr v%d ", buffer[0]);
|
|
tmp = bin2hex (buffer+1, length-1, NULL);
|
|
if (tmp)
|
|
{
|
|
es_fputs (tmp, listfp);
|
|
xfree (tmp);
|
|
}
|
|
}
|
|
break;
|
|
case SIGSUBPKT_NOTATION:
|
|
{
|
|
es_fputs ("notation: ", listfp);
|
|
if (length < 8)
|
|
p = "[too short]";
|
|
else
|
|
{
|
|
const byte *s = buffer;
|
|
size_t n1, n2;
|
|
|
|
n1 = (s[4] << 8) | s[5];
|
|
n2 = (s[6] << 8) | s[7];
|
|
s += 8;
|
|
if (8 + n1 + n2 != length)
|
|
p = "[error]";
|
|
else
|
|
{
|
|
es_write_sanitized (listfp, s, n1, ")", NULL);
|
|
es_putc ('=', listfp);
|
|
|
|
if (*buffer & 0x80)
|
|
es_write_sanitized (listfp, s + n1, n2, ")", NULL);
|
|
else
|
|
p = "[not human readable]";
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case SIGSUBPKT_PREF_HASH:
|
|
es_fputs ("pref-hash-algos:", listfp);
|
|
for (i = 0; i < length; i++)
|
|
es_fprintf (listfp, " %d", buffer[i]);
|
|
break;
|
|
case SIGSUBPKT_PREF_COMPR:
|
|
es_fputs ("pref-zip-algos:", listfp);
|
|
for (i = 0; i < length; i++)
|
|
es_fprintf (listfp, " %d", buffer[i]);
|
|
break;
|
|
case SIGSUBPKT_KS_FLAGS:
|
|
es_fputs ("keyserver preferences:", listfp);
|
|
for (i = 0; i < length; i++)
|
|
es_fprintf (listfp, " %02X", buffer[i]);
|
|
break;
|
|
case SIGSUBPKT_PREF_KS:
|
|
es_fputs ("preferred keyserver: ", listfp);
|
|
es_write_sanitized (listfp, buffer, length, ")", NULL);
|
|
break;
|
|
case SIGSUBPKT_PRIMARY_UID:
|
|
p = "primary user ID";
|
|
break;
|
|
case SIGSUBPKT_POLICY:
|
|
es_fputs ("policy: ", listfp);
|
|
es_write_sanitized (listfp, buffer, length, ")", NULL);
|
|
break;
|
|
case SIGSUBPKT_KEY_FLAGS:
|
|
es_fputs ("key flags:", listfp);
|
|
for (i = 0; i < length; i++)
|
|
es_fprintf (listfp, " %02X", buffer[i]);
|
|
break;
|
|
case SIGSUBPKT_SIGNERS_UID:
|
|
p = "signer's user ID";
|
|
break;
|
|
case SIGSUBPKT_REVOC_REASON:
|
|
if (length)
|
|
{
|
|
es_fprintf (listfp, "revocation reason 0x%02x (", *buffer);
|
|
es_write_sanitized (listfp, buffer + 1, length - 1, ")", NULL);
|
|
p = ")";
|
|
}
|
|
break;
|
|
case SIGSUBPKT_ARR:
|
|
es_fputs ("Big Brother's key (ignored): ", listfp);
|
|
if (length < 22)
|
|
p = "[too short]";
|
|
else
|
|
{
|
|
es_fprintf (listfp, "c=%02x a=%d f=", buffer[0], buffer[1]);
|
|
if (length > 2)
|
|
es_write_hexstring (listfp, buffer+2, length-2, 0, NULL);
|
|
}
|
|
break;
|
|
case SIGSUBPKT_FEATURES:
|
|
es_fputs ("features:", listfp);
|
|
for (i = 0; i < length; i++)
|
|
es_fprintf (listfp, " %02x", buffer[i]);
|
|
break;
|
|
case SIGSUBPKT_SIGNATURE:
|
|
es_fputs ("signature: ", listfp);
|
|
if (length < 17)
|
|
p = "[too short]";
|
|
else
|
|
es_fprintf (listfp, "v%d, class 0x%02X, algo %d, digest algo %d",
|
|
buffer[0],
|
|
buffer[0] == 3 ? buffer[2] : buffer[1],
|
|
buffer[0] == 3 ? buffer[15] : buffer[2],
|
|
buffer[0] == 3 ? buffer[16] : buffer[3]);
|
|
break;
|
|
|
|
case SIGSUBPKT_ATTST_SIGS:
|
|
{
|
|
unsigned int hlen;
|
|
|
|
es_fputs ("attst-sigs: ", listfp);
|
|
hlen = gcry_md_get_algo_dlen (map_md_openpgp_to_gcry (digest_algo));
|
|
if (!hlen)
|
|
p = "[unknown digest algo]";
|
|
else if ((length % hlen))
|
|
p = "[invalid length]";
|
|
else
|
|
{
|
|
es_fprintf (listfp, "%u", (unsigned int)length/hlen);
|
|
while (length)
|
|
{
|
|
es_fprintf (listfp, "\n\t%*s", nprinted-1, "");
|
|
es_write_hexstring (listfp, buffer, hlen, 0, NULL);
|
|
buffer += hlen;
|
|
length -= hlen;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SIGSUBPKT_KEY_BLOCK:
|
|
es_fputs ("key-block: ", listfp);
|
|
if (length && buffer[0])
|
|
p = "[unknown reserved octet]";
|
|
else if (length < 50) /* 50 is an arbitrary min. length. */
|
|
p = "[invalid subpacket]";
|
|
else
|
|
{
|
|
/* estream_t fp; */
|
|
/* fp = es_fopen ("a.key-block", "wb"); */
|
|
/* log_assert (fp); */
|
|
/* es_fwrite ( buffer+1, length-1, 1, fp); */
|
|
/* es_fclose (fp); */
|
|
es_fprintf (listfp, "[%u octets]", (unsigned int)length-1);
|
|
}
|
|
break;
|
|
|
|
|
|
default:
|
|
if (type >= 100 && type <= 110)
|
|
p = "experimental / private subpacket";
|
|
else
|
|
p = "?";
|
|
break;
|
|
}
|
|
|
|
es_fprintf (listfp, "%s)\n", p ? p : "");
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns: >= 0 use this offset into buffer
|
|
* -1 explicitly reject returning this type
|
|
* -2 subpacket too short
|
|
*/
|
|
int
|
|
parse_one_sig_subpkt (const byte * buffer, size_t n, int type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case SIGSUBPKT_REV_KEY:
|
|
if (n < 22)
|
|
break;
|
|
return 0;
|
|
case SIGSUBPKT_SIG_CREATED:
|
|
case SIGSUBPKT_SIG_EXPIRE:
|
|
case SIGSUBPKT_KEY_EXPIRE:
|
|
if (n < 4)
|
|
break;
|
|
return 0;
|
|
case SIGSUBPKT_KEY_FLAGS:
|
|
case SIGSUBPKT_KS_FLAGS:
|
|
case SIGSUBPKT_PREF_SYM:
|
|
case SIGSUBPKT_PREF_AEAD:
|
|
case SIGSUBPKT_PREF_HASH:
|
|
case SIGSUBPKT_PREF_COMPR:
|
|
case SIGSUBPKT_POLICY:
|
|
case SIGSUBPKT_PREF_KS:
|
|
case SIGSUBPKT_FEATURES:
|
|
case SIGSUBPKT_REGEXP:
|
|
case SIGSUBPKT_ATTST_SIGS:
|
|
return 0;
|
|
case SIGSUBPKT_SIGNATURE:
|
|
case SIGSUBPKT_EXPORTABLE:
|
|
case SIGSUBPKT_REVOCABLE:
|
|
case SIGSUBPKT_REVOC_REASON:
|
|
if (!n)
|
|
break;
|
|
return 0;
|
|
case SIGSUBPKT_ISSUER: /* issuer key ID */
|
|
if (n < 8)
|
|
break;
|
|
return 0;
|
|
case SIGSUBPKT_ISSUER_FPR: /* issuer key fingerprint */
|
|
if (n < 21)
|
|
break;
|
|
return 0;
|
|
case SIGSUBPKT_NOTATION:
|
|
/* minimum length needed, and the subpacket must be well-formed
|
|
where the name length and value length all fit inside the
|
|
packet. */
|
|
if (n < 8
|
|
|| 8 + ((buffer[4] << 8) | buffer[5]) +
|
|
((buffer[6] << 8) | buffer[7]) != n)
|
|
break;
|
|
return 0;
|
|
case SIGSUBPKT_PRIMARY_UID:
|
|
if (n != 1)
|
|
break;
|
|
return 0;
|
|
case SIGSUBPKT_TRUST:
|
|
if (n != 2)
|
|
break;
|
|
return 0;
|
|
case SIGSUBPKT_KEY_BLOCK:
|
|
if (n && buffer[0])
|
|
return -1; /* Unknown version - ignore. */
|
|
if (n < 50)
|
|
break; /* Definitely too short to carry a key block. */
|
|
return 0;
|
|
default:
|
|
return 0;
|
|
}
|
|
return -2;
|
|
}
|
|
|
|
|
|
/* Return true if we understand the critical notation. */
|
|
static int
|
|
can_handle_critical_notation (const byte *name, size_t len)
|
|
{
|
|
strlist_t sl;
|
|
|
|
register_known_notation (NULL); /* Make sure it is initialized. */
|
|
|
|
for (sl = known_notations_list; sl; sl = sl->next)
|
|
if (sl->flags == len && !memcmp (sl->d, name, len))
|
|
return 1; /* Known */
|
|
|
|
if (opt.verbose && !glo_ctrl.silence_parse_warnings)
|
|
{
|
|
log_info(_("Unknown critical signature notation: ") );
|
|
print_utf8_buffer (log_get_stream(), name, len);
|
|
log_printf ("\n");
|
|
}
|
|
|
|
return 0; /* Unknown. */
|
|
}
|
|
|
|
|
|
static int
|
|
can_handle_critical (const byte * buffer, size_t n, int type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case SIGSUBPKT_NOTATION:
|
|
if (n >= 8)
|
|
{
|
|
size_t notation_len = ((buffer[4] << 8) | buffer[5]);
|
|
if (n - 8 >= notation_len)
|
|
return can_handle_critical_notation (buffer + 8, notation_len);
|
|
}
|
|
return 0;
|
|
case SIGSUBPKT_SIGNATURE:
|
|
case SIGSUBPKT_SIG_CREATED:
|
|
case SIGSUBPKT_SIG_EXPIRE:
|
|
case SIGSUBPKT_KEY_EXPIRE:
|
|
case SIGSUBPKT_EXPORTABLE:
|
|
case SIGSUBPKT_REVOCABLE:
|
|
case SIGSUBPKT_REV_KEY:
|
|
case SIGSUBPKT_ISSUER: /* issuer key ID */
|
|
case SIGSUBPKT_ISSUER_FPR: /* issuer fingerprint */
|
|
case SIGSUBPKT_PREF_SYM:
|
|
case SIGSUBPKT_PREF_AEAD:
|
|
case SIGSUBPKT_PREF_HASH:
|
|
case SIGSUBPKT_PREF_COMPR:
|
|
case SIGSUBPKT_KEY_FLAGS:
|
|
case SIGSUBPKT_PRIMARY_UID:
|
|
case SIGSUBPKT_FEATURES:
|
|
case SIGSUBPKT_TRUST:
|
|
case SIGSUBPKT_REGEXP:
|
|
case SIGSUBPKT_ATTST_SIGS:
|
|
/* Is it enough to show the policy or keyserver? */
|
|
case SIGSUBPKT_POLICY:
|
|
case SIGSUBPKT_PREF_KS:
|
|
case SIGSUBPKT_REVOC_REASON: /* At least we know about it. */
|
|
return 1;
|
|
|
|
case SIGSUBPKT_KEY_BLOCK:
|
|
if (n && !buffer[0])
|
|
return 1;
|
|
else
|
|
return 0;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
const byte *
|
|
enum_sig_subpkt (PKT_signature *sig, int want_hashed, sigsubpkttype_t reqtype,
|
|
size_t *ret_n, int *start, int *critical)
|
|
{
|
|
const byte *buffer;
|
|
int buflen;
|
|
int type;
|
|
int critical_dummy;
|
|
int offset;
|
|
size_t n;
|
|
const subpktarea_t *pktbuf = want_hashed? sig->hashed : sig->unhashed;
|
|
int seq = 0;
|
|
int reqseq = start ? *start : 0;
|
|
|
|
if (!critical)
|
|
critical = &critical_dummy;
|
|
|
|
if (!pktbuf || reqseq == -1)
|
|
{
|
|
static char dummy[] = "x";
|
|
/* Return a value different from NULL to indicate that
|
|
* there is no critical bit we do not understand. */
|
|
return reqtype == SIGSUBPKT_TEST_CRITICAL ? dummy : NULL;
|
|
}
|
|
buffer = pktbuf->data;
|
|
buflen = pktbuf->len;
|
|
while (buflen)
|
|
{
|
|
n = *buffer++;
|
|
buflen--;
|
|
if (n == 255) /* 4 byte length header. */
|
|
{
|
|
if (buflen < 4)
|
|
goto too_short;
|
|
n = buf32_to_size_t (buffer);
|
|
buffer += 4;
|
|
buflen -= 4;
|
|
}
|
|
else if (n >= 192) /* 4 byte special encoded length header. */
|
|
{
|
|
if (buflen < 2)
|
|
goto too_short;
|
|
n = ((n - 192) << 8) + *buffer + 192;
|
|
buffer++;
|
|
buflen--;
|
|
}
|
|
if (buflen < n)
|
|
goto too_short;
|
|
if (!buflen)
|
|
goto no_type_byte;
|
|
type = *buffer;
|
|
if (type & 0x80)
|
|
{
|
|
type &= 0x7f;
|
|
*critical = 1;
|
|
}
|
|
else
|
|
*critical = 0;
|
|
if (!(++seq > reqseq))
|
|
;
|
|
else if (reqtype == SIGSUBPKT_TEST_CRITICAL)
|
|
{
|
|
if (*critical)
|
|
{
|
|
if (n - 1 > buflen + 1)
|
|
goto too_short;
|
|
if (!can_handle_critical (buffer + 1, n - 1, type))
|
|
{
|
|
if (opt.verbose && !glo_ctrl.silence_parse_warnings)
|
|
log_info (_("subpacket of type %d has "
|
|
"critical bit set\n"), type);
|
|
if (start)
|
|
*start = seq;
|
|
return NULL; /* This is an error. */
|
|
}
|
|
}
|
|
}
|
|
else if (reqtype < 0) /* List packets. */
|
|
dump_sig_subpkt (reqtype == SIGSUBPKT_LIST_HASHED,
|
|
type, *critical, buffer, buflen, n, sig->digest_algo);
|
|
else if (type == reqtype) /* Found. */
|
|
{
|
|
buffer++;
|
|
n--;
|
|
if (n > buflen)
|
|
goto too_short;
|
|
if (ret_n)
|
|
*ret_n = n;
|
|
offset = parse_one_sig_subpkt (buffer, n, type);
|
|
switch (offset)
|
|
{
|
|
case -2:
|
|
log_error ("subpacket of type %d too short\n", type);
|
|
return NULL;
|
|
case -1:
|
|
return NULL;
|
|
default:
|
|
break;
|
|
}
|
|
if (start)
|
|
*start = seq;
|
|
return buffer + offset;
|
|
}
|
|
buffer += n;
|
|
buflen -= n;
|
|
}
|
|
if (reqtype == SIGSUBPKT_TEST_CRITICAL)
|
|
/* Returning NULL means we found a subpacket with the critical bit
|
|
set that we don't grok. We've iterated over all the subpackets
|
|
and haven't found such a packet so we need to return a non-NULL
|
|
value. */
|
|
return buffer;
|
|
|
|
/* Critical bit we don't understand. */
|
|
if (start)
|
|
*start = -1;
|
|
return NULL; /* End of packets; not found. */
|
|
|
|
too_short:
|
|
if (opt.debug && !glo_ctrl.silence_parse_warnings)
|
|
{
|
|
es_fflush (es_stdout);
|
|
log_printhex (pktbuf->data, pktbuf->len > 16? 16 : pktbuf->len,
|
|
"buffer shorter than subpacket (%zu/%d/%zu); dump:",
|
|
pktbuf->len, buflen, n);
|
|
}
|
|
|
|
if (start)
|
|
*start = -1;
|
|
return NULL;
|
|
|
|
no_type_byte:
|
|
if (opt.verbose && !glo_ctrl.silence_parse_warnings)
|
|
log_info ("type octet missing in subpacket\n");
|
|
if (start)
|
|
*start = -1;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
const byte *
|
|
parse_sig_subpkt (PKT_signature *sig, int want_hashed, sigsubpkttype_t reqtype,
|
|
size_t *ret_n)
|
|
{
|
|
return enum_sig_subpkt (sig, want_hashed, reqtype, ret_n, NULL, NULL);
|
|
}
|
|
|
|
|
|
const byte *
|
|
parse_sig_subpkt2 (PKT_signature *sig, sigsubpkttype_t reqtype)
|
|
{
|
|
const byte *p;
|
|
|
|
p = parse_sig_subpkt (sig, 1, reqtype, NULL);
|
|
if (!p)
|
|
p = parse_sig_subpkt (sig, 0, reqtype, NULL);
|
|
return p;
|
|
}
|
|
|
|
|
|
/* Find all revocation keys. Look in hashed area only. */
|
|
void
|
|
parse_revkeys (PKT_signature * sig)
|
|
{
|
|
const byte *revkey;
|
|
int seq = 0;
|
|
size_t len;
|
|
|
|
if (sig->sig_class != 0x1F)
|
|
return;
|
|
|
|
while ((revkey = enum_sig_subpkt (sig, 1, SIGSUBPKT_REV_KEY,
|
|
&len, &seq, NULL)))
|
|
{
|
|
/* Consider only valid packets. They must have a length of
|
|
* either 2+20 or 2+32 octets and bit 7 of the class octet must
|
|
* be set. */
|
|
if ((len == 22 || len == 34)
|
|
&& (revkey[0] & 0x80))
|
|
{
|
|
sig->revkey = xrealloc (sig->revkey,
|
|
sizeof (struct revocation_key) *
|
|
(sig->numrevkeys + 1));
|
|
|
|
sig->revkey[sig->numrevkeys].class = revkey[0];
|
|
sig->revkey[sig->numrevkeys].algid = revkey[1];
|
|
len -= 2;
|
|
sig->revkey[sig->numrevkeys].fprlen = len;
|
|
memcpy (sig->revkey[sig->numrevkeys].fpr, revkey+2, len);
|
|
memset (sig->revkey[sig->numrevkeys].fpr+len, 0,
|
|
sizeof (sig->revkey[sig->numrevkeys].fpr) - len);
|
|
sig->numrevkeys++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
parse_signature (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PKT_signature * sig)
|
|
{
|
|
int md5_len = 0;
|
|
unsigned n;
|
|
int is_v4or5 = 0;
|
|
int rc = 0;
|
|
int i, ndata;
|
|
|
|
if (pktlen < 16)
|
|
{
|
|
log_error ("packet(%d) too short\n", pkttype);
|
|
if (list_mode)
|
|
es_fputs (":signature packet: [too short]\n", listfp);
|
|
goto leave;
|
|
}
|
|
sig->version = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
if (sig->version == 4 || sig->version == 5)
|
|
is_v4or5 = 1;
|
|
else if (sig->version != 2 && sig->version != 3)
|
|
{
|
|
log_error ("packet(%d) with unknown version %d\n",
|
|
pkttype, sig->version);
|
|
if (list_mode)
|
|
es_fputs (":signature packet: [unknown version]\n", listfp);
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
|
|
if (!is_v4or5)
|
|
{
|
|
if (pktlen == 0)
|
|
goto underflow;
|
|
md5_len = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
}
|
|
if (pktlen == 0)
|
|
goto underflow;
|
|
sig->sig_class = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
if (!is_v4or5)
|
|
{
|
|
if (pktlen < 12)
|
|
goto underflow;
|
|
sig->timestamp = read_32 (inp);
|
|
pktlen -= 4;
|
|
sig->keyid[0] = read_32 (inp);
|
|
pktlen -= 4;
|
|
sig->keyid[1] = read_32 (inp);
|
|
pktlen -= 4;
|
|
}
|
|
if (pktlen < 2)
|
|
goto underflow;
|
|
sig->pubkey_algo = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
sig->digest_algo = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
sig->flags.exportable = 1;
|
|
sig->flags.revocable = 1;
|
|
if (is_v4or5) /* Read subpackets. */
|
|
{
|
|
if (pktlen < 2)
|
|
goto underflow;
|
|
n = read_16 (inp);
|
|
pktlen -= 2; /* Length of hashed data. */
|
|
if (pktlen < n)
|
|
goto underflow;
|
|
if (n > 10000)
|
|
{
|
|
log_error ("signature packet: hashed data too long\n");
|
|
if (list_mode)
|
|
es_fputs (":signature packet: [hashed data too long]\n", listfp);
|
|
rc = GPG_ERR_INV_PACKET;
|
|
goto leave;
|
|
}
|
|
if (n)
|
|
{
|
|
sig->hashed = xmalloc (sizeof (*sig->hashed) + n - 1);
|
|
sig->hashed->size = n;
|
|
sig->hashed->len = n;
|
|
if (iobuf_read (inp, sig->hashed->data, n) != n)
|
|
{
|
|
log_error ("premature eof while reading "
|
|
"hashed signature data\n");
|
|
if (list_mode)
|
|
es_fputs (":signature packet: [premature eof]\n", listfp);
|
|
rc = -1;
|
|
goto leave;
|
|
}
|
|
pktlen -= n;
|
|
}
|
|
if (pktlen < 2)
|
|
goto underflow;
|
|
n = read_16 (inp);
|
|
pktlen -= 2; /* Length of unhashed data. */
|
|
if (pktlen < n)
|
|
goto underflow;
|
|
if (n > 10000)
|
|
{
|
|
log_error ("signature packet: unhashed data too long\n");
|
|
if (list_mode)
|
|
es_fputs (":signature packet: [unhashed data too long]\n", listfp);
|
|
rc = GPG_ERR_INV_PACKET;
|
|
goto leave;
|
|
}
|
|
if (n)
|
|
{
|
|
sig->unhashed = xmalloc (sizeof (*sig->unhashed) + n - 1);
|
|
sig->unhashed->size = n;
|
|
sig->unhashed->len = n;
|
|
if (iobuf_read (inp, sig->unhashed->data, n) != n)
|
|
{
|
|
log_error ("premature eof while reading "
|
|
"unhashed signature data\n");
|
|
if (list_mode)
|
|
es_fputs (":signature packet: [premature eof]\n", listfp);
|
|
rc = -1;
|
|
goto leave;
|
|
}
|
|
pktlen -= n;
|
|
}
|
|
}
|
|
|
|
if (pktlen < 2)
|
|
goto underflow;
|
|
sig->digest_start[0] = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
sig->digest_start[1] = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
|
|
if (is_v4or5 && sig->pubkey_algo) /* Extract required information. */
|
|
{
|
|
const byte *p;
|
|
size_t len;
|
|
|
|
/* Set sig->flags.unknown_critical if there is a critical bit
|
|
* set for packets which we do not understand. */
|
|
if (!parse_sig_subpkt (sig, 1, SIGSUBPKT_TEST_CRITICAL, NULL)
|
|
|| !parse_sig_subpkt (sig, 0, SIGSUBPKT_TEST_CRITICAL, NULL))
|
|
sig->flags.unknown_critical = 1;
|
|
|
|
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_SIG_CREATED, NULL);
|
|
if (p)
|
|
sig->timestamp = buf32_to_u32 (p);
|
|
else if (!(sig->pubkey_algo >= 100 && sig->pubkey_algo <= 110)
|
|
&& opt.verbose > 1 && !glo_ctrl.silence_parse_warnings)
|
|
log_info ("signature packet without timestamp\n");
|
|
|
|
/* Set the key id. We first try the issuer fingerprint and if
|
|
* it is a v4 signature the fallback to the issuer. Note that
|
|
* only the issuer packet is also searched in the unhashed area. */
|
|
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_ISSUER_FPR, &len);
|
|
if (p && len == 21 && p[0] == 4)
|
|
{
|
|
sig->keyid[0] = buf32_to_u32 (p + 1 + 12);
|
|
sig->keyid[1] = buf32_to_u32 (p + 1 + 16);
|
|
}
|
|
else if (p && len == 33 && p[0] == 5)
|
|
{
|
|
sig->keyid[0] = buf32_to_u32 (p + 1 );
|
|
sig->keyid[1] = buf32_to_u32 (p + 1 + 4);
|
|
}
|
|
else if ((p = parse_sig_subpkt2 (sig, SIGSUBPKT_ISSUER)))
|
|
{
|
|
sig->keyid[0] = buf32_to_u32 (p);
|
|
sig->keyid[1] = buf32_to_u32 (p + 4);
|
|
}
|
|
else if (!(sig->pubkey_algo >= 100 && sig->pubkey_algo <= 110)
|
|
&& opt.verbose > 1 && !glo_ctrl.silence_parse_warnings)
|
|
log_info ("signature packet without keyid\n");
|
|
|
|
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_SIG_EXPIRE, NULL);
|
|
if (p && buf32_to_u32 (p))
|
|
sig->expiredate = sig->timestamp + buf32_to_u32 (p);
|
|
if (sig->expiredate && sig->expiredate <= make_timestamp ())
|
|
sig->flags.expired = 1;
|
|
|
|
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_POLICY, NULL);
|
|
if (p)
|
|
sig->flags.policy_url = 1;
|
|
|
|
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_PREF_KS, NULL);
|
|
if (p)
|
|
sig->flags.pref_ks = 1;
|
|
|
|
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_SIGNERS_UID, &len);
|
|
if (p && len)
|
|
{
|
|
char *mbox;
|
|
|
|
sig->signers_uid = try_make_printable_string (p, len, 0);
|
|
if (!sig->signers_uid)
|
|
{
|
|
rc = gpg_error_from_syserror ();
|
|
goto leave;
|
|
}
|
|
mbox = mailbox_from_userid (sig->signers_uid, 0);
|
|
if (mbox)
|
|
{
|
|
xfree (sig->signers_uid);
|
|
sig->signers_uid = mbox;
|
|
}
|
|
}
|
|
|
|
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_KEY_BLOCK, NULL);
|
|
if (p)
|
|
sig->flags.key_block = 1;
|
|
|
|
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_NOTATION, NULL);
|
|
if (p)
|
|
sig->flags.notation = 1;
|
|
|
|
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_REVOCABLE, NULL);
|
|
if (p && *p == 0)
|
|
sig->flags.revocable = 0;
|
|
|
|
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_TRUST, &len);
|
|
if (p && len == 2)
|
|
{
|
|
sig->trust_depth = p[0];
|
|
sig->trust_value = p[1];
|
|
|
|
/* Only look for a regexp if there is also a trust
|
|
subpacket. */
|
|
sig->trust_regexp =
|
|
parse_sig_subpkt (sig, 1, SIGSUBPKT_REGEXP, &len);
|
|
|
|
/* If the regular expression is of 0 length, there is no
|
|
regular expression. */
|
|
if (len == 0)
|
|
sig->trust_regexp = NULL;
|
|
}
|
|
|
|
/* We accept the exportable subpacket from either the hashed or
|
|
unhashed areas as older versions of gpg put it in the
|
|
unhashed area. In theory, anyway, we should never see this
|
|
packet off of a local keyring. */
|
|
|
|
p = parse_sig_subpkt2 (sig, SIGSUBPKT_EXPORTABLE);
|
|
if (p && *p == 0)
|
|
sig->flags.exportable = 0;
|
|
|
|
/* Find all revocation keys. */
|
|
if (sig->sig_class == 0x1F)
|
|
parse_revkeys (sig);
|
|
}
|
|
|
|
if (list_mode)
|
|
{
|
|
es_fprintf (listfp, ":signature packet: algo %d, keyid %08lX%08lX\n"
|
|
"\tversion %d, created %lu, md5len %d, sigclass 0x%02x\n"
|
|
"\tdigest algo %d, begin of digest %02x %02x\n",
|
|
sig->pubkey_algo,
|
|
(ulong) sig->keyid[0], (ulong) sig->keyid[1],
|
|
sig->version, (ulong) sig->timestamp, md5_len, sig->sig_class,
|
|
sig->digest_algo, sig->digest_start[0], sig->digest_start[1]);
|
|
if (is_v4or5)
|
|
{
|
|
parse_sig_subpkt (sig, 1, SIGSUBPKT_LIST_HASHED, NULL);
|
|
parse_sig_subpkt (sig, 0, SIGSUBPKT_LIST_UNHASHED, NULL);
|
|
}
|
|
}
|
|
|
|
ndata = pubkey_get_nsig (sig->pubkey_algo);
|
|
if (!ndata)
|
|
{
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tunknown algorithm %d\n", sig->pubkey_algo);
|
|
unknown_pubkey_warning (sig->pubkey_algo);
|
|
|
|
/* We store the plain material in data[0], so that we are able
|
|
* to write it back with build_packet(). */
|
|
if (pktlen > (5 * MAX_EXTERN_MPI_BITS / 8))
|
|
{
|
|
/* We include a limit to avoid too trivial DoS attacks by
|
|
having gpg allocate too much memory. */
|
|
log_error ("signature packet: too much data\n");
|
|
rc = GPG_ERR_INV_PACKET;
|
|
}
|
|
else
|
|
{
|
|
void *tmpp;
|
|
|
|
tmpp = read_rest (inp, pktlen);
|
|
sig->data[0] = gcry_mpi_set_opaque (NULL, tmpp, tmpp? pktlen * 8 : 0);
|
|
pktlen = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < ndata; i++)
|
|
{
|
|
n = pktlen;
|
|
if (sig->pubkey_algo == PUBKEY_ALGO_ECDSA
|
|
|| sig->pubkey_algo == PUBKEY_ALGO_EDDSA)
|
|
sig->data[i] = sos_read (inp, &n, 0);
|
|
else
|
|
sig->data[i] = mpi_read (inp, &n, 0);
|
|
pktlen -= n;
|
|
if (list_mode)
|
|
{
|
|
es_fprintf (listfp, "\tdata: ");
|
|
mpi_print (listfp, sig->data[i], mpi_print_mode);
|
|
es_putc ('\n', listfp);
|
|
}
|
|
if (!sig->data[i])
|
|
rc = GPG_ERR_INV_PACKET;
|
|
}
|
|
}
|
|
|
|
leave:
|
|
iobuf_skip_rest (inp, pktlen, 0);
|
|
return rc;
|
|
|
|
underflow:
|
|
log_error ("packet(%d) too short\n", pkttype);
|
|
if (list_mode)
|
|
es_fputs (":signature packet: [too short]\n", listfp);
|
|
|
|
iobuf_skip_rest (inp, pktlen, 0);
|
|
|
|
return GPG_ERR_INV_PACKET;
|
|
}
|
|
|
|
|
|
static int
|
|
parse_onepass_sig (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PKT_onepass_sig * ops)
|
|
{
|
|
int version;
|
|
int rc = 0;
|
|
|
|
if (pktlen < 13)
|
|
{
|
|
log_error ("packet(%d) too short\n", pkttype);
|
|
if (list_mode)
|
|
es_fputs (":onepass_sig packet: [too short]\n", listfp);
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
version = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
if (version != 3)
|
|
{
|
|
log_error ("onepass_sig with unknown version %d\n", version);
|
|
if (list_mode)
|
|
es_fputs (":onepass_sig packet: [unknown version]\n", listfp);
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
ops->sig_class = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
ops->digest_algo = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
ops->pubkey_algo = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
ops->keyid[0] = read_32 (inp);
|
|
pktlen -= 4;
|
|
ops->keyid[1] = read_32 (inp);
|
|
pktlen -= 4;
|
|
ops->last = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
if (list_mode)
|
|
es_fprintf (listfp,
|
|
":onepass_sig packet: keyid %08lX%08lX\n"
|
|
"\tversion %d, sigclass 0x%02x, digest %d, pubkey %d, "
|
|
"last=%d\n",
|
|
(ulong) ops->keyid[0], (ulong) ops->keyid[1],
|
|
version, ops->sig_class,
|
|
ops->digest_algo, ops->pubkey_algo, ops->last);
|
|
|
|
|
|
leave:
|
|
iobuf_skip_rest (inp, pktlen, 0);
|
|
return rc;
|
|
}
|
|
|
|
|
|
static int
|
|
parse_key (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
byte * hdr, int hdrlen, PACKET * pkt)
|
|
{
|
|
gpg_error_t err = 0;
|
|
int i, version, algorithm;
|
|
unsigned long timestamp, expiredate, max_expiredate;
|
|
int npkey, nskey;
|
|
u32 keyid[2];
|
|
PKT_public_key *pk;
|
|
int is_v5;
|
|
unsigned int pkbytes; /* For v5 keys: Number of bytes in the public
|
|
* key material. For v4 keys: 0. */
|
|
|
|
(void) hdr;
|
|
|
|
pk = pkt->pkt.public_key; /* PK has been cleared. */
|
|
|
|
version = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
if (pkttype == PKT_PUBLIC_SUBKEY && version == '#')
|
|
{
|
|
/* Early versions of G10 used the old PGP comments packets;
|
|
* luckily all those comments are started by a hash. */
|
|
if (list_mode)
|
|
{
|
|
es_fprintf (listfp, ":rfc1991 comment packet: \"");
|
|
for (; pktlen; pktlen--)
|
|
{
|
|
int c;
|
|
c = iobuf_get (inp);
|
|
if (c == -1)
|
|
break; /* Ooops: shorter than indicated. */
|
|
if (c >= ' ' && c <= 'z')
|
|
es_putc (c, listfp);
|
|
else
|
|
es_fprintf (listfp, "\\x%02x", c);
|
|
}
|
|
es_fprintf (listfp, "\"\n");
|
|
}
|
|
iobuf_skip_rest (inp, pktlen, 0);
|
|
return 0;
|
|
}
|
|
else if (version == 4)
|
|
is_v5 = 0;
|
|
else if (version == 5)
|
|
is_v5 = 1;
|
|
else if (version == 2 || version == 3)
|
|
{
|
|
/* Not anymore supported since 2.1. Use an older gpg version
|
|
* (i.e. gpg 1.4) to parse v3 packets. */
|
|
if (opt.verbose > 1 && !glo_ctrl.silence_parse_warnings)
|
|
log_info ("packet(%d) with obsolete version %d\n", pkttype, version);
|
|
if (list_mode)
|
|
es_fprintf (listfp, ":key packet: [obsolete version %d]\n", version);
|
|
pk->version = version;
|
|
err = gpg_error (GPG_ERR_LEGACY_KEY);
|
|
goto leave;
|
|
}
|
|
else
|
|
{
|
|
log_error ("packet(%d) with unknown version %d\n", pkttype, version);
|
|
if (list_mode)
|
|
es_fputs (":key packet: [unknown version]\n", listfp);
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
|
|
if (pktlen < (is_v5? 15:11))
|
|
{
|
|
log_error ("packet(%d) too short\n", pkttype);
|
|
if (list_mode)
|
|
es_fputs (":key packet: [too short]\n", listfp);
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
else if (pktlen > MAX_KEY_PACKET_LENGTH)
|
|
{
|
|
log_error ("packet(%d) too large\n", pkttype);
|
|
if (list_mode)
|
|
es_fputs (":key packet: [too large]\n", listfp);
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
|
|
timestamp = read_32 (inp);
|
|
pktlen -= 4;
|
|
expiredate = 0; /* have to get it from the selfsignature */
|
|
max_expiredate = 0;
|
|
algorithm = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
if (is_v5)
|
|
{
|
|
pkbytes = read_32 (inp);
|
|
pktlen -= 4;
|
|
}
|
|
else
|
|
pkbytes = 0;
|
|
|
|
if (list_mode)
|
|
{
|
|
es_fprintf (listfp, ":%s key packet:\n"
|
|
"\tversion %d, algo %d, created %lu, expires %lu",
|
|
pkttype == PKT_PUBLIC_KEY ? "public" :
|
|
pkttype == PKT_SECRET_KEY ? "secret" :
|
|
pkttype == PKT_PUBLIC_SUBKEY ? "public sub" :
|
|
pkttype == PKT_SECRET_SUBKEY ? "secret sub" : "??",
|
|
version, algorithm, timestamp, expiredate);
|
|
if (is_v5)
|
|
es_fprintf (listfp, ", pkbytes %u\n", pkbytes);
|
|
else
|
|
es_fprintf (listfp, "\n");
|
|
}
|
|
|
|
pk->timestamp = timestamp;
|
|
pk->expiredate = expiredate;
|
|
pk->max_expiredate = max_expiredate;
|
|
pk->hdrbytes = hdrlen;
|
|
pk->version = version;
|
|
pk->flags.primary = (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY);
|
|
pk->pubkey_algo = algorithm;
|
|
|
|
nskey = pubkey_get_nskey (algorithm);
|
|
npkey = pubkey_get_npkey (algorithm);
|
|
if (!npkey)
|
|
{
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tunknown algorithm %d\n", algorithm);
|
|
unknown_pubkey_warning (algorithm);
|
|
}
|
|
|
|
if (!npkey)
|
|
{
|
|
/* Unknown algorithm - put data into an opaque MPI. */
|
|
void *tmpp = read_rest (inp, pktlen);
|
|
/* Current gcry_mpi_cmp does not handle a (NULL,n>0) nicely and
|
|
* thus we avoid to create such an MPI. */
|
|
pk->pkey[0] = gcry_mpi_set_opaque (NULL, tmpp, tmpp? pktlen * 8 : 0);
|
|
pktlen = 0;
|
|
goto leave;
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < npkey; i++)
|
|
{
|
|
if ( (algorithm == PUBKEY_ALGO_ECDSA && (i == 0))
|
|
|| (algorithm == PUBKEY_ALGO_EDDSA && (i == 0))
|
|
|| (algorithm == PUBKEY_ALGO_ECDH && (i == 0 || i == 2))
|
|
|| (algorithm == PUBKEY_ALGO_KYBER && (i == 0)))
|
|
{
|
|
/* Read the OID (i==0) or the KDF params (i==2). */
|
|
err = read_sized_octet_string (inp, &pktlen, pk->pkey+i);
|
|
}
|
|
else if (algorithm == PUBKEY_ALGO_KYBER && i == 2)
|
|
{
|
|
/* Read the four-octet count prefixed Kyber public key. */
|
|
err = read_octet_string (inp, &pktlen, 4, 0, 0, pk->pkey+i);
|
|
}
|
|
else
|
|
{
|
|
/* Read MPI or SOS. */
|
|
unsigned int n = pktlen;
|
|
if (algorithm == PUBKEY_ALGO_ECDSA
|
|
|| algorithm == PUBKEY_ALGO_EDDSA
|
|
|| algorithm == PUBKEY_ALGO_ECDH
|
|
|| algorithm == PUBKEY_ALGO_KYBER)
|
|
pk->pkey[i] = sos_read (inp, &n, 0);
|
|
else
|
|
pk->pkey[i] = mpi_read (inp, &n, 0);
|
|
pktlen -= n;
|
|
if (!pk->pkey[i])
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
}
|
|
if (err)
|
|
goto leave;
|
|
if (list_mode)
|
|
{
|
|
es_fprintf (listfp, "\tpkey[%d]: ", i);
|
|
mpi_print (listfp, pk->pkey[i], mpi_print_mode);
|
|
if ((algorithm == PUBKEY_ALGO_ECDSA
|
|
|| algorithm == PUBKEY_ALGO_EDDSA
|
|
|| algorithm == PUBKEY_ALGO_ECDH
|
|
|| algorithm == PUBKEY_ALGO_KYBER) && i==0)
|
|
{
|
|
char *curve = openpgp_oid_to_str (pk->pkey[0]);
|
|
const char *name = openpgp_oid_to_curve (curve, 0);
|
|
es_fprintf (listfp, " %s (%s)", name?name:"", curve);
|
|
xfree (curve);
|
|
}
|
|
es_putc ('\n', listfp);
|
|
}
|
|
}
|
|
}
|
|
if (list_mode)
|
|
keyid_from_pk (pk, keyid);
|
|
|
|
if (pkttype == PKT_SECRET_KEY || pkttype == PKT_SECRET_SUBKEY)
|
|
{
|
|
struct seckey_info *ski;
|
|
byte temp[16];
|
|
size_t snlen = 0;
|
|
unsigned int skbytes;
|
|
|
|
if (pktlen < 1)
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
|
|
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
|
|
if (!pk->seckey_info)
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
goto leave;
|
|
}
|
|
|
|
ski->algo = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
|
|
if (is_v5)
|
|
{
|
|
unsigned int protcount = 0;
|
|
|
|
/* Read the one octet count of the following key-protection
|
|
* material. Only required in case of unknown values. */
|
|
if (!pktlen)
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
protcount = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tprotbytes: %u\n", protcount);
|
|
}
|
|
|
|
if (ski->algo)
|
|
{
|
|
ski->is_protected = 1;
|
|
ski->s2k.count = 0;
|
|
if (ski->algo == 253)
|
|
{
|
|
if (list_mode)
|
|
es_fprintf (listfp,
|
|
"\tS2K pseudo algo %d is not yet supported\n",
|
|
ski->algo);
|
|
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
|
|
goto leave;
|
|
}
|
|
else if (ski->algo == 254 || ski->algo == 255)
|
|
{
|
|
if (pktlen < 3)
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
|
|
ski->sha1chk = (ski->algo == 254);
|
|
ski->algo = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
/* Note that a ski->algo > 110 is illegal, but I'm not
|
|
* erroring out here as otherwise there would be no way
|
|
* to delete such a key. */
|
|
ski->s2k.mode = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
ski->s2k.hash_algo = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
/* Check for the special GNU extension. */
|
|
if (ski->s2k.mode == 101)
|
|
{
|
|
for (i = 0; i < 4 && pktlen; i++, pktlen--)
|
|
temp[i] = iobuf_get_noeof (inp);
|
|
if (i < 4 || memcmp (temp, "GNU", 3))
|
|
{
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tunknown S2K %d\n",
|
|
ski->s2k.mode);
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
/* Here we know that it is a GNU extension. What
|
|
* follows is the GNU protection mode: All values
|
|
* have special meanings and they are mapped to MODE
|
|
* with a base of 1000. */
|
|
ski->s2k.mode = 1000 + temp[3];
|
|
}
|
|
|
|
/* Read the salt. */
|
|
if (ski->s2k.mode == 3 || ski->s2k.mode == 1)
|
|
{
|
|
for (i = 0; i < 8 && pktlen; i++, pktlen--)
|
|
temp[i] = iobuf_get_noeof (inp);
|
|
if (i < 8)
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
memcpy (ski->s2k.salt, temp, 8);
|
|
}
|
|
|
|
/* Check the mode. */
|
|
switch (ski->s2k.mode)
|
|
{
|
|
case 0:
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tsimple S2K");
|
|
break;
|
|
case 1:
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tsalted S2K");
|
|
break;
|
|
case 3:
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\titer+salt S2K");
|
|
break;
|
|
case 1001:
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tgnu-dummy");
|
|
break;
|
|
case 1002:
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tgnu-divert-to-card");
|
|
break;
|
|
case 1003:
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tgnu-mode1003");
|
|
break;
|
|
default:
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tunknown %sS2K %d\n",
|
|
ski->s2k.mode < 1000 ? "" : "GNU ",
|
|
ski->s2k.mode);
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
|
|
/* Print some info. */
|
|
if (list_mode && ski->s2k.mode != 1003)
|
|
{
|
|
es_fprintf (listfp, ", algo: %d,%s hash: %d",
|
|
ski->algo,
|
|
ski->sha1chk ? " SHA1 protection,"
|
|
: " simple checksum,", ski->s2k.hash_algo);
|
|
if (ski->s2k.mode == 1 || ski->s2k.mode == 3)
|
|
{
|
|
es_fprintf (listfp, ", salt: ");
|
|
es_write_hexstring (listfp, ski->s2k.salt, 8, 0, NULL);
|
|
}
|
|
}
|
|
if (list_mode)
|
|
es_putc ('\n', listfp);
|
|
|
|
/* Read remaining protection parameters. */
|
|
if (ski->s2k.mode == 3)
|
|
{
|
|
if (pktlen < 1)
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
ski->s2k.count = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tprotect count: %lu (%lu)\n",
|
|
(ulong)S2K_DECODE_COUNT ((ulong)ski->s2k.count),
|
|
(ulong) ski->s2k.count);
|
|
}
|
|
else if (ski->s2k.mode == 1002)
|
|
{
|
|
/* Read the serial number. */
|
|
if (pktlen < 1)
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
snlen = iobuf_get (inp);
|
|
pktlen--;
|
|
if (pktlen < snlen || snlen == (size_t)(-1))
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
}
|
|
}
|
|
else /* Old version; no S2K, so we set mode to 0, hash MD5. */
|
|
{
|
|
/* Note that a ski->algo > 110 is illegal, but I'm not
|
|
erroring on it here as otherwise there would be no
|
|
way to delete such a key. */
|
|
ski->s2k.mode = 0;
|
|
ski->s2k.hash_algo = DIGEST_ALGO_MD5;
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tprotect algo: %d (hash algo: %d)\n",
|
|
ski->algo, ski->s2k.hash_algo);
|
|
}
|
|
|
|
/* It is really ugly that we don't know the size
|
|
* of the IV here in cases we are not aware of the algorithm.
|
|
* so a
|
|
* ski->ivlen = cipher_get_blocksize (ski->algo);
|
|
* won't work. The only solution I see is to hardwire it.
|
|
* NOTE: if you change the ivlen above 16, don't forget to
|
|
* enlarge temp.
|
|
* FIXME: For v5 keys we can deduce this info!
|
|
*/
|
|
ski->ivlen = openpgp_cipher_blocklen (ski->algo);
|
|
log_assert (ski->ivlen <= sizeof (temp));
|
|
|
|
if (ski->s2k.mode == 1001 || ski->s2k.mode == 1003)
|
|
ski->ivlen = 0;
|
|
else if (ski->s2k.mode == 1002)
|
|
ski->ivlen = snlen < 16 ? snlen : 16;
|
|
|
|
if (pktlen < ski->ivlen)
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
for (i = 0; i < ski->ivlen; i++, pktlen--)
|
|
temp[i] = iobuf_get_noeof (inp);
|
|
if (list_mode && ski->s2k.mode != 1003)
|
|
{
|
|
es_fprintf (listfp,
|
|
ski->s2k.mode == 1002 ? "\tserial-number: "
|
|
: "\tprotect IV: ");
|
|
for (i = 0; i < ski->ivlen; i++)
|
|
es_fprintf (listfp, " %02x", temp[i]);
|
|
es_putc ('\n', listfp);
|
|
}
|
|
memcpy (ski->iv, temp, ski->ivlen);
|
|
}
|
|
|
|
/* Skip count of secret key material. */
|
|
if (is_v5)
|
|
{
|
|
if (pktlen < 4)
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
skbytes = read_32 (inp);
|
|
pktlen -= 4;
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tskbytes: %u\n", skbytes);
|
|
}
|
|
|
|
/* It does not make sense to read it into secure memory.
|
|
* If the user is so careless, not to protect his secret key,
|
|
* we can assume, that he operates an open system :=(.
|
|
* So we put the key into secure memory when we unprotect it. */
|
|
if (ski->s2k.mode == 1001 || ski->s2k.mode == 1002)
|
|
{
|
|
/* Better set some dummy stuff here. */
|
|
pk->pkey[npkey] = gcry_mpi_set_opaque (NULL,
|
|
xstrdup ("dummydata"),
|
|
10 * 8);
|
|
pktlen = 0;
|
|
}
|
|
else if (ski->s2k.mode == 1003)
|
|
{
|
|
void *tmpp;
|
|
|
|
if (pktlen < 2) /* At least two bytes for parenthesis. */
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
|
|
tmpp = read_rest (inp, pktlen);
|
|
if (list_mode)
|
|
{
|
|
if (mpi_print_mode)
|
|
{
|
|
char *tmpsxp = canon_sexp_to_string (tmpp, pktlen);
|
|
es_fprintf (listfp, "\tskey[%d]: %s\n", npkey,
|
|
tmpsxp? trim_trailing_spaces (tmpsxp)
|
|
/* */: "[invalid S-expression]");
|
|
xfree (tmpsxp);
|
|
}
|
|
else
|
|
es_fprintf (listfp, "\tskey[%d]: [s-expression %lu octets]\n",
|
|
npkey, pktlen);
|
|
}
|
|
pk->pkey[npkey] = gcry_mpi_set_opaque (NULL,
|
|
tmpp, tmpp? pktlen * 8 : 0);
|
|
pktlen = 0;
|
|
}
|
|
else if (ski->is_protected)
|
|
{
|
|
void *tmpp;
|
|
|
|
if (pktlen < 2) /* At least two bytes for the length. */
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
|
|
/* Ugly: The length is encrypted too, so we read all stuff
|
|
* up to the end of the packet into the first SKEY
|
|
* element.
|
|
* FIXME: We can do better for v5 keys. */
|
|
|
|
tmpp = read_rest (inp, pktlen);
|
|
pk->pkey[npkey] = gcry_mpi_set_opaque (NULL,
|
|
tmpp, tmpp? pktlen * 8 : 0);
|
|
/* Mark that MPI as protected - we need this information for
|
|
* importing a key. The OPAQUE flag can't be used because
|
|
* we also store public EdDSA values in opaque MPIs. */
|
|
if (pk->pkey[npkey])
|
|
gcry_mpi_set_flag (pk->pkey[npkey], GCRYMPI_FLAG_USER1);
|
|
pktlen = 0;
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tskey[%d]: [v4 protected]\n", npkey);
|
|
}
|
|
else
|
|
{
|
|
/* Not encrypted. */
|
|
for (i = npkey; i < nskey; i++)
|
|
{
|
|
|
|
if (pktlen < 2) /* At least two bytes for the length. */
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
if (algorithm == PUBKEY_ALGO_KYBER && i == npkey+1)
|
|
{
|
|
err = read_octet_string (inp, &pktlen, 4, 0, 1, pk->pkey+i);
|
|
if (err)
|
|
goto leave;
|
|
}
|
|
else
|
|
{
|
|
unsigned int n = pktlen;
|
|
|
|
if (algorithm == PUBKEY_ALGO_ECDSA
|
|
|| algorithm == PUBKEY_ALGO_EDDSA
|
|
|| algorithm == PUBKEY_ALGO_ECDH
|
|
|| algorithm == PUBKEY_ALGO_KYBER)
|
|
pk->pkey[i] = sos_read (inp, &n, 0);
|
|
else
|
|
pk->pkey[i] = mpi_read (inp, &n, 0);
|
|
pktlen -= n;
|
|
}
|
|
|
|
if (list_mode)
|
|
{
|
|
es_fprintf (listfp, "\tskey[%d]: ", i);
|
|
mpi_print (listfp, pk->pkey[i], mpi_print_mode);
|
|
es_putc ('\n', listfp);
|
|
}
|
|
|
|
if (!pk->pkey[i])
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
}
|
|
if (err)
|
|
goto leave;
|
|
|
|
if (pktlen < 2)
|
|
{
|
|
err = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
ski->csum = read_16 (inp);
|
|
pktlen -= 2;
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tchecksum: %04hx\n", ski->csum);
|
|
}
|
|
}
|
|
|
|
/* Note that KEYID below has been initialized above in list_mode. */
|
|
if (list_mode)
|
|
es_fprintf (listfp, "\tkeyid: %08lX%08lX\n",
|
|
(ulong) keyid[0], (ulong) keyid[1]);
|
|
|
|
leave:
|
|
iobuf_skip_rest (inp, pktlen, 0);
|
|
return err;
|
|
}
|
|
|
|
|
|
/* Attribute subpackets have the same format as v4 signature
|
|
subpackets. This is not part of OpenPGP, but is done in several
|
|
versions of PGP nevertheless. */
|
|
int
|
|
parse_attribute_subpkts (PKT_user_id * uid)
|
|
{
|
|
size_t n;
|
|
int count = 0;
|
|
struct user_attribute *attribs = NULL;
|
|
const byte *buffer = uid->attrib_data;
|
|
int buflen = uid->attrib_len;
|
|
byte type;
|
|
|
|
xfree (uid->attribs);
|
|
|
|
while (buflen)
|
|
{
|
|
n = *buffer++;
|
|
buflen--;
|
|
if (n == 255) /* 4 byte length header. */
|
|
{
|
|
if (buflen < 4)
|
|
goto too_short;
|
|
n = buf32_to_size_t (buffer);
|
|
buffer += 4;
|
|
buflen -= 4;
|
|
}
|
|
else if (n >= 192) /* 2 byte special encoded length header. */
|
|
{
|
|
if (buflen < 2)
|
|
goto too_short;
|
|
n = ((n - 192) << 8) + *buffer + 192;
|
|
buffer++;
|
|
buflen--;
|
|
}
|
|
if (buflen < n)
|
|
goto too_short;
|
|
|
|
if (!n)
|
|
{
|
|
/* Too short to encode the subpacket type. */
|
|
if (opt.verbose)
|
|
log_info ("attribute subpacket too short\n");
|
|
break;
|
|
}
|
|
|
|
attribs = xrealloc (attribs,
|
|
(count + 1) * sizeof (struct user_attribute));
|
|
memset (&attribs[count], 0, sizeof (struct user_attribute));
|
|
|
|
type = *buffer;
|
|
buffer++;
|
|
buflen--;
|
|
n--;
|
|
|
|
attribs[count].type = type;
|
|
attribs[count].data = buffer;
|
|
attribs[count].len = n;
|
|
buffer += n;
|
|
buflen -= n;
|
|
count++;
|
|
}
|
|
|
|
uid->attribs = attribs;
|
|
uid->numattribs = count;
|
|
return count;
|
|
|
|
too_short:
|
|
if (opt.verbose && !glo_ctrl.silence_parse_warnings)
|
|
log_info ("buffer shorter than attribute subpacket\n");
|
|
uid->attribs = attribs;
|
|
uid->numattribs = count;
|
|
return count;
|
|
}
|
|
|
|
|
|
static int
|
|
parse_user_id (IOBUF inp, int pkttype, unsigned long pktlen, PACKET * packet)
|
|
{
|
|
byte *p;
|
|
|
|
/* Cap the size of a user ID at 2k: a value absurdly large enough
|
|
that there is no sane user ID string (which is printable text
|
|
as of RFC2440bis) that won't fit in it, but yet small enough to
|
|
avoid allocation problems. A large pktlen may not be
|
|
allocatable, and a very large pktlen could actually cause our
|
|
allocation to wrap around in xmalloc to a small number. */
|
|
|
|
if (pktlen > MAX_UID_PACKET_LENGTH)
|
|
{
|
|
log_error ("packet(%d) too large\n", pkttype);
|
|
if (list_mode)
|
|
es_fprintf (listfp, ":user ID packet: [too large]\n");
|
|
iobuf_skip_rest (inp, pktlen, 0);
|
|
return GPG_ERR_INV_PACKET;
|
|
}
|
|
|
|
packet->pkt.user_id = xmalloc_clear (sizeof *packet->pkt.user_id + pktlen);
|
|
packet->pkt.user_id->len = pktlen;
|
|
packet->pkt.user_id->ref = 1;
|
|
|
|
p = packet->pkt.user_id->name;
|
|
for (; pktlen; pktlen--, p++)
|
|
*p = iobuf_get_noeof (inp);
|
|
*p = 0;
|
|
|
|
if (list_mode)
|
|
{
|
|
int n = packet->pkt.user_id->len;
|
|
es_fprintf (listfp, ":user ID packet: \"");
|
|
/* fixme: Hey why don't we replace this with es_write_sanitized?? */
|
|
for (p = packet->pkt.user_id->name; n; p++, n--)
|
|
{
|
|
if (*p >= ' ' && *p <= 'z')
|
|
es_putc (*p, listfp);
|
|
else
|
|
es_fprintf (listfp, "\\x%02x", *p);
|
|
}
|
|
es_fprintf (listfp, "\"\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
make_attribute_uidname (PKT_user_id * uid, size_t max_namelen)
|
|
{
|
|
log_assert (max_namelen > 70);
|
|
if (uid->numattribs <= 0)
|
|
sprintf (uid->name, "[bad attribute packet of size %lu]",
|
|
uid->attrib_len);
|
|
else if (uid->numattribs > 1)
|
|
sprintf (uid->name, "[%d attributes of size %lu]",
|
|
uid->numattribs, uid->attrib_len);
|
|
else
|
|
{
|
|
/* Only one attribute, so list it as the "user id" */
|
|
|
|
if (uid->attribs->type == ATTRIB_IMAGE)
|
|
{
|
|
u32 len;
|
|
byte type;
|
|
|
|
if (parse_image_header (uid->attribs, &type, &len))
|
|
sprintf (uid->name, "[%.20s image of size %lu]",
|
|
image_type_to_string (type, 1), (ulong) len);
|
|
else
|
|
sprintf (uid->name, "[invalid image]");
|
|
}
|
|
else
|
|
sprintf (uid->name, "[unknown attribute of size %lu]",
|
|
(ulong) uid->attribs->len);
|
|
}
|
|
|
|
uid->len = strlen (uid->name);
|
|
}
|
|
|
|
|
|
static int
|
|
parse_attribute (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * packet)
|
|
{
|
|
byte *p;
|
|
|
|
(void) pkttype;
|
|
|
|
/* We better cap the size of an attribute packet to make DoS not too
|
|
easy. 16MB should be more then enough for one attribute packet
|
|
(ie. a photo). */
|
|
if (pktlen > MAX_ATTR_PACKET_LENGTH)
|
|
{
|
|
log_error ("packet(%d) too large\n", pkttype);
|
|
if (list_mode)
|
|
es_fprintf (listfp, ":attribute packet: [too large]\n");
|
|
iobuf_skip_rest (inp, pktlen, 0);
|
|
return GPG_ERR_INV_PACKET;
|
|
}
|
|
|
|
#define EXTRA_UID_NAME_SPACE 71
|
|
packet->pkt.user_id = xmalloc_clear (sizeof *packet->pkt.user_id
|
|
+ EXTRA_UID_NAME_SPACE);
|
|
packet->pkt.user_id->ref = 1;
|
|
packet->pkt.user_id->attrib_data = xmalloc (pktlen? pktlen:1);
|
|
packet->pkt.user_id->attrib_len = pktlen;
|
|
|
|
p = packet->pkt.user_id->attrib_data;
|
|
for (; pktlen; pktlen--, p++)
|
|
*p = iobuf_get_noeof (inp);
|
|
|
|
/* Now parse out the individual attribute subpackets. This is
|
|
somewhat pointless since there is only one currently defined
|
|
attribute type (jpeg), but it is correct by the spec. */
|
|
parse_attribute_subpkts (packet->pkt.user_id);
|
|
|
|
make_attribute_uidname (packet->pkt.user_id, EXTRA_UID_NAME_SPACE);
|
|
|
|
if (list_mode)
|
|
{
|
|
es_fprintf (listfp, ":attribute packet: %s\n", packet->pkt.user_id->name);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
parse_comment (IOBUF inp, int pkttype, unsigned long pktlen, PACKET * packet)
|
|
{
|
|
byte *p;
|
|
|
|
/* Cap comment packet at a reasonable value to avoid an integer
|
|
overflow in the malloc below. Comment packets are actually not
|
|
anymore define my OpenPGP and we even stopped to use our
|
|
private comment packet. */
|
|
if (pktlen > MAX_COMMENT_PACKET_LENGTH)
|
|
{
|
|
log_error ("packet(%d) too large\n", pkttype);
|
|
if (list_mode)
|
|
es_fprintf (listfp, ":%scomment packet: [too large]\n",
|
|
pkttype == PKT_OLD_COMMENT ? "OpenPGP draft " : "");
|
|
iobuf_skip_rest (inp, pktlen, 0);
|
|
return GPG_ERR_INV_PACKET;
|
|
}
|
|
packet->pkt.comment = xmalloc (sizeof *packet->pkt.comment + pktlen - 1);
|
|
packet->pkt.comment->len = pktlen;
|
|
p = packet->pkt.comment->data;
|
|
for (; pktlen; pktlen--, p++)
|
|
*p = iobuf_get_noeof (inp);
|
|
|
|
if (list_mode)
|
|
{
|
|
int n = packet->pkt.comment->len;
|
|
es_fprintf (listfp, ":%scomment packet: \"", pkttype == PKT_OLD_COMMENT ?
|
|
"OpenPGP draft " : "");
|
|
for (p = packet->pkt.comment->data; n; p++, n--)
|
|
{
|
|
if (*p >= ' ' && *p <= 'z')
|
|
es_putc (*p, listfp);
|
|
else
|
|
es_fprintf (listfp, "\\x%02x", *p);
|
|
}
|
|
es_fprintf (listfp, "\"\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Parse a ring trust packet RFC4880 (5.10).
|
|
*
|
|
* This parser is special in that the packet is not stored as a packet
|
|
* but its content is merged into the previous packet. */
|
|
static gpg_error_t
|
|
parse_ring_trust (parse_packet_ctx_t ctx, unsigned long pktlen)
|
|
{
|
|
gpg_error_t err;
|
|
iobuf_t inp = ctx->inp;
|
|
PKT_ring_trust rt = {0};
|
|
int c;
|
|
int not_gpg = 0;
|
|
|
|
if (!pktlen)
|
|
{
|
|
if (list_mode)
|
|
es_fprintf (listfp, ":trust packet: empty\n");
|
|
err = 0;
|
|
goto leave;
|
|
}
|
|
|
|
c = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
rt.trustval = c;
|
|
if (pktlen)
|
|
{
|
|
if (!c)
|
|
{
|
|
c = iobuf_get_noeof (inp);
|
|
/* We require that bit 7 of the sigcache is 0 (easier
|
|
* eof handling). */
|
|
if (!(c & 0x80))
|
|
rt.sigcache = c;
|
|
}
|
|
else
|
|
iobuf_get_noeof (inp); /* Dummy read. */
|
|
pktlen--;
|
|
}
|
|
|
|
/* Next is the optional subtype. */
|
|
if (pktlen > 3)
|
|
{
|
|
char tmp[4];
|
|
tmp[0] = iobuf_get_noeof (inp);
|
|
tmp[1] = iobuf_get_noeof (inp);
|
|
tmp[2] = iobuf_get_noeof (inp);
|
|
tmp[3] = iobuf_get_noeof (inp);
|
|
pktlen -= 4;
|
|
if (!memcmp (tmp, "gpg", 3))
|
|
rt.subtype = tmp[3];
|
|
else
|
|
not_gpg = 1;
|
|
}
|
|
/* If it is a key or uid subtype read the remaining data. */
|
|
if ((rt.subtype == RING_TRUST_KEY || rt.subtype == RING_TRUST_UID)
|
|
&& pktlen >= 6 )
|
|
{
|
|
int i;
|
|
unsigned int namelen;
|
|
|
|
rt.keyorg = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
rt.keyupdate = read_32 (inp);
|
|
pktlen -= 4;
|
|
namelen = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
if (namelen && pktlen)
|
|
{
|
|
rt.url = xtrymalloc (namelen + 1);
|
|
if (!rt.url)
|
|
{
|
|
err = gpg_error_from_syserror ();
|
|
goto leave;
|
|
}
|
|
for (i = 0; pktlen && i < namelen; pktlen--, i++)
|
|
rt.url[i] = iobuf_get_noeof (inp);
|
|
rt.url[i] = 0;
|
|
}
|
|
}
|
|
|
|
if (list_mode)
|
|
{
|
|
if (rt.subtype == RING_TRUST_SIG)
|
|
es_fprintf (listfp, ":trust packet: sig flag=%02x sigcache=%02x\n",
|
|
rt.trustval, rt.sigcache);
|
|
else if (rt.subtype == RING_TRUST_UID || rt.subtype == RING_TRUST_KEY)
|
|
{
|
|
unsigned char *p;
|
|
|
|
es_fprintf (listfp, ":trust packet: %s upd=%lu src=%d%s",
|
|
(rt.subtype == RING_TRUST_UID? "uid" : "key"),
|
|
(unsigned long)rt.keyupdate,
|
|
rt.keyorg,
|
|
(rt.url? " url=":""));
|
|
if (rt.url)
|
|
{
|
|
for (p = rt.url; *p; p++)
|
|
{
|
|
if (*p >= ' ' && *p <= 'z')
|
|
es_putc (*p, listfp);
|
|
else
|
|
es_fprintf (listfp, "\\x%02x", *p);
|
|
}
|
|
}
|
|
es_putc ('\n', listfp);
|
|
}
|
|
else if (not_gpg)
|
|
es_fprintf (listfp, ":trust packet: not created by gpg\n");
|
|
else
|
|
es_fprintf (listfp, ":trust packet: subtype=%02x\n",
|
|
rt.subtype);
|
|
}
|
|
|
|
/* Now transfer the data to the respective packet. Do not do this
|
|
* if SKIP_META is set. */
|
|
if (!ctx->last_pkt.pkt.generic || ctx->skip_meta)
|
|
;
|
|
else if (rt.subtype == RING_TRUST_SIG
|
|
&& ctx->last_pkt.pkttype == PKT_SIGNATURE)
|
|
{
|
|
PKT_signature *sig = ctx->last_pkt.pkt.signature;
|
|
|
|
if ((rt.sigcache & 1))
|
|
{
|
|
sig->flags.checked = 1;
|
|
sig->flags.valid = !!(rt.sigcache & 2);
|
|
}
|
|
}
|
|
else if (rt.subtype == RING_TRUST_UID
|
|
&& (ctx->last_pkt.pkttype == PKT_USER_ID
|
|
|| ctx->last_pkt.pkttype == PKT_ATTRIBUTE))
|
|
{
|
|
PKT_user_id *uid = ctx->last_pkt.pkt.user_id;
|
|
|
|
uid->keyorg = rt.keyorg;
|
|
uid->keyupdate = rt.keyupdate;
|
|
uid->updateurl = rt.url;
|
|
rt.url = NULL;
|
|
}
|
|
else if (rt.subtype == RING_TRUST_KEY
|
|
&& (ctx->last_pkt.pkttype == PKT_PUBLIC_KEY
|
|
|| ctx->last_pkt.pkttype == PKT_SECRET_KEY))
|
|
{
|
|
PKT_public_key *pk = ctx->last_pkt.pkt.public_key;
|
|
|
|
pk->keyorg = rt.keyorg;
|
|
pk->keyupdate = rt.keyupdate;
|
|
pk->updateurl = rt.url;
|
|
rt.url = NULL;
|
|
}
|
|
|
|
err = 0;
|
|
|
|
leave:
|
|
xfree (rt.url);
|
|
free_packet (NULL, ctx); /* This sets ctx->last_pkt to NULL. */
|
|
iobuf_skip_rest (inp, pktlen, 0);
|
|
return err;
|
|
}
|
|
|
|
|
|
static int
|
|
parse_plaintext (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * pkt, int new_ctb, int partial)
|
|
{
|
|
int rc = 0;
|
|
int mode, namelen;
|
|
PKT_plaintext *pt;
|
|
byte *p;
|
|
int c, i;
|
|
|
|
if (!partial && pktlen < 6)
|
|
{
|
|
log_error ("packet(%d) too short (%lu)\n", pkttype, (ulong) pktlen);
|
|
if (list_mode)
|
|
es_fputs (":literal data packet: [too short]\n", listfp);
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
mode = iobuf_get_noeof (inp);
|
|
if (pktlen)
|
|
pktlen--;
|
|
namelen = iobuf_get_noeof (inp);
|
|
if (pktlen)
|
|
pktlen--;
|
|
/* Note that namelen will never exceed 255 bytes. */
|
|
pt = pkt->pkt.plaintext =
|
|
xmalloc (sizeof *pkt->pkt.plaintext + namelen - 1);
|
|
pt->new_ctb = new_ctb;
|
|
pt->mode = mode;
|
|
pt->namelen = namelen;
|
|
pt->is_partial = partial;
|
|
if (pktlen)
|
|
{
|
|
for (i = 0; pktlen > 4 && i < namelen; pktlen--, i++)
|
|
pt->name[i] = iobuf_get_noeof (inp);
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < namelen; i++)
|
|
if ((c = iobuf_get (inp)) == -1)
|
|
break;
|
|
else
|
|
pt->name[i] = c;
|
|
}
|
|
/* Fill up NAME so that a check with valgrind won't complain about
|
|
* reading from uninitialized memory. This case may be triggred by
|
|
* corrupted packets. */
|
|
for (; i < namelen; i++)
|
|
pt->name[i] = 0;
|
|
|
|
pt->timestamp = read_32 (inp);
|
|
if (pktlen)
|
|
pktlen -= 4;
|
|
pt->len = pktlen;
|
|
pt->buf = inp;
|
|
|
|
if (list_mode)
|
|
{
|
|
es_fprintf (listfp, ":literal data packet:\n"
|
|
"\tmode %c (%X), created %lu, name=\"",
|
|
mode >= ' ' && mode < 'z' ? mode : '?', mode,
|
|
(ulong) pt->timestamp);
|
|
for (p = pt->name, i = 0; i < namelen; p++, i++)
|
|
{
|
|
if (*p >= ' ' && *p <= 'z')
|
|
es_putc (*p, listfp);
|
|
else
|
|
es_fprintf (listfp, "\\x%02x", *p);
|
|
}
|
|
es_fprintf (listfp, "\",\n\traw data: ");
|
|
if (partial)
|
|
es_fprintf (listfp, "unknown length\n");
|
|
else
|
|
es_fprintf (listfp, "%lu bytes\n", (ulong) pt->len);
|
|
}
|
|
|
|
leave:
|
|
return rc;
|
|
}
|
|
|
|
|
|
static int
|
|
parse_compressed (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * pkt, int new_ctb)
|
|
{
|
|
PKT_compressed *zd;
|
|
|
|
/* PKTLEN is here 0, but data follows (this should be the last
|
|
object in a file or the compress algorithm should know the
|
|
length). */
|
|
(void) pkttype;
|
|
(void) pktlen;
|
|
|
|
zd = pkt->pkt.compressed = xmalloc (sizeof *pkt->pkt.compressed);
|
|
zd->algorithm = iobuf_get_noeof (inp);
|
|
zd->len = 0; /* not used */
|
|
zd->new_ctb = new_ctb;
|
|
zd->buf = inp;
|
|
if (list_mode)
|
|
es_fprintf (listfp, ":compressed packet: algo=%d\n", zd->algorithm);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
parse_encrypted (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * pkt, int new_ctb, int partial)
|
|
{
|
|
int rc = 0;
|
|
PKT_encrypted *ed;
|
|
unsigned long orig_pktlen = pktlen;
|
|
|
|
ed = pkt->pkt.encrypted = xmalloc (sizeof *pkt->pkt.encrypted);
|
|
/* ed->len is set below. */
|
|
ed->extralen = 0; /* Unknown here; only used in build_packet. */
|
|
ed->buf = NULL;
|
|
ed->new_ctb = new_ctb;
|
|
ed->is_partial = partial;
|
|
ed->aead_algo = 0;
|
|
ed->cipher_algo = 0; /* Only used with AEAD. */
|
|
ed->chunkbyte = 0; /* Only used with AEAD. */
|
|
if (pkttype == PKT_ENCRYPTED_MDC)
|
|
{
|
|
/* Fixme: add some pktlen sanity checks. */
|
|
int version;
|
|
|
|
version = iobuf_get_noeof (inp);
|
|
if (orig_pktlen)
|
|
pktlen--;
|
|
if (version != 1)
|
|
{
|
|
log_error ("encrypted_mdc packet with unknown version %d\n",
|
|
version);
|
|
if (list_mode)
|
|
es_fputs (":encrypted data packet: [unknown version]\n", listfp);
|
|
/*skip_rest(inp, pktlen); should we really do this? */
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
ed->mdc_method = DIGEST_ALGO_SHA1;
|
|
}
|
|
else
|
|
ed->mdc_method = 0;
|
|
|
|
/* A basic sanity check. We need at least an 8 byte IV plus the 2
|
|
detection bytes. Note that we don't known the algorithm and thus
|
|
we may only check against the minimum blocksize. */
|
|
if (orig_pktlen && pktlen < 10)
|
|
{
|
|
/* Actually this is blocksize+2. */
|
|
log_error ("packet(%d) too short\n", pkttype);
|
|
if (list_mode)
|
|
es_fputs (":encrypted data packet: [too short]\n", listfp);
|
|
rc = GPG_ERR_INV_PACKET;
|
|
iobuf_skip_rest (inp, pktlen, partial);
|
|
goto leave;
|
|
}
|
|
|
|
/* Store the remaining length of the encrypted data (i.e. without
|
|
the MDC version number but with the IV etc.). This value is
|
|
required during decryption. */
|
|
ed->len = pktlen;
|
|
|
|
if (list_mode)
|
|
{
|
|
if (orig_pktlen)
|
|
es_fprintf (listfp, ":encrypted data packet:\n\tlength: %lu\n",
|
|
orig_pktlen);
|
|
else
|
|
es_fprintf (listfp, ":encrypted data packet:\n\tlength: unknown\n");
|
|
if (ed->mdc_method)
|
|
es_fprintf (listfp, "\tmdc_method: %d\n", ed->mdc_method);
|
|
}
|
|
|
|
ed->buf = inp;
|
|
|
|
leave:
|
|
return rc;
|
|
}
|
|
|
|
|
|
/* Note, that this code is not anymore used in real life because the
|
|
MDC checking is now done right after the decryption in
|
|
decrypt_data. */
|
|
static int
|
|
parse_mdc (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * pkt, int new_ctb)
|
|
{
|
|
int rc = 0;
|
|
PKT_mdc *mdc;
|
|
byte *p;
|
|
|
|
(void) pkttype;
|
|
|
|
mdc = pkt->pkt.mdc = xmalloc (sizeof *pkt->pkt.mdc);
|
|
if (list_mode)
|
|
es_fprintf (listfp, ":mdc packet: length=%lu\n", pktlen);
|
|
if (!new_ctb || pktlen != 20)
|
|
{
|
|
log_error ("mdc_packet with invalid encoding\n");
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
p = mdc->hash;
|
|
for (; pktlen; pktlen--, p++)
|
|
*p = iobuf_get_noeof (inp);
|
|
|
|
leave:
|
|
return rc;
|
|
}
|
|
|
|
|
|
static gpg_error_t
|
|
parse_encrypted_aead (iobuf_t inp, int pkttype, unsigned long pktlen,
|
|
PACKET *pkt, int partial)
|
|
{
|
|
int rc = 0;
|
|
PKT_encrypted *ed;
|
|
unsigned long orig_pktlen = pktlen;
|
|
int version;
|
|
|
|
ed = pkt->pkt.encrypted = xtrymalloc (sizeof *pkt->pkt.encrypted);
|
|
if (!ed)
|
|
return gpg_error_from_syserror ();
|
|
ed->len = 0;
|
|
ed->extralen = 0; /* (only used in build_packet.) */
|
|
ed->buf = NULL;
|
|
ed->new_ctb = 1; /* (packet number requires a new CTB anyway.) */
|
|
ed->is_partial = partial;
|
|
ed->mdc_method = 0;
|
|
/* A basic sanity check. We need one version byte, one algo byte,
|
|
* one aead algo byte, one chunkbyte, at least 15 byte IV. */
|
|
if (orig_pktlen && pktlen < 19)
|
|
{
|
|
log_error ("packet(%d) too short\n", pkttype);
|
|
if (list_mode)
|
|
es_fputs (":aead encrypted packet: [too short]\n", listfp);
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
iobuf_skip_rest (inp, pktlen, partial);
|
|
goto leave;
|
|
}
|
|
|
|
version = iobuf_get_noeof (inp);
|
|
if (orig_pktlen)
|
|
pktlen--;
|
|
if (version != 1)
|
|
{
|
|
log_error ("aead encrypted packet with unknown version %d\n",
|
|
version);
|
|
if (list_mode)
|
|
es_fputs (":aead encrypted packet: [unknown version]\n", listfp);
|
|
/*skip_rest(inp, pktlen); should we really do this? */
|
|
rc = gpg_error (GPG_ERR_INV_PACKET);
|
|
goto leave;
|
|
}
|
|
|
|
ed->cipher_algo = iobuf_get_noeof (inp);
|
|
if (orig_pktlen)
|
|
pktlen--;
|
|
ed->aead_algo = iobuf_get_noeof (inp);
|
|
if (orig_pktlen)
|
|
pktlen--;
|
|
ed->chunkbyte = iobuf_get_noeof (inp);
|
|
if (orig_pktlen)
|
|
pktlen--;
|
|
|
|
/* Store the remaining length of the encrypted data. We read the
|
|
* rest during decryption. */
|
|
ed->len = pktlen;
|
|
|
|
if (list_mode)
|
|
{
|
|
es_fprintf (listfp, ":aead encrypted packet: cipher=%u aead=%u cb=%u\n",
|
|
ed->cipher_algo, ed->aead_algo, ed->chunkbyte);
|
|
if (orig_pktlen)
|
|
es_fprintf (listfp, "\tlength: %lu\n", orig_pktlen);
|
|
else
|
|
es_fprintf (listfp, "\tlength: unknown\n");
|
|
}
|
|
|
|
ed->buf = inp;
|
|
|
|
leave:
|
|
return rc;
|
|
}
|
|
|
|
|
|
/*
|
|
* This packet is internally generated by us (in armor.c) to transfer
|
|
* some information to the lower layer. To make sure that this packet
|
|
* is really a GPG faked one and not one coming from outside, we
|
|
* first check that there is a unique tag in it.
|
|
*
|
|
* The format of such a control packet is:
|
|
* n byte session marker
|
|
* 1 byte control type CTRLPKT_xxxxx
|
|
* m byte control data
|
|
*/
|
|
static int
|
|
parse_gpg_control (IOBUF inp, int pkttype, unsigned long pktlen,
|
|
PACKET * packet, int partial)
|
|
{
|
|
byte *p;
|
|
const byte *sesmark;
|
|
size_t sesmarklen;
|
|
int i;
|
|
|
|
(void) pkttype;
|
|
|
|
if (list_mode)
|
|
es_fprintf (listfp, ":packet 63: length %lu ", pktlen);
|
|
|
|
sesmark = get_session_marker (&sesmarklen);
|
|
if (pktlen < sesmarklen + 1) /* 1 is for the control bytes */
|
|
goto skipit;
|
|
for (i = 0; i < sesmarklen; i++, pktlen--)
|
|
{
|
|
if (sesmark[i] != iobuf_get_noeof (inp))
|
|
goto skipit;
|
|
}
|
|
if (pktlen > 4096)
|
|
goto skipit; /* Definitely too large. We skip it to avoid an
|
|
overflow in the malloc. */
|
|
if (list_mode)
|
|
es_fputs ("- gpg control packet", listfp);
|
|
|
|
packet->pkt.gpg_control = xmalloc (sizeof *packet->pkt.gpg_control
|
|
+ pktlen - 1);
|
|
packet->pkt.gpg_control->control = iobuf_get_noeof (inp);
|
|
pktlen--;
|
|
packet->pkt.gpg_control->datalen = pktlen;
|
|
p = packet->pkt.gpg_control->data;
|
|
for (; pktlen; pktlen--, p++)
|
|
*p = iobuf_get_noeof (inp);
|
|
|
|
return 0;
|
|
|
|
skipit:
|
|
if (list_mode)
|
|
{
|
|
int c;
|
|
|
|
i = 0;
|
|
es_fprintf (listfp, "- private (rest length %lu)\n", pktlen);
|
|
if (partial)
|
|
{
|
|
while ((c = iobuf_get (inp)) != -1)
|
|
dump_hex_line (c, &i);
|
|
}
|
|
else
|
|
{
|
|
for (; pktlen; pktlen--)
|
|
{
|
|
dump_hex_line ((c = iobuf_get (inp)), &i);
|
|
if (c == -1)
|
|
break;
|
|
}
|
|
}
|
|
es_putc ('\n', listfp);
|
|
}
|
|
iobuf_skip_rest (inp, pktlen, 0);
|
|
return gpg_error (GPG_ERR_INV_PACKET);
|
|
}
|
|
|
|
|
|
/* Create a GPG control packet to be used internally as a placeholder. */
|
|
PACKET *
|
|
create_gpg_control (ctrlpkttype_t type, const byte * data, size_t datalen)
|
|
{
|
|
PACKET *packet;
|
|
byte *p;
|
|
|
|
if (!data)
|
|
datalen = 0;
|
|
|
|
packet = xmalloc (sizeof *packet);
|
|
init_packet (packet);
|
|
packet->pkttype = PKT_GPG_CONTROL;
|
|
packet->pkt.gpg_control = xmalloc (sizeof *packet->pkt.gpg_control + datalen);
|
|
packet->pkt.gpg_control->control = type;
|
|
packet->pkt.gpg_control->datalen = datalen;
|
|
p = packet->pkt.gpg_control->data;
|
|
for (; datalen; datalen--, p++)
|
|
*p = *data++;
|
|
|
|
return packet;
|
|
}
|