From c370635fc4fb875220ff4f83abb330d79a145fda Mon Sep 17 00:00:00 2001 From: Vladimir Dubrovin <3proxy@3proxy.ru> Date: Fri, 31 Jul 2026 11:27:10 +0300 Subject: [PATCH] fix wolfSSL, switch minimal/busybox to musl+wolfssl --- .dockerignore | 73 +++++++++++++++++++++++++++++++ .github/workflows/docker-test.yml | 35 +++++++++++++++ .gitignore | 3 +- Dockerfile.busybox | 35 ++++++++++----- Dockerfile.full | 1 + Dockerfile.minimal | 30 ++++++++++--- src/libs/md4.h | 7 +++ src/libs/md5.h | 7 +++ src/ssl.c | 50 ++++++++++++++++++--- src/ssllib.c | 73 +++++++++++++++++++------------ 10 files changed, 262 insertions(+), 52 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/docker-test.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..97a1cba --- /dev/null +++ b/.dockerignore @@ -0,0 +1,73 @@ +# Exclude top-level dirs not needed for build +/bin/ +/bin64/ +/build/ +/cfg/ +/cmake/ +/CMakeFiles/ +/debian/ +/doc/ +/man/ + +# Build artifacts +**/*.o +**/*.obj +**/*.so +**/*.dll +**/*.exe +**/*.exp +**/*.lib +**/*.pdb +**/*.ilk +**/*.idb +**/*.err +**/*.ld.so +**/*.dSYM +**/*.var +build*/ +buildlinux.sh +tmp/ + +# macOS +.DS_Store + +# Editor / IDE +**/*.swp +**/*.swo +**/*~ +**/*.bak +**/*.tmp +**/*.log +.project +.cproject +.classpath +.settings/ +.vscode/ +.idea/ + +# Git +.git +.gitignore + +# Claude / docs +CLAUDE.md +.claude/ + +# Generated version files +version +version.c +version.sh +verfile.sh + +# Local config / keys +**/*.key +**/*.pem +**/*.pfx +3proxy.cfg +3proxy-ssl.cfg +3proxy-pcre.cfg + +# Misc +res +3proxy.res +copytgz.sh diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml new file mode 100644 index 0000000..028a33f --- /dev/null +++ b/.github/workflows/docker-test.yml @@ -0,0 +1,35 @@ +name: Build Docker images (test, no publish) + +on: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Build ${{ matrix.file }} (${{ matrix.platform }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + file: [Dockerfile.minimal, Dockerfile.busybox, Dockerfile.full] + platform: [linux/amd64, linux/arm64] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build image + uses: docker/build-push-action@v6 + with: + context: . + file: ${{ matrix.file }} + platforms: ${{ matrix.platform }} + push: false + load: ${{ matrix.platform == 'linux/amd64' }} diff --git a/.gitignore b/.gitignore index f06a233..6581395 100644 --- a/.gitignore +++ b/.gitignore @@ -270,4 +270,5 @@ bin/3proxy_socks bin/3proxy_tcppm bin/3proxy_tlspr bin/3proxy_udppm -build*/* \ No newline at end of file +build*/* +testcerts/ diff --git a/Dockerfile.busybox b/Dockerfile.busybox index af1ffab..cdc938f 100644 --- a/Dockerfile.busybox +++ b/Dockerfile.busybox @@ -1,4 +1,5 @@ -# 3proxy.full is fully functional 3proxy build based on busybox:glibc +# 3proxy.busybox is fully functional 3proxy build based on busybox:musl +# built against wolfSSL and PCRE2. # # Examples are for podman, for docker change 'podman' to 'docker' # @@ -19,11 +20,27 @@ # configuration is supported for compatibility only. -FROM docker.io/gcc AS buildenv +FROM docker.io/alpine:latest AS wolfssl +RUN apk add --no-cache build-base curl autoconf automake libtool +RUN TAG=$(curl -s https://api.github.com/repos/wolfSSL/wolfssl/releases/latest \ + | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p') && \ + echo "wolfssl tag: $TAG" && \ + curl -sL https://api.github.com/repos/wolfSSL/wolfssl/tarball/$TAG -o /tmp/w.tar.gz && \ + mkdir /src && tar -xzf /tmp/w.tar.gz -C /src --strip-components=1 && \ + cd /src && \ + autoreconf -i && \ + ./configure --enable-opensslextra --enable-opensslall --enable-certgen \ + --enable-tls13 --enable-sni --enable-session-ticket \ + --enable-static --disable-shared --prefix=/usr/local && \ + make -j$(nproc) && make install + +FROM docker.io/alpine:latest AS buildenv +RUN apk add --no-cache gcc make musl-dev pcre2-dev +COPY --from=wolfssl /usr/local /usr/local COPY . 3proxy -RUN cd 3proxy &&\ - apt --assume-yes update && apt --assume-yes install libssl-dev libpcre2-dev &&\ - make -f Makefile.Linux LIBSTATIC=true PAM_CHECK=false &&\ +RUN cd 3proxy && mkdir -p bin &&\ + make -f Makefile.Linux LIBSTATIC=true PAM_CHECK=false WOLFSSL_CHECK=true \ + LDFLAGS="-L/usr/local/lib" &&\ strip bin/3proxy &&\ strip bin/*so &&\ mkdir /dist &&\ @@ -42,11 +59,9 @@ RUN cd /dist &&\ ln -s /lib lib64 &&\ ln -s /lib usr/lib &&\ ln -s /lib usr/lib64 -RUN cp /lib/ld-*.so.* /dist/usr/local/3proxy/libexec || true -RUN cp /lib64/ld-*.so.* /dist/usr/local/3proxy/libexec || true -RUN cp "/lib/`gcc -dumpmachine`"/libdl.so.* /dist/usr/local/3proxy/libexec +RUN cp /lib/ld-musl-*.so.* /dist/usr/local/3proxy/libexec || true -FROM docker.io/busybox:glibc +FROM docker.io/busybox:musl COPY --from=buildenv /dist / -RUN ln -sf /usr/local/3proxy/libexec/* /lib/ && cd /usr/local/3proxy/ && ln -s libexec lib && ln -s libexec lib64 && mkdir usr && ln -s libexec usr/lib && ln -s libexec usr//lib64 +RUN mkdir -p /lib /lib64 && ln -sf /usr/local/3proxy/libexec/* /lib/ && cd /usr/local/3proxy/ && ln -s libexec lib && ln -s libexec lib64 && mkdir usr && ln -s libexec usr/lib && ln -s libexec usr/lib64 CMD ["/bin/3proxy", "/etc/3proxy/3proxy.cfg"] diff --git a/Dockerfile.full b/Dockerfile.full index 33f6f51..2cf184d 100644 --- a/Dockerfile.full +++ b/Dockerfile.full @@ -23,6 +23,7 @@ FROM docker.io/gcc AS buildenv COPY . 3proxy RUN cd 3proxy &&\ apt --assume-yes update && apt --assume-yes install libssl-dev libpcre2-dev &&\ + mkdir -p bin &&\ make -f Makefile.Linux LIBSTATIC=true PAM_CHECK=false &&\ strip bin/3proxy &&\ mkdir /dist &&\ diff --git a/Dockerfile.minimal b/Dockerfile.minimal index 7c013a1..549f2c0 100644 --- a/Dockerfile.minimal +++ b/Dockerfile.minimal @@ -3,7 +3,8 @@ # # Examples are for podman. For docker change 'podman' to 'docker'. # -# This is busybox based docker with only 3proxy static executable. +# This is a scratch-based docker with statically linked 3proxy executable +# built against musl and wolfSSL. # # Limitations for minimal version: # no support for plugins and system resolver. @@ -15,7 +16,7 @@ # # Run example: # -# podman run --read-only -i -p 3129:3129 --name 3proxy 3proxy.minimal +# podman run --read-only -i -p 3129:3129 --name 3proxy 3proxy.minimal #or # podman start -ai 3proxy #server_min_proto_version)SSL_CTX_set_min_proto_version(ctx, config->server_min_proto_version); @@ -655,6 +680,10 @@ static void* ssl_filter_open(void * idata, struct srvparam * srv){ fprintf(stderr, "failed to read server cert: %s\n", srvcert); return sc; } + if(ssl_ctx_use_pkey(sc->cli_ctx, sc->server_key) <= 0){ + fprintf(stderr, "failed to use server key\n"); + return sc; + } sc->serv = 1; } if(mitm || cli || serv){ @@ -678,7 +707,7 @@ static void* ssl_filter_open(void * idata, struct srvparam * srv){ } if(sc->client_cert){ SSL_CTX_use_certificate(sc->srv_ctx, (X509 *) sc->client_cert); - SSL_CTX_use_PrivateKey(sc->srv_ctx, sc->client_key); + ssl_ctx_use_pkey(sc->srv_ctx, sc->client_key); } if(sc->client_min_proto_version)SSL_CTX_set_min_proto_version(sc->srv_ctx, sc->client_min_proto_version); if(sc->client_max_proto_version)SSL_CTX_set_max_proto_version(sc->srv_ctx, sc->client_max_proto_version); @@ -705,6 +734,15 @@ static void* ssl_filter_open(void * idata, struct srvparam * srv){ SSL_CTX_set_default_verify_paths(sc->srv_ctx); SSL_CTX_set_verify(sc->srv_ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); } +#ifdef WITH_WOLFSSL + else { + /* wolfSSL defaults to peer verification; OpenSSL defaults to + * SSL_VERIFY_NONE. Make the no-verify intent explicit so the + * upstream handshake (ssl_cli / ssl_mitm) succeeds without a + * trusted CA store. */ + SSL_CTX_set_verify(sc->srv_ctx, SSL_VERIFY_NONE, NULL); + } +#endif } #ifdef WITHSPLICE srv->usesplice = 0; diff --git a/src/ssllib.c b/src/ssllib.c index 9b67d8e..1d46cb3 100644 --- a/src/ssllib.c +++ b/src/ssllib.c @@ -45,8 +45,6 @@ static char hexMap[] = { '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; -static BIO *bio_err=NULL; - char * getSSLErr(){ @@ -94,26 +92,14 @@ static int add_ext(X509 *cert, int nid, char *value) return 1; } -void del_ext(X509 *dst_cert, int nid, int where){ - int ex; - - ex = X509_get_ext_by_NID(dst_cert, nid, where); - if(ex>=0){ - X509_EXTENSION *ext; - if((ext = X509_delete_ext(dst_cert, ex))) X509_EXTENSION_free(ext); - } - -} - SSL_CERT ssl_copy_cert(SSL_CERT cert, SSL_CONFIG *config) { int err = -1; + int san_idx; BIO *fcache; X509 *src_cert = (X509 *) cert; X509 *dst_cert = NULL; - EVP_PKEY *pk = NULL; - unsigned char hash_sha256[32]; char hash_name_sha256[(16*2) + 1]; char cache_name[256]; @@ -142,27 +128,35 @@ SSL_CERT ssl_copy_cert(SSL_CERT cert, SSL_CONFIG *config) } } } - /* proceed if certificate is not cached */ - dst_cert = X509_dup(src_cert); + /* Build a fresh certificate instead of duplicating the source: only + * the fields required for a usable server cert are copied (version, + * serial, subject, validity, SAN). This avoids inheriting upstream + * extensions (AKI, CRL dist points, certificate policies, ...) that + * break chain validation, and works around wolfSSL's no-op + * X509_delete_ext compat shim. */ + dst_cert = X509_new(); if ( dst_cert == NULL ) { return NULL; } - del_ext(dst_cert, NID_crl_distribution_points, -1); - del_ext(dst_cert, NID_info_access, -1); - del_ext(dst_cert, NID_authority_key_identifier, -1); - del_ext(dst_cert, NID_certificate_policies, 0); - + X509_set_version(dst_cert, X509_get_version(src_cert)); + X509_set_serialNumber(dst_cert, X509_get_serialNumber(src_cert)); + if(!X509_set_subject_name(dst_cert, X509_get_subject_name(src_cert)) + || !X509_set_issuer_name(dst_cert, X509_get_subject_name(config->CA_cert))){ + X509_free(dst_cert); + return NULL; + } err = X509_set_pubkey(dst_cert, config->server_key?config->server_key:config->CA_key); if ( err == 0 ) { X509_free(dst_cert); return NULL; } - - - err = X509_set_issuer_name(dst_cert, X509_get_subject_name(config->CA_cert)); - if(!err){ - X509_free(dst_cert); - return NULL; + X509_set_notBefore(dst_cert, X509_get_notBefore(src_cert)); + X509_set_notAfter(dst_cert, X509_get_notAfter(src_cert)); + san_idx = X509_get_ext_by_NID(src_cert, NID_subject_alt_name, -1); + if(san_idx >= 0){ + X509_EXTENSION *san; + san = X509_get_ext(src_cert, san_idx); + if(san) X509_add_ext(dst_cert, san, -1); } err = X509_sign(dst_cert, config->CA_key, EVP_sha256()); if(!err){ @@ -229,6 +223,14 @@ void _ssl_cert_free(SSL_CERT cert) +/* OpenSSL before 1.1.0 requires the application to install threading + callbacks; OpenSSL >= 1.1.0 and wolfSSL handle locking internally. */ +#if !defined(WITH_WOLFSSL) && defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L +#define LEGACY_SSL_THREADING 1 +#else +#define LEGACY_SSL_THREADING 0 +#endif + /* This array will store all of the mutexes available to OpenSSL. */ static _3proxy_mutex_t *mutex_buf= NULL; @@ -252,6 +254,7 @@ static unsigned long id_function(void) int thread_setup(void) { +#if LEGACY_SSL_THREADING int i; mutex_buf = malloc(CRYPTO_num_locks( ) * sizeof(_3proxy_mutex_t)); @@ -262,10 +265,14 @@ int thread_setup(void) CRYPTO_set_id_callback(id_function); CRYPTO_set_locking_callback(locking_function); return 1; +#else + return 1; +#endif } int thread_cleanup(void) { +#if LEGACY_SSL_THREADING int i; if (!mutex_buf) @@ -277,6 +284,9 @@ int thread_cleanup(void) free(mutex_buf); mutex_buf = NULL; return 1; +#else + return 1; +#endif } @@ -291,9 +301,14 @@ void ssl_init() ssl_init_done = 1; thread_setup(); +#ifdef WITH_WOLFSSL + wolfSSL_Init(); +#elif defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000L + OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL); +#else SSLeay_add_ssl_algorithms(); SSL_load_error_strings(); +#endif _3proxy_mutex_init(&ssl_file_mutex); - bio_err=BIO_new_fp(stderr,BIO_NOCLOSE); } }