From 5ea878274ef51c819368f021c69c518b9aef6f82 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 24 Apr 2020 13:14:05 +0200 Subject: [PATCH] common: Add an easy to use DER builder. * common/tlv-builder.c: New. * common/tlv.c: Remove stuff only used by GnuPG 1. (put_tlv_to_membuf, get_tlv_length): Move to ... * common/tlv-builder.c: here. * common/tlv.h (tlv_builder_t): New. -- Such code should actually go into libksba and we will eventually do that. However, for now it is easier to keep it here. Signed-off-by: Werner Koch --- common/Makefile.am | 2 +- common/tlv-builder.c | 387 +++++++++++++++++++++++++++++++++++++++++++ common/tlv.c | 113 +------------ common/tlv.h | 34 +++- 4 files changed, 416 insertions(+), 120 deletions(-) create mode 100644 common/tlv-builder.c diff --git a/common/Makefile.am b/common/Makefile.am index 094ebe6e2..31924317e 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -57,7 +57,7 @@ common_sources = \ gc-opt-flags.h \ keyserver.h \ sexp-parse.h \ - tlv.c tlv.h \ + tlv.c tlv.h tlv-builder.c \ init.c init.h \ sexputil.c \ sysutils.c sysutils.h \ diff --git a/common/tlv-builder.c b/common/tlv-builder.c new file mode 100644 index 000000000..3b644ca24 --- /dev/null +++ b/common/tlv-builder.c @@ -0,0 +1,387 @@ +/* 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 . + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include + +#include +#include +#include +#include + +#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. */ + + 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; +} diff --git a/common/tlv.c b/common/tlv.c index 86e954a19..947464bf7 100644 --- a/common/tlv.c +++ b/common/tlv.c @@ -32,21 +32,13 @@ #include #include #include -#include - -#if GNUPG_MAJOR_VERSION == 1 -#define GPG_ERR_EOF (-1) -#define GPG_ERR_BAD_BER (1) /*G10ERR_GENERAL*/ -#define GPG_ERR_INV_SEXP (45) /*G10ERR_INV_ARG*/ -typedef int gpg_error_t; -#define gpg_make_err(x,n) (n) -#else #include -#endif + #include "util.h" #include "tlv.h" + static const unsigned char * do_find_tlv (const unsigned char *buffer, size_t length, int tag, size_t *nbytes, int nestlevel) @@ -157,107 +149,6 @@ find_tlv_unchecked (const unsigned char *buffer, size_t length, } -/* 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. */ - - 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; -} - - /* ASN.1 BER parser: Parse BUFFER of length SIZE and return the tag and the length part from the TLV triplet. Update BUFFER and SIZE on success. */ diff --git a/common/tlv.h b/common/tlv.h index 0c915e63f..e371ca57e 100644 --- a/common/tlv.h +++ b/common/tlv.h @@ -72,6 +72,11 @@ enum tlv_tag_type { }; +struct tlv_builder_s; +typedef struct tlv_builder_s *tlv_builder_t; + +/*-- tlv.c --*/ + /* Locate a TLV encoded data object in BUFFER of LENGTH and return a pointer to value as well as its length in NBYTES. Return NULL if it was not found or if the object does not fit into the buffer. */ @@ -87,14 +92,6 @@ const unsigned char *find_tlv_unchecked (const unsigned char *buffer, size_t length, int tag, size_t *nbytes); -/* Wite a TLV header to MEMBUF. */ -void put_tlv_to_membuf (membuf_t *membuf, int class, int tag, - int constructed, size_t length); - -/* Count the length of a to be constructed TLV. */ -size_t get_tlv_length (int class, int tag, int constructed, size_t length); - - /* ASN.1 BER parser: Parse BUFFER of length SIZE and return the tag and the length part from the TLV triplet. Update BUFFER and SIZE on success. */ @@ -120,5 +117,26 @@ gpg_error_t parse_sexp (unsigned char const **buf, size_t *buflen, int *depth, unsigned char const **tok, size_t *toklen); +/*-- tlv-builder.c --*/ + +tlv_builder_t tlv_builder_new (int use_secure); +void tlv_builder_add_ptr (tlv_builder_t tb, int class, int tag, + void *value, size_t valuelen); +void tlv_builder_add_val (tlv_builder_t tb, int class, int tag, + const void *value, size_t valuelen); +void tlv_builder_add_tag (tlv_builder_t tb, int class, int tag); +void tlv_builder_add_end (tlv_builder_t tb); +gpg_error_t tlv_builder_finalize (tlv_builder_t tb, + void **r_obj, size_t *r_objlen); + +/* Wite a TLV header to MEMBUF. */ +void put_tlv_to_membuf (membuf_t *membuf, int class, int tag, + int constructed, size_t length); + +/* Count the length of a to be constructed TLV. */ +size_t get_tlv_length (int class, int tag, int constructed, size_t length); + + + #endif /* SCD_TLV_H */