From 731861b9de8dccaf7d3b0c1446833051e48670c2 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Thu, 12 Mar 2026 09:48:57 +0100
Subject: cert-session: fix multi-entry OCSP revocation bypass

In check_ocsp_response(), the code first searched
for the SingleResponse that matches the certificate being validated.
But later, the status was retrieved from entry 0 unconditionally,
rather than from the matched resp_indx.
As a result, if entry 0 corresponded to a different certificate and was good,
while the matched entry for the peer certificate is revoked,
the revocation check could've mistakenly accept the certificate.

Reported-by: Oleh Konko (1seal) <security@1seal.org>
Reported-by: Joshua Rogers of AISLE Research Team <joshua@joshua.hu>
Fixes: #1801
Fixes: #1812
Fixes: CVE-2026-3832
Fixes: GNUTLS-SA-2026-04-29-12
CVSS: 3.7 Low CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N
Introduced-in: ae404fe8488dee424876b5963c00d7e041672415 3.8.9
Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
 lib/cert-session.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

From 6a7999807d72bd2320d959092235fb06e751c332 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Thu, 12 Mar 2026 10:25:41 +0100
Subject: [PATCH] cert-session: log "no responses" case separately

Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
 lib/cert-session.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

From d52d5f4f383e8c5d8e9a03334f2421ff35d37d2e Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Thu, 12 Mar 2026 15:25:24 +0100
Subject: [PATCH] tests/ocsp-tests/ocsp-must-staple-connection: test
 CVE-2026-3832

Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
 .../ocsp-tests/ocsp-must-staple-connection.sh | 70 +++++++++++++++++++
 1 file changed, 70 insertions(+)

From f36276e1224719160584ae52398a0d2ceb670ac2 Mon Sep 17 00:00:00 2001
From: Alexander Sosedkin <asosedkin@redhat.com>
Date: Thu, 12 Mar 2026 10:57:14 +0100
Subject: [PATCH] tests/ocsp-tests/ocsp-must-staple-connection: no response
 case

Signed-off-by: Alexander Sosedkin <asosedkin@redhat.com>
---
 tests/Makefile.am                             |   4 +-
 tests/ocsp-tests/certs/ocsp-staple-empty.der  | Bin 0 -> 1202 bytes
 .../ocsp-tests/ocsp-must-staple-connection.sh |  45 ++++++++++++++++++
 3 files changed, 48 insertions(+), 1 deletion(-)
 create mode 100644 tests/ocsp-tests/certs/ocsp-staple-empty.der

