From efff9f8a94ab4118bc5f5a1a65bf6938f4dedadf Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Mon, 16 Dec 2024 22:16:27 -0600 Subject: [PATCH 1/2] Import libder v1.0 libder will be used by pkg(7) to read DER-encoded keys and signatures in the upcoming ECC support. --- .cirrus.yml | 16 + .github/workflows/build.yml | 41 ++ .gitignore | 11 + 1.0.tar.gz | Bin 0 -> 32406 bytes CMakeLists.txt | 28 + LICENSE | 22 + README.md | 28 + derdump/.gitignore | 1 + derdump/CMakeLists.txt | 6 + derdump/derdump.1 | 51 ++ derdump/derdump.c | 52 ++ libder/CMakeLists.txt | 12 + libder/libder.3 | 179 ++++++ libder/libder.c | 119 ++++ libder/libder.h | 181 ++++++ libder/libder_error.c | 76 +++ libder/libder_obj.3 | 138 ++++ libder/libder_obj.c | 1192 +++++++++++++++++++++++++++++++++++ libder/libder_private.h | 178 ++++++ libder/libder_read.3 | 101 +++ libder/libder_read.c | 864 +++++++++++++++++++++++++ libder/libder_type.3 | 71 +++ libder/libder_type.c | 150 +++++ libder/libder_write.3 | 54 ++ libder/libder_write.c | 229 +++++++ tests/.gitignore | 12 + tests/CMakeLists.txt | 41 ++ tests/fuzz_parallel.c | 111 ++++ tests/fuzz_stream.c | 246 ++++++++ tests/fuzz_write.c | 79 +++ tests/fuzzers.h | 40 ++ tests/make_corpus.c | 137 ++++ tests/repo.priv | Bin 0 -> 64 bytes tests/repo.pub | Bin 0 -> 88 bytes tests/test_common.h | 29 + tests/test_privkey.c | 175 +++++ tests/test_pubkey.c | 143 +++++ 37 files changed, 4813 insertions(+) create mode 100644 .cirrus.yml create mode 100644 .github/workflows/build.yml create mode 100644 .gitignore create mode 100644 1.0.tar.gz create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 derdump/.gitignore create mode 100644 derdump/CMakeLists.txt create mode 100644 derdump/derdump.1 create mode 100644 derdump/derdump.c create mode 100644 libder/CMakeLists.txt create mode 100644 libder/libder.3 create mode 100644 libder/libder.c create mode 100644 libder/libder.h create mode 100644 libder/libder_error.c create mode 100644 libder/libder_obj.3 create mode 100644 libder/libder_obj.c create mode 100644 libder/libder_private.h create mode 100644 libder/libder_read.3 create mode 100644 libder/libder_read.c create mode 100644 libder/libder_type.3 create mode 100644 libder/libder_type.c create mode 100644 libder/libder_write.3 create mode 100644 libder/libder_write.c create mode 100644 tests/.gitignore create mode 100644 tests/CMakeLists.txt create mode 100644 tests/fuzz_parallel.c create mode 100644 tests/fuzz_stream.c create mode 100644 tests/fuzz_write.c create mode 100644 tests/fuzzers.h create mode 100644 tests/make_corpus.c create mode 100644 tests/repo.priv create mode 100644 tests/repo.pub create mode 100644 tests/test_common.h create mode 100644 tests/test_privkey.c create mode 100644 tests/test_pubkey.c diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 00000000000..a63de71d8bf --- /dev/null +++ b/.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/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000000..a10daa25e38 --- /dev/null +++ b/.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/.gitignore b/.gitignore new file mode 100644 index 00000000000..34fb4e06c50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.*.swp +.depend* +*.a +*.so +*.so.* +*.o +*.pico +*.debug +*.full + +build/ diff --git a/1.0.tar.gz b/1.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..63cb6ee1df5bfac19923803ce2d48b6c78831e84 GIT binary patch literal 32406 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 ZrZlA~O=(I~n$pD5e*u>MxS{}90swTOfU*Dp literal 0 HcmV?d00001 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000000..cf0d39e3248 --- /dev/null +++ b/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/LICENSE b/LICENSE new file mode 100644 index 00000000000..477af8f22e4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2024 Kyle Evans + +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/README.md b/README.md new file mode 100644 index 00000000000..9f700493520 --- /dev/null +++ b/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/derdump/.gitignore b/derdump/.gitignore new file mode 100644 index 00000000000..a35adcc4b71 --- /dev/null +++ b/derdump/.gitignore @@ -0,0 +1 @@ +derdump diff --git a/derdump/CMakeLists.txt b/derdump/CMakeLists.txt new file mode 100644 index 00000000000..11657426fbc --- /dev/null +++ b/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/derdump/derdump.1 b/derdump/derdump.1 new file mode 100644 index 00000000000..414799f3055 --- /dev/null +++ b/derdump/derdump.1 @@ -0,0 +1,51 @@ +.\" +.\" SPDX-Copyright-Identifier: BSD-2-Clause +.\" +.\" Copyright (C) 2024 Kyle Evans +.\" +.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/derdump/derdump.c b/derdump/derdump.c new file mode 100644 index 00000000000..7ea3768524d --- /dev/null +++ b/derdump/derdump.c @@ -0,0 +1,52 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +#include + +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/libder/CMakeLists.txt b/libder/CMakeLists.txt new file mode 100644 index 00000000000..8e6f3426d64 --- /dev/null +++ b/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/libder/libder.3 b/libder/libder.3 new file mode 100644 index 00000000000..0e06254ef3f --- /dev/null +++ b/libder/libder.3 @@ -0,0 +1,179 @@ +.\" +.\" SPDX-Copyright-Identifier: BSD-2-Clause +.\" +.\" Copyright (C) 2024 Kyle Evans +.\" +.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/libder/libder.c b/libder/libder.c new file mode 100644 index 00000000000..2d52fedd62b --- /dev/null +++ b/libder/libder.c @@ -0,0 +1,119 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "libder_private.h" + +#include +#include + +/* + * 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/libder/libder.h b/libder/libder.h new file mode 100644 index 00000000000..4d28aa3052b --- /dev/null +++ b/libder/libder.h @@ -0,0 +1,181 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +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/libder/libder_error.c b/libder/libder_error.c new file mode 100644 index 00000000000..6ca0acc83e6 --- /dev/null +++ b/libder/libder_error.c @@ -0,0 +1,76 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#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/libder/libder_obj.3 b/libder/libder_obj.3 new file mode 100644 index 00000000000..d7e51da1d2f --- /dev/null +++ b/libder/libder_obj.3 @@ -0,0 +1,138 @@ +.\" +.\" SPDX-Copyright-Identifier: BSD-2-Clause +.\" +.\" Copyright (C) 2024 Kyle Evans +.\" +.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 diff --git a/libder/libder_obj.c b/libder/libder_obj.c new file mode 100644 index 00000000000..21d39e01fc1 --- /dev/null +++ b/libder/libder_obj.c @@ -0,0 +1,1192 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#include "libder_private.h" + +#undef DER_CHILDREN +#undef DER_NEXT + +#define DER_CHILDREN(obj) ((obj)->children) +#define DER_NEXT(obj) ((obj)->next) + +static uint8_t * +libder_obj_alloc_copy_payload(struct libder_ctx *ctx, const uint8_t *payload_in, + size_t length) +{ + uint8_t *payload; + + if ((length == 0 && payload_in != NULL) || + (length != 0 && payload_in == NULL)) { + libder_set_error(ctx, LDE_INVAL); + return (NULL); + } + + if (length > 0) { + payload = malloc(length); + if (payload == NULL) { + libder_set_error(ctx, LDE_NOMEM); + return (NULL); + } + + memcpy(payload, payload_in, length); + } else { + payload = NULL; + } + + return (payload); +} + +static bool +libder_obj_alloc_check(struct libder_ctx *ctx, struct libder_tag *type, + const uint8_t *payload_in, size_t length) +{ + /* + * In addition to our normal constraints, constructed objects coming in + * from lib users should not have payloads. + */ + if (!libder_is_valid_obj(ctx, type, payload_in, length, false) || + (type->tag_constructed && length != 0)) { + libder_set_error(ctx, LDE_BADOBJECT); + return (false); + } + + return (true); +} + +struct libder_object * +libder_obj_alloc(struct libder_ctx *ctx, struct libder_tag *type, + const uint8_t *payload_in, size_t length) +{ + struct libder_object *obj; + uint8_t *payload; + + if (!libder_obj_alloc_check(ctx, type, payload_in, length)) + return (NULL); + + payload = libder_obj_alloc_copy_payload(ctx, payload_in, length); + + obj = libder_obj_alloc_internal(ctx, type, payload, length, 0); + if (obj == NULL) { + if (length != 0) { + libder_bzero(payload, length); + free(payload); + } + + libder_set_error(ctx, LDE_NOMEM); + } + + return (obj); +} + +struct libder_object * +libder_obj_alloc_simple(struct libder_ctx *ctx, uint8_t stype, + const uint8_t *payload_in, size_t length) +{ + struct libder_object *obj; + struct libder_tag *type; + uint8_t *payload; + + type = libder_type_alloc_simple(ctx, stype); + if (type == NULL) + return (NULL); + + if (!libder_obj_alloc_check(ctx, type, payload_in, length)) { + libder_type_free(type); + return (NULL); + } + + payload = libder_obj_alloc_copy_payload(ctx, payload_in, length); + + obj = libder_obj_alloc_internal(ctx, type, payload, length, LDO_OWNTAG); + if (obj == NULL) { + if (length != 0) { + libder_bzero(payload, length); + free(payload); + } + + libder_type_free(type); + libder_set_error(ctx, LDE_NOMEM); + } + + return (obj); +} + +/* + * Returns an obj on success, NULL if out of memory. `obj` takes ownership of + * the payload on success. + */ +LIBDER_PRIVATE struct libder_object * +libder_obj_alloc_internal(struct libder_ctx *ctx, struct libder_tag *type, + uint8_t *payload, size_t length, uint32_t flags) +{ + struct libder_object *obj; + + assert((flags & ~(LDO_OWNTAG)) == 0); + + if (length != 0) + assert(payload != NULL); + else + assert(payload == NULL); + + obj = malloc(sizeof(*obj)); + if (obj == NULL) + return (NULL); + + if ((flags & LDO_OWNTAG) != 0) { + obj->type = type; + } else { + /* + * Deep copies the tag data, so that the caller can predict what + * it can do with the buffer. + */ + obj->type = libder_type_dup(ctx, type); + if (obj->type == NULL) { + free(obj); + return (NULL); + } + } + + obj->length = length; + obj->payload = payload; + obj->children = obj->next = obj->parent = NULL; + obj->nchildren = 0; + + return (obj); +} + +LIBDER_PRIVATE size_t +libder_size_length(size_t sz) +{ + size_t nbytes; + + /* + * With DER, we use the smallest encoding necessary: less than 0x80 + * can be encoded in one byte. + */ + if (sz < 0x80) + return (1); + + /* + * We can support up to 0x7f size bytes, but we don't really have a way + * to represent that right now. It's a good thing this function only + * takes a size_t, otherwise this would be a bit wrong. + */ + for (nbytes = 1; nbytes < sizeof(size_t); nbytes++) { + if ((sz & ~((1ULL << 8 * nbytes) - 1)) == 0) + break; + } + + /* Add one for the lead byte describing the length of the length. */ + return (nbytes + 1); +} + +/* + * Returns the size on-disk. If an object has children, we encode the size as + * the sum of their lengths recursively. Otherwise, we use the object's size. + * + * Returns 0 if the object size would overflow a size_t... perhaps we could + * lift this restriction later. + * + * Note that the size of the object will be set/updated to simplify later write + * calculations. + */ +LIBDER_PRIVATE size_t +libder_obj_disk_size(struct libder_object *obj, bool include_header) +{ + struct libder_object *walker; + size_t disk_size, header_size; + + disk_size = obj->length; + if (obj->children != NULL) { + /* We should have rejected these. */ + assert(obj->length == 0); + + DER_FOREACH_CHILD(walker, obj) { + size_t child_size; + + child_size = libder_obj_disk_size(walker, true); + if (SIZE_MAX - child_size < disk_size) + return (0); /* Overflow */ + disk_size += child_size; + } + } + + obj->disk_size = disk_size; + + /* + * Children always include the header above, we only include the header + * at the root if we're calculating how much space we need in total. + */ + if (include_header) { + /* Size of the length + the tag (arbitrary length) */ + header_size = libder_size_length(disk_size) + obj->type->tag_size; + if (obj->type->tag_encoded) + header_size++; /* Lead byte */ + if (SIZE_MAX - header_size < disk_size) + return (0); + + disk_size += header_size; + } + + return (disk_size); +} + +void +libder_obj_free(struct libder_object *obj) +{ + struct libder_object *child, *tmp; + + if (obj == NULL) + return; + + DER_FOREACH_CHILD_SAFE(child, obj, tmp) + libder_obj_free(child); + + if (obj->payload != NULL) { + libder_bzero(obj->payload, obj->length); + free(obj->payload); + } + + libder_type_free(obj->type); + free(obj); +} + +static void +libder_obj_unlink(struct libder_object *obj) +{ + struct libder_object *child, *parent, *prev; + + parent = obj->parent; + if (parent == NULL) + return; + + prev = NULL; + assert(parent->nchildren > 0); + DER_FOREACH_CHILD(child, parent) { + if (child == obj) { + if (prev == NULL) + parent->children = child->next; + else + prev->next = child->next; + parent->nchildren--; + child->parent = NULL; + return; + } + + prev = child; + } + + assert(0 && "Internal inconsistency: parent set, but child not found"); +} + +bool +libder_obj_append(struct libder_object *parent, struct libder_object *child) +{ + struct libder_object *end, *walker; + + if (!parent->type->tag_constructed) + return (false); + + /* XXX Type check */ + + if (child->parent != NULL) + libder_obj_unlink(child); + + if (parent->nchildren == 0) { + parent->children = child; + parent->nchildren++; + return (true); + } + + /* Walk the chain */ + DER_FOREACH_CHILD(walker, parent) { + end = walker; + } + + assert(end != NULL); + end->next = child; + parent->nchildren++; + child->parent = parent; + return (true); +} + +struct libder_object * +libder_obj_child(const struct libder_object *obj, size_t idx) +{ + struct libder_object *cur; + + DER_FOREACH_CHILD(cur, obj) { + if (idx-- == 0) + return (cur); + } + + return (NULL); +} + +struct libder_object * +libder_obj_children(const struct libder_object *obj) +{ + + return (obj->children); +} + +struct libder_object * +libder_obj_next(const struct libder_object *obj) +{ + + return (obj->next); +} + +struct libder_tag * +libder_obj_type(const struct libder_object *obj) +{ + + return (obj->type); +} + +uint8_t +libder_obj_type_simple(const struct libder_object *obj) +{ + struct libder_tag *type = obj->type; + uint8_t simple = type->tag_class << 6; + + if (type->tag_constructed) + simple |= BER_TYPE_CONSTRUCTED_MASK; + + if (type->tag_encoded) + simple |= 0x1f; /* Encode the "long tag" tag. */ + else + simple |= type->tag_short; + return (simple); +} + +const uint8_t * +libder_obj_data(const struct libder_object *obj, size_t *osz) +{ + + if (obj->type->tag_constructed) + return (NULL); + + *osz = obj->length; + return (obj->payload); +} + +static const char * +libder_type_name(const struct libder_tag *type) +{ + static char namebuf[128]; + + if (type->tag_encoded) { + return ("{ ... }"); + } + + if (type->tag_class != BC_UNIVERSAL) + goto fallback; + +#define UTYPE(val) case val: return (&(#val)[3]) + switch (type->tag_short) { + UTYPE(BT_RESERVED); + UTYPE(BT_BOOLEAN); + UTYPE(BT_INTEGER); + UTYPE(BT_BITSTRING); + UTYPE(BT_OCTETSTRING); + UTYPE(BT_NULL); + UTYPE(BT_OID); + UTYPE(BT_OBJDESC); + UTYPE(BT_EXTERNAL); + UTYPE(BT_REAL); + UTYPE(BT_ENUMERATED); + UTYPE(BT_PDV); + UTYPE(BT_UTF8STRING); + UTYPE(BT_RELOID); + UTYPE(BT_NUMERICSTRING); + UTYPE(BT_STRING); + UTYPE(BT_TELEXSTRING); + UTYPE(BT_VIDEOTEXSTRING); + UTYPE(BT_IA5STRING); + UTYPE(BT_UTCTIME); + UTYPE(BT_GENTIME); + UTYPE(BT_GFXSTRING); + UTYPE(BT_VISSTRING); + UTYPE(BT_GENSTRING); + UTYPE(BT_UNIVSTRING); + UTYPE(BT_CHARSTRING); + UTYPE(BT_BMPSTRING); + case BT_SEQUENCE & ~BER_TYPE_CONSTRUCTED_MASK: + case BT_SEQUENCE: return "SEQUENCE"; + case BT_SET & ~BER_TYPE_CONSTRUCTED_MASK: + case BT_SET: return "SET"; + } + +fallback: + snprintf(namebuf, sizeof(namebuf), "%.02x", libder_type_simple(type)); + return (&namebuf[0]); +} + +static void +libder_obj_dump_internal(const struct libder_object *obj, FILE *fp, int lvl) +{ + static char spacer[4096]; + const struct libder_object *child; + + /* Primitive, goofy, but functional. */ + if (spacer[0] == '\0') + memset(spacer, '\t', sizeof(spacer)); + + if (lvl >= (int)sizeof(spacer)) { + /* Too large, truncate the display. */ + fprintf(fp, "%.*s...\n", (int)sizeof(spacer), spacer); + return; + } + + if (obj->children == NULL) { + size_t col = lvl * 8; + + col += fprintf(fp, "%.*sOBJECT[type=%s, size=%zx]%s", + lvl, spacer, libder_type_name(obj->type), + obj->length, obj->length != 0 ? ": " : ""); + + if (obj->length != 0) { + uint8_t printb; + +#define LIBDER_CONTENTS_WRAP 80 + for (size_t i = 0; i < obj->length; i++) { + if (col + 3 >= LIBDER_CONTENTS_WRAP) { + fprintf(fp, "\n%.*s ", lvl, spacer); + col = (lvl * 8) + 4; + } + + if (obj->payload == NULL) + printb = 0; + else + printb = obj->payload[i]; + + col += fprintf(fp, "%.02x ", printb); + } + } + + fprintf(fp, "\n"); + + return; + } + + fprintf(fp, "%.*sOBJECT[type=%s]\n", lvl, spacer, + libder_type_name(obj->type)); + DER_FOREACH_CHILD(child, obj) + libder_obj_dump_internal(child, fp, lvl + 1); +} + +void +libder_obj_dump(const struct libder_object *root, FILE *fp) +{ + + libder_obj_dump_internal(root, fp, 0); +} + +LIBDER_PRIVATE bool +libder_is_valid_obj(struct libder_ctx *ctx, const struct libder_tag *type, + const uint8_t *payload, size_t payloadsz, bool varlen) +{ + + if (payload != NULL) { + assert(payloadsz > 0); + assert(!varlen); + } else { + assert(payloadsz == 0); + } + + /* No rules for non-universal types. */ + if (type->tag_class != BC_UNIVERSAL || type->tag_encoded) + return (true); + + if (ctx->strict && type->tag_constructed) { + /* Types that don't allow constructed */ + switch (libder_type_simple(type) & ~BER_TYPE_CONSTRUCTED_MASK) { + case BT_BOOLEAN: + case BT_INTEGER: + case BT_REAL: + case BT_NULL: + libder_set_error(ctx, LDE_STRICT_PRIMITIVE); + return (false); + default: + break; + } + } else if (ctx->strict) { + /* Types that cannot be primitive */ + switch (libder_type_simple(type) | BER_TYPE_CONSTRUCTED_MASK) { + case BT_SEQUENCE: + case BT_SET: + libder_set_error(ctx, LDE_STRICT_CONSTRUCTED); + return (false); + default: + break; + } + } + + /* Further validation */ + switch (libder_type_simple(type)) { + case BT_BOOLEAN: + if (ctx->strict && payloadsz != 1) { + libder_set_error(ctx, LDE_STRICT_BOOLEAN); + return (false); + } + break; + case BT_NULL: + if (ctx->strict && (payloadsz != 0 || varlen)) { + libder_set_error(ctx, LDE_STRICT_NULL); + return (false); + } + break; + case BT_BITSTRING: /* Primitive */ + /* + * Bit strings require more invasive parsing later during child + * coalescing or normalization, so we alway strictly enforce + * their form. + */ + if (payloadsz == 1 && payload[0] != 0) + return (false); + + /* We can't have more than seven unused bits. */ + return (payloadsz < 2 || payload[0] < 8); + case BT_RESERVED: + if (payloadsz != 0) { + libder_set_error(ctx, LDE_STRICT_EOC); + return (false); + } + break; + default: + break; + } + + return (true); +} + +LIBDER_PRIVATE bool +libder_obj_may_coalesce_children(const struct libder_object *obj) +{ + + /* No clue about non-universal types. */ + if (obj->type->tag_class != BC_UNIVERSAL || obj->type->tag_encoded) + return (false); + + /* Constructed types don't have children. */ + if (!obj->type->tag_constructed) + return (false); + + /* Strip the constructed bit off. */ + switch (libder_type_simple(obj->type)) { + case BT_OCTETSTRING: /* Raw data types */ + case BT_BITSTRING: + return (true); + case BT_UTF8STRING: /* String types */ + case BT_NUMERICSTRING: + case BT_STRING: + case BT_TELEXSTRING: + case BT_VIDEOTEXSTRING: + case BT_IA5STRING: + case BT_GFXSTRING: + case BT_VISSTRING: + case BT_GENSTRING: + case BT_UNIVSTRING: + case BT_CHARSTRING: + case BT_BMPSTRING: + return (true); + case BT_UTCTIME: /* Time types */ + case BT_GENTIME: + return (true); + default: + return (false); + } +} + +static size_t +libder_merge_bitstrings(uint8_t *buf, size_t offset, size_t bufsz, + const struct libder_object *child) +{ + const uint8_t *rhs = child->payload; + size_t rsz = child->disk_size, startoff = offset; + uint8_t rhsunused, unused; + + rhsunused = (rhs != NULL ? rhs[0] : 0); + + /* We have no unused bits if the buffer's empty as of yet. */ + if (offset == 0) + unused = 0; + else + unused = buf[0]; + + /* Shave the lead byte off if we have one. */ + if (rsz > 1) { + if (rhs != NULL) + rhs++; + rsz--; + } + + if (unused == 0) { + size_t extra = 0; + + /* + * In all cases we'll just write the unused byte separately, + * since we're copying way past it in the common case and can't + * just overwrite it as part of the memcpy(). + */ + if (offset == 0) { + offset = 1; + extra++; + } + + assert(rhsunused < 8); + assert(offset + rsz <= bufsz); + + buf[0] = rhsunused; + if (rhs == NULL) + memset(&buf[offset], 0, rsz); + else + memcpy(&buf[offset], rhs, rsz); + + return (rsz + extra); + } + + for (size_t i = 0; i < rsz; i++) { + uint8_t bits, next; + + if (rhs == NULL) + next = 0; + else + next = rhs[i]; + + /* Rotate the leading bits into the byte before it. */ + assert(unused < 8); + bits = next >> (8 - unused); + buf[offset - 1] |= bits; + + next <<= unused; + + /* + * Copy the new valid bits in; we shift over the old unused + * amount up until the very last bit, then we have to recalculate + * because we may be dropping it entirely. + */ + if (i == rsz - 1) { + assert(rhsunused < 8); + + /* + * Figure out how many unused bits we have between the two + * buffers, sum % 8 is the new # unused bits. It will be + * somewhere in the range of [0, 14], and if it's at or + * higher than a single byte then that's a clear indicator + * that we shifted some unused bits into the previous byte and + * can just halt here. + */ + unused += rhsunused; + buf[0] = unused % 8; + if (unused >= 8) + break; + } + + assert(offset < bufsz); + buf[offset++] = next; + } + + return (offset - startoff); +} + +LIBDER_PRIVATE bool +libder_obj_coalesce_children(struct libder_object *obj, struct libder_ctx *ctx) +{ + struct libder_object *child, *last_child, *tmp; + size_t new_size = 0, offset = 0; + uint8_t *coalesced_data; + uint8_t type; + bool need_payload = false, strict_violation = false; + + if (obj->nchildren == 0 || !libder_obj_may_coalesce_children(obj)) + return (true); + + assert(obj->type->tag_class == BC_UNIVERSAL); + assert(obj->type->tag_constructed); + assert(!obj->type->tag_encoded); + type = obj->type->tag_short; + + last_child = NULL; + DER_FOREACH_CHILD(child, obj) { + /* Sanity check and coalesce our children. */ + if (child->type->tag_class != BC_UNIVERSAL || + child->type->tag_short != obj->type->tag_short) { + libder_set_error(ctx, LDE_COALESCE_BADCHILD); + return (false); + } + + /* Recursively coalesce everything. */ + if (!libder_obj_coalesce_children(child, ctx)) + return (false); + + /* + * The child node will be disappearing anyways, so we stash the + * disk size sans header in its disk_size to reuse in the later + * loop. + */ + child->disk_size = libder_obj_disk_size(child, false); + + /* + * We strip the lead byte off of every element, and add it back + * in pre-allocation. + */ + if (type == BT_BITSTRING && child->disk_size > 1) + child->disk_size--; + if (child->disk_size > 0) + last_child = child; + + new_size += child->disk_size; + + if (child->payload != NULL) + need_payload = true; + } + + if (new_size != 0 && need_payload) { + if (type == BT_BITSTRING) + new_size++; + coalesced_data = malloc(new_size); + if (coalesced_data == NULL) { + libder_set_error(ctx, LDE_NOMEM); + return (false); + } + } else { + /* + * This would perhaps be a bit weird, but that's normalization + * for you. We shouldn't really have a UTF-8 string that's + * composed of a series of zero-length UTF-8 strings, but + * weirder things have happened. + */ + coalesced_data = NULL; + } + + /* Avoid leaking any children as we coalesce. */ + DER_FOREACH_CHILD_SAFE(child, obj, tmp) { + if (child->disk_size != 0) + assert(coalesced_data != NULL || !need_payload); + + /* + * Just free everything when we violate strict rules. + */ + if (strict_violation) + goto violated; + + if (child->disk_size != 0 && need_payload) { + assert(coalesced_data != NULL); + assert(offset + child->disk_size <= new_size); + + /* + * Bit strings are special, in that the first byte + * contains the number of unused bits at the end. We + * need to trim that off when concatenating bit strings + */ + if (type == BT_BITSTRING) { + if (ctx->strict && child != last_child && + child->disk_size > 1 && child->payload != NULL) { + /* + * Each child must have a multiple of 8, + * up until the final one. + */ + if (child->payload[0] != 0) { + libder_set_error(ctx, LDE_STRICT_BITSTRING); + strict_violation = true; + goto violated; + } + } + + offset += libder_merge_bitstrings(coalesced_data, + offset, new_size, child); + } else { + /* + * Write zeroes out if we don't have a payload. + */ + if (child->payload == NULL) { + memset(&coalesced_data[offset], 0, child->disk_size); + offset += child->disk_size; + } else { + memcpy(&coalesced_data[offset], child->payload, + child->disk_size); + offset += child->disk_size; + } + } + } + +violated: + libder_obj_free(child); + } + + assert(offset <= new_size); + + /* Zap the children, we've absorbed their bodies. */ + obj->children = NULL; + + if (strict_violation) { + if (coalesced_data != NULL) { + libder_bzero(coalesced_data, offset); + free(coalesced_data); + } + + return (false); + } + + /* Finally, swap out the payload. */ + if (obj->payload != NULL) { + libder_bzero(obj->payload, obj->length); + free(obj->payload); + } + + obj->length = offset; + obj->payload = coalesced_data; + obj->type->tag_constructed = false; + + return (true); +} + +static bool +libder_obj_normalize_bitstring(struct libder_object *obj) +{ + uint8_t *payload = obj->payload; + size_t length = obj->length; + uint8_t unused; + + if (payload == NULL || length < 2) + return (true); + + unused = payload[0]; + if (unused == 0) + return (true); + + /* Clear the unused bits completely. */ + payload[length - 1] &= ~((1 << unused) - 1); + return (true); +} + +static bool +libder_obj_normalize_boolean(struct libder_object *obj) +{ + uint8_t *payload = obj->payload; + size_t length = obj->length; + int sense = 0; + + assert(length > 0); + + /* + * Booleans must be collapsed down to a single byte, 0x00 or 0xff, + * indicating false or true respectively. + */ + if (length == 1 && (payload[0] == 0x00 || payload[0] == 0xff)) + return (true); + + for (size_t bpos = 0; bpos < length; bpos++) { + sense |= payload[bpos]; + if (sense != 0) + break; + } + + payload[0] = sense != 0 ? 0xff : 0x00; + obj->length = 1; + return (true); +} + +static bool +libder_obj_normalize_integer(struct libder_object *obj) +{ + uint8_t *payload = obj->payload; + size_t length = obj->length; + size_t strip = 0; + + /* + * Strip any leading sign-extended looking bytes, but note that + * we can't strip a leading byte unless it matches the sign bit + * on the next byte. + */ + for (size_t bpos = 0; bpos < length - 1; bpos++) { + if (payload[bpos] != 0 && payload[bpos] != 0xff) + break; + + if (payload[bpos] == 0xff) { + /* Only if next byte indicates signed. */ + if ((payload[bpos + 1] & 0x80) == 0) + break; + } else { + /* Only if next byte indicates unsigned. */ + if ((payload[bpos + 1] & 0x80) != 0) + break; + } + + strip++; + } + + if (strip != 0) { + payload += strip; + length -= strip; + + memmove(&obj->payload[0], payload, length); + obj->length = length; + } + + return (true); +} + +static int +libder_obj_tag_compare(const struct libder_tag *lhs, const struct libder_tag *rhs) +{ + const uint8_t *lbits, *rbits; + size_t delta, end, lsz, rsz; + uint8_t lbyte, rbyte; + + /* Highest bits: tag class, libder_ber_class has the same bit ordering. */ + if (lhs->tag_class < rhs->tag_class) + return (-1); + if (lhs->tag_class > rhs->tag_class) + return (1); + + /* Next bit: constructed vs. primitive */ + if (!lhs->tag_constructed && rhs->tag_constructed) + return (-1); + if (lhs->tag_constructed && rhs->tag_constructed) + return (1); + + /* + * Finally: tag data; we can use the size as a first-order heuristic + * because we store tags in the shortest possible representation. + */ + if (lhs->tag_size < rhs->tag_size) + return (-1); + else if (lhs->tag_size > rhs->tag_size) + return (1); + + if (!lhs->tag_encoded) { + lbits = (const void *)&lhs->tag_short; + lsz = sizeof(uint64_t); + } else { + lbits = lhs->tag_long; + lsz = lhs->tag_size; + } + + if (!rhs->tag_encoded) { + rbits = (const void *)&rhs->tag_short; + rsz = sizeof(uint64_t); + } else { + rbits = rhs->tag_long; + rsz = rhs->tag_size; + } + + delta = 0; + end = MAX(lsz, rsz); + if (lsz > rsz) + delta = lsz - rsz; + else if (lsz < rsz) + delta = rsz - lsz; + for (size_t i = 0; i < end; i++) { + /* Zero-extend the short one the difference. */ + if (lsz < rsz && i < delta) + lbyte = 0; + else + lbyte = lbits[i - delta]; + + if (lsz > rsz && i < delta) + rbyte = 0; + else + rbyte = rbits[i - delta]; + + if (lbyte < rbyte) + return (-1); + else if (lbyte > rbyte) + return (-1); + } + + return (0); +} + +/* + * Similar to strcmp(), returns -1, 0, or 1. + */ +static int +libder_obj_compare(const struct libder_object *lhs, const struct libder_object *rhs) +{ + size_t end; + int cmp; + uint8_t lbyte, rbyte; + + cmp = libder_obj_tag_compare(lhs->type, rhs->type); + if (cmp != 0) + return (cmp); + + /* + * We'll compare up to the longer of the two; the shorter payload is + * zero-extended at the end for comparison purposes. + */ + end = MAX(lhs->length, rhs->length); + for (size_t pos = 0; pos < end; pos++) { + if (lhs->payload != NULL && pos < lhs->length) + lbyte = lhs->payload[pos]; + else + lbyte = 0; + if (rhs->payload != NULL && pos < rhs->length) + rbyte = rhs->payload[pos]; + else + rbyte = 0; + + if (lbyte < rbyte) + return (-1); + else if (lbyte > rbyte) + return (1); + } + + return (0); +} + +static int +libder_obj_normalize_set_cmp(const void *lhs_entry, const void *rhs_entry) +{ + const struct libder_object *lhs = + *__DECONST(const struct libder_object **, lhs_entry); + const struct libder_object *rhs = + *__DECONST(const struct libder_object **, rhs_entry); + + return (libder_obj_compare(lhs, rhs)); +} + +static bool +libder_obj_normalize_set(struct libder_object *obj, struct libder_ctx *ctx) +{ + struct libder_object **sorting; + struct libder_object *child; + size_t offset = 0; + + if (obj->nchildren < 2) + return (true); + + /* + * Kind of goofy, but we'll just take advantage of a standardized + * qsort() rather than rolling our own sort -- we have no idea how large + * of a dataset we're working with. + */ + sorting = calloc(obj->nchildren, sizeof(*sorting)); + if (sorting == NULL) { + libder_set_error(ctx, LDE_NOMEM); + return (false); + } + + DER_FOREACH_CHILD(child, obj) { + sorting[offset++] = child; + } + + assert(offset == obj->nchildren); + qsort(sorting, offset, sizeof(*sorting), libder_obj_normalize_set_cmp); + + obj->children = sorting[0]; + sorting[offset - 1]->next = NULL; + for (size_t i = 0; i < offset - 1; i++) { + sorting[i]->next = sorting[i + 1]; + } + + free(sorting); + + return (true); +} + +LIBDER_PRIVATE bool +libder_obj_normalize(struct libder_object *obj, struct libder_ctx *ctx) +{ + uint8_t *payload = obj->payload; + size_t length = obj->length; + + if (obj->type->tag_constructed) { + /* + * For constructed types, we'll see if we can coalesce their + * children into them, then we'll proceed with whatever normalization + * rules we can apply to the children. + */ + if (DER_NORMALIZING(ctx, CONSTRUCTED) && !libder_obj_coalesce_children(obj, ctx)) + return (false); + + /* + * We may not be a constructed object anymore after the above coalescing + * happened, so we check it again here. Constructed objects need not go + * any further, but the now-primitive coalesced types still need to be + * normalized. + */ + if (obj->type->tag_constructed) { + struct libder_object *child; + + DER_FOREACH_CHILD(child, obj) { + if (!libder_obj_normalize(child, ctx)) + return (false); + } + + /* Sets must be sorted. */ + if (obj->type->tag_short != BT_SET) + return (true); + + return (libder_obj_normalize_set(obj, ctx)); + } + } + + /* We only have normalization rules for universal types. */ + if (obj->type->tag_class != BC_UNIVERSAL || obj->type->tag_encoded) + return (true); + + if (!libder_normalizing_type(ctx, obj->type)) + return (true); + + /* + * We are clear to normalize this object, check for some easy cases that + * don't need normalization. + */ + switch (libder_type_simple(obj->type)) { + case BT_BITSTRING: + case BT_BOOLEAN: + case BT_INTEGER: + /* + * If we have a zero payload, then we need to encode them as a + * single zero byte. + */ + if (payload == NULL) { + if (length != 1) + obj->length = 1; + + return (true); + } + + break; + case BT_NULL: + if (payload != NULL) { + free(payload); + + obj->payload = NULL; + obj->length = 0; + } + + return (true); + default: + /* + * If we don't have a payload, we'll just leave it alone. + */ + if (payload == NULL) + return (true); + break; + } + + switch (libder_type_simple(obj->type)) { + case BT_BITSTRING: + return (libder_obj_normalize_bitstring(obj)); + case BT_BOOLEAN: + return (libder_obj_normalize_boolean(obj)); + case BT_INTEGER: + return (libder_obj_normalize_integer(obj)); + default: + break; + } + + return (true); +} diff --git a/libder/libder_private.h b/libder/libder_private.h new file mode 100644 index 00000000000..3324420ef6d --- /dev/null +++ b/libder/libder_private.h @@ -0,0 +1,178 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +#include +#include +#include +#ifdef __APPLE__ +#define __STDC_WANT_LIB_EXT1__ 1 /* memset_s */ +#endif +/* explicit_bzero is in one of these... */ +#include +#include +#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); +} + +static inline void +libder_bzero(uint8_t *buf, size_t bufsz) +{ + +#ifdef __APPLE__ + memset_s(buf, bufsz, 0, bufsz); +#else + explicit_bzero(buf, bufsz); +#endif +} + +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 *); diff --git a/libder/libder_read.3 b/libder/libder_read.3 new file mode 100644 index 00000000000..69c9ba8d0d2 --- /dev/null +++ b/libder/libder_read.3 @@ -0,0 +1,101 @@ +.\" +.\" SPDX-Copyright-Identifier: BSD-2-Clause +.\" +.\" Copyright (C) 2024 Kyle Evans +.\" +.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 diff --git a/libder/libder_read.c b/libder/libder_read.c new file mode 100644 index 00000000000..dba56746be2 --- /dev/null +++ b/libder/libder_read.c @@ -0,0 +1,864 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + + if (payload->payload_data != NULL) { + libder_bzero(payload->payload_data, payload->payload_size); + 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) +{ + if (stream->stream_buf != NULL) { + libder_bzero(stream->stream_buf, stream->stream_bufsz); + 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]); +} + +/* + * We can't just use realloc() because it won't provide any guarantees about + * the previous region if it can't just resize in-place, so we'll always just + * allocate a new one and copy ourselves. + */ +static uint8_t * +libder_read_realloc(uint8_t *ptr, size_t oldsz, size_t newsz) +{ + uint8_t *newbuf; + + if (oldsz == 0) + assert(ptr == NULL); + else + assert(ptr != NULL); + assert(newsz > oldsz); + + newbuf = malloc(newsz); + if (newbuf == NULL) + return (NULL); + + if (oldsz != 0) { + memcpy(newbuf, ptr, oldsz); + + libder_bzero(ptr, oldsz); + free(ptr); + } + + return (newbuf); +} + +#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; + + /* + * We play it conservative here: we could allocate the + * buffer up-front, but we have no idea how much data we + * actually have to receive! The length is a potentially + * attacker-controlled aspect, so we're cautiously optimistic + * that it's accurate. + */ + 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) { + libder_bzero(payload_data, offset); + free(payload_data); + + libder_set_error(ctx, LDE_SHORTDATA); + goto failed; + } + + next_data = libder_read_realloc(payload_data, + offset, offset + req); + if (next_data == NULL) { + libder_bzero(payload_data, offset); + 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); +} diff --git a/libder/libder_type.3 b/libder/libder_type.3 new file mode 100644 index 00000000000..df577a70f40 --- /dev/null +++ b/libder/libder_type.3 @@ -0,0 +1,71 @@ +.\" +.\" SPDX-Copyright-Identifier: BSD-2-Clause +.\" +.\" Copyright (C) 2024 Kyle Evans +.\" +.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 diff --git a/libder/libder_type.c b/libder/libder_type.c new file mode 100644 index 00000000000..dec942ce68f --- /dev/null +++ b/libder/libder_type.c @@ -0,0 +1,150 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#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; +} diff --git a/libder/libder_write.3 b/libder/libder_write.3 new file mode 100644 index 00000000000..8b1a5aa2bbf --- /dev/null +++ b/libder/libder_write.3 @@ -0,0 +1,54 @@ +.\" +.\" SPDX-Copyright-Identifier: BSD-2-Clause +.\" +.\" Copyright (C) 2024 Kyle Evans +.\" +.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 diff --git a/libder/libder_write.c b/libder/libder_write.c new file mode 100644 index 00000000000..66ccbcfbf21 --- /dev/null +++ b/libder/libder_write.c @@ -0,0 +1,229 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#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)) { + libder_bzero(mwrite.buf, mwrite.offset); + 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); +} diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 00000000000..075588a81e7 --- /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 00000000000..fc366ab88ed --- /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 00000000000..afd4425970a --- /dev/null +++ b/tests/fuzz_parallel.c @@ -0,0 +1,111 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 00000000000..0f7cc6167e7 --- /dev/null +++ b/tests/fuzz_stream.c @@ -0,0 +1,246 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#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 00000000000..2ad5b5eb176 --- /dev/null +++ b/tests/fuzz_write.c @@ -0,0 +1,79 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#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 00000000000..0f94bc7f25f --- /dev/null +++ b/tests/fuzzers.h @@ -0,0 +1,40 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +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 00000000000..68554d7c17d --- /dev/null +++ b/tests/make_corpus.c @@ -0,0 +1,137 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#undef NDEBUG +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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] \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, ¶ms, sizeof(params)) == sizeof(params)); + else + assert(write(fd, ¶ms->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(¶ms, 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(¶ms, 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 0000000000000000000000000000000000000000..74a030b6802c7a3a83de957d7ecb78958a1b094a GIT binary patch literal 64 zcmV-G0Kfk*J^}#&1P-P3Dpq0RSaw`b){S6Ppa%v8D}e+62B9DW9smTcX+aH~oANaF WEqsyT$A2TBgfv_@CfLuu_uPu5hZxZS literal 0 HcmV?d00001 diff --git a/tests/repo.pub b/tests/repo.pub new file mode 100644 index 0000000000000000000000000000000000000000..bdcb1a20a1c78440d43e49bc969e56ce00ba9c92 GIT binary patch literal 88 zcmV-e0H^;jRxl6-2P%e0&OHJF1_djD1ON&HLI4EOb|5+BK4*Q6{lp<4&9J{mhido- ubH2XSyF9!yliA;vCASVdRTA!c0yz)vd;S`ecK5=p1-hDpH}%a`S2=HC$s}?B literal 0 HcmV?d00001 diff --git a/tests/test_common.h b/tests/test_common.h new file mode 100644 index 00000000000..76e850f1912 --- /dev/null +++ b/tests/test_common.h @@ -0,0 +1,29 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +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 00000000000..5e7519f5a71 --- /dev/null +++ b/tests/test_privkey.c @@ -0,0 +1,175 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 00000000000..9fbd070f0e8 --- /dev/null +++ b/tests/test_pubkey.c @@ -0,0 +1,143 @@ +/*- + * Copyright (c) 2024 Kyle Evans + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#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); +} From 9c40c4de4c33b2ba1124fb752ebea0bebaa6013f Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Mon, 16 Dec 2024 22:36:43 -0600 Subject: [PATCH 2/2] Remove unwanted artifact from libder import *sigh* --- 1.0.tar.gz | Bin 32406 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 1.0.tar.gz diff --git a/1.0.tar.gz b/1.0.tar.gz deleted file mode 100644 index 63cb6ee1df5bfac19923803ce2d48b6c78831e84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32406 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 ZrZlA~O=(I~n$pD5e*u>MxS{}90swTOfU*Dp