Add 'contrib/libder/' from commit '9c40c4de4c33b2ba1124fb752ebea0bebaa6013f'

git-subtree-dir: contrib/libder
git-subtree-mainline: d11904b350214943dedb64c7121d4602799d7afd
git-subtree-split: 9c40c4de4c33b2ba1124fb752ebea0bebaa6013f
(cherry picked from commit 35c0a8c449fd2b7f75029ebed5e10852240f0865)
This commit is contained in:
Kyle Evans 2025-01-01 15:11:02 -06:00
parent 6584e5a1c5
commit ba667efb53
36 changed files with 4813 additions and 0 deletions

View file

@ -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

View file

@ -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

11
contrib/libder/.gitignore vendored Normal file
View file

@ -0,0 +1,11 @@
.*.swp
.depend*
*.a
*.so
*.so.*
*.o
*.pico
*.debug
*.full
build/

View file

@ -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)

22
contrib/libder/LICENSE Normal file
View file

@ -0,0 +1,22 @@
Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

28
contrib/libder/README.md Normal file
View file

@ -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.

1
contrib/libder/derdump/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
derdump

View file

@ -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)

View file

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

View file

@ -0,0 +1,52 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <err.h>
#include <stdio.h>
#include <libder.h>
int
main(int argc, char *argv[])
{
FILE *fp;
struct libder_ctx *ctx;
struct libder_object *root;
size_t rootsz;
bool first = true;
if (argc < 2) {
fprintf(stderr, "usage: %s file [file...]\n", argv[0]);
return (1);
}
ctx = libder_open();
libder_set_verbose(ctx, 2);
for (int i = 1; i < argc; i++) {
fp = fopen(argv[i], "rb");
if (fp == NULL) {
warn("%s", argv[i]);
continue;
}
if (!first)
fprintf(stderr, "\n");
fprintf(stdout, "[%s]\n", argv[i]);
root = libder_read_file(ctx, fp, &rootsz);
if (root != NULL) {
libder_obj_dump(root, stdout);
libder_obj_free(root);
root = NULL;
}
first = false;
fclose(fp);
}
libder_close(ctx);
return (0);
}

View file

@ -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()

View file

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

View file

@ -0,0 +1,119 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "libder_private.h"
#include <stdlib.h>
#include <unistd.h>
/*
* Sets up the context, returns NULL on error.
*/
struct libder_ctx *
libder_open(void)
{
struct libder_ctx *ctx;
ctx = malloc(sizeof(*ctx));
if (ctx == NULL)
return (NULL);
/* Initialize */
ctx->error = LDE_NONE;
ctx->buffer_size = 0;
ctx->verbose = 0;
ctx->normalize = LIBDER_NORMALIZE_ALL;
ctx->strict = true;
ctx->abort = 0;
return (ctx);
}
void
libder_abort(struct libder_ctx *ctx)
{
ctx->abort = 1;
}
LIBDER_PRIVATE size_t
libder_get_buffer_size(struct libder_ctx *ctx)
{
if (ctx->buffer_size == 0) {
long psize;
psize = sysconf(_SC_PAGESIZE);
if (psize <= 0)
psize = 4096;
ctx->buffer_size = psize;
}
return (ctx->buffer_size);
}
uint64_t
libder_get_normalize(struct libder_ctx *ctx)
{
return (ctx->normalize);
}
/*
* Set the normalization flags; returns the previous value.
*/
uint64_t
libder_set_normalize(struct libder_ctx *ctx, uint64_t nmask)
{
uint64_t old = ctx->normalize;
ctx->normalize = (nmask & LIBDER_NORMALIZE_ALL);
return (old);
}
bool
libder_get_strict(struct libder_ctx *ctx)
{
return (ctx->strict);
}
bool
libder_set_strict(struct libder_ctx *ctx, bool strict)
{
bool oval = ctx->strict;
ctx->strict = strict;
return (oval);
}
int
libder_get_verbose(struct libder_ctx *ctx)
{
return (ctx->verbose);
}
int
libder_set_verbose(struct libder_ctx *ctx, int verbose)
{
int oval = ctx->verbose;
ctx->verbose = verbose;
return (oval);
}
void
libder_close(struct libder_ctx *ctx)
{
if (ctx == NULL)
return;
free(ctx);
}

View file

