git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH] http: add http.sslVerifyStatus to check stapled OCSP responses

From
Ggraysongordon-gl <graysongordon1@gmail.com>
Date
Aug 11, 2026, 17:02 UTC
Message-ID
<20260811170200.43097-1-ggordon@gitlab.com>
From: Grayson Gordon <graysongordon1@gmail.com>

git asks libcurl to verify the peer certificate and the hostname, but it never sets CURLOPT_SSL_VERIFYSTATUS, so the "Certificate Status Request" TLS extension is never requested and any stapled OCSP response the server does send is ignored.

On an OpenSSL-linked build this is silent. OpenSSL hands the stapled response to the application and takes no view on it: SSL_CTX_set_tlsext_status_cb(3) says the callback "should determine whether the returned OCSP response(s) are acceptable or not", and libcurl only installs that callback when CURLOPT_SSL_VERIFYSTATUS is set. So git will fetch from a server whose own staple says its certificate has been revoked.

A GnuTLS-linked build behaves differently, and the difference does not come from curl. GnuTLS consults a stapled response inside gnutls_certificate_verify_peers(), so the failure surfaces through the verifypeer branch of curl's GnuTLS backend (lib/vtls/gtls.c) whether or not CURLOPT_SSL_VERIFYSTATUS was ever set. The same git, against the same server, therefore enforces revocation or not depending only on how its libcurl was built. That difference is documented here rather than papered over: this option turns the check on where the backend needs asking, and setting it to false does not turn the check off on GnuTLS.

Add an http.sslVerifyStatus boolean that sets CURLOPT_SSL_VERIFYSTATUS. Because http_options() is the collect_fn of a urlmatch config, the per-URL form works with no further changes:

    git config http.https://example.com/.sslVerifyStatus true

It defaults to false, and has to. The option is fail-closed: libcurl fails verification when the server staples nothing at all, so turning this on globally would break every remote that does not staple.

Leaving the default to libcurl is not an option either. The same complaint was raised there in https://github.com/curl/curl/issues/15483 and closed as intentional ("Marked as enhancement since this was done on purpose"), with the observation that stapling is expected to see less use as Let's Encrypt drops OCSP support. If the check is to be reachable at all, the lever has to come from the application.

If the TLS backend cannot check the staple, curl_easy_setopt() returns CURLE_NOT_BUILT_IN. Fail loudly there rather than carrying on, since silently not checking is precisely what this option exists to prevent.

CURLOPT_SSL_VERIFYSTATUS has been available since libcurl 7.41.0, well below the 7.61.0 floor documented in INSTALL, so no version guard is needed.

The new test exercises the fail-closed path, which needs no CA and no OCSP responder: lib-httpd's server staples nothing, so enabling the option has to turn a working fetch into a failing one. Verified against an unpatched build, where exactly the two assertions that depend on the new option fail and the three controls still pass, and against OpenSSL, GnuTLS and mbedTLS-linked builds of libcurl.

Signed-off-by: Grayson Gordon <graysongordon1@gmail.com>
---
 Documentation/config/http.adoc  | 17 +++++++
 http.c                          | 21 +++++++++
 t/t5567-http-verify-status.sh   | 72 +++++++++++++++++++++++++++++++
 3 files changed, 110 insertions(+)
 create mode 100755 t/t5567-http-verify-status.sh
diff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc
index 792a71b413..40b849bf7f 100644
--- a/Documentation/config/http.adoc
+++ b/Documentation/config/http.adoc
@@ -196,6 +196,23 @@ http.sslVerify::
 	over HTTPS. Defaults to true. Can be overridden by the
 	`GIT_SSL_NO_VERIFY` environment variable.
 
