* [PATCH] http: add http.sslVerifyStatus to check stapled OCSP responses
@ 2026-08-11 17:02 graysongordon-gl
2026-08-11 19:28 ` Junio C Hamano
2026-08-11 20:44 ` [PATCH v2] " graysongordon-gl
0 siblings, 2 replies; 38+ messages in thread
From: graysongordon-gl @ 2026-08-11 17:02 UTC (permalink / raw)
To: git; +Cc: gitster, peff, avarab, ps, Grayson Gordon
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
^ permalink raw reply related [flat|nested] 38+ messages in thread* Re: [PATCH] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-11 17:02 [PATCH] http: add http.sslVerifyStatus to check stapled OCSP responses graysongordon-gl @ 2026-08-11 19:28 ` Junio C Hamano 2026-08-11 20:44 ` [PATCH v2] " graysongordon-gl 1 sibling, 0 replies; 38+ messages in thread From: Junio C Hamano @ 2026-08-11 19:28 UTC (permalink / raw) To: graysongordon-gl; +Cc: git, peff, avarab, ps graysongordon-gl <graysongordon1@gmail.com> writes: > 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. Good to see that the author paid extra attention to compatibility. > + /* > + * 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. > + */ The comment may not be telling any lies per se, but it is dubious that this belongs here as an in-code comment. Developers hunting a bug they suspect this setting might have caused will need access to this information, and they can access it by running 'git blame' to locate the commit that introduced the code. As long as a solid commit log message explains how you arrived at various design decisions (such as 'off by default because'), they can use that as a starting point. For other developers hunting different bugs or trying to add their own enhancements, the comment is a mere distraction. > + 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")); > + Thanks. ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH v2] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-11 17:02 [PATCH] http: add http.sslVerifyStatus to check stapled OCSP responses graysongordon-gl 2026-08-11 19:28 ` Junio C Hamano @ 2026-08-11 20:44 ` graysongordon-gl 2026-08-12 6:25 ` Patrick Steinhardt 2026-08-12 14:17 ` Junio C Hamano 1 sibling, 2 replies; 38+ messages in thread From: graysongordon-gl @ 2026-08-11 20:44 UTC (permalink / raw) To: git; +Cc: gitster, peff, avarab, ps, Grayson Gordon 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> --- v2: drop the block comment above the setopt. What it explained (why the check is needed, and why the default is false) is already in the commit message, which is where "git blame" leads anyone debugging this. No code change otherwise. Documentation/config/http.adoc | 17 +++++++ http.c | 10 ++++ t/t5567-http-verify-status.sh | 72 +++++++++++++++++++++++++++++++ 3 files changed, 99 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)) @@ -1133,6 +1138,11 @@ static CURL *get_curl_handle(void) curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L); } + 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 ^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH v2] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-11 20:44 ` [PATCH v2] " graysongordon-gl @ 2026-08-12 6:25 ` Patrick Steinhardt 2026-08-12 15:53 ` Grayson Gordon 2026-08-12 14:17 ` Junio C Hamano 1 sibling, 1 reply; 38+ messages in thread From: Patrick Steinhardt @ 2026-08-12 6:25 UTC (permalink / raw) To: graysongordon-gl; +Cc: git, gitster, peff, avarab On Tue, Aug 11, 2026 at 04:44:07PM -0400, graysongordon-gl wrote: > 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 Nit: this is arguably not the same git, as it links against different libraries. It is not exactly unexpected that using different dependencies may cause different behaviour, even though we should of course try to minimize the differences. > 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. Okay. One could make the argument that we shouldn't add support for OCSP either if it's being phased out now. But I assume there's still going to be enough servers out there that do use it. The big question to me is why we want to have this change in the first place. It doesn't help to address the behaviour difference between GnuTLS and OpenSSL: if set to "false" OpenSSL would continue to ignore OCSP, whereas GnuTLS would still honor it. If set to "true", OpenSSL would fail closed, whereas GnuTLS would still behave the same as before. So nothing really changes here, unless I misunderstand something. We don't really gain security, either, because the setting is disabled by default and can only be enabled host-by-host. I doubt anybody out there is really going to do that though, and consequently we haven't really made the world a more secure place :/ So is there any specific use case that you're after? Who exactly is this new feature for? Thanks! Patrick ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-12 6:25 ` Patrick Steinhardt @ 2026-08-12 15:53 ` Grayson Gordon 0 siblings, 0 replies; 38+ messages in thread From: Grayson Gordon @ 2026-08-12 15:53 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: git, gitster, peff, avarab Patrick, Thank you for reviewing my submission! I understand the "nit" you're describing: software can't exactly be called the same thing if it is built against different libraries, which in turn creates an opportunity for different behaviors. I agree with your follow-up that we should aim to maintain consistency despite those library choices. The benefits of the Principle of Least Astonishment are well established, and I would argue that users reasonably expect Git to behave consistently regardless of the underlying TLS library. I can provide additional context to motivate this change. Like you, I currently work for GitLab, but as part of the Professional Services organization, which works directly with customers deploying the software in their environments for production use. I am supporting a government customer whose servers use OCSP stapling. There are many such government and government-adjacent customers that utilize certificates issued by US Department of Defense PKI CAs, which have published policies that explicitly outline support for OCSP: https://dl.dod.cyber.mil/wp-content/uploads/pki-pke/pdf/Unclass-DoD_X.509_Certificate_Policy_v10.7_Jun_3_21.pdf. Many DoD PKI CAs serve certificates with stapled OCSP responses today and can reasonably be expected to continue to do so until there is a DoD-wide policy change. A related bug in GnuTLS has affected my customer in their current production environment, preventing them from being able to push-mirror to repositories on remotes whose servers use OCSP stapling. The push-mirror failure is what prompted my initial investigation into this issue. Original GnuTLS issue: https://gitlab.com/gnutls/gnutls/-/work_items/1372, resolved in GnuTLS 3.8.8. GitLab issue on the Cloud Native GitLab build that resolves this behavior in GitLab's default Helm chart base image: https://gitlab.com/gitlab-org/build/CNG/-/work_items/2374#note_3653072099 GitLab ships its Helm charts with two primary "flavors": one based on Debian and the other on UBI. On Debian, the default SSL backend is GnuTLS, and the aforementioned issues resolve the problem. On UBI, the default backend is OpenSSL, and this issue surfaces. For my government customers who need FIPS, switching to the UBI-based image is the long-term path forward: https://gitlab.com/graysongordon-gl/gitaly-tls-experiments/-/blob/main/docs/FIPS-AND-THE-TLS-BACKEND.md?ref_type=heads. To be more explicit, OpenSSL-linked Git binaries are the default case for many government customers, and those customers frequently interface with Git servers that use this type of certificate revocation mechanism. In summary, there are many instances of Git servers serving a large base of developers working on government-related software that are impacted by this issue and need this functionality. These users have experienced the pain and confusion of this behavior being broken firsthand in downstream applications and have brought the issue to me. These customers care that their Git clients respect certificate revocation when it occurs, whether from their development machines or through service-to-service communications over Git on platforms like GitLab. They interface with these kinds of certificates frequently and will continue to do so, which warrants the inclusion of this flag. The benefit they would receive is correct validation of a remote's certificate. As it stands today, users leveraging OpenSSL-linked Git binaries can receive a response indicating that the certificate is valid even when the stapled OCSP response indicates that the certificate has been revoked. I think there is a reasonable case that this could qualify as a low-to-medium severity CVE. The threat model is: An attacker steals the private key of a Git server whose certificate is accompanied by an OCSP-stapled response. The breach is detected, and the certificate authority revokes the certificate. Despite the revocation, Git clients continue to accept the certificate and push/pull code from a malicious Git server impersonating the legitimate server. A malicious actor could leverage this to facilitate the exfiltration of an organization's Git data. Similar CVEs against libcurl include: https://curl.se/mail/lib-2026-04/0036.html, https://curl.se/docs/CVE-2024-0853.html In addition, the attacker would need a mechanism for intercepting or redirecting the victim's connection to the Git server, hence this not being a higher-severity issue. However, I think that in the context of DoD systems, this is sufficiently dangerous to warrant remediation, and this patch provides that capability. On the GitLab side, we already have mechanisms for per-remote configuration values to be passed, and integrating this would not be a monumental lift. Thank you, Grayson On Wed, Aug 12, 2026 at 2:25 AM Patrick Steinhardt <ps@pks.im> wrote: > > On Tue, Aug 11, 2026 at 04:44:07PM -0400, graysongordon-gl wrote: > > 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 > > Nit: this is arguably not the same git, as it links against different > libraries. It is not exactly unexpected that using different > dependencies may cause different behaviour, even though we should of > course try to minimize the differences. > > > 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. > > Okay. One could make the argument that we shouldn't add support for OCSP > either if it's being phased out now. But I assume there's still going to > be enough servers out there that do use it. > > The big question to me is why we want to have this change in the first > place. It doesn't help to address the behaviour difference between > GnuTLS and OpenSSL: if set to "false" OpenSSL would continue to ignore > OCSP, whereas GnuTLS would still honor it. If set to "true", OpenSSL > would fail closed, whereas GnuTLS would still behave the same as before. > So nothing really changes here, unless I misunderstand something. > > We don't really gain security, either, because the setting is disabled > by default and can only be enabled host-by-host. I doubt anybody out > there is really going to do that though, and consequently we haven't > really made the world a more secure place :/ > > So is there any specific use case that you're after? Who exactly is this > new feature for? > > Thanks! > > Patrick ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-11 20:44 ` [PATCH v2] " graysongordon-gl 2026-08-12 6:25 ` Patrick Steinhardt @ 2026-08-12 14:17 ` Junio C Hamano 2026-08-12 18:25 ` [PATCH v3] " graysongordon-gl 1 sibling, 1 reply; 38+ messages in thread From: Junio C Hamano @ 2026-08-12 14:17 UTC (permalink / raw) To: graysongordon-gl; +Cc: git, peff, avarab, ps graysongordon-gl <graysongordon1@gmail.com> writes: > Documentation/config/http.adoc | 17 +++++++ > http.c | 10 ++++ > t/t5567-http-verify-status.sh | 72 +++++++++++++++++++++++++++++++ > 3 files changed, 99 insertions(+) > create mode 100755 t/t5567-http-verify-status.sh Hmph, if we need a brand new script, please make sure the 4-digit number is not taken, not just in the sources to released versions but by other topics that are in flight. $ git show origin/seen:t | grep t5567 should be empty, but it is not. It seems mm/lib-httpd-cgi-safe topic grabbed it. ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH v3] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-12 14:17 ` Junio C Hamano @ 2026-08-12 18:25 ` graysongordon-gl 2026-08-12 21:34 ` Junio C Hamano 0 siblings, 1 reply; 38+ messages in thread From: graysongordon-gl @ 2026-08-12 18:25 UTC (permalink / raw) To: git; +Cc: gitster, peff, avarab, ps, Grayson Gordon 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> --- v3: rename the test from t5567 to t5568. t5567 is taken on 'seen' by mm/lib-httpd-cgi-safe. t5568 is free on master, next, seen, jch and maint as of b9720e4723, and sits next to the other http tests. No other change. v2: drop the block comment above the setopt. What it explained (why the check is needed, and why the default is false) is already in the commit message, which is where "git blame" leads anyone debugging this. No code change otherwise. Documentation/config/http.adoc | 17 +++++++ http.c | 10 ++++ t/t5568-http-verify-status.sh | 72 +++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100755 t/t5568-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)) @@ -1133,6 +1138,11 @@ static CURL *get_curl_handle(void) curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L); } + 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/t5568-http-verify-status.sh b/t/t5568-http-verify-status.sh new file mode 100755 index 0000000000..c9167a05c2 --- /dev/null +++ b/t/t5568-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 ^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH v3] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-12 18:25 ` [PATCH v3] " graysongordon-gl @ 2026-08-12 21:34 ` Junio C Hamano 2026-08-13 16:06 ` Junio C Hamano 0 siblings, 1 reply; 38+ messages in thread From: Junio C Hamano @ 2026-08-12 21:34 UTC (permalink / raw) To: graysongordon-gl; +Cc: git, peff, avarab, ps graysongordon-gl <graysongordon1@gmail.com> writes: > v3: rename the test from t5567 to t5568. t5567 is taken on 'seen' by > mm/lib-httpd-cgi-safe. t5568 is free on master, next, seen, jch and > maint as of b9720e4723, and sits next to the other http tests. No > other change. I thought I first asked whether we need a new script before suggesting moving it out of the way because 't5567' was already taken. It is much better not to waste a scarce, shared resource such as a test number, and doing so avoids breaking the build if we are not careful. If we really need to add a new script, you would need to squash in at least a patch like this to avoid breaking Meson-based builds. t/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git i/t/meson.build w/t/meson.build index 3219264fe7..3d68f67680 100644 --- i/t/meson.build +++ w/t/meson.build @@ -707,6 +707,7 @@ integration_tests = [ 't5564-http-proxy.sh', 't5565-push-multiple.sh', 't5566-push-group.sh', + 't5568-http-verify-status.sh', 't5570-git-daemon.sh', 't5571-pre-push-hook.sh', 't5572-pull-submodule.sh', ^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH v3] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-12 21:34 ` Junio C Hamano @ 2026-08-13 16:06 ` Junio C Hamano 2026-08-17 18:52 ` [PATCH v4] " graysongordon-gl ` (2 more replies) 0 siblings, 3 replies; 38+ messages in thread From: Junio C Hamano @ 2026-08-13 16:06 UTC (permalink / raw) To: graysongordon-gl; +Cc: git, peff, avarab, ps Junio C Hamano <gitster@pobox.com> writes: > graysongordon-gl <graysongordon1@gmail.com> writes: > >> v3: rename the test from t5567 to t5568. t5567 is taken on 'seen' by >> mm/lib-httpd-cgi-safe. t5568 is free on master, next, seen, jch and >> maint as of b9720e4723, and sits next to the other http tests. No >> other change. > > I thought I first asked whether we need a new script before > suggesting moving it out of the way because 't5567' was already > taken. It is much better not to waste a scarce, shared resource > such as a test number, and doing so avoids breaking the build if we > are not careful. > > If we really need to add a new script, you would need to squash in > at least a patch like this to avoid breaking Meson-based builds. > > > t/meson.build | 1 + > 1 file changed, 1 insertion(+) > > diff --git i/t/meson.build w/t/meson.build > index 3219264fe7..3d68f67680 100644 > --- i/t/meson.build > +++ w/t/meson.build > @@ -707,6 +707,7 @@ integration_tests = [ > 't5564-http-proxy.sh', > 't5565-push-multiple.sh', > 't5566-push-group.sh', > + 't5568-http-verify-status.sh', > 't5570-git-daemon.sh', > 't5571-pre-push-hook.sh', > 't5572-pull-submodule.sh', BTW, exit status of ls-remote is lost without the following: diff --git a/t/t5568-http-verify-status.sh b/t/t5568-http-verify-status.sh index c9167a05c2..7ba70fc8af 100755 --- a/t/t5568-http-verify-status.sh +++ b/t/t5568-http-verify-status.sh @@ -38,7 +38,7 @@ test_expect_success 'create http-accessible bare repository' ' # 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 + ls-remote "$HTTPD_URL/smart/repo.git" 2>err && ! grep "cannot verify certificate status" err ' ^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v4] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-13 16:06 ` Junio C Hamano @ 2026-08-17 18:52 ` graysongordon-gl 2026-08-17 19:19 ` Junio C Hamano 2026-08-18 7:50 ` Patrick Steinhardt 2026-08-18 19:37 ` [PATCH v5] " graysongordon-gl 2026-08-18 21:48 ` [PATCH v6] " graysongordon-gl 2 siblings, 2 replies; 38+ messages in thread From: graysongordon-gl @ 2026-08-17 18:52 UTC (permalink / raw) To: gitster; +Cc: git, Grayson Gordon 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 tests go in t5551 and run only in its https pass, which t5559 provides by sourcing t5551 with LIB_HTTPD_SSL set; that is the only https server the suite has. They exercise 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 two controls still pass, and against OpenSSL, GnuTLS and mbedTLS-linked builds of libcurl. Signed-off-by: Grayson Gordon <graysongordon1@gmail.com> --- v4: drop the new test script. The four tests now live in t5551, keyed on $HTTPD_PROTO so they run in the https pass (t5559) only. To answer the question I skipped past in v3: no, we do not need a new script. t5559 is t5551 run with LIB_HTTPD_SSL set, and it is the only https server the suite has, so a new script would have had to stand up a second one to reach the same place. Nothing is left over from t5567 or t5568 and no test number is spent. That also means the t/meson.build hunk is not squashed in. t5551 is already listed there. Adding t5568 to the list now would break configure the other way round, since the list is checked against ls in both directions and errors with "Test files configured, but not found". On the lost exit status: changed, but to test_might_fail rather than a bare &&. The ls-remote in the prerequisite is expected to fail, that is the premise of the test, so && short-circuits on the expected failure and leaves the prerequisite unsatisfied. Run against the https server both ways: bare && ok 51 # skip http.sslVerifyStatus=true fails without a staple (missing SSL_VERIFYSTATUS) test_might_fail ok 51 - http.sslVerifyStatus=true fails without a staple The first still reports "passed all 61 test(s)", which is the failure mode the prerequisite was written to avoid. test_might_fail keeps the chain intact and says the status is ignored on purpose. Also dropped the "ls-remote succeeds with http.sslVerifyStatus unset" test. It was a control for the standalone script, and in t5551 the surrounding tests already exercise that URL throughout. The rationale that sat in an in-code comment in v3 is in the log now, per the earlier review. Verified: t5551 over plain http and t5559 over https both pass all 61 tests, with the four new ones skipping on the former and running on the latter. Documentation/config/http.adoc | 17 +++++++++++++++++ http.c | 10 ++++++++++ t/t5551-http-fetch-smart.sh | 29 +++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) 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 caccf2108e..94f8dd817a 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)) @@ -1133,6 +1138,11 @@ static CURL *get_curl_handle(void) curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L); } + 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/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index 805bec025c..c11e96c1ac 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -680,6 +680,35 @@ test_expect_success 'passing hostname resolution information works' ' git -c "http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1" ls-remote "$BOGUS_HTTPD_URL/smart/repo.git" >/dev/null ' +test_lazy_prereq SSL_VERIFYSTATUS ' + test "$HTTPD_PROTO" = "https" && + test_might_fail git -c http.sslVerifyStatus=true \ + ls-remote "$HTTPD_URL/smart/repo.git" 2>err && + ! grep "cannot verify certificate status" err +' + +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 sslVerifyStatus 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 sslVerifyStatus 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 +' + # here user%40host is the URL-encoded version of user@host, # which is our intentionally-odd username to catch parsing errors url_user=$HTTPD_URL_USER/auth/smart/repo.git -- 2.50.1 (Apple Git-155) ^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH v4] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-17 18:52 ` [PATCH v4] " graysongordon-gl @ 2026-08-17 19:19 ` Junio C Hamano 2026-08-18 7:50 ` Patrick Steinhardt 1 sibling, 0 replies; 38+ messages in thread From: Junio C Hamano @ 2026-08-17 19:19 UTC (permalink / raw) To: graysongordon-gl; +Cc: git graysongordon-gl <graysongordon1@gmail.com> writes: > Verified: t5551 over plain http and t5559 over https both pass all 61 > tests, with the four new ones skipping on the former and running on the > latter. > Documentation/config/http.adoc | 17 +++++++++++++++++ > http.c | 10 ++++++++++ > t/t5551-http-fetch-smart.sh | 29 +++++++++++++++++++++++++++++ > 3 files changed, 56 insertions(+) OK, instead of adding a new test script that weighs 72-line we are testing the feature with 29-line addition, which sounds like a good economy ;-). The code changes and the documentation haven't changed since the previous round, both looking good. Will replace. Thanks. > 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 caccf2108e..94f8dd817a 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)) > @@ -1133,6 +1138,11 @@ static CURL *get_curl_handle(void) > curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L); > } > > + 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/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh > index 805bec025c..c11e96c1ac 100755 > --- a/t/t5551-http-fetch-smart.sh > +++ b/t/t5551-http-fetch-smart.sh > @@ -680,6 +680,35 @@ test_expect_success 'passing hostname resolution information works' ' > git -c "http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1" ls-remote "$BOGUS_HTTPD_URL/smart/repo.git" >/dev/null > ' > > +test_lazy_prereq SSL_VERIFYSTATUS ' > + test "$HTTPD_PROTO" = "https" && > + test_might_fail git -c http.sslVerifyStatus=true \ > + ls-remote "$HTTPD_URL/smart/repo.git" 2>err && > + ! grep "cannot verify certificate status" err > +' > + > +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 sslVerifyStatus 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 sslVerifyStatus 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 > +' > + > # here user%40host is the URL-encoded version of user@host, > # which is our intentionally-odd username to catch parsing errors > url_user=$HTTPD_URL_USER/auth/smart/repo.git ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v4] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-17 18:52 ` [PATCH v4] " graysongordon-gl 2026-08-17 19:19 ` Junio C Hamano @ 2026-08-18 7:50 ` Patrick Steinhardt 2026-08-18 14:51 ` Grayson Gordon 2026-08-18 16:40 ` Junio C Hamano 1 sibling, 2 replies; 38+ messages in thread From: Patrick Steinhardt @ 2026-08-18 7:50 UTC (permalink / raw) To: graysongordon-gl; +Cc: gitster, git On Mon, Aug 17, 2026 at 02:52:42PM -0400, graysongordon-gl wrote: > 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. This is only part of the story though: GnuTLS 3.8 introduced GNUTLS_NO_STATUS_REQUEST, and curl 8.10 started to set that option in case of `!verifystatus`. So with new-enough versions of both libraries, Git behaves the same no matter whether we use OpenSSL or GnuTLS as backend. See also aeb1a281ca (gtls: fix OCSP stapling management, 2024-08-20) in curl. > 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. But... don't we still leave the default to libcurl? If "http.sslVerifyStatus" is not set then we don't touch `CURLOPT_SSL_VERIFYSTATUS`, either. I might be misreading this though, as the whole commit message is quite hard to digest. I'd assume that this is because it's generated by AI, and it added a lot of the usual weird phrases to the message. It might be a good idea to adapt the message to have a bit more of a human touch to it. > 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. Makes sense. > 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. This information is not accurate because recent GnuTLS+libcurl versions handle this the same as OpenSSL, as mentioned above. Also, it might make sense to convert the backend-specific information into a bulleted list as we may add more items to it going forward. Do we have any info how other backends like mbedTLS behave? Or do we know that those all fail. > diff --git a/http.c b/http.c > index caccf2108e..94f8dd817a 100644 > --- a/http.c > +++ b/http.c > @@ -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)) > @@ -1133,6 +1138,11 @@ static CURL *get_curl_handle(void) > curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L); > } > > + 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")); Should we include the output of `curl_easy_strerror()` in the error message? That'd cause us to include the following error message in case we see CURLE_NOT_BUILT_IN: case CURLE_NOT_BUILT_IN: return "A requested feature, protocol or option was not found built-in in" " this libcurl due to a build-time decision."; So we could instead do: if (curl_ssl_verify_status) { CURLcode error = curl_easy_setopt(result, CURLOPT_SSL_VERIFYSTATUS, 1L); if (error != CURLE_OK) die(_("http.sslVerifyStatus is set, but could not enable OCSP status verification: %s"), curl_easy_strerror(error)); } > diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh > index 805bec025c..c11e96c1ac 100755 > --- a/t/t5551-http-fetch-smart.sh > +++ b/t/t5551-http-fetch-smart.sh > @@ -680,6 +680,35 @@ test_expect_success 'passing hostname resolution information works' ' > git -c "http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1" ls-remote "$BOGUS_HTTPD_URL/smart/repo.git" >/dev/null > ' > > +test_lazy_prereq SSL_VERIFYSTATUS ' > + test "$HTTPD_PROTO" = "https" && > + test_might_fail git -c http.sslVerifyStatus=true \ > + ls-remote "$HTTPD_URL/smart/repo.git" 2>err && > + ! grep "cannot verify certificate status" err > +' > + > +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 sslVerifyStatus 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 sslVerifyStatus 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 > +' Can we reasonably add tests that send OCSP information and verify that enabling "sslVerifyStatus" makes this work as expected? Patrick ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v4] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-18 7:50 ` Patrick Steinhardt @ 2026-08-18 14:51 ` Grayson Gordon 2026-08-19 8:14 ` Patrick Steinhardt 2026-08-18 16:40 ` Junio C Hamano 1 sibling, 1 reply; 38+ messages in thread From: Grayson Gordon @ 2026-08-18 14:51 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: gitster, git Patrick, Thanks again for the feedback. I'm going to break this into sections delimited by all caps headers to address each thing you mentioned. ON GNUTLS VS OPENSSL DIFFERENCES I appreciate you including the extra context around GnuTLS 3.8's GNUTLS_NO_STATUS_REQUEST flag, and curl 8.10 setting it when "verifystatus" is false. Fair enough, said another way, if either of these are true: Condition 1: curl is being built with GnuTLS version < 3.8. (There's no NO_STATUS_REQUEST flag to set.) OR Condition 2: curl version < 8.10. (Curl's not using the flag.) You'll see a discrepancy in cert verification behavior between the versions of git built with GnuTLS vs OpenSSL. While I appreciate the increased precision here, the purpose of my contribution was to expose the functionality that enables git users to set it if they so choose. As it stands today, this option is not presented. So while the discrepancy is what incited me to look deeper, it isn't the central reason I'm here. You had a section further down that said: "This information is not accurate because recent GnuTLS+libcurl versions handle this the same as OpenSSL, as mentioned above." I think that this response section suffices for both. I didn't look into any other TLS backends, as curl's docs say that the verifystatus option only works with GnuTLS and OpenSSL https://curl.se/libcurl/c/CURLOPT_SSL_VERIFYSTATUS.html and the other backend options didn't apply to GitLab customers as it relates to our CNG charts. There may still be value in looking and capturing it in the git docs somewhere. --------------- ON DEFAULT BEHAVIOR AND THE COMMIT MESSAGE In reality we ARE leaving the default to curl without the flag being set, so my comment "Leaving the default to libcurl is not an option either." wasn't super precise either. A nit. Again the change that's being introduced here is an OPTION for git users to include the "verifystatus" flag. Sorry that the commit message feels a bit awkward to you, I can rework it to be a bit more direct and not spend as much time on justifications if that'll be easier to read. --------------- ON DESCRIPTIVE ERROR MESSAGES Yes, I like this idea. We could give user's a much clearer error that way. I'll include that. My tests grep for the current string though, so I'll have to update that too. --------------- ON COMPREHENSIVE TESTING I'll leave this at you and Junio's discretion. I worked with him earlier in this thread to avoid introducing another test file and keep the testing succinct. Right now these tests are just limited to parsing the config and applying it to the user-provided remote. We COULD do the full suite of tests that cover the full range of cases/behaviors: - The flag is set AND - no staple sent (should fail) - good staple (should pass) - bad staple (should fail) etc. We're going to need a lot of infrax for that though: - test CA. - test server certificate issued by that CA. - OCSP responder which knows the certificate's status. - a way for the TLS server to obtain and staple that response. - a way to control the response so you can test good vs revoked/invalid. I set all of that stuff up in my own experiment repo, emulating this with nginx in docker... It's feasible, just need to know how you all would like it. - Grayson On Tue, Aug 18, 2026 at 3:50 AM Patrick Steinhardt <ps@pks.im> wrote: > > On Mon, Aug 17, 2026 at 02:52:42PM -0400, graysongordon-gl wrote: > > 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. > > This is only part of the story though: GnuTLS 3.8 introduced > GNUTLS_NO_STATUS_REQUEST, and curl 8.10 started to set that option in > case of `!verifystatus`. So with new-enough versions of both libraries, > Git behaves the same no matter whether we use OpenSSL or GnuTLS as > backend. See also aeb1a281ca (gtls: fix OCSP stapling management, > 2024-08-20) in curl. > > > 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. > > But... don't we still leave the default to libcurl? If > "http.sslVerifyStatus" is not set then we don't touch > `CURLOPT_SSL_VERIFYSTATUS`, either. > > I might be misreading this though, as the whole commit message is quite > hard to digest. I'd assume that this is because it's generated by AI, > and it added a lot of the usual weird phrases to the message. It might > be a good idea to adapt the message to have a bit more of a human touch > to it. > > > 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. > > Makes sense. > > > 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. > > This information is not accurate because recent GnuTLS+libcurl versions > handle this the same as OpenSSL, as mentioned above. > > Also, it might make sense to convert the backend-specific information > into a bulleted list as we may add more items to it going forward. Do we > have any info how other backends like mbedTLS behave? Or do we know that > those all fail. > > > diff --git a/http.c b/http.c > > index caccf2108e..94f8dd817a 100644 > > --- a/http.c > > +++ b/http.c > > @@ -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)) > > @@ -1133,6 +1138,11 @@ static CURL *get_curl_handle(void) > > curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L); > > } > > > > + 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")); > > Should we include the output of `curl_easy_strerror()` in the error > message? That'd cause us to include the following error message in case > we see CURLE_NOT_BUILT_IN: > > case CURLE_NOT_BUILT_IN: > return "A requested feature, protocol or option was not found built-in in" > " this libcurl due to a build-time decision."; > > So we could instead do: > > if (curl_ssl_verify_status) { > CURLcode error = curl_easy_setopt(result, CURLOPT_SSL_VERIFYSTATUS, 1L); > if (error != CURLE_OK) > die(_("http.sslVerifyStatus is set, but could not enable OCSP status verification: %s"), > curl_easy_strerror(error)); > } > > > diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh > > index 805bec025c..c11e96c1ac 100755 > > --- a/t/t5551-http-fetch-smart.sh > > +++ b/t/t5551-http-fetch-smart.sh > > @@ -680,6 +680,35 @@ test_expect_success 'passing hostname resolution information works' ' > > git -c "http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1" ls-remote "$BOGUS_HTTPD_URL/smart/repo.git" >/dev/null > > ' > > > > +test_lazy_prereq SSL_VERIFYSTATUS ' > > + test "$HTTPD_PROTO" = "https" && > > + test_might_fail git -c http.sslVerifyStatus=true \ > > + ls-remote "$HTTPD_URL/smart/repo.git" 2>err && > > + ! grep "cannot verify certificate status" err > > +' > > + > > +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 sslVerifyStatus 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 sslVerifyStatus 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 > > +' > > Can we reasonably add tests that send OCSP information and verify that > enabling "sslVerifyStatus" makes this work as expected? > > Patrick ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v4] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-18 14:51 ` Grayson Gordon @ 2026-08-19 8:14 ` Patrick Steinhardt 0 siblings, 0 replies; 38+ messages in thread From: Patrick Steinhardt @ 2026-08-19 8:14 UTC (permalink / raw) To: Grayson Gordon; +Cc: gitster, git Hi, On Tue, Aug 18, 2026 at 10:51:08AM -0400, Grayson Gordon wrote: > Patrick, one hint: we prefer to not top-post on this mailing list and instead answer inline. [snip] > ON GNUTLS VS OPENSSL DIFFERENCES > > I appreciate you including the extra context around GnuTLS 3.8's > GNUTLS_NO_STATUS_REQUEST flag, and curl 8.10 setting it when > "verifystatus" is false. > > Fair enough, said another way, if either of these are true: > Condition 1: curl is being built with GnuTLS version < 3.8. (There's > no NO_STATUS_REQUEST flag to set.) > OR > Condition 2: curl version < 8.10. (Curl's not using the flag.) > > You'll see a discrepancy in cert verification behavior between the > versions of git built with GnuTLS vs OpenSSL. > > While I appreciate the increased precision here, the purpose of my > contribution was to expose the functionality that enables git users to > set it if they so choose. As it stands today, this option is not > presented. So while the discrepancy is what incited me to look deeper, > it isn't the central reason I'm here. That's fair, and I think adding support for OCSP is useful indeed. I just want us to be more accurate in both the commit message and in the docs, as the way is currently written is only partially true and thus misleading both for developers and for readers of git-config(1). [snip] > ON COMPREHENSIVE TESTING > > I'll leave this at you and Junio's discretion. I worked with him > earlier in this thread to avoid introducing another test file and keep > the testing succinct. > Right now these tests are just limited to parsing the config and > applying it to the user-provided remote. > We COULD do the full suite of tests that cover the full range of > cases/behaviors: > - The flag is set AND > - no staple sent (should fail) > - good staple (should pass) > - bad staple (should fail) > etc. > > We're going to need a lot of infrax for that though: > - test CA. > - test server certificate issued by that CA. > - OCSP responder which knows the certificate's status. > - a way for the TLS server to obtain and staple that response. > - a way to control the response so you can test good vs revoked/invalid. > > I set all of that stuff up in my own experiment repo, emulating this > with nginx in docker... Yeah, it's certainly non-trivial to set all of this up as there's a bunch of pieces to it. Something like the below patch would do it, but I'm not a 100% sure whether it's really worth it given the complexity. Patrick diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index a216e5376f..0af506c950 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -25,6 +25,9 @@ # LIB_HTTPD_DAV enable DAV # LIB_HTTPD_SVN enable SVN at given location (e.g. "svn") # LIB_HTTPD_SSL enable SSL +# LIB_HTTPD_OCSP enable OCSP stapling (implies SSL); requires +# the openssl(1) command and needs the caller +# to run start_ocsp_responder after start_httpd # LIB_HTTPD_PROXY enable proxy # # Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at> @@ -171,15 +174,26 @@ prepare_httpd() { ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules" + if test -n "$LIB_HTTPD_OCSP" + then + LIB_HTTPD_SSL=t + fi + if test -n "$LIB_HTTPD_SSL" then HTTPD_PROTO=https - RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \ - -config "$TEST_PATH/ssl.cnf" \ - -new -x509 -nodes \ - -out "$HTTPD_ROOT_PATH/httpd.pem" \ - -keyout "$HTTPD_ROOT_PATH/httpd.pem" + if test -n "$LIB_HTTPD_OCSP" + then + prepare_ocsp_stapling + HTTPD_PARA="$HTTPD_PARA -DOCSP" + else + RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \ + -config "$TEST_PATH/ssl.cnf" \ + -new -x509 -nodes \ + -out "$HTTPD_ROOT_PATH/httpd.pem" \ + -keyout "$HTTPD_ROOT_PATH/httpd.pem" + fi GIT_SSL_NO_VERIFY=t export GIT_SSL_NO_VERIFY HTTPD_PARA="$HTTPD_PARA -DSSL" @@ -250,6 +264,106 @@ stop_httpd() { -f "$TEST_PATH/apache.conf" $HTTPD_PARA -k stop } +restart_httpd () { + httpd_pid=$(cat "$HTTPD_ROOT_PATH/httpd.pid") && + stop_httpd && + while kill -0 "$httpd_pid" 2>/dev/null + do + sleep 1 + done && + "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \ + -f "$TEST_PATH/apache.conf" $HTTPD_PARA \ + -c "Listen 127.0.0.1:$LIB_HTTPD_PORT" -k start +} + +# Set up a certificate authority whose certificate httpd.pem is signed +# with, such that "openssl ocsp" can vouch for (or revoke) it. Used +# instead of the self-signed certificate when LIB_HTTPD_OCSP is set. +prepare_ocsp_stapling () { + LIB_HTTPD_OCSP_PORT=$((LIB_HTTPD_PORT + 10000)) + + # Referenced by ocsp-ca.cnf. + OCSP_CA_DIR="$HTTPD_ROOT_PATH/ocsp-ca" + OCSP_URI="http://127.0.0.1:$LIB_HTTPD_OCSP_PORT" + export OCSP_CA_DIR OCSP_URI + + mkdir -p "$OCSP_CA_DIR/newcerts" && + >"$OCSP_CA_DIR/index.txt" && + echo 1000 >"$OCSP_CA_DIR/serial" && + + openssl req -config "$TEST_PATH/ocsp-ca.cnf" \ + -new -x509 -nodes -days 2 \ + -subj "/CN=git-test-ca" -extensions v3_ca \ + -keyout "$HTTPD_ROOT_PATH/ca.key" \ + -out "$HTTPD_ROOT_PATH/ca.pem" && + openssl req -config "$TEST_PATH/ocsp-ca.cnf" \ + -new -nodes \ + -subj "/CN=127.0.0.1" \ + -keyout "$HTTPD_ROOT_PATH/httpd.key" \ + -out "$HTTPD_ROOT_PATH/httpd.csr" && + openssl ca -config "$TEST_PATH/ocsp-ca.cnf" -batch \ + -cert "$HTTPD_ROOT_PATH/ca.pem" \ + -keyfile "$HTTPD_ROOT_PATH/ca.key" \ + -in "$HTTPD_ROOT_PATH/httpd.csr" \ + -out "$HTTPD_ROOT_PATH/httpd.crt" && + cat "$HTTPD_ROOT_PATH/httpd.key" "$HTTPD_ROOT_PATH/httpd.crt" \ + >"$HTTPD_ROOT_PATH/httpd.pem" +} + +run_ocsp_responder () { + openssl ocsp -port "$LIB_HTTPD_OCSP_PORT" \ + -index "$OCSP_CA_DIR/index.txt" \ + -CA "$HTTPD_ROOT_PATH/ca.pem" \ + -rsigner "$HTTPD_ROOT_PATH/ca.pem" \ + -rkey "$HTTPD_ROOT_PATH/ca.key" \ + -nmin 60 >>"$HTTPD_ROOT_PATH/ocsp.log" 2>&1 & + echo $! >"$HTTPD_ROOT_PATH/ocsp.pid" + + for i in $(test_seq 1 10) + do + if openssl ocsp -no_nonce \ + -CAfile "$HTTPD_ROOT_PATH/ca.pem" \ + -issuer "$HTTPD_ROOT_PATH/ca.pem" \ + -cert "$HTTPD_ROOT_PATH/httpd.crt" \ + -url "$OCSP_URI" >/dev/null 2>&1 + then + return 0 + fi + sleep 1 + done + return 1 +} + +start_ocsp_responder () { + test_atexit stop_ocsp_responder + + if ! run_ocsp_responder + then + cat "$HTTPD_ROOT_PATH"/ocsp.log >&4 2>/dev/null + test_skip_or_die GIT_TEST_HTTPD "OCSP responder setup failed" + fi +} + +stop_ocsp_responder () { + if test -f "$HTTPD_ROOT_PATH/ocsp.pid" + then + kill "$(cat "$HTTPD_ROOT_PATH/ocsp.pid")" 2>/dev/null + rm -f "$HTTPD_ROOT_PATH/ocsp.pid" + fi +} + +# Revoke the certificate used by httpd and make both the OCSP responder +# and httpd aware of it. +revoke_httpd_cert () { + openssl ca -config "$TEST_PATH/ocsp-ca.cnf" \ + -cert "$HTTPD_ROOT_PATH/ca.pem" \ + -keyfile "$HTTPD_ROOT_PATH/ca.key" \ + -revoke "$HTTPD_ROOT_PATH/httpd.crt" && + stop_ocsp_responder && + run_ocsp_responder && + restart_httpd +} + test_http_push_nonff () { REMOTE_REPO=$1 LOCAL_REPO=$2 diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 4149fc1078..f3287566e0 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -242,6 +242,19 @@ SSLSessionCache none SSLEngine On </IfDefine> +<IfDefine OCSP> +<IfModule !mod_socache_shmcb.c> + LoadModule socache_shmcb_module modules/mod_socache_shmcb.so +</IfModule> + +SSLCertificateChainFile ca.pem +SSLUseStapling On +SSLStaplingCache shmcb:ssl_stapling(65536) +# Also staple responses whose certificate status is not "good", so +# that clients get to see e.g. "revoked" responses. +SSLStaplingReturnResponderErrors On +</IfDefine> + <Location /auth/> AuthType Basic AuthName "git-auth" diff --git a/t/lib-httpd/ocsp-ca.cnf b/t/lib-httpd/ocsp-ca.cnf new file mode 100644 index 0000000000..eec4b5b932 --- /dev/null +++ b/t/lib-httpd/ocsp-ca.cnf @@ -0,0 +1,35 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = $ENV::OCSP_CA_DIR +database = $dir/index.txt +new_certs_dir = $dir/newcerts +serial = $dir/serial +default_md = sha256 +default_days = 2 +policy = policy_anything +email_in_dn = no +unique_subject = no +x509_extensions = server_cert + +[ policy_anything ] +commonName = supplied + +[ req ] +default_bits = 2048 +distinguished_name = req_distinguished_name +prompt = no + +[ req_distinguished_name ] +# The subject is always given on the command line via -subj. + +[ v3_ca ] +basicConstraints = critical, CA:TRUE +keyUsage = critical, digitalSignature, keyCertSign, cRLSign +subjectKeyIdentifier = hash + +[ server_cert ] +basicConstraints = CA:FALSE +subjectAltName = IP:127.0.0.1 +authorityInfoAccess = OCSP;URI:$ENV::OCSP_URI diff --git a/t/meson.build b/t/meson.build index 2133c840da..1413baed80 100644 --- a/t/meson.build +++ b/t/meson.build @@ -727,6 +727,7 @@ integration_tests = [ 't5582-fetch-negative-refspec.sh', 't5583-push-branches.sh', 't5584-http-429-retry.sh', + 't5585-http-ssl-ocsp.sh', 't5600-clone-fail-cleanup.sh', 't5601-clone.sh', 't5602-clone-remote-exec.sh', diff --git a/t/t5585-http-ssl-ocsp.sh b/t/t5585-http-ssl-ocsp.sh new file mode 100755 index 0000000000..c279f0c9ca --- /dev/null +++ b/t/t5585-http-ssl-ocsp.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +test_description='test verification of stapled OCSP responses via http.sslVerifyStatus' + +. ./test-lib.sh + +LIB_HTTPD_OCSP=1 +. "$TEST_DIRECTORY"/lib-httpd.sh + +start_httpd +start_ocsp_responder + +test_expect_success 'setup repository' ' + test_commit one && + git init --bare "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git push "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" HEAD:refs/heads/main +' + +with_ssl_verification () { + ( + sane_unset GIT_SSL_NO_VERIFY && + GIT_SSL_CAINFO="$HTTPD_ROOT_PATH/ca.pem" "$@" + ) +} + +test_lazy_prereq SSL_VERIFYSTATUS ' + test_might_fail git -c http.sslVerifyStatus=true \ + ls-remote "$HTTPD_URL/smart/repo.git" 2>err && + ! grep "http.sslVerifyStatus is set" err +' + +test_expect_success SSL_VERIFYSTATUS 'certificate verification works against test CA' ' + with_ssl_verification git ls-remote "$HTTPD_URL/smart/repo.git" >actual && + test_line_count -gt 0 actual +' + +test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled "good" OCSP response' ' + with_ssl_verification git -c http.sslVerifyStatus=true \ + ls-remote "$HTTPD_URL/smart/repo.git" >actual && + test_line_count -gt 0 actual +' + +test_expect_success SSL_VERIFYSTATUS 'revoked certificate is rejected' ' + revoke_httpd_cert && + with_ssl_verification test_must_fail git -c http.sslVerifyStatus=true \ + ls-remote "$HTTPD_URL/smart/repo.git" 2>err && + test_grep -i -e "ocsp" -e "revocation" -e "revoked" -e "certificate status" err +' + +test_expect_success SSL_VERIFYSTATUS 'revoked certificate is accepted without http.sslVerifyStatus' ' + with_ssl_verification git ls-remote "$HTTPD_URL/smart/repo.git" >actual && + test_line_count -gt 0 actual +' + +test_done ^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH v4] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-18 7:50 ` Patrick Steinhardt 2026-08-18 14:51 ` Grayson Gordon @ 2026-08-18 16:40 ` Junio C Hamano 1 sibling, 0 replies; 38+ messages in thread From: Junio C Hamano @ 2026-08-18 16:40 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: graysongordon-gl, git Patrick Steinhardt <ps@pks.im> writes: > This is only part of the story though: GnuTLS 3.8 introduced > GNUTLS_NO_STATUS_REQUEST, and curl 8.10 started to set that option in > case of `!verifystatus`. So with new-enough versions of both libraries, > Git behaves the same no matter whether we use OpenSSL or GnuTLS as > backend. See also aeb1a281ca (gtls: fix OCSP stapling management, > 2024-08-20) in curl. Thanks for additional details. >> 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. > > But... don't we still leave the default to libcurl? If > "http.sslVerifyStatus" is not set then we don't touch > `CURLOPT_SSL_VERIFYSTATUS`, either. > > I might be misreading this though, as the whole commit message is quite > hard to digest. I'd assume that this is because it's generated by AI, > and it added a lot of the usual weird phrases to the message. It might > be a good idea to adapt the message to have a bit more of a human touch > to it. I too had trouble figuring out what the proposed log message really wanted to say, but I wrote it off, blaming the difficulty on a language barrier. But as you said, perhaps it is because it was written by something that does not truly understand what it is talking about. It may not have to explain things to readers as if they were 5 years old, but it is definitely necessary to explain well to readers as if they were humans with average intelligence ;-). ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH v5] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-13 16:06 ` Junio C Hamano 2026-08-17 18:52 ` [PATCH v4] " graysongordon-gl @ 2026-08-18 19:37 ` graysongordon-gl 2026-08-18 20:12 ` Junio C Hamano 2026-08-18 21:48 ` [PATCH v6] " graysongordon-gl 2 siblings, 1 reply; 38+ messages in thread From: graysongordon-gl @ 2026-08-18 19:37 UTC (permalink / raw) To: git; +Cc: gitster, peff, avarab, ps, Grayson Gordon From: Grayson Gordon <graysongordon1@gmail.com> git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the OCSP "Certificate Status Request" extension and any stapled response a server sends is ignored, including responses that explicitly state the certificate has been revoked. Add an http.sslVerifyStatus boolean that maps to CURLOPT_SSL_VERIFYSTATUS. http_options() is already the collect_fn for a urlmatch config, so the per-URL form works with no changes: git config http.https://example.com/.sslVerifyStatus true Defaults to false/"off". This is due to the nature of the OCSP protocol. If enabled, git would expect to receive OCSP stapled responses. If the stapled responses were not present, the connection would be blocked as the status of the server's certificate could not be verified. This would break connections to legitimate services that don't use OCSP as their certificate revocation mechanism. If the backend can't check the staple, curl_easy_setopt() returns CURLE_NOT_BUILT_IN. Error message includes curl_easy_strerror() with the option name to enable users to more easily identify a libcurl built without status verification. CURLOPT_SSL_VERIFYSTATUS has existed since libcurl 7.41.0, below our 7.61.0 floor, so no version guard is needed. Tests are in t5551. Signed-off-by: Grayson Gordon <graysongordon1@gmail.com> --- Documentation/config/http.adoc | 9 +++++++++ http.c | 14 ++++++++++++++ t/t5551-http-fetch-smart.sh | 29 +++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc index 792a71b413..6bc2e3823d 100644 --- a/Documentation/config/http.adoc +++ b/Documentation/config/http.adoc @@ -196,6 +196,15 @@ 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. + 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 caccf2108e..4a4dd40fe2 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)) @@ -1133,6 +1138,15 @@ static CURL *get_curl_handle(void) curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L); } + if (curl_ssl_verify_status) { + CURLcode ret = curl_easy_setopt(result, + CURLOPT_SSL_VERIFYSTATUS, 1L); + if (ret != CURLE_OK) + die(_("http.sslVerifyStatus is set, but could not " + "enable OCSP status verification: %s"), + curl_easy_strerror(ret)); + } + if (curl_http_version) { long opt; if (!get_curl_http_version_opt(curl_http_version, &opt)) { diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index 805bec025c..75ab07f031 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -680,6 +680,35 @@ test_expect_success 'passing hostname resolution information works' ' git -c "http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1" ls-remote "$BOGUS_HTTPD_URL/smart/repo.git" >/dev/null ' +test_lazy_prereq SSL_VERIFYSTATUS ' + test "$HTTPD_PROTO" = "https" && + test_might_fail git -c http.sslVerifyStatus=true \ + ls-remote "$HTTPD_URL/smart/repo.git" 2>err && + ! grep "http.sslVerifyStatus is set" err +' + +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 sslVerifyStatus 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 sslVerifyStatus 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 +' + # here user%40host is the URL-encoded version of user@host, # which is our intentionally-odd username to catch parsing errors url_user=$HTTPD_URL_USER/auth/smart/repo.git -- 2.50.1 (Apple Git-155) ^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH v5] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-18 19:37 ` [PATCH v5] " graysongordon-gl @ 2026-08-18 20:12 ` Junio C Hamano 2026-08-18 21:22 ` Grayson Gordon 0 siblings, 1 reply; 38+ messages in thread From: Junio C Hamano @ 2026-08-18 20:12 UTC (permalink / raw) To: graysongordon-gl; +Cc: git, peff, avarab, ps graysongordon-gl <graysongordon1@gmail.com> writes: > +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. I do not see us describe a knob or setting that can stop the operation depending on some condition as "fail-closed". Can we rephrase this for regular human beings? Perhaps Whether to refuse connecting to the server when its certificate has been revoked. Default to false, allowing connection even when its certificate is not known to be still valid. or something like that might be a good starting point. After all, the "check revocation and/or validity" is *not* the primary objective from the end-user's point of view. Ensuring that they do not talk to suspicious servers is. Thanks. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v5] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-18 20:12 ` Junio C Hamano @ 2026-08-18 21:22 ` Grayson Gordon 0 siblings, 0 replies; 38+ messages in thread From: Grayson Gordon @ 2026-08-18 21:22 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, peff, avarab, ps Junio, By "fail-closed" I was specifically referring to the case where no OCSP stapled response was provided. Failing open in this context would mean that, despite verifystatus being set, a response with no stapled response is ALLOWED. Maybe I'm just a very irregular human being lol. I'll update the adoc to be similar to what you provided. I refer to the edge cases/different behaviors that we talked about earlier in this thread with the older curl and gnutls versions in the commit message and keep the adoc as simple as possible. - Grayson On Tue, Aug 18, 2026 at 4:12 PM Junio C Hamano <gitster@pobox.com> wrote: > > graysongordon-gl <graysongordon1@gmail.com> writes: > > > +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. > > I do not see us describe a knob or setting that can stop the > operation depending on some condition as "fail-closed". Can we > rephrase this for regular human beings? Perhaps > > Whether to refuse connecting to the server when its > certificate has been revoked. Default to false, allowing > connection even when its certificate is not known to be > still valid. > > or something like that might be a good starting point. After all, > the "check revocation and/or validity" is *not* the primary > objective from the end-user's point of view. Ensuring that they do > not talk to suspicious servers is. > > Thanks. ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-13 16:06 ` Junio C Hamano 2026-08-17 18:52 ` [PATCH v4] " graysongordon-gl 2026-08-18 19:37 ` [PATCH v5] " graysongordon-gl @ 2026-08-18 21:48 ` graysongordon-gl 2026-08-26 22:01 ` Junio C Hamano 2 siblings, 1 reply; 38+ messages in thread From: graysongordon-gl @ 2026-08-18 21:48 UTC (permalink / raw) To: git; +Cc: gitster, peff, avarab, ps, Grayson Gordon From: Grayson Gordon <graysongordon1@gmail.com> git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the OCSP "Certificate Status Request" extension and any stapled response a server sends is ignored, including responses that explicitly state the certificate has been revoked. Add an http.sslVerifyStatus boolean that maps to CURLOPT_SSL_VERIFYSTATUS. http_options() is already the collect_fn for a urlmatch config, so the per-URL form works with no changes: git config http.https://example.com/.sslVerifyStatus true Defaults to false/"off". This is due to the nature of the OCSP protocol. If enabled, git would expect to receive OCSP stapled responses. If the stapled responses were not present, the connection would be blocked as the status of the server's certificate could not be verified. This would break connections to legitimate services that don't use OCSP as their certificate revocation mechanism. If the backend can't check the staple, curl_easy_setopt() returns CURLE_NOT_BUILT_IN. Error message includes curl_easy_strerror() with the option name to enable users to more easily identify a libcurl built without status verification. CURLOPT_SSL_VERIFYSTATUS has existed since libcurl 7.41.0, below our 7.61.0 floor, so no version guard is needed. Tests are in t5551. Additional note - I put this in http.adoc: "Defaults to false, which allows connections to remotes without validating whether or not the certificate has been revoked by the certificate authority." Technically, there are cases with older combinations of GnuTLS and curl where the revocation logic actually WILL NOT allow such connections. Search "OCSP" in the lore for full details. Signed-off-by: Grayson Gordon <graysongordon1@gmail.com> --- Documentation/config/http.adoc | 14 ++++++++++++++ http.c | 14 ++++++++++++++ t/t5551-http-fetch-smart.sh | 29 +++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc index 792a71b413..b54f627969 100644 --- a/Documentation/config/http.adoc +++ b/Documentation/config/http.adoc @@ -196,6 +196,20 @@ 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, which + allows connections to servers without validating if the + certificate has been revoked by the certificate authority. + Enabling this option will prevent connections to servers that + have a certificate status other than "good" per RFC 6960. + Connections to servers that do not return a stapled response + will also be refused. ++ +Set it per remote, e.g. +`http.https://example.com/.sslVerifyStatus`, rather than globally. + 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 caccf2108e..4a4dd40fe2 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)) @@ -1133,6 +1138,15 @@ static CURL *get_curl_handle(void) curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L); } + if (curl_ssl_verify_status) { + CURLcode ret = curl_easy_setopt(result, + CURLOPT_SSL_VERIFYSTATUS, 1L); + if (ret != CURLE_OK) + die(_("http.sslVerifyStatus is set, but could not " + "enable OCSP status verification: %s"), + curl_easy_strerror(ret)); + } + if (curl_http_version) { long opt; if (!get_curl_http_version_opt(curl_http_version, &opt)) { diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index 805bec025c..75ab07f031 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -680,6 +680,35 @@ test_expect_success 'passing hostname resolution information works' ' git -c "http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1" ls-remote "$BOGUS_HTTPD_URL/smart/repo.git" >/dev/null ' +test_lazy_prereq SSL_VERIFYSTATUS ' + test "$HTTPD_PROTO" = "https" && + test_might_fail git -c http.sslVerifyStatus=true \ + ls-remote "$HTTPD_URL/smart/repo.git" 2>err && + ! grep "http.sslVerifyStatus is set" err +' + +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 sslVerifyStatus 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 sslVerifyStatus 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 +' + # here user%40host is the URL-encoded version of user@host, # which is our intentionally-odd username to catch parsing errors url_user=$HTTPD_URL_USER/auth/smart/repo.git -- 2.50.1 (Apple Git-155) ^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-18 21:48 ` [PATCH v6] " graysongordon-gl @ 2026-08-26 22:01 ` Junio C Hamano 2026-08-28 13:51 ` Grayson Gordon 0 siblings, 1 reply; 38+ messages in thread From: Junio C Hamano @ 2026-08-26 22:01 UTC (permalink / raw) To: git; +Cc: graysongordon-gl, peff, avarab, ps graysongordon-gl <graysongordon1@gmail.com> writes: > From: Grayson Gordon <graysongordon1@gmail.com> > > git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the > OCSP "Certificate Status Request" extension and any stapled response a > server sends is ignored, including responses that explicitly state the > certificate has been revoked. > > Add an http.sslVerifyStatus boolean that maps to > CURLOPT_SSL_VERIFYSTATUS. > http_options() is already the collect_fn for a urlmatch config, so the > per-URL form works with no changes: > > git config http.https://example.com/.sslVerifyStatus true > > Defaults to false/"off". This is due to the nature of the OCSP protocol. > If enabled, git would expect to receive OCSP stapled responses. If the > stapled responses were not present, the connection would be blocked as > the status of the server's certificate could not be verified. This would > break connections to legitimate services that don't use OCSP as their > certificate revocation mechanism. > > If the backend can't check the staple, curl_easy_setopt() returns > CURLE_NOT_BUILT_IN. Error message includes curl_easy_strerror() with > the option name to enable users to more easily identify a libcurl > built without status verification. > > CURLOPT_SSL_VERIFYSTATUS has existed since libcurl 7.41.0, below our > 7.61.0 floor, so no version guard is needed. > > Tests are in t5551. > > Additional note - I put this in http.adoc: > "Defaults to false, which > allows connections to remotes without validating whether or not > the certificate has been revoked by the certificate authority." > > Technically, there are cases with older combinations of GnuTLS > and curl where the revocation logic actually WILL NOT allow > such connections. Search "OCSP" in the lore for full details. > > Signed-off-by: Grayson Gordon <graysongordon1@gmail.com> > --- > Documentation/config/http.adoc | 14 ++++++++++++++ > http.c | 14 ++++++++++++++ > t/t5551-http-fetch-smart.sh | 29 +++++++++++++++++++++++++++++ > 3 files changed, 57 insertions(+) Are folks happy with this iteration? I think we have already reached the point of diminishing returns before the thread went dark. Thanks. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-26 22:01 ` Junio C Hamano @ 2026-08-28 13:51 ` Grayson Gordon 2026-08-28 17:20 ` Junio C Hamano 0 siblings, 1 reply; 38+ messages in thread From: Grayson Gordon @ 2026-08-28 13:51 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, peff, avarab, ps Junio, Yes, I was hoping for clarity on how thorough we wanted the testing to be. Patrick added a lot of great stuff that I’m happy to use if that’s your preference, but we also talked about wanting to keep the tests succinct. Please let me know what you feel is most appropriate. - Grayson On Wed, Aug 26, 2026 at 6:01 PM Junio C Hamano <gitster@pobox.com> wrote: > > graysongordon-gl <graysongordon1@gmail.com> writes: > > > From: Grayson Gordon <graysongordon1@gmail.com> > > > > git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the > > OCSP "Certificate Status Request" extension and any stapled response a > > server sends is ignored, including responses that explicitly state the > > certificate has been revoked. > > > > Add an http.sslVerifyStatus boolean that maps to > > CURLOPT_SSL_VERIFYSTATUS. > > http_options() is already the collect_fn for a urlmatch config, so the > > per-URL form works with no changes: > > > > git config http.https://example.com/.sslVerifyStatus true > > > > Defaults to false/"off". This is due to the nature of the OCSP protocol. > > If enabled, git would expect to receive OCSP stapled responses. If the > > stapled responses were not present, the connection would be blocked as > > the status of the server's certificate could not be verified. This would > > break connections to legitimate services that don't use OCSP as their > > certificate revocation mechanism. > > > > If the backend can't check the staple, curl_easy_setopt() returns > > CURLE_NOT_BUILT_IN. Error message includes curl_easy_strerror() with > > the option name to enable users to more easily identify a libcurl > > built without status verification. > > > > CURLOPT_SSL_VERIFYSTATUS has existed since libcurl 7.41.0, below our > > 7.61.0 floor, so no version guard is needed. > > > > Tests are in t5551. > > > > Additional note - I put this in http.adoc: > > "Defaults to false, which > > allows connections to remotes without validating whether or not > > the certificate has been revoked by the certificate authority." > > > > Technically, there are cases with older combinations of GnuTLS > > and curl where the revocation logic actually WILL NOT allow > > such connections. Search "OCSP" in the lore for full details. > > > > Signed-off-by: Grayson Gordon <graysongordon1@gmail.com> > > --- > > Documentation/config/http.adoc | 14 ++++++++++++++ > > http.c | 14 ++++++++++++++ > > t/t5551-http-fetch-smart.sh | 29 +++++++++++++++++++++++++++++ > > 3 files changed, 57 insertions(+) > > Are folks happy with this iteration? I think we have already > reached the point of diminishing returns before the thread went > dark. > > Thanks. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-28 13:51 ` Grayson Gordon @ 2026-08-28 17:20 ` Junio C Hamano 2026-08-31 6:56 ` Patrick Steinhardt 0 siblings, 1 reply; 38+ messages in thread From: Junio C Hamano @ 2026-08-28 17:20 UTC (permalink / raw) To: Grayson Gordon; +Cc: git, peff, avarab, ps Grayson Gordon <graysongordon1@gmail.com> writes: > Junio, > > Yes, I was hoping for clarity on how thorough we wanted the testing to > be. Patrick added a lot of great stuff that I’m happy to use if that’s > your preference, but we also talked about wanting to keep the tests > succinct. Please let me know what you feel is most appropriate. If you can keep them succinct but still test the essential bits, that would be great, but I am not sure if that is a great question to ask me ;-) Patrick? You said "not 100% sure given the complexity", but which parts make you feel iffy? They do look involved but seem to cover the situations we do care about, except we seem not to test when the server does not explicitly say "this is still good", or am I not reading the tests correctly? Thanks. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-28 17:20 ` Junio C Hamano @ 2026-08-31 6:56 ` Patrick Steinhardt 2026-08-31 14:16 ` Junio C Hamano 0 siblings, 1 reply; 38+ messages in thread From: Patrick Steinhardt @ 2026-08-31 6:56 UTC (permalink / raw) To: Junio C Hamano; +Cc: Grayson Gordon, git, peff, avarab On Fri, Aug 28, 2026 at 10:20:31AM -0700, Junio C Hamano wrote: > Grayson Gordon <graysongordon1@gmail.com> writes: > > > Junio, > > > > Yes, I was hoping for clarity on how thorough we wanted the testing to > > be. Patrick added a lot of great stuff that I’m happy to use if that’s > > your preference, but we also talked about wanting to keep the tests > > succinct. Please let me know what you feel is most appropriate. > > If you can keep them succinct but still test the essential bits, > that would be great, but I am not sure if that is a great question > to ask me ;-) Patrick? You said "not 100% sure given the complexity", > but which parts make you feel iffy? Setting up OCSP is quite a pain, and that is what made me feel iffy. That being said, given that this is a security-focussed feature I feel like we should probably bite the bullet and verify that we indeed know to reject servers that respond with invalid stapled responses. And given that this whole setup is now getting more complex I feel like it's worth it to also allocate a new test number for it. > They do look involved but seem to cover the situations we do care > about, except we seem not to test when the server does not explicitly > say "this is still good", or am I not reading the tests correctly? Isn't the following test covering that scenario? Or am I misreading? test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled "good" OCSP response' with_ssl_verification git -c http.sslVerifyStatus=true \ ls-remote "$HTTPD_URL/smart/repo.git" >actual && test_line_count -gt 0 actual ' Thanks! Patrick ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-31 6:56 ` Patrick Steinhardt @ 2026-08-31 14:16 ` Junio C Hamano 2026-08-31 14:24 ` Patrick Steinhardt 0 siblings, 1 reply; 38+ messages in thread From: Junio C Hamano @ 2026-08-31 14:16 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: Grayson Gordon, git, peff, avarab Patrick Steinhardt <ps@pks.im> writes: >> They do look involved but seem to cover the situations we do care >> about, except we seem not to test when the server does not explicitly >> say "this is still good", or am I not reading the tests correctly? > > Isn't the following test covering that scenario? Or am I misreading? > > test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled "good" OCSP response' > with_ssl_verification git -c http.sslVerifyStatus=true \ > ls-remote "$HTTPD_URL/smart/repo.git" >actual && > test_line_count -gt 0 actual > ' Probably I misstated. What I meant was a reaction to "fail close" floated earlier. A server does not explicitly give stapled good, and the client says "this is not known-good" and not talking to it. I.e. 'fetch fails without stapled "good"' ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-31 14:16 ` Junio C Hamano @ 2026-08-31 14:24 ` Patrick Steinhardt 2026-08-31 14:31 ` Junio C Hamano 0 siblings, 1 reply; 38+ messages in thread From: Patrick Steinhardt @ 2026-08-31 14:24 UTC (permalink / raw) To: Junio C Hamano; +Cc: Grayson Gordon, git, peff, avarab On Mon, Aug 31, 2026 at 07:16:54AM -0700, Junio C Hamano wrote: > Patrick Steinhardt <ps@pks.im> writes: > > >> They do look involved but seem to cover the situations we do care > >> about, except we seem not to test when the server does not explicitly > >> say "this is still good", or am I not reading the tests correctly? > > > > Isn't the following test covering that scenario? Or am I misreading? > > > > test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled "good" OCSP response' > > with_ssl_verification git -c http.sslVerifyStatus=true \ > > ls-remote "$HTTPD_URL/smart/repo.git" >actual && > > test_line_count -gt 0 actual > > ' > > Probably I misstated. What I meant was a reaction to "fail close" > floated earlier. A server does not explicitly give stapled good, > and the client says "this is not known-good" and not talking to it. > I.e. 'fetch fails without stapled "good"' Ah, I think you're correct, my tests didn't include that. But Grayson's already did as it doesn't require any setup, so that's why I didn't include it specifically. Patrick ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-31 14:24 ` Patrick Steinhardt @ 2026-08-31 14:31 ` Junio C Hamano 2026-09-08 12:55 ` Grayson Gordon 2026-09-15 16:23 ` [PATCH v7] " graysongordon-gl 0 siblings, 2 replies; 38+ messages in thread From: Junio C Hamano @ 2026-08-31 14:31 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: Grayson Gordon, git, peff, avarab Patrick Steinhardt <ps@pks.im> writes: > On Mon, Aug 31, 2026 at 07:16:54AM -0700, Junio C Hamano wrote: >> Patrick Steinhardt <ps@pks.im> writes: >> >> >> They do look involved but seem to cover the situations we do care >> >> about, except we seem not to test when the server does not explicitly >> >> say "this is still good", or am I not reading the tests correctly? >> > >> > Isn't the following test covering that scenario? Or am I misreading? >> > >> > test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled "good" OCSP response' >> > with_ssl_verification git -c http.sslVerifyStatus=true \ >> > ls-remote "$HTTPD_URL/smart/repo.git" >actual && >> > test_line_count -gt 0 actual >> > ' >> >> Probably I misstated. What I meant was a reaction to "fail close" >> floated earlier. A server does not explicitly give stapled good, >> and the client says "this is not known-good" and not talking to it. >> I.e. 'fetch fails without stapled "good"' > > Ah, I think you're correct, my tests didn't include that. But Grayson's > already did as it doesn't require any setup, so that's why I didn't > include it specifically. Ah, I missed that. So a combined patch taking the best parts from both sides is what we want. Thanks for helping move the topic forward. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-31 14:31 ` Junio C Hamano @ 2026-09-08 12:55 ` Grayson Gordon 2026-09-15 16:23 ` [PATCH v7] " graysongordon-gl 1 sibling, 0 replies; 38+ messages in thread From: Grayson Gordon @ 2026-09-08 12:55 UTC (permalink / raw) To: Junio C Hamano; +Cc: Patrick Steinhardt, git, peff, avarab hello all, Sorry I've been away awhile. I'll put together the combined patch and shoot it over. - Grayson On Mon, Aug 31, 2026 at 10:31 AM Junio C Hamano <gitster@pobox.com> wrote: > > Patrick Steinhardt <ps@pks.im> writes: > > > On Mon, Aug 31, 2026 at 07:16:54AM -0700, Junio C Hamano wrote: > >> Patrick Steinhardt <ps@pks.im> writes: > >> > >> >> They do look involved but seem to cover the situations we do care > >> >> about, except we seem not to test when the server does not explicitly > >> >> say "this is still good", or am I not reading the tests correctly? > >> > > >> > Isn't the following test covering that scenario? Or am I misreading? > >> > > >> > test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled "good" OCSP response' > >> > with_ssl_verification git -c http.sslVerifyStatus=true \ > >> > ls-remote "$HTTPD_URL/smart/repo.git" >actual && > >> > test_line_count -gt 0 actual > >> > ' > >> > >> Probably I misstated. What I meant was a reaction to "fail close" > >> floated earlier. A server does not explicitly give stapled good, > >> and the client says "this is not known-good" and not talking to it. > >> I.e. 'fetch fails without stapled "good"' > > > > Ah, I think you're correct, my tests didn't include that. But Grayson's > > already did as it doesn't require any setup, so that's why I didn't > > include it specifically. > > Ah, I missed that. So a combined patch taking the best parts from > both sides is what we want. Thanks for helping move the topic > forward. ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-08-31 14:31 ` Junio C Hamano 2026-09-08 12:55 ` Grayson Gordon @ 2026-09-15 16:23 ` graysongordon-gl 2026-09-16 19:29 ` Junio C Hamano ` (2 more replies) 1 sibling, 3 replies; 38+ messages in thread From: graysongordon-gl @ 2026-09-15 16:23 UTC (permalink / raw) To: git; +Cc: gitster, ps, peff, avarab, Grayson Gordon From: Grayson Gordon <graysongordon1@gmail.com> git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the OCSP "Certificate Status Request" extension and any stapled response a server sends is ignored, including responses that explicitly state the certificate has been revoked. Add an http.sslVerifyStatus boolean that maps to CURLOPT_SSL_VERIFYSTATUS. http_options() is already the collect_fn for a urlmatch config, so the per-URL form works with no changes: git config http.https://example.com/.sslVerifyStatus true Defaults to false/"off". This is due to the nature of the OCSP protocol. If enabled, git would expect to receive OCSP stapled responses. If the stapled responses were not present, the connection would be blocked as the status of the server's certificate could not be verified. This would break connections to legitimate services that don't use OCSP as their certificate revocation mechanism. If the backend can't check the staple, curl_easy_setopt() returns CURLE_NOT_BUILT_IN. The error message includes curl_easy_strerror() along with the option name, so a libcurl built without status verification is easy to identify. CURLOPT_SSL_VERIFYSTATUS has existed since libcurl 7.41.0, below our 7.61.0 floor, so no version guard is needed. The tests that need no OCSP infrastructure stay in t5551, which t5559 runs over https. The rest need a certificate authority, a responder to answer for it and a server configured to staple, so lib-httpd gains an opt-in LIB_HTTPD_OCSP mode and t5585 uses it to check that a "good" staple is accepted, a "revoked" one is refused, and that the revoked one is ignored when the option is off. Signed-off-by: Grayson Gordon <graysongordon1@gmail.com> --- Junio, Patrick: this is the combined version we discussed. The cases that need no OCSP setup stayed in t5551, since t5559 already runs that file over https, and everything that needs a responder is in the new t5585. A note on the testing stuff. SSLUseStapling makes apache create a mutex in a compiled-in system-wide runtime directory. I set DefaultRuntimeDir in the OCSP block to keep that mutex in the server root, the other way resolved to a path on my box that didn't exist and prevented the server from starting. Changes since v6: - added t5585 and LIB_HTTPD_OCSP support in lib-httpd, taken from Patrick's patch - moved the SSL_VERIFYSTATUS prereq into lib-httpd.sh so both files share one definition Documentation/config/http.adoc | 14 ++++ http.c | 14 ++++ t/lib-httpd.sh | 130 +++++++++++++++++++++++++++++++-- t/lib-httpd/apache.conf | 16 ++++ t/lib-httpd/ocsp-ca.cnf | 35 +++++++++ t/meson.build | 1 + t/t5551-http-fetch-smart.sh | 22 ++++++ t/t5585-http-ssl-ocsp.sh | 55 ++++++++++++++ 8 files changed, 282 insertions(+), 5 deletions(-) diff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc index 792a71b413..b54f627969 100644 --- a/Documentation/config/http.adoc +++ b/Documentation/config/http.adoc @@ -196,6 +196,20 @@ 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, which + allows connections to servers without validating if the + certificate has been revoked by the certificate authority. + Enabling this option will prevent connections to servers that + have a certificate status other than "good" per RFC 6960. + Connections to servers that do not return a stapled response + will also be refused. ++ +Set it per remote, e.g. +`http.https://example.com/.sslVerifyStatus`, rather than globally. + 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 c8fcfd7693..9c2892cafb 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)) @@ -1133,6 +1138,15 @@ static CURL *get_curl_handle(void) curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L); } + if (curl_ssl_verify_status) { + CURLcode ret = curl_easy_setopt(result, + CURLOPT_SSL_VERIFYSTATUS, 1L); + if (ret != CURLE_OK) + die(_("http.sslVerifyStatus is set, but could not " + "enable OCSP status verification: %s"), + curl_easy_strerror(ret)); + } + if (curl_http_version) { long opt; if (!get_curl_http_version_opt(curl_http_version, &opt)) { diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index 115455784c..554b0e44fa 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -25,6 +25,7 @@ # LIB_HTTPD_DAV enable DAV # LIB_HTTPD_SVN enable SVN at given location (e.g. "svn") # LIB_HTTPD_SSL enable SSL +# LIB_HTTPD_OCSP enable OCSP stapling # LIB_HTTPD_PROXY enable proxy # # Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at> @@ -183,15 +184,26 @@ prepare_httpd() { ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules" + if test -n "$LIB_HTTPD_OCSP" + then + LIB_HTTPD_SSL=t + fi + if test -n "$LIB_HTTPD_SSL" then HTTPD_PROTO=https - RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \ - -config "$TEST_PATH/ssl.cnf" \ - -new -x509 -nodes \ - -out "$HTTPD_ROOT_PATH/httpd.pem" \ - -keyout "$HTTPD_ROOT_PATH/httpd.pem" + if test -n "$LIB_HTTPD_OCSP" + then + prepare_ocsp_stapling + HTTPD_PARA="$HTTPD_PARA -DOCSP" + else + RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \ + -config "$TEST_PATH/ssl.cnf" \ + -new -x509 -nodes \ + -out "$HTTPD_ROOT_PATH/httpd.pem" \ + -keyout "$HTTPD_ROOT_PATH/httpd.pem" + fi GIT_SSL_NO_VERIFY=t export GIT_SSL_NO_VERIFY HTTPD_PARA="$HTTPD_PARA -DSSL" @@ -262,6 +274,114 @@ stop_httpd() { -f "$TEST_PATH/apache.conf" $HTTPD_PARA -k stop } +restart_httpd () { + httpd_pid=$(cat "$HTTPD_ROOT_PATH/httpd.pid") && + stop_httpd && + while kill -0 "$httpd_pid" 2>/dev/null + do + sleep 1 + done && + "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \ + -f "$TEST_PATH/apache.conf" $HTTPD_PARA \ + -c "Listen 127.0.0.1:$LIB_HTTPD_PORT" -k start +} + +# Check if the linked libcurl can verify stapled OCSP responses. +test_lazy_prereq SSL_VERIFYSTATUS ' + test "$HTTPD_PROTO" = "https" && + test_might_fail git -c http.sslVerifyStatus=true \ + ls-remote "$HTTPD_URL" 2>err && + ! grep "http.sslVerifyStatus is set" err +' + +# Set up a certificate authority. It issues certificate "httpd.pem" +# and is able to revoke it. Used instead of the self-signed +# certificate when LIB_HTTPD_OCSP is set. +prepare_ocsp_stapling () { + LIB_HTTPD_OCSP_PORT=$((LIB_HTTPD_PORT + 10000)) + + # Referenced by ocsp-ca.cnf. + OCSP_CA_DIR="$HTTPD_ROOT_PATH/ocsp-ca" + OCSP_URI="http://127.0.0.1:$LIB_HTTPD_OCSP_PORT" + export OCSP_CA_DIR OCSP_URI + + mkdir -p "$OCSP_CA_DIR/newcerts" && + >"$OCSP_CA_DIR/index.txt" && + echo 1000 >"$OCSP_CA_DIR/serial" && + + openssl req -config "$TEST_PATH/ocsp-ca.cnf" \ + -new -x509 -nodes -days 2 \ + -subj "/CN=git-test-ca" -extensions v3_ca \ + -keyout "$HTTPD_ROOT_PATH/ca.key" \ + -out "$HTTPD_ROOT_PATH/ca.pem" && + openssl req -config "$TEST_PATH/ocsp-ca.cnf" \ + -new -nodes \ + -subj "/CN=127.0.0.1" \ + -keyout "$HTTPD_ROOT_PATH/httpd.key" \ + -out "$HTTPD_ROOT_PATH/httpd.csr" && + openssl ca -config "$TEST_PATH/ocsp-ca.cnf" -batch \ + -cert "$HTTPD_ROOT_PATH/ca.pem" \ + -keyfile "$HTTPD_ROOT_PATH/ca.key" \ + -in "$HTTPD_ROOT_PATH/httpd.csr" \ + -out "$HTTPD_ROOT_PATH/httpd.crt" && + cat "$HTTPD_ROOT_PATH/httpd.key" "$HTTPD_ROOT_PATH/httpd.crt" \ + >"$HTTPD_ROOT_PATH/httpd.pem" +} + +run_ocsp_responder () { + openssl ocsp -port "$LIB_HTTPD_OCSP_PORT" \ + -index "$OCSP_CA_DIR/index.txt" \ + -CA "$HTTPD_ROOT_PATH/ca.pem" \ + -rsigner "$HTTPD_ROOT_PATH/ca.pem" \ + -rkey "$HTTPD_ROOT_PATH/ca.key" \ + -nmin 60 >>"$HTTPD_ROOT_PATH/ocsp.log" 2>&1 & + echo $! >"$HTTPD_ROOT_PATH/ocsp.pid" + + for i in $(test_seq 1 10) + do + if openssl ocsp -no_nonce \ + -CAfile "$HTTPD_ROOT_PATH/ca.pem" \ + -issuer "$HTTPD_ROOT_PATH/ca.pem" \ + -cert "$HTTPD_ROOT_PATH/httpd.crt" \ + -url "$OCSP_URI" >/dev/null 2>&1 + then + return 0 + fi + sleep 1 + done + return 1 +} + +start_ocsp_responder () { + test_atexit stop_ocsp_responder + + if ! run_ocsp_responder + then + cat "$HTTPD_ROOT_PATH"/ocsp.log >&4 2>/dev/null + test_skip_or_die GIT_TEST_HTTPD "OCSP responder setup failed" + fi +} + +stop_ocsp_responder () { + if test -f "$HTTPD_ROOT_PATH/ocsp.pid" + then + kill "$(cat "$HTTPD_ROOT_PATH/ocsp.pid")" 2>/dev/null + rm -f "$HTTPD_ROOT_PATH/ocsp.pid" + fi +} + +# Revoke the certificate used by httpd and make both the OCSP responder +# and httpd aware of it. +revoke_httpd_cert () { + openssl ca -config "$TEST_PATH/ocsp-ca.cnf" \ + -cert "$HTTPD_ROOT_PATH/ca.pem" \ + -keyfile "$HTTPD_ROOT_PATH/ca.key" \ + -revoke "$HTTPD_ROOT_PATH/httpd.crt" && + stop_ocsp_responder && + run_ocsp_responder && + restart_httpd +} + test_http_push_nonff () { REMOTE_REPO=$1 LOCAL_REPO=$2 diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 4149fc1078..de5ca45bb8 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -242,6 +242,22 @@ SSLSessionCache none SSLEngine On </IfDefine> +<IfDefine OCSP> +<IfModule !mod_socache_shmcb.c> + LoadModule socache_shmcb_module modules/mod_socache_shmcb.so +</IfModule> + +SSLCertificateChainFile ca.pem +SSLUseStapling On +# Stapling needs a mutex, which apache would put in a system-wide +# runtime directory that need not be writable. Keep it in the server +# root, or httpd refuses to start instead of skipping the tests. +DefaultRuntimeDir . +SSLStaplingCache shmcb:ssl_stapling(65536) +# Staple non-"good" responses too, so clients get to see "revoked". +SSLStaplingReturnResponderErrors On +</IfDefine> + <Location /auth/> AuthType Basic AuthName "git-auth" diff --git a/t/lib-httpd/ocsp-ca.cnf b/t/lib-httpd/ocsp-ca.cnf new file mode 100644 index 0000000000..47a58139b5 --- /dev/null +++ b/t/lib-httpd/ocsp-ca.cnf @@ -0,0 +1,35 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = $ENV::OCSP_CA_DIR +database = $dir/index.txt +new_certs_dir = $dir/newcerts +serial = $dir/serial +default_md = sha256 +default_days = 2 +policy = policy_anything +email_in_dn = no +unique_subject = no +x509_extensions = server_cert + +[ policy_anything ] +commonName = supplied + +[ req ] +default_bits = 2048 +distinguished_name = req_distinguished_name +prompt = no + +[ req_distinguished_name ] +# The subject is always given on the command line via -subj. + +[ v3_ca ] +basicConstraints = critical, CA:TRUE +keyUsage = critical, digitalSignature, keyCertSign, cRLSign +subjectKeyIdentifier = hash + +[ server_cert ] +basicConstraints = CA:FALSE +subjectAltName = IP:127.0.0.1 +authorityInfoAccess = OCSP;URI:$ENV::OCSP_URI diff --git a/t/meson.build b/t/meson.build index 3ca7b27104..72cbd12d8f 100644 --- a/t/meson.build +++ b/t/meson.build @@ -728,6 +728,7 @@ integration_tests = [ 't5582-fetch-negative-refspec.sh', 't5583-push-branches.sh', 't5584-http-429-retry.sh', + 't5585-http-ssl-ocsp.sh', 't5600-clone-fail-cleanup.sh', 't5601-clone.sh', 't5602-clone-remote-exec.sh', diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index 805bec025c..c51b14291d 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -680,6 +680,28 @@ test_expect_success 'passing hostname resolution information works' ' git -c "http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1" ls-remote "$BOGUS_HTTPD_URL/smart/repo.git" >/dev/null ' +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 sslVerifyStatus 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 sslVerifyStatus 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 +' + # here user%40host is the URL-encoded version of user@host, # which is our intentionally-odd username to catch parsing errors url_user=$HTTPD_URL_USER/auth/smart/repo.git diff --git a/t/t5585-http-ssl-ocsp.sh b/t/t5585-http-ssl-ocsp.sh new file mode 100755 index 0000000000..0d1310215f --- /dev/null +++ b/t/t5585-http-ssl-ocsp.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +test_description='verification of stapled OCSP responses via http.sslVerifyStatus' + +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh + +LIB_HTTPD_OCSP=1 +. "$TEST_DIRECTORY"/lib-httpd.sh + +start_httpd +start_ocsp_responder + +test_expect_success 'setup repository' ' + test_commit one && + git init --bare "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git push "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" HEAD:refs/heads/main +' + +# lib-httpd.sh exports GIT_SSL_NO_VERIFY, which would keep us from ever +# looking at the certificate. Trust our own CA instead. +with_ssl_verification () { + ( + sane_unset GIT_SSL_NO_VERIFY && + GIT_SSL_CAINFO="$HTTPD_ROOT_PATH/ca.pem" "$@" + ) +} + +test_expect_success SSL_VERIFYSTATUS 'certificate verification works against test CA' ' + with_ssl_verification git ls-remote "$HTTPD_URL/smart/repo.git" >actual && + test_line_count -gt 0 actual +' + +test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled "good" OCSP response' ' + with_ssl_verification git -c http.sslVerifyStatus=true \ + ls-remote "$HTTPD_URL/smart/repo.git" >actual && + test_line_count -gt 0 actual +' + +test_expect_success SSL_VERIFYSTATUS 'revoked certificate is rejected' ' + revoke_httpd_cert && + with_ssl_verification test_must_fail git -c http.sslVerifyStatus=true \ + ls-remote "$HTTPD_URL/smart/repo.git" 2>err && + test_grep -i -e "ocsp" -e "revocation" -e "revoked" -e "certificate status" err +' + +# Depends on the certificate revoked by the preceding test. +test_expect_success SSL_VERIFYSTATUS 'revoked certificate is accepted without http.sslVerifyStatus' ' + with_ssl_verification git ls-remote "$HTTPD_URL/smart/repo.git" >actual && + test_line_count -gt 0 actual +' + +test_done -- 2.50.1 (Apple Git-155) ^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-09-15 16:23 ` [PATCH v7] " graysongordon-gl @ 2026-09-16 19:29 ` Junio C Hamano 2026-09-23 12:42 ` Patrick Steinhardt 2026-09-23 21:07 ` SZEDER Gábor 2 siblings, 0 replies; 38+ messages in thread From: Junio C Hamano @ 2026-09-16 19:29 UTC (permalink / raw) To: graysongordon-gl; +Cc: git, ps, peff, avarab graysongordon-gl <graysongordon1@gmail.com> writes: > From: Grayson Gordon <graysongordon1@gmail.com> > > git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the > OCSP "Certificate Status Request" extension and any stapled response a > server sends is ignored, including responses that explicitly state the > certificate has been revoked. > > Add an http.sslVerifyStatus boolean that maps to > CURLOPT_SSL_VERIFYSTATUS. http_options() is already the collect_fn for a > urlmatch config, so the per-URL form works with no changes: > --- > > Junio, Patrick: this is the combined version we discussed. The > cases that need no OCSP setup stayed in t5551, since t5559 already > runs that file over https, and everything that needs a responder is > in the new t5585. > > A note on the testing stuff. SSLUseStapling makes apache > create a mutex in a compiled-in system-wide runtime directory. > I set DefaultRuntimeDir in the OCSP block to keep that > mutex in the server root, the other way resolved to a path > on my box that didn't exist and prevented the server from starting. > > Changes since v6: > - added t5585 and LIB_HTTPD_OCSP support in lib-httpd, taken > from Patrick's patch > - moved the SSL_VERIFYSTATUS prereq into lib-httpd.sh so both > files share one definition The updated tests look good; will replace. Thanks. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-09-15 16:23 ` [PATCH v7] " graysongordon-gl 2026-09-16 19:29 ` Junio C Hamano @ 2026-09-23 12:42 ` Patrick Steinhardt 2026-09-23 21:43 ` Junio C Hamano 2026-09-23 21:07 ` SZEDER Gábor 2 siblings, 1 reply; 38+ messages in thread From: Patrick Steinhardt @ 2026-09-23 12:42 UTC (permalink / raw) To: graysongordon-gl; +Cc: git, gitster, peff, avarab On Tue, Sep 15, 2026 at 12:23:48PM -0400, graysongordon-gl wrote: > From: Grayson Gordon <graysongordon1@gmail.com> > > git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the > OCSP "Certificate Status Request" extension and any stapled response a > server sends is ignored, including responses that explicitly state the > certificate has been revoked. > > Add an http.sslVerifyStatus boolean that maps to > CURLOPT_SSL_VERIFYSTATUS. http_options() is already the collect_fn for a > urlmatch config, so the per-URL form works with no changes: > > git config http.https://example.com/.sslVerifyStatus true > > Defaults to false/"off". This is due to the nature of the OCSP protocol. > If enabled, git would expect to receive OCSP stapled responses. If the > stapled responses were not present, the connection would be blocked as > the status of the server's certificate could not be verified. This would > break connections to legitimate services that don't use OCSP as their > certificate revocation mechanism. > > If the backend can't check the staple, curl_easy_setopt() returns > CURLE_NOT_BUILT_IN. The error message includes curl_easy_strerror() > along with the option name, so a libcurl built without status > verification is easy to identify. Nit: I feel like this paragraph is excessive information, as it doesn't give the reviewer any additional context over what the code already states. > CURLOPT_SSL_VERIFYSTATUS has existed since libcurl 7.41.0, below our > 7.61.0 floor, so no version guard is needed. > > The tests that need no OCSP infrastructure stay in t5551, which t5559 > runs over https. The rest need a certificate authority, a responder to > answer for it and a server configured to staple, so lib-httpd gains an > opt-in LIB_HTTPD_OCSP mode and t5585 uses it to check that a "good" > staple is accepted, a "revoked" one is refused, and that the revoked one > is ignored when the option is off. Nit: Likewise, this paragraph doesn't add much value. Other than that I'm happy with this patch. I'll leave it to you (or others) to decide whether this requires another reroll to address the two nits. Thanks! Patrick ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-09-23 12:42 ` Patrick Steinhardt @ 2026-09-23 21:43 ` Junio C Hamano 0 siblings, 0 replies; 38+ messages in thread From: Junio C Hamano @ 2026-09-23 21:43 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: graysongordon-gl, git, peff, avarab Patrick Steinhardt <ps@pks.im> writes: > Nit: Likewise, this paragraph doesn't add much value. > > Other than that I'm happy with this patch. I'll leave it to you (or > others) to decide whether this requires another reroll to address the > two nits. SZEDER reports breakages with this topic. https://lore.kernel.org/git/arQ%2FnOH+o3XwQFD%2F@szeder.dev/ Since we are not in a hurry to take this topic, let me revert it out of 'next' and give it time to mature. When the reroll comes, we can critique these overly verbose words without much meaning again. Thanks. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-09-15 16:23 ` [PATCH v7] " graysongordon-gl 2026-09-16 19:29 ` Junio C Hamano 2026-09-23 12:42 ` Patrick Steinhardt @ 2026-09-23 21:07 ` SZEDER Gábor 2026-09-23 21:47 ` Junio C Hamano 2 siblings, 1 reply; 38+ messages in thread From: SZEDER Gábor @ 2026-09-23 21:07 UTC (permalink / raw) To: graysongordon-gl, ps; +Cc: git, gitster, peff, avarab On Tue, Sep 15, 2026 at 12:23:48PM -0400, graysongordon-gl wrote: > From: Grayson Gordon <graysongordon1@gmail.com> > > git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the > OCSP "Certificate Status Request" extension and any stapled response a > server sends is ignored, including responses that explicitly state the > certificate has been revoked. > > Add an http.sslVerifyStatus boolean that maps to > CURLOPT_SSL_VERIFYSTATUS. http_options() is already the collect_fn for a > urlmatch config, so the per-URL form works with no changes: > > git config http.https://example.com/.sslVerifyStatus true > > Defaults to false/"off". This is due to the nature of the OCSP protocol. > If enabled, git would expect to receive OCSP stapled responses. If the > stapled responses were not present, the connection would be blocked as > the status of the server's certificate could not be verified. This would > break connections to legitimate services that don't use OCSP as their > certificate revocation mechanism. > > If the backend can't check the staple, curl_easy_setopt() returns > CURLE_NOT_BUILT_IN. The error message includes curl_easy_strerror() > along with the option name, so a libcurl built without status > verification is easy to identify. > > CURLOPT_SSL_VERIFYSTATUS has existed since libcurl 7.41.0, below our > 7.61.0 floor, so no version guard is needed. > > The tests that need no OCSP infrastructure stay in t5551, which t5559 > runs over https. The rest need a certificate authority, a responder to > answer for it and a server configured to staple, so lib-httpd gains an > opt-in LIB_HTTPD_OCSP mode and t5585 uses it to check that a "good" > staple is accepted, a "revoked" one is refused, and that the revoked one > is ignored when the option is off. > > Signed-off-by: Grayson Gordon <graysongordon1@gmail.com> > --- This patch was merged to 'next' the other day, and the last test in the new t5585 fails on my system. > diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh > index 115455784c..554b0e44fa 100644 > --- a/t/lib-httpd.sh > +++ b/t/lib-httpd.sh > @@ -25,6 +25,7 @@ > # LIB_HTTPD_DAV enable DAV > # LIB_HTTPD_SVN enable SVN at given location (e.g. "svn") > # LIB_HTTPD_SSL enable SSL > +# LIB_HTTPD_OCSP enable OCSP stapling > # LIB_HTTPD_PROXY enable proxy > # > # Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at> > @@ -183,15 +184,26 @@ prepare_httpd() { > > ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules" > > + if test -n "$LIB_HTTPD_OCSP" > + then > + LIB_HTTPD_SSL=t > + fi > + > if test -n "$LIB_HTTPD_SSL" > then > HTTPD_PROTO=https > > - RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \ > - -config "$TEST_PATH/ssl.cnf" \ > - -new -x509 -nodes \ > - -out "$HTTPD_ROOT_PATH/httpd.pem" \ > - -keyout "$HTTPD_ROOT_PATH/httpd.pem" > + if test -n "$LIB_HTTPD_OCSP" > + then > + prepare_ocsp_stapling > + HTTPD_PARA="$HTTPD_PARA -DOCSP" > + else > + RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \ > + -config "$TEST_PATH/ssl.cnf" \ > + -new -x509 -nodes \ > + -out "$HTTPD_ROOT_PATH/httpd.pem" \ > + -keyout "$HTTPD_ROOT_PATH/httpd.pem" > + fi > GIT_SSL_NO_VERIFY=t > export GIT_SSL_NO_VERIFY > HTTPD_PARA="$HTTPD_PARA -DSSL" > @@ -262,6 +274,114 @@ stop_httpd() { > -f "$TEST_PATH/apache.conf" $HTTPD_PARA -k stop > } > > +restart_httpd () { > + httpd_pid=$(cat "$HTTPD_ROOT_PATH/httpd.pid") && > + stop_httpd && > + while kill -0 "$httpd_pid" 2>/dev/null > + do > + sleep 1 > + done && > + "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \ > + -f "$TEST_PATH/apache.conf" $HTTPD_PARA \ > + -c "Listen 127.0.0.1:$LIB_HTTPD_PORT" -k start > +} > + > +# Check if the linked libcurl can verify stapled OCSP responses. > +test_lazy_prereq SSL_VERIFYSTATUS ' > + test "$HTTPD_PROTO" = "https" && > + test_might_fail git -c http.sslVerifyStatus=true \ > + ls-remote "$HTTPD_URL" 2>err && > + ! grep "http.sslVerifyStatus is set" err > +' When checking this prereq in t5585, I get the following trace: mkdir -p "$TRASH_DIRECTORY/prereq-test-dir-SSL_VERIFYSTATUS" && ( cd "$TRASH_DIRECTORY/prereq-test-dir-SSL_VERIFYSTATUS" && test "$HTTPD_PROTO" = "https" && test_might_fail git -c http.sslVerifyStatus=true \ ls-remote "$HTTPD_URL" 2>err && cat err && # debug ! grep "http.sslVerifyStatus is set" err ) + mkdir -p /home/szeder/src/git/t/trash directory.t5585-http-ssl-ocsp/prereq-test-dir-SSL_VERIFYSTATUS + cd /home/szeder/src/git/t/trash directory.t5585-http-ssl-ocsp/prereq-test-dir-SSL_VERIFYSTATUS + test https = https + test_might_fail git -c http.sslVerifyStatus=true ls-remote https://127.0.0.1:5585 + cat err fatal: repository 'https://127.0.0.1:5585/' not found + grep http.sslVerifyStatus is set err prerequisite SSL_VERIFYSTATUS ok I added that 'cat err' to see the error message. Turns out that 'git ls-remote' can't even find the repository on the remote, but the prereq is still considered fulfilled. Is that right? In t5559 I get the following trace: + mkdir -p /home/szeder/src/git/t/trash directory.t5559-http-fetch-smart-http2/prereq-test-dir-SSL_VERIFYSTATUS + cd /home/szeder/src/git/t/trash directory.t5559-http-fetch-smart-http2/prereq-test-dir-SSL_VERIFYSTATUS + test https = https + test_might_fail git -c http.sslVerifyStatus=true ls-remote https://127.0.0.1:5559 + cat err fatal: unable to access 'https://127.0.0.1:5559/': No OCSP response received + grep http.sslVerifyStatus is set err prerequisite SSL_VERIFYSTATUS ok This time the error message talks about missing OCSP response, but the prereq is still considered fulfilled. Again: is that right?! Instead of the lack of a certain string in the error message, is there something positive that we can test instead? > +# Set up a certificate authority. It issues certificate "httpd.pem" > +# and is able to revoke it. Used instead of the self-signed > +# certificate when LIB_HTTPD_OCSP is set. > +prepare_ocsp_stapling () { > + LIB_HTTPD_OCSP_PORT=$((LIB_HTTPD_PORT + 10000)) > + > + # Referenced by ocsp-ca.cnf. > + OCSP_CA_DIR="$HTTPD_ROOT_PATH/ocsp-ca" > + OCSP_URI="http://127.0.0.1:$LIB_HTTPD_OCSP_PORT" > + export OCSP_CA_DIR OCSP_URI > + > + mkdir -p "$OCSP_CA_DIR/newcerts" && > + >"$OCSP_CA_DIR/index.txt" && > + echo 1000 >"$OCSP_CA_DIR/serial" && > + > + openssl req -config "$TEST_PATH/ocsp-ca.cnf" \ > + -new -x509 -nodes -days 2 \ > + -subj "/CN=git-test-ca" -extensions v3_ca \ > + -keyout "$HTTPD_ROOT_PATH/ca.key" \ > + -out "$HTTPD_ROOT_PATH/ca.pem" && > + openssl req -config "$TEST_PATH/ocsp-ca.cnf" \ > + -new -nodes \ > + -subj "/CN=127.0.0.1" \ > + -keyout "$HTTPD_ROOT_PATH/httpd.key" \ > + -out "$HTTPD_ROOT_PATH/httpd.csr" && > + openssl ca -config "$TEST_PATH/ocsp-ca.cnf" -batch \ > + -cert "$HTTPD_ROOT_PATH/ca.pem" \ > + -keyfile "$HTTPD_ROOT_PATH/ca.key" \ > + -in "$HTTPD_ROOT_PATH/httpd.csr" \ > + -out "$HTTPD_ROOT_PATH/httpd.crt" && > + cat "$HTTPD_ROOT_PATH/httpd.key" "$HTTPD_ROOT_PATH/httpd.crt" \ > + >"$HTTPD_ROOT_PATH/httpd.pem" > +} > + > +run_ocsp_responder () { > + openssl ocsp -port "$LIB_HTTPD_OCSP_PORT" \ > + -index "$OCSP_CA_DIR/index.txt" \ > + -CA "$HTTPD_ROOT_PATH/ca.pem" \ > + -rsigner "$HTTPD_ROOT_PATH/ca.pem" \ > + -rkey "$HTTPD_ROOT_PATH/ca.key" \ > + -nmin 60 >>"$HTTPD_ROOT_PATH/ocsp.log" 2>&1 & > + echo $! >"$HTTPD_ROOT_PATH/ocsp.pid" > + > + for i in $(test_seq 1 10) > + do > + if openssl ocsp -no_nonce \ > + -CAfile "$HTTPD_ROOT_PATH/ca.pem" \ > + -issuer "$HTTPD_ROOT_PATH/ca.pem" \ > + -cert "$HTTPD_ROOT_PATH/httpd.crt" \ > + -url "$OCSP_URI" >/dev/null 2>&1 > + then > + return 0 > + fi > + sleep 1 > + done > + return 1 > +} > + > +start_ocsp_responder () { > + test_atexit stop_ocsp_responder > + > + if ! run_ocsp_responder > + then > + cat "$HTTPD_ROOT_PATH"/ocsp.log >&4 2>/dev/null > + test_skip_or_die GIT_TEST_HTTPD "OCSP responder setup failed" > + fi > +} > + > +stop_ocsp_responder () { > + if test -f "$HTTPD_ROOT_PATH/ocsp.pid" > + then > + kill "$(cat "$HTTPD_ROOT_PATH/ocsp.pid")" 2>/dev/null > + rm -f "$HTTPD_ROOT_PATH/ocsp.pid" > + fi > +} > + > +# Revoke the certificate used by httpd and make both the OCSP responder > +# and httpd aware of it. > +revoke_httpd_cert () { > + openssl ca -config "$TEST_PATH/ocsp-ca.cnf" \ > + -cert "$HTTPD_ROOT_PATH/ca.pem" \ > + -keyfile "$HTTPD_ROOT_PATH/ca.key" \ > + -revoke "$HTTPD_ROOT_PATH/httpd.crt" && > + stop_ocsp_responder && > + run_ocsp_responder && > + restart_httpd > +} > + > test_http_push_nonff () { > REMOTE_REPO=$1 > LOCAL_REPO=$2 > diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf > index 4149fc1078..de5ca45bb8 100644 > --- a/t/lib-httpd/apache.conf > +++ b/t/lib-httpd/apache.conf > @@ -242,6 +242,22 @@ SSLSessionCache none > SSLEngine On > </IfDefine> > > +<IfDefine OCSP> > +<IfModule !mod_socache_shmcb.c> > + LoadModule socache_shmcb_module modules/mod_socache_shmcb.so > +</IfModule> > + > +SSLCertificateChainFile ca.pem > +SSLUseStapling On > +# Stapling needs a mutex, which apache would put in a system-wide > +# runtime directory that need not be writable. Keep it in the server > +# root, or httpd refuses to start instead of skipping the tests. > +DefaultRuntimeDir . > +SSLStaplingCache shmcb:ssl_stapling(65536) > +# Staple non-"good" responses too, so clients get to see "revoked". > +SSLStaplingReturnResponderErrors On > +</IfDefine> > + > <Location /auth/> > AuthType Basic > AuthName "git-auth" > diff --git a/t/lib-httpd/ocsp-ca.cnf b/t/lib-httpd/ocsp-ca.cnf > new file mode 100644 > index 0000000000..47a58139b5 > --- /dev/null > +++ b/t/lib-httpd/ocsp-ca.cnf > @@ -0,0 +1,35 @@ > +[ ca ] > +default_ca = CA_default > + > +[ CA_default ] > +dir = $ENV::OCSP_CA_DIR > +database = $dir/index.txt > +new_certs_dir = $dir/newcerts > +serial = $dir/serial > +default_md = sha256 > +default_days = 2 > +policy = policy_anything > +email_in_dn = no > +unique_subject = no > +x509_extensions = server_cert > + > +[ policy_anything ] > +commonName = supplied > + > +[ req ] > +default_bits = 2048 > +distinguished_name = req_distinguished_name > +prompt = no > + > +[ req_distinguished_name ] > +# The subject is always given on the command line via -subj. > + > +[ v3_ca ] > +basicConstraints = critical, CA:TRUE > +keyUsage = critical, digitalSignature, keyCertSign, cRLSign > +subjectKeyIdentifier = hash > + > +[ server_cert ] > +basicConstraints = CA:FALSE > +subjectAltName = IP:127.0.0.1 > +authorityInfoAccess = OCSP;URI:$ENV::OCSP_URI > diff --git a/t/meson.build b/t/meson.build > index 3ca7b27104..72cbd12d8f 100644 > --- a/t/meson.build > +++ b/t/meson.build > @@ -728,6 +728,7 @@ integration_tests = [ > 't5582-fetch-negative-refspec.sh', > 't5583-push-branches.sh', > 't5584-http-429-retry.sh', > + 't5585-http-ssl-ocsp.sh', > 't5600-clone-fail-cleanup.sh', > 't5601-clone.sh', > 't5602-clone-remote-exec.sh', > diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh > index 805bec025c..c51b14291d 100755 > --- a/t/t5551-http-fetch-smart.sh > +++ b/t/t5551-http-fetch-smart.sh > @@ -680,6 +680,28 @@ test_expect_success 'passing hostname resolution information works' ' > git -c "http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1" ls-remote "$BOGUS_HTTPD_URL/smart/repo.git" >/dev/null > ' > > +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" Shouldn't we check the error message, to make sure that the command failed for the expected reason (here and in t5585 as well)? > +' > + > +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 sslVerifyStatus 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 sslVerifyStatus 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 > +' > + > # here user%40host is the URL-encoded version of user@host, > # which is our intentionally-odd username to catch parsing errors > url_user=$HTTPD_URL_USER/auth/smart/repo.git > diff --git a/t/t5585-http-ssl-ocsp.sh b/t/t5585-http-ssl-ocsp.sh > new file mode 100755 > index 0000000000..0d1310215f > --- /dev/null > +++ b/t/t5585-http-ssl-ocsp.sh > @@ -0,0 +1,55 @@ > +#!/bin/sh > + > +test_description='verification of stapled OCSP responses via http.sslVerifyStatus' > + > +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main > +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME > + > +. ./test-lib.sh > + > +LIB_HTTPD_OCSP=1 > +. "$TEST_DIRECTORY"/lib-httpd.sh > + > +start_httpd > +start_ocsp_responder > + > +test_expect_success 'setup repository' ' > + test_commit one && > + git init --bare "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && > + git push "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" HEAD:refs/heads/main > +' > + > +# lib-httpd.sh exports GIT_SSL_NO_VERIFY, which would keep us from ever > +# looking at the certificate. Trust our own CA instead. > +with_ssl_verification () { > + ( > + sane_unset GIT_SSL_NO_VERIFY && > + GIT_SSL_CAINFO="$HTTPD_ROOT_PATH/ca.pem" "$@" According to our CodingGuidelines, a temporary variable assignment like this should not be used for shell functions for portability reasons. In most test cases this is fine, becase "$@" is a git command, but ... > + ) > +} > + > +test_expect_success SSL_VERIFYSTATUS 'certificate verification works against test CA' ' > + with_ssl_verification git ls-remote "$HTTPD_URL/smart/repo.git" >actual && > + test_line_count -gt 0 actual > +' > + > +test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled "good" OCSP response' ' > + with_ssl_verification git -c http.sslVerifyStatus=true \ > + ls-remote "$HTTPD_URL/smart/repo.git" >actual && > + test_line_count -gt 0 actual > +' > + > +test_expect_success SSL_VERIFYSTATUS 'revoked certificate is rejected' ' > + revoke_httpd_cert && > + with_ssl_verification test_must_fail git -c http.sslVerifyStatus=true \ > + ls-remote "$HTTPD_URL/smart/repo.git" 2>err && > + test_grep -i -e "ocsp" -e "revocation" -e "revoked" -e "certificate status" err > +' ... in this case "$@" is the test_must_fail shell function. Please set and then export that variable instead; it's already in a subshell because of the sane_unset anyway. > +# Depends on the certificate revoked by the preceding test. > +test_expect_success SSL_VERIFYSTATUS 'revoked certificate is accepted without http.sslVerifyStatus' ' > + with_ssl_verification git ls-remote "$HTTPD_URL/smart/repo.git" >actual && > + test_line_count -gt 0 actual > +' So this test case fails for me with the following trace output: expecting success of 5585.5 'revoked certificate is accepted without http.sslVerifyStatus': with_ssl_verification git ls-remote "$HTTPD_URL/smart/repo.git" >actual && test_line_count -gt 0 actual + with_ssl_verification git ls-remote https://127.0.0.1:5585/smart/repo.git + sane_unset GIT_SSL_NO_VERIFY + unset GIT_SSL_NO_VERIFY + return 0 + GIT_SSL_CAINFO=/home/szeder/src/git/t/trash directory.t5585-http-ssl-ocsp/httpd/ca.pem git ls-remote https://127.0.0.1:5585/smart/repo.git fatal: unable to access 'https://127.0.0.1:5585/smart/repo.git/': server certificate verification failed. CAfile: /home/szeder/src/git/t/trash directory.t5585-http-ssl-ocsp/httpd/ca.pem CRLfile: none error: last command exited with $?=128 not ok 5 - revoked certificate is accepted without http.sslVerifyStatus # # with_ssl_verification git ls-remote "$HTTPD_URL/smart/repo.git" >actual && # test_line_count -gt 0 actual # libcurl is 7.81.0, apache is 2.4.52 (whatever is shipped in this slowly aging LTS...) ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-09-23 21:07 ` SZEDER Gábor @ 2026-09-23 21:47 ` Junio C Hamano 2026-09-24 7:41 ` SZEDER Gábor 0 siblings, 1 reply; 38+ messages in thread From: Junio C Hamano @ 2026-09-23 21:47 UTC (permalink / raw) To: SZEDER Gábor; +Cc: graysongordon-gl, ps, git, peff, avarab SZEDER Gábor <szeder.dev@gmail.com> writes: > On Tue, Sep 15, 2026 at 12:23:48PM -0400, graysongordon-gl wrote: >> From: Grayson Gordon <graysongordon1@gmail.com> >> >> git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the >> OCSP "Certificate Status Request" extension and any stapled response a >> server sends is ignored, including responses that explicitly state the >> certificate has been revoked. > ... > This patch was merged to 'next' the other day, and the last test in > the new t5585 fails on my system. Sorry about a premature merge. Since we are not in a hurry to take this topic in (or no new feature topic in general), let me revert it out of 'next' and give it a clean slate to try again. > ... > I added that 'cat err' to see the error message. Turns out that 'git > ls-remote' can't even find the repository on the remote, but the > prereq is still considered fulfilled. Is that right? > ... > This time the error message talks about missing OCSP response, but the > prereq is still considered fulfilled. Again: is that right?! > > Instead of the lack of a certain string in the error message, is > there something positive that we can test instead? Oh, that is a very constructive and useful suggestion. Greatly appreciated. > So this test case fails for me with the following trace output: > > expecting success of 5585.5 'revoked certificate is accepted without http.sslVerifyStatus': > with_ssl_verification git ls-remote "$HTTPD_URL/smart/repo.git" >actual && > test_line_count -gt 0 actual > > + with_ssl_verification git ls-remote https://127.0.0.1:5585/smart/repo.git > + sane_unset GIT_SSL_NO_VERIFY > + unset GIT_SSL_NO_VERIFY > + return 0 > + GIT_SSL_CAINFO=/home/szeder/src/git/t/trash directory.t5585-http-ssl-ocsp/httpd/ca.pem git ls-remote https://127.0.0.1:5585/smart/repo.git > fatal: unable to access 'https://127.0.0.1:5585/smart/repo.git/': server certificate verification failed. CAfile: /home/szeder/src/git/t/trash directory.t5585-http-ssl-ocsp/httpd/ca.pem CRLfile: none > error: last command exited with $?=128 > not ok 5 - revoked certificate is accepted without http.sslVerifyStatus > # > # with_ssl_verification git ls-remote "$HTTPD_URL/smart/repo.git" >actual && > # test_line_count -gt 0 actual > # > > libcurl is 7.81.0, apache is 2.4.52 (whatever is shipped in this > slowly aging LTS...) Thanks. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-09-23 21:47 ` Junio C Hamano @ 2026-09-24 7:41 ` SZEDER Gábor 2026-09-24 7:59 ` Patrick Steinhardt 2026-09-24 16:22 ` Junio C Hamano 0 siblings, 2 replies; 38+ messages in thread From: SZEDER Gábor @ 2026-09-24 7:41 UTC (permalink / raw) To: Junio C Hamano; +Cc: graysongordon-gl, ps, git, peff, avarab On Wed, Sep 23, 2026 at 02:47:18PM -0700, Junio C Hamano wrote: > SZEDER Gábor <szeder.dev@gmail.com> writes: > > > On Tue, Sep 15, 2026 at 12:23:48PM -0400, graysongordon-gl wrote: > >> From: Grayson Gordon <graysongordon1@gmail.com> > >> > >> git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the > >> OCSP "Certificate Status Request" extension and any stapled response a > >> server sends is ignored, including responses that explicitly state the > >> certificate has been revoked. > > ... > > This patch was merged to 'next' the other day, and the last test in > > the new t5585 fails on my system. > > Sorry about a premature merge. Since we are not in a hurry to take > this topic in (or no new feature topic in general), let me revert it > out of 'next' and give it a clean slate to try again. Well, if you hadn't merged it, we would perhaps still be none the wiser, because, alas, I don't have the bandwidth to run tests on the seen branch regularly... However, CI does, but I can't seem to find any CI runs that failed because of this, which makes me worried that something is wrong on my end. > > ... > > I added that 'cat err' to see the error message. Turns out that 'git > > ls-remote' can't even find the repository on the remote, but the > > prereq is still considered fulfilled. Is that right? > > ... > > This time the error message talks about missing OCSP response, but the > > prereq is still considered fulfilled. Again: is that right?! > > > > Instead of the lack of a certain string in the error message, is > > there something positive that we can test instead? > > Oh, that is a very constructive and useful suggestion. Greatly > appreciated. After having slept on it :) I now start to realize that this SSL_VERIFYSTATUS prereq only checks that libcurl supports the CURLOPT_SSL_VERIFYSTATUS option, and has nothing to do with the capabilities and configuration of the web server. If my understanding is correct, then I think that: - Merely attempting a connection to somewhere is indeed sufficient to check this, and it doesn't matter that the server can't find the requested repository. - Checking for the error message printed after curl_easy_setopt(..., CURLOPT_SSL_VERIFYSTATUS, ...) returns with error is indeed the right thing to do. However, in that new error message the second half is much more informative than the first, and if the prereq looked for the absence of "could not enable OCSP status verification" instead of "http.sslVerifyStatus is set", then I think I would have realized all this sooner. Perhaps calling the prereq CURL_SSL_VERIFYSTATUS would have helped, too. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-09-24 7:41 ` SZEDER Gábor @ 2026-09-24 7:59 ` Patrick Steinhardt 2026-09-25 9:28 ` SZEDER Gábor 2026-09-24 16:22 ` Junio C Hamano 1 sibling, 1 reply; 38+ messages in thread From: Patrick Steinhardt @ 2026-09-24 7:59 UTC (permalink / raw) To: SZEDER Gábor; +Cc: Junio C Hamano, graysongordon-gl, git, peff, avarab On Thu, Sep 24, 2026 at 09:41:41AM +0200, SZEDER Gábor wrote: > On Wed, Sep 23, 2026 at 02:47:18PM -0700, Junio C Hamano wrote: > > SZEDER Gábor <szeder.dev@gmail.com> writes: > > > > > On Tue, Sep 15, 2026 at 12:23:48PM -0400, graysongordon-gl wrote: > > >> From: Grayson Gordon <graysongordon1@gmail.com> > > >> > > >> git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the > > >> OCSP "Certificate Status Request" extension and any stapled response a > > >> server sends is ignored, including responses that explicitly state the > > >> certificate has been revoked. > > > ... > > > This patch was merged to 'next' the other day, and the last test in > > > the new t5585 fails on my system. > > > > Sorry about a premature merge. Since we are not in a hurry to take > > this topic in (or no new feature topic in general), let me revert it > > out of 'next' and give it a clean slate to try again. > > Well, if you hadn't merged it, we would perhaps still be none the > wiser, because, alas, I don't have the bandwidth to run tests on the > seen branch regularly... > > However, CI does, but I can't seem to find any CI runs that failed > because of this, which makes me worried that something is wrong on my > end. Do you maybe run with a curl backend that doesn't properly support OCSP? But even if so, our test suite should notice and skip the tests. Patrick ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-09-24 7:59 ` Patrick Steinhardt @ 2026-09-25 9:28 ` SZEDER Gábor 2026-09-25 16:13 ` Junio C Hamano 0 siblings, 1 reply; 38+ messages in thread From: SZEDER Gábor @ 2026-09-25 9:28 UTC (permalink / raw) To: Patrick Steinhardt; +Cc: Junio C Hamano, graysongordon-gl, git, peff, avarab On Thu, Sep 24, 2026 at 09:59:16AM +0200, Patrick Steinhardt wrote: > On Thu, Sep 24, 2026 at 09:41:41AM +0200, SZEDER Gábor wrote: > > On Wed, Sep 23, 2026 at 02:47:18PM -0700, Junio C Hamano wrote: > > > SZEDER Gábor <szeder.dev@gmail.com> writes: > > > > > > > On Tue, Sep 15, 2026 at 12:23:48PM -0400, graysongordon-gl wrote: > > > >> From: Grayson Gordon <graysongordon1@gmail.com> > > > >> > > > >> git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the > > > >> OCSP "Certificate Status Request" extension and any stapled response a > > > >> server sends is ignored, including responses that explicitly state the > > > >> certificate has been revoked. > > > > ... > > > > This patch was merged to 'next' the other day, and the last test in > > > > the new t5585 fails on my system. > > > > > > Sorry about a premature merge. Since we are not in a hurry to take > > > this topic in (or no new feature topic in general), let me revert it > > > out of 'next' and give it a clean slate to try again. > > > > Well, if you hadn't merged it, we would perhaps still be none the > > wiser, because, alas, I don't have the bandwidth to run tests on the > > seen branch regularly... > > > > However, CI does, but I can't seem to find any CI runs that failed > > because of this, which makes me worried that something is wrong on my > > end. > > Do you maybe run with a curl backend that doesn't properly support OCSP? > But even if so, our test suite should notice and skip the tests. Apparently I did! Removing 'libcurl4-gnutls-dev' and installing 'libcurl4-openssl-dev' instead makes t5585 succeed. Go figure. Thanks for the hint! ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-09-25 9:28 ` SZEDER Gábor @ 2026-09-25 16:13 ` Junio C Hamano 0 siblings, 0 replies; 38+ messages in thread From: Junio C Hamano @ 2026-09-25 16:13 UTC (permalink / raw) To: SZEDER Gábor; +Cc: Patrick Steinhardt, graysongordon-gl, git, peff, avarab SZEDER Gábor <szeder.dev@gmail.com> writes: >> Do you maybe run with a curl backend that doesn't properly support OCSP? >> But even if so, our test suite should notice and skip the tests. > > Apparently I did! Removing 'libcurl4-gnutls-dev' and installing > 'libcurl4-openssl-dev' instead makes t5585 succeed. Go figure. > > Thanks for the hint! Thanks for collectively digging down the cause of the issue to (1) help your set-up to pass the test, and (2) point out that the prerequisite setting needs to be improved. ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses 2026-09-24 7:41 ` SZEDER Gábor 2026-09-24 7:59 ` Patrick Steinhardt @ 2026-09-24 16:22 ` Junio C Hamano 1 sibling, 0 replies; 38+ messages in thread From: Junio C Hamano @ 2026-09-24 16:22 UTC (permalink / raw) To: SZEDER Gábor; +Cc: graysongordon-gl, ps, git, peff, avarab SZEDER Gábor <szeder.dev@gmail.com> writes: > - Checking for the error message printed after curl_easy_setopt(..., > CURLOPT_SSL_VERIFYSTATUS, ...) returns with error is indeed the > right thing to do. > > However, in that new error message the second half is much more > informative than the first, and if the prereq looked for the > absence of "could not enable OCSP status verification" instead of > "http.sslVerifyStatus is set", then I think I would have realized > all this sooner. Perhaps calling the prereq CURL_SSL_VERIFYSTATUS > would have helped, too. Yeah, both are understandable. Thanks. ^ permalink raw reply [flat|nested] 38+ messages in thread
end of thread, other threads:[~2026-09-25 16:13 UTC | newest] Thread overview: 38+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-11 17:02 [PATCH] http: add http.sslVerifyStatus to check stapled OCSP responses graysongordon-gl 2026-08-11 19:28 ` Junio C Hamano 2026-08-11 20:44 ` [PATCH v2] " graysongordon-gl 2026-08-12 6:25 ` Patrick Steinhardt 2026-08-12 15:53 ` Grayson Gordon 2026-08-12 14:17 ` Junio C Hamano 2026-08-12 18:25 ` [PATCH v3] " graysongordon-gl 2026-08-12 21:34 ` Junio C Hamano 2026-08-13 16:06 ` Junio C Hamano 2026-08-17 18:52 ` [PATCH v4] " graysongordon-gl 2026-08-17 19:19 ` Junio C Hamano 2026-08-18 7:50 ` Patrick Steinhardt 2026-08-18 14:51 ` Grayson Gordon 2026-08-19 8:14 ` Patrick Steinhardt 2026-08-18 16:40 ` Junio C Hamano 2026-08-18 19:37 ` [PATCH v5] " graysongordon-gl 2026-08-18 20:12 ` Junio C Hamano 2026-08-18 21:22 ` Grayson Gordon 2026-08-18 21:48 ` [PATCH v6] " graysongordon-gl 2026-08-26 22:01 ` Junio C Hamano 2026-08-28 13:51 ` Grayson Gordon 2026-08-28 17:20 ` Junio C Hamano 2026-08-31 6:56 ` Patrick Steinhardt 2026-08-31 14:16 ` Junio C Hamano 2026-08-31 14:24 ` Patrick Steinhardt 2026-08-31 14:31 ` Junio C Hamano 2026-09-08 12:55 ` Grayson Gordon 2026-09-15 16:23 ` [PATCH v7] " graysongordon-gl 2026-09-16 19:29 ` Junio C Hamano 2026-09-23 12:42 ` Patrick Steinhardt 2026-09-23 21:43 ` Junio C Hamano 2026-09-23 21:07 ` SZEDER Gábor 2026-09-23 21:47 ` Junio C Hamano 2026-09-24 7:41 ` SZEDER Gábor 2026-09-24 7:59 ` Patrick Steinhardt 2026-09-25 9:28 ` SZEDER Gábor 2026-09-25 16:13 ` Junio C Hamano 2026-09-24 16:22 ` Junio C Hamano
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.