Radish alpha
H
rad:z3QDZAW2FAfuLvihrhiyDC9fAD8G9
HardenedBSD Package Manager
Radicle
Git
external: import libder
Kyle Evans committed 2 years ago
commit 4d68800b155e237c8c3340bc0d258dc453c64e5b
parent 1b90ceb
38 files changed +4683 -2
modified .gitignore
@@ -55,6 +55,7 @@ scripts/sbin/pkg2ng
/external/picosat/Makefile
/external/linenoise/Makefile
/external/libcurl/Makefile
+
/external/libder/Makefile
/external/libecc/Makefile
/external/libucl/Makefile
/external/libucl/aclocal.m4
modified auto.def
@@ -366,7 +366,8 @@ foreach dir [list external/blake2 external/picosat \
	external/linenoise external/sqlite \
	external compat libpkg libpkg/repo libpkg/repo/binary src \
	external/libucl external/libelf external/libmachista tests docs \
-
	external/liblua external/yxml scripts external/libcurl external/libecc] {
+
	external/liblua external/yxml scripts external/libcurl external/libder \
+
	external/libecc] {
	make-template $dir/Makefile.autosetup $dir/Makefile
}

modified external/Makefile.autosetup
@@ -1,5 +1,5 @@
include @builddir@/mk/defs.mk
-
DIRS=	blake2 picosat linenoise sqlite libucl liblua yxml libcurl libecc
+
DIRS=	blake2 picosat linenoise sqlite libucl liblua yxml libcurl libder libecc
@if libmachista
DIRS+=	libmachista
@endif
added external/libder/.gitignore
@@ -0,0 +1,11 @@
+
.*.swp
+
.depend*
+
*.a
+
*.so
+
*.so.*
+
*.o
+
*.pico
+
*.debug
+
*.full
+

+
build/
added external/libder/CMakeLists.txt
@@ -0,0 +1,24 @@
+
cmake_minimum_required(VERSION 3.18)
+

+
project(libder)
+

+
if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
+
	add_compile_options(-fsanitize=address,undefined -fstrict-aliasing)
+
	add_link_options(-fsanitize=address,undefined -fstrict-aliasing)
+
endif()
+

+
# AppleClang is excluded for the time being; the version used in GitHub Action
+
# runners doesn't seem to have that part of libclang_rt installed, though the
+
# -fsanitize=fuzzer-no-link instrumentation seems to be fine.  Maybe re-evaluate
+
# this for MATCHES as a possibility later.
+
if(CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
+
	set(BUILD_FUZZERS TRUE
+
		CACHE BOOL "Build the libFuzzer fuzzers (needs llvm)")
+
else()
+
	set(BUILD_FUZZERS FALSE
+
		CACHE BOOL "Build the libFuzzer fuzzers (needs llvm)")
+
endif()
+

+
add_subdirectory(libder)
+
add_subdirectory(derdump)
+
add_subdirectory(tests)
added external/libder/LICENSE
@@ -0,0 +1,22 @@
+
Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+

+
Redistribution and use in source and binary forms, with or without
+
modification, are permitted provided that the following conditions
+
are met:
+
1. Redistributions of source code must retain the above copyright
+
   notice, this list of conditions and the following disclaimer.
+
2. Redistributions in binary form must reproduce the above copyright
+
   notice, this list of conditions and the following disclaimer in the
+
   documentation and/or other materials provided with the distribution.
+

+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
SUCH DAMAGE.
added external/libder/Makefile.autosetup
@@ -0,0 +1,15 @@
+
include @builddir@/mk/defs.mk
+

+
LIB=	der
+

+
VPATH=	$(top_srcdir)/external/libder/libder
+
.PATH:	$(top_srcdir)/external/libder/libder
+

+
SRCS=	libder.c \
+
	libder_error.c \
+
	libder_obj.c \
+
	libder_read.c \
+
	libder_type.c \
+
	libder_write.c
+

+
include $(MK)/static-lib.mk
added external/libder/README.md
@@ -0,0 +1,23 @@
+
# libder
+

+
## What is libder?
+

+
libder is a small library for encoding/decoding DER-encoded objects.  It is
+
expected to be able to decode any BER-encoded buffer, and an attempt to
+
re-encode the resulting tree would apply any normalization expected by a DER
+
decoder.  The author's use is primarily to decode/encode ECC keys for
+
interoperability with OpenSSL.
+

+
## What is libder not?
+

+
libder is not intended to be a general-purpose library for working with DER/BER
+
specified objects.  It may provide some helpers for building more primitive
+
data types, but libder will quickly punt on anything even remotely complex and
+
require the library consumer to supply it as a type/payload/size triple that it
+
will treat as relatively opaque (modulo some encoding normalization rules that
+
can be applied without deeply understanding the data contained within).
+

+
libder also doesn't do strict validation of what it reads in today, for better
+
or worse.  e.g., a boolean may occupy more than one byte and libder will happily
+
present it to the application in that way.  It would be normalized on
+
re-encoding to 0xff or 0x00 depending on whether any bits are set or not.
added external/libder/derdump/.gitignore
@@ -0,0 +1 @@
+
derdump
added external/libder/derdump/CMakeLists.txt
@@ -0,0 +1,6 @@
+
file(GLOB derdump_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.c)
+

+
add_executable(derdump ${derdump_SOURCES})
+

+
target_include_directories(derdump PRIVATE "${CMAKE_SOURCE_DIR}/libder")
+
target_link_libraries(derdump der_static)
added external/libder/derdump/derdump.1
@@ -0,0 +1,51 @@
+
.\"
+
.\" SPDX-Copyright-Identifier: BSD-2-Clause
+
.\"
+
.\" Copyright (C) 2024 Kyle Evans <kevans@FreeBSD.org>
+
.\"
+
.Dd March 4, 2024
+
.Dt DERDUMP 1
+
.Os
+
.Sh NAME
+
.Nm derdump
+
.Nd dumping contents of DER encoded files
+
.Sh SYNOPSIS
+
.Nm
+
.Ar file1
+
.Oo Ar fileN ... Oc
+
.Sh DESCRIPTION
+
The
+
.Nm
+
utility dumps the contents of one or more DER encoded
+
Ar file
+
in a more human readable format.
+
This is similar to the
+
.Xr asn1parse 1
+
utility distributed with OpenSSL when used with the
+
.Fl inform
+
.Ar DER
+
option.
+
.Pp
+
A representation of the object will be output to
+
.Em stdout ,
+
with indentation to denote objects that are encoded within other constructed
+
objects.
+
Note that
+
.Nm
+
does not make much attempt to interpret the contents of any particular object.
+
If an object uses one of the universal types, then a friendly name will be
+
displayed for that object.
+
If an object uses any other type, then
+
.Nm
+
will display the raw hex value of the type used.
+
Values of primitive objects are output as raw hex, and no effort is made to
+
try and print a friendly representation.
+
.Sh SEE ALSO
+
.Xr asn1parse 1 ,
+
.Xr libder 3
+
.Sh BUGS
+
.Nm
+
does not currently make any attempt to render a type that uses the long encoded
+
format.
+
Instead, it will render as
+
.Dq { ... } .
added external/libder/derdump/derdump.c
@@ -0,0 +1,52 @@
+
/*-
+
 * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

+
#include <err.h>
+
#include <stdio.h>
+

+
#include <libder.h>
+

+
int
+
main(int argc, char *argv[])
+
{
+
	FILE *fp;
+
	struct libder_ctx *ctx;
+
	struct libder_object *root;
+
	size_t rootsz;
+
	bool first = true;
+

+
	if (argc < 2) {
+
		fprintf(stderr, "usage: %s file [file...]\n", argv[0]);
+
		return (1);
+
	}
+

+
	ctx = libder_open();
+
	libder_set_verbose(ctx, 2);
+
	for (int i = 1; i < argc; i++) {
+
		fp = fopen(argv[i], "rb");
+
		if (fp == NULL) {
+
			warn("%s", argv[i]);
+
			continue;
+
		}
+

+
		if (!first)
+
			fprintf(stderr, "\n");
+
		fprintf(stdout, "[%s]\n", argv[i]);
+
		root = libder_read_file(ctx, fp, &rootsz);
+
		if (root != NULL) {
+
			libder_obj_dump(root, stdout);
+
			libder_obj_free(root);
+
			root = NULL;
+
		}
+

+
		first = false;
+
		fclose(fp);
+
	}
+

+
	libder_close(ctx);
+

+
	return (0);
+
}
added external/libder/libder/CMakeLists.txt
@@ -0,0 +1,12 @@
+
file(GLOB libder_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.c)
+

+
add_library(der SHARED ${libder_SOURCES})
+
add_library(der_static STATIC ${libder_SOURCES})
+

+
if(BUILD_FUZZERS AND CMAKE_BUILD_TYPE STREQUAL "Debug")
+
	target_compile_options(der PUBLIC -fsanitize=fuzzer-no-link)
+
	target_link_options(der PUBLIC -fsanitize=fuzzer-no-link)
+

+
	target_compile_options(der_static PUBLIC -fsanitize=fuzzer-no-link)
+
	target_link_options(der_static PUBLIC -fsanitize=fuzzer-no-link)
+
endif()
added external/libder/libder/libder.3
@@ -0,0 +1,179 @@
+
.\"
+
.\" SPDX-Copyright-Identifier: BSD-2-Clause
+
.\"
+
.\" Copyright (C) 2024 Kyle Evans <kevans@FreeBSD.org>
+
.\"
+
.Dd March 2, 2024
+
.Dt LIBDER 3
+
.Os
+
.Sh NAME
+
.Nm libder ,
+
.Nm libder_open ,
+
.Nm libder_close ,
+
.Nm libder_abort ,
+
.Nm libder_get_error ,
+
.Nm libder_has_error ,
+
.Nm libder_get_normalize ,
+
.Nm libder_set_normalize ,
+
.Nm libder_get_strict ,
+
.Nm libder_set_strict ,
+
.Nm libder_get_verbose ,
+
.Nm libder_set_verbose
+
.Nd DER encoding and decoding library
+
.Sh LIBRARY
+
.Lb libder
+
.Sh SYNOPSIS
+
.In libder.h
+
.Ft struct libder_ctx *
+
.Fn libder_open "void"
+
.Ft void
+
.Fn libder_close "struct libder_ctx *ctx"
+
.Ft void
+
.Fn libder_abort "struct libder_ctx *ctx"
+
.Ft const char *
+
.Fn libder_get_error "struct libder_ctx *ctx"
+
.Ft bool
+
.Fn libder_has_error "struct libder_ctx *ctx"
+
.Ft uint64_t
+
.Fn libder_get_normalize "struct libder_ctx *ctx"
+
.Ft uint64_t
+
.Fn libder_set_normalize "struct libder_ctx *ctx" "uint64_t normalize"
+
.Ft bool
+
.Fn libder_get_strict "struct libder_ctx *ctx"
+
.Ft bool
+
.Fn libder_set_strict "struct libder_ctx *ctx" "bool strict"
+
.Ft int
+
.Fn libder_get_verbose "struct libder_ctx *ctx"
+
.Ft int
+
.Fn libder_set_verbose "struct libder_ctx *ctx" "int verbose"
+
.Sh DESCRIPTION
+
The
+
.Nm
+
library provides functionality for decoding BER and DER encoded data, and
+
DER encoding data subjected to constraints outline in ITU-T
+
Recommendation X.690.
+
.Nm
+
will apply relevant normalization rules on write, unless they've been disabled
+
with
+
.Ft libder_set_normalize ,
+
under the assumption that it may not be reading strictly DER encoded data.
+
.Pp
+
Note that not all of the DER rules are currently implemented.
+
.Nm
+
will coalesce constructed types that DER specifies should be primitive.
+
.Nm
+
will primarily normalize bitstrings, booleans, and integers.
+
This library was primarily written to be able to provide interoperability with
+
OpenSSL keys and signatures, so the library was written with that in mind.
+
Eventually it is intended that
+
.Nm
+
will support the full set of rules, but currently some responsibility is left
+
to the library user.
+
.Pp
+
Also note that
+
.Nm
+
does not necessarily provide
+
.Dq neat
+
ways to construct primitives.
+
For example, even booleans and integers currently work just by providing a
+
buffer that is expected to be formatted in a sane fashion.
+
The library user is expected to build the object tree and generally provide the
+
object data in a format reasonably encoded as the data for that type should be,
+
then
+
.Nm
+
will provide the proper framing on write and do any transformations that may
+
need to be done for strict conformance.
+
.Pp
+
The
+
.Fn libder_open
+
function allocates a new
+
.Nm
+
context.
+
The context does not hold any state about any particular structure.
+
All of the state held in the context is generally described in this manpage.
+
The
+
.Fn libder_close
+
function will free the context.
+
.Pp
+
The
+
.Fn libder_abort
+
function will abort an in-progress
+
.Xr libder_read_fd 3
+
operation on the existing
+
.Fa ctx
+
if it is interrupted by a signal in the middle of a
+
.Xr read 2
+
syscall.
+
See
+
.Xr libder_read_fd 3
+
for further discussion.
+
.Pp
+
The
+
.Fn libder_get_error
+
function will return an error string appropriate for the current error, if any.
+
The
+
.Fn libder_has_error
+
function can be used to check if an error was raised in a previous operation.
+
.Pp
+
The
+
.Fn libder_get_normalize
+
and
+
.Fn libder_set_normalize
+
functions retrieve and manipulate any number of flags that detail how
+
functions may be used to check or set the normalization flags given
+
.Nm context ,
+
which dictates how
+
.Nm
+
will normalize data on write.
+
The following normalization flags may be specified:
+
.Bl -column "LIBDER_NORMALIZE_CONSTRUCTED"
+
.It LIBDER_NORMALIZE_CONSTRUCTED Ta Coalesce types that may be primitive or constructed
+
.It LIBDER_NORMALIZE_TAGS Ta Pack tags into the lowest possible encoded value
+
.El
+
.Pp
+
The
+
.Fn LIBDER_NORMALIZE_TYPE_FLAG "enum libder_ber_type"
+
macaro may also be used to specify normalization of the given universal type.
+
By default, every valid normalization flag is enabled.
+
.Pp
+
The
+
.Fn libder_get_strict
+
and
+
.Fn libder_set_strict
+
functions may used to check or set the strict read state of the given
+
.Nm
+
context.
+
By default,
+
.Nm
+
operates in strict mode and rejects various methods of expressing data that are
+
valid looking but not strictly conformant.
+
The
+
.Va LDE_STRICT_*
+
constants in
+
.In libder.h
+
describe the various scenarios that strict mode may reject.
+
.Pp
+
The
+
.Fn libder_get_verbose
+
and
+
.Fn libder_set_verbose
+
functions may be used to check or set the verbosity of the given
+
.Nm
+
context.
+
This primarily controls how
+
.Nm
+
behaves when an error is encountered.
+
By default, the library will silently set the error state and return.
+
With a verbosity level of 1, an error will be printed when the error state is
+
set that contains the string that would be returned by
+
.Fn libder_get_error .
+
With a verbosity level of 2, the filename and line within
+
.Nm
+
that the error occurred in will be printed, which is primarily intended for
+
debugging
+
.Nm .
+
.Sh SEE ALSO
+
.Xr libder_obj 3 ,
+
.Xr libder_read 3 ,
+
.Xr libder_type 3 ,
+
.Xr libder_write 3
added external/libder/libder/libder.c
@@ -0,0 +1,119 @@
+
/*-
+
 * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

+
#include "libder_private.h"
+

+
#include <stdlib.h>
+
#include <unistd.h>
+

+
/*
+
 * Sets up the context, returns NULL on error.
+
 */
+
struct libder_ctx *
+
libder_open(void)
+
{
+
	struct libder_ctx *ctx;
+

+
	ctx = malloc(sizeof(*ctx));
+
	if (ctx == NULL)
+
		return (NULL);
+

+
	/* Initialize */
+
	ctx->error = LDE_NONE;
+
	ctx->buffer_size = 0;
+
	ctx->verbose = 0;
+
	ctx->normalize = LIBDER_NORMALIZE_ALL;
+
	ctx->strict = true;
+
	ctx->abort = 0;
+

+
	return (ctx);
+
}
+

+
void
+
libder_abort(struct libder_ctx *ctx)
+
{
+

+
	ctx->abort = 1;
+
}
+

+
LIBDER_PRIVATE size_t
+
libder_get_buffer_size(struct libder_ctx *ctx)
+
{
+

+
	if (ctx->buffer_size == 0) {
+
		long psize;
+

+
		psize = sysconf(_SC_PAGESIZE);
+
		if (psize <= 0)
+
			psize = 4096;
+

+
		ctx->buffer_size = psize;
+
	}
+

+
	return (ctx->buffer_size);
+
}
+

+
uint64_t
+
libder_get_normalize(struct libder_ctx *ctx)
+
{
+

+
	return (ctx->normalize);
+
}
+

+
/*
+
 * Set the normalization flags; returns the previous value.
+
 */
+
uint64_t
+
libder_set_normalize(struct libder_ctx *ctx, uint64_t nmask)
+
{
+
	uint64_t old = ctx->normalize;
+

+
	ctx->normalize = (nmask & LIBDER_NORMALIZE_ALL);
+
	return (old);
+
}
+

+
bool
+
libder_get_strict(struct libder_ctx *ctx)
+
{
+

+
	return (ctx->strict);
+
}
+

+
bool
+
libder_set_strict(struct libder_ctx *ctx, bool strict)
+
{
+
	bool oval = ctx->strict;
+

+
	ctx->strict = strict;
+
	return (oval);
+
}
+

+
int
+
libder_get_verbose(struct libder_ctx *ctx)
+
{
+

+
	return (ctx->verbose);
+
}
+

+
int
+
libder_set_verbose(struct libder_ctx *ctx, int verbose)
+
{
+
	int oval = ctx->verbose;
+

+
	ctx->verbose = verbose;
+
	return (oval);
+
}
+

+
void
+
libder_close(struct libder_ctx *ctx)
+
{
+

+
	if (ctx == NULL)
+
		return;
+

+
	free(ctx);
+
}
+

added external/libder/libder/libder.h
@@ -0,0 +1,181 @@
+
/*-
+
 * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

+
#pragma once
+

+
#include <stdbool.h>
+
#include <stdio.h>
+
#include <stdint.h>
+
#include <stddef.h>
+

+
enum libder_ber_class {
+
	BC_UNIVERSAL = 0,
+
	BC_APPLICATION = 1,
+
	BC_CONTEXT = 2,
+
	BC_PRIVATE = 3,
+
};
+

+
enum libder_ber_type {
+
	BT_RESERVED = 0x00,
+
	BT_BOOLEAN = 0x01,
+
	BT_INTEGER = 0x02,
+
	BT_BITSTRING = 0x03,
+
	BT_OCTETSTRING = 0x04,
+
	BT_NULL = 0x05,
+
	BT_OID = 0x06,
+
	BT_OBJDESC = 0x07,
+
	BT_EXTERNAL = 0x08,
+
	BT_REAL = 0x09,
+
	BT_ENUMERATED = 0x0a,
+
	BT_PDV = 0x0b,
+
	BT_UTF8STRING =  0x0c,
+
	BT_RELOID = 0x0d,
+

+
	/* 0x10, 011 not usable */
+

+
	BT_NUMERICSTRING = 0x012,
+
	BT_STRING = 0x13,
+
	BT_TELEXSTRING = 0x14,
+
	BT_VIDEOTEXSTRING = 0x15,
+
	BT_IA5STRING = 0x16,
+
	BT_UTCTIME = 0x17,
+
	BT_GENTIME = 0x18,
+
	BT_GFXSTRING = 0x19,
+
	BT_VISSTRING = 0x1a,
+
	BT_GENSTRING = 0x1b,
+
	BT_UNIVSTRING = 0x1c,
+
	BT_CHARSTRING = 0x1d,
+
	BT_BMPSTRING = 0x1e,
+

+
	BT_SEQUENCE = 0x30,
+
	BT_SET = 0x31,
+
};
+

