diff options
Diffstat (limited to 'libder/libder_obj.c')
| -rw-r--r-- | libder/libder_obj.c | 1192 |
1 files changed, 1192 insertions, 0 deletions
diff --git a/libder/libder_obj.c b/libder/libder_obj.c new file mode 100644 index 000000000000..21d39e01fc17 --- /dev/null +++ b/libder/libder_obj.c @@ -0,0 +1,1192 @@ +/*- + * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "libder_private.h" + +#undef DER_CHILDREN +#undef DER_NEXT + +#define DER_CHILDREN(obj) ((obj)->children) +#define DER_NEXT(obj) ((obj)->next) + +static uint8_t * +libder_obj_alloc_copy_payload(struct libder_ctx *ctx, const uint8_t *payload_in, + size_t length) +{ + uint8_t *payload; + + if ((length == 0 && payload_in != NULL) || + (length != 0 && payload_in == NULL)) { + libder_set_error(ctx, LDE_INVAL); + return (NULL); + } + + if (length > 0) { + payload = malloc(length); + if (payload == NULL) { + libder_set_error(ctx, LDE_NOMEM); + return (NULL); + } + + memcpy(payload, payload_in, length); + } else { + payload = NULL; + } + + return (payload); +} + +static bool +libder_obj_alloc_check(struct libder_ctx *ctx, struct libder_tag *type, + const uint8_t *payload_in, size_t length) +{ + /* + * In addition to our normal constraints, constructed objects coming in + * from lib users should not have payloads. + */ + if (!libder_is_valid_obj(ctx, type, payload_in, length, false) || + (type->tag_constructed && length != 0)) { + libder_set_error(ctx, LDE_BADOBJECT); + return (false); + } + + return (true); +} + +struct libder_object * +libder_obj_alloc(struct libder_ctx *ctx, struct libder_tag *type, + const uint8_t *payload_in, size_t length) +{ + struct libder_object *obj; + uint8_t *payload; + + if (!libder_obj_alloc_check(ctx, type, payload_in, length)) + return (NULL); + + payload = libder_obj_alloc_copy_payload(ctx, payload_in, length); + + obj = libder_obj_alloc_internal(ctx, type, payload, length, 0); + if (obj == NULL) { + if (length != 0) { + libder_bzero(payload, length); + free(payload); + } + + libder_set_error(ctx, LDE_NOMEM); + } + + return (obj); +} + +struct libder_object * +libder_obj_alloc_simple(struct libder_ctx *ctx, uint8_t stype, + const uint8_t *payload_in, size_t length) +{ + struct libder_object *obj; + struct libder_tag *type; + uint8_t *payload; + + type = libder_type_alloc_simple(ctx, stype); + if (type == NULL) + return (NULL); + + if (!libder_obj_alloc_check(ctx, type, payload_in, length)) { + libder_type_free(type); + return (NULL); + } + + payload = libder_obj_alloc_copy_payload(ctx, payload_in, length); + + obj = libder_obj_alloc_internal(ctx, type, payload, length, LDO_OWNTAG); + if (obj == NULL) { + if (length != 0) { + libder_bzero(payload, length); + free(payload); + } + + libder_type_free(type); + libder_set_error(ctx, LDE_NOMEM); + } + + return (obj); +} + +/* + * Returns an obj on success, NULL if out of memory. `obj` takes ownership of + * the payload on success. + */ +LIBDER_PRIVATE struct libder_object * +libder_obj_alloc_internal(struct libder_ctx *ctx, struct libder_tag *type, + uint8_t *payload, size_t length, uint32_t flags) +{ + struct libder_object *obj; + + assert((flags & ~(LDO_OWNTAG)) == 0); + + if (length != 0) + assert(payload != NULL); + else + assert(payload == NULL); + + obj = malloc(sizeof(*obj)); + if (obj == NULL) + return (NULL); + + if ((flags & LDO_OWNTAG) != 0) { + obj->type = type; + } else { + /* + * Deep copies the tag data, so that the caller can predict what + * it can do with the buffer. + */ + obj->type = libder_type_dup(ctx, type); + if (obj->type == NULL) { + free(obj); + return (NULL); + } + } + + obj->length = length; + obj->payload = payload; + obj->children = obj->next = obj->parent = NULL; + obj->nchildren = 0; + + return (obj); +} + +LIBDER_PRIVATE size_t +libder_size_length(size_t sz) +{ + size_t nbytes; + + /* + * With DER, we use the smallest encoding necessary: less than 0x80 + * can be encoded in one byte. + */ + if (sz < 0x80) + return (1); + + /* + * We can support up to 0x7f size bytes, but we don't really have a way + * to represent that right now. It's a good thing this function only + * takes a size_t, otherwise this would be a bit wrong. + */ + for (nbytes = 1; nbytes < sizeof(size_t); nbytes++) { + if ((sz & ~((1ULL << 8 * nbytes) - 1)) == 0) + break; + } + + /* Add one for the lead byte describing the length of the length. */ + return (nbytes + 1); +} + +/* + * Returns the size on-disk. If an object has children, we encode the size as + * the sum of their lengths recursively. Otherwise, we use the object's size. + * + * Returns 0 if the object size would overflow a size_t... perhaps we could + * lift this restriction later. + * + * Note that the size of the object will be set/updated to simplify later write + * calculations. + */ +LIBDER_PRIVATE size_t +libder_obj_disk_size(struct libder_object *obj, bool include_header) +{ + struct libder_object *walker; + size_t disk_size, header_size; + + disk_size = obj->length; + if (obj->children != NULL) { + /* We should have rejected these. */ + assert(obj->length == 0); + + DER_FOREACH_CHILD(walker, obj) { + size_t child_size; + + child_size = libder_obj_disk_size(walker, true); + if (SIZE_MAX - child_size < disk_size) + return (0); /* Overflow */ + disk_size += child_size; + } + } + + obj->disk_size = disk_size; + + /* + * Children always include the header above, we only include the header + * at the root if we're calculating how much space we need in total. + */ + if (include_header) { + /* Size of the length + the tag (arbitrary length) */ + header_size = libder_size_length(disk_size) + obj->type->tag_size; + if (obj->type->tag_encoded) + header_size++; /* Lead byte */ + if (SIZE_MAX - header_size < disk_size) + return (0); + + disk_size += header_size; + } + + return (disk_size); +} + +void +libder_obj_free(struct libder_object *obj) +{ + struct libder_object *child, *tmp; + + if (obj == NULL) + return; + + DER_FOREACH_CHILD_SAFE(child, obj, tmp) + libder_obj_free(child); + + if (obj->payload != NULL) { + libder_bzero(obj->payload, obj->length); + free(obj->payload); + } + + libder_type_free(obj->type); + free(obj); +} + +static void +libder_obj_unlink(struct libder_object *obj) +{ + struct libder_object *child, *parent, *prev; + + parent = obj->parent; + if (parent == NULL) + return; + + prev = NULL; + assert(parent->nchildren > 0); + DER_FOREACH_CHILD(child, parent) { + if (child == obj) { + if (prev == NULL) + parent->children = child->next; + else + prev->next = child->next; + parent->nchildren--; + child->parent = NULL; + return; + } + + prev = child; + } + + assert(0 && "Internal inconsistency: parent set, but child not found"); +} + +bool +libder_obj_append(struct libder_object *parent, struct libder_object *child) +{ + struct libder_object *end, *walker; + + if (!parent->type->tag_constructed) + return (false); + + /* XXX Type check */ + + if (child->parent != NULL) + libder_obj_unlink(child); + + if (parent->nchildren == 0) { + parent->children = child; + parent->nchildren++; + return (true); + } + + /* Walk the chain */ + DER_FOREACH_CHILD(walker, parent) { + end = walker; + } + + assert(end != NULL); + end->next = child; + parent->nchildren++; + child->parent = parent; + return (true); +} + +struct libder_object * +libder_obj_child(const struct libder_object *obj, size_t idx) +{ + struct libder_object *cur; + + DER_FOREACH_CHILD(cur, obj) { + if (idx-- == 0) + return (cur); + } + + return (NULL); +} + +struct libder_object * +libder_obj_children(const struct libder_object *obj) +{ + + return (obj->children); +} + +struct libder_object * +libder_obj_next(const struct libder_object *obj) +{ + + return (obj->next); +} + +struct libder_tag * +libder_obj_type(const struct libder_object *obj) +{ + + return (obj->type); +} + +uint8_t +libder_obj_type_simple(const struct libder_object *obj) +{ + struct libder_tag *type = obj->type; + uint8_t simple = type->tag_class << 6; + + if (type->tag_constructed) + simple |= BER_TYPE_CONSTRUCTED_MASK; + + if (type->tag_encoded) + simple |= 0x1f; /* Encode the "long tag" tag. */ + else + simple |= type->tag_short; + return (simple); +} + +const uint8_t * +libder_obj_data(const struct libder_object *obj, size_t *osz) +{ + + if (obj->type->tag_constructed) + return (NULL); + + *osz = obj->length; + return (obj->payload); +} + +static const char * +libder_type_name(const struct libder_tag *type) +{ + static char namebuf[128]; + + if (type->tag_encoded) { + return ("{ ... }"); + } + + if (type->tag_class != BC_UNIVERSAL) + goto fallback; + +#define UTYPE(val) case val: return (&(#val)[3]) + switch (type->tag_short) { + UTYPE(BT_RESERVED); + UTYPE(BT_BOOLEAN); + UTYPE(BT_INTEGER); + UTYPE(BT_BITSTRING); + UTYPE(BT_OCTETSTRING); + UTYPE(BT_NULL); + UTYPE(BT_OID); + UTYPE(BT_OBJDESC); + UTYPE(BT_EXTERNAL); + UTYPE(BT_REAL); + UTYPE(BT_ENUMERATED); + UTYPE(BT_PDV); + UTYPE(BT_UTF8STRING); + UTYPE(BT_RELOID); + UTYPE(BT_NUMERICSTRING); + UTYPE(BT_STRING); + UTYPE(BT_TELEXSTRING); + UTYPE(BT_VIDEOTEXSTRING); + UTYPE(BT_IA5STRING); + UTYPE(BT_UTCTIME); + UTYPE(BT_GENTIME); + UTYPE(BT_GFXSTRING); + UTYPE(BT_VISSTRING); + UTYPE(BT_GENSTRING); + UTYPE(BT_UNIVSTRING); + UTYPE(BT_CHARSTRING); + UTYPE(BT_BMPSTRING); + case BT_SEQUENCE & ~BER_TYPE_CONSTRUCTED_MASK: + case BT_SEQUENCE: return "SEQUENCE"; + case BT_SET & ~BER_TYPE_CONSTRUCTED_MASK: + case BT_SET: return "SET"; + } + +fallback: + snprintf(namebuf, sizeof(namebuf), "%.02x", libder_type_simple(type)); + return (&namebuf[0]); +} + +static void +libder_obj_dump_internal(const struct libder_object *obj, FILE *fp, int lvl) +{ + static char spacer[4096]; + const struct libder_object *child; + + /* Primitive, goofy, but functional. */ + if (spacer[0] == '\0') + memset(spacer, '\t', sizeof(spacer)); + + if (lvl >= (int)sizeof(spacer)) { + /* Too large, truncate the display. */ + fprintf(fp, "%.*s...\n", (int)sizeof(spacer), spacer); + return; + } + + if (obj->children == NULL) { + size_t col = lvl * 8; + + col += fprintf(fp, "%.*sOBJECT[type=%s, size=%zx]%s", + lvl, spacer, libder_type_name(obj->type), + obj->length, obj->length != 0 ? ": " : ""); + + if (obj->length != 0) { + uint8_t printb; + +#define LIBDER_CONTENTS_WRAP 80 + for (size_t i = 0; i < obj->length; i++) { + if (col + 3 >= LIBDER_CONTENTS_WRAP) { + fprintf(fp, "\n%.*s ", lvl, spacer); + col = (lvl * 8) + 4; + } + + if (obj->payload == NULL) + printb = 0; + else + printb = obj->payload[i]; + + col += fprintf(fp, "%.02x ", printb); + } + } + + fprintf(fp, "\n"); + + return; + } + + fprintf(fp, "%.*sOBJECT[type=%s]\n", lvl, spacer, + libder_type_name(obj->type)); + DER_FOREACH_CHILD(child, obj) + libder_obj_dump_internal(child, fp, lvl + 1); +} + +void +libder_obj_dump(const struct libder_object *root, FILE *fp) +{ + + libder_obj_dump_internal(root, fp, 0); +} + +LIBDER_PRIVATE bool +libder_is_valid_obj(struct libder_ctx *ctx, const struct libder_tag *type, + const uint8_t *payload, size_t payloadsz, bool varlen) +{ + + if (payload != NULL) { + assert(payloadsz > 0); + assert(!varlen); + } else { + assert(payloadsz == 0); + } + + /* No rules for non-universal types. */ + if (type->tag_class != BC_UNIVERSAL || type->tag_encoded) + return (true); + + if (ctx->strict && type->tag_constructed) { + /* Types that don't allow constructed */ + switch (libder_type_simple(type) & ~BER_TYPE_CONSTRUCTED_MASK) { + case BT_BOOLEAN: + case BT_INTEGER: + case BT_REAL: + case BT_NULL: + libder_set_error(ctx, LDE_STRICT_PRIMITIVE); + return (false); + default: + break; + } + } else if (ctx->strict) { + /* Types that cannot be primitive */ + switch (libder_type_simple(type) | BER_TYPE_CONSTRUCTED_MASK) { + case BT_SEQUENCE: + case BT_SET: + libder_set_error(ctx, LDE_STRICT_CONSTRUCTED); + return (false); + default: + break; + } + } + + /* Further validation */ + switch (libder_type_simple(type)) { + case BT_BOOLEAN: + if (ctx->strict && payloadsz != 1) { + libder_set_error(ctx, LDE_STRICT_BOOLEAN); + return (false); + } + break; + case BT_NULL: + if (ctx->strict && (payloadsz != 0 || varlen)) { + libder_set_error(ctx, LDE_STRICT_NULL); + return (false); + } + break; + case BT_BITSTRING: /* Primitive */ + /* + * Bit strings require more invasive parsing later during child + * coalescing or normalization, so we alway strictly enforce + * their form. + */ + if (payloadsz == 1 && payload[0] != 0) + return (false); + + /* We can't have more than seven unused bits. */ + return (payloadsz < 2 || payload[0] < 8); + case BT_RESERVED: + if (payloadsz != 0) { + libder_set_error(ctx, LDE_STRICT_EOC); + return (false); + } + break; + default: + break; + } + + return (true); +} + +LIBDER_PRIVATE bool +libder_obj_may_coalesce_children(const struct libder_object *obj) +{ + + /* No clue about non-universal types. */ + if (obj->type->tag_class != BC_UNIVERSAL || obj->type->tag_encoded) + return (false); + + /* Constructed types don't have children. */ + if (!obj->type->tag_constructed) + return (false); + + /* Strip the constructed bit off. */ + switch (libder_type_simple(obj->type)) { + case BT_OCTETSTRING: /* Raw data types */ + case BT_BITSTRING: + return (true); + case BT_UTF8STRING: /* String types */ + case BT_NUMERICSTRING: + case BT_STRING: + case BT_TELEXSTRING: + case BT_VIDEOTEXSTRING: + case BT_IA5STRING: + case BT_GFXSTRING: + case BT_VISSTRING: + case BT_GENSTRING: + case BT_UNIVSTRING: + case BT_CHARSTRING: + case BT_BMPSTRING: + return (true); + case BT_UTCTIME: /* Time types */ + case BT_GENTIME: + return (true); + default: + return (false); + } +} + +static size_t +libder_merge_bitstrings(uint8_t *buf, size_t offset, size_t bufsz, + const struct libder_object *child) +{ + const uint8_t *rhs = child->payload; + size_t rsz = child->disk_size, startoff = offset; + uint8_t rhsunused, unused; + + rhsunused = (rhs != NULL ? rhs[0] : 0); + + /* We have no unused bits if the buffer's empty as of yet. */ + if (offset == 0) + unused = 0; + else + unused = buf[0]; + + /* Shave the lead byte off if we have one. */ + if (rsz > 1) { + if (rhs != NULL) + rhs++; + rsz--; + } + + if (unused == 0) { + size_t extra = 0; + + /* + * In all cases we'll just write the unused byte separately, + * since we're copying way past it in the common case and can't + * just overwrite it as part of the memcpy(). + */ + if (offset == 0) { + offset = 1; + extra++; + } + + assert(rhsunused < 8); + assert(offset + rsz <= bufsz); + + buf[0] = rhsunused; + if (rhs == NULL) + memset(&buf[offset], 0, rsz); + else + memcpy(&buf[offset], rhs, rsz); + + return (rsz + extra); + } + + for (size_t i = 0; i < rsz; i++) { + uint8_t bits, next; + + if (rhs == NULL) + next = 0; + else + next = rhs[i]; + + /* Rotate the leading bits into the byte before it. */ + assert(unused < 8); + bits = next >> (8 - unused); + buf[offset - 1] |= bits; + + next <<= unused; + + /* + * Copy the new valid bits in; we shift over the old unused + * amount up until the very last bit, then we have to recalculate + * because we may be dropping it entirely. + */ + if (i == rsz - 1) { + assert(rhsunused < 8); + + /* + * Figure out how many unused bits we have between the two + * buffers, sum % 8 is the new # unused bits. It will be + * somewhere in the range of [0, 14], and if it's at or + * higher than a single byte then that's a clear indicator + * that we shifted some unused bits into the previous byte and + * can just halt here. + */ + unused += rhsunused; + buf[0] = unused % 8; + if (unused >= 8) + break; + } + + assert(offset < bufsz); + buf[offset++] = next; + } + + return (offset - startoff); +} + +LIBDER_PRIVATE bool +libder_obj_coalesce_children(struct libder_object *obj, struct libder_ctx *ctx) +{ + struct libder_object *child, *last_child, *tmp; + size_t new_size = 0, offset = 0; + uint8_t *coalesced_data; + uint8_t type; + bool need_payload = false, strict_violation = false; + + if (obj->nchildren == 0 || !libder_obj_may_coalesce_children(obj)) + return (true); + + assert(obj->type->tag_class == BC_UNIVERSAL); + assert(obj->type->tag_constructed); + assert(!obj->type->tag_encoded); + type = obj->type->tag_short; + + last_child = NULL; + DER_FOREACH_CHILD(child, obj) { + /* Sanity check and coalesce our children. */ + if (child->type->tag_class != BC_UNIVERSAL || + child->type->tag_short != obj->type->tag_short) { + libder_set_error(ctx, LDE_COALESCE_BADCHILD); + return (false); + } + + /* Recursively coalesce everything. */ + if (!libder_obj_coalesce_children(child, ctx)) + return (false); + + /* + * The child node will be disappearing anyways, so we stash the + * disk size sans header in its disk_size to reuse in the later + * loop. + */ + child->disk_size = libder_obj_disk_size(child, false); + + /* + * We strip the lead byte off of every element, and add it back + * in pre-allocation. + */ + if (type == BT_BITSTRING && child->disk_size > 1) + child->disk_size--; + if (child->disk_size > 0) + last_child = child; + + new_size += child->disk_size; + + if (child->payload != NULL) + need_payload = true; + } + + if (new_size != 0 && need_payload) { + if (type == BT_BITSTRING) + new_size++; + coalesced_data = malloc(new_size); + if (coalesced_data == NULL) { + libder_set_error(ctx, LDE_NOMEM); + return (false); + } + } else { + /* + * This would perhaps be a bit weird, but that's normalization + * for you. We shouldn't really have a UTF-8 string that's + * composed of a series of zero-length UTF-8 strings, but + * weirder things have happened. + */ + coalesced_data = NULL; + } + + /* Avoid leaking any children as we coalesce. */ + DER_FOREACH_CHILD_SAFE(child, obj, tmp) { + if (child->disk_size != 0) + assert(coalesced_data != NULL || !need_payload); + + /* + * Just free everything when we violate strict rules. + */ + if (strict_violation) + goto violated; + + if (child->disk_size != 0 && need_payload) { + assert(coalesced_data != NULL); + assert(offset + child->disk_size <= new_size); + + /* + * Bit strings are special, in that the first byte + * contains the number of unused bits at the end. We + * need to trim that off when concatenating bit strings + */ + if (type == BT_BITSTRING) { + if (ctx->strict && child != last_child && + child->disk_size > 1 && child->payload != NULL) { + /* + * Each child must have a multiple of 8, + * up until the final one. + */ + if (child->payload[0] != 0) { + libder_set_error(ctx, LDE_STRICT_BITSTRING); + strict_violation = true; + goto violated; + } + } + + offset += libder_merge_bitstrings(coalesced_data, + offset, new_size, child); + } else { + /* + * Write zeroes out if we don't have a payload. + */ + if (child->payload == NULL) { + memset(&coalesced_data[offset], 0, child->disk_size); + offset += child->disk_size; + } else { + memcpy(&coalesced_data[offset], child->payload, + child->disk_size); + offset += child->disk_size; + } + } + } + +violated: + libder_obj_free(child); + } + + assert(offset <= new_size); + + /* Zap the children, we've absorbed their bodies. */ + obj->children = NULL; + + if (strict_violation) { + if (coalesced_data != NULL) { + libder_bzero(coalesced_data, offset); + free(coalesced_data); + } + + return (false); + } + + /* Finally, swap out the payload. */ + if (obj->payload != NULL) { + libder_bzero(obj->payload, obj->length); + free(obj->payload); + } + + obj->length = offset; + obj->payload = coalesced_data; + obj->type->tag_constructed = false; + + return (true); +} + +static bool +libder_obj_normalize_bitstring(struct libder_object *obj) +{ + uint8_t *payload = obj->payload; + size_t length = obj->length; + uint8_t unused; + + if (payload == NULL || length < 2) + return (true); + + unused = payload[0]; + if (unused == 0) + return (true); + + /* Clear the unused bits completely. */ + payload[length - 1] &= ~((1 << unused) - 1); + return (true); +} + +static bool +libder_obj_normalize_boolean(struct libder_object *obj) +{ + uint8_t *payload = obj->payload; + size_t length = obj->length; + int sense = 0; + + assert(length > 0); + + /* + * Booleans must be collapsed down to a single byte, 0x00 or 0xff, + * indicating false or true respectively. + */ + if (length == 1 && (payload[0] == 0x00 || payload[0] == 0xff)) + return (true); + + for (size_t bpos = 0; bpos < length; bpos++) { + sense |= payload[bpos]; + if (sense != 0) + break; + } + + payload[0] = sense != 0 ? 0xff : 0x00; + obj->length = 1; + return (true); +} + +static bool +libder_obj_normalize_integer(struct libder_object *obj) +{ + uint8_t *payload = obj->payload; + size_t length = obj->length; + size_t strip = 0; + + /* + * Strip any leading sign-extended looking bytes, but note that + * we can't strip a leading byte unless it matches the sign bit + * on the next byte. + */ + for (size_t bpos = 0; bpos < length - 1; bpos++) { + if (payload[bpos] != 0 && payload[bpos] != 0xff) + break; + + if (payload[bpos] == 0xff) { + /* Only if next byte indicates signed. */ + if ((payload[bpos + 1] & 0x80) == 0) + break; + } else { + /* Only if next byte indicates unsigned. */ + if ((payload[bpos + 1] & 0x80) != 0) + break; + } + + strip++; + } + + if (strip != 0) { + payload += strip; + length -= strip; + + memmove(&obj->payload[0], payload, length); + obj->length = length; + } + + return (true); +} + +static int +libder_obj_tag_compare(const struct libder_tag *lhs, const struct libder_tag *rhs) +{ + const uint8_t *lbits, *rbits; + size_t delta, end, lsz, rsz; + uint8_t lbyte, rbyte; + + /* Highest bits: tag class, libder_ber_class has the same bit ordering. */ + if (lhs->tag_class < rhs->tag_class) + return (-1); + if (lhs->tag_class > rhs->tag_class) + return (1); + + /* Next bit: constructed vs. primitive */ + if (!lhs->tag_constructed && rhs->tag_constructed) + return (-1); + if (lhs->tag_constructed && rhs->tag_constructed) + return (1); + + /* + * Finally: tag data; we can use the size as a first-order heuristic + * because we store tags in the shortest possible representation. + */ + if (lhs->tag_size < rhs->tag_size) + return (-1); + else if (lhs->tag_size > rhs->tag_size) + return (1); + + if (!lhs->tag_encoded) { + lbits = (const void *)&lhs->tag_short; + lsz = sizeof(uint64_t); + } else { + lbits = lhs->tag_long; + lsz = lhs->tag_size; + } + + if (!rhs->tag_encoded) { + rbits = (const void *)&rhs->tag_short; + rsz = sizeof(uint64_t); + } else { + rbits = rhs->tag_long; + rsz = rhs->tag_size; + } + + delta = 0; + end = MAX(lsz, rsz); + if (lsz > rsz) + delta = lsz - rsz; + else if (lsz < rsz) + delta = rsz - lsz; + for (size_t i = 0; i < end; i++) { + /* Zero-extend the short one the difference. */ + if (lsz < rsz && i < delta) + lbyte = 0; + else + lbyte = lbits[i - delta]; + + if (lsz > rsz && i < delta) + rbyte = 0; + else + rbyte = rbits[i - delta]; + + if (lbyte < rbyte) + return (-1); + else if (lbyte > rbyte) + return (-1); + } + + return (0); +} + +/* + * Similar to strcmp(), returns -1, 0, or 1. + */ +static int +libder_obj_compare(const struct libder_object *lhs, const struct libder_object *rhs) +{ + size_t end; + int cmp; + uint8_t lbyte, rbyte; + + cmp = libder_obj_tag_compare(lhs->type, rhs->type); + if (cmp != 0) + return (cmp); + + /* + * We'll compare up to the longer of the two; the shorter payload is + * zero-extended at the end for comparison purposes. + */ + end = MAX(lhs->length, rhs->length); + for (size_t pos = 0; pos < end; pos++) { + if (lhs->payload != NULL && pos < lhs->length) + lbyte = lhs->payload[pos]; + else + lbyte = 0; + if (rhs->payload != NULL && pos < rhs->length) + rbyte = rhs->payload[pos]; + else + rbyte = 0; + + if (lbyte < rbyte) + return (-1); + else if (lbyte > rbyte) + return (1); + } + + return (0); +} + +static int +libder_obj_normalize_set_cmp(const void *lhs_entry, const void *rhs_entry) +{ + const struct libder_object *lhs = + *__DECONST(const struct libder_object **, lhs_entry); + const struct libder_object *rhs = + *__DECONST(const struct libder_object **, rhs_entry); + + return (libder_obj_compare(lhs, rhs)); +} + +static bool +libder_obj_normalize_set(struct libder_object *obj, struct libder_ctx *ctx) +{ + struct libder_object **sorting; + struct libder_object *child; + size_t offset = 0; + + if (obj->nchildren < 2) + return (true); + + /* + * Kind of goofy, but we'll just take advantage of a standardized + * qsort() rather than rolling our own sort -- we have no idea how large + * of a dataset we're working with. + */ + sorting = calloc(obj->nchildren, sizeof(*sorting)); + if (sorting == NULL) { + libder_set_error(ctx, LDE_NOMEM); + return (false); + } + + DER_FOREACH_CHILD(child, obj) { + sorting[offset++] = child; + } + + assert(offset == obj->nchildren); + qsort(sorting, offset, sizeof(*sorting), libder_obj_normalize_set_cmp); + + obj->children = sorting[0]; + sorting[offset - 1]->next = NULL; + for (size_t i = 0; i < offset - 1; i++) { + sorting[i]->next = sorting[i + 1]; + } + + free(sorting); + + return (true); +} + +LIBDER_PRIVATE bool +libder_obj_normalize(struct libder_object *obj, struct libder_ctx *ctx) +{ + uint8_t *payload = obj->payload; + size_t length = obj->length; + + if (obj->type->tag_constructed) { + /* + * For constructed types, we'll see if we can coalesce their + * children into them, then we'll proceed with whatever normalization + * rules we can apply to the children. + */ + if (DER_NORMALIZING(ctx, CONSTRUCTED) && !libder_obj_coalesce_children(obj, ctx)) + return (false); + + /* + * We may not be a constructed object anymore after the above coalescing + * happened, so we check it again here. Constructed objects need not go + * any further, but the now-primitive coalesced types still need to be + * normalized. + */ + if (obj->type->tag_constructed) { + struct libder_object *child; + + DER_FOREACH_CHILD(child, obj) { + if (!libder_obj_normalize(child, ctx)) + return (false); + } + + /* Sets must be sorted. */ + if (obj->type->tag_short != BT_SET) + return (true); + + return (libder_obj_normalize_set(obj, ctx)); + } + } + + /* We only have normalization rules for universal types. */ + if (obj->type->tag_class != BC_UNIVERSAL || obj->type->tag_encoded) + return (true); + + if (!libder_normalizing_type(ctx, obj->type)) + return (true); + + /* + * We are clear to normalize this object, check for some easy cases that + * don't need normalization. + */ + switch (libder_type_simple(obj->type)) { + case BT_BITSTRING: + case BT_BOOLEAN: + case BT_INTEGER: + /* + * If we have a zero payload, then we need to encode them as a + * single zero byte. + */ + if (payload == NULL) { + if (length != 1) + obj->length = 1; + + return (true); + } + + break; + case BT_NULL: + if (payload != NULL) { + free(payload); + + obj->payload = NULL; + obj->length = 0; + } + + return (true); + default: + /* + * If we don't have a payload, we'll just leave it alone. + */ + if (payload == NULL) + return (true); + break; + } + + switch (libder_type_simple(obj->type)) { + case BT_BITSTRING: + return (libder_obj_normalize_bitstring(obj)); + case BT_BOOLEAN: + return (libder_obj_normalize_boolean(obj)); + case BT_INTEGER: + return (libder_obj_normalize_integer(obj)); + default: + break; + } + + return (true); +} |
