diff --git a/.github/workflows/build-platform.yml b/.github/workflows/build-platform.yml
index c38a2ae..ed57bbd 100644
--- a/.github/workflows/build-platform.yml
+++ b/.github/workflows/build-platform.yml
@@ -70,7 +70,7 @@ jobs:
fi
apt-get update
apt-get install -y git ca-certificates gnupg wget software-properties-common curl \
- python3-pip ninja-build cmake pkg-config uuid-dev libssl-dev libsodium-dev jq
+ python3-pip ninja-build cmake pkg-config uuid-dev libsodium-dev jq
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt-get install -y nodejs
pip3 install meson tomli
@@ -81,7 +81,7 @@ jobs:
if: startsWith(inputs.platform, 'linux-musl')
run: |
apk add --no-cache git clang lld llvm meson ninja cmake pkgconf curl npm nodejs \
- musl-dev openssl-dev openssl-libs-static libsodium-dev libsodium-static \
+ musl-dev libsodium-dev libsodium-static \
util-linux-dev util-linux-static linux-headers libunwind-dev libunwind-static \
py3-tomli tar xz zstd jq bash
git config --global --add safe.directory "$GITHUB_WORKSPACE"
@@ -93,7 +93,6 @@ jobs:
for pkg in meson ninja llvm jq; do
brew list "$pkg" &>/dev/null || brew install "$pkg"
done
- brew list openssl@3 &>/dev/null || brew install openssl@3
# === WINDOWS SETUP ===
- name: Disable Windows Defender real-time monitoring
@@ -109,7 +108,7 @@ jobs:
cache: true
install: >-
mingw-w64-clang-x86_64-toolchain mingw-w64-clang-x86_64-meson mingw-w64-clang-x86_64-ninja
- mingw-w64-clang-x86_64-cmake mingw-w64-clang-x86_64-openssl mingw-w64-clang-x86_64-libsodium
+ mingw-w64-clang-x86_64-cmake mingw-w64-clang-x86_64-libsodium
mingw-w64-clang-x86_64-lld mingw-w64-clang-x86_64-nodejs git jq
- name: Configure git line endings
diff --git a/BUILDING.md b/BUILDING.md
index 6440487..5712d82 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -29,7 +29,6 @@ If you can reproduce a test failure, search for it in the
- [Windows prerequisites](#windows-prerequisites)
- [Building Ant](#building-ant-2)
- [Meson build options](#meson-build-options)
-- [TLS library selection](#tls-library-selection)
## Supported platforms
@@ -90,26 +89,33 @@ The following tools are required to build Ant regardless of platform:
System libraries required:
-- **OpenSSL**
- **libsodium**
- **libuuid** (Linux/macOS)
-- **llhttp** (if not building from source via cmake)
The remaining dependencies are vendored as Meson subprojects under `vendor/`
and are fetched automatically:
+- aklomp-base64 0.5.2
+- argtable3 3.3.1
+- BoringSSL `297b11798a0ed6bc7736aa57328909a4afbbf67a`
+- crprintf `HEAD`
+- google-brotli 1.1.0
+- libffi 3.5.2
- libuv 1.52.0
+- llhttp 9.3.1
+- LMDB (OpenLDAP LMDB 0.9.33)
+- minicoro `HEAD`
+- MIR `HEAD`
+- nghttp2 1.68.0
+- PCRE2 10.47
+- tlsuv 0.40.13
+- uriparser 1.0.0
+- utf8proc 2.10.0
+- uthash 2.3.0
+- uuidv7-h `HEAD`
+- wasm-micro-runtime `92f40918bbfad35546a1512b10bd25eaa31add4d`
- yyjson 0.12.0
- zlib-ng 2.3.3
-- nghttp2
-- pcre2
-- libffi
-- lmdb (OpenLDAP LMDB 0.9.33)
-- minicoro
-- argtable3
-- uthash
-- uuidv7
-- tlsuv (cmake subproject)
### Unix and macOS
@@ -121,7 +127,7 @@ Installation via package manager:
```bash
sudo apt-get install python3 python3-pip gcc-14 g++-14 ninja-build cmake \
- pkg-config uuid-dev libssl-dev libsodium-dev nodejs npm
+ pkg-config uuid-dev libsodium-dev nodejs npm
pip3 install meson
```
@@ -129,14 +135,14 @@ Installation via package manager:
```bash
sudo dnf install python3 gcc gcc-c++ ninja-build cmake pkgconf \
- libuuid-devel openssl-devel libsodium-devel nodejs npm
+ libuuid-devel libsodium-devel nodejs npm
pip3 install meson
```
- Alpine (musl):
```sh
apk add clang lld llvm meson ninja cmake pkgconf nodejs npm \
- musl-dev openssl-dev openssl-libs-static libsodium-dev libsodium-static \
+ musl-dev libsodium-dev libsodium-static \
util-linux-dev util-linux-static linux-headers libunwind-dev libunwind-static
```
@@ -161,7 +167,7 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- Install remaining tools via [Homebrew](https://brew.sh):
```bash
- brew install meson ninja llvm openssl@3 libsodium node
+ brew install meson ninja llvm libsodium node
```
- Rust and Zig:
@@ -372,7 +378,7 @@ Ant on Windows is built using the MSYS2 MINGW64 toolchain.
```bash
pacman -S mingw-w64-x86_64-toolchain mingw-w64-x86_64-meson \
mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake \
- mingw-w64-x86_64-openssl mingw-w64-x86_64-libsodium \
+ mingw-w64-x86_64-libsodium \
mingw-w64-x86_64-lld mingw-w64-x86_64-nodejs git
```
3. Install Rust via [rustup](https://rustup.rs/) (select the
diff --git a/flake.nix b/flake.nix
index a91a1ab..5a6fcbb 100644
--- a/flake.nix
+++ b/flake.nix
@@ -8,7 +8,7 @@ inputs = {
outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system:
let pkgs = nixpkgs.legacyPackages.${system}; in {
devShells.default = pkgs.mkShell {
- packages = with pkgs; [ pkg-config libossp_uuid libsodium openssl ];
+ packages = with pkgs; [ pkg-config libossp_uuid libsodium ];
shellHook = ''
export CC="ccache clang"
diff --git a/meson.build b/meson.build
index 2a08830..80cf24f 100644
--- a/meson.build
+++ b/meson.build
@@ -95,3 +95,10 @@ executable(
link_args: link_args,
export_dynamic: true
)
+
+executable(
+ 'tlsuv-boringssl-smoke',
+ files('tests/tlsuv_boringssl_smoke.c'),
+ dependencies: [tlsuv_dep, libuv_dep, llhttp, zlib_dep] + (host_machine.system() == 'darwin' ? [security_dep] : []),
+ build_by_default: false
+)
diff --git a/meson/deps/meson.build b/meson/deps/meson.build
index 257ab7c..e39982d 100644
--- a/meson/deps/meson.build
+++ b/meson/deps/meson.build
@@ -1,6 +1,25 @@
cc = meson.get_compiler('c')
+cmake = import('cmake')
+
+boringssl_opts = cmake.subproject_options()
+boringssl_opts.add_cmake_defines({
+ 'BUILD_SHARED_LIBS': 'OFF',
+ 'BUILD_TESTING': 'OFF',
+ 'INSTALL_ENABLED': '0',
+})
+
+boringssl_warning_flag = cc.get_argument_syntax() == 'msvc' ? '/W0' : '-w'
+boringssl_opts.append_compile_args('c', boringssl_warning_flag)
+boringssl_opts.append_compile_args('cpp', boringssl_warning_flag)
+boringssl_opts.set_override_option('warning_level', '0')
+
+boringssl_sub = cmake.subproject('boringssl', options: boringssl_opts)
+ssl_dep = boringssl_sub.dependency('ssl')
+crypto_dep = boringssl_sub.dependency('crypto')
+
+meson.override_dependency('ssl', ssl_dep)
+meson.override_dependency('crypto', crypto_dep)
-openssl_dep = dependency('openssl', required: true, static: is_static)
libsodium_dep = dependency('libsodium', required: true, static: is_static)
if host_machine.system() == 'windows'
@@ -77,7 +96,7 @@ brotli_enc_dep = brotli_sub.get_variable('brotli_encoder_dep')
cmake_prefix = get_option('deps_prefix_cmake')
tlsuv_sub = subproject('tlsuv', default_options: [
- 'tls_backend=openssl',
+ 'tls_backend=boringssl',
'deps_prefix_cmake=' + cmake_prefix,
'warning_level=0',
])
@@ -201,7 +220,7 @@ ant_deps = [
llhttp, pcre2_dep, libuv_dep, base64_dep,
argtable3_dep, tlsuv_dep, libsodium_dep,
yyjson_dep, minicoro_dep, uuidv7_dep,
- uriparser_dep, utf8proc_dep, openssl_dep,
+ uriparser_dep, utf8proc_dep, ssl_dep, crypto_dep,
zlib_dep, brotli_common_dep, brotli_dec_dep,
brotli_enc_dep, uthash_dep, lmdb_dep, wamr_dep,
] + win_deps
diff --git a/src/core/features.ts b/src/core/features.ts
index 7a6c529..b7319bb 100644
--- a/src/core/features.ts
+++ b/src/core/features.ts
@@ -1,6 +1,6 @@
process.features = {
uv: true,
- tls: 'openssl',
+ tls: 'BoringSSL',
typescript: 'transform'
};
diff --git a/tests/tlsuv_boringssl_smoke.c b/tests/tlsuv_boringssl_smoke.c
new file mode 100644
index 0000000..6f22e3f
--- /dev/null
+++ b/tests/tlsuv_boringssl_smoke.c
@@ -0,0 +1,207 @@
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+typedef struct smoke_http_capture_s {
+ int status;
+ int finished;
+ int eof_seen;
+ char *body;
+ size_t body_len;
+ size_t body_cap;
+} smoke_http_capture_t;
+
+static void smoke_fail(const char *message) {
+ fprintf(stderr, "FAIL: %s\n", message);
+ exit(1);
+}
+
+static void smoke_check(int ok, const char *message) {
+ if (!ok) smoke_fail(message);
+ printf("ok: %s\n", message);
+}
+
+static void smoke_check_status(int actual, int expected, const char *body) {
+ if (actual == expected) {
+ printf("ok: HTTPS request returned %d\n", expected);
+ return;
+ }
+
+ fprintf(stderr, "FAIL: HTTPS request returned %d (expected %d)\n", actual, expected);
+ if (body && *body) {
+ fprintf(stderr, "response body sample: %.200s\n", body);
+ }
+ exit(1);
+}
+
+static void smoke_http_close_cb(tlsuv_http_t *client) {
+ (void)client;
+}
+
+static void smoke_http_body_cb(tlsuv_http_req_t *req, char *chunk, ssize_t len) {
+ smoke_http_capture_t *cap = (smoke_http_capture_t *)req->data;
+
+ if (len > 0) {
+ size_t needed = cap->body_len + (size_t)len + 1;
+ if (needed > cap->body_cap) {
+ size_t next_cap = cap->body_cap ? cap->body_cap * 2 : 256;
+ while (next_cap < needed) next_cap *= 2;
+ char *next = (char *)realloc(cap->body, next_cap);
+ if (!next) smoke_fail("out of memory growing HTTP body buffer");
+ cap->body = next;
+ cap->body_cap = next_cap;
+ }
+
+ memcpy(cap->body + cap->body_len, chunk, (size_t)len);
+ cap->body_len += (size_t)len;
+ cap->body[cap->body_len] = '\0';
+ return;
+ }
+
+ if (len == UV_EOF) {
+ cap->eof_seen = 1;
+ cap->finished = 1;
+ tlsuv_http_close(req->client, smoke_http_close_cb);
+ }
+}
+
+static void smoke_http_resp_cb(tlsuv_http_resp_t *resp, void *ctx) {
+ smoke_http_capture_t *cap = (smoke_http_capture_t *)ctx;
+ cap->status = resp->code;
+ resp->req->data = cap;
+ resp->body_cb = smoke_http_body_cb;
+
+ if (resp->code < 0) {
+ cap->finished = 1;
+ tlsuv_http_close(resp->req->client, smoke_http_close_cb);
+ }
+}
+
+static void smoke_usage(const char *argv0) {
+ fprintf(stderr, "usage: %s [--url https://localhost:8443] [--ca path] [--cert path] [--key path]\n", argv0);
+}
+
+int main(int argc, char **argv) {
+ const char *ca_path = "vendor/tlsuv/tests/certs/ca.pem";
+ const char *cert_path = "vendor/tlsuv/tests/certs/server.crt";
+ const char *key_path = "vendor/tlsuv/tests/certs/server.key";
+ const char *url = NULL;
+
+ for (int i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "--url") == 0 && i + 1 < argc) {
+ url = argv[++i];
+ } else if (strcmp(argv[i], "--ca") == 0 && i + 1 < argc) {
+ ca_path = argv[++i];
+ } else if (strcmp(argv[i], "--cert") == 0 && i + 1 < argc) {
+ cert_path = argv[++i];
+ } else if (strcmp(argv[i], "--key") == 0 && i + 1 < argc) {
+ key_path = argv[++i];
+ } else if (strcmp(argv[i], "--help") == 0) {
+ smoke_usage(argv[0]);
+ return 0;
+ } else {
+ smoke_usage(argv[0]);
+ return 1;
+ }
+ }
+
+ tls_context *base = default_tls_context(NULL, 0);
+ smoke_check(base != NULL, "created default TLS context");
+
+ const char *version = base->version ? base->version() : NULL;
+ smoke_check(version != NULL && strstr(version, "BoringSSL") != NULL, "tlsuv reports BoringSSL");
+ printf("tls version: %s\n", version);
+
+ smoke_check(base->generate_key != NULL, "generate_key is available");
+ smoke_check(base->load_key != NULL, "load_key is available");
+ smoke_check(base->load_cert != NULL, "load_cert is available");
+ smoke_check(base->set_own_cert != NULL, "set_own_cert is available");
+ smoke_check(base->generate_csr_to_pem != NULL, "generate_csr_to_pem is available");
+
+ tlsuv_private_key_t generated = NULL;
+ smoke_check(base->generate_key(&generated) == 0 && generated != NULL, "generated private key");
+
+ char *generated_pem = NULL;
+ size_t generated_pem_len = 0;
+ smoke_check(generated->to_pem(generated, &generated_pem, &generated_pem_len) == 0, "generated key exports to PEM");
+ smoke_check(generated_pem != NULL && generated_pem_len > 0, "generated key PEM is non-empty");
+
+ tlsuv_private_key_t roundtrip = NULL;
+ smoke_check(base->load_key(&roundtrip, generated_pem, generated_pem_len) == 0 && roundtrip != NULL, "generated key PEM reloads");
+
+ char *csr_pem = NULL;
+ size_t csr_pem_len = 0;
+ smoke_check(
+ base->generate_csr_to_pem(
+ generated, &csr_pem, &csr_pem_len,
+ "CN", "boringssl-smoke",
+ "O", "ant",
+ NULL
+ ) == 0,
+ "generated key can produce CSR"
+ );
+ smoke_check(csr_pem != NULL && csr_pem_len > 0, "CSR PEM is non-empty");
+
+ tlsuv_private_key_t file_key = NULL;
+ tlsuv_certificate_t file_cert = NULL;
+ smoke_check(base->load_key(&file_key, key_path, strlen(key_path)) == 0 && file_key != NULL, "fixture private key loads");
+ smoke_check(base->load_cert(&file_cert, cert_path, strlen(cert_path)) == 0 && file_cert != NULL, "fixture certificate loads");
+ smoke_check(base->set_own_cert(base, file_key, file_cert) == 0, "fixture key/cert install on context");
+
+ struct tm expiration = {0};
+ smoke_check(file_cert->get_expiration != NULL, "certificate expiration accessor is available");
+ smoke_check(file_cert->get_expiration(file_cert, &expiration) == 0, "certificate expiration decodes");
+
+ if (url != NULL) {
+ uv_loop_t *loop = uv_loop_new();
+ smoke_check(loop != NULL, "created libuv loop");
+
+ tls_context *http_tls = default_tls_context(ca_path, strlen(ca_path));
+ smoke_check(http_tls != NULL, "created CA-backed TLS context");
+
+ tlsuv_http_t client;
+ memset(&client, 0, sizeof(client));
+ smoke_http_capture_t cap;
+ memset(&cap, 0, sizeof(cap));
+
+ smoke_check(tlsuv_http_init(loop, &client, url) == 0, "initialized tlsuv HTTP client");
+ tlsuv_http_set_ssl(&client, http_tls);
+
+ tlsuv_http_req_t *req = tlsuv_http_req(&client, "GET", "/get", smoke_http_resp_cb, &cap);
+ smoke_check(req != NULL, "created HTTPS request");
+ tlsuv_http_req_end(req);
+
+ uv_run(loop, UV_RUN_DEFAULT);
+
+ smoke_check(cap.finished, "HTTPS request finished");
+ smoke_check_status(cap.status, 200, cap.body);
+ smoke_check(cap.eof_seen, "HTTPS body reached EOF");
+ smoke_check(cap.body != NULL && cap.body_len > 0, "HTTPS body is non-empty");
+ printf("https body sample: %.80s\n", cap.body);
+
+ http_tls->free_ctx(http_tls);
+ if (uv_loop_close(loop) != 0) smoke_fail("failed to close libuv loop");
+ free(loop);
+ free(cap.body);
+ } else {
+ printf("skipped live HTTPS probe; rerun with --url https://localhost:8443 to exercise a real handshake\n");
+ printf("tip: start vendor/tlsuv/tests/test_server/test-server.go with the existing cert fixtures first\n");
+ }
+
+ free(csr_pem);
+ free(generated_pem);
+
+ if (base && base->free_ctx) base->free_ctx(base);
+ if (file_cert && file_cert->free) file_cert->free(file_cert);
+ if (file_key && file_key->free) file_key->free(file_key);
+ if (roundtrip && roundtrip->free) roundtrip->free(roundtrip);
+ if (generated && generated->free) generated->free(generated);
+
+ printf("BoringSSL smoke test passed\n");
+ return 0;
+}
diff --git a/vendor/boringssl.wrap b/vendor/boringssl.wrap
new file mode 100644
index 0000000..d75ee97
--- /dev/null
+++ b/vendor/boringssl.wrap
@@ -0,0 +1,4 @@
+[wrap-git]
+url = https://github.com/google/boringssl.git
+revision = 297b11798a0ed6bc7736aa57328909a4afbbf67a
+depth = 1
diff --git a/vendor/packagefiles/patches/tlsuv-boringssl.patch b/vendor/packagefiles/patches/tlsuv-boringssl.patch
new file mode 100644
index 0000000..5d00bd7
--- /dev/null
+++ b/vendor/packagefiles/patches/tlsuv-boringssl.patch
@@ -0,0 +1,13 @@
+diff --git a/src/alloc.c b/src/alloc.c
+index 6a9adf7..2d3a5ab 100644
+--- a/src/alloc.c
++++ b/src/alloc.c
+@@ -63,7 +63,7 @@ void tlsuv_set_allocator(uv_malloc_func malloc_f,
+ ALLOC.free_f = free_f;
+
+ uv_replace_allocator(malloc_f, realloc_f, calloc_f, free_f);
+-#if USE_OPENSSL
++#if USE_OPENSSL && !defined(OPENSSL_IS_BORINGSSL)
+ CRYPTO_set_mem_functions(crypto_malloc, crypto_realloc, crypto_free);
+ #endif
+
diff --git a/vendor/packagefiles/tlsuv/meson.build b/vendor/packagefiles/tlsuv/meson.build
index 7831e4b..7519783 100644
--- a/vendor/packagefiles/tlsuv/meson.build
+++ b/vendor/packagefiles/tlsuv/meson.build
@@ -47,7 +47,11 @@ elif host_machine.system() == 'windows'
tlsuv_sources += files('src/win32/win32_keychain.c')
endif
-tlsuv_sources += files('src/openssl/engine.c', 'src/openssl/keys.c')
+if tls_backend == 'boringssl'
+ tlsuv_sources += files('src/boringssl/engine.c', 'src/boringssl/keys.c')
+else
+ tlsuv_sources += files('src/openssl/engine.c', 'src/openssl/keys.c')
+endif
tlsuv_c_args = [
'-DTLSUV_VERSION=v' + meson.project_version(),
@@ -69,8 +73,8 @@ elif host_machine.system() == 'linux'
tlsuv_c_args += ['-D_POSIX_C_SOURCE=200112', '-D_GNU_SOURCE']
endif
-tls_dep = dependency('openssl', required: true)
-tlsuv_c_args += ['-I' + tls_dep.get_variable(pkgconfig: 'includedir')]
+tls_ssl_dep = dependency('ssl', required: true)
+tls_crypto_dep = dependency('crypto', required: true)
win_deps = []
if host_machine.system() == 'windows'
@@ -91,7 +95,7 @@ tlsuv_lib = static_library(
'tlsuv',
tlsuv_sources, tlsuv_config_h,
c_args: tlsuv_c_args,
- dependencies: [libuv_dep, llhttp_dep, zlib_dep] + win_deps,
+ dependencies: [libuv_dep, llhttp_dep, zlib_dep, tls_ssl_dep, tls_crypto_dep] + win_deps,
include_directories: [
include_directories('include'),
include_directories('src'),
@@ -103,6 +107,7 @@ tlsuv_lib = static_library(
tlsuv_dep = declare_dependency(
link_with: tlsuv_lib,
+ dependencies: [tls_ssl_dep, tls_crypto_dep],
include_directories: [
include_directories('include'),
include_directories('deps/uv_link_t/include'),
diff --git a/vendor/packagefiles/tlsuv/meson_options.txt b/vendor/packagefiles/tlsuv/meson_options.txt
index c52a38b..9a895e1 100644
--- a/vendor/packagefiles/tlsuv/meson_options.txt
+++ b/vendor/packagefiles/tlsuv/meson_options.txt
@@ -1,2 +1,2 @@
-option('tls_backend', type: 'combo', choices: ['openssl'], value: 'openssl', description: 'TLS backend library')
+option('tls_backend', type: 'combo', choices: ['openssl', 'boringssl'], value: 'boringssl', description: 'TLS backend library')
option('deps_prefix_cmake', type: 'string', value: '', description: 'Prefix for cmake-built dependencies')
diff --git a/vendor/packagefiles/tlsuv/src/boringssl/engine.c b/vendor/packagefiles/tlsuv/src/boringssl/engine.c
new file mode 100644
index 0000000..939cd3e
--- /dev/null
+++ b/vendor/packagefiles/tlsuv/src/boringssl/engine.c
@@ -0,0 +1,954 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "../alloc.h"
+#include "../keychain.h"
+#include "../um_debug.h"
+
+#include
+#include
+#include
+#include
+
+#include "keys.h"
+
+#if _WIN32
+#include
+#ifndef PATH_MAX
+#define PATH_MAX MAX_PATH
+#endif
+#endif
+
+#if __APPLE__
+#include
+#endif
+
+struct boringssl_ctx {
+ tls_context api;
+ SSL_CTX *ctx;
+ int (*cert_verify_f)(const struct tlsuv_certificate_s *cert, void *v_ctx);
+ void *verify_ctx;
+ unsigned char *alpn_protocols;
+};
+
+struct boringssl_engine {
+ struct tlsuv_engine_s api;
+ SSL *ssl;
+ char *alpn;
+ BIO *bio;
+ bool handshake_started;
+ io_ctx io;
+ io_read read_f;
+ io_write write_f;
+ unsigned long error;
+};
+
+static int is_self_signed(X509 *cert);
+static const char *name_str(const X509_NAME *n);
+static void init_ssl_context(struct boringssl_ctx *c, const char *cabuf, size_t cabuf_len);
+static int tls_set_own_cert(tls_context *ctx, tlsuv_private_key_t key, tlsuv_certificate_t cert);
+static int set_ca_bundle(tls_context *tls, const char *ca, size_t ca_len);
+tlsuv_engine_t new_boringssl_engine(tls_context *ctx, const char *host);
+static int parse_pkcs7_certs(tlsuv_certificate_t *chain, const char *pkcs7, size_t pkcs7len);
+static int load_cert(tlsuv_certificate_t *cert, const char *buf, size_t buflen);
+static int generate_csr(tlsuv_private_key_t key, char **pem, size_t *pemlen, ...);
+static int tls_set_partial_vfy(tls_context *ctx, int allow);
+static void tls_set_cert_verify(tls_context *ctx, int (*verify_f)(const struct tlsuv_certificate_s *cert, void *v_ctx), void *v_ctx);
+
+static void set_io(tlsuv_engine_t self, io_ctx io, io_read rdf, io_write wrtf);
+static void set_io_fd(tlsuv_engine_t self, tlsuv_sock_t fd);
+static void set_protocols(tlsuv_engine_t self, const char **protocols, int len);
+static tls_handshake_state tls_hs_state(tlsuv_engine_t engine);
+static tls_handshake_state tls_continue_hs(tlsuv_engine_t self);
+static const char *tls_get_alpn(tlsuv_engine_t self);
+static int tls_write(tlsuv_engine_t self, const char *data, size_t data_len);
+static int tls_read(tlsuv_engine_t self, char *out, size_t *out_bytes, size_t maxout);
+static int tls_close(tlsuv_engine_t self);
+static int tls_reset(tlsuv_engine_t self);
+static void tls_free(tlsuv_engine_t self);
+static void tls_free_ctx(tls_context *ctx);
+static const char *tls_lib_version();
+static const char *tls_eng_error(tlsuv_engine_t self);
+static void msg_cb(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg);
+static void info_cb(const SSL *s, int where, int ret);
+static BIO_METHOD *BIO_s_engine(void);
+#if _WIN32
+static X509_STORE *load_system_certs();
+#endif
+
+static tls_context boringssl_context_api = {
+ .version = tls_lib_version,
+ .strerror = (const char *(*)(long))tls_error,
+ .new_engine = new_boringssl_engine,
+ .free_ctx = tls_free_ctx,
+ .set_ca_bundle = set_ca_bundle,
+ .set_own_cert = tls_set_own_cert,
+ .allow_partial_chain = tls_set_partial_vfy,
+ .set_cert_verify = tls_set_cert_verify,
+ .parse_pkcs7_certs = parse_pkcs7_certs,
+ .load_cert = load_cert,
+ .generate_key = gen_key,
+ .load_key = load_key,
+ .generate_csr_to_pem = generate_csr,
+};
+
+static struct tlsuv_engine_s boringssl_engine_api = {
+ .set_io = set_io,
+ .set_io_fd = set_io_fd,
+ .set_protocols = set_protocols,
+ .handshake_state = tls_hs_state,
+ .handshake = tls_continue_hs,
+ .get_alpn = tls_get_alpn,
+ .close = tls_close,
+ .write = tls_write,
+ .read = tls_read,
+ .reset = tls_reset,
+ .free = tls_free,
+ .strerror = tls_eng_error,
+};
+
+int configure_boringssl() {
+ return 0;
+}
+
+static const char *tls_lib_version() {
+ return OpenSSL_version(OPENSSL_VERSION);
+}
+
+const char *tls_error(unsigned long code) {
+ static char err_buf[32];
+ if (code == 0) return "no error";
+
+ const char *lib_err = ERR_lib_error_string(code);
+ if (lib_err) return lib_err;
+
+ snprintf(err_buf, sizeof(err_buf), "error[%lX]", code);
+ return err_buf;
+}
+
+static const char *tls_eng_error(tlsuv_engine_t self) {
+ struct boringssl_engine *e = (struct boringssl_engine *)self;
+ const char *err = ERR_reason_error_string(e->error);
+ return err;
+}
+
+tls_context *new_boringssl_ctx(const char *ca, size_t ca_len) {
+ OPENSSL_init_ssl(OPENSSL_INIT_SSL_DEFAULT, NULL);
+
+ struct boringssl_ctx *c = tlsuv__calloc(1, sizeof(struct boringssl_ctx));
+ c->api = boringssl_context_api;
+ init_ssl_context(c, ca, ca_len);
+ return &c->api;
+}
+
+static X509_STORE *load_certs(const char *buf, size_t buf_len) {
+ X509_STORE *certs = X509_STORE_new();
+ X509 *c;
+ struct stat fstat;
+
+ if (stat(buf, &fstat) == 0) {
+ if (fstat.st_mode & S_IFREG) {
+ if (!X509_STORE_load_locations(certs, buf, NULL)) {
+ UM_LOG(ERR, "failed to load certs from [%s]", buf);
+ }
+ } else if (fstat.st_mode & S_IFDIR) {
+ X509_LOOKUP *lu = X509_STORE_add_lookup(certs, X509_LOOKUP_hash_dir());
+ if (lu == NULL || X509_LOOKUP_add_dir(lu, buf, X509_FILETYPE_PEM) != 1) {
+ UM_LOG(ERR, "failed to load cert directory from [%s]", buf);
+ }
+ } else {
+ UM_LOG(ERR, "cert bundle[%s] is not a regular file", buf);
+ }
+ } else {
+ BIO *crt_bio = BIO_new_mem_buf(buf, (int)buf_len);
+ while ((c = PEM_read_bio_X509(crt_bio, NULL, NULL, NULL)) != NULL) {
+ int root = is_self_signed(c);
+ UM_LOG(VERB, "%s root[%s]", name_str(X509_get_subject_name(c)), root ? "true" : "false");
+ X509_STORE_add_cert(certs, c);
+ X509_free(c);
+ }
+ BIO_free(crt_bio);
+ }
+
+ return certs;
+}
+
+static int load_cert(tlsuv_certificate_t *cert, const char *buf, size_t buflen) {
+ X509_STORE *store = load_certs(buf, buflen);
+ STACK_OF(X509_OBJECT) *certs = X509_STORE_get0_objects(store);
+ int count = sk_X509_OBJECT_num(certs);
+ if (count == 0) {
+ X509_STORE_free(store);
+ return -1;
+ }
+
+ struct cert_s *crt = tlsuv__calloc(1, sizeof(*crt));
+ cert_init(crt);
+ crt->cert = store;
+ *cert = (tlsuv_certificate_t)crt;
+ return 0;
+}
+
+static int is_self_signed(X509 *cert) {
+ X509_NAME *subj = X509_get_subject_name(cert);
+ X509_NAME *issuer = X509_get_issuer_name(cert);
+ if (X509_NAME_cmp(subj, issuer) != 0) {
+ return 0;
+ }
+
+ EVP_PKEY *pub = X509_get_pubkey(cert);
+ if (pub == NULL) {
+ return 0;
+ }
+ int ok = X509_verify(cert, pub);
+ EVP_PKEY_free(pub);
+ return ok == 1;
+}
+
+static const char *name_str(const X509_NAME *n) {
+ static char buf[1024];
+ BIO *b = BIO_new(BIO_s_mem());
+ X509_NAME_print(b, n, 0);
+ BIO_read(b, buf, sizeof(buf));
+ BIO_free(b);
+ return buf;
+}
+
+#if __APPLE__
+static int apple_ca_verify(int pre_verify, X509_STORE_CTX *st) {
+ (void)pre_verify;
+ CFMutableArrayRef certs = CFArrayCreateMutable(kCFAllocatorDefault, 10, &kCFTypeArrayCallBacks);
+
+ STACK_OF(X509) *chain = X509_STORE_CTX_get1_chain(st);
+ for (int i = 0; i < sk_X509_num(chain); i++) {
+ X509 *x = sk_X509_value(chain, i);
+ uint8_t *der = NULL;
+ int der_len = i2d_X509(x, &der);
+ CFDataRef d = CFDataCreate(kCFAllocatorDefault, der, der_len);
+ OPENSSL_free(der);
+
+ SecCertificateRef c = SecCertificateCreateWithData(kCFAllocatorDefault, d);
+ CFArrayAppendValue(certs, c);
+ CFRelease(d);
+ CFRelease(c);
+ }
+ sk_X509_pop_free(chain, X509_free);
+
+ SecTrustRef trust;
+ SecPolicyRef policy = SecPolicyCreateBasicX509();
+ CFErrorRef err = NULL;
+ bool result = SecTrustCreateWithCertificates(certs, policy, &trust) == errSecSuccess &&
+ SecTrustEvaluateWithError(trust, &err);
+ CFRelease(trust);
+ CFRelease(policy);
+ CFRelease(certs);
+ return result;
+}
+#endif
+
+static int set_ca_bundle(tls_context *tls, const char *ca, size_t ca_len) {
+ struct boringssl_ctx *c = (struct boringssl_ctx *)tls;
+ SSL_CTX *ctx = c->ctx;
+
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
+ if (ca != NULL) {
+ X509_STORE *store = load_certs(ca, ca_len);
+ SSL_CTX_set0_verify_cert_store(ctx, store);
+ } else {
+#if _WIN32
+ if (!SSL_CTX_load_verify_store(ctx, "org.openssl.winstore:")) {
+ X509_STORE *sys_ca = load_system_certs();
+ SSL_CTX_set0_verify_cert_store(ctx, sys_ca);
+ }
+#elif __APPLE__
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, apple_ca_verify);
+#elif defined(ANDROID) || defined(__ANDROID__)
+ X509_STORE *ca_store = SSL_CTX_get_cert_store(ctx);
+ X509_LOOKUP *lu = X509_STORE_add_lookup(ca_store, X509_LOOKUP_hash_dir());
+ if (lu != NULL) {
+ X509_LOOKUP_add_dir(lu, "/etc/security/cacerts", X509_FILETYPE_PEM);
+ }
+#else
+ SSL_CTX_set_default_verify_paths(ctx);
+#endif
+ }
+
+ return 0;
+}
+
+static void init_ssl_context(struct boringssl_ctx *c, const char *cabuf, size_t cabuf_len) {
+ const SSL_METHOD *method = TLS_client_method();
+ SSL_CTX *ctx = SSL_CTX_new(method);
+ if (ctx == NULL) {
+ ERR_print_errors_fp(stderr);
+ UM_LOG(ERR, "FATAL: failed to create SSL_CTX: %s", tls_error(ERR_get_error()));
+ abort();
+ }
+
+ SSL_CTX_set_app_data(ctx, c);
+ c->ctx = ctx;
+ set_ca_bundle((tls_context *)c, cabuf, cabuf_len);
+ SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
+ SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION);
+
+ char *tls_debug = getenv("TLS_DEBUG");
+ if (tls_debug) {
+ SSL_CTX_set_msg_callback(ctx, msg_cb);
+ SSL_CTX_set_info_callback(ctx, info_cb);
+ }
+}
+
+typedef struct string_int_pair_st {
+ const char *name;
+ int retval;
+} STRINT_PAIR;
+
+static const char *lookup(int val, const STRINT_PAIR *list, const char *def) {
+ for (; list->name; ++list) {
+ if (list->retval == val) return list->name;
+ }
+ return def;
+}
+
+static STRINT_PAIR handshakes[] = {
+ {", HelloRequest", SSL3_MT_HELLO_REQUEST},
+ {", ClientHello", SSL3_MT_CLIENT_HELLO},
+ {", ServerHello", SSL3_MT_SERVER_HELLO},
+ {", HelloVerifyRequest", DTLS1_MT_HELLO_VERIFY_REQUEST},
+ {", NewSessionTicket", SSL3_MT_NEWSESSION_TICKET},
+ {", EndOfEarlyData", SSL3_MT_END_OF_EARLY_DATA},
+ {", EncryptedExtensions", SSL3_MT_ENCRYPTED_EXTENSIONS},
+ {", Certificate", SSL3_MT_CERTIFICATE},
+ {", ServerKeyExchange", SSL3_MT_SERVER_KEY_EXCHANGE},
+ {", CertificateRequest", SSL3_MT_CERTIFICATE_REQUEST},
+ {", ServerHelloDone", SSL3_MT_SERVER_DONE},
+ {", CertificateVerify", SSL3_MT_CERTIFICATE_VERIFY},
+ {", ClientKeyExchange", SSL3_MT_CLIENT_KEY_EXCHANGE},
+ {", Finished", SSL3_MT_FINISHED},
+ {", CertificateStatus", SSL3_MT_CERTIFICATE_STATUS},
+ {", SupplementalData", SSL3_MT_SUPPLEMENTAL_DATA},
+ {", KeyUpdate", SSL3_MT_KEY_UPDATE},
+#ifndef OPENSSL_NO_NEXTPROTONEG
+ {", NextProto", SSL3_MT_NEXT_PROTO},
+#endif
+ {", MessageHash", SSL3_MT_MESSAGE_HASH},
+ {NULL}
+};
+
+static STRINT_PAIR alert_types[] = {
+ {" close_notify", 0},
+ {" end_of_early_data", 1},
+ {" unexpected_message", 10},
+ {" bad_record_mac", 20},
+ {" decryption_failed", 21},
+ {" record_overflow", 22},
+ {" decompression_failure", 30},
+ {" handshake_failure", 40},
+ {" bad_certificate", 42},
+ {" unsupported_certificate", 43},
+ {" certificate_revoked", 44},
+ {" certificate_expired", 45},
+ {" certificate_unknown", 46},
+ {" illegal_parameter", 47},
+ {" unknown_ca", 48},
+ {" access_denied", 49},
+ {" decode_error", 50},
+ {" decrypt_error", 51},
+ {" export_restriction", 60},
+ {" protocol_version", 70},
+ {" insufficient_security", 71},
+ {" internal_error", 80},
+ {" inappropriate_fallback", 86},
+ {" user_canceled", 90},
+ {" no_renegotiation", 100},
+ {" missing_extension", 109},
+ {" unsupported_extension", 110},
+ {" certificate_unobtainable", 111},
+ {" unrecognized_name", 112},
+ {" bad_certificate_status_response", 113},
+ {" bad_certificate_hash_value", 114},
+ {" unknown_psk_identity", 115},
+ {" certificate_required", 116},
+ {NULL}
+};
+
+static STRINT_PAIR ssl_versions[] = {
+ {"SSL 3.0", SSL3_VERSION},
+ {"TLS 1.0", TLS1_VERSION},
+ {"TLS 1.1", TLS1_1_VERSION},
+ {"TLS 1.2", TLS1_2_VERSION},
+ {"TLS 1.3", TLS1_3_VERSION},
+ {"DTLS 1.0", DTLS1_VERSION},
+ {NULL}
+};
+
+static void msg_cb(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg) {
+ (void)ssl;
+ (void)arg;
+ const char *str_write_p = write_p ? ">>>" : "<<<";
+ const char *str_content_type = "", *str_details1 = "", *str_details2 = "";
+ const char *str_version = lookup(version, ssl_versions, "???");
+ const unsigned char *bp = buf;
+
+ if (version == SSL3_VERSION || version == TLS1_VERSION || version == TLS1_1_VERSION ||
+ version == TLS1_2_VERSION || version == TLS1_3_VERSION ||
+ version == DTLS1_VERSION) {
+ switch (content_type) {
+ case 20: str_content_type = ", ChangeCipherSpec"; break;
+ case 21:
+ str_content_type = ", Alert";
+ str_details1 = ", ???";
+ if (len == 2) {
+ switch (bp[0]) {
+ case 1: str_details1 = ", warning"; break;
+ case 2: str_details1 = ", fatal"; break;
+ }
+ str_details2 = lookup((int)bp[1], alert_types, " ???");
+ }
+ break;
+ case 22:
+ str_content_type = ", Handshake";
+ str_details1 = "???";
+ if (len > 0) str_details1 = lookup((int)bp[0], handshakes, "???");
+ break;
+ case 23: str_content_type = ", ApplicationData"; break;
+ }
+ } else if (version == 0 && content_type == SSL3_RT_HEADER) {
+ str_version = "";
+ str_content_type = "TLS Header";
+ }
+
+ UM_LOG(TRACE, "%s %s%s [length %04lx]%s%s", str_write_p, str_version, str_content_type, (unsigned long)len, str_details1, str_details2);
+}
+
+void info_cb(const SSL *s, int where, int ret) {
+ const char *str;
+ int w = where & ~SSL_ST_MASK;
+
+ if (w & SSL_ST_CONNECT) str = "SSL_connect";
+ else if (w & SSL_ST_ACCEPT) str = "SSL_accept";
+ else str = "undefined";
+
+ if (where & SSL_CB_LOOP) {
+ UM_LOG(TRACE, "%s:%s", str, SSL_state_string_long(s));
+ } else if (where & SSL_CB_ALERT) {
+ str = (where & SSL_CB_READ) ? "read" : "write";
+ UM_LOG(VERB, "SSL3 alert %s:%s:%s", str, SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
+ } else if (where & SSL_CB_EXIT) {
+ if (ret == 0) UM_LOG(VERB, "%s:failed in %s", str, SSL_state_string_long(s));
+ else if (ret < 0) UM_LOG(VERB, "%s:error in %s", str, SSL_state_string_long(s));
+ }
+}
+
+tlsuv_engine_t new_boringssl_engine(tls_context *ctx, const char *host) {
+ struct boringssl_ctx *context = (struct boringssl_ctx *)ctx;
+ struct boringssl_engine *engine = tlsuv__calloc(1, sizeof(struct boringssl_engine));
+ engine->api = boringssl_engine_api;
+ engine->ssl = SSL_new(context->ctx);
+
+ SSL_set_tlsext_host_name(engine->ssl, host);
+ SSL_set1_host(engine->ssl, host);
+ SSL_set_connect_state(engine->ssl);
+ SSL_set_app_data(engine->ssl, engine);
+
+ return &engine->api;
+}
+
+static void set_io(tlsuv_engine_t self, io_ctx io, io_read rdf, io_write wrtf) {
+ struct boringssl_engine *e = (struct boringssl_engine *)self;
+ assert(e->bio == NULL);
+
+#ifdef SSL_OP_IGNORE_UNEXPECTED_EOF
+ SSL_set_options(e->ssl, SSL_OP_IGNORE_UNEXPECTED_EOF);
+#endif
+ e->bio = BIO_new(BIO_s_engine());
+ BIO_set_data(e->bio, e);
+ BIO_set_init(e->bio, true);
+ SSL_set_bio(e->ssl, e->bio, e->bio);
+
+ e->io = io;
+ e->read_f = rdf;
+ e->write_f = wrtf;
+}
+
+static void set_io_fd(tlsuv_engine_t self, tlsuv_sock_t fd) {
+ struct boringssl_engine *e = (struct boringssl_engine *)self;
+ assert(e->bio == NULL);
+ e->bio = BIO_new_socket((int)fd, false);
+ SSL_set_bio(e->ssl, e->bio, e->bio);
+}
+
+static void set_protocols(tlsuv_engine_t self, const char **protocols, int len) {
+ struct boringssl_engine *e = (struct boringssl_engine *)self;
+ size_t protolen = 0;
+ for (int i = 0; i < len; i++) protolen += strlen(protocols[i]) + 1;
+
+ unsigned char *alpn_protocols = tlsuv__malloc(protolen + 1);
+ unsigned char *p = alpn_protocols;
+ for (int i = 0; i < len; i++) {
+ size_t plen = strlen(protocols[i]);
+ *p++ = (unsigned char)plen;
+ strncpy((char *)p, protocols[i], plen);
+ p += plen;
+ }
+ *p = 0;
+ SSL_set_alpn_protos(e->ssl, alpn_protocols, (unsigned int)strlen((char *)alpn_protocols));
+ tlsuv__free(alpn_protocols);
+}
+
+static int cert_verify_cb(X509_STORE_CTX *certs, void *ctx) {
+ struct boringssl_ctx *c = ctx;
+ X509_STORE *store = X509_STORE_new();
+ X509 *crt = X509_STORE_CTX_get0_cert(certs);
+ X509_STORE_add_cert(store, crt);
+
+ char n[1024];
+ X509_NAME_oneline(X509_get_subject_name(crt), n, 1024);
+ UM_LOG(VERB, "verifying %s", n);
+
+ int rc = 1;
+ struct cert_s cert;
+ cert_init(&cert);
+ cert.cert = store;
+ if (c->cert_verify_f && c->cert_verify_f((const struct tlsuv_certificate_s *)&cert, c->verify_ctx) != 0) {
+ UM_LOG(WARN, "verify failed for certificate[%s]", n);
+ rc = 0;
+ }
+ X509_STORE_free(store);
+ return rc;
+}
+
+int tls_set_partial_vfy(tls_context *ctx, int allow) {
+ struct boringssl_ctx *c = (struct boringssl_ctx *)ctx;
+ X509_VERIFY_PARAM *vfy = SSL_CTX_get0_param(c->ctx);
+ if (allow) X509_VERIFY_PARAM_set_flags(vfy, X509_V_FLAG_PARTIAL_CHAIN);
+ else X509_VERIFY_PARAM_clear_flags(vfy, X509_V_FLAG_PARTIAL_CHAIN);
+ return 0;
+}
+
+static void tls_set_cert_verify(tls_context *ctx, int (*verify_f)(const struct tlsuv_certificate_s *cert, void *v_ctx), void *v_ctx) {
+ struct boringssl_ctx *c = (struct boringssl_ctx *)ctx;
+ c->cert_verify_f = verify_f;
+ c->verify_ctx = v_ctx;
+ SSL_CTX_set_verify(c->ctx, SSL_VERIFY_PEER, NULL);
+ SSL_CTX_set_cert_verify_callback(c->ctx, cert_verify_cb, c);
+}
+
+static void tls_free_ctx(tls_context *ctx) {
+ struct boringssl_ctx *c = (struct boringssl_ctx *)ctx;
+ if (c->alpn_protocols) {
+ tlsuv__free(c->alpn_protocols);
+ }
+ SSL_CTX_free(c->ctx);
+ tlsuv__free(c);
+}
+
+static int tls_reset(tlsuv_engine_t self) {
+ struct boringssl_engine *e = (struct boringssl_engine *)self;
+ ERR_clear_error();
+ e->bio = NULL;
+ if (!SSL_clear(e->ssl)) {
+ int err = SSL_get_error(e->ssl, 0);
+ UM_LOG(ERR, "error resetting TLS engine: %d(%s)", err, tls_error(err));
+ return -1;
+ }
+ return 0;
+}
+
+static void tls_free(tlsuv_engine_t self) {
+ struct boringssl_engine *e = (struct boringssl_engine *)self;
+ SSL_free(e->ssl);
+ if (e->alpn) {
+ tlsuv__free(e->alpn);
+ }
+ tlsuv__free(e);
+}
+
+#define SSL_OP_CHECK(op, desc) do { \
+ if ((op) != 1) { \
+ uint32_t err = ERR_get_error(); \
+ UM_LOG(ERR, "failed to " desc ": %d(%s)", err, tls_error(err)); \
+ return TLS_ERR; \
+ } \
+} while (0)
+
+static X509 *tls_set_cert_internal(SSL_CTX *ssl, X509_STORE *store) {
+ STACK_OF(X509_OBJECT) *certs = X509_STORE_get0_objects(store);
+ X509 *crt = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(certs, 0));
+ SSL_CTX_use_certificate(ssl, crt);
+
+ for (int i = 1; i < sk_X509_OBJECT_num(certs); i++) {
+ X509 *x509 = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(certs, i));
+ X509_up_ref(x509);
+ SSL_CTX_add_extra_chain_cert(ssl, x509);
+ }
+ return crt;
+}
+
+static int tls_set_own_cert(tls_context *ctx, tlsuv_private_key_t key, tlsuv_certificate_t cert) {
+ struct boringssl_ctx *c = (struct boringssl_ctx *)ctx;
+ SSL_CTX *ssl = c->ctx;
+
+ SSL_CTX_use_PrivateKey(ssl, NULL);
+ SSL_CTX_use_certificate(ssl, NULL);
+ SSL_CTX_clear_chain_certs(ssl);
+
+ if (key == NULL) {
+ return 0;
+ }
+
+ struct cert_s *crt = (struct cert_s *)cert;
+ X509_STORE *store = NULL;
+ if (crt == NULL) {
+ if (key->get_certificate) {
+ if (key->get_certificate(key, (tlsuv_certificate_t *)&crt) != 0) {
+ return -1;
+ }
+ store = crt->cert;
+ free(crt);
+ }
+ } else {
+ store = crt->cert;
+ X509_STORE_up_ref(crt->cert);
+ }
+
+ if (store == NULL) {
+ return -1;
+ }
+
+ struct priv_key_s *pk = (struct priv_key_s *)key;
+ X509 *certs = tls_set_cert_internal(ssl, store);
+ X509_STORE_free(store);
+
+ SSL_OP_CHECK(X509_check_private_key(certs, pk->pkey), "verify key/cert combo");
+ SSL_OP_CHECK(SSL_CTX_use_PrivateKey(ssl, pk->pkey), "set private key");
+ return 0;
+}
+
+static tls_handshake_state tls_hs_state(tlsuv_engine_t engine) {
+ struct boringssl_engine *eng = (struct boringssl_engine *)engine;
+ if (SSL_is_init_finished(eng->ssl)) return TLS_HS_COMPLETE;
+ return eng->handshake_started ? TLS_HS_CONTINUE : TLS_HS_BEFORE;
+}
+
+static int print_err_cb(const char *e, size_t len, void *v) {
+ (void)v;
+ UM_LOG(WARN, "%.*s", (int)len, e);
+ return 1;
+}
+
+static tls_handshake_state tls_continue_hs(tlsuv_engine_t self) {
+ struct boringssl_engine *eng = (struct boringssl_engine *)self;
+ ERR_clear_error();
+ eng->handshake_started = true;
+
+ int rc = SSL_do_handshake(eng->ssl);
+ if (rc != 1) {
+ eng->error = ERR_peek_error();
+ ERR_print_errors_cb(print_err_cb, NULL);
+ }
+
+ if (rc == 1) {
+ eng->error = 0;
+ return TLS_HS_COMPLETE;
+ }
+
+ int err = SSL_get_error(eng->ssl, rc);
+ if (rc == 0) {
+ UM_LOG(ERR, "boringssl: handshake was terminated: %s", tls_error(eng->error));
+ return TLS_HS_ERROR;
+ }
+
+ if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
+ return TLS_HS_CONTINUE;
+ }
+
+ UM_LOG(ERR, "boringssl: handshake was terminated: %s", tls_error(eng->error));
+ return TLS_HS_ERROR;
+}
+
+static const char *tls_get_alpn(tlsuv_engine_t self) {
+ struct boringssl_engine *eng = (struct boringssl_engine *)self;
+ const unsigned char *proto;
+ unsigned int protolen;
+ SSL_get0_alpn_selected(eng->ssl, &proto, &protolen);
+
+ eng->alpn = tlsuv__calloc(1, protolen + 1);
+ strncpy(eng->alpn, (const char *)proto, protolen);
+ return eng->alpn;
+}
+
+static int tls_write(tlsuv_engine_t self, const char *data, size_t data_len) {
+ struct boringssl_engine *eng = (struct boringssl_engine *)self;
+ ERR_clear_error();
+
+ if (data_len > INT_MAX) data_len = INT_MAX;
+
+ size_t wrote = 0;
+ while (data_len > wrote) {
+ int remaining = (int)(data_len - wrote);
+ int ret = SSL_write(eng->ssl, data + wrote, remaining);
+ if (ret <= 0) {
+ int err = SSL_get_error(eng->ssl, ret);
+ if (err == SSL_ERROR_WANT_WRITE) {
+ return wrote > 0 ? (int)wrote : TLS_AGAIN;
+ }
+ eng->error = ERR_peek_last_error();
+ UM_LOG(ERR, "boringssl: write[%d] error: %s", err, tls_error(eng->error));
+ return -1;
+ }
+ wrote += (size_t)ret;
+ }
+
+ return (int)wrote;
+}
+
+static int tls_read(tlsuv_engine_t self, char *out, size_t *out_bytes, size_t maxout) {
+ struct boringssl_engine *eng = (struct boringssl_engine *)self;
+ int err = SSL_ERROR_NONE;
+ uint8_t *writep = (uint8_t *)out;
+ size_t total_out = 0;
+
+ ERR_clear_error();
+ while (maxout - total_out > 0) {
+ int want = (int)(maxout - total_out);
+ int rc = SSL_read(eng->ssl, writep, want);
+ if (rc <= 0) {
+ err = SSL_get_error(eng->ssl, rc);
+ break;
+ }
+ total_out += (size_t)rc;
+ writep += rc;
+ }
+
+ if (total_out > 0) {
+ *out_bytes = total_out;
+ return SSL_pending(eng->ssl) ? TLS_MORE_AVAILABLE : TLS_OK;
+ }
+
+ *out_bytes = 0;
+ if (SSL_get_shutdown(eng->ssl)) return TLS_EOF;
+
+ unsigned long err_code = ERR_peek_last_error();
+ switch (err) {
+ case SSL_ERROR_NONE:
+ UM_LOG(WARN, "boringssl read: SSL_ERROR_NONE with 0 bytes read");
+ return TLS_OK;
+ case SSL_ERROR_ZERO_RETURN:
+ return TLS_EOF;
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+ return TLS_AGAIN;
+ case SSL_ERROR_SYSCALL:
+ case SSL_ERROR_SSL:
+ eng->error = err_code;
+ UM_LOG(WARN, "boringssl read[%d]: %lX/%s", err, err_code, tls_error(err_code));
+ return TLS_ERR;
+ default:
+ UM_LOG(WARN, "boringssl read: unexpected err[%d] code[%lX]", err, err_code);
+ break;
+ }
+ return TLS_OK;
+}
+
+static int tls_close(tlsuv_engine_t self) {
+ struct boringssl_engine *eng = (struct boringssl_engine *)self;
+ ERR_clear_error();
+ int rc = SSL_shutdown(eng->ssl);
+ if (rc < 0) {
+ int err = SSL_get_error(eng->ssl, rc);
+ unsigned long err_code = ERR_peek_last_error();
+ UM_LOG(WARN, "boringssl shutdown[%d]: %lX/%s", err, err_code, tls_error(err_code));
+ }
+ return 0;
+}
+
+static int parse_pkcs7_certs(tlsuv_certificate_t *chain, const char *pkcs7buf, size_t pkcs7len) {
+ BIO *buf = BIO_new_mem_buf(pkcs7buf, (int)pkcs7len);
+ PKCS7 *pkcs7 = PEM_read_bio_PKCS7(buf, NULL, NULL, NULL);
+ if (pkcs7 == NULL) {
+ BIO_free(buf);
+ buf = BIO_new_mem_buf(pkcs7buf, (int)pkcs7len);
+ pkcs7 = d2i_PKCS7_bio(buf, NULL);
+ }
+
+ if (pkcs7 == NULL) {
+ BIO_free(buf);
+ return -1;
+ }
+
+ STACK_OF(X509) *certs;
+ if (PKCS7_type_is_signed(pkcs7)) certs = pkcs7->d.sign->cert;
+ else if (PKCS7_type_is_signedAndEnveloped(pkcs7)) certs = pkcs7->d.signed_and_enveloped->cert;
+ else {
+ BIO_free(buf);
+ PKCS7_free(pkcs7);
+ return -1;
+ }
+
+ X509_STORE *store = X509_STORE_new();
+ for (int i = 0; i < sk_X509_num(certs); i++) {
+ X509 *c = sk_X509_value(certs, i);
+ X509_STORE_add_cert(store, c);
+ }
+
+ struct cert_s *c = tlsuv__calloc(1, sizeof(*c));
+ cert_init(c);
+ c->cert = store;
+ *chain = (tlsuv_certificate_t)c;
+
+ PKCS7_free(pkcs7);
+ BIO_free(buf);
+ return 0;
+}
+
+static int generate_csr(tlsuv_private_key_t key, char **pem, size_t *pemlen, ...) {
+ struct priv_key_s *privkey = (struct priv_key_s *)key;
+ int ret = 0;
+ const char *op = NULL;
+ EVP_PKEY *pk = privkey->pkey;
+ X509_REQ *req = X509_REQ_new();
+ X509_NAME *subj = X509_REQ_get_subject_name(req);
+ BIO *b = BIO_new(BIO_s_mem());
+
+ va_list va;
+ va_start(va, pemlen);
+ while (true) {
+ char *id = va_arg(va, char *);
+ if (id == NULL) break;
+ const uint8_t *val = va_arg(va, uint8_t *);
+ if (val == NULL) break;
+ X509_NAME_add_entry_by_txt(subj, id, MBSTRING_ASC, val, -1, -1, 0);
+ }
+ va_end(va);
+
+#define ssl_check(OP) do { \
+ op = #OP; \
+ if ((OP) == 0) { \
+ ret = ERR_get_error(); \
+ goto on_error; \
+ } \
+} while (0)
+
+ ssl_check(X509_REQ_set_pubkey(req, pk));
+ ssl_check(X509_REQ_sign(req, pk, EVP_sha256()));
+ ssl_check(PEM_write_bio_X509_REQ(b, req));
+
+on_error:
+ if (ret) {
+ UM_LOG(WARN, "%s => %s", op, tls_error(ret));
+ } else {
+ size_t len = BIO_ctrl_pending(b);
+ *pem = tlsuv__calloc(1, len + 1);
+ BIO_read(b, *pem, (int)len);
+ if (pemlen) *pemlen = len;
+ }
+
+ BIO_free(b);
+ X509_REQ_free(req);
+ return ret;
+}
+
+#if _WIN32
+#include
+#pragma comment (lib, "crypt32.lib")
+
+static X509_STORE *load_system_certs() {
+ X509_STORE *store = X509_STORE_new();
+ X509 *c;
+ HCERTSTORE hCertStore;
+ PCCERT_CONTEXT pCertContext = NULL;
+
+ if (!(hCertStore = CertOpenSystemStore(0, "ROOT"))) {
+ UM_LOG(ERR, "The first system store did not open.");
+ return store;
+ }
+
+ while ((pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext)) != NULL) {
+ c = d2i_X509(NULL, (const uint8_t **)&pCertContext->pbCertEncoded, (long)pCertContext->cbCertEncoded);
+ X509_STORE_add_cert(store, c);
+ }
+ CertFreeCertificateContext(pCertContext);
+ CertCloseStore(hCertStore, 0);
+
+ return store;
+}
+#endif
+
+static int engine_bio_write(BIO *b, const char *data, int len) {
+ struct boringssl_engine *e = BIO_get_data(b);
+ assert(e);
+ assert(e->write_f);
+
+ ssize_t r = e->write_f(e->io, data, (size_t)len);
+ if (r > 0) {
+ return (int)r;
+ }
+ if (r == 0) {
+ BIO_set_retry_write(b);
+ }
+ return -1;
+}
+
+static int engine_bio_read(BIO *b, char *data, int len) {
+ struct boringssl_engine *e = BIO_get_data(b);
+ assert(e);
+ assert(e->read_f);
+
+ ssize_t r = e->read_f(e->io, data, (size_t)len);
+ if (r > 0) {
+ return (int)r;
+ }
+ if (r == 0) {
+ BIO_set_retry_read(b);
+ }
+ return -1;
+}
+
+static int engine_bio_puts(BIO *b, const char *str) {
+ return engine_bio_write(b, str, (int)strlen(str));
+}
+
+static long engine_bio_ctrl(BIO *b, int cmd, long num, void *ptr) {
+ (void)b;
+ (void)num;
+ (void)ptr;
+ switch (cmd) {
+ case BIO_CTRL_FLUSH:
+ case BIO_CTRL_DGRAM_QUERY_MTU:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int engine_bio_create(BIO *b) {
+ BIO_set_init(b, 0);
+ BIO_set_data(b, NULL);
+ return 1;
+}
+
+static int engine_bio_destroy(BIO *b) {
+ if (b == NULL) return 0;
+ BIO_set_data(b, NULL);
+ BIO_set_init(b, 0);
+ return 1;
+}
+
+static BIO_METHOD *BIO_s_engine(void) {
+ static BIO_METHOD *engine = NULL;
+ if (engine == NULL) {
+ engine = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "tlsuv-engine");
+ BIO_meth_set_write(engine, engine_bio_write);
+ BIO_meth_set_read(engine, engine_bio_read);
+ BIO_meth_set_puts(engine, engine_bio_puts);
+ BIO_meth_set_ctrl(engine, engine_bio_ctrl);
+ BIO_meth_set_create(engine, engine_bio_create);
+ BIO_meth_set_destroy(engine, engine_bio_destroy);
+ }
+ return engine;
+}
diff --git a/vendor/packagefiles/tlsuv/src/boringssl/keys.c b/vendor/packagefiles/tlsuv/src/boringssl/keys.c
new file mode 100644
index 0000000..6ea25f8
--- /dev/null
+++ b/vendor/packagefiles/tlsuv/src/boringssl/keys.c
@@ -0,0 +1,386 @@
+#define OPENSSL_SUPPRESS_DEPRECATED
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "../alloc.h"
+#include "../um_debug.h"
+#include "keys.h"
+
+static const char *cert_to_text(const struct tlsuv_certificate_s *cert);
+static int cert_to_pem(const struct tlsuv_certificate_s *cert, int full, char **pem, size_t *pemlen);
+static void cert_free(tlsuv_certificate_t cert);
+static int cert_verify(const struct tlsuv_certificate_s *cert, enum hash_algo md, const char *data, size_t datalen, const char *sig, size_t siglen);
+static int cert_exp(const struct tlsuv_certificate_s *cert, struct tm *time);
+
+static struct cert_s cert_API = {
+ .free = cert_free,
+ .verify = cert_verify,
+ .to_pem = cert_to_pem,
+ .get_text = cert_to_text,
+ .get_expiration = cert_exp,
+};
+
+static int pubkey_to_pem(tlsuv_public_key_t pub, char **pem, size_t *pemlen);
+static void pubkey_free(tlsuv_public_key_t k);
+static int pubkey_verify(tlsuv_public_key_t pk, enum hash_algo md, const char *data, size_t datalen, const char *sig, size_t siglen);
+
+static struct pub_key_s PUB_KEY_API = {
+ .free = pubkey_free,
+ .verify = pubkey_verify,
+ .to_pem = pubkey_to_pem,
+};
+
+static void privkey_free(tlsuv_private_key_t k);
+static tlsuv_public_key_t privkey_pubkey(tlsuv_private_key_t pk);
+static int privkey_to_pem(tlsuv_private_key_t pk, char **pem, size_t *pemlen);
+static int privkey_sign(tlsuv_private_key_t pk, enum hash_algo md, const char *data, size_t datalen, char *sig, size_t *siglen);
+static int privkey_get_cert(tlsuv_private_key_t pk, tlsuv_certificate_t *cert);
+static int privkey_store_cert(tlsuv_private_key_t pk, tlsuv_certificate_t cert);
+
+static struct priv_key_s PRIV_KEY_API = {
+ .free = privkey_free,
+ .to_pem = privkey_to_pem,
+ .pubkey = privkey_pubkey,
+ .sign = privkey_sign,
+ .get_certificate = privkey_get_cert,
+ .store_certificate = privkey_store_cert,
+};
+
+static tlsuv_private_key_t new_private_key(EVP_PKEY *pkey) {
+ struct priv_key_s *private_key = tlsuv__calloc(1, sizeof(struct priv_key_s));
+ *private_key = PRIV_KEY_API;
+ private_key->pkey = pkey;
+ return (tlsuv_private_key_t)private_key;
+}
+
+void pub_key_init(struct pub_key_s *pubkey) {
+ *pubkey = PUB_KEY_API;
+}
+
+void cert_init(struct cert_s *c) {
+ *c = cert_API;
+}
+
+static void pubkey_free(tlsuv_public_key_t k) {
+ struct pub_key_s *pub = (struct pub_key_s *)k;
+ EVP_PKEY_free(pub->pkey);
+ tlsuv__free(pub);
+}
+
+static int verify_ecdsa_sig(EC_KEY *ec, const EVP_MD *hash, const char *data, size_t datalen, const char *sig, size_t siglen) {
+ int rc;
+ ECDSA_SIG *ecdsa_sig = NULL;
+ EVP_MD_CTX *digestor = EVP_MD_CTX_new();
+ uint8_t digest[EVP_MAX_MD_SIZE];
+ unsigned int digest_len;
+
+ EVP_DigestInit(digestor, hash);
+ EVP_DigestUpdate(digestor, data, datalen);
+ rc = EVP_DigestFinal(digestor, digest, &digest_len);
+
+ if (rc == 1) {
+ BIGNUM *r = BN_bin2bn((const uint8_t *)sig, (int)(siglen / 2), NULL);
+ BIGNUM *s = BN_bin2bn((const uint8_t *)sig + siglen / 2, (int)(siglen / 2), NULL);
+
+ ecdsa_sig = ECDSA_SIG_new();
+ ECDSA_SIG_set0(ecdsa_sig, r, s);
+ rc = ECDSA_do_verify(digest, (int)digest_len, ecdsa_sig, ec);
+ }
+
+ ECDSA_SIG_free(ecdsa_sig);
+ EVP_MD_CTX_free(digestor);
+ return rc == 1 ? 0 : -1;
+}
+
+int verify_signature(EVP_PKEY *pk, enum hash_algo md, const char *data, size_t datalen, const char *sig, size_t siglen) {
+ const EVP_MD *hash = NULL;
+ switch (md) {
+ case hash_SHA256: hash = EVP_sha256(); break;
+ case hash_SHA384: hash = EVP_sha384(); break;
+ case hash_SHA512: hash = EVP_sha512(); break;
+ default: break;
+ }
+
+ EVP_MD_CTX *digestor = EVP_MD_CTX_new();
+ if (EVP_DigestVerifyInit(digestor, NULL, hash, NULL, pk) != 1 ||
+ EVP_DigestVerifyUpdate(digestor, data, datalen) != 1) {
+ UM_LOG(WARN, "failed to create digest: %s", tls_error(ERR_get_error()));
+ EVP_MD_CTX_free(digestor);
+ return -1;
+ }
+
+ int rc = EVP_DigestVerifyFinal(digestor, (const uint8_t *)sig, siglen);
+ EVP_MD_CTX_free(digestor);
+
+ if (rc != 1 && EVP_PKEY_id(pk) == EVP_PKEY_EC) {
+ const uint8_t *p = (const uint8_t *)sig;
+ ECDSA_SIG *ecdsa_sig = d2i_ECDSA_SIG(NULL, &p, (int)siglen);
+ if (ecdsa_sig == NULL) {
+ EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pk);
+ int verified = verify_ecdsa_sig(ec, hash, data, datalen, sig, siglen);
+ EC_KEY_free(ec);
+ return verified;
+ }
+ ECDSA_SIG_free(ecdsa_sig);
+ }
+
+ return rc == 1 ? 0 : -1;
+}
+
+static int pubkey_verify(tlsuv_public_key_t pk, enum hash_algo md, const char *data, size_t datalen, const char *sig, size_t siglen) {
+ struct pub_key_s *pub = (struct pub_key_s *)pk;
+ return verify_signature(pub->pkey, md, data, datalen, sig, siglen);
+}
+
+static void privkey_free(tlsuv_private_key_t k) {
+ struct priv_key_s *priv = (struct priv_key_s *)k;
+ EVP_PKEY_free(priv->pkey);
+ tlsuv__free(priv);
+}
+
+static int privkey_sign(tlsuv_private_key_t pk, enum hash_algo md, const char *data, size_t datalen, char *sig, size_t *siglen) {
+ struct priv_key_s *priv = (struct priv_key_s *)pk;
+ int rc = 0;
+ EVP_MD_CTX *digest = EVP_MD_CTX_new();
+ EVP_PKEY_CTX *pctx = NULL;
+
+ const EVP_MD *hash = NULL;
+ switch (md) {
+ case hash_SHA256: hash = EVP_sha256(); break;
+ case hash_SHA384: hash = EVP_sha384(); break;
+ case hash_SHA512: hash = EVP_sha512(); break;
+ default: break;
+ }
+
+ if ((EVP_DigestSignInit(digest, &pctx, hash, NULL, priv->pkey) != 1) ||
+ (EVP_DigestSignUpdate(digest, data, datalen) != 1)) {
+ unsigned long err = ERR_get_error();
+ UM_LOG(WARN, "failed to setup digest %ld/%s", err, ERR_lib_error_string(err));
+ rc = -1;
+ }
+
+ if (rc == 0 && EVP_DigestSignFinal(digest, (uint8_t *)sig, siglen) != 1) {
+ unsigned long err = ERR_get_error();
+ UM_LOG(WARN, "failed to sign digest %ld/%s", err, ERR_lib_error_string(err));
+ rc = -1;
+ }
+
+ EVP_MD_CTX_free(digest);
+ return rc;
+}
+
+static tlsuv_public_key_t privkey_pubkey(tlsuv_private_key_t pk) {
+ struct priv_key_s *priv = (struct priv_key_s *)pk;
+ struct pub_key_s *pub = tlsuv__calloc(1, sizeof(*pub));
+ pub_key_init(pub);
+
+ BIO *bio = BIO_new(BIO_s_mem());
+ PEM_write_bio_PUBKEY(bio, priv->pkey);
+ pub->pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
+ BIO_free_all(bio);
+
+ return (tlsuv_public_key_t)pub;
+}
+
+static int privkey_to_pem(tlsuv_private_key_t pk, char **pem, size_t *pemlen) {
+ BIO *b = BIO_new(BIO_s_mem());
+ struct priv_key_s *privkey = (struct priv_key_s *)pk;
+
+ *pem = NULL;
+ *pemlen = 0;
+
+ if (!PEM_write_bio_PKCS8PrivateKey(b, privkey->pkey, NULL, NULL, 0, NULL, NULL)) {
+ unsigned long err = ERR_get_error();
+ UM_LOG(WARN, "failed to generate PEM for private key: %ld/%s", err, ERR_lib_error_string(err));
+ } else {
+ size_t len = BIO_ctrl_pending(b);
+ *pem = tlsuv__calloc(1, len + 1);
+ BIO_read(b, *pem, (int)len);
+ *pemlen = len;
+ }
+
+ BIO_free(b);
+ return *pem != NULL ? 0 : -1;
+}
+
+static int pubkey_to_pem(tlsuv_public_key_t pub, char **pem, size_t *pemlen) {
+ BIO *b = BIO_new(BIO_s_mem());
+ struct pub_key_s *pubkey = (struct pub_key_s *)pub;
+
+ *pem = NULL;
+ *pemlen = 0;
+
+ if (!PEM_write_bio_PUBKEY(b, pubkey->pkey)) {
+ unsigned long err = ERR_get_error();
+ UM_LOG(WARN, "failed to generate PEM for public key: %ld/%s", err, ERR_lib_error_string(err));
+ } else {
+ size_t len = BIO_ctrl_pending(b);
+ *pem = tlsuv__calloc(1, len + 1);
+ BIO_read(b, *pem, (int)len);
+ *pemlen = len;
+ }
+
+ BIO_free(b);
+ return *pem != NULL ? 0 : -1;
+}
+
+int load_key(tlsuv_private_key_t *key, const char *keydata, size_t keydatalen) {
+ BIO *kb;
+ int rc = 0;
+ FILE *kf = fopen(keydata, "r");
+ if (kf != NULL) {
+ kb = BIO_new_fp(kf, 1);
+ } else {
+ kb = BIO_new_mem_buf(keydata, (int)keydatalen);
+ }
+
+ EVP_PKEY *pk = NULL;
+ if (!PEM_read_bio_PrivateKey(kb, &pk, NULL, NULL)) {
+ unsigned long err = ERR_get_error();
+ UM_LOG(WARN, "failed to load key: %ld/%s", err, ERR_lib_error_string(err));
+ rc = -1;
+ } else {
+ *key = new_private_key(pk);
+ }
+
+ BIO_free(kb);
+ return rc;
+}
+
+int gen_key(tlsuv_private_key_t *key) {
+ int rc = 0;
+ EVP_PKEY *pk = EVP_PKEY_new();
+ EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
+ EVP_PKEY_keygen_init(pctx);
+ EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1);
+
+ if (!EVP_PKEY_keygen(pctx, &pk)) {
+ uint32_t err = ERR_get_error();
+ UM_LOG(ERR, "failed to generate key: %d(%s)", err, tls_error(err));
+ rc = -1;
+ EVP_PKEY_free(pk);
+ }
+
+ if (rc == 0) {
+ *key = new_private_key(pk);
+ }
+
+ EVP_PKEY_CTX_free(pctx);
+ return rc;
+}
+
+static int privkey_get_cert(tlsuv_private_key_t pk, tlsuv_certificate_t *cert) {
+ (void)pk;
+ (void)cert;
+ return -1;
+}
+
+static int privkey_store_cert(tlsuv_private_key_t pk, tlsuv_certificate_t cert) {
+ (void)pk;
+ (void)cert;
+ return -1;
+}
+
+static const char *cert_to_text(const struct tlsuv_certificate_s *cert) {
+ struct cert_s *c = (struct cert_s *)cert;
+
+ if (c->text) return c->text;
+
+ STACK_OF(X509_OBJECT) *s = X509_STORE_get0_objects(c->cert);
+ X509 *x509 = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(s, 0));
+ BIO *bio = BIO_new(BIO_s_mem());
+ X509_print_ex(bio, x509, 0, X509_FLAG_NO_HEADER | X509_FLAG_NO_SIGDUMP | X509_FLAG_NO_SIGNAME);
+
+ int len = BIO_pending(bio);
+ c->text = tlsuv__malloc((size_t)len + 1);
+ BIO_read(bio, c->text, len);
+ c->text[len] = '\0';
+ BIO_free(bio);
+
+ return c->text;
+}
+
+static void cert_free(tlsuv_certificate_t cert) {
+ struct cert_s *c = (struct cert_s *)cert;
+ if (c->cert != NULL) {
+ X509_STORE_free(c->cert);
+ }
+ tlsuv__free(c->text);
+ tlsuv__free(c);
+}
+
+static int cert_to_pem(const struct tlsuv_certificate_s *cert, int full_chain, char **pem, size_t *pemlen) {
+ X509_STORE *store = ((struct cert_s *)cert)->cert;
+ BIO *pembio = BIO_new(BIO_s_mem());
+ STACK_OF(X509_OBJECT) *s = X509_STORE_get0_objects(store);
+
+ for (int i = 0; i < sk_X509_OBJECT_num(s); i++) {
+ X509 *c = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(s, i));
+ PEM_write_bio_X509(pembio, c);
+ if (!full_chain) {
+ break;
+ }
+ }
+
+ *pemlen = BIO_ctrl_pending(pembio);
+ *pem = tlsuv__calloc(1, *pemlen + 1);
+ BIO_read(pembio, *pem, (int)*pemlen);
+ BIO_free_all(pembio);
+ return 0;
+}
+
+static int cert_verify(const struct tlsuv_certificate_s *cert, enum hash_algo md, const char *data, size_t datalen, const char *sig, size_t siglen) {
+ X509_STORE *store = ((struct cert_s *)cert)->cert;
+ STACK_OF(X509_OBJECT) *s = X509_STORE_get0_objects(store);
+ X509 *c = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(s, 0));
+ EVP_PKEY *pk = X509_get_pubkey(c);
+ if (pk == NULL) {
+ unsigned long err = ERR_peek_error();
+ UM_LOG(WARN, "no pub key: %ld/%s", err, ERR_lib_error_string(err));
+ return -1;
+ }
+
+ int rc = verify_signature(pk, md, data, datalen, sig, siglen);
+ EVP_PKEY_free(pk);
+ return rc;
+}
+
+static int cert_exp(const struct tlsuv_certificate_s *cert, struct tm *time) {
+ if (time == NULL || cert == NULL) {
+ return -1;
+ }
+
+ X509_STORE *store = ((struct cert_s *)cert)->cert;
+ STACK_OF(X509_OBJECT) *s = X509_STORE_get0_objects(store);
+ X509 *c = X509_OBJECT_get0_X509(sk_X509_OBJECT_value(s, 0));
+ const ASN1_TIME *notAfter = X509_get0_notAfter(c);
+ ASN1_GENERALIZEDTIME *gt = ASN1_TIME_to_generalizedtime(notAfter, NULL);
+ if (gt == NULL) {
+ return -1;
+ }
+
+ const char *v = (const char *)ASN1_STRING_get0_data((const ASN1_STRING *)gt);
+ size_t len = (size_t)ASN1_STRING_length((const ASN1_STRING *)gt);
+ if (v == NULL || len < 14) {
+ ASN1_GENERALIZEDTIME_free(gt);
+ return -1;
+ }
+
+ memset(time, 0, sizeof(*time));
+ time->tm_year = (v[0] - '0') * 1000 + (v[1] - '0') * 100 + (v[2] - '0') * 10 + (v[3] - '0') - 1900;
+ time->tm_mon = ((v[4] - '0') * 10 + (v[5] - '0')) - 1;
+ time->tm_mday = (v[6] - '0') * 10 + (v[7] - '0');
+ time->tm_hour = (v[8] - '0') * 10 + (v[9] - '0');
+ time->tm_min = (v[10] - '0') * 10 + (v[11] - '0');
+ time->tm_sec = (v[12] - '0') * 10 + (v[13] - '0');
+
+ ASN1_GENERALIZEDTIME_free(gt);
+ return 0;
+}
diff --git a/vendor/packagefiles/tlsuv/src/boringssl/keys.h b/vendor/packagefiles/tlsuv/src/boringssl/keys.h
new file mode 100644
index 0000000..dcab8aa
--- /dev/null
+++ b/vendor/packagefiles/tlsuv/src/boringssl/keys.h
@@ -0,0 +1,31 @@
+#ifndef TLSUV_BORINGSSL_KEYS_H
+#define TLSUV_BORINGSSL_KEYS_H
+
+#include
+
+struct cert_s {
+ TLSUV_CERT_API
+ X509_STORE *cert;
+ char *text;
+};
+
+struct pub_key_s {
+ TLSUV_PUBKEY_API
+ EVP_PKEY *pkey;
+};
+
+struct priv_key_s {
+ TLSUV_PRIVKEY_API
+ EVP_PKEY *pkey;
+};
+
+const char *tls_error(unsigned long code);
+
+void pub_key_init(struct pub_key_s *pubkey);
+void cert_init(struct cert_s *c);
+
+int gen_key(tlsuv_private_key_t *key);
+int load_key(tlsuv_private_key_t *key, const char *keydata, size_t keydatalen);
+int verify_signature(EVP_PKEY *pk, enum hash_algo md, const char *data, size_t datalen, const char *sig, size_t siglen);
+
+#endif
diff --git a/vendor/tlsuv.wrap b/vendor/tlsuv.wrap
index c316f76..d416d6d 100644
--- a/vendor/tlsuv.wrap
+++ b/vendor/tlsuv.wrap
@@ -3,6 +3,7 @@ url = https://github.com/openziti/tlsuv.git
revision = v0.40.13
depth = 1
patch_directory = tlsuv
+diff_files = patches/tlsuv-boringssl.patch
[provide]
tlsuv = tlsuv_dep