+
#define	BER_TYPE_CONSTRUCTED_MASK	0x20	/* Bit 6 */
+
#define	BER_TYPE_CLASS_MASK		0xc0	/* Bits 7 and 8 */
+

+
/*
+
 * The difference between the type and the full type is just that the full type
+
 * will indicate the class of type, so it may be more useful for some operations.
+
 */
+
#define	BER_FULL_TYPE(tval)		\
+
    ((tval) & ~(BER_TYPE_CONSTRUCTED_MASK))
+
#define	BER_TYPE(tval)			\
+
    ((tval) & ~(BER_TYPE_CLASS_MASK | BER_TYPE_CONSTRUCTED_MASK))
+
#define	BER_TYPE_CLASS(tval)		\
+
    (((tval) & BER_TYPE_CLASS_MASK) >> 6)
+
#define BER_TYPE_CONSTRUCTED(tval)	\
+
    (((tval) & BER_TYPE_CONSTRUCTED_MASK) != 0)
+

+
enum libder_error {
+
	LDE_NONE = 0x00,
+
	LDE_NOMEM,		/* Out of memory */
+
	LDE_INVAL,		/* Invalid parameter */
+
	LDE_SHORTHDR,		/* Header too short */
+
	LDE_BADVARLEN,		/* Bad variable length encoding */
+
	LDE_LONGLEN,		/* Encoded length too large (8 byte max) */
+
	LDE_SHORTDATA,		/* Payload not available */
+
	LDE_GARBAGE,		/* Garbage after encoded data */
+
	LDE_STREAMERR,		/* Stream error */
+
	LDE_TRUNCVARLEN,	/* Variable length object truncated */
+
	LDE_COALESCE_BADCHILD,	/* Bad child encountered when coalescing */
+
	LDE_BADOBJECT,		/* Payload not valid for object type */
+

+
	/* Strict violations */
+
	LDE_STRICT_EOC,		/* Strict: end-of-content violation */
+
	LDE_STRICT_TAG,		/* Strict: tag violation */
+
	LDE_STRICT_PVARLEN,	/* Strict: primitive using indefinite length */
+
	LDE_STRICT_BOOLEAN,	/* Strict: boolean encoded incorrectly */
+
	LDE_STRICT_NULL,	/* Strict: null encoded incorrectly */
+
	LDE_STRICT_PRIMITIVE,	/* Strict: type must be primitive */
+
	LDE_STRICT_CONSTRUCTED,	/* Strict: type must be constructed */
+
	LDE_STRICT_BITSTRING,	/* Strict: malformed constructed bitstring */
+
};
+

+
struct libder_ctx;
+
struct libder_tag;
+
struct libder_object;
+

+
/*
+
 * By default we normalize everything, but we allow some subset of the
+
 * functionality to be disabled.  Lengths are non-optional and will always be
+
 * normalized to a fixed short or long length.  The upper 32-bits of
+
 * ctx->normalize are reserved for universal types so that we can quickly map
+
 * those without assigning them names.
+
 */
+

+
/* Normalize constructed types that should be coalesced (e.g., strings, time). */
+
#define	LIBDER_NORMALIZE_CONSTRUCTED	0x0000000000000001ULL
+

+
/*
+
 * Normalize tags on read.  This is mostly a measure to ensure that
+
 * normalization on write doesn't get thwarted; there's no reason anybody should
+
 * be encoding low tags with the long form, but the spec doesn't appear to
+
 * forbid it.
+
 */
+
#define	LIBDER_NORMALIZE_TAGS		0x0000000000000002ULL
+

+
/* Universal types (reserved) */
+
#define	LIBDER_NORMALIZE_TYPE_MASK	0xffffffff00000000ULL
+
#define	LIBDER_NORMALIZE_TYPE_FLAG(val)	((1ULL << val) << 32ULL)
+

+
/* All valid bits. */
+
#define	LIBDER_NORMALIZE_ALL		\
+
    (LIBDER_NORMALIZE_TYPE_MASK | LIBDER_NORMALIZE_CONSTRUCTED |	\
+
    LIBDER_NORMALIZE_TAGS)
+

+
struct libder_ctx *		 libder_open(void);
+
void			 libder_close(struct libder_ctx *);
+
void			 libder_abort(struct libder_ctx *);
+
const char		*libder_get_error(struct libder_ctx *);
+
bool			 libder_has_error(struct libder_ctx *);
+
uint64_t		 libder_get_normalize(struct libder_ctx *);
+
uint64_t		 libder_set_normalize(struct libder_ctx *, uint64_t);
+
bool			 libder_get_strict(struct libder_ctx *);
+
bool			 libder_set_strict(struct libder_ctx *, bool);
+
int			 libder_get_verbose(struct libder_ctx *);
+
int			 libder_set_verbose(struct libder_ctx *, int);
+

+
struct libder_object	*libder_read(struct libder_ctx *, const uint8_t *, size_t *);
+
struct libder_object	*libder_read_fd(struct libder_ctx *, int, size_t *);
+
struct libder_object	*libder_read_file(struct libder_ctx *, FILE *, size_t *);
+

+
uint8_t			*libder_write(struct libder_ctx *, struct libder_object *, uint8_t *,
+
			    size_t *);
+

+
#define	DER_CHILDREN(obj)	libder_obj_children(obj)
+
#define	DER_NEXT(obj)		libder_obj_next(obj)
+

+
#define	DER_FOREACH_CHILD(var, obj)	\
+
	for ((var) = DER_CHILDREN((obj));	\
+
	    (var);				\
+
	    (var) = DER_NEXT((var)))
+
#define	DER_FOREACH_CHILD_SAFE(var, obj, tvar)		\
+
	for ((var) = DER_CHILDREN((obj));		\
+
	    (var) && ((tvar) = DER_NEXT((var)), 1);	\
+
	    (var) = (tvar))
+

+
struct libder_object	*libder_obj_alloc(struct libder_ctx *, struct libder_tag *, const uint8_t *, size_t);
+
struct libder_object	*libder_obj_alloc_simple(struct libder_ctx *, uint8_t, const uint8_t *,
+
		    size_t);
+
void		 libder_obj_free(struct libder_object *);
+

+
bool		 libder_obj_append(struct libder_object *, struct libder_object *);
+
struct libder_object	*libder_obj_child(const struct libder_object *, size_t);
+
struct libder_object	*libder_obj_children(const struct libder_object *);
+
struct libder_object	*libder_obj_next(const struct libder_object *);
+
struct libder_tag	*libder_obj_type(const struct libder_object *);
+
uint8_t		 libder_obj_type_simple(const struct libder_object *);
+
const uint8_t	*libder_obj_data(const struct libder_object *, size_t *);
+

+
/* Debugging aide -- probably shouldn't use. */
+
void		 libder_obj_dump(const struct libder_object *, FILE *);
+