@ -0,0 +1,181 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
enum libder_ber_class {
BC_UNIVERSAL = 0,
BC_APPLICATION = 1,
BC_CONTEXT = 2,
BC_PRIVATE = 3,
};
enum libder_ber_type {
BT_RESERVED = 0x00,
BT_BOOLEAN = 0x01,
BT_INTEGER = 0x02,
BT_BITSTRING = 0x03,
BT_OCTETSTRING = 0x04,
BT_NULL = 0x05,
BT_OID = 0x06,
BT_OBJDESC = 0x07,
BT_EXTERNAL = 0x08,
BT_REAL = 0x09,
BT_ENUMERATED = 0x0a,
BT_PDV = 0x0b,
BT_UTF8STRING = 0x0c,
BT_RELOID = 0x0d,
/* 0x10, 011 not usable */
BT_NUMERICSTRING = 0x012,
BT_STRING = 0x13,
BT_TELEXSTRING = 0x14,
BT_VIDEOTEXSTRING = 0x15,
BT_IA5STRING = 0x16,
BT_UTCTIME = 0x17,
BT_GENTIME = 0x18,
BT_GFXSTRING = 0x19,
BT_VISSTRING = 0x1a,
BT_GENSTRING = 0x1b,
BT_UNIVSTRING = 0x1c,
BT_CHARSTRING = 0x1d,
BT_BMPSTRING = 0x1e,
BT_SEQUENCE = 0x30,
BT_SET = 0x31,
};
#define BER_TYPE_CONSTRUCTED_MASK 0x20 /* Bit 6 */
#define BER_TYPE_CLASS_MASK 0xc0 /* Bits 7 and 8 */
/*
* The difference between the type and the full type is just that the full type
* will indicate the class of type, so it may be more useful for some operations.
*/
#define BER_FULL_TYPE(tval) \
((tval) & ~(BER_TYPE_CONSTRUCTED_MASK))
#define BER_TYPE(tval) \
((tval) & ~(BER_TYPE_CLASS_MASK | BER_TYPE_CONSTRUCTED_MASK))
#define BER_TYPE_CLASS(tval) \
(((tval) & BER_TYPE_CLASS_MASK) >> 6)
#define BER_TYPE_CONSTRUCTED(tval) \
(((tval) & BER_TYPE_CONSTRUCTED_MASK) != 0)
enum libder_error {
LDE_NONE = 0x00,
LDE_NOMEM, /* Out of memory */
LDE_INVAL, /* Invalid parameter */
LDE_SHORTHDR, /* Header too short */
LDE_BADVARLEN, /* Bad variable length encoding */
LDE_LONGLEN, /* Encoded length too large (8 byte max) */
LDE_SHORTDATA, /* Payload not available */
LDE_GARBAGE, /* Garbage after encoded data */
LDE_STREAMERR, /* Stream error */
LDE_TRUNCVARLEN, /* Variable length object truncated */
LDE_COALESCE_BADCHILD, /* Bad child encountered when coalescing */
LDE_BADOBJECT, /* Payload not valid for object type */
/* Strict violations */
LDE_STRICT_EOC, /* Strict: end-of-content violation */
LDE_STRICT_TAG, /* Strict: tag violation */
LDE_STRICT_PVARLEN, /* Strict: primitive using indefinite length */
LDE_STRICT_BOOLEAN, /* Strict: boolean encoded incorrectly */
LDE_STRICT_NULL, /* Strict: null encoded incorrectly */
LDE_STRICT_PRIMITIVE, /* Strict: type must be primitive */
LDE_STRICT_CONSTRUCTED, /* Strict: type must be constructed */
LDE_STRICT_BITSTRING, /* Strict: malformed constructed bitstring */
};
struct libder_ctx;
struct libder_tag;
struct libder_object;
/*
* By default we normalize everything, but we allow some subset of the
* functionality to be disabled. Lengths are non-optional and will always be
* normalized to a fixed short or long length. The upper 32-bits of
* ctx->normalize are reserved for universal types so that we can quickly map
* those without assigning them names.
*/
/* Normalize constructed types that should be coalesced (e.g., strings, time). */
#define LIBDER_NORMALIZE_CONSTRUCTED 0x0000000000000001ULL
/*
* Normalize tags on read. This is mostly a measure to ensure that
* normalization on write doesn't get thwarted; there's no reason anybody should
* be encoding low tags with the long form, but the spec doesn't appear to
* forbid it.
*/
#define LIBDER_NORMALIZE_TAGS 0x0000000000000002ULL
/* Universal types (reserved) */
#define LIBDER_NORMALIZE_TYPE_MASK 0xffffffff00000000ULL
#define LIBDER_NORMALIZE_TYPE_FLAG(val) ((1ULL << val) << 32ULL)
/* All valid bits. */
#define LIBDER_NORMALIZE_ALL \
(LIBDER_NORMALIZE_TYPE_MASK | LIBDER_NORMALIZE_CONSTRUCTED | \
LIBDER_NORMALIZE_TAGS)
struct libder_ctx * libder_open(void);
void libder_close(struct libder_ctx *);
void libder_abort(struct libder_ctx *);
const char *libder_get_error(struct libder_ctx *);
bool libder_has_error(struct libder_ctx *);
uint64_t libder_get_normalize(struct libder_ctx *);
uint64_t libder_set_normalize(struct libder_ctx *, uint64_t);
bool libder_get_strict(struct libder_ctx *);
bool libder_set_strict(struct libder_ctx *, bool);
int libder_get_verbose(struct libder_ctx *);
int libder_set_verbose(struct libder_ctx *, int);
struct libder_object *libder_read(struct libder_ctx *, const uint8_t *, size_t *);
struct libder_object *libder_read_fd(struct libder_ctx *, int, size_t *);
struct libder_object *libder_read_file(struct libder_ctx *, FILE *, size_t *);
uint8_t *libder_write(struct libder_ctx *, struct libder_object *, uint8_t *,
size_t *);
#define DER_CHILDREN(obj) libder_obj_children(obj)
#define DER_NEXT(obj) libder_obj_next(obj)
#define DER_FOREACH_CHILD(var, obj) \
for ((var) = DER_CHILDREN((obj)); \
(var); \
(var) = DER_NEXT((var)))
#define DER_FOREACH_CHILD_SAFE(var, obj, tvar) \
for ((var) = DER_CHILDREN((obj)); \
(var) && ((tvar) = DER_NEXT((var)), 1); \
(var) = (tvar))
struct libder_object *libder_obj_alloc(struct libder_ctx *, struct libder_tag *, const uint8_t *, size_t);
struct libder_object *libder_obj_alloc_simple(struct libder_ctx *, uint8_t, const uint8_t *,
size_t);
void libder_obj_free(struct libder_object *);
bool libder_obj_append(struct libder_object *, struct libder_object *);
struct libder_object *libder_obj_child(const struct libder_object *, size_t);
struct libder_object *libder_obj_children(const struct libder_object *);
struct libder_object *libder_obj_next(const struct libder_object *);
struct libder_tag *libder_obj_type(const struct libder_object *);
uint8_t libder_obj_type_simple(const struct libder_object *);
const uint8_t *libder_obj_data(const struct libder_object *, size_t *);
/* Debugging aide -- probably shouldn't use. */
void libder_obj_dump(const struct libder_object *, FILE *);
struct libder_tag *libder_type_alloc_simple(struct libder_ctx *, uint8_t);
struct libder_tag *libder_type_dup(struct libder_ctx *, const struct libder_tag *);
void libder_type_free(struct libder_tag *);
#define libder_type_simple libder_type_simple_abi
uint8_t libder_type_simple(const struct libder_tag *);

View file

