/* 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; }