mirror of
https://github.com/opnsense/src.git
synced 2026-02-18 18:20:26 -05:00
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:
parent
6584e5a1c5
commit
ba667efb53
36 changed files with 4813 additions and 0 deletions
16
contrib/libder/.cirrus.yml
Normal file
16
contrib/libder/.cirrus.yml
Normal 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
|
||||
41
contrib/libder/.github/workflows/build.yml
vendored
Normal file
41
contrib/libder/.github/workflows/build.yml
vendored
Normal 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
11
contrib/libder/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
.*.swp
|
||||
.depend*
|
||||
*.a
|
||||
*.so
|
||||
*.so.*
|
||||
*.o
|
||||
*.pico
|
||||
*.debug
|
||||
*.full
|
||||
|
||||
build/
|
||||
28
contrib/libder/CMakeLists.txt
Normal file
28
contrib/libder/CMakeLists.txt
Normal 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
22
contrib/libder/LICENSE
Normal 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
28
contrib/libder/README.md
Normal 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
1
contrib/libder/derdump/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
derdump
|
||||
6
contrib/libder/derdump/CMakeLists.txt
Normal file
6
contrib/libder/derdump/CMakeLists.txt
Normal 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)
|
||||
51
contrib/libder/derdump/derdump.1
Normal file
51
contrib/libder/derdump/derdump.1
Normal 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 { ... } .
|
||||
52
contrib/libder/derdump/derdump.c
Normal file
52
contrib/libder/derdump/derdump.c
Normal 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);
|
||||
}
|
||||
12
contrib/libder/libder/CMakeLists.txt
Normal file
12
contrib/libder/libder/CMakeLists.txt
Normal 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()
|
||||
179
contrib/libder/libder/libder.3
Normal file
179
contrib/libder/libder/libder.3
Normal 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
|
||||
119
contrib/libder/libder/libder.c
Normal file
119
contrib/libder/libder/libder.c
Normal 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);
|
||||
}
|
||||
|
||||
181
contrib/libder/libder/libder.h
Normal file
181
contrib/libder/libder/libder.h
Normal 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 *);
|
||||
76
contrib/libder/libder/libder_error.c
Normal file
76
contrib/libder/libder/libder_error.c
Normal 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);
|
||||
}
|
||||
}
|
||||
138
contrib/libder/libder/libder_obj.3
Normal file
138
contrib/libder/libder/libder_obj.3
Normal 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
|
||||
1192
contrib/libder/libder/libder_obj.c
Normal file
1192
contrib/libder/libder/libder_obj.c
Normal file
File diff suppressed because it is too large
Load diff
178
contrib/libder/libder/libder_private.h
Normal file
178
contrib/libder/libder/libder_private.h
Normal 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 *);
|
||||
101
contrib/libder/libder/libder_read.3
Normal file
101
contrib/libder/libder/libder_read.3
Normal 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
|
||||
864
contrib/libder/libder/libder_read.c
Normal file
864
contrib/libder/libder/libder_read.c
Normal 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);
|
||||
}
|
||||
71
contrib/libder/libder/libder_type.3
Normal file
71
contrib/libder/libder/libder_type.3
Normal 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
|
||||
150
contrib/libder/libder/libder_type.c
Normal file
150
contrib/libder/libder/libder_type.c
Normal 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;
|
||||
}
|
||||
54
contrib/libder/libder/libder_write.3
Normal file
54
contrib/libder/libder/libder_write.3
Normal 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
|
||||
229
contrib/libder/libder/libder_write.c
Normal file
229
contrib/libder/libder/libder_write.c
Normal 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
12
contrib/libder/tests/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
CORPUS*
|
||||
crash-*
|
||||
leak-*
|
||||
oom-*
|
||||
*.log
|
||||
|
||||
fuzz_*
|
||||
test_*
|
||||
!*.c
|
||||
!*.h
|
||||
|
||||
make_corpus
|
||||
41
contrib/libder/tests/CMakeLists.txt
Normal file
41
contrib/libder/tests/CMakeLists.txt
Normal 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"
|
||||
)
|
||||
111
contrib/libder/tests/fuzz_parallel.c
Normal file
111
contrib/libder/tests/fuzz_parallel.c
Normal 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);
|
||||
}
|
||||
246
contrib/libder/tests/fuzz_stream.c
Normal file
246
contrib/libder/tests/fuzz_stream.c
Normal 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();
|
||||
}
|
||||
79
contrib/libder/tests/fuzz_write.c
Normal file
79
contrib/libder/tests/fuzz_write.c
Normal 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);
|
||||
}
|
||||
40
contrib/libder/tests/fuzzers.h
Normal file
40
contrib/libder/tests/fuzzers.h
Normal 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");
|
||||
|
||||
137
contrib/libder/tests/make_corpus.c
Normal file
137
contrib/libder/tests/make_corpus.c
Normal 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, ¶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);
|
||||
}
|
||||
BIN
contrib/libder/tests/repo.priv
Normal file
BIN
contrib/libder/tests/repo.priv
Normal file
Binary file not shown.
BIN
contrib/libder/tests/repo.pub
Normal file
BIN
contrib/libder/tests/repo.pub
Normal file
Binary file not shown.
29
contrib/libder/tests/test_common.h
Normal file
29
contrib/libder/tests/test_common.h
Normal 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);
|
||||
}
|
||||
175
contrib/libder/tests/test_privkey.c
Normal file
175
contrib/libder/tests/test_privkey.c
Normal 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);
|
||||
}
|
||||
143
contrib/libder/tests/test_pubkey.c
Normal file
143
contrib/libder/tests/test_pubkey.c
Normal 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);
|
||||
}
|
||||
Loading…
Reference in a new issue