+
struct libder_tag	*libder_type_alloc_simple(struct libder_ctx *, uint8_t);
+
struct libder_tag	*libder_type_dup(struct libder_ctx *, const struct libder_tag *);
+
void		 libder_type_free(struct libder_tag *);
+
#define	libder_type_simple	libder_type_simple_abi
+
uint8_t		 libder_type_simple(const struct libder_tag *);
added external/libder/libder/libder_error.c
@@ -0,0 +1,76 @@
+
/*-
+
 * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

+
#include <stdio.h>
+

+
#include "libder_private.h"
+

+
#undef libder_set_error
+

+
static const char libder_error_nodesc[] = "[Description not available]";
+

+
#define	DESCRIBE(err, msg)	{ LDE_ ## err, msg }
+
static const struct libder_error_desc {
+
	enum libder_error	 desc_error;
+
	const char		*desc_str;
+
} libder_error_descr[] = {
+
	DESCRIBE(NONE,		"No error"),
+
	DESCRIBE(NOMEM,		"Out of memory"),
+
	DESCRIBE(INVAL,		"Invalid parameter"),
+
	DESCRIBE(SHORTHDR,	"Header too short"),
+
	DESCRIBE(BADVARLEN,	"Bad variable length encoding"),
+
	DESCRIBE(LONGLEN,	"Encoded length too large (8 byte max)"),
+
	DESCRIBE(SHORTDATA,	"Payload not available (too short)"),
+
	DESCRIBE(GARBAGE,	"Garbage after encoded data"),
+
	DESCRIBE(STREAMERR,	"Stream error"),
+
	DESCRIBE(TRUNCVARLEN,	"Variable length object truncated"),
+
	DESCRIBE(COALESCE_BADCHILD,	"Bad child encountered when coalescing"),
+
	DESCRIBE(BADOBJECT,	"Payload not valid for object type"),
+
	DESCRIBE(STRICT_EOC,		"Strict: end-of-content violation"),
+
	DESCRIBE(STRICT_TAG,		"Strict: tag violation"),
+
	DESCRIBE(STRICT_PVARLEN,	"Strict: primitive using indefinite length"),
+
	DESCRIBE(STRICT_BOOLEAN,	"Strict: boolean encoded incorrectly"),
+
	DESCRIBE(STRICT_NULL,		"Strict: null encoded incorrectly"),
+
	DESCRIBE(STRICT_PRIMITIVE,	"Strict: type must be primitive"),
+
	DESCRIBE(STRICT_CONSTRUCTED,	"Strict: type must be constructed"),
+
	DESCRIBE(STRICT_BITSTRING,	"Strict: malformed constructed bitstring"),
+
};
+

+
const char *
+
libder_get_error(struct libder_ctx *ctx)
+
{
+
	const struct libder_error_desc *desc;
+

+
	for (size_t i = 0; i < nitems(libder_error_descr); i++) {
+
		desc = &libder_error_descr[i];
+

+
		if (desc->desc_error == ctx->error)
+
			return (desc->desc_str);
+
	}
+

+
	return (libder_error_nodesc);
+
}
+

+
bool
+
libder_has_error(struct libder_ctx *ctx)
+
{
+

+
	return (ctx->error != 0);
+
}
+

+
LIBDER_PRIVATE void
+
libder_set_error(struct libder_ctx *ctx, int error, const char *file, int line)
+
{
+
	ctx->error = error;
+

+
	if (ctx->verbose >= 2) {
+
		fprintf(stderr, "%s: [%s:%d]: %s (error %d)\n",
+
		    __func__, file, line, libder_get_error(ctx), error);
+
	} else if (ctx->verbose >= 1) {
+
		fprintf(stderr, "%s: %s (error %d)\n", __func__,
+
		    libder_get_error(ctx), error);
+
	}
+
}
added external/libder/libder/libder_obj.3
@@ -0,0 +1,138 @@
+
.\"
+
.\" SPDX-Copyright-Identifier: BSD-2-Clause
+
.\"
+
.\" Copyright (C) 2024 Kyle Evans <kevans@FreeBSD.org>
+
.\"
+
.Dd March 2, 2024
+
.Dt LIBDER_OBJ 3
+
.Os
+
.Sh NAME
+
.Nm libder_obj ,
+
.Nm libder_obj_alloc ,
+
.Nm libder_obj_alloc_simple ,
+
.Nm libder_obj_free ,
+
.Nm libder_obj_append ,
+
.Nm libder_obj_child ,
+
.Nm libder_obj_next ,
+
.Nm libder_obj_type ,
+
.Nm libder_obj_type_simple ,
+
.Nm libder_obj_data ,
+
.Nm libder_obj_dump
+
.Nd inspecting and creating libder objects
+
.Sh LIBRARY
+
.Lb libder
+
.Sh SYNOPSIS
+
.In libder.h
+
.Ft struct libder_object *
+
.Fn libder_obj_alloc "struct libder_ctx *ctx" "struct libder_tag *type" "const uint8_t *data" "size_t datasz"
+
.Ft struct libder_object *
+
.Fn libder_obj_alloc_simple "struct libder_ctx *ctx" "uint8_t type" "const uint8_t *data" "size_t datasz"
+
.Ft void
+
.Fn libder_obj_free "struct libder_object *ctx"
+
.Ft bool
+
.Fn libder_obj_append "struct libder_object *parent" "struct libder_object *child"
+
.Ft struct libder_object *
+
.Fn libder_obj_child "const struct libder_object *obj" "size_t which"
+
.Ft struct libder_object *
+
.Fn libder_obj_next "const struct libder_object *obj"
+
.Fn "DER_FOREACH_CHILD" "struct libder_obj *iter" "struct libder_obj *obj"
+
.Fn "DER_FOREACH_CHILD_SAFE" "struct libder_obj *iter" "struct libder_obj *obj" "struct libder_obj *tmp"
+
.Ft struct libder_tag *
+
.Fn libder_obj_type "const struct libder_object *obj"
+
.Ft uint8_t
+
.Fn libder_obj_type_simple "const struct libder_object *obj"
+
.Ft const uint8_t *
+
.Fn libder_obj_data "const struct libder_object *obj" "size_t *sz"
+
.Ft void
+
.Fn libder_obj_dump "const struct libder_object *obj" "FILE *fp"
+
.Sh DESCRIPTION
+
The
+
.Nm
+
family of functions may be used by the application to create its own objects and
+
object hierarchy, rather than reading them from an existing stream.
+
.Pp
+
The
+
.Fn libder_obj_alloc
+
and
+
.Fn libder_obj_alloc_simple
+
functions allocate a new object with the specified
+
.Fa type
+
and
+
.Fa data .
+
Most applications will likely prefer to use the
+
.Dq simple
+
variant to avoid having to manage a
+
.Xr libder_type 3
+
lifecycle and associated boilerplate.
+
The base variant remains around for when
+
.Xr libder_type 3
+
grows the necessary API to create arbitrarily large tags.
+
.Pp
+
The
+
.Fn libder_obj_append
+
function is used to append
+
.Fa obj
+
to the
+
.Fa parent
+
object's children.
+
For example, to add an object to a sequence.
+
.Pp
+
The
+
.Fn libder_obj_child
+
and
+
.Fn libder_obj_next
+
functions are used to iterate through the children of
+
.Fa obj .
+
The
+
.Fa which
+
argument to
+
.Fn libder_obj_child
+
specifies the index of the child requested, starting at
+
.Dv 0 .
+
The
+
.Fn DER_FOREACH_CHILD
+
and
+
.Fn DER_FOREACH_CHILD_SAFE
+
macros are provided for convenience.
+
The difference between these two is that it is safe to free the iterator in the
+
.Fn DER_FOREACH_CHILD_SAFE
+
loop body.
+
.Pp
+
The
+
.Fn libder_obj_type
+
and
+
.Fn libder_obj_type_simple
+
functions are used to get the type information about an
+
.Fa obj .
+
As usual, the
+
.Dq simple
+
variant will return the one-byte encoding of a tag between 0 and 30.
+
If the tag is actually larger than 30, then all of the lower 5 bits will be set
+
to indicate that it's a long tag, and that the application should have used
+
.Fn libder_obj_type
+
instead.
+
.Pp
+
The
+
.Fn libder_obj_data
+
function returns a pointer to the
+
.Fa data
+
from
+
.Fa obj ,
+
and updates
+
.Fa *sz
+
with the data's size.
+
Note that the data is not copied out here, the application is responsible for
+
making its own copy of the returned buffer.
+
.Pp
+
The
+
.Fn libder_obj_dump
+
function is a debugging function that likely shouldn't be used.
+
A human readable representation of the provided
+
.Fa obj
+
will be written to the stream
+
.Fa fp .
+
.Sh SEE ALSO
+
.Xr libder 3 ,
+
.Xr libder_read 3 ,
+
.Xr libder_type 3 ,
+
.Xr libder_write 3
added external/libder/libder/libder_obj.c
@@ -0,0 +1,1172 @@
+
/*-
+
 * 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) {
+
		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) {
+
		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);
+

+
	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) {
+
		free(coalesced_data);
+
		return (false);
+
	}
+

+
	/* Finally, swap out the payload. */
+
	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);
+
}
added external/libder/libder/libder_private.h
@@ -0,0 +1,162 @@
+
/*-
+
 * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

+
#pragma once
+

+
#include <sys/param.h>
+

+
#include <assert.h>
+
#include <signal.h>
+
#include <stdbool.h>
+

+
#include "libder.h"
+

+
/* FreeBSD's sys/cdefs.h */
+
#ifndef __DECONST
+
#define	__DECONST(type, var)	((type)(uintptr_t)(const void *)(var))
+
#endif
+
#ifndef __unused
+
#define	__unused		__attribute__((__unused__))
+
#endif
+

+
/* FreeBSD's sys/params.h */
+
#ifndef nitems
+
#define	nitems(x)	(sizeof((x)) / sizeof((x)[0]))
+
#endif
+
#ifndef MIN
+
#define	MIN(a,b) (((a)<(b))?(a):(b))
+
#endif
+
#ifndef MAX
+
#define	MAX(a,b) (((a)>(b))?(a):(b))
+
#endif
+

+
struct libder_ctx;
+
struct libder_object;
+

+
struct libder_ctx {
+
	uint64_t		 normalize;
+
	size_t			 buffer_size;
+
	enum libder_error	 error;
+
	int			 verbose;
+
	bool			 strict;
+
	volatile sig_atomic_t	 abort;
+
};
+

+
struct libder_tag {
+
	union {
+
		uint8_t		 tag_short;
+
		uint8_t		*tag_long;
+
	};
+
	size_t			 tag_size;
+
	enum libder_ber_class	 tag_class;
+
	bool			 tag_constructed;
+
	bool			 tag_encoded;
+
};
+

+
struct libder_object {
+
	struct libder_tag	*type;
+
	size_t			 length;
+
	size_t			 nchildren;
+
	size_t			 disk_size;
+
	uint8_t			*payload;	/* NULL for sequences */
+
	struct libder_object	*children;
+
	struct libder_object	*parent;
+
	struct libder_object	*next;
+
};
+

+
static inline sig_atomic_t
+
libder_check_abort(struct libder_ctx *ctx)
+
{
+

+
	return (ctx->abort);
+
}
+

+
static inline void
+
libder_clear_abort(struct libder_ctx *ctx)
+
{
+

+
	ctx->abort = 1;
+
}
+

+
#define	LIBDER_PRIVATE	__attribute__((__visibility__("hidden")))
+

