mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-08-17 21:41:38 +02:00
The OcspRequest carried a "void* ssl" back-pointer that the stapling paths wrote just before handing the request to the OCSP layer. For the request cached on the WOLFSSL_CTX that field is shared by every connection using it, so concurrent handshakes raced on it. Drop the field and pass the connection to CheckOcspRequest() and CheckOcspResponse() as an argument instead, which is the only thing it was ever read for. Ownership of the cached request was equally implicit. Publication moves out of CreateOcspRequest() into CreateOcspResponse(), and callers now learn whether the CTX took ownership from a "ctxOwnsRequest" flag rather than by comparing pointers against ssl->ctx->certOcspRequest, which was read without the lock that guards it. The flag and the request are handed back together on success and both left untouched on failure, so a caller never decides ownership against a request it is not holding. The cache is a field of the WOLFSSL_CTX, so serialize it with a lock scoped to the CTX. SSL_CM(ssl) can resolve to a per-SSL cert manager when WOLFSSL_LOCAL_X509_STORE is defined, which left two connections on one CTX taking different locks for a check-then-set on the same pointer. GetCtxOcspLock() keys off ssl->ctx->cm for both the reader and the publisher, and a failure to take it is logged instead of silently disabling the cache. CheckOcspRequest() also loses its heap argument. It was only ever the hint for the response buffer it hands back, which the caller frees against the connection, so take it from the connection rather than from a parameter every caller had to keep in step with its own free. Smaller fixes in the same paths: zero the caller's response buffer before the argument check can return, since SendCertificateStatus() frees it without checking the return code; fold the ocsp_stapling NULL check into the single early skip so the later uses need no guard; gate the SetupOcspResp() free on success like the other two callers; split the three differently owned requests in the WOLFSSL_CSR2_OCSP_MULTI case into separate variables; and let that case's allocation failures fall through to its shared cleanup instead of returning, which leaked an already built leaf response. Add test_ocsp_ctx_request_cache, which runs three handshakes over one CTX pair and checks that the later ones reuse the cached request rather than building another. The responder callback answers with a canned good response, so stapling runs all the way through and the ownership decision each connection makes is actually acted on: a connection that freed the shared request shows up as a use after free on the next pass and a double free at CTX teardown. The cached request is marked before the last pass and the encoded request the callback sees is compared, since a request rebuilt from the same certificate would otherwise be identical byte for byte. The test is gated on !WOLFSSL_COPY_CERT: OPENSSL_ALL implies it, and it gives every WOLFSSL its own certificate copy, which takes the cache out of play. A new ocsp.yml job covers the plain stapling build, an --enable-all build with the copy turned back off, and an ASan build. Also gate test_tls13_pha_status_request on KEEP_PEER_CERT. It checks the received client certificate with wolfSSL_get_peer_certificate(), which is only built when that macro is defined, so a post-handshake auth build with stapling but without the OpenSSL compatibility layer failed to link tests/unit.test. Fixes F-7230 and F-7231.
151 lines
7.0 KiB
YAML
151 lines
7.0 KiB
YAML
name: OCSP Test
|
|
|
|
# START OF COMMON SECTION
|
|
on:
|
|
push:
|
|
branches: [ 'release/**' ]
|
|
pull_request:
|
|
types: [opened, synchronize, reopened, ready_for_review]
|
|
branches: [ '*' ]
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
# END OF COMMON SECTION
|
|
|
|
jobs:
|
|
ocsp_stapling:
|
|
name: ocsp stapling
|
|
if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 10
|
|
steps:
|
|
- name: Checkout wolfSSL
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Build wolfSSL
|
|
run: autoreconf -ivf && ./configure --enable-ocsp --enable-ocspstapling && make
|
|
|
|
- name: Start OCSP responder 1
|
|
run: openssl ocsp -port 22221 -ndays 1000 -index certs/ocsp/index-intermediate1-ca-issued-certs.txt -rsigner certs/ocsp/ocsp-responder-int1-cert.pem -rkey certs/ocsp/ocsp-responder-int1-key.pem -CA certs/ocsp/intermediate1-ca-cert.pem &
|
|
|
|
- name: Start OCSP responder 2
|
|
run: openssl ocsp -port 22220 -ndays 1000 -index certs/ocsp/index-ca-and-intermediate-cas.txt -rsigner certs/ocsp/ocsp-responder-cert.pem -rkey certs/ocsp/ocsp-responder-key.pem -CA certs/ocsp/root-ca-cert.pem &
|
|
|
|
- name: Start TLS server
|
|
run: ./examples/server/server -p 11111 -c ./certs/ocsp/server1-cert.pem -k ./certs/ocsp/server1-key.pem -d &
|
|
|
|
- name: Test Look Up
|
|
run: ./examples/client/client -A ./certs/ocsp/root-ca-cert.pem -o
|
|
|
|
ocsp_ssrf_screen:
|
|
name: ocsp responder SSRF screening
|
|
if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 10
|
|
steps:
|
|
- name: Checkout wolfSSL
|
|
uses: actions/checkout@v5
|
|
|
|
# Build with the opt-in OCSP responder destination screening enabled
|
|
# (WOLFSSL_OCSP_SCREEN_RESPONDER). This guards against a certificate AIA
|
|
# OCSP URL driving an outbound request to an internal address (SSRF,
|
|
# CWE-918). The screening is off by default, so it is not exercised by
|
|
# the ocsp_stapling job above (which uses localhost responders).
|
|
- name: Build wolfSSL with OCSP responder screening enabled
|
|
run: autoreconf -ivf && ./configure --enable-ocsp CPPFLAGS=-DWOLFSSL_OCSP_SCREEN_RESPONDER && make
|
|
|
|
# Run only the boundary unit test, not the localhost OCSP test scripts:
|
|
# with screening on, 127.0.0.1 responders are (correctly) rejected, so
|
|
# the stapling scripts do not apply to this build. Assert the test
|
|
# actually ran (passed) rather than being compiled out and skipped, so a
|
|
# future build-define change cannot turn this into a false-green signal.
|
|
- name: Run OCSP destination screening boundary tests
|
|
run: |
|
|
./tests/unit.test -test_wolfIO_OcspDestAllowed | tee out.txt
|
|
grep -Eq 'test_wolfIO_OcspDestAllowed[^_].*: passed' out.txt
|
|
|
|
# The leaf OCSP request built for stapling is cached on the WOLFSSL_CTX and
|
|
# reused by every later connection on it, with the CTX owning it. None of the
|
|
# jobs above reach that cache: it is only populated when the SSL shares the
|
|
# CTX certificate buffer (ssl->buffers.weOwnCert == 0), and OPENSSL_ALL
|
|
# implies WOLFSSL_COPY_CERT, which gives every SSL its own copy instead.
|
|
ocsp_ctx_request_cache:
|
|
name: ocsp ctx request cache (${{ matrix.name }})
|
|
if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 20
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
# Plain stapling build: no OPENSSL_ALL, so no WOLFSSL_COPY_CERT and
|
|
# the cache is live.
|
|
- name: default
|
|
config: --enable-ocsp --enable-ocspstapling --enable-ocspstapling2
|
|
# The same cache under --enable-all, which pulls in OPENSSL_ALL and
|
|
# with it the compatibility-layer code paths around the cert manager.
|
|
# OPENSSL_ALL would otherwise force WOLFSSL_COPY_CERT and take the
|
|
# cache out of play entirely, so that is turned back off explicitly -
|
|
# which is what this entry is really here to prove.
|
|
- name: all, no cert copy
|
|
config: --enable-all CPPFLAGS=-DWOLFSSL_NO_COPY_CERT
|
|
# The cache hands one OcspRequest to many connections, so the failure
|
|
# mode of an ownership mistake is a double free or a use after free at
|
|
# CTX teardown rather than a wrong answer. ASan is what turns that into
|
|
# a test failure.
|
|
- name: asan
|
|
config: --enable-ocsp --enable-ocspstapling --enable-ocspstapling2 CFLAGS='-fsanitize=address -g' LDFLAGS='-fsanitize=address'
|
|
steps:
|
|
- name: workaround high-entropy ASLR
|
|
# Needed for the ASan build on this runner image; harmless for the rest.
|
|
run: sudo sysctl vm.mmap_rnd_bits=28
|
|
|
|
- name: Checkout wolfSSL
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Build wolfSSL
|
|
run: autoreconf -ivf && ./configure ${{ matrix.config }} && make
|
|
|
|
# Assert on the counters rather than grepping the test name for "passed":
|
|
# the handshake under test logs to the same stream and splits the name and
|
|
# the result across lines. Running the one test on its own makes 0/0/1/1
|
|
# exact, and a build where the cache is compiled out reports 0/1/0/1
|
|
# instead - so a config change that quietly disables this cannot pass as
|
|
# green.
|
|
#
|
|
# Leak detection is off because wolfSSL's own unit.test has no verified
|
|
# clean LSan baseline; the double free and use after free this is here to
|
|
# catch are reported either way.
|
|
- name: Run the CTX OCSP request cache test
|
|
env:
|
|
ASAN_OPTIONS: detect_leaks=0
|
|
run: |
|
|
set -o pipefail
|
|
./tests/unit.test -test_ocsp_ctx_request_cache | tee out.txt
|
|
grep -Eq 'Failed/Skipped/Passed/All: 0/0/1/1' out.txt
|
|
|
|
ocsp_ssrf_screen_fallback:
|
|
name: ocsp responder SSRF screening (gethostbyname fallback)
|
|
if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 10
|
|
steps:
|
|
- name: Checkout wolfSSL
|
|
uses: actions/checkout@v5
|
|
|
|
# Force the gethostbyname() resolver fallback (ac_cv_func_getaddrinfo=no)
|
|
# so the otherwise-untested fallback path of wolfIO_OcspDestAllowed is
|
|
# exercised. The fallback is IPv4-only and relies on glibc parsing
|
|
# numeric IPv4 literals locally.
|
|
- name: Build wolfSSL forcing the gethostbyname resolver fallback
|
|
run: autoreconf -ivf && ./configure --enable-ocsp ac_cv_func_getaddrinfo=no CPPFLAGS=-DWOLFSSL_OCSP_SCREEN_RESPONDER && make
|
|
|
|
- name: Run OCSP destination screening fallback boundary tests
|
|
run: |
|
|
./tests/unit.test -test_wolfIO_OcspDestAllowed_fallback | tee out.txt
|
|
grep -Eq 'test_wolfIO_OcspDestAllowed_fallback.*: passed' out.txt
|