@ -0,0 +1,76 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <stdio.h>
#include "libder_private.h"
#undef libder_set_error
static const char libder_error_nodesc[] = "[Description not available]";
#define DESCRIBE(err, msg) { LDE_ ## err, msg }
static const struct libder_error_desc {
enum libder_error desc_error;
const char *desc_str;
} libder_error_descr[] = {
DESCRIBE(NONE, "No error"),
DESCRIBE(NOMEM, "Out of memory"),
DESCRIBE(INVAL, "Invalid parameter"),
DESCRIBE(SHORTHDR, "Header too short"),
DESCRIBE(BADVARLEN, "Bad variable length encoding"),
DESCRIBE(LONGLEN, "Encoded length too large (8 byte max)"),
DESCRIBE(SHORTDATA, "Payload not available (too short)"),
DESCRIBE(GARBAGE, "Garbage after encoded data"),
DESCRIBE(STREAMERR, "Stream error"),
DESCRIBE(TRUNCVARLEN, "Variable length object truncated"),
DESCRIBE(COALESCE_BADCHILD, "Bad child encountered when coalescing"),
DESCRIBE(BADOBJECT, "Payload not valid for object type"),
DESCRIBE(STRICT_EOC, "Strict: end-of-content violation"),
DESCRIBE(STRICT_TAG, "Strict: tag violation"),
DESCRIBE(STRICT_PVARLEN, "Strict: primitive using indefinite length"),
DESCRIBE(STRICT_BOOLEAN, "Strict: boolean encoded incorrectly"),
DESCRIBE(STRICT_NULL, "Strict: null encoded incorrectly"),
DESCRIBE(STRICT_PRIMITIVE, "Strict: type must be primitive"),
DESCRIBE(STRICT_CONSTRUCTED, "Strict: type must be constructed"),
DESCRIBE(STRICT_BITSTRING, "Strict: malformed constructed bitstring"),
};
const char *
libder_get_error(struct libder_ctx *ctx)
{
const struct libder_error_desc *desc;
for (size_t i = 0; i < nitems(libder_error_descr); i++) {
desc = &libder_error_descr[i];
if (desc->desc_error == ctx->error)
return (desc->desc_str);
}
return (libder_error_nodesc);
}
bool
libder_has_error(struct libder_ctx *ctx)
{
return (ctx->error != 0);
}
LIBDER_PRIVATE void
libder_set_error(struct libder_ctx *ctx, int error, const char *file, int line)
{
ctx->error = error;
if (ctx->verbose >= 2) {
fprintf(stderr, "%s: [%s:%d]: %s (error %d)\n",
__func__, file, line, libder_get_error(ctx), error);
} else if (ctx->verbose >= 1) {
fprintf(stderr, "%s: %s (error %d)\n", __func__,
libder_get_error(ctx), error);
}
}