+
#define	DER_NORMALIZING(ctx, bit)	\
+
    (((ctx)->normalize & (LIBDER_NORMALIZE_ ## bit)) != 0)
+

+
static inline bool
+
libder_normalizing_type(const struct libder_ctx *ctx, const struct libder_tag *type)
+
{
+
	uint8_t tagval;
+

+
	assert(!type->tag_constructed);
+
	assert(!type->tag_encoded);
+
	assert(type->tag_class == BC_UNIVERSAL);
+
	assert(type->tag_short < 0x1f);
+

+
	tagval = type->tag_short;
+
	return ((ctx->normalize & LIBDER_NORMALIZE_TYPE_FLAG(tagval)) != 0);
+
}
+

+
/* All of the lower bits set. */
+
#define	BER_TYPE_LONG_MASK	0x1f
+

+
/*
+
 * Check if the type matches one of our universal types.
+
 */
+
static inline bool
+
libder_type_is(const struct libder_tag *type, uint8_t utype)
+
{
+

+
	if (type->tag_class != BC_UNIVERSAL || type->tag_encoded)
+
		return (false);
+
	if ((utype & BER_TYPE_CONSTRUCTED_MASK) != type->tag_constructed)
+
		return (false);
+

+
	utype &= ~BER_TYPE_CONSTRUCTED_MASK;
+
	return (utype == type->tag_short);
+
}
+

+
/*
+
 * We'll use this one a decent amount, so we'll keep it inline.  There's also
+
 * an _abi version that we expose as public interface via a 'libder_type_simple'
+
 * macro.
+
 */
+
#undef libder_type_simple
+

+
static inline uint8_t
+
libder_type_simple(const struct libder_tag *type)
+
{
+
	uint8_t encoded = type->tag_class << 6;
+

+
	assert(!type->tag_encoded);
+
	if (type->tag_constructed)
+
		encoded |= BER_TYPE_CONSTRUCTED_MASK;
+

+
	encoded |= type->tag_short;
+
	return (encoded);
+
}
+

+
size_t	 libder_get_buffer_size(struct libder_ctx *);
+
void	 libder_set_error(struct libder_ctx *, int, const char *, int);
+

+
#define	libder_set_error(ctx, error)	\
+
	libder_set_error((ctx), (error), __FILE__, __LINE__)
+

+
struct libder_object	*libder_obj_alloc_internal(struct libder_ctx *,
+
			    struct libder_tag *, uint8_t *, size_t, uint32_t);
+
#define	LDO_OWNTAG	0x0001	/* Object owns passed in tag */
+

+
size_t			 libder_size_length(size_t);
+
bool			 libder_is_valid_obj(struct libder_ctx *,
+
			    const struct libder_tag *, const uint8_t *, size_t, bool);
+
size_t			 libder_obj_disk_size(struct libder_object *, bool);
+
bool			 libder_obj_may_coalesce_children(const struct libder_object *);
+
bool			 libder_obj_coalesce_children(struct libder_object *, struct libder_ctx *);
+
bool			 libder_obj_normalize(struct libder_object *, struct libder_ctx *);
+

+
struct libder_tag	*libder_type_alloc(void);
+
void			 libder_type_release(struct libder_tag *);
+
void			 libder_normalize_type(struct libder_ctx *, struct libder_tag *);
added external/libder/libder/libder_read.3
@@ -0,0 +1,101 @@
+
.\"
+
.\" SPDX-Copyright-Identifier: BSD-2-Clause
+
.\"
+
.\" Copyright (C) 2024 Kyle Evans <kevans@FreeBSD.org>
+
.\"
+
.Dd March 2, 2024
+
.Dt LIBDER_READ 3
+
.Os
+
.Sh NAME
+
.Nm libder_read ,
+
.Nm libder_read_fd ,
+
.Nm libder_read_file
+
.Nd reading DER encoded streams
+
.Sh LIBRARY
+
.Lb libder
+
.Sh SYNOPSIS
+
.In libder.h
+
.Ft struct libder_object *
+
.Fn libder_read "struct libder_ctx *ctx" "const uint8_t *buf" "size_t *bufsz"
+
.Ft struct libder_object *
+
.Fn libder_read_fd "struct libder_ctx *ctx" "int fd" "size_t *readsz"
+
.Ft struct libder_object *
+
.Fn libder_read_file "struct libder_ctx *ctx" "FILE *fp" "size_t *readsz"
+
.Sh DESCRIPTION
+
The
+
.Nm
+
family of functions are used to parse BER/DER encoded data into an object tree
+
that
+
.Xr libder 3
+
can work with.
+
All of these functions will return an object on success and update
+
.Fa *readsz
+
with the number of bytes consumed, or
+
.Dv NULL
+
on failure.
+
.Pp
+
The
+
.Fn libder_read
+
function will read from a buffer
+
.Fa buf
+
of known size
+
.Fa bufsz .
+
It is not considered an error for
+
.Fa buf
+
to have contents past the first valid object encountered.
+
The application is
+
expected to check
+
.Fa *bufsz
+
upon success and determine if any residual buffer exists, and if that residual
+
is OK.
+
.Pp
+
.Xr libder 3
+
can also stream a BER encoded object with either of the
+
.Fn libder_read_fd
+
or
+
.Fn libder_read_file
+
functions from a file descriptor or
+
.Xr stdio 3
+
stream respectively.
+
Both functions will try very hard not to over-read from the stream to avoid
+
putting it in a precarious state, but bogus looking data may still cause them
+
to consume more of the stream than intended.
+
.Pp
+
Note that
+
.Fn libder_read_fd
+
will ignore an
+
.Ev EINTR
+
return value from
+
.Xr read 2
+
by default and continue reading from the
+
.Fa fd .
+
If the application is signalled, it can abort the
+
.Xr read 2
+
operation instead with
+
.Xr libder_abort 3 .
+
Note that
+
.Nm libder
+
does not currently have other points that an abort can be signalled from, so if
+
.Fn libder_read_fd
+
is not specifically waiting for data from the
+
.Va fd
+
when a signal hits, then the operation will continue until successful with
+
one exception.
+
If
+
.Xr libder_abort 3
+
is called at any other point in the middle of
+
.Fn libder_read_fd ,
+
then the abort flag will not be cleared until it does receive an interrupted
+
.Xr read 2
+
call, or until the next call to one of the
+
.Nm
+
family of functions.
+
In the future,
+
.Nm
+
may support resuming an aborted operation and allow cancellation at other
+
specific points within the operation.
+
.Sh SEE ALSO
+
.Xr libder 3 ,
+
.Xr libder_obj 3 ,
+
.Xr libder_type 3 ,
+
.Xr libder_write 3
added external/libder/libder/libder_read.c
@@ -0,0 +1,818 @@
+
/*-
+
 * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

+
#include <sys/types.h>
+

+
#include <assert.h>
+
#include <err.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <poll.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+

+
#include "libder_private.h"
+

+
enum libder_stream_type {
+
	LDST_NONE,
+
	LDST_FD,
+
	LDST_FILE,
+
};
+

+
struct libder_payload {
+
	bool			 payload_heap;
+
	uint8_t			*payload_data;
+
	size_t			 payload_size;
+
};
+

+
struct libder_stream {
+
	enum libder_stream_type	 stream_type;
+
	struct libder_ctx	*stream_ctx;
+
	uint8_t			*stream_buf;
+
	size_t			 stream_bufsz;
+

+
	size_t			 stream_offset;
+
	size_t			 stream_resid;
+
	size_t			 stream_consumed;
+
	size_t			 stream_last_commit;
+

+
	union {
+
		const uint8_t	*stream_src_buf;
+
		FILE		*stream_src_file;
+
		int		 stream_src_fd;
+
	};
+

+
	int			 stream_error;
+
	bool			 stream_eof;
+
};
+

+
static uint8_t *
+
payload_move(struct libder_payload *payload, size_t *sz)
+
{
+
	uint8_t *data;
+
	size_t datasz;
+

+
	data = NULL;
+
	datasz = payload->payload_size;
+
	if (payload->payload_heap) {
+
		data = payload->payload_data;
+
	} else if (datasz > 0) {
+
		data = malloc(datasz);
+
		if (data == NULL)
+
			return (NULL);
+

+
		memcpy(data, payload->payload_data, datasz);
+
	}
+

+
	payload->payload_heap = false;
+
	payload->payload_data = NULL;
+
	payload->payload_size = 0;
+

+
	*sz = datasz;
+
	return (data);
+
}
+

+
static void
+
payload_free(struct libder_payload *payload)
+
{
+

+
	if (!payload->payload_heap)
+
		return;
+

+
	free(payload->payload_data);
+

+
	payload->payload_heap = false;
+
	payload->payload_data = NULL;
+
	payload->payload_size = 0;
+
}
+

+
static bool
+
libder_stream_init(struct libder_ctx *ctx, struct libder_stream *stream)
+
{
+
	size_t buffer_size;
+

+
	stream->stream_ctx = ctx;
+
	stream->stream_error = 0;
+
	stream->stream_eof = false;
+
	stream->stream_offset = 0;
+
	stream->stream_consumed = 0;
+
	stream->stream_last_commit = 0;
+
	if (stream->stream_type == LDST_NONE) {
+
		assert(stream->stream_src_buf != NULL);
+
		assert(stream->stream_bufsz != 0);
+
		assert(stream->stream_resid != 0);
+

+
		return (true);
+
	}
+

+
	buffer_size = libder_get_buffer_size(ctx);
+
	assert(buffer_size != 0);
+

+
	stream->stream_buf = malloc(buffer_size);
+
	if (stream->stream_buf == NULL) {
+
		libder_set_error(ctx, LDE_NOMEM);
+
	} else {
+
		stream->stream_bufsz = buffer_size;
+
		stream->stream_resid = 0;	/* Nothing read yet */
+
	}
+

+
	return (stream->stream_buf != NULL);
+
}
+

+
static void
+
libder_stream_free(struct libder_stream *stream)
+
{
+
	free(stream->stream_buf);
+
}
+

+
static void
+
libder_stream_commit(struct libder_stream *stream)
+
{
+

+
	if (stream->stream_offset <= stream->stream_last_commit)
+
		return;
+

+
	stream->stream_consumed += stream->stream_offset - stream->stream_last_commit;
+
	stream->stream_last_commit = stream->stream_offset;
+
}
+

+
static bool
+
libder_stream_dynamic(const struct libder_stream *stream)
+
{
+

+
	return (stream->stream_type != LDST_NONE);
+
}
+

+
static bool
+
libder_stream_eof(const struct libder_stream *stream)
+
{
+

+
	/*
+
	 * We're not EOF until we're both EOF and have processed all of the data
+
	 * remaining in the buffer.
+
	 */
+
	return (stream->stream_eof && stream->stream_resid == 0);
+
}
+

+
static void
+
libder_stream_repack(struct libder_stream *stream)
+
{
+

+
	/*
+
	 * Nothing to do, data's already at the beginning.
+
	 */
+
	if (stream->stream_offset == 0)
+
		return;
+

+
	/*
+
	 * If there's data in-flight, we'll repack it back to the beginning so
+
	 * that we can store more with fewer calls to refill.  If there's no
+
	 * data in-flight, we naturally just reset the offset.
+
	 */
+
	if (stream->stream_resid != 0) {
+
		uint8_t *dst = &stream->stream_buf[0];
+
		uint8_t *src = &stream->stream_buf[stream->stream_offset];
+

+
		memmove(dst, src, stream->stream_resid);
+
	}
+

+
	stream->stream_last_commit -= stream->stream_offset;
+
	stream->stream_offset = 0;
+
}
+

+
static const uint8_t *
+
libder_stream_refill(struct libder_stream *stream, size_t req)
+
{
+
	size_t offset = stream->stream_offset;
+
	const uint8_t *src;
+
#ifndef NDEBUG
+
	const uint8_t *bufend;
+
#endif
+
	uint8_t *refill_buf;
+
	size_t bufleft, freadsz, needed, totalsz;
+
	ssize_t readsz;
+

+
	/*
+
	 * For non-streaming, we just fulfill requests straight out of
+
	 * the source buffer.
+
	 */
+
	if (stream->stream_type == LDST_NONE)
+
		src = stream->stream_src_buf;
+
	else
+
		src = stream->stream_buf;
+

+
	if (stream->stream_resid >= req) {
+
		stream->stream_offset += req;
+
		stream->stream_resid -= req;
+
		return (&src[offset]);
+
	}
+

+
	/* Cannot refill the non-streaming type. */
+
	if (stream->stream_type == LDST_NONE) {
+
		stream->stream_eof = true;
+
		return (NULL);
+
	}
+

+
	bufleft = stream->stream_bufsz - (stream->stream_offset + stream->stream_resid);
+

+
	/*
+
	 * If we can't fit all of our data in the remainder of the buffer, we'll
+
	 * try to repack it to just fit as much as we can in.
+
	 */
+
	if (req > bufleft && stream->stream_offset != 0) {
+
		libder_stream_repack(stream);
+

+
		bufleft = stream->stream_bufsz - stream->stream_resid;
+
		offset = stream->stream_offset;
+
	}
+

+
	refill_buf = &stream->stream_buf[offset + stream->stream_resid];
+
	needed = req - stream->stream_resid;
+

+
	assert(needed <= bufleft);
+

+
#ifndef NDEBUG
+
	bufend = &stream->stream_buf[stream->stream_bufsz];
+
#endif
+
	totalsz = 0;
+

+
	switch (stream->stream_type) {
+
	case LDST_FILE:
+
		assert(stream->stream_src_file != NULL);
+

+
		while (needed != 0) {
+
			assert(refill_buf + needed <= bufend);
+

+
			freadsz = fread(refill_buf, 1, needed, stream->stream_src_file);
+
			if (freadsz == 0) {
+
				/*
+
				 * Error always put us into EOF state.
+
				 */
+
				stream->stream_eof = true;
+
				if (ferror(stream->stream_src_file))
+
					stream->stream_error = 1;
+
				break;
+
			}
+

+
			stream->stream_resid += freadsz;
+
			refill_buf += freadsz;
+
			needed -= freadsz;
+
			totalsz += freadsz;
+
		}
+
		break;
+
	case LDST_FD:
+
		assert(stream->stream_src_fd >= 0);
+

+
		while (needed != 0) {
+
			assert(refill_buf + needed <= bufend);
+

+
			readsz = read(stream->stream_src_fd, refill_buf, needed);
+
			if (readsz <= 0) {
+
				/*
+
				 * In the future, we should likely make this
+
				 * configurable in some sense, but for now this
+
				 * seems fine.  If, e.g., we caught a SIGINT,
+
				 * the application could always just close the
+
				 * fd on us if we should bail out.  The problem
+
				 * right now is that we have no way to resume a
+
				 * partial transfer.
+
				 */
+
				if (readsz < 0 && errno == EINTR &&
+
				    !libder_check_abort(stream->stream_ctx))
+
					continue;
+
				stream->stream_eof = true;
+
				if (readsz < 0) {
+
					stream->stream_ctx->abort = false;
+
					stream->stream_error = errno;
+
					if (stream->stream_ctx->verbose > 0)
+
						warn("libder_read");
+
				}
+
				break;
+
			}
+

+
			stream->stream_resid += readsz;
+
			refill_buf += readsz;
+
			needed -= readsz;
+
			totalsz += readsz;
+
		}
+

+
		break;
+
	case LDST_NONE:
+
		assert(0 && "Unrecognized stream type");
+
		break;
+
	}
+

+
	/*
+
	 * For streaming types, we commit as soon as we refill the buffer because
+
	 * we can't just rewind.
+
	 */
+
	stream->stream_consumed += totalsz;
+
	stream->stream_last_commit += totalsz;
+

+
	if (needed != 0) {
+
		if (stream->stream_error != 0)
+
			libder_set_error(stream->stream_ctx, LDE_STREAMERR);
+
		return (NULL);
+
	} else {
+
		stream->stream_offset += req;
+
		stream->stream_resid -= req;
+
	}
+

+
	return (&stream->stream_buf[offset]);
+
}
+

+
#define	BER_TYPE_LONG_BATCH	0x04
+

+
static bool
+
der_read_structure_tag(struct libder_ctx *ctx, struct libder_stream *stream,
+
    struct libder_tag *type)
+
{
+
	const uint8_t *buf;
+
	uint8_t *longbuf = NULL, val;
+
	size_t longbufsz = 0, offset = 0, received = 0;
+

+
	for (;;) {
+
		/*
+
		 * We have to refill one byte at a time to avoid overreading
+
		 * into the structure size.
+
		 */
+
		if ((buf = libder_stream_refill(stream, 1)) == NULL) {
+
			free(longbuf);
+
			if (!libder_stream_eof(stream))
+
				libder_set_error(ctx, LDE_SHORTHDR);
+
			return (false);
+
		}
+

+
		received++;
+
		val = buf[0];
+
		if (received == 1) {
+
			/* Deconstruct the class and p/c */
+
			type->tag_class = BER_TYPE_CLASS(val);
+
			type->tag_constructed = BER_TYPE_CONSTRUCTED(val);
+

+
			/* Long form, or short form? */
+
			if (BER_TYPE(val) != BER_TYPE_LONG_MASK) {
+
				type->tag_short = BER_TYPE(val);
+
				type->tag_size = sizeof(uint8_t);
+
				type->tag_encoded = false;
+

+
				return (true);
+
			}
+

+
			/*
+
			 * No content from this one, grab another byte.
+
			 */
+
			type->tag_encoded = true;
+
			continue;
+
		}
+

+
		/* We might normalize it later, depending on flags. */
+
		if (offset == 0 && (val & 0x7f) == 0 && ctx->strict) {
+
			libder_set_error(ctx, LDE_STRICT_TAG);
+
			return (false);
+
		}
+

+
		/* XXX Impose a max size? Perhaps configurable. */
+
		if (offset == longbufsz) {
+
			uint8_t *next;
+
			size_t nextsz;
+

+
			nextsz = longbufsz + BER_TYPE_LONG_BATCH;
+
			next = realloc(longbuf, nextsz * sizeof(*longbuf));
+
			if (next == NULL) {
+
				free(longbuf);
+
				libder_set_error(ctx, LDE_NOMEM);
+
				return (false);
+
			}
+

+
			longbuf = next;
+
			longbufsz = nextsz;
+
		}
+

+
		longbuf[offset++] = val;
+

+
		if ((val & 0x80) == 0)
+
			break;
+
	}
+

+
	type->tag_long = longbuf;
+
	type->tag_size = offset;
+

+
	libder_normalize_type(ctx, type);
+

+
	return (true);
+
}
+

+
static int
+
der_read_structure(struct libder_ctx *ctx, struct libder_stream *stream,
+
    struct libder_tag *type, struct libder_payload *payload, bool *varlen)
+
{
+
	const uint8_t *buf;
+
	size_t rsz, offset, resid;
+
	uint8_t bsz;
+

+
	rsz = 0;
+
	if (!der_read_structure_tag(ctx, stream, type)) {
+
		return (-1);
+
	}
+

+
	if ((buf = libder_stream_refill(stream, 1)) == NULL) {
+
		if (!libder_stream_eof(stream))
+
			libder_set_error(ctx, LDE_SHORTHDR);
+
		goto failed;
+
	}
+

+
	bsz = *buf++;
+

+
#define	LENBIT_LONG	0x80
+
	*varlen = false;
+
	if ((bsz & LENBIT_LONG) != 0) {
+
		/* Long or long form, bsz describes how many bytes we have. */
+
		bsz &= ~LENBIT_LONG;
+
		if (bsz != 0) {
+
			/* Long */
+
			if (bsz > sizeof(rsz)) {
+
				libder_set_error(ctx, LDE_LONGLEN);
+
				goto failed;	/* Only support up to long bytes. */
+
			} else if ((buf = libder_stream_refill(stream, bsz)) == NULL) {
+
				libder_set_error(ctx, LDE_SHORTHDR);
+
				goto failed;
+
			}
+

+
			rsz = 0;
+
			for (int i = 0; i < bsz; i++) {
+
				if (i != 0)
+
					rsz <<= 8;
+
				rsz |= *buf++;
+
			}
+
		} else {
+
			if (ctx->strict && !type->tag_constructed) {
+
				libder_set_error(ctx, LDE_STRICT_PVARLEN);
+
				goto failed;
+
			}
+

+
			*varlen = true;
+
		}
+
	} else {
+
		/* Short form */
+
		rsz = bsz;
+
	}
+

+
	if (rsz != 0) {
+
		assert(!*varlen);
+

+
		/*
+
		 * If we're not running a dynamic stream, we can just use a
+
		 * pointer into the buffer.  The caller may copy the payload out
+
		 * anyways, but there's no sense in doing it up-front in case we
+
		 * hit an error in between then and now.
+
		 */
+
		if (!libder_stream_dynamic(stream)) {
+
			/*
+
			 * This is a little dirty, but the caller won't mutate
+
			 * the data -- it'll either strictly read it, or it will
+
			 * copy it out to a known-mutable region.
+
			 */
+
			payload->payload_data =
+
			    __DECONST(void *, libder_stream_refill(stream, rsz));
+
			payload->payload_heap = false;
+
			if (payload->payload_data == NULL) {
+
				libder_set_error(ctx, LDE_SHORTDATA);
+
				goto failed;
+
			}
+
		} else {
+
			uint8_t *payload_data;
+

+
			payload_data = NULL;
+

+
			offset = 0;
+
			resid = rsz;
+
			while (resid != 0) {
+
				uint8_t *next_data;
+
				size_t req;
+

+
				req = MIN(stream->stream_bufsz, resid);
+
				if ((buf = libder_stream_refill(stream, req)) == NULL) {
+
					free(payload_data);
+

+
					libder_set_error(ctx, LDE_SHORTDATA);
+
					goto failed;
+
				}
+

+
				next_data = realloc(payload_data, offset + req);
+
				if (next_data == NULL) {
+
					free(payload_data);
+

+
					libder_set_error(ctx, LDE_NOMEM);
+
					goto failed;
+
				}
+

+
				payload_data = next_data;
+
				next_data = NULL;
+

+
				memcpy(&payload_data[offset], buf, req);
+
				offset += req;
+
				resid -= req;
+
			}
+

+
			payload->payload_heap = true;
+
			payload->payload_data = payload_data;
+
		}
+

+
		payload->payload_size = rsz;
+
	}
+

+
	libder_stream_commit(stream);
+
	return (0);
+

+
failed:
+
	libder_type_release(type);
+
	return (-1);
+
}
+

+
static struct libder_object *
+
libder_read_object(struct libder_ctx *ctx, struct libder_stream *stream)
+
{
+
	struct libder_payload payload = { 0 };
+
	struct libder_object *child, **next, *obj;
+
	struct libder_stream memstream, *childstream;
+
	struct libder_tag type;
+
	int error;
+
	bool varlen;
+

+
	/* Peel off one structure. */
+
	obj = NULL;
+
	error = der_read_structure(ctx, stream, &type, &payload, &varlen);
+
	if (error != 0) {
+
		assert(payload.payload_data == NULL);
+
		return (NULL);	/* Error already set, if needed. */
+
	}
+

+
	if (!libder_is_valid_obj(ctx, &type, payload.payload_data,
+
	    payload.payload_size, varlen)) {
+
		/*
+
		 * libder_is_valid_obj may set a more specific error, e.g., a
+
		 * strict mode violation.
+
		 */
+
		if (ctx->error == LDE_NONE)
+
			libder_set_error(ctx, LDE_BADOBJECT);
+
		goto out;
+
	}
+

+
	if (!type.tag_constructed) {
+
		uint8_t *payload_data;
+
		size_t payloadsz;
+

+
		/*
+
		 * Primitive types cannot use the indefinite form, they must
+
		 * have an encoded size.
+
		 */
+
		if (varlen) {
+
			libder_set_error(ctx, LDE_BADVARLEN);
+
			goto out;
+
		}
+

+
		/*
+
		 * Copy the payload out now if it's not heap-allocated.
+
		 */
+
		payload_data = payload_move(&payload, &payloadsz);
+
		if (payload_data == NULL) {
+
			libder_set_error(ctx, LDE_NOMEM);
+
			goto out;
+
		}
+

+
		obj = libder_obj_alloc_internal(ctx, &type, payload_data,
+
		    payloadsz, 0);
+
		if (obj == NULL) {
+
			free(payload_data);
+
			libder_set_error(ctx, LDE_NOMEM);
+
			goto out;
+
		}
+

+
		libder_type_release(&type);
+
		return (obj);
+
	}
+

+
	obj = libder_obj_alloc_internal(ctx, &type, NULL, 0, 0);
+
	if (obj == NULL) {
+
		libder_set_error(ctx, LDE_NOMEM);
+
		goto out;
+
	}
+

+
	if (varlen) {
+
		childstream = stream;
+
	} else {
+
		memstream = (struct libder_stream){
+
			.stream_type = LDST_NONE,
+
			.stream_bufsz = payload.payload_size,
+
			.stream_resid = payload.payload_size,
+
			.stream_src_buf = payload.payload_data,
+
		};
+

+
		childstream = &memstream;
+
	}
+

+
	/* Enumerate children */
+
	next = &obj->children;
+
	for (;;) {
+
		child = libder_read_object(ctx, childstream);
+
		if (child == NULL) {
+
			/*
+
			 * We may not know how much data we have, so this is our
+
			 * normal terminal condition.
+
			 */
+
			if (ctx->error != LDE_NONE) {
+
				/* Free everything and bubble the error up. */
+
				libder_obj_free(obj);
+
				obj = NULL;
+
			}
+
			break;
+
		}
+

+
		if (libder_type_is(child->type, BT_RESERVED) &&
+
		    child->length == 0) {
+
			/*
+
			 * This child is just a marker; free it, don't leak it,
+
			 * and stop here.
+
			 */
+
			libder_obj_free(child);
+

+
			/* Malformed: shall not be present */
+
			if (!varlen) {
+
				if (ctx->strict) {
+
					libder_set_error(ctx, LDE_STRICT_EOC);
+
					libder_obj_free(obj);
+
					obj = NULL;
+
					break;
+
				}
+

+
				continue;
+
			}
+

+
			/* Error detection */
+
			varlen = false;
+
			break;
+
		}
+

+
		obj->nchildren++;
+
		child->parent = obj;
+
		*next = child;
+
		next = &child->next;
+
	}
+

+
	if (varlen) {
+
		libder_set_error(ctx, LDE_TRUNCVARLEN);
+
		libder_obj_free(obj);
+
		obj = NULL;
+
	}
+

+
out:
+
	libder_type_release(&type);
+
	payload_free(&payload);
+
	return (obj);
+
}
+

+
static struct libder_object *
+
libder_read_stream(struct libder_ctx *ctx, struct libder_stream *stream)
+
{
+
	struct libder_object *root;
+

+
	ctx->error = LDE_NONE;
+
	root = libder_read_object(ctx, stream);
+

+
	if (root != NULL && libder_type_is(root->type, BT_RESERVED) &&
+
	    root->length == 0) {
+
		/* Strict violation: must not appear. */
+
		if (ctx->strict)
+
			libder_set_error(ctx, LDE_STRICT_EOC);
+
		libder_obj_free(root);
+
		root = NULL;
+
	}
+
	if (root != NULL)
+
		assert(stream->stream_consumed != 0);
+
	return (root);
+
}
+

+
/*
+
 * Read the DER-encoded `data` into `ctx`.
+
 *
+
 * Returns an object on success, or NULL on failure.  *datasz is updated to
+
 * indicate the number of bytes consumed either way -- it will only be updated
+
 * in the failure case if at least one object was valid.
+
 */
+
struct libder_object *
+
libder_read(struct libder_ctx *ctx, const uint8_t *data, size_t *datasz)
+
{
+
	struct libder_stream *stream;
+
	struct libder_object *root;
+

+
	stream = malloc(sizeof(*stream));
+
	if (stream == NULL) {
+
		libder_set_error(ctx, LDE_NOMEM);
+
		return (NULL);
+
	}
+

+
	*stream = (struct libder_stream){
+
		.stream_type = LDST_NONE,
+
		.stream_bufsz = *datasz,
+
		.stream_resid = *datasz,
+
		.stream_src_buf = data,
+
	};
+

+
	libder_clear_abort(ctx);
+
	ctx->error = LDE_NONE;
+
	if (!libder_stream_init(ctx, stream)) {
+
		free(stream);
+
		return (NULL);
+
	}
+

+
	root = libder_read_stream(ctx, stream);
+
	if (stream->stream_consumed != 0)
+
		*datasz = stream->stream_consumed;
+

+
	libder_stream_free(stream);
+
	free(stream);
+

+
	return (root);
+
}
+

+
/*
+
 * Ditto above, but with an fd.  *consumed is not ignored on entry, and returned
+
 * with the number of bytes read from fd if consumed is not NULL.  libder(3)
+
 * tries to not over-read if an invalid structure is detected.
+
 */
+
struct libder_object *
+
libder_read_fd(struct libder_ctx *ctx, int fd, size_t *consumed)
+
{
+
	struct libder_stream *stream;
+
	struct libder_object *root;
+

+
	stream = malloc(sizeof(*stream));
+
	if (stream == NULL) {
+
		libder_set_error(ctx, LDE_NOMEM);
+
		return (NULL);
+
	}
+

+
	*stream = (struct libder_stream){
+
		.stream_type = LDST_FD,
+
		.stream_src_fd = fd,
+
	};
+

+
	root = NULL;
+
	libder_clear_abort(ctx);
+
	ctx->error = LDE_NONE;
+
	if (!libder_stream_init(ctx, stream)) {
+
		free(stream);
+
		return (NULL);
+
	}
+

+
	root = libder_read_stream(ctx, stream);
+
	if (consumed != NULL && stream->stream_consumed != 0)
+
		*consumed = stream->stream_consumed;
+

+
	libder_stream_free(stream);
+
	free(stream);
+
	return (root);
+
}
+

+
/*
+
 * Ditto above, but with a FILE instead of an fd.
+
 */
+
struct libder_object *
+
libder_read_file(struct libder_ctx *ctx, FILE *fp, size_t *consumed)
+
{
+
	struct libder_stream *stream;
+
	struct libder_object *root;
+

+
	stream = malloc(sizeof(*stream));
+
	if (stream == NULL) {
+
		libder_set_error(ctx, LDE_NOMEM);
+
		return (NULL);
+
	}
+

+
	*stream = (struct libder_stream){
+
		.stream_type = LDST_FILE,
+
		.stream_src_file = fp,
+
	};
+

+
	root = NULL;
+
	libder_clear_abort(ctx);
+
	ctx->error = LDE_NONE;
+
	if (!libder_stream_init(ctx, stream)) {
+
		free(stream);
+
		return (NULL);
+
	}
+

+
	root = libder_read_stream(ctx, stream);
+
	if (consumed != NULL && stream->stream_consumed != 0)
+
		*consumed = stream->stream_consumed;
+

+
	libder_stream_free(stream);
+
	free(stream);
+

+
	return (root);
+
}
added external/libder/libder/libder_type.3
@@ -0,0 +1,71 @@
+
.\"
+
.\" SPDX-Copyright-Identifier: BSD-2-Clause
+
.\"
+
.\" Copyright (C) 2024 Kyle Evans <kevans@FreeBSD.org>
+
.\"
+
.Dd March 2, 2024
+
.Dt LIBDER_TYPE 3
+
.Os
+
.Sh NAME
+
.Nm libder_type ,
+
.Nm libder_type_alloc_simple ,
+
.Nm libder_type_dup ,
+
.Nm libder_type_free ,
+
.Nm libder_type_simple
+
.Nd creating DER types
+
.Sh LIBRARY
+
.Lb libder
+
.Sh SYNOPSIS
+
.In libder.h
+
.Ft struct libder_tag *
+
.Fn libder_type_alloc_simple "struct libder_ctx *ctx" "uint8_t type"
+
.Ft struct libder_tag *
+
.Fn libder_type_dup "struct libder_ctx *ctx" "const struct libder_tag *type"
+
.Ft void
+
.Fn libder_type_free "struct libder_tag *type"
+
.Ft uint8_t
+
.Fn libder_type_simple "const struct libder_tag *type"
+
.Sh DESCRIPTION
+
The
+
.Nm
+
family of functions operate on the
+
.Xr libder 3
+
type primitive.
+
These functions are largely useless as currently implemented, as
+
.Xr libder_obj 3
+
has a method for allocating an object using a simple tag directly.
+
In the future,
+
.Nm
+
will have an API for importing encoded tags that need more than the
+
.Dq simple
+
one byte form (tags 0-30).
+
.Pp
+
The
+
.Fn libder_type_alloc_simple
+
function allocates a new type from the
+
.Dq simple
+
one byte form.
+
This type may be subsequently passed to
+
.Xr libder_obj_alloc 3 .
+
.Pp
+
The
+
.Fn libder_type_dup
+
function duplicates an existing type, and the
+
.Fn libder_type_free
+
function frees the type.
+
.Pp
+
The
+
.Ft libder_type_simple
+
function encodes the given
+
.Fa type
+
in the
+
.Dq simple
+
one byte buffer form.
+
In this form, the class bits and the primitive and constructed bits are encoded
+
in the three most significant bits, and the lower five bits are used to encode
+
a tag number between 0 and 30.
+
.Sh SEE ALSO
+
.Xr libder 3 ,
+
.Xr libder_obj 3 ,
+
.Xr libder_read 3 ,
+
.Xr libder_write 3
added external/libder/libder/libder_type.c
@@ -0,0 +1,150 @@
+
/*-
+
 * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

+
#include <stdint.h>
+
#include <stdlib.h>
+
#include <string.h>
+

+
#include "libder_private.h"
+

+
uint8_t
+
libder_type_simple_abi(const struct libder_tag *type)
+
{
+

+
	return (libder_type_simple(type));
+
}
+

+
/*
+
 * We'll likely expose this in the form of libder_type_import(), which validates
+
 * and allocates a tag.
+
 */
+
LIBDER_PRIVATE struct libder_tag *
+
libder_type_alloc(void)
+
{
+

+
	return (calloc(1, sizeof(struct libder_tag)));
+
}
+

+
struct libder_tag *
+
libder_type_dup(struct libder_ctx *ctx, const struct libder_tag *dtype)
+
{
+
	struct libder_tag *type;
+

+
	type = libder_type_alloc();
+
	if (type == NULL) {
+
		libder_set_error(ctx, LDE_NOMEM);
+
		return (NULL);
+
	}
+

+
	memcpy(type, dtype, sizeof(*dtype));
+

+
	if (type->tag_encoded) {
+
		uint8_t *tdata;
+

+
		/* Deep copy the tag data. */
+
		tdata = malloc(type->tag_size);
+
		if (tdata == NULL) {
+
			libder_set_error(ctx, LDE_NOMEM);
+

+
			/*
+
			 * Don't accidentally free the caller's buffer; it may
+
			 * be an external user of the API.
+
			 */
+
			type->tag_long = NULL;
+
			type->tag_size = 0;
+
			libder_type_free(type);
+
			return (NULL);
+
		}
+

+
		memcpy(tdata, dtype->tag_long, dtype->tag_size);
+
		type->tag_long = tdata;
+
	}
+

+
	return (type);
+
}
+

+
struct libder_tag *
+
libder_type_alloc_simple(struct libder_ctx *ctx, uint8_t typeval)
+
{
+
	struct libder_tag *type;
+

+
	type = libder_type_alloc();
+
	if (type == NULL) {
+
		libder_set_error(ctx, LDE_NOMEM);
+
		return (NULL);
+
	}
+

+
	type->tag_size = sizeof(typeval);
+
	type->tag_class = BER_TYPE_CLASS(typeval);
+
	type->tag_constructed = BER_TYPE_CONSTRUCTED(typeval);
+
	type->tag_short = BER_TYPE(typeval);
+
	return (type);
+
}
+

+
LIBDER_PRIVATE void
+
libder_type_release(struct libder_tag *type)
+
{
+

+
	if (type->tag_encoded) {
+
		free(type->tag_long);
+
		type->tag_long = NULL;
+

+
		/*
+
		 * Leaving type->tag_encoded set in case it helps us catch some
+
		 * bogus re-use of the type; we'd surface that as a null ptr
+
		 * deref as they think they should be using tag_long.
+
		 */
+
	}
+
}
+

+
void
+
libder_type_free(struct libder_tag *type)
+
{
+

+
	if (type == NULL)
+
		return;
+

+
	libder_type_release(type);
+
	free(type);
+
}
+

+
LIBDER_PRIVATE void
+
libder_normalize_type(struct libder_ctx *ctx, struct libder_tag *type)
+
{
+
	uint8_t tagval;
+
	size_t offset;
+

+
	if (!type->tag_encoded || !DER_NORMALIZING(ctx, TAGS))
+
		return;
+

+
	/*
+
	 * Strip any leading 0's off -- not possible in strict mode.
+
	 */
+
	for (offset = 0; offset < type->tag_size - 1; offset++) {
+
		if ((type->tag_long[offset] & 0x7f) != 0)
+
			break;
+
	}
+

+
	assert(offset == 0 || !ctx->strict);
+
	if (offset != 0) {
+
		type->tag_size -= offset;
+
		memmove(&type->tag_long[0], &type->tag_long[offset],
+
		    type->tag_size);
+
	}
+

+
	/*
+
	 * We might be able to strip it down to a unencoded tag_short, if only
+
	 * the lower 5 bits are in use.
+
	 */
+
	if (type->tag_size != 1 || (type->tag_long[0] & ~0x1e) != 0)
+
		return;
+

+
	tagval = type->tag_long[0];
+

+
	free(type->tag_long);
+
	type->tag_short = tagval;
+
	type->tag_encoded = false;
+
}
added external/libder/libder/libder_write.3
@@ -0,0 +1,54 @@
+
.\"
+
.\" SPDX-Copyright-Identifier: BSD-2-Clause
+
.\"
+
.\" Copyright (C) 2024 Kyle Evans <kevans@FreeBSD.org>
+
.\"
+
.Dd March 2, 2024
+
.Dt LIBDER_WRITE 3
+
.Os
+
.Sh NAME
+
.Nm libder_write
+
.Nd writing DER encoded buffers
+
.Sh LIBRARY
+
.Lb libder
+
.Sh SYNOPSIS
+
.In libder.h
+
.Ft uint8_t *
+
.Fn libder_write "struct libder_ctx *ctx" "struct libder_object *root" "uint8_t *buf" "size_t *bufsize"
+
.Sh DESCRIPTION
+
The
+
.Fn libder_write
+
writes the specified
+
.Fa root
+
into the given
+
.Fa buf
+
of size
+
.Fa bufsize .
+
If a
+
.Dv NULL
+
and
+
.Dv 0
+
are passed in, then
+
.Fn libder_write
+
will alllocate a buffer just large enough to fit the encoded
+
.Fa root .
+
Upon successful write,
+
.Fn libder_write
+
will return a pointer to the buffer used, and
+
.Fa *bufsize
+
is updated to indicate how many bytes were written.
+
On failure,
+
.Dv NULL
+
is returned and
+
.Fa *bufsize
+
will remain unmodified.
+
.Pp
+
Normalization rules are applied at write time, if specified via
+
.Xr libder_set_normalize 3 .
+
Note that applications do not typically need to enable normalization, as they
+
are all enabled by default.
+
.Sh SEE ALSO
+
.Xr libder 3 ,
+
.Xr libder_obj 3 ,
+
.Xr libder_read 3 ,
+
.Xr libder_type 3
added external/libder/libder/libder_write.c
@@ -0,0 +1,228 @@
+
/*-
+
 * 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.h"
+
#include "libder_private.h"
+

+
struct memory_write_data {
+
	uint8_t		*buf;
+
	size_t		 bufsz;
+
	size_t		 offset;
+
};
+

+
typedef bool (write_buffer_t)(void *, const uint8_t *, size_t);
+

+
static bool
+
libder_write_object_tag(struct libder_ctx *ctx __unused,
+
    const struct libder_object *obj, write_buffer_t *write_buffer, void *cookie)
+
{
+
	const struct libder_tag *type = obj->type;
+
	uint8_t value;
+

+
	if (!type->tag_encoded) {
+
		value = libder_type_simple(type);
+
		return (write_buffer(cookie, &value, sizeof(value)));
+
	}
+

+
	/* Write out the tag info first. */
+
	value = BER_TYPE_LONG_MASK;
+
	value |= type->tag_class << 6;
+
	if (type->tag_constructed)
+
		value |= BER_TYPE_CONSTRUCTED_MASK;
+

+
	if (!write_buffer(cookie, &value, sizeof(value)))
+
		return (false);
+

+
	/* Write out the encoded tag next. */
+
	return (write_buffer(cookie, type->tag_long, type->tag_size));
+
}
+

