From 0f3d063f0a6580307e9a9ac2a7bc8e8e167a86fe Mon Sep 17 00:00:00 2001 From: yhirose Date: Sun, 24 May 2026 02:48:46 -0400 Subject: [PATCH] ci: add best-effort BoringSSL job (#2456) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds Ubuntu and macOS CI jobs that build BoringSSL from source and exercise cpp-httplib's existing OpenSSL backend path (continue-on-error: best-effort). Makes SSLClientServerTest.TlsVerifyHostname backend-aware (BoringSSL is SAN-only per RFC 6125 §6.4.4). README notes BoringSSL as a best-effort variant with the C++14 and SAN-only caveats. --- .github/workflows/test.yaml | 149 ++++++++++++++++++++++++++++++++++++ README.md | 3 + test/test.cc | 12 +++ 3 files changed, 164 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d0e41c3..ea50f94 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -120,6 +120,155 @@ jobs: - name: build and run ThreadPool test run: cd test && make test_thread_pool && ./test_thread_pool + # BoringSSL is Google's fork of OpenSSL. It has no API stability guarantee + # and is not packaged by distros, so we build it from source. cpp-httplib + # treats it as an OpenSSL backend variant via the OPENSSL_IS_BORINGSSL + # macro (see httplib.h). This job is best-effort: continue-on-error keeps + # upstream API drift from blocking PRs while still surfacing breakage. + ubuntu-boringssl: + runs-on: ubuntu-latest + if: > + (github.event_name == 'push') || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) || + (github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true') + continue-on-error: true + name: ubuntu (boringssl, best-effort) + env: + # Tracking HEAD keeps us honest about upstream churn. If breakage + # becomes routine, replace HEAD with a 40-char commit SHA; the + # resolve step uses the SHA directly when it matches that shape. + BORINGSSL_REF: HEAD + BORINGSSL_PREFIX: ${{ github.workspace }}/boringssl-install + steps: + - name: checkout + uses: actions/checkout@v4 + - name: install common libraries + run: | + sudo apt-get update + sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev + - name: resolve BoringSSL commit + id: boringssl-rev + # Accept either a ref name (resolved via git ls-remote) or a full + # 40-char SHA used directly. ls-remote does not list arbitrary + # commit SHAs, so pinning requires the second path. + run: | + if [[ "${BORINGSSL_REF}" =~ ^[0-9a-f]{40}$ ]]; then + sha="${BORINGSSL_REF}" + echo "Using pinned BoringSSL SHA: ${sha}" + else + sha=$(git ls-remote https://boringssl.googlesource.com/boringssl "${BORINGSSL_REF}" | awk '{print $1}') + if [ -z "$sha" ]; then + echo "Failed to resolve BoringSSL ref ${BORINGSSL_REF}" >&2 + exit 1 + fi + echo "Resolved ${BORINGSSL_REF} -> ${sha}" + fi + echo "sha=${sha}" >> "$GITHUB_OUTPUT" + - name: cache BoringSSL build + id: boringssl-cache + uses: actions/cache@v4 + with: + path: ${{ env.BORINGSSL_PREFIX }} + key: boringssl-${{ runner.os }}-${{ steps.boringssl-rev.outputs.sha }} + - name: build BoringSSL + if: steps.boringssl-cache.outputs.cache-hit != 'true' + run: | + set -e + git clone https://boringssl.googlesource.com/boringssl boringssl + cd boringssl + git checkout "${{ steps.boringssl-rev.outputs.sha }}" + cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_INSTALL_PREFIX="${BORINGSSL_PREFIX}" + cmake --build build -j"$(nproc)" --target install + - name: build and run tests (BoringSSL) + # Override OPENSSL_SUPPORT to point the existing OpenSSL Makefile path + # at BoringSSL's prefix. BoringSSL defines OPENSSL_IS_BORINGSSL in + # , which httplib.h and test.cc use to switch on API + # differences (e.g. SAN-only hostname verification, no CN fallback). + # + # BoringSSL's public headers () use std::enable_if_t, + # so consumers must compile with C++14 or later. cpp-httplib itself + # supports C++11, but anyone pairing it with BoringSSL inherits this + # constraint. EXTRA_CXXFLAGS appends after the Makefile's -std=c++11 + # and the later flag wins. + run: | + cd test + BORINGSSL_FLAGS="-DCPPHTTPLIB_OPENSSL_SUPPORT -I${BORINGSSL_PREFIX}/include -L${BORINGSSL_PREFIX}/lib -lssl -lcrypto -lpthread" + make test_split OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17" + make test_openssl_parallel OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17" + env: + LSAN_OPTIONS: suppressions=lsan_suppressions.txt + + # macOS counterpart of the BoringSSL job. Same best-effort posture; the + # extra framework links cover the macOS Keychain integration that + # httplib.h auto-enables for any TLS backend on macOS. + macos-boringssl: + runs-on: macos-latest + if: > + (github.event_name == 'push') || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) || + (github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true') + continue-on-error: true + name: macos (boringssl, best-effort) + env: + BORINGSSL_REF: HEAD + BORINGSSL_PREFIX: ${{ github.workspace }}/boringssl-install + steps: + - name: checkout + uses: actions/checkout@v4 + - name: resolve BoringSSL commit + id: boringssl-rev + # Accept either a ref name (resolved via git ls-remote) or a full + # 40-char SHA used directly. ls-remote does not list arbitrary + # commit SHAs, so pinning requires the second path. + run: | + if [[ "${BORINGSSL_REF}" =~ ^[0-9a-f]{40}$ ]]; then + sha="${BORINGSSL_REF}" + echo "Using pinned BoringSSL SHA: ${sha}" + else + sha=$(git ls-remote https://boringssl.googlesource.com/boringssl "${BORINGSSL_REF}" | awk '{print $1}') + if [ -z "$sha" ]; then + echo "Failed to resolve BoringSSL ref ${BORINGSSL_REF}" >&2 + exit 1 + fi + echo "Resolved ${BORINGSSL_REF} -> ${sha}" + fi + echo "sha=${sha}" >> "$GITHUB_OUTPUT" + - name: cache BoringSSL build + id: boringssl-cache + uses: actions/cache@v4 + with: + path: ${{ env.BORINGSSL_PREFIX }} + key: boringssl-${{ runner.os }}-${{ steps.boringssl-rev.outputs.sha }} + - name: build BoringSSL + if: steps.boringssl-cache.outputs.cache-hit != 'true' + run: | + set -e + git clone https://boringssl.googlesource.com/boringssl boringssl + cd boringssl + git checkout "${{ steps.boringssl-rev.outputs.sha }}" + cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_INSTALL_PREFIX="${BORINGSSL_PREFIX}" + cmake --build build -j"$(sysctl -n hw.ncpu)" --target install + - name: build and run tests (BoringSSL) + run: | + cd test + # CoreFoundation/Security frameworks satisfy the Keychain integration + # auto-enabled in httplib.h for macOS TLS builds. + BORINGSSL_FLAGS="-DCPPHTTPLIB_OPENSSL_SUPPORT -I${BORINGSSL_PREFIX}/include -L${BORINGSSL_PREFIX}/lib -lssl -lcrypto -framework CoreFoundation -framework Security" + make test_split OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17" + make test_openssl_parallel OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17" + env: + LSAN_OPTIONS: suppressions=lsan_suppressions.txt + # Reproducer for https://github.com/yhirose/cpp-httplib/issues/2431. # On Linux/glibc, getaddrinfo_with_timeout() schedules an asynchronous # DNS lookup with getaddrinfo_a(GAI_NOWAIT) using a stack-local gaicb. diff --git a/README.md b/README.md index f1fd63a..1037ab2 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,9 @@ cpp-httplib supports multiple TLS backends through an abstraction layer: > [!NOTE] > **Mbed TLS / wolfSSL limitation:** `get_ca_certs()` and `get_ca_names()` only reflect CA certificates loaded via `load_ca_cert_store()`. Certificates loaded through `set_ca_cert_path()` or system certificates (`load_system_certs`) are not enumerable. +> [!NOTE] +> **BoringSSL (best-effort):** BoringSSL builds under `CPPHTTPLIB_OPENSSL_SUPPORT` and is exercised by CI against current upstream. Because BoringSSL does not guarantee API stability, support is best-effort — breakage may occasionally land. Two known behavioral differences vs OpenSSL: (1) BoringSSL's public headers require C++14 or later, so consumers must compile accordingly; (2) hostname verification is SAN-only per RFC 6125 §6.4.4 (no CN fallback). + ```c++ // Use either OpenSSL, Mbed TLS, or wolfSSL #define CPPHTTPLIB_OPENSSL_SUPPORT // or CPPHTTPLIB_MBEDTLS_SUPPORT or CPPHTTPLIB_WOLFSSL_SUPPORT diff --git a/test/test.cc b/test/test.cc index f09bde9..e968322 100644 --- a/test/test.cc +++ b/test/test.cc @@ -10673,8 +10673,20 @@ TEST(SSLClientServerTest, TlsVerifyHostname) { << "Verify callback should have been called"; // CN="Common Name" should match our test certificate + // + // BoringSSL intentionally drops CN-based hostname matching per RFC 6125 + // §6.4.4 — only SubjectAltName is consulted. Other backends (OpenSSL, + // MbedTLS, wolfSSL) still honor the CN fallback, so flip the expectation + // for BoringSSL builds. OPENSSL_IS_BORINGSSL is defined by BoringSSL's + // , which is included transitively when + // CPPHTTPLIB_OPENSSL_SUPPORT is set against a BoringSSL install. +#if defined(OPENSSL_IS_BORINGSSL) + EXPECT_FALSE(verify_result_cn) + << "BoringSSL should reject CN-based hostname matching (SAN-only)"; +#else EXPECT_TRUE(verify_result_cn) << "verify_hostname should match 'Common Name' (certificate CN)"; +#endif // Wrong hostname should not match EXPECT_FALSE(verify_result_wrong)