View file

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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,178 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <sys/param.h>
#include <assert.h>
#include <signal.h>
#include <stdbool.h>
#ifdef __APPLE__
#define __STDC_WANT_LIB_EXT1__ 1 /* memset_s */
#endif
/* explicit_bzero is in one of these... */
#include <string.h>
#include <strings.h>
#include "libder.h"
/* FreeBSD's sys/cdefs.h */
#ifndef __DECONST
#define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var))
#endif
#ifndef __unused
#define __unused __attribute__((__unused__))
#endif
/* FreeBSD's sys/params.h */
#ifndef nitems
#define nitems(x) (sizeof((x)) / sizeof((x)[0]))
#endif
#ifndef MIN
#define MIN(a,b) (((a)<(b))?(a):(b))
#endif
#ifndef MAX
#define MAX(a,b) (((a)>(b))?(a):(b))
#endif
struct libder_ctx;
struct libder_object;
struct libder_ctx {
uint64_t normalize;
size_t buffer_size;
enum libder_error error;
int verbose;
bool strict;
volatile sig_atomic_t abort;
};
struct libder_tag {
union {
uint8_t tag_short;
uint8_t *tag_long;
};
size_t tag_size;
enum libder_ber_class tag_class;
bool tag_constructed;
bool tag_encoded;
};
struct libder_object {
struct libder_tag *type;
size_t length;
size_t nchildren;
size_t disk_size;
uint8_t *payload; /* NULL for sequences */
struct libder_object *children;
struct libder_object *parent;
struct libder_object *next;
};
static inline sig_atomic_t
libder_check_abort(struct libder_ctx *ctx)
{
return (ctx->abort);
}
static inline void
libder_clear_abort(struct libder_ctx *ctx)
{
ctx->abort = 1;
}
#define LIBDER_PRIVATE __attribute__((__visibility__("hidden")))
#define DER_NORMALIZING(ctx, bit) \
(((ctx)->normalize & (LIBDER_NORMALIZE_ ## bit)) != 0)
static inline bool
libder_normalizing_type(const struct libder_ctx *ctx, const struct libder_tag *type)
{
uint8_t tagval;
assert(!type->tag_constructed);
assert(!type->tag_encoded);
assert(type->tag_class == BC_UNIVERSAL);
assert(type->tag_short < 0x1f);
tagval = type->tag_short;
return ((ctx->normalize & LIBDER_NORMALIZE_TYPE_FLAG(tagval)) != 0);
}
/* All of the lower bits set. */
#define BER_TYPE_LONG_MASK 0x1f
/*
* Check if the type matches one of our universal types.
*/
static inline bool
libder_type_is(const struct libder_tag *type, uint8_t utype)
{
if (type->tag_class != BC_UNIVERSAL || type->tag_encoded)
return (false);
if ((utype & BER_TYPE_CONSTRUCTED_MASK) != type->tag_constructed)
return (false);
utype &= ~BER_TYPE_CONSTRUCTED_MASK;
return (utype == type->tag_short);
}
/*
* We'll use this one a decent amount, so we'll keep it inline. There's also
* an _abi version that we expose as public interface via a 'libder_type_simple'
* macro.
*/
#undef libder_type_simple
static inline uint8_t
libder_type_simple(const struct libder_tag *type)
{
uint8_t encoded = type->tag_class << 6;
assert(!type->tag_encoded);
if (type->tag_constructed)
encoded |= BER_TYPE_CONSTRUCTED_MASK;
encoded |= type->tag_short;
return (encoded);
}
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 *);

View file

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

View file

@ -0,0 +1,864 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <sys/types.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "libder_private.h"
enum libder_stream_type {
LDST_NONE,
LDST_FD,
LDST_FILE,
};
struct libder_payload {
bool payload_heap;
uint8_t *payload_data;
size_t payload_size;
};
struct libder_stream {
enum libder_stream_type stream_type;
struct libder_ctx *stream_ctx;
uint8_t *stream_buf;
size_t stream_bufsz;
size_t stream_offset;
size_t stream_resid;
size_t stream_consumed;
size_t stream_last_commit;
union {
const uint8_t *stream_src_buf;
FILE *stream_src_file;
int stream_src_fd;
};
int stream_error;
bool stream_eof;
};
static uint8_t *
payload_move(struct libder_payload *payload, size_t *sz)
{
uint8_t *data;
size_t datasz;
data = NULL;
datasz = payload->payload_size;
if (payload->payload_heap) {
data = payload->payload_data;
} else if (datasz > 0) {
data = malloc(datasz);
if (data == NULL)
return (NULL);
memcpy(data, payload->payload_data, datasz);
}
payload->payload_heap = false;
payload->payload_data = NULL;
payload->payload_size = 0;
*sz = datasz;
return (data);
}
static void
payload_free(struct libder_payload *payload)
{
if (!payload->payload_heap)
return;
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);
}

View file

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

View file

@ -0,0 +1,150 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "libder_private.h"
uint8_t
libder_type_simple_abi(const struct libder_tag *type)
{
return (libder_type_simple(type));
}
/*
* We'll likely expose this in the form of libder_type_import(), which validates
* and allocates a tag.
*/
LIBDER_PRIVATE struct libder_tag *
libder_type_alloc(void)
{
return (calloc(1, sizeof(struct libder_tag)));
}
struct libder_tag *
libder_type_dup(struct libder_ctx *ctx, const struct libder_tag *dtype)
{
struct libder_tag *type;
type = libder_type_alloc();
if (type == NULL) {
libder_set_error(ctx, LDE_NOMEM);
return (NULL);
}
memcpy(type, dtype, sizeof(*dtype));
if (type->tag_encoded) {
uint8_t *tdata;
/* Deep copy the tag data. */
tdata = malloc(type->tag_size);
if (tdata == NULL) {
libder_set_error(ctx, LDE_NOMEM);
/*
* Don't accidentally free the caller's buffer; it may
* be an external user of the API.
*/
type->tag_long = NULL;
type->tag_size = 0;
libder_type_free(type);
return (NULL);
}
memcpy(tdata, dtype->tag_long, dtype->tag_size);
type->tag_long = tdata;
}
return (type);
}
struct libder_tag *
libder_type_alloc_simple(struct libder_ctx *ctx, uint8_t typeval)
{
struct libder_tag *type;
type = libder_type_alloc();
if (type == NULL) {
libder_set_error(ctx, LDE_NOMEM);
return (NULL);
}
type->tag_size = sizeof(typeval);
type->tag_class = BER_TYPE_CLASS(typeval);
type->tag_constructed = BER_TYPE_CONSTRUCTED(typeval);
type->tag_short = BER_TYPE(typeval);
return (type);
}
LIBDER_PRIVATE void
libder_type_release(struct libder_tag *type)
{
if (type->tag_encoded) {
free(type->tag_long);
type->tag_long = NULL;
/*
* Leaving type->tag_encoded set in case it helps us catch some
* bogus re-use of the type; we'd surface that as a null ptr
* deref as they think they should be using tag_long.
*/
}
}
void
libder_type_free(struct libder_tag *type)
{
if (type == NULL)
return;
libder_type_release(type);
free(type);
}
LIBDER_PRIVATE void
libder_normalize_type(struct libder_ctx *ctx, struct libder_tag *type)
{
uint8_t tagval;
size_t offset;
if (!type->tag_encoded || !DER_NORMALIZING(ctx, TAGS))
return;
/*
* Strip any leading 0's off -- not possible in strict mode.
*/
for (offset = 0; offset < type->tag_size - 1; offset++) {
if ((type->tag_long[offset] & 0x7f) != 0)
break;
}
assert(offset == 0 || !ctx->strict);
if (offset != 0) {
type->tag_size -= offset;
memmove(&type->tag_long[0], &type->tag_long[offset],
type->tag_size);
}
/*
* We might be able to strip it down to a unencoded tag_short, if only
* the lower 5 bits are in use.
*/
if (type->tag_size != 1 || (type->tag_long[0] & ~0x1e) != 0)
return;
tagval = type->tag_long[0];
free(type->tag_long);
type->tag_short = tagval;
type->tag_encoded = false;
}

View file

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

View file

@ -0,0 +1,229 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "libder.h"
#include "libder_private.h"
struct memory_write_data {
uint8_t *buf;
size_t bufsz;
size_t offset;
};
typedef bool (write_buffer_t)(void *, const uint8_t *, size_t);
static bool
libder_write_object_tag(struct libder_ctx *ctx __unused,
const struct libder_object *obj, write_buffer_t *write_buffer, void *cookie)
{
const struct libder_tag *type = obj->type;
uint8_t value;
if (!type->tag_encoded) {
value = libder_type_simple(type);
return (write_buffer(cookie, &value, sizeof(value)));
}
/* Write out the tag info first. */
value = BER_TYPE_LONG_MASK;
value |= type->tag_class << 6;
if (type->tag_constructed)
value |= BER_TYPE_CONSTRUCTED_MASK;
if (!write_buffer(cookie, &value, sizeof(value)))
return (false);
/* Write out the encoded tag next. */
return (write_buffer(cookie, type->tag_long, type->tag_size));
}
static bool
libder_write_object_header(struct libder_ctx *ctx, struct libder_object *obj,
write_buffer_t *write_buffer, void *cookie)
{
size_t size;
uint8_t sizelen, value;
if (!libder_write_object_tag(ctx, obj, write_buffer, cookie))
return (false);
size = obj->disk_size;
sizelen = libder_size_length(size);
if (sizelen == 1) {
assert((size & ~0x7f) == 0);
value = size;
if (!write_buffer(cookie, &value, sizeof(value)))
return (false);
} else {
/*
* Protocol supports at most 0x7f size bytes, but we can only
* do up to a size_t.
*/
uint8_t sizebuf[sizeof(size_t)], *sizep;
sizelen--; /* Remove the lead byte. */
value = 0x80 | sizelen;
if (!write_buffer(cookie, &value, sizeof(value)))
return (false);
sizep = &sizebuf[0];
for (uint8_t i = 0; i < sizelen; i++)
*sizep++ = (size >> ((sizelen - i - 1) * 8)) & 0xff;
if (!write_buffer(cookie, &sizebuf[0], sizelen))
return (false);
}
return (true);
}
static bool
libder_write_object_payload(struct libder_ctx *ctx __unused,
struct libder_object *obj, write_buffer_t *write_buffer, void *cookie)
{
uint8_t *payload = obj->payload;
size_t length = obj->length;
/* We don't expect `obj->payload` to be valid for a zero-size value. */
if (length == 0)
return (true);
/*
* We allow a NULL payload with a non-zero length to indicate that an
* object should write zeroes out, we just didn't waste the memory on
* these small allocations. Ideally if it's more than just one or two
* zeroes we're instead allocating a buffer for it and doing some more
* efficient copying from there.
*/
if (payload == NULL) {
uint8_t zero = 0;
for (size_t i = 0; i < length; i++) {
if (!write_buffer(cookie, &zero, 1))
return (false);
}
return (true);
}
return (write_buffer(cookie, payload, length));
}
static bool
libder_write_object(struct libder_ctx *ctx, struct libder_object *obj,
write_buffer_t *write_buffer, void *cookie)
{
struct libder_object *child;
if (DER_NORMALIZING(ctx, CONSTRUCTED) && !libder_obj_coalesce_children(obj, ctx))
return (false);
/* Write out this object's header first */
if (!libder_write_object_header(ctx, obj, write_buffer, cookie))
return (false);
/* Write out the payload. */
if (obj->children == NULL)
return (libder_write_object_payload(ctx, obj, write_buffer, cookie));
assert(obj->type->tag_constructed);
/* Recurse on each child. */
DER_FOREACH_CHILD(child, obj) {
if (!libder_write_object(ctx, child, write_buffer, cookie))
return (false);
}
return (true);
}
static bool
memory_write(void *cookie, const uint8_t *data, size_t datasz)
{
struct memory_write_data *mwrite = cookie;
uint8_t *dst = &mwrite->buf[mwrite->offset];
size_t left;
/* Small buffers should have been rejected long before now. */
left = mwrite->bufsz - mwrite->offset;
assert(datasz <= left);
memcpy(dst, data, datasz);
mwrite->offset += datasz;
return (true);
}
/*
* Writes the object rooted at `root` to the buffer. If `buf` == NULL and
* `*bufsz` == 0, we'll allocate a buffer just large enough to hold the result
* and pass the size back via `*bufsz`. If a pre-allocated buffer is passed,
* we may still update `*bufsz` if normalization made the buffer smaller.
*
* If the buffer is too small, *bufsz will be set to the size of buffer needed.
*/
uint8_t *
libder_write(struct libder_ctx *ctx, struct libder_object *root, uint8_t *buf,
size_t *bufsz)
{
struct memory_write_data mwrite = { 0 };
size_t needed;
/*
* We shouldn't really see buf == NULL with *bufsz != 0 or vice-versa.
* Combined, they mean that we should allocate whatever buffer size we
* need.
*/
if ((buf == NULL && *bufsz != 0) || (buf != NULL && *bufsz == 0))
return (NULL); /* XXX Surface error? */
/*
* If we're doing any normalization beyond our standard size
* normalization, we apply those rules up front since they may alter our
* disk size every so slightly.
*/
if (ctx->normalize != 0 && !libder_obj_normalize(root, ctx))
return (NULL);
needed = libder_obj_disk_size(root, true);
if (needed == 0)
return (NULL); /* Overflow */
/* Allocate if we weren't passed a buffer. */
if (*bufsz == 0) {
*bufsz = needed;
buf = malloc(needed);
if (buf == NULL)
return (NULL);
} else if (needed > *bufsz) {
*bufsz = needed;
return (NULL); /* Insufficient space */
}
/* Buffer large enough, write into it. */
mwrite.buf = buf;
mwrite.bufsz = *bufsz;
if (!libder_write_object(ctx, root, &memory_write, &mwrite)) {
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);
}

12
contrib/libder/tests/.gitignore vendored Normal file
View file

@ -0,0 +1,12 @@
CORPUS*
crash-*
leak-*
oom-*
*.log
fuzz_*
test_*
!*.c
!*.h
make_corpus

View file

@ -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"
)

