Jelajahi Sumber

ci: add best-effort BoringSSL job (#2456)

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.
yhirose 4 bulan lalu
induk
melakukan
0f3d063f0a
3 mengubah file dengan 164 tambahan dan 0 penghapusan
  1. 149 0
      .github/workflows/test.yaml
  2. 3 0
      README.md
  3. 12 0
      test/test.cc

+ 149 - 0
.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
+        # <openssl/base.h>, 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 (<openssl/stack.h>) 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.

+ 3 - 0
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

+ 12 - 0
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
+  // <openssl/base.h>, 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)