+http.sslVerifyStatus::
+	Whether to check the revocation status of the server
+	certificate using the stapled OCSP response supplied during
+	the TLS handshake ("OCSP stapling"). Defaults to false.
++
+This is fail-closed: if the server staples no response, verification
+fails. Set it per remote, e.g.
+`http.https://example.com/.sslVerifyStatus`, rather than globally.
++
+What it changes depends on the TLS backend libcurl was built against.
+An OpenSSL-linked build ignores a stapled response unless this is set.
+A GnuTLS-linked build consults the staple during ordinary certificate
+verification, so it already rejects a revoked certificate under
+`http.sslVerify` alone, and setting this to `false` does not disable
+that. Where a backend cannot check the staple at all, git fails with an
+error rather than continuing unchecked.
+
 http.sslCert::
 	File containing the SSL certificate when fetching or pushing
 	over HTTPS. Can be overridden by the `GIT_SSL_CERT` environment
diff --git a/http.c b/http.c
index 5f0f42fb18..c1a66988e7 100644
--- a/http.c
+++ b/http.c
@@ -44,6 +44,7 @@ static CURL *curl_default;
 char curl_errorstr[CURL_ERROR_SIZE];
 
 static int curl_ssl_verify = -1;
+static int curl_ssl_verify_status;
 static int curl_ssl_try;
 static char *curl_http_version;
 static char *ssl_cert;
@@ -400,6 +401,10 @@ static int http_options(const char *var, const char *value,
 		curl_ssl_verify = git_config_bool(var, value);
 		return 0;
 	}
+	if (!strcmp("http.sslverifystatus", var)) {
+		curl_ssl_verify_status = git_config_bool(var, value);
+		return 0;
+	}
 	if (!strcmp("http.sslcipherlist", var))
 		return git_config_string(&ssl_cipherlist, var, value);
 	if (!strcmp("http.sslversion", var))
@@ -1131,6 +1136,22 @@ static CURL *get_curl_handle(void)
 		curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L);
 	}
 