View file

@ -0,0 +1,111 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <sys/param.h>
#include <sys/socket.h>
#include <assert.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libder.h>
#include "fuzzers.h"
struct fuzz_frame {
uint8_t frame_threads;
};
struct thread_input {
const uint8_t *data;
size_t datasz;
};
static void *
thread_main(void *cookie)
{
const struct thread_input *input = cookie;
struct libder_ctx *ctx;
struct libder_object *obj;
const uint8_t *data = input->data;
size_t readsz, sz = input->datasz;
ctx = libder_open();
readsz = sz;
obj = libder_read(ctx, data, &readsz);
if (obj == NULL || readsz != sz)
goto out;
if (obj != NULL) {
uint8_t *buf = NULL;
size_t bufsz = 0;
/*
* If we successfully read it, then it shouldn't
* overflow. We're letting libder allocate the buffer,
* so we shouldn't be able to hit the 'too small' bit.
*
* I can't imagine what other errors might happen, so
* we'll just assert on it.
*/
buf = libder_write(ctx, obj, buf, &bufsz);
if (buf == NULL)
goto out;
assert(bufsz != 0);
free(buf);
}
out:
libder_obj_free(obj);
libder_close(ctx);
return (NULL);
}
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz)
{
const struct fuzz_frame *frame;
pthread_t *threads;
struct thread_input inp;
size_t nthreads;
if (sz <= sizeof(*frame))
return (-1);
frame = (const void *)data;
data += sizeof(*frame);
sz -= sizeof(*frame);
if (frame->frame_threads < 2)
return (-1);
threads = malloc(sizeof(*threads) * frame->frame_threads);
if (threads == NULL)
return (-1);
inp.data = data;
inp.datasz = sz;
for (nthreads = 0; nthreads < frame->frame_threads; nthreads++) {
if (pthread_create(&threads[nthreads], NULL, thread_main,
&inp) != 0)
break;
}
for (uint8_t i = 0; i < nthreads; i++)
pthread_join(threads[i], NULL);
free(threads);
return (0);
}

View file