+
static bool
+
libder_write_object_header(struct libder_ctx *ctx, struct libder_object *obj,
+
    write_buffer_t *write_buffer, void *cookie)
+
{
+
	size_t size;
+
	uint8_t sizelen, value;
+

+
	if (!libder_write_object_tag(ctx, obj, write_buffer, cookie))
+
		return (false);
+

+
	size = obj->disk_size;
+
	sizelen = libder_size_length(size);
+

+
	if (sizelen == 1) {
+
		assert((size & ~0x7f) == 0);
+

+
		value = size;
+
		if (!write_buffer(cookie, &value, sizeof(value)))
+
			return (false);
+
	} else {
+
		/*
+
		 * Protocol supports at most 0x7f size bytes, but we can only
+
		 * do up to a size_t.
+
		 */
+
		uint8_t sizebuf[sizeof(size_t)], *sizep;
+

+
		sizelen--;	/* Remove the lead byte. */
+

+
		value = 0x80 | sizelen;
+
		if (!write_buffer(cookie, &value, sizeof(value)))
+
			return (false);
+

+
		sizep = &sizebuf[0];
+
		for (uint8_t i = 0; i < sizelen; i++)
+
			*sizep++ = (size >> ((sizelen - i - 1) * 8)) & 0xff;
+

+
		if (!write_buffer(cookie, &sizebuf[0], sizelen))
+
			return (false);
+
	}
+

+
	return (true);
+
}
+

