From bc150995e44cc7552c693277abd9f858be47cc11 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 8 Aug 2024 14:20:44 +0200 Subject: [PATCH] common: Add missing file to repo -- Fixes-commit: 0dcd1504babb4b98898f7bd738d7aaa1e4a73a05 --- common/tlv-parser.c | 827 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 827 insertions(+) create mode 100644 common/tlv-parser.c diff --git a/common/tlv-parser.c b/common/tlv-parser.c new file mode 100644 index 000000000..b52cc7a21 --- /dev/null +++ b/common/tlv-parser.c @@ -0,0 +1,827 @@ +/* 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 . + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include + +#include +#include +#include +#include + +#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; +}