diff options
Diffstat (limited to 'common/tlv-builder.c')
-rw-r--r-- | common/tlv-builder.c | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/common/tlv-builder.c b/common/tlv-builder.c new file mode 100644 index 0000000..59e2691 --- /dev/null +++ b/common/tlv-builder.c @@ -0,0 +1,388 @@ +/* tlv-builder.c - Build DER encoded objects + * Copyright (C) 2020 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" + + +struct item_s +{ + int class; + int tag; + unsigned int is_constructed:1; /* This is a constructed element. */ + unsigned int is_stop:1; /* This is a STOP item. */ + const void *value; + size_t valuelen; + char *buffer; /* Malloced space or NULL. */ +}; + + +struct tlv_builder_s +{ + gpg_error_t error; /* Last error. */ + int use_secure; /* Use secure memory for the result. */ + size_t nallocateditems; /* Number of allocated items. */ + size_t nitems; /* Number of used items. */ + struct item_s *items; /* Array of items. */ + int laststop; /* Used as return value of compute_length. */ +}; + + +/* Allocate a new TLV Builder instance. Returns NULL on error. If + * SECURE is set the final object is stored in secure memory. */ +tlv_builder_t +tlv_builder_new (int secure) +{ + tlv_builder_t tb; + + tb = xtrycalloc (1, sizeof *tb); + if (tb && secure) + tb->use_secure = 1; + return tb; +} + + +/* Make sure the array of items is large enough for one new item. + * Records any error in TB and returns true in that case. */ +static int +ensure_space (tlv_builder_t tb) +{ + struct item_s *newitems; + + if (!tb || tb->error) + return 1; + + if (tb->nitems == tb->nallocateditems) + { + tb->nallocateditems += 32; + newitems = gpgrt_reallocarray (tb->items, tb->nitems, + tb->nallocateditems, sizeof *newitems); + if (!newitems) + tb->error = gpg_error_from_syserror (); + else + tb->items = newitems; + } + return !!tb->error; +} + + + +/* Add a new primitive element to the builder instance TB. The + * element is described by CLASS, TAG, VALUE, and VALUEEN. CLASS and + * TAG must describe a primitive element and (VALUE,VALUELEN) specify + * its value. The value is a pointer and its object must not be + * changed as long as the instance TB exists. For a TAG_NULL no vlaue + * is expected. Errors are not returned but recorded for later + * retrieval. */ +void +tlv_builder_add_ptr (tlv_builder_t tb, int class, int tag, + void *value, size_t valuelen) +{ + if (ensure_space (tb)) + return; + tb->items[tb->nitems].class = class; + tb->items[tb->nitems].tag = tag; + tb->items[tb->nitems].value = value; + tb->items[tb->nitems].valuelen = valuelen; + tb->nitems++; +} + + +/* This is the same as tlv_builder_add_ptr but it takes a copy of the + * value and thus the caller does not need to care about it. */ +void +tlv_builder_add_val (tlv_builder_t tb, int class, int tag, + const void *value, size_t valuelen) +{ + void *p; + + if (ensure_space (tb)) + return; + if (!value || !valuelen) + { + tb->error = gpg_error (GPG_ERR_INV_VALUE); + return; + } + p = tb->use_secure? xtrymalloc_secure (valuelen) : xtrymalloc (valuelen); + if (!p) + { + tb->error = gpg_error_from_syserror (); + return; + } + memcpy (p, value, valuelen); + tb->items[tb->nitems].buffer = p; + tb->items[tb->nitems].class = class; + tb->items[tb->nitems].tag = tag; + tb->items[tb->nitems].value = p; + tb->items[tb->nitems].valuelen = valuelen; + tb->nitems++; +} + + +/* Add a new constructed object to the builder instance TB. The + * object is described by CLASS and TAG which must describe a + * constructed object. The elements of the constructed objects are + * added with more call to the add functions. To close a constructed + * element a call to tlv_builer_add_end is required. Errors are not + * returned but recorded for later retrieval. */ +void +tlv_builder_add_tag (tlv_builder_t tb, int class, int tag) +{ + if (ensure_space (tb)) + return; + tb->items[tb->nitems].class = class; + tb->items[tb->nitems].tag = tag; + tb->items[tb->nitems].is_constructed = 1; + tb->nitems++; +} + + +/* A call to this function closes a constructed element. This must be + * called even for an empty constructed element. */ +void +tlv_builder_add_end (tlv_builder_t tb) +{ + if (ensure_space (tb)) + return; + tb->items[tb->nitems].is_stop = 1; + tb->nitems++; +} + + +/* Compute and set the length of all constructed elements in the item + * array of TB starting at IDX up to the corresponding stop item. On + * error tb->error is set. */ +static size_t +compute_lengths (tlv_builder_t tb, int idx) +{ + size_t total = 0; + + if (tb->error) + return 0; + + for (; idx < tb->nitems; idx++) + { + if (tb->items[idx].is_stop) + { + tb->laststop = idx; + break; + } + if (tb->items[idx].is_constructed) + { + tb->items[idx].valuelen = compute_lengths (tb, idx+1); + if (tb->error) + return 0; + /* Note: The last processed IDX is stored at tb->LASTSTOP. */ + } + total += get_tlv_length (tb->items[idx].class, tb->items[idx].tag, + tb->items[idx].is_constructed, + tb->items[idx].valuelen); + if (tb->items[idx].is_constructed) + idx = tb->laststop; + } + return total; +} + + +/* Return the constructed DER encoding and release this instance. On + * success the object is stored at R_OBJ and its length at R_OBJLEN. + * The caller needs to release that memory. On error NULL is stored + * at R_OBJ and an error code is returned. Note than an error may + * stem from any of the previous call made to this object or from + * constructing the the DER object. */ +gpg_error_t +tlv_builder_finalize (tlv_builder_t tb, void **r_obj, size_t *r_objlen) +{ + gpg_error_t err; + membuf_t mb; + int mb_initialized = 0; + int idx; + + *r_obj = NULL; + *r_objlen = 0; + + if (!tb) + return gpg_error (GPG_ERR_INTERNAL); + if (tb->error) + { + err = tb->error; + goto leave; + } + if (!tb->nitems || !tb->items[tb->nitems-1].is_stop) + { + err = gpg_error (GPG_ERR_NO_OBJ); + goto leave; + } + + compute_lengths (tb, 0); + err = tb->error; + if (err) + goto leave; + + /* for (idx=0; idx < tb->nitems; idx++) */ + /* log_debug ("TLVB[%2d]: c=%d t=%2d %s p=%p l=%zu\n", */ + /* idx, */ + /* tb->items[idx].class, */ + /* tb->items[idx].tag, */ + /* tb->items[idx].is_stop? "stop": */ + /* tb->items[idx].is_constructed? "cons":"prim", */ + /* tb->items[idx].value, */ + /* tb->items[idx].valuelen); */ + + if (tb->use_secure) + init_membuf_secure (&mb, 512); + else + init_membuf (&mb, 512); + mb_initialized = 1; + + for (idx=0; idx < tb->nitems; idx++) + { + if (tb->items[idx].is_stop) + continue; + put_tlv_to_membuf (&mb, tb->items[idx].class, tb->items[idx].tag, + tb->items[idx].is_constructed, + tb->items[idx].valuelen); + if (tb->items[idx].value) + put_membuf (&mb, tb->items[idx].value, tb->items[idx].valuelen); + } + + *r_obj = get_membuf (&mb, r_objlen); + if (!*r_obj) + err = gpg_error_from_syserror (); + mb_initialized = 0; + + leave: + if (mb_initialized) + xfree (get_membuf (&mb, NULL)); + for (idx=0; idx < tb->nitems; idx++) + xfree (tb->items[idx].buffer); + xfree (tb->items); + xfree (tb); + return err; +} + + +/* Write TAG of CLASS to MEMBUF. CONSTRUCTED is a flag telling + * whether the value is constructed. LENGTH gives the length of the + * value, if it is 0 undefinite length is assumed. LENGTH is ignored + * for the NULL tag. TAG must be less that 0x1f. */ +void +put_tlv_to_membuf (membuf_t *membuf, int class, int tag, + int constructed, size_t length) +{ + unsigned char buf[20]; + int buflen = 0; + int i; + + if (tag < 0x1f) + { + *buf = (class << 6) | tag; + if (constructed) + *buf |= 0x20; + buflen++; + } + else + BUG (); + + if (!tag && !class) + buf[buflen++] = 0; /* end tag */ + else if (tag == TAG_NULL && !class) + buf[buflen++] = 0; /* NULL tag */ + else if (!length) + buf[buflen++] = 0x80; /* indefinite length */ + else if (length < 128) + buf[buflen++] = length; + else + { + /* If we know the sizeof a size_t we could support larger + * objects - however this is pretty ridiculous */ + i = (length <= 0xff ? 1: + length <= 0xffff ? 2: + length <= 0xffffff ? 3: 4); + + buf[buflen++] = (0x80 | i); + if (i > 3) + buf[buflen++] = length >> 24; + if (i > 2) + buf[buflen++] = length >> 16; + if (i > 1) + buf[buflen++] = length >> 8; + buf[buflen++] = length; + } + + put_membuf (membuf, buf, buflen); +} + + +/* Return the length of the to be constructed TLV. CONSTRUCTED is a + * flag telling whether the value is constructed. LENGTH gives the + * length of the value, if it is 0 undefinite length is assumed. + * LENGTH is ignored for the NULL tag. TAG must be less that 0x1f. */ +size_t +get_tlv_length (int class, int tag, int constructed, size_t length) +{ + size_t buflen = 0; + int i; + + (void)constructed; /* Not used, but passed for uniformity of such calls. */ + + /* coverity[identical_branches] */ + if (tag < 0x1f) + { + buflen++; + } + else + { + buflen++; /* assume one and let the actual write function bail out */ + } + + if (!tag && !class) + buflen++; /* end tag */ + else if (tag == TAG_NULL && !class) + buflen++; /* NULL tag */ + else if (!length) + buflen++; /* indefinite length */ + else if (length < 128) + buflen++; + else + { + i = (length <= 0xff ? 1: + length <= 0xffff ? 2: + length <= 0xffffff ? 3: 4); + + buflen++; + if (i > 3) + buflen++; + if (i > 2) + buflen++; + if (i > 1) + buflen++; + buflen++; + } + + return buflen + length; +} |