mirror of
git://git.gnupg.org/gnupg.git
synced 2024-10-29 19:48:43 +01:00
828 lines
24 KiB
C
828 lines
24 KiB
C
|
/* tlv-parser.c - Parse BER encoded objects
|
|||
|
* Copyright (C) 2023, 2024 g10 Code GmbH
|
|||
|
*
|
|||
|
* This file is part of GnuPG.
|
|||
|
*
|
|||
|
* This file is free software; you can redistribute it and/or modify
|
|||
|
* it under the terms of the GNU Lesser General Public License as
|
|||
|
* published by the Free Software Foundation; either version 2.1 of
|
|||
|
* the License, or (at your option) any later version.
|
|||
|
*
|
|||
|
* This file 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 Lesser General Public License
|
|||
|
* along with this program; if not, see <https://www.gnu.org/licenses/>.
|
|||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|||
|
*/
|
|||
|
|
|||
|
#include <config.h>
|
|||
|
|
|||
|
#include <stdio.h>
|
|||
|
#include <stdlib.h>
|
|||
|
#include <string.h>
|
|||
|
#include <gpg-error.h>
|
|||
|
|
|||
|
#include "util.h"
|
|||
|
#include "tlv.h"
|
|||
|
|
|||
|
|
|||
|
#define TLV_MAX_DEPTH 25
|
|||
|
|
|||
|
|
|||
|
|
|||
|
/* An object to control the ASN.1 parsing. */
|
|||
|
struct tlv_parser_s
|
|||
|
{
|
|||
|
/* The original buffer with the entire pkcs#12 object and its length. */
|
|||
|
unsigned char *origbuffer;
|
|||
|
size_t origbufsize;
|
|||
|
|
|||
|
/* The original offset for debugging. */
|
|||
|
size_t origoff;
|
|||
|
|
|||
|
/* Here we keep a copy of the former TLV. This is returned by
|
|||
|
* tlv_parser_release. */
|
|||
|
tlv_parser_t lasttlv;
|
|||
|
|
|||
|
/* The current buffer we are working on and its length. */
|
|||
|
unsigned char *buffer;
|
|||
|
size_t bufsize;
|
|||
|
|
|||
|
size_t crammed; /* 0 or actual length of crammed octet strings. */
|
|||
|
int in_ndef; /* Flag indicating that we are in a NDEF. */
|
|||
|
int pending; /* The last tlv_next has not yet been processed. */
|
|||
|
|
|||
|
struct tag_info ti; /* The current tag. */
|
|||
|
gpg_error_t lasterr; /* Last error from tlv function. */
|
|||
|
const char *lastfunc;/* Name of last called function. */
|
|||
|
int verbosity; /* Arg from tlv_parser_new. */
|
|||
|
|
|||
|
unsigned int stacklen; /* Used size of the stack. */
|
|||
|
struct {
|
|||
|
unsigned char *buffer; /* Saved value of BUFFER. */
|
|||
|
size_t bufsize; /* Saved value of BUFSIZE. */
|
|||
|
size_t length; /* Length of the container (ti.length). */
|
|||
|
size_t crammed; /* Saved CRAMMED value. */
|
|||
|
int in_ndef; /* Saved IN_NDEF flag (ti.ndef). */
|
|||
|
} stack[TLV_MAX_DEPTH];
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
static size_t cram_octet_string (tlv_parser_t tlv, int testmode);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
void
|
|||
|
_tlv_parser_dump_tag (const char *text, int lno, tlv_parser_t tlv)
|
|||
|
{
|
|||
|
struct tag_info *ti;
|
|||
|
|
|||
|
if (!tlv || tlv->verbosity < 2)
|
|||
|
return;
|
|||
|
|
|||
|
ti = &tlv->ti;
|
|||
|
|
|||
|
log_debug ("%s:%d: %zu@%04zu class=%d tag=%lu %c len=%zu%s nhdr=%zu\n",
|
|||
|
text, lno, tlv->origoff, tlv_parser_offset (tlv) - ti->nhdr,
|
|||
|
ti->class, ti->tag, ti->is_constructed?'c':'p',
|
|||
|
ti->length,ti->ndef?" ndef":"", ti->nhdr);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
void
|
|||
|
_tlv_parser_dump_state (const char *text, const char *text2,
|
|||
|
int lno, tlv_parser_t tlv)
|
|||
|
{
|
|||
|
if (!tlv || tlv->verbosity < 2)
|
|||
|
return;
|
|||
|
|
|||
|
log_debug ("p12_parse:%s%s%s:%d: %zu@%04zu lvl=%u %s\n",
|
|||
|
text,
|
|||
|
text2? "/":"", text2? text2:"",
|
|||
|
lno, tlv->origoff, tlv_parser_offset (tlv),
|
|||
|
tlv->stacklen,
|
|||
|
tlv->in_ndef? " in-ndef":"");
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
static void
|
|||
|
dump_to_file (const void *s, size_t n, const char *name)
|
|||
|
{
|
|||
|
#if 0
|
|||
|
FILE *fp;
|
|||
|
char fname[100];
|
|||
|
static int fcount;
|
|||
|
|
|||
|
snprintf (fname, sizeof fname, "tmp-%03d-%s", ++fcount, name);
|
|||
|
log_debug ("dumping %zu bytes to '%s'\n", n, fname);
|
|||
|
fp = fopen (fname, "wb");
|
|||
|
if (!fp || fwrite (s, n, 1, fp) != 1)
|
|||
|
exit (2);
|
|||
|
fclose (fp);
|
|||
|
#else
|
|||
|
(void)s;
|
|||
|
(void)n;
|
|||
|
(void)name;
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Parse the buffer at the address BUFFER which is of SIZE and return
|
|||
|
* the tag and the length part from the TLV triplet. Update BUFFER
|
|||
|
* and SIZE on success. Checks that the encoded length does not
|
|||
|
* exhaust the length of the provided buffer. */
|
|||
|
static int
|
|||
|
parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti)
|
|||
|
{
|
|||
|
gpg_error_t err;
|
|||
|
int tag;
|
|||
|
|
|||
|
err = parse_ber_header (buffer, size,
|
|||
|
&ti->class, &tag,
|
|||
|
&ti->is_constructed, &ti->ndef,
|
|||
|
&ti->length, &ti->nhdr);
|
|||
|
if (err)
|
|||
|
return err;
|
|||
|
if (tag < 0)
|
|||
|
return gpg_error (GPG_ERR_EOVERFLOW);
|
|||
|
ti->tag = tag;
|
|||
|
|
|||
|
if (ti->length > *size)
|
|||
|
{
|
|||
|
/* data larger than buffer. */
|
|||
|
log_debug ("%s: ti->length=%zu for a buffer of size=%zu\n",
|
|||
|
__func__, ti->length, *size);
|
|||
|
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/* Public version of parse_tag. */
|
|||
|
gpg_error_t
|
|||
|
tlv_parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti)
|
|||
|
{
|
|||
|
return parse_tag (buffer, size, ti);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Create a new TLV object. */
|
|||
|
tlv_parser_t
|
|||
|
_tlv_parser_new (const unsigned char *buffer, size_t bufsize,
|
|||
|
int verbosity, tlv_parser_t lasttlv, int lno)
|
|||
|
{
|
|||
|
tlv_parser_t tlv;
|
|||
|
|
|||
|
if (verbosity > 1)
|
|||
|
log_debug ("%s:%d: %zu@%zu (%p,%zu)\n", __func__, lno,
|
|||
|
lasttlv?lasttlv->origoff:0, tlv_parser_offset (lasttlv),
|
|||
|
buffer, bufsize);
|
|||
|
|
|||
|
tlv = xtrycalloc (1, sizeof *tlv);
|
|||
|
if (tlv)
|
|||
|
{
|
|||
|
char *mybuf = xtrymalloc ( bufsize + 1);
|
|||
|
if (!mybuf)
|
|||
|
{
|
|||
|
xfree (tlv);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
memcpy (mybuf, buffer, bufsize);
|
|||
|
mybuf[bufsize] = 0;
|
|||
|
tlv->origbuffer = mybuf;
|
|||
|
tlv->origbufsize = bufsize;
|
|||
|
tlv->origoff = tlv_parser_offset (lasttlv);
|
|||
|
tlv->buffer = mybuf;
|
|||
|
tlv->bufsize = bufsize;
|
|||
|
tlv->crammed = 0;
|
|||
|
tlv->verbosity = verbosity;
|
|||
|
tlv->lasttlv = lasttlv;
|
|||
|
dump_to_file (mybuf, bufsize, "context");
|
|||
|
}
|
|||
|
return tlv;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Free the TLV object and returns the last TLV object stored in this
|
|||
|
* TLV. */
|
|||
|
tlv_parser_t
|
|||
|
_tlv_parser_release (tlv_parser_t tlv, int lno)
|
|||
|
{
|
|||
|
tlv_parser_t result;
|
|||
|
|
|||
|
if (!tlv)
|
|||
|
return NULL;
|
|||
|
result = tlv->lasttlv;
|
|||
|
if (tlv->verbosity > 1)
|
|||
|
{
|
|||
|
if (result)
|
|||
|
log_debug ("%s:%d: done; returning last TLV %zu@%zu (%p,%zu)\n",
|
|||
|
__func__, lno, result->origoff,
|
|||
|
tlv_parser_offset (result), result->buffer, result->bufsize);
|
|||
|
else
|
|||
|
log_debug ("%s:%d: done\n", __func__, lno);
|
|||
|
}
|
|||
|
xfree (tlv->origbuffer);
|
|||
|
xfree (tlv);
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Helper for tlv_expect_sequence and tlv_expect_context_tag. */
|
|||
|
static gpg_error_t
|
|||
|
_tlv_push (tlv_parser_t tlv)
|
|||
|
{
|
|||
|
|
|||
|
/* Right now our pointer is at the value of the current container.
|
|||
|
* We push that info onto the stack. */
|
|||
|
if (tlv->stacklen >= TLV_MAX_DEPTH)
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_MANY));
|
|||
|
tlv->stack[tlv->stacklen].buffer = tlv->buffer;
|
|||
|
tlv->stack[tlv->stacklen].bufsize = tlv->bufsize;
|
|||
|
tlv->stack[tlv->stacklen].in_ndef = tlv->in_ndef;
|
|||
|
tlv->stack[tlv->stacklen].length = tlv->ti.length;
|
|||
|
tlv->stack[tlv->stacklen].crammed = tlv->crammed;
|
|||
|
tlv->stacklen++;
|
|||
|
|
|||
|
tlv->in_ndef = tlv->ti.ndef;
|
|||
|
|
|||
|
/* We set the size of the buffer to the TLV length if it is known or
|
|||
|
* else to the size of the remaining entire buffer. */
|
|||
|
if (tlv->in_ndef)
|
|||
|
{
|
|||
|
if ((tlv->buffer - tlv->origbuffer) > tlv->origbufsize)
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_BUG));
|
|||
|
tlv->bufsize = tlv->origbufsize - (tlv->buffer - tlv->origbuffer);
|
|||
|
}
|
|||
|
else
|
|||
|
tlv->bufsize = tlv->ti.length;
|
|||
|
|
|||
|
_tlv_parser_dump_state (__func__, NULL, 0, tlv);
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Helper for tlv_next. */
|
|||
|
static gpg_error_t
|
|||
|
_tlv_pop (tlv_parser_t tlv)
|
|||
|
{
|
|||
|
size_t length;
|
|||
|
|
|||
|
/* We reached the end of a container, either due to the size limit
|
|||
|
* or due to an end tag. Now we pop the last container so that we
|
|||
|
* are positioned at the value of the last container. */
|
|||
|
if (!tlv->stacklen)
|
|||
|
return gpg_error (GPG_ERR_EOF);
|
|||
|
|
|||
|
tlv->stacklen--;
|
|||
|
tlv->in_ndef = tlv->stack[tlv->stacklen].in_ndef;
|
|||
|
length = tlv->ti.length = tlv->stack[tlv->stacklen].length;
|
|||
|
tlv->crammed = tlv->stack[tlv->stacklen].crammed;
|
|||
|
if (tlv->in_ndef)
|
|||
|
{
|
|||
|
/* We keep buffer but adjust bufsize to the end of the origbuffer. */
|
|||
|
if ((tlv->buffer - tlv->origbuffer) > tlv->origbufsize)
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_BUG));
|
|||
|
tlv->bufsize = tlv->origbufsize - (tlv->buffer - tlv->origbuffer);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
tlv->buffer = tlv->stack[tlv->stacklen].buffer;
|
|||
|
tlv->bufsize = tlv->stack[tlv->stacklen].bufsize;
|
|||
|
if (length > tlv->bufsize)
|
|||
|
{
|
|||
|
if (tlv->verbosity > 1)
|
|||
|
log_debug ("%s: container larger than buffer (%zu/%zu)\n",
|
|||
|
__func__, length, tlv->bufsize);
|
|||
|
return gpg_error (GPG_ERR_INV_BER);
|
|||
|
}
|
|||
|
tlv->buffer += length;
|
|||
|
tlv->bufsize -= length;
|
|||
|
|
|||
|
}
|
|||
|
_tlv_parser_dump_state (__func__, NULL, 0, tlv);
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Parse the next tag and value. Also detect the end of a
|
|||
|
* container. The caller should use the tlv_next macro. */
|
|||
|
gpg_error_t
|
|||
|
_tlv_parser_next (tlv_parser_t tlv, unsigned int flag, int lno)
|
|||
|
{
|
|||
|
gpg_error_t err;
|
|||
|
const unsigned char *buffer;
|
|||
|
size_t save_bufsize;
|
|||
|
const unsigned char *save_buffer;
|
|||
|
int i;
|
|||
|
|
|||
|
tlv->lasterr = 0;
|
|||
|
tlv->lastfunc = __func__;
|
|||
|
|
|||
|
if (tlv->pending)
|
|||
|
{
|
|||
|
tlv->pending = 0;
|
|||
|
if (tlv->verbosity > 1)
|
|||
|
log_debug ("%s:%d: skipped\n", __func__, lno);
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
if (tlv->verbosity > 1)
|
|||
|
log_debug ("%s:%d: called (%p,%zu)\n", __func__, lno,
|
|||
|
tlv->buffer, tlv->bufsize);
|
|||
|
/* If we are at the end of an ndef container pop the stack. */
|
|||
|
if (!tlv->in_ndef && !tlv->bufsize)
|
|||
|
{
|
|||
|
if (tlv->verbosity > 1)
|
|||
|
for (i=0; i < tlv->stacklen; i++)
|
|||
|
log_debug ("%s: stack[%d] (%p,@%zu,%zu) len=%zu (%zu) %s\n",
|
|||
|
__func__, i,
|
|||
|
tlv->stack[i].buffer,
|
|||
|
tlv->stack[i].buffer - tlv->origbuffer,
|
|||
|
tlv->stack[i].bufsize,
|
|||
|
tlv->stack[i].length,
|
|||
|
tlv->stack[i].crammed,
|
|||
|
tlv->stack[i].in_ndef? " ndef":"");
|
|||
|
do
|
|||
|
err = _tlv_pop (tlv);
|
|||
|
while (!err && !tlv->in_ndef && !tlv->bufsize);
|
|||
|
|
|||
|
if (err)
|
|||
|
return (tlv->lasterr = err);
|
|||
|
if (tlv->verbosity > 1)
|
|||
|
log_debug ("%s: container(s) closed due to size (lvl=%d)\n",
|
|||
|
__func__, tlv->stacklen);
|
|||
|
}
|
|||
|
|
|||
|
again:
|
|||
|
/* Get the next tag. */
|
|||
|
save_buffer = buffer = tlv->buffer;
|
|||
|
save_bufsize = tlv->bufsize;
|
|||
|
err = parse_tag (&buffer, &tlv->bufsize, &tlv->ti);
|
|||
|
tlv->buffer = (unsigned char *)buffer;
|
|||
|
if (err)
|
|||
|
{
|
|||
|
if (tlv->verbosity > 1)
|
|||
|
{
|
|||
|
log_debug ("%s: reading tag returned err=%d\n", __func__, err);
|
|||
|
log_printhex (save_buffer, save_bufsize > 40? 40: save_bufsize,
|
|||
|
"%s: data was\n", __func__);
|
|||
|
dump_to_file (tlv->origbuffer, save_buffer - tlv->origbuffer,
|
|||
|
"parseerr");
|
|||
|
}
|
|||
|
return err;
|
|||
|
}
|
|||
|
|
|||
|
if ( ( (tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OCTET_STRING)
|
|||
|
|| ((flag & TLV_PARSER_FLAG_T5793)
|
|||
|
&& tlv->ti.class == CLASS_CONTEXT && tlv->ti.tag == 0))
|
|||
|
&& tlv->ti.is_constructed && cram_octet_string (tlv, 1))
|
|||
|
{
|
|||
|
if (tlv->verbosity > 1)
|
|||
|
log_debug ("%s: cramming %s\n", __func__,
|
|||
|
tlv->ti.tag? "constructed octet strings":"for Mozilla bug");
|
|||
|
if (!cram_octet_string (tlv, 0))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_BAD_BER));
|
|||
|
}
|
|||
|
|
|||
|
/* If there is an end tag in an ndef container pop the stack. Also
|
|||
|
* pop other containers which are fully consumed. */
|
|||
|
if (tlv->in_ndef && (tlv->ti.class == CLASS_UNIVERSAL
|
|||
|
&& !tlv->ti.tag && !tlv->ti.is_constructed))
|
|||
|
{
|
|||
|
do
|
|||
|
err = _tlv_pop (tlv);
|
|||
|
while (!err && !tlv->in_ndef && !tlv->bufsize);
|
|||
|
if (err)
|
|||
|
return (tlv->lasterr = err);
|
|||
|
if (tlv->verbosity > 1)
|
|||
|
log_debug ("%s: container(s) closed due to end tag (lvl=%d)\n",
|
|||
|
__func__, tlv->stacklen);
|
|||
|
goto again;
|
|||
|
}
|
|||
|
|
|||
|
_tlv_parser_dump_tag (__func__, lno, tlv);
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Return the current neting level of the TLV object. */
|
|||
|
unsigned int
|
|||
|
tlv_parser_level (tlv_parser_t tlv)
|
|||
|
{
|
|||
|
return tlv? tlv->stacklen : 0;
|
|||
|
}
|
|||
|
|
|||
|
/* Returns the current offset of the parser. */
|
|||
|
size_t
|
|||
|
tlv_parser_offset (tlv_parser_t tlv)
|
|||
|
{
|
|||
|
return tlv? (size_t)(tlv->buffer - tlv->origbuffer) : 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Return a string with the last function used. If TLV is NULL an
|
|||
|
* empty string is returned. */
|
|||
|
const char *
|
|||
|
tlv_parser_lastfunc (tlv_parser_t tlv)
|
|||
|
{
|
|||
|
return tlv? tlv->lastfunc:"";
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
const char *
|
|||
|
tlv_parser_lasterrstr (tlv_parser_t tlv)
|
|||
|
{
|
|||
|
return tlv? gpg_strerror (tlv->lasterr) : "tlv parser not yet initialized";
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Set a flag to indicate that the last tlv_next has not yet been
|
|||
|
* consumed. */
|
|||
|
void
|
|||
|
tlv_parser_set_pending (tlv_parser_t tlv)
|
|||
|
{
|
|||
|
tlv->pending = 1;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Return the length of the last read tag. If with_header is 1 the
|
|||
|
* lengtb of the header is added to the returned length. */
|
|||
|
size_t
|
|||
|
tlv_parser_tag_length (tlv_parser_t tlv, int with_header)
|
|||
|
{
|
|||
|
if (with_header)
|
|||
|
return tlv->ti.length + tlv->ti.nhdr;
|
|||
|
else
|
|||
|
return tlv->ti.length;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Skip over the value of the current tag. Does not yet work for ndef
|
|||
|
* containers. */
|
|||
|
void
|
|||
|
tlv_parser_skip (tlv_parser_t tlv)
|
|||
|
{
|
|||
|
tlv->lastfunc = __func__;
|
|||
|
log_assert (tlv->bufsize >= tlv->ti.length);
|
|||
|
tlv->buffer += tlv->ti.length;
|
|||
|
tlv->bufsize -= tlv->ti.length;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Expect that the current tag is a sequence and setup the context for
|
|||
|
* processing. */
|
|||
|
gpg_error_t
|
|||
|
tlv_expect_sequence (tlv_parser_t tlv)
|
|||
|
{
|
|||
|
tlv->lastfunc = __func__;
|
|||
|
if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_SEQUENCE
|
|||
|
&& tlv->ti.is_constructed))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
|
|||
|
return _tlv_push (tlv);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Expect that the current tag is a context tag and setup the context
|
|||
|
* for processing. The tag of the context is returned at R_TAG. */
|
|||
|
gpg_error_t
|
|||
|
tlv_expect_context_tag (tlv_parser_t tlv, int *r_tag)
|
|||
|
{
|
|||
|
tlv->lastfunc = __func__;
|
|||
|
if (!(tlv->ti.class == CLASS_CONTEXT && tlv->ti.is_constructed))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
|
|||
|
*r_tag = tlv->ti.tag;
|
|||
|
return _tlv_push (tlv);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Expect that the current tag is a SET and setup the context for
|
|||
|
* processing. */
|
|||
|
gpg_error_t
|
|||
|
tlv_expect_set (tlv_parser_t tlv)
|
|||
|
{
|
|||
|
tlv->lastfunc = __func__;
|
|||
|
if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_SET
|
|||
|
&& tlv->ti.is_constructed))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
|
|||
|
return _tlv_push (tlv);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Expect an object of CLASS with TAG and store its value at
|
|||
|
* (R_DATA,R_DATALEN). Then skip over its value to the next tag.
|
|||
|
* Note that the stored value is not allocated but points into
|
|||
|
* TLV. */
|
|||
|
gpg_error_t
|
|||
|
tlv_expect_object (tlv_parser_t tlv, int class, int tag,
|
|||
|
unsigned char const **r_data, size_t *r_datalen)
|
|||
|
{
|
|||
|
const unsigned char *p;
|
|||
|
size_t n;
|
|||
|
int needpush = 0;
|
|||
|
|
|||
|
tlv->lastfunc = __func__;
|
|||
|
/* Note that the parser has already crammed the octet strings for a
|
|||
|
* [0] to workaround the Mozilla bug. */
|
|||
|
if (!(tlv->ti.class == class && tlv->ti.tag == tag))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
|
|||
|
p = tlv->buffer;
|
|||
|
n = tlv->ti.length;
|
|||
|
if (!n && tlv->ti.ndef)
|
|||
|
{
|
|||
|
n = tlv->bufsize;
|
|||
|
needpush = 1;
|
|||
|
}
|
|||
|
else if (!tlv->ti.length)
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
|
|||
|
|
|||
|
if (tlv->verbosity > 1)
|
|||
|
log_debug ("%s: %zu@%zu %c len=%zu (%zu) bufsize=%zu of %zu\n",
|
|||
|
__func__,
|
|||
|
tlv->origoff, tlv_parser_offset (tlv),
|
|||
|
tlv->ti.is_constructed? 'c':'p',
|
|||
|
n, tlv->crammed,
|
|||
|
tlv->bufsize, tlv->origbufsize);
|
|||
|
|
|||
|
if (r_data)
|
|||
|
*r_data = p;
|
|||
|
if (r_datalen)
|
|||
|
*r_datalen = tlv->crammed? tlv->crammed : n;
|
|||
|
|
|||
|
if (needpush)
|
|||
|
return _tlv_push (tlv);
|
|||
|
|
|||
|
if (!(tlv->bufsize >= n))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
|
|||
|
tlv->buffer += n;
|
|||
|
tlv->bufsize -= n;
|
|||
|
tlv->crammed = 0;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Expect that the current tag is an object string and store its value
|
|||
|
* at (R_DATA,R_DATALEN). Then skip over its value to the next tag.
|
|||
|
* Note that the stored value are not allocated but point into TLV. */
|
|||
|
gpg_error_t
|
|||
|
tlv_expect_octet_string (tlv_parser_t tlv,
|
|||
|
unsigned char const **r_data, size_t *r_datalen)
|
|||
|
{
|
|||
|
size_t n;
|
|||
|
|
|||
|
tlv->lastfunc = __func__;
|
|||
|
/* The parser has already crammed constructed octet strings. */
|
|||
|
if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OCTET_STRING))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
|
|||
|
if (!(n=tlv->ti.length) || tlv->ti.ndef )
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
|
|||
|
|
|||
|
if (tlv->verbosity > 1)
|
|||
|
log_debug ("%s: %zu@%zu %c len=%zu (%zu) bufsize=%zu of %zu\n",
|
|||
|
__func__,
|
|||
|
tlv->origoff, tlv_parser_offset (tlv),
|
|||
|
tlv->ti.is_constructed? 'c':'p',
|
|||
|
n, tlv->crammed,
|
|||
|
tlv->bufsize, tlv->origbufsize);
|
|||
|
|
|||
|
if (r_data)
|
|||
|
*r_data = tlv->buffer;
|
|||
|
if (r_datalen)
|
|||
|
*r_datalen = tlv->crammed? tlv->crammed : tlv->ti.length;
|
|||
|
|
|||
|
if (!(tlv->bufsize >= tlv->ti.length))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
|
|||
|
tlv->buffer += tlv->ti.length;
|
|||
|
tlv->bufsize -= tlv->ti.length;
|
|||
|
tlv->crammed = 0;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Expect that the current tag is an integer and return its value at
|
|||
|
* R_VALUE. Then skip over its value to the next tag. */
|
|||
|
gpg_error_t
|
|||
|
tlv_expect_integer (tlv_parser_t tlv, int *r_value)
|
|||
|
{
|
|||
|
const unsigned char *p;
|
|||
|
size_t n;
|
|||
|
int value;
|
|||
|
|
|||
|
tlv->lastfunc = __func__;
|
|||
|
if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_INTEGER
|
|||
|
&& !tlv->ti.is_constructed))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
|
|||
|
p = tlv->buffer;
|
|||
|
if (!(n=tlv->ti.length))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
|
|||
|
|
|||
|
/* We currently support only positive values. */
|
|||
|
if ((*p & 0x80))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_ERANGE));
|
|||
|
|
|||
|
for (value = 0; n; n--)
|
|||
|
{
|
|||
|
value <<= 8;
|
|||
|
value |= (*p++) & 0xff;
|
|||
|
if (value < 0)
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_EOVERFLOW));
|
|||
|
}
|
|||
|
*r_value = value;
|
|||
|
if (!(tlv->bufsize >= tlv->ti.length))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
|
|||
|
tlv->buffer += tlv->ti.length;
|
|||
|
tlv->bufsize -= tlv->ti.length;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Variant of tlv_expect_integer which returns an MPI. If IGNORE_ZERO
|
|||
|
* is set a value of 0 is ignored and R_VALUE not changed and the
|
|||
|
* function returns GPG_ERR_FALSE. No check for negative encoded
|
|||
|
* integers is done because the old code here worked the same and we
|
|||
|
* can't foreclose invalid encoded PKCS#12 stuff - after all it is
|
|||
|
* PKCS#12 see https://www.cs.auckland.ac.nz/~pgut001/pubs/pfx.html */
|
|||
|
#ifdef GCRYPT_VERSION
|
|||
|
gpg_error_t
|
|||
|
tlv_expect_mpinteger (tlv_parser_t tlv, int ignore_zero,
|
|||
|
gcry_mpi_t *r_value)
|
|||
|
{
|
|||
|
const unsigned char *p;
|
|||
|
size_t n;
|
|||
|
|
|||
|
tlv->lastfunc = __func__;
|
|||
|
if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_INTEGER
|
|||
|
&& !tlv->ti.is_constructed))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
|
|||
|
p = tlv->buffer;
|
|||
|
if (!(n=tlv->ti.length))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
|
|||
|
|
|||
|
if (!(tlv->bufsize >= tlv->ti.length))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
|
|||
|
tlv->buffer += tlv->ti.length;
|
|||
|
tlv->bufsize -= tlv->ti.length;
|
|||
|
if (ignore_zero && n == 1 && !*p)
|
|||
|
return gpg_error (GPG_ERR_FALSE);
|
|||
|
|
|||
|
return gcry_mpi_scan (r_value, GCRYMPI_FMT_USG, p, n, NULL);
|
|||
|
}
|
|||
|
#endif /*GCRYPT_VERSION*/
|
|||
|
|
|||
|
|
|||
|
/* Expect that the current tag is an object id and store its value at
|
|||
|
* (R_OID,R_OIDLEN). Then skip over its value to the next tag. Note
|
|||
|
* that the stored value is not allocated but points into TLV. */
|
|||
|
gpg_error_t
|
|||
|
tlv_expect_object_id (tlv_parser_t tlv,
|
|||
|
unsigned char const **r_oid, size_t *r_oidlen)
|
|||
|
{
|
|||
|
const unsigned char *p;
|
|||
|
size_t n;
|
|||
|
|
|||
|
tlv->lastfunc = __func__;
|
|||
|
if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OBJECT_ID
|
|||
|
&& !tlv->ti.is_constructed))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
|
|||
|
p = tlv->buffer;
|
|||
|
if (!(n=tlv->ti.length))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
|
|||
|
|
|||
|
*r_oid = p;
|
|||
|
*r_oidlen = tlv->ti.length;
|
|||
|
if (!(tlv->bufsize >= tlv->ti.length))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
|
|||
|
tlv->buffer += tlv->ti.length;
|
|||
|
tlv->bufsize -= tlv->ti.length;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Expect a NULL tag. */
|
|||
|
gpg_error_t
|
|||
|
tlv_expect_null (tlv_parser_t tlv)
|
|||
|
{
|
|||
|
tlv->lastfunc = __func__;
|
|||
|
if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_NULL
|
|||
|
&& !tlv->ti.is_constructed && !tlv->ti.length))
|
|||
|
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Given a BER encoded constructed octet string like the example below
|
|||
|
* from Mozilla Firefox 1.0.4 (which actually exports certs as single
|
|||
|
* byte chunks of octet strings) in the buffer described by TLV.
|
|||
|
* Although the example uses an ndef for the length of the constructed
|
|||
|
* octet string, a fixed length is also allowed.
|
|||
|
*
|
|||
|
* 24 NDEF: OCTET STRING
|
|||
|
* 04 1: OCTET STRING -- TLV->buffer points to here
|
|||
|
* : 30
|
|||
|
* 04 1: OCTET STRING
|
|||
|
* : 80
|
|||
|
* [...]
|
|||
|
* 04 2: OCTET STRING
|
|||
|
* : 00 00
|
|||
|
* : } -- This denotes a Null tag and are the last
|
|||
|
* -- two bytes in INPUT.
|
|||
|
*
|
|||
|
* Turn it into a primitive octet string of this form:
|
|||
|
*
|
|||
|
* 24 2: OCTET STRING
|
|||
|
* : 30 80
|
|||
|
*
|
|||
|
* and fill it up with FE to the original length. Unless TESTMODE is
|
|||
|
* true the TLV object including the the member and the data is
|
|||
|
* adjusted accordingly; however the intiial tag is not changed (in
|
|||
|
* the example the "24 NDEF") because this is not needed anymore.
|
|||
|
*
|
|||
|
* On error 0 is returned and in this case the buffer might have
|
|||
|
* already been modified and thus the caller should better stop
|
|||
|
* parsing - unless TESTMODE was used. */
|
|||
|
static size_t
|
|||
|
cram_octet_string (tlv_parser_t tlv, int testmode)
|
|||
|
{
|
|||
|
gpg_error_t err;
|
|||
|
size_t totallen; /* Length of the non-crammed octet strings. */
|
|||
|
size_t crammedlen; /* Length of the crammed octet strings. */
|
|||
|
const unsigned char *s, *save_s;
|
|||
|
unsigned char *d;
|
|||
|
size_t n, save_n;
|
|||
|
struct tag_info ti;
|
|||
|
|
|||
|
if (tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OCTET_STRING)
|
|||
|
; /* Okay. */
|
|||
|
else if (tlv->ti.class == CLASS_CONTEXT && tlv->ti.tag == 0)
|
|||
|
; /* Workaround for Mozilla bug; see T5793 */
|
|||
|
else
|
|||
|
return 0; /* Oops - we should not have been called. */
|
|||
|
if (!tlv->ti.is_constructed)
|
|||
|
return 0; /* Oops - Not a constructed octet string. */
|
|||
|
if (!tlv->ti.ndef && tlv->ti.length < 4)
|
|||
|
return 0; /* Fixed length but too short. */
|
|||
|
|
|||
|
/* Let S point to the first octet string chunk. */
|
|||
|
s = tlv->buffer;
|
|||
|
n = tlv->ti.ndef? tlv->bufsize : tlv->ti.length;
|
|||
|
|
|||
|
d = (unsigned char *)s;
|
|||
|
totallen = crammedlen = 0;
|
|||
|
while (n)
|
|||
|
{
|
|||
|
save_s = s;
|
|||
|
save_n = n;
|
|||
|
if ((err=parse_tag (&s, &n, &ti)))
|
|||
|
{
|
|||
|
if (tlv->verbosity > 1)
|
|||
|
{
|
|||
|
log_debug ("%s: parse_tag(n=%zu) failed : %s\n",
|
|||
|
__func__, save_n, gpg_strerror (err));
|
|||
|
log_printhex (save_s, save_n > 40? 40:save_n, "%s: data was",
|
|||
|
__func__);
|
|||
|
}
|
|||
|
return 0;
|
|||
|
}
|
|||
|
if (tlv->verbosity > 1)
|
|||
|
log_debug ("%s:%s ti.ndef=%d ti.tag=%lu ti.length=%zu (n %zu->%zu)\n",
|
|||
|
__func__, testmode?"test:":"",
|
|||
|
ti.ndef, ti.tag, ti.length, save_n, n);
|
|||
|
if (ti.class == CLASS_UNIVERSAL && ti.tag == TAG_OCTET_STRING
|
|||
|
&& !ti.ndef && !ti.is_constructed)
|
|||
|
{
|
|||
|
if (!testmode)
|
|||
|
memmove (d, s, ti.length);
|
|||
|
d += ti.length; /* Update destination */
|
|||
|
totallen += ti.length + ti.nhdr;
|
|||
|
crammedlen += ti.length;
|
|||
|
s += ti.length; /* Skip to next tag. */
|
|||
|
n -= ti.length;
|
|||
|
}
|
|||
|
else if (ti.class == CLASS_UNIVERSAL && !ti.tag && !ti.is_constructed)
|
|||
|
{
|
|||
|
totallen += ti.nhdr;
|
|||
|
break; /* EOC - Ready */
|
|||
|
}
|
|||
|
else
|
|||
|
return 0; /* Invalid data. */
|
|||
|
}
|
|||
|
if (!testmode)
|
|||
|
{
|
|||
|
memset (d, '\xfe', totallen - crammedlen);
|
|||
|
tlv->ti.length = totallen;
|
|||
|
tlv->ti.is_constructed = 0;
|
|||
|
tlv->ti.ndef = 0;
|
|||
|
tlv->crammed = crammedlen;
|
|||
|
if (tlv->verbosity > 1)
|
|||
|
{
|
|||
|
log_debug ("%s: crammed length is %zu\n", __func__, crammedlen);
|
|||
|
log_debug ("%s: total length is %zu\n", __func__, totallen);
|
|||
|
}
|
|||
|
dump_to_file (tlv->buffer, totallen, "crammed");
|
|||
|
}
|
|||
|
return totallen;
|
|||
|
}
|