+
static bool
+
libder_write_object_payload(struct libder_ctx *ctx __unused,
+
    struct libder_object *obj, write_buffer_t *write_buffer, void *cookie)
+
{
+
	uint8_t *payload = obj->payload;
+
	size_t length = obj->length;
+

+
	/* We don't expect `obj->payload` to be valid for a zero-size value. */
+
	if (length == 0)
+
		return (true);
+

+
	/*
+
	 * We allow a NULL payload with a non-zero length to indicate that an
+
	 * object should write zeroes out, we just didn't waste the memory on
+
	 * these small allocations.  Ideally if it's more than just one or two
+
	 * zeroes we're instead allocating a buffer for it and doing some more
+
	 * efficient copying from there.
+
	 */
+
	if (payload == NULL) {
+
		uint8_t zero = 0;
+

+
		for (size_t i = 0; i < length; i++) {
+
			if (!write_buffer(cookie, &zero, 1))
+
				return (false);
+
		}
+

+
		return (true);
+
	}
+

+
	return (write_buffer(cookie, payload, length));
+
}
+

+
static bool
+
libder_write_object(struct libder_ctx *ctx, struct libder_object *obj,
+
    write_buffer_t *write_buffer, void *cookie)
+
{
+
	struct libder_object *child;
+

+
	if (DER_NORMALIZING(ctx, CONSTRUCTED) && !libder_obj_coalesce_children(obj, ctx))
+
		return (false);
+

+
	/* Write out this object's header first */
+
	if (!libder_write_object_header(ctx, obj, write_buffer, cookie))
+
		return (false);
+

+
	/* Write out the payload. */
+
	if (obj->children == NULL)
+
		return (libder_write_object_payload(ctx, obj, write_buffer, cookie));
+

+
	assert(obj->type->tag_constructed);
+

+
	/* Recurse on each child. */
+
	DER_FOREACH_CHILD(child, obj) {
+
		if (!libder_write_object(ctx, child, write_buffer, cookie))
+
			return (false);
+
	}
+

+
	return (true);
+
}
+

+
static bool
+
memory_write(void *cookie, const uint8_t *data, size_t datasz)
+
{
+
	struct memory_write_data *mwrite = cookie;
+
	uint8_t *dst = &mwrite->buf[mwrite->offset];
+
	size_t left;
+

+
	/* Small buffers should have been rejected long before now. */
+
	left = mwrite->bufsz - mwrite->offset;
+
	assert(datasz <= left);
+

+
	memcpy(dst, data, datasz);
+
	mwrite->offset += datasz;
+
	return (true);
+
}
+

+
/*
+
 * Writes the object rooted at `root` to the buffer.  If `buf` == NULL and
+
 * `*bufsz` == 0, we'll allocate a buffer just large enough to hold the result
+
 * and pass the size back via `*bufsz`.  If a pre-allocated buffer is passed,
+
 * we may still update `*bufsz` if normalization made the buffer smaller.
+
 *
+
 * If the buffer is too small, *bufsz will be set to the size of buffer needed.
+
 */
+
uint8_t *
+
libder_write(struct libder_ctx *ctx, struct libder_object *root, uint8_t *buf,
+
    size_t *bufsz)
+
{
+
	struct memory_write_data mwrite = { 0 };
+
	size_t needed;
+

+
	/*
+
	 * We shouldn't really see buf == NULL with *bufsz != 0 or vice-versa.
+
	 * Combined, they mean that we should allocate whatever buffer size we
+
	 * need.
+
	 */
+
	if ((buf == NULL && *bufsz != 0) || (buf != NULL && *bufsz == 0))
+
		return (NULL);	/* XXX Surface error? */
+

+
	/*
+
	 * If we're doing any normalization beyond our standard size
+
	 * normalization, we apply those rules up front since they may alter our
+
	 * disk size every so slightly.
+
	 */
+
	if (ctx->normalize != 0 && !libder_obj_normalize(root, ctx))
+
		return (NULL);
+

+
	needed = libder_obj_disk_size(root, true);
+
	if (needed == 0)
+
		return (NULL);	/* Overflow */
+

+
	/* Allocate if we weren't passed a buffer. */
+
	if (*bufsz == 0) {
+
		*bufsz = needed;
+
		buf = malloc(needed);
+
		if (buf == NULL)
+
			return (NULL);
+
	} else if (needed > *bufsz) {
+
		*bufsz = needed;
+
		return (NULL);	/* Insufficient space */
+
	}
+

+
	/* Buffer large enough, write into it. */
+
	mwrite.buf = buf;
+
	mwrite.bufsz = *bufsz;
+
	if (!libder_write_object(ctx, root, &memory_write, &mwrite)) {
+
		free(buf);
+
		return (NULL);	/* XXX Error */
+
	}
+

+
	/*
+
	 * We don't normalize the in-memory representation of the tree, we do
+
	 * that as we're writing into the buffer.  It could be the case that we
+
	 * didn't need the full buffer as a result of normalization.
+
	 */
+
	*bufsz = mwrite.offset;
+

+
	return (buf);
+
}
added external/libder/tests/.gitignore
@@ -0,0 +1,12 @@
+
CORPUS*
+
crash-*
+
leak-*
+
oom-*
+
*.log
+

+
fuzz_*
+
test_*
+
!*.c
+
!*.h
+

+
make_corpus
added external/libder/tests/CMakeLists.txt
@@ -0,0 +1,41 @@
+
set(FUZZERS fuzz_parallel fuzz_stream fuzz_write)
+
set(UTILS )
+
set(TESTS test_privkey test_pubkey)
+

+
set(ALL_TESTS ${UTILS} ${TESTS})
+

+
if(BUILD_FUZZERS)
+
	set(UTILS ${UTILS} make_corpus)
+
	set(ALL_TESTS ${ALL_TESTS} ${FUZZERS} make_corpus)
+

+
	foreach(fuzzer IN LISTS FUZZERS)
+
		add_executable(${fuzzer} ${fuzzer}.c)
+

+
		target_compile_options(${fuzzer} PUBLIC -fsanitize=fuzzer)
+
		target_link_options(${fuzzer} PUBLIC -fsanitize=fuzzer)
+
	endforeach()
+

+
	target_link_options(fuzz_parallel PUBLIC -pthread)
+
endif()
+

+
foreach(prog IN LISTS UTILS TESTS)
+
	add_executable(${prog} ${prog}.c)
+
endforeach()
+

+
foreach(prog IN LISTS ALL_TESTS)
+
	target_include_directories(${prog} PRIVATE ${CMAKE_SOURCE_DIR}/libder)
+
	target_link_libraries(${prog} der_static)
+
endforeach()
+

+
add_custom_command(TARGET test_privkey POST_BUILD
+
	COMMAND ${CMAKE_COMMAND} -E copy
+
	${CMAKE_CURRENT_SOURCE_DIR}/repo.priv ${CMAKE_CURRENT_BINARY_DIR}/repo.priv)
+
add_custom_command(TARGET test_pubkey POST_BUILD
+
	COMMAND ${CMAKE_COMMAND} -E copy
+
	${CMAKE_CURRENT_SOURCE_DIR}/repo.pub ${CMAKE_CURRENT_BINARY_DIR}/repo.pub)
+