@ -0,0 +1,246 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <sys/param.h>
#include <sys/socket.h>
#include <assert.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <libder.h>
#include "fuzzers.h"
struct supply_data {
const uint8_t *data;
volatile size_t datasz;
int socket;
};
static void *
supply_thread(void *data)
{
struct supply_data *sdata = data;
size_t sz = sdata->datasz;
ssize_t writesz;
do {
writesz = write(sdata->socket, sdata->data, sz);
data += writesz;
sz -= writesz;
} while (sz != 0 && writesz > 0);
sdata->datasz = sz;
shutdown(sdata->socket, SHUT_RDWR);
close(sdata->socket);
return (NULL);
}
static int
fuzz_fd(const struct fuzz_params *fparams, const uint8_t *data, size_t sz)
{
struct supply_data sdata;
struct libder_ctx *ctx;
struct libder_object *obj;
size_t totalsz;
int sockets[2];
pid_t pid;
int ret;
ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0,
&sockets[0]);
if (ret == -1)
return (-1);
sdata.data = data;
sdata.datasz = sz;
sdata.socket = sockets[1];
signal(SIGCHLD, SIG_IGN);
pid = fork();
if (pid == -1) {
close(sockets[0]);
close(sockets[1]);
return (-1);
}
if (pid == 0) {
close(sockets[0]);
supply_thread(&sdata);
_exit(0);
} else {
close(sockets[1]);
}
totalsz = 0;
ret = 0;
ctx = libder_open();
libder_set_strict(ctx, !!fparams->strict);
while (totalsz < sz) {
size_t readsz = 0;
obj = libder_read_fd(ctx, sockets[0], &readsz);
libder_obj_free(obj);
/*
* Even invalid reads should consume at least one byte.
*/
assert(readsz != 0);
totalsz += readsz;
if (readsz == 0)
break;
}
assert(totalsz == sz);
libder_close(ctx);
close(sockets[0]);
return (ret);
}
static int
fuzz_file(const struct fuzz_params *fparams, const uint8_t *data, size_t sz)
{
FILE *fp;
struct libder_ctx *ctx;
struct libder_object *obj;
size_t totalsz;
int ret;
if (fparams->buftype >= BUFFER_END)
return (-1);
fp = fmemopen(__DECONST(void *, data), sz, "rb");
assert(fp != NULL);
switch (fparams->buftype) {
case BUFFER_NONE:
setvbuf(fp, NULL, 0, _IONBF);
break;
case BUFFER_FULL:
setvbuf(fp, NULL, 0, _IOFBF);
break;
case BUFFER_END:
assert(0);
}
totalsz = 0;
ret = 0;
ctx = libder_open();
libder_set_strict(ctx, !!fparams->strict);
while (!feof(fp)) {
size_t readsz = 0;
obj = libder_read_file(ctx, fp, &readsz);
libder_obj_free(obj);
if (obj == NULL)
assert(readsz != 0 || feof(fp));
else
assert(readsz != 0);
totalsz += readsz;
}
assert(totalsz == sz);
libder_close(ctx);
fclose(fp);
return (ret);
}
static int
fuzz_plain(const struct fuzz_params *fparams, const uint8_t *data, size_t sz)
{
struct libder_ctx *ctx;
struct libder_object *obj;
int ret;
if (sz == 0)
return (-1);
ret = 0;
ctx = libder_open();
libder_set_strict(ctx, !!fparams->strict);
do {
size_t readsz;
readsz = sz;
obj = libder_read(ctx, data, &readsz);
libder_obj_free(obj);
if (obj == NULL)
assert(readsz != 0 || readsz == sz);
else
assert(readsz != 0);
/*
* If we hit an entirely invalid segment of the buffer, we'll
* just skip a byte and try again.
*/
data += MAX(1, readsz);
sz -= MAX(1, readsz);
} while (sz != 0);
libder_close(ctx);
return (ret);
};
static bool
validate_padding(const struct fuzz_params *fparams)
{
const uint8_t *end = (const void *)(fparams + 1);
const uint8_t *pad = (const uint8_t *)&fparams->PARAM_PAD_START;
while (pad < end) {
if (*pad++ != 0)
return (false);
}
return (true);
}
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz)
{
const struct fuzz_params *fparams;
if (sz <= sizeof(*fparams))
return (-1);
fparams = (const void *)data;
if (fparams->type >= STREAM_END)
return (-1);
if (!validate_padding(fparams))
return (-1);
data += sizeof(*fparams);
sz -= sizeof(*fparams);
if (fparams->type != STREAM_FILE && fparams->buftype != BUFFER_NONE)
return (-1);
switch (fparams->type) {
case STREAM_FD:
return (fuzz_fd(fparams, data, sz));
case STREAM_FILE:
return (fuzz_file(fparams, data, sz));
case STREAM_PLAIN:
return (fuzz_plain(fparams, data, sz));
case STREAM_END:
assert(0);
}
__builtin_trap();
}

View file

@ -0,0 +1,79 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <sys/param.h>
#include <sys/socket.h>
#include <assert.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libder.h>
#include "fuzzers.h"
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz)
{
struct libder_ctx *ctx;
struct libder_object *obj;
size_t readsz;
int ret;
bool strict;
if (sz < 2)
return (-1);
/*
* I worked this in originally by just using the high bit of the first
* byte, but then I realized that encoding it that way would make it
* impossible to get strict validation of universal and application
* tags. The former is a bit more important than the latter.
*/
strict = !!data[0];
data++;
sz--;
ctx = libder_open();
libder_set_strict(ctx, strict);
ret = -1;
readsz = sz;
obj = libder_read(ctx, data, &readsz);
if (obj == NULL || readsz != sz)
goto out;
if (obj != NULL) {
uint8_t *buf = NULL;
size_t bufsz = 0;
/*
* If we successfully read it, then it shouldn't
* overflow. We're letting libder allocate the buffer,
* so we shouldn't be able to hit the 'too small' bit.
*
* I can't imagine what other errors might happen, so
* we'll just assert on it.
*/
buf = libder_write(ctx, obj, buf, &bufsz);
if (buf == NULL)
goto out;
assert(bufsz != 0);
free(buf);
}
ret = 0;
out:
libder_obj_free(obj);
libder_close(ctx);
return (ret);
}

View file

@ -0,0 +1,40 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <stdint.h>
enum stream_type {
STREAM_FD = 0, /* read_fd() type */
STREAM_FILE = 1, /* read_file() type */
STREAM_PLAIN = 2,
STREAM_END
} __attribute__((packed));
enum stream_buffer {
BUFFER_NONE = 0,
BUFFER_FULL = 1,
BUFFER_END,
} __attribute__((packed));
struct fuzz_params {
enum stream_type type;
enum stream_buffer buftype;
#define PARAM_PAD_START _pad0
uint8_t strict;
uint8_t _pad0[5];
/* Give me plenty of padding. */
uint64_t padding[3];
};
_Static_assert(sizeof(struct fuzz_params) == 32,
"fuzz_params ABI broken, will invalidate CORPUS");

View file

