/* parse-packet.c - read packets
- * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2009, 2010 Free Software Foundation, Inc.
- * Copyright (C) 2014 Werner Koch
+ * 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.
*
* 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>
#define MAX_COMMENT_PACKET_LENGTH ( 64 * 1024)
#define MAX_ATTR_PACKET_LENGTH ( 16 * 1024*1024)
-
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
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,
}
+/* 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;
+ sl = add_to_strlist (&known_notations_list, "pka-address@gnupg.org");
+ sl->flags = 21;
+ }
+ 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)
{
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. */
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;
}
-/* Read PKTLEN bytes form 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. */
+/* 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)
{
{
PKT_symkey_enc *k;
int rc = 0;
- int i, version, s2kmode, cipher_algo, hash_algo, seskeylen, minlen;
+ int i, version, s2kmode, cipher_algo, aead_algo, hash_algo, seskeylen, minlen;
if (pktlen < 4)
- {
- 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;
- }
+ goto too_short;
version = iobuf_get_noeof (inp);
pktlen--;
- if (version != 4)
+ if (version == 4)
+ ;
+ else if (version == 5)
+ ;
+ else
{
log_error ("packet(%d) with unknown version %d\n", pkttype, version);
if (list_mode)
}
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);
+ 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)
}
if (s2kmode == 3)
{
- k->s2k.count = iobuf_get (inp);
+ k->s2k.count = iobuf_get_noeof (inp);
pktlen--;
}
k->seskeylen = seskeylen;
if (list_mode)
{
es_fprintf (listfp,
- ":symkey enc packet: version %d, cipher %d, s2k %d, hash %d",
- version, cipher_algo, s2kmode, hash_algo);
+ ":symkey enc packet: version %d, cipher %d, aead %d,"
+ " s2k %d, hash %d",
+ version, cipher_algo, aead_algo, s2kmode, hash_algo);
if (seskeylen)
- es_fprintf (listfp, ", seskey %d bits", (seskeylen - 1) * 8);
+ {
+ /* 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 seize 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)
{
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;
}
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)
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:
/* Return true if we understand the critical notation. */
static int
-can_handle_critical_notation (const byte * name, size_t len)
+can_handle_critical_notation (const byte *name, size_t len)
{
- if (len == 32 && memcmp (name, "preferred-email-encoding@pgp.com", 32) == 0)
- return 1;
- if (len == 21 && memcmp (name, "pka-address@gnupg.org", 21) == 0)
- return 1;
+ strlist_t sl;
- return 0;
+ 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)
+ {
+ log_info(_("Unknown critical signature notation: ") );
+ print_utf8_buffer (log_get_stream(), name, len);
+ log_printf ("\n");
+ }
+
+ return 0; /* Unknown. */
}
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:
}
if (buflen < n)
goto too_short;
+ if (!buflen)
+ goto no_type_byte;
type = *buffer;
if (type & 0x80)
{
if (start)
*start = -1;
return NULL;
+
+ no_type_byte:
+ if (opt.verbose)
+ log_info ("type octet missing in subpacket\n");
+ if (start)
+ *start = -1;
+ return NULL;
}
while ((revkey = enum_sig_subpkt (sig->hashed, SIGSUBPKT_REV_KEY,
&len, &seq, NULL)))
{
- if (/* The only valid length is 22 bytes. See RFC 4880
- 5.2.3.15. */
- len == 22
- /* 0x80 bit must be set on the class. */
+ /* 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));
- /* Copy the individual fields. */
sig->revkey[sig->numrevkeys].class = revkey[0];
sig->revkey[sig->numrevkeys].algid = revkey[1];
- memcpy (sig->revkey[sig->numrevkeys].fpr, &revkey[2], 20);
-
+ 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 md5_len = 0;
unsigned n;
- int is_v4 = 0;
+ int is_v4or5 = 0;
int rc = 0;
int i, ndata;
}
sig->version = iobuf_get_noeof (inp);
pktlen--;
- if (sig->version == 4)
- is_v4 = 1;
+ 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",
goto leave;
}
- if (!is_v4)
+ if (!is_v4or5)
{
if (pktlen == 0)
goto underflow;
goto underflow;
sig->sig_class = iobuf_get_noeof (inp);
pktlen--;
- if (!is_v4)
+ if (!is_v4or5)
{
if (pktlen < 12)
goto underflow;
pktlen--;
sig->flags.exportable = 1;
sig->flags.revocable = 1;
- if (is_v4) /* Read subpackets. */
+ if (is_v4or5) /* Read subpackets. */
{
if (pktlen < 2)
goto underflow;
sig->digest_start[1] = iobuf_get_noeof (inp);
pktlen--;
- if (is_v4 && sig->pubkey_algo) /* Extract required information. */
+ if (is_v4or5 && sig->pubkey_algo) /* Extract required information. */
{
const byte *p;
size_t len;
(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_v4)
+ if (is_v4or5)
{
parse_sig_subpkt (sig->hashed, SIGSUBPKT_LIST_HASHED, NULL);
parse_sig_subpkt (sig->unhashed, SIGSUBPKT_LIST_UNHASHED, NULL);
{
log_error ("packet(%d) too large\n", pkttype);
if (list_mode)
- es_fputs (":key packet: [too larget]\n", listfp);
+ es_fputs (":key packet: [too large]\n", listfp);
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
err = gpg_error (GPG_ERR_INV_PACKET);
goto leave;
}
- ski->s2k.count = iobuf_get (inp);
+ ski->s2k.count = iobuf_get_noeof (inp);
pktlen--;
if (list_mode)
es_fprintf (listfp, "\tprotect count: %lu (%lu)\n",
pt->name[i] = c;
}
/* Fill up NAME so that a check with valgrind won't complain about
- * reading from uninitalized memory. This case may be triggred by
+ * reading from uninitialized memory. This case may be triggred by
* corrupted packets. */
for (; i < namelen; i++)
pt->name[i] = 0;
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. */
}
+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