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