+
add_custom_target(check
+
	DEPENDS test_pubkey test_privkey
+
	COMMAND "${CMAKE_CURRENT_BINARY_DIR}/test_pubkey"
+
	COMMAND "${CMAKE_CURRENT_BINARY_DIR}/test_privkey"
+
)
added external/libder/tests/fuzz_parallel.c
@@ -0,0 +1,111 @@
+
/*-
+
 * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

+
#include <sys/param.h>
+
#include <sys/socket.h>
+

+
#include <assert.h>
+
#include <pthread.h>
+
#include <signal.h>
+
#include <stdbool.h>
+
#include <stdint.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <unistd.h>
+

+
#include <libder.h>
+

+
#include "fuzzers.h"
+

+
struct fuzz_frame {
+
	uint8_t		frame_threads;
+
};
+

+
struct thread_input {
+
	const uint8_t	*data;
+
	size_t		 datasz;
+
};
+

+
static void *
+
thread_main(void *cookie)
+
{
+
	const struct thread_input *input = cookie;
+
	struct libder_ctx *ctx;
+
	struct libder_object *obj;
+
	const uint8_t *data = input->data;
+
	size_t readsz, sz = input->datasz;
+

+
	ctx = libder_open();
+
	readsz = sz;
+
	obj = libder_read(ctx, data, &readsz);
+
	if (obj == NULL || readsz != sz)
+
		goto out;
+

+
	if (obj != NULL) {
+
		uint8_t *buf = NULL;
+
		size_t bufsz = 0;
+

+
		/*
+
		 * If we successfully read it, then it shouldn't
+
		 * overflow.  We're letting libder allocate the buffer,
+
		 * so we shouldn't be able to hit the 'too small' bit.
+
		 *
+
		 * I can't imagine what other errors might happen, so
+
		 * we'll just assert on it.
+
		 */
+
		buf = libder_write(ctx, obj, buf, &bufsz);
+
		if (buf == NULL)
+
			goto out;
+

+
		assert(bufsz != 0);
+

+
		free(buf);
+
	}
+

+
out:
+
	libder_obj_free(obj);
+
	libder_close(ctx);
+
	return (NULL);
+
}
+

+
int
+
LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz)
+
{
+
	const struct fuzz_frame *frame;
+
	pthread_t *threads;
+
	struct thread_input inp;
+
	size_t nthreads;
+

+
	if (sz <= sizeof(*frame))
+
		return (-1);
+

+
	frame = (const void *)data;
+
	data += sizeof(*frame);
+
	sz -= sizeof(*frame);
+

+
	if (frame->frame_threads < 2)
+
		return (-1);
+

+
	threads = malloc(sizeof(*threads) * frame->frame_threads);
+
	if (threads == NULL)
+
		return (-1);
+

+
	inp.data = data;
+
	inp.datasz = sz;
+

+
	for (nthreads = 0; nthreads < frame->frame_threads; nthreads++) {
+
		if (pthread_create(&threads[nthreads], NULL, thread_main,
+
		    &inp) != 0)
+
			break;
+
	}
+

+
	for (uint8_t i = 0; i < nthreads; i++)
+
		pthread_join(threads[i], NULL);
+

+
	free(threads);
+

+
	return (0);
+
}
added external/libder/tests/fuzz_stream.c
@@ -0,0 +1,246 @@
+
/*-
+
 * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

+
#include <sys/param.h>
+
#include <sys/socket.h>
+

+
#include <assert.h>
+
#include <pthread.h>
+
#include <signal.h>
+
#include <stdbool.h>
+
#include <stdio.h>
+
#include <unistd.h>
+

+
#include <libder.h>
+

+
#include "fuzzers.h"
+

+
struct supply_data {
+
	const uint8_t	*data;
+
	volatile size_t	 datasz;
+
	int		 socket;
+
};
+

+
static void *
+
supply_thread(void *data)
+
{
+
	struct supply_data *sdata = data;
+
	size_t sz = sdata->datasz;
+
	ssize_t writesz;
+

+
	do {
+
		writesz = write(sdata->socket, sdata->data, sz);
+

+
		data += writesz;
+
		sz -= writesz;
+
	} while (sz != 0 && writesz > 0);
+

+
	sdata->datasz = sz;
+
	shutdown(sdata->socket, SHUT_RDWR);
+
	close(sdata->socket);
+

+
	return (NULL);
+
}
+

+
static int
+
fuzz_fd(const struct fuzz_params *fparams, const uint8_t *data, size_t sz)
+
{
+
	struct supply_data sdata;
+
	struct libder_ctx *ctx;
+
	struct libder_object *obj;
+
	size_t totalsz;
+
	int sockets[2];
+
	pid_t pid;
+
	int ret;
+

+
	ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0,
+
	    &sockets[0]);
+
	if (ret == -1)
+
		return (-1);
+

+
	sdata.data = data;
+
	sdata.datasz = sz;
+
	sdata.socket = sockets[1];
+
	signal(SIGCHLD, SIG_IGN);
+
	pid = fork();
+
	if (pid == -1) {
+
		close(sockets[0]);
+
		close(sockets[1]);
+
		return (-1);
+
	}
+

+
	if (pid == 0) {
+
		close(sockets[0]);
+
		supply_thread(&sdata);
+
		_exit(0);
+
	} else {
+
		close(sockets[1]);
+
	}
+

+
	totalsz = 0;
+
	ret = 0;
+
	ctx = libder_open();
+
	libder_set_strict(ctx, !!fparams->strict);
+
	while (totalsz < sz) {
+
		size_t readsz = 0;
+

+
		obj = libder_read_fd(ctx, sockets[0], &readsz);
+
		libder_obj_free(obj);
+

+
		/*
+
		 * Even invalid reads should consume at least one byte.
+
		 */
+
		assert(readsz != 0);
+

+
		totalsz += readsz;
+
		if (readsz == 0)
+
			break;
+
	}
+

+
	assert(totalsz == sz);
+
	libder_close(ctx);
+
	close(sockets[0]);
+

+
	return (ret);
+
}
+

+
static int
+
fuzz_file(const struct fuzz_params *fparams, const uint8_t *data, size_t sz)
+
{
+
	FILE *fp;
+
	struct libder_ctx *ctx;
+
	struct libder_object *obj;
+
	size_t totalsz;
+
	int ret;
+

+
	if (fparams->buftype >= BUFFER_END)
+
		return (-1);
+

+
	fp = fmemopen(__DECONST(void *, data), sz, "rb");
+
	assert(fp != NULL);
+

+
	switch (fparams->buftype) {
+
	case BUFFER_NONE:
+
		setvbuf(fp, NULL, 0, _IONBF);
+
		break;
+
	case BUFFER_FULL:
+
		setvbuf(fp, NULL, 0, _IOFBF);
+
		break;
+
	case BUFFER_END:
+
		assert(0);
+
	}
+

+
	totalsz = 0;
+
	ret = 0;
+
	ctx = libder_open();
+
	libder_set_strict(ctx, !!fparams->strict);
+
	while (!feof(fp)) {
+
		size_t readsz = 0;
+

+
		obj = libder_read_file(ctx, fp, &readsz);
+
		libder_obj_free(obj);
+

+
		if (obj == NULL)
+
			assert(readsz != 0 || feof(fp));
+
		else
+
			assert(readsz != 0);
+

+
		totalsz += readsz;
+
	}
+

+
	assert(totalsz == sz);
+
	libder_close(ctx);
+
	fclose(fp);
+

+
	return (ret);
+
}
+

+
static int
+
fuzz_plain(const struct fuzz_params *fparams, const uint8_t *data, size_t sz)
+
{
+
	struct libder_ctx *ctx;
+
	struct libder_object *obj;
+
	int ret;
+

+
	if (sz == 0)
+
		return (-1);
+

+
	ret = 0;
+
	ctx = libder_open();
+
	libder_set_strict(ctx, !!fparams->strict);
+
	do {
+
		size_t readsz;
+

+
		readsz = sz;
+
		obj = libder_read(ctx, data, &readsz);
+
		libder_obj_free(obj);
+

+
		if (obj == NULL)
+
			assert(readsz != 0 || readsz == sz);
+
		else
+
			assert(readsz != 0);
+

+
		/*
+
		 * If we hit an entirely invalid segment of the buffer, we'll
+
		 * just skip a byte and try again.
+
		 */
+
		data += MAX(1, readsz);
+
		sz -= MAX(1, readsz);
+
	} while (sz != 0);
+

+
	libder_close(ctx);
+

+
	return (ret);
+
};
+

+
static bool
+
validate_padding(const struct fuzz_params *fparams)
+
{
+
	const uint8_t *end = (const void *)(fparams + 1);
+
	const uint8_t *pad = (const uint8_t *)&fparams->PARAM_PAD_START;
+

+
	while (pad < end) {
+
		if (*pad++ != 0)
+
			return (false);
+
	}
+

+
	return (true);
+
}
+

+
int
+
LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz)
+
{
+
	const struct fuzz_params *fparams;
+

+
	if (sz <= sizeof(*fparams))
+
		return (-1);
+

+
	fparams = (const void *)data;
+
	if (fparams->type >= STREAM_END)
+
		return (-1);
+

+
	if (!validate_padding(fparams))
+
		return (-1);
+

+
	data += sizeof(*fparams);
+
	sz -= sizeof(*fparams);
+

+
	if (fparams->type != STREAM_FILE && fparams->buftype != BUFFER_NONE)
+
		return (-1);
+

+
	switch (fparams->type) {
+
	case STREAM_FD:
+
		return (fuzz_fd(fparams, data, sz));
+
	case STREAM_FILE:
+
		return (fuzz_file(fparams, data, sz));
+
	case STREAM_PLAIN:
+
		return (fuzz_plain(fparams, data, sz));
+
	case STREAM_END:
+
		assert(0);
+
	}
+

+
	__builtin_trap();
+
}
added external/libder/tests/fuzz_write.c
@@ -0,0 +1,79 @@
+
/*-
+
 * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

+
#include <sys/param.h>
+
#include <sys/socket.h>
+

+
#include <assert.h>
+
#include <signal.h>
+
#include <stdbool.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <unistd.h>
+

+
#include <libder.h>
+

+
#include "fuzzers.h"
+

+
int
+
LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz)
+
{
+
	struct libder_ctx *ctx;
+
	struct libder_object *obj;
+
	size_t readsz;
+
	int ret;
+
	bool strict;
+

+
	if (sz < 2)
+
		return (-1);
+

+
	/*
+
	 * I worked this in originally by just using the high bit of the first
+
	 * byte, but then I realized that encoding it that way would make it
+
	 * impossible to get strict validation of universal and application
+
	 * tags.  The former is a bit more important than the latter.
+
	 */
+
	strict = !!data[0];
+
	data++;
+
	sz--;
+

+
	ctx = libder_open();
+
	libder_set_strict(ctx, strict);
+
	ret = -1;
+
	readsz = sz;
+
	obj = libder_read(ctx, data, &readsz);
+
	if (obj == NULL || readsz != sz)
+
		goto out;
+

+
	if (obj != NULL) {
+
		uint8_t *buf = NULL;
+
		size_t bufsz = 0;
+

+
		/*
+
		 * If we successfully read it, then it shouldn't
+
		 * overflow.  We're letting libder allocate the buffer,
+
		 * so we shouldn't be able to hit the 'too small' bit.
+
		 *
+
		 * I can't imagine what other errors might happen, so
+
		 * we'll just assert on it.
+
		 */
+
		buf = libder_write(ctx, obj, buf, &bufsz);
+
		if (buf == NULL)
+
			goto out;
+

+
		assert(bufsz != 0);
+

+
		free(buf);
+
	}
+

+
	ret = 0;
+

+
out:
+
	libder_obj_free(obj);
+
	libder_close(ctx);
+

+
	return (ret);
+
}
added external/libder/tests/fuzzers.h
@@ -0,0 +1,40 @@
+
/*-
+
 * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

+
#pragma once
+

+
#include <stdint.h>
+

+
enum stream_type {
+
	STREAM_FD = 0,		/* read_fd() type */
+
	STREAM_FILE = 1,	/* read_file() type */
+
	STREAM_PLAIN = 2,
+

+
	STREAM_END
+
} __attribute__((packed));
+

+
enum stream_buffer {
+
	BUFFER_NONE = 0,
+
	BUFFER_FULL = 1,
+

+
	BUFFER_END,
+
} __attribute__((packed));
+

+
struct fuzz_params {
+
	enum stream_type	 type;
+
	enum stream_buffer	 buftype;
+

+
#define	PARAM_PAD_START	_pad0
+
	uint8_t			 strict;
+
	uint8_t			 _pad0[5];
+

+
	/* Give me plenty of padding. */
+
	uint64_t		 padding[3];
+
};
+

+
_Static_assert(sizeof(struct fuzz_params) == 32,
+
    "fuzz_params ABI broken, will invalidate CORPUS");
+

added external/libder/tests/make_corpus.c
@@ -0,0 +1,137 @@
+
/*-
+
 * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

+
#include <sys/param.h>
+

+
#undef NDEBUG
+
#include <assert.h>
+
#include <err.h>
+
#include <fcntl.h>
+
#include <stdbool.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+

+
#include <libder.h>
+

+
#include "fuzzers.h"
+

+
#define	LARGE_SIZE	(1024 * 64)
+

+
static const uint8_t empty_seq[] = { BT_SEQUENCE, 0x00 };
+
static const uint8_t long_size[21] = { BT_OCTETSTRING, 0x83, 0x00, 0x00, 0x10 };
+

+
/* 64k */
+
#define	LARGE_SIZE_ENCODING	0x83, 0x01, 0x00, 0x00
+
static const uint8_t large_octet[LARGE_SIZE + 5] = { BT_OCTETSTRING, LARGE_SIZE_ENCODING };
+

+
#define	VARLEN_SEQ	BT_OCTETSTRING, 0x04, 0x01, 0x02, 0x03, 0x04
+
#define	VARLEN_CHILDREN	VARLEN_SEQ, VARLEN_SEQ, VARLEN_SEQ
+
static const uint8_t varlen[] = { BT_SEQUENCE, 0x80,
+
    VARLEN_CHILDREN, 0x00, 0x00 };
+

+
#define	BITSTRING1	BT_BITSTRING, 0x04, 0x03, 0xc0, 0xc0, 0xcc
+
#define	BITSTRING2	BT_BITSTRING, 0x04, 0x05, 0xdd, 0xdd, 0xff
+
static const uint8_t constructed_bitstring[] = { 0x20 | BT_BITSTRING,
+
    2 * 6, BITSTRING1, BITSTRING2 };
+

+
#define	FUZZER_SEED(seq)	{ #seq, sizeof(seq), seq }
+
static const struct seed {
+
	const char	*seed_name;
+
	size_t		 seed_seqsz;
+
	const uint8_t	*seed_seq;
+
} seeds[] = {
+
	FUZZER_SEED(empty_seq),
+
	FUZZER_SEED(long_size),
+
	FUZZER_SEED(large_octet),
+
	FUZZER_SEED(varlen),
+
	FUZZER_SEED(constructed_bitstring),
+
};
+

+
static void
+
usage(void)
+
{
+
	fprintf(stderr, "usage: %s [-H] <corpus-dir>\n", getprogname());
+
	exit(1);
+
}
+

+
static void
+
write_one(const struct fuzz_params *params, const struct seed *seed, int dirfd,
+
    bool striphdr)