Index: gnutls-3.8.10/lib/cert-session.c
===================================================================
--- gnutls-3.8.10.orig/lib/cert-session.c
+++ gnutls-3.8.10/lib/cert-session.c
@@ -283,10 +283,16 @@ static int check_ocsp_response(gnutls_se
 			break;
 	}
 	if (ret < 0) {
+		if (resp_indx == 0 &&
+		    ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+			_gnutls_audit_log(session, "Got OCSP response with"
+						   " no certificates.\n");
+		} else {
+			_gnutls_audit_log(session,
+					  "Got OCSP response with"
+					  " an unrelated certificate.\n");
+		}
 		ret = gnutls_assert_val(0);
-		_gnutls_audit_log(
-			session,
-			"Got OCSP response with an unrelated certificate.\n");
 		check_failed = 1;
 		*ostatus |= GNUTLS_CERT_INVALID;
 		*ostatus |= GNUTLS_CERT_INVALID_OCSP_STATUS;
@@ -343,9 +349,9 @@ static int check_ocsp_response(gnutls_se
 		goto cleanup;
 	}
 
-	ret = gnutls_ocsp_resp_get_single(resp, 0, NULL, NULL, NULL, NULL,
-					  &cert_status, &vtime, &ntime, &rtime,
-					  NULL);
+	ret = gnutls_ocsp_resp_get_single(resp, resp_indx, NULL, NULL, NULL,
+					  NULL, &cert_status, &vtime, &ntime,
+					  &rtime, NULL);
 	if (ret < 0) {
 		_gnutls_audit_log(
 			session,
Index: gnutls-3.8.10/tests/ocsp-tests/ocsp-must-staple-connection.sh
===================================================================
--- gnutls-3.8.10.orig/tests/ocsp-tests/ocsp-must-staple-connection.sh
+++ gnutls-3.8.10/tests/ocsp-tests/ocsp-must-staple-connection.sh
@@ -85,6 +85,7 @@ OCSP_RESPONSE_FILE="$testdir/ms-resp.tmp
 OCSP_REQ_FILE="$testdir/ms-req.tmp"
 INDEXFILE="$testdir/ocsp_index.txt"
 ATTRFILE="${INDEXFILE}.attr"
+SERVER_CERT_BAD_FILE="$testdir/ms-cert-bad.pem.tmp"
 
 stop_servers ()
 {
@@ -118,6 +119,20 @@ ${CERTTOOL} \
 	--load-privkey "${srcdir}/ocsp-tests/certs/server_good.key" \
 	--template "${TEMPLATE_FILE}" --outfile "${SERVER_CERT_FILE}" 2>/dev/null
 
+echo "=== Generating bad server certificate ==="
+
+rm -f "$TEMPLATE_FILE"
+cp "${srcdir}/ocsp-tests/certs/server_bad.template" "$TEMPLATE_FILE"
+chmod u+w "$TEMPLATE_FILE"
+echo "ocsp_uri=http://localhost:${OCSP_PORT}/ocsp/" >>"$TEMPLATE_FILE"
+
+${CERTTOOL} \
+	--attime "${CERTDATE}" \
+	--generate-certificate --load-ca-privkey "${srcdir}/ocsp-tests/certs/ca.key" \
+	--load-ca-certificate "${srcdir}/ocsp-tests/certs/ca.pem" \
+	--load-privkey "${srcdir}/ocsp-tests/certs/server_bad.key" \
+	--template "${TEMPLATE_FILE}" --outfile "${SERVER_CERT_BAD_FILE}" 2>/dev/null
+
 echo "=== Bringing OCSP server up ==="
 
 cp "${srcdir}/ocsp-tests/certs/ocsp_index.txt" ${INDEXFILE}
@@ -277,20 +292,75 @@ wait_server $TLS_SERVER_PID
 
 wait_for_port "${TLS_SERVER_PORT}"
 
-echo "test 123456" | \
-	"${CLI}" --attime "${TESTDATE}" --ocsp --x509cafile="${srcdir}/ocsp-tests/certs/ca.pem" \
-		 --port="${TLS_SERVER_PORT}" localhost
+out=$(
+    echo "test 123456" | \
+        "${CLI}" --attime "${TESTDATE}" --ocsp \
+             --x509cafile="${srcdir}/ocsp-tests/certs/ca.pem" \
+             --port="${TLS_SERVER_PORT}" localhost \
+             2>&1
+)
 rc=$?
+printf '%s\n' "$out"
 
 if test "${rc}" = "0"; then
     echo "Connecting to server with valid certificate and invalid staple succeeded"
     exit 1
 fi
 
+if ! echo "${out}" | grep "Got OCSP response with an unrelated certificate" > /dev/null
+then
+    echo '"Got OCSP response with an unrelated certificate" not found in output'
+    exit 1
+fi
+
 kill "${TLS_SERVER_PID}"
 wait "${TLS_SERVER_PID}"
 unset TLS_SERVER_PID
 
+echo "=== Test 4.1: Server with valid certificate - no response staple ==="
+
+rm -f "${OCSP_RESPONSE_FILE}"
+cp "${srcdir}/ocsp-tests/certs/ocsp-staple-empty.der" "${OCSP_RESPONSE_FILE}"
+
+eval "${GETPORT}"
+# Port for gnutls-serv
+TLS_SERVER_PORT=$PORT
+PORT=${TLS_SERVER_PORT}
+launch_bare_server \
+	"${SERV}" --attime "${TESTDATE}" --echo --disable-client-cert \
+	--x509keyfile="${srcdir}/ocsp-tests/certs/server_good.key" \
+	--x509certfile="${SERVER_CERT_FILE}" \
+	--port="${TLS_SERVER_PORT}" \
+	--ocsp-response="${OCSP_RESPONSE_FILE}" --ignore-ocsp-response-errors
+TLS_SERVER_PID="${!}"
+wait_server $TLS_SERVER_PID
+
+wait_for_port "${TLS_SERVER_PORT}"
+
+out=$(
+    echo "test 123456" | \
+        "${CLI}" --attime "${TESTDATE}" --ocsp \
+             --x509cafile="${srcdir}/ocsp-tests/certs/ca.pem" \
+             --port="${TLS_SERVER_PORT}" localhost \
+             2>&1
+)
+rc=$?
+printf '%s\n' "$out"
+
+if test "${rc}" = "0"; then
+    echo "Connecting to server with valid certificate and no response staple succeeded"
+    exit 1
+fi
+
+if ! echo "${out}" | grep  "Got OCSP response with no certificates" > /dev/null
+then
+    echo '"Got OCSP response with no certificates" not found in output'
+    exit 1
+fi
+
+kill "${TLS_SERVER_PID}"
+wait "${TLS_SERVER_PID}"
+unset TLS_SERVER_PID
 
 echo "=== Test 5: Server with valid certificate - expired staple ==="
 
@@ -486,6 +556,61 @@ kill "${TLS_SERVER_PID}"
 wait "${TLS_SERVER_PID}"
 unset TLS_SERVER_PID
 
+echo "=== Test 10: Server with revoked certificate - CVE-2026-3832 ==="
+
+# The revocation status was always mistakenly checked for the first cert.
+# Check a pair of responses: (irrelevant good unrevoked, relevant bad revoked).
+
+rm -f "${OCSP_RESPONSE_FILE}"
+
+"$FAKETIME" "${TESTDATE}" \
+    ${OPENSSL} ocsp -index "${INDEXFILE}" \
+    -issuer "${srcdir}/ocsp-tests/certs/ca.pem" \
+    -CA "${srcdir}/ocsp-tests/certs/ca.pem" \
+    -rsigner "${srcdir}/ocsp-tests/certs/ocsp-server.pem" \
+    -rkey "${srcdir}/ocsp-tests/certs/ocsp-server.key" \
+    -cert "${SERVER_CERT_FILE}" \
+    -cert "${SERVER_CERT_BAD_FILE}" \
+    -respout "${OCSP_RESPONSE_FILE}"
+
+eval "${GETPORT}"
+# Port for gnutls-serv
+TLS_SERVER_PORT=$PORT
+PORT=${TLS_SERVER_PORT}
+launch_bare_server \
+    "${SERV}" --attime "${TESTDATE}" --echo --disable-client-cert \
+    --x509keyfile="${srcdir}/ocsp-tests/certs/server_bad.key" \
+    --x509certfile="${SERVER_CERT_BAD_FILE}" \
+    --port="${TLS_SERVER_PORT}" \
+    --ocsp-response="${OCSP_RESPONSE_FILE}" --ignore-ocsp-response-errors
+TLS_SERVER_PID="${!}"
+wait_server $TLS_SERVER_PID
+
+wait_for_port "${TLS_SERVER_PORT}"
+
+out=$(
+    echo "test 123456" | \
+        "${CLI}" -d1 --attime "${TESTDATE}" --ocsp \
+        --x509cafile "${srcdir}/ocsp-tests/certs/ca.pem" \
+        --port "${TLS_SERVER_PORT}" localhost \
+        2>&1
+    rc=$?
+)
+printf '%s\n' "$out"
+
+if test "${rc}" = "0"; then
+    echo 'ERROR: client accepted a revoked leaf (CVE-2026-3832)'
+    exit 1
+fi
+if ! echo "${out}" | grep "The certificate was revoked via OCSP" >/dev/null
+then
+    echo '"The certificate was revoked via OCSP" not found in output'
+    exit 1
+fi
+
+kill "${TLS_SERVER_PID}"
+wait "${TLS_SERVER_PID}"
+unset TLS_SERVER_PID
 
 kill ${OCSP_PID}
 wait ${OCSP_PID}
Index: gnutls-3.8.10/tests/Makefile.am
===================================================================
--- gnutls-3.8.10.orig/tests/Makefile.am
+++ gnutls-3.8.10/tests/Makefile.am
@@ -61,7 +61,9 @@ EXTRA_DIST = suppressions.valgrind eagai
 	ocsp-tests/response2.der ocsp-tests/response3.der ocsp-tests/certs/ocsp_index.txt ocsp-tests/certs/ocsp_index.txt.attr \
 	ocsp-tests/response1.pem ocsp-tests/response2.pem \
 	ocsp-tests/certs/server_good.key ocsp-tests/certs/server_bad.key ocsp-tests/certs/server_good.template \
-	ocsp-tests/certs/server_bad.template ocsp-tests/certs/ocsp-staple-unrelated.der ocsp-tests/suppressions.valgrind \
+	ocsp-tests/certs/server_bad.template ocsp-tests/certs/ocsp-staple-unrelated.der \
+	ocsp-tests/certs/ocsp-staple-empty.der \
+	ocsp-tests/suppressions.valgrind \
 	ocsp-tests/signer-verify/response-ca.der \
 	ocsp-tests/signer-verify/response-delegated.der \
 	ocsp-tests/signer-verify/response-non-delegated.der \
Index: gnutls-3.8.10/tests/ocsp-tests/certs/ocsp-staple-empty.der
===================================================================
--- /dev/null
+++ gnutls-3.8.10/tests/ocsp-tests/certs/ocsp-staple-empty.der
@@ -0,0 +1,23 @@
+zcmXqLVp+$<$grS^Ww}8U%VIW8Z8k<$R(1nMMwTX)DF#g}6AZK$iLr=q9@<;)`-D4E
+z!%f`Pb8})(pWKuC68uI6hGqt)#s)@4rj{n=Q3ebKylk9WZ60mkc^MhGSs9p{7#SJ*
+zKKDM+|K1uQylhIwtsoDzluFGA?ZXE&PIf+c5v+0TcGfcqZWZ&R{0~*Tet2d7TGO}l
+z!+Vdn(S~bZU+(aTuuf$DRg@UAPN3;@PSjFIwM^bDm2Xeic<S8X;B*T)<*;unYk|an
+z?oTh~UvOueb)fY2@gLi&<T<T77cA|#{_gFSgjd%esM-nJM{~T>xBGnO&Vn0V-)09L
+z*eSU130qD}Xy|i~uN$K$EwFzmD1OwO;ob#fqvNKGW+KNHT=iSTU#5BCQ0tnU0}2aP
+zEc`I}(nVu^@z36~k2CM{{Ozt2QojE27PC#aKTXs1yO3lppy98VZkKq@xVgoCy=7h9
+zg-e0!EBM~03M^=1_BLo@b~k8Zb~b2Y(q6#K#K^?N6ZTo+n$W$O;yI@lK0pgL0~tdp
+z0|_?fP!?uk!I0GAlFYnx1;^5ojQpa^l1c+Pab80+17jm&BO@bA6Qd|`UPE&vu7S3p
+zrhz&{w*rQ41%Kz@0EM8`;)49Vl++@FCPpP>+Zn-;#?N5T#K^@2ioMHBDO|iss(BB;
+zyvhC0a>vyoFYdL9cF)R5je>zUAG@5-jj_MDNl2{P?%k%HQ`cNh`h97qQE7>q`>YzV
+z_4iLso+qxiV_v#y;@8jTUvtQn7yetLuUsJ@v5M!ho>k>n!7?#({%v-%xBh0=-T0p4
+z#<7YQvD24)IPQPOuz_XY)>}6v9g6=(i`Y%hRq#7gb+%~=8;`5~Ipg&kl|psD$2D8X
+z?k|o?JXCx|_E2tGyzEZCc`GdCs~58D_gue{<^00N|I&>fV&#F;HJ)uPJ`%=s$o)^`
+z5?@O$!wZ)SB&`&*jekx5*&`NrYi--H^=$1w-b|h+abdy1iRV^xcD`}9^@~c8D84e4
+ziJ6gsadDYJi2)BAbEvE^BjbM-CIbcoVGvK1g~x!4jRTTrm^lsjL4y1sC9KTM><tVC
+zvLFFI7BLo)=XMJVL>X7y`c&Zj-seQS)ZrN=wg&PbX=N4(1F;4X-5Vma#IEN4`@P}8
+zy^mjyDsx{>+J_u7u<TT|?8tYQtwjz6+tT;`X*njD;@!f2%s%$;;b}=}TV)E2gp>>Q
+zqOUiee2{c1IOO7SIYsShk7wOAcb*`bP--4<y=|h}LJt3j%yC^;KAe8<zkBf>)BSC^
+z9Xw{QbMG;oyqoaq2<NgFOIf|2Jru3io2kMil33pE(pJH~skLNvU^Me4!;UY#UOJ(D
+z6Eb!uDq4JDUEX>0NQnL%N3Ux<Pq*1_`!zGW%6n1TWuZ6EA2vUWlHEIj-R<GNWoJ(8
+zTb%Fz<|tdy>8QmDb`{bqU$5Ws&)&GsqiV&~>(|<zNy~`vsNSk$$XvG}G4Vk9vMWFP
+cp0LfeGq;*rZ97x{|Gzs$UE<vmlQK%#0A#V}1poj5