+	/*
+	 * Ask the TLS backend to check the certificate's revocation
+	 * status via the stapled OCSP response. libcurl defaults this
+	 * off, and no backend except GnuTLS consults the staple on its
+	 * own, so without this git will happily accept a certificate
+	 * whose own staple says it has been revoked.
+	 *
+	 * Off by default because it is fail-closed: a server that
+	 * staples nothing fails verification outright, so enabling it
+	 * globally would break every remote that does not staple.
+	 */
+	if (curl_ssl_verify_status &&
+	    curl_easy_setopt(result, CURLOPT_SSL_VERIFYSTATUS, 1L) != CURLE_OK)
+		die(_("http.sslVerifyStatus is set, but the TLS backend of "
+		      "this libcurl cannot verify certificate status"));
+
     if (curl_http_version) {
 		long opt;
 		if (!get_curl_http_version_opt(curl_http_version, &opt)) {
diff --git a/t/t5567-http-verify-status.sh b/t/t5567-http-verify-status.sh
new file mode 100755
index 0000000000..c9167a05c2
--- /dev/null
+++ b/t/t5567-http-verify-status.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+test_description='http.sslVerifyStatus'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+
+LIB_HTTPD_SSL=t
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+# The test server staples no OCSP response, and that is what makes this
+# testable without standing up a CA and a responder: http.sslVerifyStatus is
+# fail-closed, so turning it on has to turn a working fetch into a failing one.
+#
+# lib-httpd.sh exports GIT_SSL_NO_VERIFY for its self-signed certificate. In
+# libcurl the status check is independent of peer verification, so it still
+# applies here.
+
+test_expect_success 'setup repository' '
+	echo content >file &&
+	git add file &&
+	git commit -m one
+'
+
+test_expect_success 'create http-accessible bare repository' '
+	git init --bare "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+	git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+	git push public main:main
+'
+
+# A TLS backend that cannot check the staple makes curl_easy_setopt() fail,
+# which http.c reports with a distinct message. Skip in that case rather than
+# reporting a failure that really means "this libcurl was built differently".
+# Any other failure leaves the prerequisite satisfied on purpose, so a broken
+# server makes the tests below fail loudly instead of silently vanishing.
+test_lazy_prereq SSL_VERIFYSTATUS '
+	git -c http.sslVerifyStatus=true \
+		ls-remote "$HTTPD_URL/smart/repo.git" 2>err
+	! grep "cannot verify certificate status" err
+'
+
+test_expect_success 'ls-remote succeeds with http.sslVerifyStatus unset' '
+	git ls-remote "$HTTPD_URL/smart/repo.git" >actual &&
+	test_line_count -gt 0 actual
+'
+
+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=true fails without a staple' '
+	test_must_fail git -c http.sslVerifyStatus=true \
+		ls-remote "$HTTPD_URL/smart/repo.git"
+'
+
+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=false is a no-op' '
+	git -c http.sslVerifyStatus=false \
+		ls-remote "$HTTPD_URL/smart/repo.git" >actual &&
+	test_line_count -gt 0 actual
+'
+
+test_expect_success SSL_VERIFYSTATUS 'per-URL configuration applies to a matching URL' '
+	test_must_fail git -c "http.$HTTPD_URL/.sslVerifyStatus=true" \
+		ls-remote "$HTTPD_URL/smart/repo.git"
+'
+
+test_expect_success SSL_VERIFYSTATUS 'per-URL configuration is not applied to other URLs' '
+	git -c "http.https://example.com/.sslVerifyStatus=true" \
+		ls-remote "$HTTPD_URL/smart/repo.git" >actual &&
+	test_line_count -gt 0 actual
+'
+
+test_done
-- 
2.55.0
Next: Junio C Hamano
Message 1 of 39 in “http: add http.sslVerifyStatus to check stapled OCSP responses”
  1. http: add http.sslVerifyStatus to check stapled OCSP responsesgraysongordon-gl, Aug 11, 2026
  2. Junio C HamanoAug 11, 2026
  3. http: add http.sslVerifyStatus to check stapled OCSP responsesgraysongordon-gl, Aug 11, 2026
  4. Patrick SteinhardtAug 12, 2026
  5. Grayson GordonAug 12, 2026
  6. Junio C HamanoAug 12, 2026
  7. http: add http.sslVerifyStatus to check stapled OCSP responsesgraysongordon-gl, Aug 12, 2026
  8. Junio C HamanoAug 12, 2026
  9. Junio C HamanoAug 13, 2026
  10. http: add http.sslVerifyStatus to check stapled OCSP responsesgraysongordon-gl, Aug 17, 2026
  11. Junio C HamanoAug 17, 2026
  12. Patrick SteinhardtAug 18, 2026
  13. Grayson GordonAug 18, 2026
  14. Patrick SteinhardtAug 19, 2026
  15. Junio C HamanoAug 18, 2026
  16. http: add http.sslVerifyStatus to check stapled OCSP responsesgraysongordon-gl, Aug 18, 2026
  17. Junio C HamanoAug 18, 2026
  18. Grayson GordonAug 18, 2026
  19. http: add http.sslVerifyStatus to check stapled OCSP responsesgraysongordon-gl, Aug 18, 2026
  20. Junio C HamanoAug 26, 2026
  21. Grayson GordonAug 28, 2026
  22. Junio C HamanoAug 28, 2026
  23. Patrick SteinhardtAug 31, 2026
  24. Junio C HamanoAug 31, 2026
  25. Patrick SteinhardtAug 31, 2026
  26. Junio C HamanoAug 31, 2026
  27. Grayson GordonSep 8, 2026
  28. http: add http.sslVerifyStatus to check stapled OCSP responsesgraysongordon-gl, Sep 15, 2026
  29. Junio C HamanoSep 16, 2026
  30. Patrick SteinhardtSep 23, 2026
  31. Junio C HamanoSep 23, 2026
  32. SZEDER GáborSep 23, 2026
  33. Junio C HamanoSep 23, 2026
  34. SZEDER GáborSep 24, 2026
  35. Patrick SteinhardtSep 24, 2026
  36. SZEDER GáborSep 25, 2026
  37. Junio C HamanoSep 25, 2026
  38. Junio C HamanoSep 24, 2026
  39. Junio C HamanoOct 7, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.