+
{
+
	char *name;
+
	int fd = -1;
+

+
	assert(asprintf(&name, "base_%d_%d_%d_%s", params->type,
+
	    params->buftype, params->strict, seed->seed_name) != -1);
+

+
	fd = openat(dirfd, name, O_RDWR | O_TRUNC | O_CREAT, 0644);
+
	assert(fd != -1);
+

+
	/*
+
	 * Write our params + seed; if we're stripping the header we won't have
+
	 * the full params, but we'll still have our signal byte for strict
+
	 * mode.
+
	 */
+
	if (!striphdr)
+
		assert(write(fd, &params,  sizeof(params)) == sizeof(params));
+
	else
+
		assert(write(fd, &params->strict, sizeof(params->strict)) == sizeof(params->strict));
+

+
	assert(write(fd, seed->seed_seq, seed->seed_seqsz) == seed->seed_seqsz);
+

+
	free(name);
+
	close(fd);
+
}
+

+
int
+
main(int argc, char *argv[])
+
{
+
	struct fuzz_params params;
+
	const struct seed *seed;
+
	const char *seed_dir;
+
	int dirfd = -1;
+
	bool striphdr = false;
+

+
	if (argc < 2 || argc > 3)
+
		usage();
+

+
	if (argc == 3 && strcmp(argv[1], "-H") != 0)
+
		usage();
+

+
	striphdr = argc == 3;
+
	seed_dir = argv[argc - 1];
+

+
	dirfd = open(seed_dir, O_SEARCH);
+
	if (dirfd == -1)
+
		err(1, "%s: open", seed_dir);
+

+
	memset(&params, 0, sizeof(params));
+

+
	for (int type = 0; type < STREAM_END; type++) {
+
		params.type = type;
+

+
		for (int buffered = 0; buffered < BUFFER_END; buffered++) {
+
			params.buftype = buffered;
+

+
			for (uint8_t strict = 0; strict < 2; strict++) {
+
				params.strict = strict;
+

+
				for (size_t i = 0; i < nitems(seeds); i++) {
+
					seed = &seeds[i];
+

+
					write_one(&params, seed, dirfd, striphdr);
+
				}
+
			}
+

+
			if (type != STREAM_FILE)
+
				break;
+
		}
+
	}
+

+
	close(dirfd);
+
}
added external/libder/tests/repo.priv
added external/libder/tests/repo.pub
added external/libder/tests/test_common.h
@@ -0,0 +1,29 @@
+
/*-
+
 * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

+
#include <assert.h>
+
#include <fcntl.h>
+
#include <libgen.h>
+
#include <limits.h>
+
#include <stdlib.h>
+

+
static inline int
+
open_progdir(const char *prog)
+
{
+
	char pdir[PATH_MAX], *resolved;
+
	int dfd;
+

+
	resolved = realpath(prog, &pdir[0]);
+
	assert(resolved != NULL);
+

+
	resolved = dirname(&pdir[0]);
+
	assert(resolved != NULL);
+

+
	dfd = open(resolved, O_DIRECTORY);
+
	assert(dfd != -1);
+

+
	return (dfd);
+
}
added external/libder/tests/test_privkey.c
@@ -0,0 +1,175 @@
+
/*-
+
 * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

+
#include <sys/stat.h>
+

+
#include <assert.h>
+
#include <fcntl.h>
+
#include <inttypes.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+

+
#include <libder.h>
+

+
#include "test_common.h"
+

+
/*
+
 * Note that the choice of secp112r1 is completely arbitrary.  I was mainly
+
 * shooting for something pretty weak to avoid people trying to "catch me"
+
 * checking in private key material, even though it's very incredibly clearly
+
 * just for a test case.
+
 */
+
static const uint8_t oid_secp112r1[] =
+
    { 0x2b, 0x81, 0x04, 0x00, 0x06 };
+

+
static const uint8_t privdata[] = { 0xa5, 0xf5, 0x2a, 0x56, 0x61, 0xe3, 0x58,
+
    0x76, 0x5c, 0x4f, 0xd6, 0x8d, 0x60, 0x54 };
+

+
static const uint8_t pubdata[] = { 0x00, 0x04, 0xae, 0x69, 0x41, 0x0d, 0x9c,
+
    0x9b, 0xf2, 0x34, 0xf6, 0x2d, 0x7c, 0x91, 0xe1, 0xc7, 0x7f, 0x23, 0xa0,
+
    0x84, 0x34, 0x5c, 0x38, 0x26, 0xd8, 0xcf, 0xbe, 0xf7, 0xdc, 0x8a };
+

+
static void
+
test_interface(struct libder_object *root)
+
{
+
	const uint8_t *data;
+
	size_t datasz;
+
	struct libder_object *keystring, *oid;
+

+
	/* Grab the oid first. */
+
	oid = libder_obj_child(root, 2);
+
	assert(oid != NULL);	/* Actually just the container... */
+
	assert(libder_obj_type_simple(oid) == 0xa0);
+

+
	oid = libder_obj_child(oid, 0);
+
	assert(oid != NULL);	/* Now *that*'s an OID. */
+
	assert(libder_obj_type_simple(oid) == BT_OID);
+
	data = libder_obj_data(oid, &datasz);
+
	assert(datasz == sizeof(oid_secp112r1));
+
	assert(memcmp(oid_secp112r1, data, datasz) == 0);
+

+
	keystring = libder_obj_child(root, 1);
+
	assert(keystring != NULL);
+
	assert(libder_obj_type_simple(keystring) == BT_OCTETSTRING);
+

+
	data = libder_obj_data(keystring, &datasz);
+
	assert(datasz == sizeof(privdata));
+
	assert(memcmp(privdata, data, datasz) == 0);
+
}
+

+
/* buf and bufszs are just our reference */
+
static void
+
test_construction(struct libder_ctx *ctx, const uint8_t *buf, size_t bufsz)
+
{
+
	uint8_t *out;
+
	struct libder_object *obj, *oidp, *pubp, *root;
+
	struct libder_object *keystring;
+
	size_t outsz;
+
	uint8_t data;
+

+
	root = libder_obj_alloc_simple(ctx, BT_SEQUENCE, NULL, 0);
+
	assert(root != NULL);
+

+
	data = 1;
+
	obj = libder_obj_alloc_simple(ctx, BT_INTEGER, &data, 1);
+
	assert(obj != NULL);
+
	assert(libder_obj_append(root, obj));
+

+
	/* Private key material */
+
	obj = libder_obj_alloc_simple(ctx, BT_OCTETSTRING, privdata, sizeof(privdata));
+
	assert(obj != NULL);
+
	assert(libder_obj_append(root, obj));
+

+
	/* Now throw in the OID and pubkey containers */
+
	oidp = libder_obj_alloc_simple(ctx,
+
	    (BC_CONTEXT << 6) | BER_TYPE_CONSTRUCTED_MASK | 0, NULL, 0);
+
	assert(oidp != NULL);
+
	assert(libder_obj_append(root, oidp));
+

+
	pubp = libder_obj_alloc_simple(ctx,
+
	    (BC_CONTEXT << 6) | BER_TYPE_CONSTRUCTED_MASK | 1, NULL, 0);
+
	assert(pubp != NULL);
+
	assert(libder_obj_append(root, pubp));
+

+
	/* Actually add the OID */
+
	obj = libder_obj_alloc_simple(ctx, BT_OID, oid_secp112r1, sizeof(oid_secp112r1));
+
	assert(obj != NULL);
+
	assert(libder_obj_append(oidp, obj));
+

+
	/* Finally, add the pubkey */
+
	obj = libder_obj_alloc_simple(ctx, BT_BITSTRING, pubdata, sizeof(pubdata));
+
	assert(obj != NULL);
+
	assert(libder_obj_append(pubp, obj));
+

+
	out = NULL;
+
	outsz = 0;
+
	out = libder_write(ctx, root, out, &outsz);
+
	assert(out != NULL);
+
	assert(outsz == bufsz);
+

+
	assert(memcmp(out, buf, bufsz) == 0);
+

+
	libder_obj_free(root);
+
	free(out);
+
}
+

+
int
+
main(int argc, char *argv[])
+
{
+
	struct stat sb;
+
	struct libder_ctx *ctx;
+
	struct libder_object *root;
+
	uint8_t *buf, *out;
+
	size_t bufsz, outsz, rootsz;
+
	ssize_t readsz;
+
	int dfd, error, fd;
+

+
	dfd = open_progdir(argv[0]);
+

+
	fd = openat(dfd, "repo.priv", O_RDONLY);
+
	assert(fd >= 0);
+

+
	close(dfd);
+
	dfd = -1;
+

+
	error = fstat(fd, &sb);
+
	assert(error == 0);
+

+
	bufsz = sb.st_size;
+
	buf = malloc(bufsz);
+
	assert(buf != NULL);
+

+
	readsz = read(fd, buf, bufsz);
+
	close(fd);
+

+
	assert(readsz == bufsz);
+

+
	ctx = libder_open();
+
	rootsz = bufsz;
+
	libder_set_verbose(ctx, 2);
+
	root = libder_read(ctx, buf, &rootsz);
+

+
	assert(root != NULL);
+
	assert(rootsz == bufsz);
+

+
	test_interface(root);
+
	test_construction(ctx, buf, bufsz);
+

+
	outsz = 0;
+
	out = NULL;
+
	out = libder_write(ctx, root, out, &outsz);
+
	assert(out != NULL);
+
	assert(outsz == bufsz);
+

+
	assert(memcmp(buf, out, outsz) == 0);
+

+
	free(out);
+
	free(buf);
+
	libder_obj_free(root);
+
	libder_close(ctx);
+
}
added external/libder/tests/test_pubkey.c
@@ -0,0 +1,143 @@
+
/*-
+
 * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
+
 *
+
 * SPDX-License-Identifier: BSD-2-Clause
+
 */
+

+
#include <sys/stat.h>
+

+
#include <assert.h>
+
#include <fcntl.h>
+
#include <inttypes.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+

+
#include <libder.h>
+

+
#include "test_common.h"
+

+
static const uint8_t oid_ecpubkey[] =
+
    { 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01 };
+
static const uint8_t oid_secp256k1[] =
+
    { 0x2b, 0x81, 0x04, 0x00, 0x0a };
+

+
static const uint8_t pubdata[] = { 0x00, 0x04, 0xd1, 0x76, 0x20, 0x39, 0xe5, 0x3e,
+
    0x67, 0x7d, 0x8d, 0xfd, 0xc4, 0x21, 0x20, 0xcd, 0xb0, 0xbf, 0x47, 0x87, 0x6a,
+
    0xf8, 0x07, 0x73, 0xbe, 0xbe, 0xd5, 0xbb, 0x3c, 0xbc, 0x32, 0x93, 0xd9, 0xdf,
+
    0x96, 0x25, 0xb7, 0x0e, 0x3c, 0x55, 0x12, 0xee, 0x7a, 0x02, 0x39, 0x0f, 0xee,
+
    0x7b, 0xfe, 0x1a, 0x93, 0x76, 0xf7, 0xc2, 0xac, 0x05, 0xba, 0x9a, 0x83, 0x37,
+
    0xf5, 0xcd, 0x55, 0x57, 0x39, 0x6f };
+

+
static void
+
test_interface(struct libder_object *root)
+
{
+
	const uint8_t *data;
+
	size_t datasz;
+
	struct libder_object *keystring;
+

+
	keystring = libder_obj_child(root, 1);
+
	assert(keystring != NULL);
+
	assert(libder_obj_type_simple(keystring) == BT_BITSTRING);
+

+
	data = libder_obj_data(keystring, &datasz);
+
	assert(datasz == sizeof(pubdata));
+
	assert(memcmp(pubdata, data, datasz) == 0);
+
}
+

+
/* buf and bufszs are just our reference */
+
static void
+
test_construction(struct libder_ctx*ctx, const uint8_t *buf, size_t bufsz)
+
{
+
	uint8_t *out;
+
	struct libder_object *obj, *params, *root;
+
	struct libder_object *keystring;
+
	size_t outsz;
+

+
	root = libder_obj_alloc_simple(ctx, BT_SEQUENCE, NULL, 0);
+
	assert(root != NULL);
+

+
	params = libder_obj_alloc_simple(ctx, BT_SEQUENCE, NULL, 0);
+
	assert(params != NULL);
+
	assert(libder_obj_append(root, params));
+

+
	keystring = libder_obj_alloc_simple(ctx, BT_BITSTRING, pubdata, sizeof(pubdata));
+
	assert(keystring != NULL);
+
	assert(libder_obj_append(root, keystring));
+

+
	/* Now go back and build the two params, id and curve */
+
	obj = libder_obj_alloc_simple(ctx, BT_OID, oid_ecpubkey, sizeof(oid_ecpubkey));
+
	assert(obj != NULL);
+
	assert(libder_obj_append(params, obj));
+

+
	obj = libder_obj_alloc_simple(ctx, BT_OID, oid_secp256k1, sizeof(oid_secp256k1));
+
	assert(obj != NULL);
+
	assert(libder_obj_append(params, obj));
+

+
	out = NULL;
+
	outsz = 0;
+
	out = libder_write(ctx, root, out, &outsz);
+
	assert(out != NULL);
+
	assert(outsz == bufsz);
+
	assert(memcmp(out, buf, bufsz) == 0);
+

+
	libder_obj_free(root);
+
	free(out);
+
}
+

+
int
+
main(int argc, char *argv[])
+
{
+
	struct stat sb;
+
	struct libder_ctx *ctx;
+
	struct libder_object *root;
+
	uint8_t *buf, *out;
+
	size_t bufsz, outsz, rootsz;
+
	ssize_t readsz;
+
	int dfd, error, fd;
+

+
	dfd = open_progdir(argv[0]);
+

+
	fd = openat(dfd, "repo.pub", O_RDONLY);
+
	assert(fd >= 0);
+

+
	close(dfd);
+
	dfd = -1;
+

+
	error = fstat(fd, &sb);
+
	assert(error == 0);
+

+
	bufsz = sb.st_size;
+
	buf = malloc(bufsz);
+
	assert(buf != NULL);
+

+
	readsz = read(fd, buf, bufsz);
+
	close(fd);
+

+
	assert(readsz == bufsz);
+

+
	ctx = libder_open();
+
	rootsz = bufsz;
+
	libder_set_verbose(ctx, 2);
+
	root = libder_read(ctx, buf, &rootsz);
+

+
	assert(root != NULL);
+
	assert(rootsz == bufsz);
+

+
	test_interface(root);
+
	test_construction(ctx, buf, bufsz);
+

+
	outsz = 0;
+
	out = NULL;
+
	out = libder_write(ctx, root, out, &outsz);
+
	assert(out != NULL);
+
	assert(outsz == bufsz);
+

+
	assert(memcmp(buf, out, outsz) == 0);
+

+
	free(out);
+
	free(buf);
+
	libder_obj_free(root);
+
	libder_close(ctx);
+
}