summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/.gitignore12
-rw-r--r--tests/CMakeLists.txt41
-rw-r--r--tests/fuzz_parallel.c111
-rw-r--r--tests/fuzz_stream.c246
-rw-r--r--tests/fuzz_write.c79
-rw-r--r--tests/fuzzers.h40
-rw-r--r--tests/make_corpus.c137
-rw-r--r--tests/repo.privbin0 -> 64 bytes
-rw-r--r--tests/repo.pubbin0 -> 88 bytes
-rw-r--r--tests/test_common.h29
-rw-r--r--tests/test_privkey.c175
-rw-r--r--tests/test_pubkey.c143
12 files changed, 1013 insertions, 0 deletions
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 000000000000..075588a81e75
--- /dev/null
+++ b/tests/.gitignore
@@ -0,0 +1,12 @@
+CORPUS*
+crash-*
+leak-*
+oom-*
+*.log
+
+fuzz_*
+test_*
+!*.c
+!*.h
+
+make_corpus
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 000000000000..fc366ab88ed7
--- /dev/null
+++ b/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"
+)
diff --git a/tests/fuzz_parallel.c b/tests/fuzz_parallel.c
new file mode 100644
index 000000000000..afd4425970a2
--- /dev/null
+++ b/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);
+}
diff --git a/tests/fuzz_stream.c b/tests/fuzz_stream.c
new file mode 100644
index 000000000000..0f7cc6167e7e
--- /dev/null
+++ b/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();
+}
diff --git a/tests/fuzz_write.c b/tests/fuzz_write.c
new file mode 100644
index 000000000000..2ad5b5eb1764
--- /dev/null
+++ b/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);
+}
diff --git a/tests/fuzzers.h b/tests/fuzzers.h
new file mode 100644
index 000000000000..0f94bc7f25fb
--- /dev/null
+++ b/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");
+
diff --git a/tests/make_corpus.c b/tests/make_corpus.c
new file mode 100644
index 000000000000..68554d7c17de
--- /dev/null
+++ b/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);
+}
diff --git a/tests/repo.priv b/tests/repo.priv
new file mode 100644
index 000000000000..74a030b6802c
--- /dev/null
+++ b/tests/repo.priv
Binary files differ
diff --git a/tests/repo.pub b/tests/repo.pub
new file mode 100644
index 000000000000..bdcb1a20a1c7
--- /dev/null
+++ b/tests/repo.pub
Binary files differ
diff --git a/tests/test_common.h b/tests/test_common.h
new file mode 100644
index 000000000000..76e850f19128
--- /dev/null
+++ b/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);
+}
diff --git a/tests/test_privkey.c b/tests/test_privkey.c
new file mode 100644
index 000000000000..5e7519f5a715
--- /dev/null
+++ b/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);
+}
diff --git a/tests/test_pubkey.c b/tests/test_pubkey.c
new file mode 100644
index 000000000000..9fbd070f0e87
--- /dev/null
+++ b/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);
+}