@ -0,0 +1,137 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <sys/param.h>
#undef NDEBUG
#include <assert.h>
#include <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libder.h>
#include "fuzzers.h"
#define LARGE_SIZE (1024 * 64)
static const uint8_t empty_seq[] = { BT_SEQUENCE, 0x00 };
static const uint8_t long_size[21] = { BT_OCTETSTRING, 0x83, 0x00, 0x00, 0x10 };
/* 64k */
#define LARGE_SIZE_ENCODING 0x83, 0x01, 0x00, 0x00
static const uint8_t large_octet[LARGE_SIZE + 5] = { BT_OCTETSTRING, LARGE_SIZE_ENCODING };
#define VARLEN_SEQ BT_OCTETSTRING, 0x04, 0x01, 0x02, 0x03, 0x04
#define VARLEN_CHILDREN VARLEN_SEQ, VARLEN_SEQ, VARLEN_SEQ
static const uint8_t varlen[] = { BT_SEQUENCE, 0x80,
VARLEN_CHILDREN, 0x00, 0x00 };
#define BITSTRING1 BT_BITSTRING, 0x04, 0x03, 0xc0, 0xc0, 0xcc
#define BITSTRING2 BT_BITSTRING, 0x04, 0x05, 0xdd, 0xdd, 0xff
static const uint8_t constructed_bitstring[] = { 0x20 | BT_BITSTRING,
2 * 6, BITSTRING1, BITSTRING2 };
#define FUZZER_SEED(seq) { #seq, sizeof(seq), seq }
static const struct seed {
const char *seed_name;
size_t seed_seqsz;
const uint8_t *seed_seq;
} seeds[] = {
FUZZER_SEED(empty_seq),
FUZZER_SEED(long_size),
FUZZER_SEED(large_octet),
FUZZER_SEED(varlen),
FUZZER_SEED(constructed_bitstring),
};
static void
usage(void)
{
fprintf(stderr, "usage: %s [-H] <corpus-dir>\n", getprogname());
exit(1);
}
static void
write_one(const struct fuzz_params *params, const struct seed *seed, int dirfd,
bool striphdr)
{
char *name;
int fd = -1;
assert(asprintf(&name, "base_%d_%d_%d_%s", params->type,
params->buftype, params->strict, seed->seed_name) != -1);
fd = openat(dirfd, name, O_RDWR | O_TRUNC | O_CREAT, 0644);
assert(fd != -1);
/*
* Write our params + seed; if we're stripping the header we won't have
* the full params, but we'll still have our signal byte for strict
* mode.
*/
if (!striphdr)
assert(write(fd, &params, sizeof(params)) == sizeof(params));
else
assert(write(fd, &params->strict, sizeof(params->strict)) == sizeof(params->strict));
assert(write(fd, seed->seed_seq, seed->seed_seqsz) == seed->seed_seqsz);
free(name);
close(fd);
}
int
main(int argc, char *argv[])
{
struct fuzz_params params;
const struct seed *seed;
const char *seed_dir;
int dirfd = -1;
bool striphdr = false;
if (argc < 2 || argc > 3)
usage();
if (argc == 3 && strcmp(argv[1], "-H") != 0)
usage();
striphdr = argc == 3;
seed_dir = argv[argc - 1];
dirfd = open(seed_dir, O_SEARCH);
if (dirfd == -1)
err(1, "%s: open", seed_dir);
memset(&params, 0, sizeof(params));
for (int type = 0; type < STREAM_END; type++) {
params.type = type;
for (int buffered = 0; buffered < BUFFER_END; buffered++) {
params.buftype = buffered;
for (uint8_t strict = 0; strict < 2; strict++) {
params.strict = strict;
for (size_t i = 0; i < nitems(seeds); i++) {
seed = &seeds[i];
write_one(&params, seed, dirfd, striphdr);
}
}
if (type != STREAM_FILE)
break;
}
}
close(dirfd);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,29 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <assert.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <stdlib.h>
static inline int
open_progdir(const char *prog)
{
char pdir[PATH_MAX], *resolved;
int dfd;
resolved = realpath(prog, &pdir[0]);
assert(resolved != NULL);
resolved = dirname(&pdir[0]);
assert(resolved != NULL);
dfd = open(resolved, O_DIRECTORY);
assert(dfd != -1);
return (dfd);
}

View file

@ -0,0 +1,175 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <sys/stat.h>
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libder.h>
#include "test_common.h"
/*
* Note that the choice of secp112r1 is completely arbitrary. I was mainly
* shooting for something pretty weak to avoid people trying to "catch me"
* checking in private key material, even though it's very incredibly clearly
* just for a test case.
*/
static const uint8_t oid_secp112r1[] =
{ 0x2b, 0x81, 0x04, 0x00, 0x06 };
static const uint8_t privdata[] = { 0xa5, 0xf5, 0x2a, 0x56, 0x61, 0xe3, 0x58,
0x76, 0x5c, 0x4f, 0xd6, 0x8d, 0x60, 0x54 };
static const uint8_t pubdata[] = { 0x00, 0x04, 0xae, 0x69, 0x41, 0x0d, 0x9c,
0x9b, 0xf2, 0x34, 0xf6, 0x2d, 0x7c, 0x91, 0xe1, 0xc7, 0x7f, 0x23, 0xa0,
0x84, 0x34, 0x5c, 0x38, 0x26, 0xd8, 0xcf, 0xbe, 0xf7, 0xdc, 0x8a };
static void
test_interface(struct libder_object *root)
{
const uint8_t *data;
size_t datasz;
struct libder_object *keystring, *oid;
/* Grab the oid first. */
oid = libder_obj_child(root, 2);
assert(oid != NULL); /* Actually just the container... */
assert(libder_obj_type_simple(oid) == 0xa0);
oid = libder_obj_child(oid, 0);
assert(oid != NULL); /* Now *that*'s an OID. */
assert(libder_obj_type_simple(oid) == BT_OID);
data = libder_obj_data(oid, &datasz);
assert(datasz == sizeof(oid_secp112r1));
assert(memcmp(oid_secp112r1, data, datasz) == 0);
keystring = libder_obj_child(root, 1);
assert(keystring != NULL);
assert(libder_obj_type_simple(keystring) == BT_OCTETSTRING);
data = libder_obj_data(keystring, &datasz);
assert(datasz == sizeof(privdata));
assert(memcmp(privdata, data, datasz) == 0);
}
/* buf and bufszs are just our reference */
static void
test_construction(struct libder_ctx *ctx, const uint8_t *buf, size_t bufsz)
{
uint8_t *out;
struct libder_object *obj, *oidp, *pubp, *root;
struct libder_object *keystring;
size_t outsz;
uint8_t data;
root = libder_obj_alloc_simple(ctx, BT_SEQUENCE, NULL, 0);
assert(root != NULL);
data = 1;
obj = libder_obj_alloc_simple(ctx, BT_INTEGER, &data, 1);
assert(obj != NULL);
assert(libder_obj_append(root, obj));
/* Private key material */
obj = libder_obj_alloc_simple(ctx, BT_OCTETSTRING, privdata, sizeof(privdata));
assert(obj != NULL);
assert(libder_obj_append(root, obj));
/* Now throw in the OID and pubkey containers */
oidp = libder_obj_alloc_simple(ctx,
(BC_CONTEXT << 6) | BER_TYPE_CONSTRUCTED_MASK | 0, NULL, 0);
assert(oidp != NULL);
assert(libder_obj_append(root, oidp));
pubp = libder_obj_alloc_simple(ctx,
(BC_CONTEXT << 6) | BER_TYPE_CONSTRUCTED_MASK | 1, NULL, 0);
assert(pubp != NULL);
assert(libder_obj_append(root, pubp));
/* Actually add the OID */
obj = libder_obj_alloc_simple(ctx, BT_OID, oid_secp112r1, sizeof(oid_secp112r1));
assert(obj != NULL);
assert(libder_obj_append(oidp, obj));
/* Finally, add the pubkey */
obj = libder_obj_alloc_simple(ctx, BT_BITSTRING, pubdata, sizeof(pubdata));
assert(obj != NULL);
assert(libder_obj_append(pubp, obj));
out = NULL;
outsz = 0;
out = libder_write(ctx, root, out, &outsz);
assert(out != NULL);
assert(outsz == bufsz);
assert(memcmp(out, buf, bufsz) == 0);
libder_obj_free(root);
free(out);
}
int
main(int argc, char *argv[])
{
struct stat sb;
struct libder_ctx *ctx;
struct libder_object *root;
uint8_t *buf, *out;
size_t bufsz, outsz, rootsz;
ssize_t readsz;
int dfd, error, fd;
dfd = open_progdir(argv[0]);
fd = openat(dfd, "repo.priv", O_RDONLY);
assert(fd >= 0);
close(dfd);
dfd = -1;
error = fstat(fd, &sb);
assert(error == 0);
bufsz = sb.st_size;
buf = malloc(bufsz);
assert(buf != NULL);
readsz = read(fd, buf, bufsz);
close(fd);
assert(readsz == bufsz);
ctx = libder_open();
rootsz = bufsz;
libder_set_verbose(ctx, 2);
root = libder_read(ctx, buf, &rootsz);
assert(root != NULL);
assert(rootsz == bufsz);
test_interface(root);
test_construction(ctx, buf, bufsz);
outsz = 0;
out = NULL;
out = libder_write(ctx, root, out, &outsz);
assert(out != NULL);
assert(outsz == bufsz);
assert(memcmp(buf, out, outsz) == 0);
free(out);
free(buf);
libder_obj_free(root);
libder_close(ctx);
}

View file

@ -0,0 +1,143 @@
/*-
* Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <sys/stat.h>
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libder.h>
#include "test_common.h"
static const uint8_t oid_ecpubkey[] =
{ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01 };
static const uint8_t oid_secp256k1[] =
{ 0x2b, 0x81, 0x04, 0x00, 0x0a };
static const uint8_t pubdata[] = { 0x00, 0x04, 0xd1, 0x76, 0x20, 0x39, 0xe5, 0x3e,
0x67, 0x7d, 0x8d, 0xfd, 0xc4, 0x21, 0x20, 0xcd, 0xb0, 0xbf, 0x47, 0x87, 0x6a,
0xf8, 0x07, 0x73, 0xbe, 0xbe, 0xd5, 0xbb, 0x3c, 0xbc, 0x32, 0x93, 0xd9, 0xdf,
0x96, 0x25, 0xb7, 0x0e, 0x3c, 0x55, 0x12, 0xee, 0x7a, 0x02, 0x39, 0x0f, 0xee,
0x7b, 0xfe, 0x1a, 0x93, 0x76, 0xf7, 0xc2, 0xac, 0x05, 0xba, 0x9a, 0x83, 0x37,
0xf5, 0xcd, 0x55, 0x57, 0x39, 0x6f };
static void
test_interface(struct libder_object *root)
{
const uint8_t *data;
size_t datasz;
struct libder_object *keystring;
keystring = libder_obj_child(root, 1);
assert(keystring != NULL);
assert(libder_obj_type_simple(keystring) == BT_BITSTRING);
data = libder_obj_data(keystring, &datasz);
assert(datasz == sizeof(pubdata));
assert(memcmp(pubdata, data, datasz) == 0);
}
/* buf and bufszs are just our reference */
static void
test_construction(struct libder_ctx*ctx, const uint8_t *buf, size_t bufsz)
{
uint8_t *out;
struct libder_object *obj, *params, *root;
struct libder_object *keystring;
size_t outsz;
root = libder_obj_alloc_simple(ctx, BT_SEQUENCE, NULL, 0);
assert(root != NULL);
params = libder_obj_alloc_simple(ctx, BT_SEQUENCE, NULL, 0);
assert(params != NULL);
assert(libder_obj_append(root, params));
keystring = libder_obj_alloc_simple(ctx, BT_BITSTRING, pubdata, sizeof(pubdata));
assert(keystring != NULL);
assert(libder_obj_append(root, keystring));
/* Now go back and build the two params, id and curve */
obj = libder_obj_alloc_simple(ctx, BT_OID, oid_ecpubkey, sizeof(oid_ecpubkey));
assert(obj != NULL);
assert(libder_obj_append(params, obj));
obj = libder_obj_alloc_simple(ctx, BT_OID, oid_secp256k1, sizeof(oid_secp256k1));
assert(obj != NULL);
assert(libder_obj_append(params, obj));
out = NULL;
outsz = 0;
out = libder_write(ctx, root, out, &outsz);
assert(out != NULL);
assert(outsz == bufsz);
assert(memcmp(out, buf, bufsz) == 0);
libder_obj_free(root);
free(out);
}
int
main(int argc, char *argv[])
{
struct stat sb;
struct libder_ctx *ctx;
struct libder_object *root;
uint8_t *buf, *out;
size_t bufsz, outsz, rootsz;
ssize_t readsz;
int dfd, error, fd;
dfd = open_progdir(argv[0]);
fd = openat(dfd, "repo.pub", O_RDONLY);
assert(fd >= 0);
close(dfd);
dfd = -1;
error = fstat(fd, &sb);
assert(error == 0);
bufsz = sb.st_size;
buf = malloc(bufsz);
assert(buf != NULL);
readsz = read(fd, buf, bufsz);
close(fd);
assert(readsz == bufsz);
ctx = libder_open();
rootsz = bufsz;
libder_set_verbose(ctx, 2);
root = libder_read(ctx, buf, &rootsz);
assert(root != NULL);
assert(rootsz == bufsz);
test_interface(root);
test_construction(ctx, buf, bufsz);
outsz = 0;
out = NULL;
out = libder_write(ctx, root, out, &outsz);
assert(out != NULL);
assert(outsz == bufsz);
assert(memcmp(buf, out, outsz) == 0);
free(out);
free(buf);
libder_obj_free(root);
libder_close(ctx);
}