git: ba667efb5301 - stable/14 - Add 'contrib/libder/' from commit '9c40c4de4c33b2ba1124fb752ebea0bebaa6013f'

From: Kyle Evans <kevans_at_FreeBSD.org>
Date: Sat, 11 Jan 2025 02:48:35 UTC
The branch stable/14 has been updated by kevans:

URL: https://cgit.FreeBSD.org/src/commit/?id=ba667efb5301da32009d5cbc5ae5df9cff895e82

commit ba667efb5301da32009d5cbc5ae5df9cff895e82
Author:     Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2025-01-01 21:11:02 +0000
Commit:     Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2025-01-11 02:48:21 +0000

    Add 'contrib/libder/' from commit '9c40c4de4c33b2ba1124fb752ebea0bebaa6013f'
    
    git-subtree-dir: contrib/libder
    git-subtree-mainline: d11904b350214943dedb64c7121d4602799d7afd
    git-subtree-split: 9c40c4de4c33b2ba1124fb752ebea0bebaa6013f
    (cherry picked from commit 35c0a8c449fd2b7f75029ebed5e10852240f0865)
---
 contrib/libder/.cirrus.yml                 |   16 +
 contrib/libder/.github/workflows/build.yml |   41 +
 contrib/libder/.gitignore                  |   11 +
 contrib/libder/CMakeLists.txt              |   28 +
 contrib/libder/LICENSE                     |   22 +
 contrib/libder/README.md                   |   28 +
 contrib/libder/derdump/.gitignore          |    1 +
 contrib/libder/derdump/CMakeLists.txt      |    6 +
 contrib/libder/derdump/derdump.1           |   51 ++
 contrib/libder/derdump/derdump.c           |   52 ++
 contrib/libder/libder/CMakeLists.txt       |   12 +
 contrib/libder/libder/libder.3             |  179 +++++
 contrib/libder/libder/libder.c             |  119 +++
 contrib/libder/libder/libder.h             |  181 +++++
 contrib/libder/libder/libder_error.c       |   76 ++
 contrib/libder/libder/libder_obj.3         |  138 ++++
 contrib/libder/libder/libder_obj.c         | 1192 ++++++++++++++++++++++++++++
 contrib/libder/libder/libder_private.h     |  178 +++++
 contrib/libder/libder/libder_read.3        |  101 +++
 contrib/libder/libder/libder_read.c        |  864 ++++++++++++++++++++
 contrib/libder/libder/libder_type.3        |   71 ++
 contrib/libder/libder/libder_type.c        |  150 ++++
 contrib/libder/libder/libder_write.3       |   54 ++
 contrib/libder/libder/libder_write.c       |  229 ++++++
 contrib/libder/tests/.gitignore            |   12 +
 contrib/libder/tests/CMakeLists.txt        |   41 +
 contrib/libder/tests/fuzz_parallel.c       |  111 +++
 contrib/libder/tests/fuzz_stream.c         |  246 ++++++
 contrib/libder/tests/fuzz_write.c          |   79 ++
 contrib/libder/tests/fuzzers.h             |   40 +
 contrib/libder/tests/make_corpus.c         |  137 ++++
 contrib/libder/tests/repo.priv             |  Bin 0 -> 64 bytes
 contrib/libder/tests/repo.pub              |  Bin 0 -> 88 bytes
 contrib/libder/tests/test_common.h         |   29 +
 contrib/libder/tests/test_privkey.c        |  175 ++++
 contrib/libder/tests/test_pubkey.c         |  143 ++++
 36 files changed, 4813 insertions(+)

