diff --git a/src/der-builder.c b/src/der-builder.c
index c6c0e39..b345e35 100644
--- a/src/der-builder.c
+++ b/src/der-builder.c
@@ -1,625 +1,627 @@
/* der-builder.c - Straightforward DER object builder
* Copyright (C) 2020 g10 Code GmbH
*
* This file is part of KSBA.
*
* 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
*/
/* This is a new way in KSBA to build DER objects without the need and
* overhead of using an ASN.1 module. It further avoids a lot of error
* checking because the error checking is delayed to the last call.
*
* For an example on how to use it see cms.c
*/
#include
#include
#include
#include
#include
#include "util.h"
#include "asn1-constants.h"
#include "convert.h"
#include "ber-help.h"
#include "der-builder.h"
struct item_s
{
short int class;
short int tag;
unsigned int hdrlen:4; /* Computed size of tag+length field. */
unsigned int is_constructed:1; /* This is a constructed element. */
unsigned int verbatim:1; /* Copy the value verbatim. */
unsigned int is_stop:1; /* This is a STOP item. */
const void *value;
size_t valuelen;
char *buffer; /* Malloced space or NULL. */
};
/* Our DER context object; it may eventually be extended to also
* feature a parser. */
struct ksba_der_s
{
gpg_error_t error; /* Last error. */
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. */
unsigned int finished:1;/* The object has been constructed. */
};
/* Release a DER object. */
void
_ksba_der_release (ksba_der_t d)
{
int idx;
if (!d)
return;
for (idx=0; idx < d->nitems; idx++)
xfree (d->items[idx].buffer);
xfree (d->items);
xfree (d);
}
/* Allocate a new DER builder instance. Returns NULL on error.
* NITEMS can be used to tell the number of DER items needed so to
* reduce the number of automatic reallocations. */
ksba_der_t
_ksba_der_builder_new (unsigned int nitems)
{
ksba_der_t d;
d = xtrycalloc (1, sizeof *d);
if (!d)
return NULL;
if (nitems)
{
d->nallocateditems = nitems;
d->items = xtrycalloc (d->nallocateditems, sizeof *d->items);
if (!d->items)
{
xfree (d);
return NULL;
}
}
return d;
}
/* Reset a DER build context so that a new sequence can be build. */
void
_ksba_der_builder_reset (ksba_der_t d)
{
int idx;
if (!d)
return; /* Oops. */
for (idx=0; idx < d->nitems; idx++)
{
if (d->items[idx].buffer)
{
xfree (d->items[idx].buffer);
d->items[idx].buffer = NULL;
}
d->items[idx].hdrlen = 0;
d->items[idx].is_constructed = 0;
d->items[idx].verbatim = 0;
d->items[idx].is_stop = 0;
d->items[idx].value = NULL;
}
d->nitems = 0;
d->finished = 0;
d->error = 0;
}
/* Make sure the array of items is large enough for one new item.
* Records any error in D and returns true in that case. True is also
* returned if D is in finished state. */
static int
ensure_space (ksba_der_t d)
{
struct item_s *newitems;
if (!d || d->error || d->finished)
return 1;
if (d->nitems == d->nallocateditems)
{
d->nallocateditems += 32;
newitems = _ksba_reallocarray (d->items, d->nitems,
d->nallocateditems, sizeof *newitems);
if (!newitems)
d->error = gpg_error_from_syserror ();
else
d->items = newitems;
}
return !!d->error;
}
/* Add a new primitive element to the builder instance D. The element
* is described by CLASS, TAG, VALUE, and VALUELEN. 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 D exists. For a TYPE_NULL tag no value is
* expected. Errors are not returned but recorded for later
* retrieval. */
void
_ksba_der_add_ptr (ksba_der_t d, int class, int tag,
void *value, size_t valuelen)
{
if (ensure_space (d))
return;
d->items[d->nitems].class = class;
d->items[d->nitems].tag = tag;
d->items[d->nitems].value = value;
d->items[d->nitems].valuelen = valuelen;
d->nitems++;
}
/* This is a low level function which assumes that D has been
* validated, VALUE is not NULL and enough space for a new item is
* available. It takes ownership of VALUE. VERBATIM is usually
* passed as false */
static void
add_val_core (ksba_der_t d, int class, int tag, void *value, size_t valuelen,
int verbatim)
{
d->items[d->nitems].buffer = value;
d->items[d->nitems].class = class;
d->items[d->nitems].tag = tag;
d->items[d->nitems].value = value;
d->items[d->nitems].valuelen = valuelen;
d->items[d->nitems].verbatim = !!verbatim;
d->nitems++;
}
/* This is the same as ksba_der_add_ptr but it takes a copy of the
* value and thus the caller does not need to care about keeping the
* value. */
void
_ksba_der_add_val (ksba_der_t d, int class, int tag,
const void *value, size_t valuelen)
{
void *p;
if (ensure_space (d))
return;
if (!value || !valuelen)
{
d->error = gpg_error (GPG_ERR_INV_VALUE);
return;
}
p = xtrymalloc (valuelen);
if (!p)
{
d->error = gpg_error_from_syserror ();
return;
}
memcpy (p, value, valuelen);
add_val_core (d, class, tag, p, valuelen, 0);
}
/* Add an OBJECT ID element to D. The OID is given in decimal dotted
* format as OIDSTR. */
void
_ksba_der_add_oid (ksba_der_t d, const char *oidstr)
{
gpg_error_t err;
unsigned char *buf;
size_t len;
if (ensure_space (d))
return;
err = ksba_oid_from_str (oidstr, &buf, &len);
if (err)
d->error = err;
else
add_val_core (d, 0, TYPE_OBJECT_ID, buf, len, 0);
}
/* Add a BIT STRING to D. Using a separate function allows to easily
* pass the number of unused bits. */
void
_ksba_der_add_bts (ksba_der_t d, const void *value, size_t valuelen,
unsigned int unusedbits)
{
unsigned char *p;
if (ensure_space (d))
return;
if (!value || !valuelen || unusedbits > 7)
{
d->error = gpg_error (GPG_ERR_INV_VALUE);
return;
}
p = xtrymalloc (1+valuelen);
if (!p)
{
d->error = gpg_error_from_syserror ();
return;
}
p[0] = unusedbits;
memcpy (p+1, value, valuelen);
add_val_core (d, 0, TYPE_BIT_STRING, p, 1+valuelen, 0);
}
/* Add (VALUE, VALUELEN) as an INTEGER to D. If FORCE_POSITIVE iset
* set a 0 or positive number is stored regardless of what is in
* (VALUE, VALUELEN). */
void
_ksba_der_add_int (ksba_der_t d, const void *value, size_t valuelen,
int force_positive)
{
unsigned char *p;
int need_extra;
if (ensure_space (d))
return;
if (!value || !valuelen)
need_extra = 1; /* Assume the integer value 0 was meant. */
else
need_extra = (force_positive && (*(const unsigned char*)value & 0x80));
p = xtrymalloc (need_extra+valuelen);
if (!p)
{
d->error = gpg_error_from_syserror ();
return;
}
if (need_extra)
p[0] = 0;
if (valuelen)
memcpy (p+need_extra, value, valuelen);
add_val_core (d, 0, TYPE_INTEGER, p, need_extra+valuelen, 0);
}
/* This function allows to add a pre-constructed DER object to the
* builder. It should be a valid DER object but its values is not
* further checked and copied verbatim to the final DER object
* constructed for the handle D. */
void
_ksba_der_add_der (ksba_der_t d, const void *der, size_t derlen)
{
void *p;
if (ensure_space (d))
return;
if (!der || !derlen)
{
d->error = gpg_error (GPG_ERR_INV_VALUE);
return;
}
p = xtrymalloc (derlen);
if (!p)
{
d->error = gpg_error_from_syserror ();
return;
}
memcpy (p, der, derlen);
add_val_core (d, 0, 0, p, derlen, 1);
}
/* Add a new constructed object to the builder instance D. 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 using 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
_ksba_der_add_tag (ksba_der_t d, int class, int tag)
{
if (ensure_space (d))
return;
d->items[d->nitems].class = class;
d->items[d->nitems].tag = tag;
d->items[d->nitems].is_constructed = 1;
d->nitems++;
}
/* A call to this function closes a constructed element. This must be
* called even for an empty constructed element. */
void
_ksba_der_add_end (ksba_der_t d)
{
if (ensure_space (d))
return;
d->items[d->nitems].is_stop = 1;
d->nitems++;
}
/* Return the length of the TL header of a to be constructed TLV.
* LENGTH gives the length of the value, if it is 0 indefinite length
* is assumed. LENGTH is ignored for the NULL tag. TAG must be less
* than 0x1f. On error 0 is returned. Note that this function is
* similar to _ksba_ber_count_tl but we want our own copy here. Note
* that the returned length is always less than 16 and can thus be
* storred in a few bits. */
static unsigned int
count_tl (int class, int tag, size_t length)
{
unsigned int hdrlen = 0;
int i;
if (tag < 0x1f)
hdrlen++;
else
return 0;
if (!tag && !class)
hdrlen++; /* end tag */
else if (tag == TYPE_NULL && !class)
hdrlen++; /* NULL tag */
else if (!length)
hdrlen++; /* indefinite length */
else if (length < 128)
hdrlen++;
else
{
i = (length <= 0xff ? 1:
length <= 0xffff ? 2:
length <= 0xffffff ? 3: 4);
hdrlen++;
if (i > 3)
hdrlen++;
if (i > 2)
hdrlen++;
if (i > 1)
hdrlen++;
hdrlen++;
}
return hdrlen;
}
/* Write TAG of CLASS to BUFFER. 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. The caller must
* make sure that the written TL field does not overflow the
* buffer. */
static void
write_tl (unsigned char *buffer, int class, int tag,
int constructed, size_t length)
{
int i;
if (tag < 0x1f)
{
*buffer = (class << 6) | tag;
if (constructed)
*buffer |= 0x20;
buffer++;
}
else
{
assert (!"oops");
}
if (!tag && !class)
*buffer++ = 0; /* end tag */
else if (tag == TYPE_NULL && !class)
*buffer++ = 0; /* NULL tag */
else if (!length)
*buffer++ = 0x80; /* indefinite length */
else if (length < 128)
*buffer++ = 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);
*buffer++ = (0x80 | i);
if (i > 3)
*buffer++ = length >> 24;
if (i > 2)
*buffer++ = length >> 16;
if (i > 1)
*buffer++ = length >> 8;
*buffer++ = length;
}
}
/* Compute and set the length of all constructed elements in the item
* array of D starting at IDX up to the corresponding stop item. On
* error d->error is set. */
static size_t
compute_lengths (ksba_der_t d, int idx)
{
size_t total = 0;
if (d->error)
return 0;
for (; idx < d->nitems; idx++)
{
if (d->items[idx].is_stop)
{
d->laststop = idx;
break;
}
if (d->items[idx].verbatim)
{
total += d->items[idx].valuelen;
continue;
}
if (d->items[idx].is_constructed)
{
d->items[idx].valuelen = compute_lengths (d, idx+1);
if (d->error)
return 0;
/* Note: The last processed IDX is stored at d->LASTSTOP. */
}
d->items[idx].hdrlen = count_tl (d->items[idx].class,
d->items[idx].tag,
d->items[idx].valuelen);
if (!d->items[idx].hdrlen)
{
if (d->error)
d->error = gpg_error (GPG_ERR_ENCODING_PROBLEM);
return 0; /* Error. */
}
total += d->items[idx].hdrlen + d->items[idx].valuelen;
if (d->items[idx].is_constructed)
idx = d->laststop;
}
return total;
}
/* Return the constructed DER object at D. 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. Further the number of successful calls prior to
* the error are stored at R_OBJLEN. Note than an error may stem from
* any of the previous call made to this object or from constructing
* the DER object. If this function is called with NULL for R_OBJ
* only the current error state is returned and no further processing
* is done. This can be used to figure which of the add calls induced
* the error.
*/
gpg_error_t
_ksba_der_builder_get (ksba_der_t d, unsigned char **r_obj, size_t *r_objlen)
{
gpg_error_t err;
int idx;
unsigned char *buffer = NULL;
unsigned char *p;
size_t bufsize, buflen;
*r_obj = NULL;
*r_objlen = 0;
if (!d)
return gpg_error (GPG_ERR_INV_ARG);
if (d->error)
{
err = d->error;
if (r_objlen)
*r_objlen = d->nitems;
goto leave;
}
if (!r_obj)
return 0;
if (!d->finished)
{
- if (!d->nitems || !d->items[d->nitems-1].is_stop)
+ if (d->nitems == 1)
+ ; /* Single item does not need an end tag. */
+ else if (!d->nitems || !d->items[d->nitems-1].is_stop)
{
err = gpg_error (GPG_ERR_NO_OBJ);
goto leave;
}
compute_lengths (d, 0);
err = d->error;
if (err)
goto leave;
d->finished = 1;
}
/* If the first element is a primitive element we rightly assume no
* other elements follow. It is the user's duty to build a valid
* ASN.1 object. */
bufsize = d->items[0].hdrlen + d->items[0].valuelen;
/* for (idx=0; idx < d->nitems; idx++) */
/* gpgrt_log_debug ("DERB[%2d]: c=%d t=%2d %s p=%p h=%u l=%zu\n", */
/* idx, */
/* d->items[idx].class, */
/* d->items[idx].tag, */
/* d->items[idx].verbatim? "verbatim": */
/* d->items[idx].is_stop? "stop": */
/* d->items[idx].is_constructed? "cons":"prim", */
/* d->items[idx].value, */
/* d->items[idx].hdrlen, */
/* d->items[idx].valuelen); */
buffer = xtrymalloc (bufsize);
if (!buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
buflen = 0;
p = buffer;
for (idx=0; idx < d->nitems; idx++)
{
if (d->items[idx].is_stop)
continue;
if (!d->items[idx].verbatim)
{
if (buflen + d->items[idx].hdrlen > bufsize)
{
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
write_tl (p, d->items[idx].class, d->items[idx].tag,
d->items[idx].is_constructed, d->items[idx].valuelen);
p += d->items[idx].hdrlen;
buflen += d->items[idx].hdrlen;
}
if (d->items[idx].value)
{
if (buflen + d->items[idx].valuelen > bufsize)
{
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
memcpy (p, d->items[idx].value, d->items[idx].valuelen);
p += d->items[idx].valuelen;
buflen += d->items[idx].valuelen;
}
}
assert (buflen == bufsize);
*r_obj = buffer;
*r_objlen = buflen;
buffer = NULL;
leave:
xfree (buffer);
return err;
}