diff --git a/contrib/libder/.cirrus.yml b/contrib/libder/.cirrus.yml
new file mode 100644
index 000000000000..a63de71d8bf4
--- /dev/null
+++ b/contrib/libder/.cirrus.yml
@@ -0,0 +1,16 @@
+build_task:
+  matrix:
+    - name: FreeBSD 13
+      freebsd_instance:
+        image: freebsd-13-2-release-amd64
+    - name: FreeBSD 14
+      freebsd_instance:
+        image: freebsd-14-0-release-amd64-ufs
+  setup_script:
+    sudo pkg install -y cmake
+  configure_script:
+    - cmake -B build -DCMAKE_BUILD_TYPE=Debug
+  build_script:
+    make -C build
+  test_script:
+    make -C build check
diff --git a/contrib/libder/.github/workflows/build.yml b/contrib/libder/.github/workflows/build.yml
new file mode 100644
index 000000000000..a10daa25e38f
--- /dev/null
+++ b/contrib/libder/.github/workflows/build.yml
@@ -0,0 +1,41 @@
+name: Build libder
+on:
+  push:
+    branches: ['**']
+  pull_request:
+    types: [opened, reopened, edited, synchronize]
+
+permissions:
+  contents: read
+
+jobs:
+  build:
+    name: Build ${{ matrix.os }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ubuntu-20.04, ubuntu-22.04, macos-latest]
+        include:
+          - os: ubuntu-20.04
+          - os: ubuntu-22.04
+          - os: macos-latest
+    steps:
+      - name: checkout
+        uses: actions/checkout@v4
+      - name: install system packages (Ubuntu)
+        if: runner.os == 'Linux'
+        run: |
+          sudo apt-get update --quiet || true
+          sudo apt-get -yq --no-install-suggests --no-install-recommends install cmake
+      - name: install system packages (macOS)
+        if: runner.os == 'macOS'
+        run: |
+          brew update --quiet || true
+          brew install cmake coreutils
+      - name: configure
+        run: |
+          cmake -B build -DCMAKE_BUILD_TYPE=Debug
+      - name: build libder
+        run: make -C build
+      - name: Run self-tests
+        run: make -C build check
diff --git a/contrib/libder/.gitignore b/contrib/libder/.gitignore
new file mode 100644
index 000000000000..34fb4e06c50b
--- /dev/null
+++ b/contrib/libder/.gitignore
@@ -0,0 +1,11 @@
+.*.swp
+.depend*
+*.a
+*.so
+*.so.*
+*.o
+*.pico
+*.debug
+*.full
+
+build/
diff --git a/contrib/libder/CMakeLists.txt b/contrib/libder/CMakeLists.txt
new file mode 100644
index 000000000000..cf0d39e32489
--- /dev/null
+++ b/contrib/libder/CMakeLists.txt
@@ -0,0 +1,28 @@
+cmake_minimum_required(VERSION 3.18)
+
+project(libder)
+
+if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+	if(NOT CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
+		add_compile_options(-fsanitize=address,undefined -fstrict-aliasing)
+		add_link_options(-fsanitize=address,undefined -fstrict-aliasing)
+	endif()
+
+	add_compile_options(-Werror)
+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)
diff --git a/contrib/libder/LICENSE b/contrib/libder/LICENSE
new file mode 100644
index 000000000000..477af8f22e4c
--- /dev/null
+++ b/contrib/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.
diff --git a/contrib/libder/README.md b/contrib/libder/README.md
new file mode 100644
index 000000000000..9f700493520d
--- /dev/null
+++ b/contrib/libder/README.md
@@ -0,0 +1,28 @@
+# 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.
+
+The authoritative source for this software is located at
+https://git.kevans.dev/kevans/libder, but it's additionally mirrored to
+[GitHub](https://github.com/kevans91/libder) for user-facing interactions.
+Pull requests and issues are open on GitHub.
+
+## 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.
diff --git a/contrib/libder/derdump/.gitignore b/contrib/libder/derdump/.gitignore
new file mode 100644
index 000000000000..a35adcc4b71d
--- /dev/null
+++ b/contrib/libder/derdump/.gitignore
@@ -0,0 +1 @@
+derdump
diff --git a/contrib/libder/derdump/CMakeLists.txt b/contrib/libder/derdump/CMakeLists.txt
new file mode 100644
index 000000000000..11657426fbc9
--- /dev/null
+++ b/contrib/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)
diff --git a/contrib/libder/derdump/derdump.1 b/contrib/libder/derdump/derdump.1
new file mode 100644
index 000000000000..414799f3055f
--- /dev/null
+++ b/contrib/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 { ... } .
diff --git a/contrib/libder/derdump/derdump.c b/contrib/libder/derdump/derdump.c
new file mode 100644
index 000000000000..7ea3768524d8
--- /dev/null
+++ b/contrib/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);
+}
diff --git a/contrib/libder/libder/CMakeLists.txt b/contrib/libder/libder/CMakeLists.txt
new file mode 100644
index 000000000000..8e6f3426d649
--- /dev/null
+++ b/contrib/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()
diff --git a/contrib/libder/libder/libder.3 b/contrib/libder/libder/libder.3
new file mode 100644
index 000000000000..0e06254ef3fb
--- /dev/null
+++ b/contrib/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
diff --git a/contrib/libder/libder/libder.c b/contrib/libder/libder/libder.c
new file mode 100644
index 000000000000..2d52fedd62bd
--- /dev/null
+++ b/contrib/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);
+}
+
diff --git a/contrib/libder/libder/libder.h b/contrib/libder/libder/libder.h
new file mode 100644
index 000000000000..4d28aa3052ba
--- /dev/null
+++ b/contrib/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 *);
diff --git a/contrib/libder/libder/libder_error.c b/contrib/libder/libder/libder_error.c
new file mode 100644
index 000000000000..6ca0acc83e6d
--- /dev/null
+++ b/contrib/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);
+	}
+}
diff --git a/contrib/libder/libder/libder_obj.3 b/contrib/libder/libder/libder_obj.3
new file mode 100644
index 000000000000..d7e51da1d2fb
--- /dev/null
+++ b/contrib/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 ,
*** 4094 LINES SKIPPED ***