{"thread":{"id":"66156","subject":"[PATCH] http: add http.sslVerifyStatus to check stapled OCSP responses","startedAt":"2026-08-11T17:02:05Z","lastAt":"2026-09-25T16:13:34Z","messageCount":38,"participants":["graysongordon-gl","Junio C Hamano","Patrick Steinhardt","Grayson Gordon","SZEDER Gábor"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"550303","messageId":"20260811170200.43097-1-ggordon@gitlab.com","threadId":"66156","inReplyTo":null,"subject":"[PATCH] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"graysongordon-gl","fromEmail":"graysongordon1@gmail.com","sentAt":"2026-08-11T17:02:00Z","receivedAt":"2026-08-11T17:02:05Z","isPatch":true,"body":"From: Grayson Gordon <graysongordon1@gmail.com>\n\ngit asks libcurl to verify the peer certificate and the hostname, but it\nnever sets CURLOPT_SSL_VERIFYSTATUS, so the \"Certificate Status Request\"\nTLS extension is never requested and any stapled OCSP response the server\ndoes send is ignored.\n\nOn an OpenSSL-linked build this is silent. OpenSSL hands the stapled\nresponse to the application and takes no view on it:\nSSL_CTX_set_tlsext_status_cb(3) says the callback \"should determine\nwhether the returned OCSP response(s) are acceptable or not\", and libcurl\nonly installs that callback when CURLOPT_SSL_VERIFYSTATUS is set. So git\nwill fetch from a server whose own staple says its certificate has been\nrevoked.\n\nA GnuTLS-linked build behaves differently, and the difference does not\ncome from curl. GnuTLS consults a stapled response inside\ngnutls_certificate_verify_peers(), so the failure surfaces through the\nverifypeer branch of curl's GnuTLS backend (lib/vtls/gtls.c) whether or\nnot CURLOPT_SSL_VERIFYSTATUS was ever set. The same git, against the same\nserver, therefore enforces revocation or not depending only on how its\nlibcurl was built. That difference is documented here rather than papered\nover: this option turns the check on where the backend needs asking, and\nsetting it to false does not turn the check off on GnuTLS.\n\nAdd an http.sslVerifyStatus boolean that sets CURLOPT_SSL_VERIFYSTATUS.\nBecause http_options() is the collect_fn of a urlmatch config, the\nper-URL form works with no further changes:\n\n    git config http.https://example.com/.sslVerifyStatus true\n\nIt defaults to false, and has to. The option is fail-closed: libcurl fails\nverification when the server staples nothing at all, so turning this on\nglobally would break every remote that does not staple.\n\nLeaving the default to libcurl is not an option either. The same\ncomplaint was raised there in https://github.com/curl/curl/issues/15483\nand closed as intentional (\"Marked as enhancement since this was done on\npurpose\"), with the observation that stapling is expected to see less use\nas Let's Encrypt drops OCSP support. If the check is to be reachable at\nall, the lever has to come from the application.\n\nIf the TLS backend cannot check the staple, curl_easy_setopt() returns\nCURLE_NOT_BUILT_IN. Fail loudly there rather than carrying on, since\nsilently not checking is precisely what this option exists to prevent.\n\nCURLOPT_SSL_VERIFYSTATUS has been available since libcurl 7.41.0, well\nbelow the 7.61.0 floor documented in INSTALL, so no version guard is\nneeded.\n\nThe new test exercises the fail-closed path, which needs no CA and no OCSP\nresponder: lib-httpd's server staples nothing, so enabling the option has\nto turn a working fetch into a failing one. Verified against an unpatched\nbuild, where exactly the two assertions that depend on the new option fail\nand the three controls still pass, and against OpenSSL, GnuTLS and\nmbedTLS-linked builds of libcurl.\n\nSigned-off-by: Grayson Gordon <graysongordon1@gmail.com>\n---\n Documentation/config/http.adoc  | 17 +++++++\n http.c                          | 21 +++++++++\n t/t5567-http-verify-status.sh   | 72 +++++++++++++++++++++++++++++++\n 3 files changed, 110 insertions(+)\n create mode 100755 t/t5567-http-verify-status.sh\n\ndiff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc\nindex 792a71b413..40b849bf7f 100644\n--- a/Documentation/config/http.adoc\n+++ b/Documentation/config/http.adoc\n@@ -196,6 +196,23 @@ http.sslVerify::\n \tover HTTPS. Defaults to true. Can be overridden by the\n \t`GIT_SSL_NO_VERIFY` environment variable.\n \n+http.sslVerifyStatus::\n+\tWhether to check the revocation status of the server\n+\tcertificate using the stapled OCSP response supplied during\n+\tthe TLS handshake (\"OCSP stapling\"). Defaults to false.\n++\n+This is fail-closed: if the server staples no response, verification\n+fails. Set it per remote, e.g.\n+`http.https://example.com/.sslVerifyStatus`, rather than globally.\n++\n+What it changes depends on the TLS backend libcurl was built against.\n+An OpenSSL-linked build ignores a stapled response unless this is set.\n+A GnuTLS-linked build consults the staple during ordinary certificate\n+verification, so it already rejects a revoked certificate under\n+`http.sslVerify` alone, and setting this to `false` does not disable\n+that. Where a backend cannot check the staple at all, git fails with an\n+error rather than continuing unchecked.\n+\n http.sslCert::\n \tFile containing the SSL certificate when fetching or pushing\n \tover HTTPS. Can be overridden by the `GIT_SSL_CERT` environment\ndiff --git a/http.c b/http.c\nindex 5f0f42fb18..c1a66988e7 100644\n--- a/http.c\n+++ b/http.c\n@@ -44,6 +44,7 @@ static CURL *curl_default;\n char curl_errorstr[CURL_ERROR_SIZE];\n \n static int curl_ssl_verify = -1;\n+static int curl_ssl_verify_status;\n static int curl_ssl_try;\n static char *curl_http_version;\n static char *ssl_cert;\n@@ -400,6 +401,10 @@ static int http_options(const char *var, const char *value,\n \t\tcurl_ssl_verify = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(\"http.sslverifystatus\", var)) {\n+\t\tcurl_ssl_verify_status = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(\"http.sslcipherlist\", var))\n \t\treturn git_config_string(&ssl_cipherlist, var, value);\n \tif (!strcmp(\"http.sslversion\", var))\n@@ -1131,6 +1136,22 @@ static CURL *get_curl_handle(void)\n \t\tcurl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L);\n \t}\n \n+\t/*\n+\t * Ask the TLS backend to check the certificate's revocation\n+\t * status via the stapled OCSP response. libcurl defaults this\n+\t * off, and no backend except GnuTLS consults the staple on its\n+\t * own, so without this git will happily accept a certificate\n+\t * whose own staple says it has been revoked.\n+\t *\n+\t * Off by default because it is fail-closed: a server that\n+\t * staples nothing fails verification outright, so enabling it\n+\t * globally would break every remote that does not staple.\n+\t */\n+\tif (curl_ssl_verify_status &&\n+\t    curl_easy_setopt(result, CURLOPT_SSL_VERIFYSTATUS, 1L) != CURLE_OK)\n+\t\tdie(_(\"http.sslVerifyStatus is set, but the TLS backend of \"\n+\t\t      \"this libcurl cannot verify certificate status\"));\n+\n     if (curl_http_version) {\n \t\tlong opt;\n \t\tif (!get_curl_http_version_opt(curl_http_version, &opt)) {\ndiff --git a/t/t5567-http-verify-status.sh b/t/t5567-http-verify-status.sh\nnew file mode 100755\nindex 0000000000..c9167a05c2\n--- /dev/null\n+++ b/t/t5567-http-verify-status.sh\n@@ -0,0 +1,72 @@\n+#!/bin/sh\n+\n+test_description='http.sslVerifyStatus'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+LIB_HTTPD_SSL=t\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+# The test server staples no OCSP response, and that is what makes this\n+# testable without standing up a CA and a responder: http.sslVerifyStatus is\n+# fail-closed, so turning it on has to turn a working fetch into a failing one.\n+#\n+# lib-httpd.sh exports GIT_SSL_NO_VERIFY for its self-signed certificate. In\n+# libcurl the status check is independent of peer verification, so it still\n+# applies here.\n+\n+test_expect_success 'setup repository' '\n+\techo content >file &&\n+\tgit add file &&\n+\tgit commit -m one\n+'\n+\n+test_expect_success 'create http-accessible bare repository' '\n+\tgit init --bare \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\" &&\n+\tgit remote add public \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\" &&\n+\tgit push public main:main\n+'\n+\n+# A TLS backend that cannot check the staple makes curl_easy_setopt() fail,\n+# which http.c reports with a distinct message. Skip in that case rather than\n+# reporting a failure that really means \"this libcurl was built differently\".\n+# Any other failure leaves the prerequisite satisfied on purpose, so a broken\n+# server makes the tests below fail loudly instead of silently vanishing.\n+test_lazy_prereq SSL_VERIFYSTATUS '\n+\tgit -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" 2>err\n+\t! grep \"cannot verify certificate status\" err\n+'\n+\n+test_expect_success 'ls-remote succeeds with http.sslVerifyStatus unset' '\n+\tgit ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=true fails without a staple' '\n+\ttest_must_fail git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=false is a no-op' '\n+\tgit -c http.sslVerifyStatus=false \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'per-URL configuration applies to a matching URL' '\n+\ttest_must_fail git -c \"http.$HTTPD_URL/.sslVerifyStatus=true\" \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'per-URL configuration is not applied to other URLs' '\n+\tgit -c \"http.https://example.com/.sslVerifyStatus=true\" \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_done\n-- \n2.55.0\n\n"},{"id":"550323","messageId":"xmqq8q6c1mpi.fsf@gitster.g","threadId":"66156","inReplyTo":"20260811170200.43097-1-ggordon@gitlab.com","subject":"Re: [PATCH] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-11T19:28:09Z","receivedAt":"2026-08-11T19:28:12Z","isPatch":true,"body":"graysongordon-gl <graysongordon1@gmail.com> writes:\n\n> CURLOPT_SSL_VERIFYSTATUS has been available since libcurl 7.41.0, well\n> below the 7.61.0 floor documented in INSTALL, so no version guard is\n> needed.\n\nGood to see that the author paid extra attention to compatibility.\n\n> +\t/*\n> +\t * Ask the TLS backend to check the certificate's revocation\n> +\t * status via the stapled OCSP response. libcurl defaults this\n> +\t * off, and no backend except GnuTLS consults the staple on its\n> +\t * own, so without this git will happily accept a certificate\n> +\t * whose own staple says it has been revoked.\n> +\t *\n> +\t * Off by default because it is fail-closed: a server that\n> +\t * staples nothing fails verification outright, so enabling it\n> +\t * globally would break every remote that does not staple.\n> +\t */\n\nThe comment may not be telling any lies per se, but it is dubious\nthat this belongs here as an in-code comment.  Developers hunting a\nbug they suspect this setting might have caused will need access to\nthis information, and they can access it by running 'git blame' to\nlocate the commit that introduced the code.  As long as a solid\ncommit log message explains how you arrived at various design\ndecisions (such as 'off by default because'), they can use that as a\nstarting point.  For other developers hunting different bugs or\ntrying to add their own enhancements, the comment is a mere\ndistraction.\n\n> +\tif (curl_ssl_verify_status &&\n> +\t    curl_easy_setopt(result, CURLOPT_SSL_VERIFYSTATUS, 1L) != CURLE_OK)\n> +\t\tdie(_(\"http.sslVerifyStatus is set, but the TLS backend of \"\n> +\t\t      \"this libcurl cannot verify certificate status\"));\n> +\n\n\nThanks.\n"},{"id":"550328","messageId":"20260811204407.52471-1-ggordon@gitlab.com","threadId":"66156","inReplyTo":"20260811170200.43097-1-ggordon@gitlab.com","subject":"[PATCH v2] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"graysongordon-gl","fromEmail":"graysongordon1@gmail.com","sentAt":"2026-08-11T20:44:07Z","receivedAt":"2026-08-11T20:48:00Z","isPatch":true,"body":"From: Grayson Gordon <graysongordon1@gmail.com>\n\ngit asks libcurl to verify the peer certificate and the hostname, but it\nnever sets CURLOPT_SSL_VERIFYSTATUS, so the \"Certificate Status Request\"\nTLS extension is never requested and any stapled OCSP response the server\ndoes send is ignored.\n\nOn an OpenSSL-linked build this is silent. OpenSSL hands the stapled\nresponse to the application and takes no view on it:\nSSL_CTX_set_tlsext_status_cb(3) says the callback \"should determine\nwhether the returned OCSP response(s) are acceptable or not\", and libcurl\nonly installs that callback when CURLOPT_SSL_VERIFYSTATUS is set. So git\nwill fetch from a server whose own staple says its certificate has been\nrevoked.\n\nA GnuTLS-linked build behaves differently, and the difference does not\ncome from curl. GnuTLS consults a stapled response inside\ngnutls_certificate_verify_peers(), so the failure surfaces through the\nverifypeer branch of curl's GnuTLS backend (lib/vtls/gtls.c) whether or\nnot CURLOPT_SSL_VERIFYSTATUS was ever set. The same git, against the same\nserver, therefore enforces revocation or not depending only on how its\nlibcurl was built. That difference is documented here rather than papered\nover: this option turns the check on where the backend needs asking, and\nsetting it to false does not turn the check off on GnuTLS.\n\nAdd an http.sslVerifyStatus boolean that sets CURLOPT_SSL_VERIFYSTATUS.\nBecause http_options() is the collect_fn of a urlmatch config, the\nper-URL form works with no further changes:\n\n    git config http.https://example.com/.sslVerifyStatus true\n\nIt defaults to false, and has to. The option is fail-closed: libcurl fails\nverification when the server staples nothing at all, so turning this on\nglobally would break every remote that does not staple.\n\nLeaving the default to libcurl is not an option either. The same\ncomplaint was raised there in https://github.com/curl/curl/issues/15483\nand closed as intentional (\"Marked as enhancement since this was done on\npurpose\"), with the observation that stapling is expected to see less use\nas Let's Encrypt drops OCSP support. If the check is to be reachable at\nall, the lever has to come from the application.\n\nIf the TLS backend cannot check the staple, curl_easy_setopt() returns\nCURLE_NOT_BUILT_IN. Fail loudly there rather than carrying on, since\nsilently not checking is precisely what this option exists to prevent.\n\nCURLOPT_SSL_VERIFYSTATUS has been available since libcurl 7.41.0, well\nbelow the 7.61.0 floor documented in INSTALL, so no version guard is\nneeded.\n\nThe new test exercises the fail-closed path, which needs no CA and no OCSP\nresponder: lib-httpd's server staples nothing, so enabling the option has\nto turn a working fetch into a failing one. Verified against an unpatched\nbuild, where exactly the two assertions that depend on the new option fail\nand the three controls still pass, and against OpenSSL, GnuTLS and\nmbedTLS-linked builds of libcurl.\n\nSigned-off-by: Grayson Gordon <graysongordon1@gmail.com>\n---\nv2: drop the block comment above the setopt. What it explained (why the\n    check is needed, and why the default is false) is already in the\n    commit message, which is where \"git blame\" leads anyone debugging\n    this. No code change otherwise.\n\n Documentation/config/http.adoc  | 17 +++++++\n http.c                          | 10 ++++\n t/t5567-http-verify-status.sh   | 72 +++++++++++++++++++++++++++++++\n 3 files changed, 99 insertions(+)\n create mode 100755 t/t5567-http-verify-status.sh\n\ndiff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc\nindex 792a71b413..40b849bf7f 100644\n--- a/Documentation/config/http.adoc\n+++ b/Documentation/config/http.adoc\n@@ -196,6 +196,23 @@ http.sslVerify::\n \tover HTTPS. Defaults to true. Can be overridden by the\n \t`GIT_SSL_NO_VERIFY` environment variable.\n \n+http.sslVerifyStatus::\n+\tWhether to check the revocation status of the server\n+\tcertificate using the stapled OCSP response supplied during\n+\tthe TLS handshake (\"OCSP stapling\"). Defaults to false.\n++\n+This is fail-closed: if the server staples no response, verification\n+fails. Set it per remote, e.g.\n+`http.https://example.com/.sslVerifyStatus`, rather than globally.\n++\n+What it changes depends on the TLS backend libcurl was built against.\n+An OpenSSL-linked build ignores a stapled response unless this is set.\n+A GnuTLS-linked build consults the staple during ordinary certificate\n+verification, so it already rejects a revoked certificate under\n+`http.sslVerify` alone, and setting this to `false` does not disable\n+that. Where a backend cannot check the staple at all, git fails with an\n+error rather than continuing unchecked.\n+\n http.sslCert::\n \tFile containing the SSL certificate when fetching or pushing\n \tover HTTPS. Can be overridden by the `GIT_SSL_CERT` environment\ndiff --git a/http.c b/http.c\nindex 5f0f42fb18..c1a66988e7 100644\n--- a/http.c\n+++ b/http.c\n@@ -44,6 +44,7 @@ static CURL *curl_default;\n char curl_errorstr[CURL_ERROR_SIZE];\n \n static int curl_ssl_verify = -1;\n+static int curl_ssl_verify_status;\n static int curl_ssl_try;\n static char *curl_http_version;\n static char *ssl_cert;\n@@ -400,6 +401,10 @@ static int http_options(const char *var, const char *value,\n \t\tcurl_ssl_verify = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(\"http.sslverifystatus\", var)) {\n+\t\tcurl_ssl_verify_status = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(\"http.sslcipherlist\", var))\n \t\treturn git_config_string(&ssl_cipherlist, var, value);\n \tif (!strcmp(\"http.sslversion\", var))\n@@ -1133,6 +1138,11 @@ static CURL *get_curl_handle(void)\n \t\tcurl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L);\n \t}\n \n+\tif (curl_ssl_verify_status &&\n+\t    curl_easy_setopt(result, CURLOPT_SSL_VERIFYSTATUS, 1L) != CURLE_OK)\n+\t\tdie(_(\"http.sslVerifyStatus is set, but the TLS backend of \"\n+\t\t      \"this libcurl cannot verify certificate status\"));\n+\n     if (curl_http_version) {\n \t\tlong opt;\n \t\tif (!get_curl_http_version_opt(curl_http_version, &opt)) {\ndiff --git a/t/t5567-http-verify-status.sh b/t/t5567-http-verify-status.sh\nnew file mode 100755\nindex 0000000000..c9167a05c2\n--- /dev/null\n+++ b/t/t5567-http-verify-status.sh\n@@ -0,0 +1,72 @@\n+#!/bin/sh\n+\n+test_description='http.sslVerifyStatus'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+LIB_HTTPD_SSL=t\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+# The test server staples no OCSP response, and that is what makes this\n+# testable without standing up a CA and a responder: http.sslVerifyStatus is\n+# fail-closed, so turning it on has to turn a working fetch into a failing one.\n+#\n+# lib-httpd.sh exports GIT_SSL_NO_VERIFY for its self-signed certificate. In\n+# libcurl the status check is independent of peer verification, so it still\n+# applies here.\n+\n+test_expect_success 'setup repository' '\n+\techo content >file &&\n+\tgit add file &&\n+\tgit commit -m one\n+'\n+\n+test_expect_success 'create http-accessible bare repository' '\n+\tgit init --bare \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\" &&\n+\tgit remote add public \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\" &&\n+\tgit push public main:main\n+'\n+\n+# A TLS backend that cannot check the staple makes curl_easy_setopt() fail,\n+# which http.c reports with a distinct message. Skip in that case rather than\n+# reporting a failure that really means \"this libcurl was built differently\".\n+# Any other failure leaves the prerequisite satisfied on purpose, so a broken\n+# server makes the tests below fail loudly instead of silently vanishing.\n+test_lazy_prereq SSL_VERIFYSTATUS '\n+\tgit -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" 2>err\n+\t! grep \"cannot verify certificate status\" err\n+'\n+\n+test_expect_success 'ls-remote succeeds with http.sslVerifyStatus unset' '\n+\tgit ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=true fails without a staple' '\n+\ttest_must_fail git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=false is a no-op' '\n+\tgit -c http.sslVerifyStatus=false \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'per-URL configuration applies to a matching URL' '\n+\ttest_must_fail git -c \"http.$HTTPD_URL/.sslVerifyStatus=true\" \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'per-URL configuration is not applied to other URLs' '\n+\tgit -c \"http.https://example.com/.sslVerifyStatus=true\" \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_done\n-- \n2.55.0\n\n"},{"id":"550346","messageId":"anwR3Inkf-9nLmYm@pks.im","threadId":"66156","inReplyTo":"20260811204407.52471-1-ggordon@gitlab.com","subject":"Re: [PATCH v2] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-12T06:25:32Z","receivedAt":"2026-08-12T06:25:40Z","isPatch":true,"body":"On Tue, Aug 11, 2026 at 04:44:07PM -0400, graysongordon-gl wrote:\n> From: Grayson Gordon <graysongordon1@gmail.com>\n> \n> git asks libcurl to verify the peer certificate and the hostname, but it\n> never sets CURLOPT_SSL_VERIFYSTATUS, so the \"Certificate Status Request\"\n> TLS extension is never requested and any stapled OCSP response the server\n> does send is ignored.\n> \n> On an OpenSSL-linked build this is silent. OpenSSL hands the stapled\n> response to the application and takes no view on it:\n> SSL_CTX_set_tlsext_status_cb(3) says the callback \"should determine\n> whether the returned OCSP response(s) are acceptable or not\", and libcurl\n> only installs that callback when CURLOPT_SSL_VERIFYSTATUS is set. So git\n> will fetch from a server whose own staple says its certificate has been\n> revoked.\n> \n> A GnuTLS-linked build behaves differently, and the difference does not\n> come from curl. GnuTLS consults a stapled response inside\n> gnutls_certificate_verify_peers(), so the failure surfaces through the\n> verifypeer branch of curl's GnuTLS backend (lib/vtls/gtls.c) whether or\n> not CURLOPT_SSL_VERIFYSTATUS was ever set. The same git, against the same\n\nNit: this is arguably not the same git, as it links against different\nlibraries. It is not exactly unexpected that using different\ndependencies may cause different behaviour, even though we should of\ncourse try to minimize the differences.\n\n> server, therefore enforces revocation or not depending only on how its\n> libcurl was built. That difference is documented here rather than papered\n> over: this option turns the check on where the backend needs asking, and\n> setting it to false does not turn the check off on GnuTLS.\n> \n> Add an http.sslVerifyStatus boolean that sets CURLOPT_SSL_VERIFYSTATUS.\n> Because http_options() is the collect_fn of a urlmatch config, the\n> per-URL form works with no further changes:\n> \n>     git config http.https://example.com/.sslVerifyStatus true\n> \n> It defaults to false, and has to. The option is fail-closed: libcurl fails\n> verification when the server staples nothing at all, so turning this on\n> globally would break every remote that does not staple.\n> \n> Leaving the default to libcurl is not an option either. The same\n> complaint was raised there in https://github.com/curl/curl/issues/15483\n> and closed as intentional (\"Marked as enhancement since this was done on\n> purpose\"), with the observation that stapling is expected to see less use\n> as Let's Encrypt drops OCSP support. If the check is to be reachable at\n> all, the lever has to come from the application.\n\nOkay. One could make the argument that we shouldn't add support for OCSP\neither if it's being phased out now. But I assume there's still going to\nbe enough servers out there that do use it.\n\nThe big question to me is why we want to have this change in the first\nplace. It doesn't help to address the behaviour difference between\nGnuTLS and OpenSSL: if set to \"false\" OpenSSL would continue to ignore\nOCSP, whereas GnuTLS would still honor it. If set to \"true\", OpenSSL\nwould fail closed, whereas GnuTLS would still behave the same as before.\nSo nothing really changes here, unless I misunderstand something.\n\nWe don't really gain security, either, because the setting is disabled\nby default and can only be enabled host-by-host. I doubt anybody out\nthere is really going to do that though, and consequently we haven't\nreally made the world a more secure place :/\n\nSo is there any specific use case that you're after? Who exactly is this\nnew feature for?\n\nThanks!\n\nPatrick\n"},{"id":"550409","messageId":"xmqqldabzamj.fsf@gitster.g","threadId":"66156","inReplyTo":"20260811204407.52471-1-ggordon@gitlab.com","subject":"Re: [PATCH v2] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-12T14:17:24Z","receivedAt":"2026-08-12T14:17:27Z","isPatch":true,"body":"graysongordon-gl <graysongordon1@gmail.com> writes:\n\n>  Documentation/config/http.adoc  | 17 +++++++\n>  http.c                          | 10 ++++\n>  t/t5567-http-verify-status.sh   | 72 +++++++++++++++++++++++++++++++\n>  3 files changed, 99 insertions(+)\n>  create mode 100755 t/t5567-http-verify-status.sh\n\nHmph, if we need a brand new script, please make sure the 4-digit\nnumber is not taken, not just in the sources to released versions\nbut by other topics that are in flight.  \n\n    $ git show origin/seen:t | grep t5567\n\nshould be empty, but it is not.  It seems mm/lib-httpd-cgi-safe topic\ngrabbed it.\n"},{"id":"550415","messageId":"CALgUfNhoSdp191e=r6593GQHAC6DQsfh=g7hB+SwnRRGEzAGDw@mail.gmail.com","threadId":"66156","inReplyTo":"anwR3Inkf-9nLmYm@pks.im","subject":"Re: [PATCH v2] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Grayson Gordon","fromEmail":"graysongordon1@gmail.com","sentAt":"2026-08-12T15:53:29Z","receivedAt":"2026-08-12T15:53:43Z","isPatch":true,"body":"Patrick,\n\nThank you for reviewing my submission!\n\nI understand the \"nit\" you're describing: software can't exactly be\ncalled the same thing if it is built against different libraries,\nwhich in turn creates an opportunity for different behaviors. I agree\nwith your follow-up that we should aim to maintain consistency despite\nthose library choices. The benefits of the Principle of Least\nAstonishment are well established, and I would argue that users\nreasonably expect Git to behave consistently regardless of the\nunderlying TLS library.\n\nI can provide additional context to motivate this change. Like you, I\ncurrently work for GitLab, but as part of the Professional Services\norganization, which works directly with customers deploying the\nsoftware in their environments for production use. I am supporting a\ngovernment customer whose servers use OCSP stapling. There are many\nsuch government and government-adjacent customers that utilize\ncertificates issued by US Department of Defense PKI CAs, which have\npublished policies that explicitly outline support for OCSP:\nhttps://dl.dod.cyber.mil/wp-content/uploads/pki-pke/pdf/Unclass-DoD_X.509_Certificate_Policy_v10.7_Jun_3_21.pdf.\nMany DoD PKI CAs serve certificates with stapled OCSP responses today\nand can reasonably be expected to continue to do so until there is a\nDoD-wide policy change.\n\nA related bug in GnuTLS has affected my customer in their current\nproduction environment, preventing them from being able to push-mirror\nto repositories on remotes whose servers use OCSP stapling. The\npush-mirror failure is what prompted my initial investigation into\nthis issue.\n\nOriginal GnuTLS issue:\nhttps://gitlab.com/gnutls/gnutls/-/work_items/1372, resolved in GnuTLS\n3.8.8.\n\nGitLab issue on the Cloud Native GitLab build that resolves this\nbehavior in GitLab's default Helm chart base image:\nhttps://gitlab.com/gitlab-org/build/CNG/-/work_items/2374#note_3653072099\n\nGitLab ships its Helm charts with two primary \"flavors\": one based on\nDebian and the other on UBI. On Debian, the default SSL backend is\nGnuTLS, and the aforementioned issues resolve the problem. On UBI, the\ndefault backend is OpenSSL, and this issue surfaces. For my government\ncustomers who need FIPS, switching to the UBI-based image is the\nlong-term path forward:\nhttps://gitlab.com/graysongordon-gl/gitaly-tls-experiments/-/blob/main/docs/FIPS-AND-THE-TLS-BACKEND.md?ref_type=heads.\n\nTo be more explicit, OpenSSL-linked Git binaries are the default case\nfor many government customers, and those customers frequently\ninterface with Git servers that use this type of certificate\nrevocation mechanism.\n\nIn summary, there are many instances of Git servers serving a large\nbase of developers working on government-related software that are\nimpacted by this issue and need this functionality. These users have\nexperienced the pain and confusion of this behavior being broken\nfirsthand in downstream applications and have brought the issue to me.\nThese customers care that their Git clients respect certificate\nrevocation when it occurs, whether from their development machines or\nthrough service-to-service communications over Git on platforms like\nGitLab. They interface with these kinds of certificates frequently and\nwill continue to do so, which warrants the inclusion of this flag. The\nbenefit they would receive is correct validation of a remote's\ncertificate.\n\nAs it stands today, users leveraging OpenSSL-linked Git binaries can\nreceive a response indicating that the certificate is valid even when\nthe stapled OCSP response indicates that the certificate has been\nrevoked. I think there is a reasonable case that this could qualify as\na low-to-medium severity CVE.\n\nThe threat model is:\n\nAn attacker steals the private key of a Git server whose certificate\nis accompanied by an OCSP-stapled response.\nThe breach is detected, and the certificate authority revokes the certificate.\nDespite the revocation, Git clients continue to accept the certificate\nand push/pull code from a malicious Git server impersonating the\nlegitimate server.\nA malicious actor could leverage this to facilitate the exfiltration\nof an organization's Git data.\n\nSimilar CVEs against libcurl include:\nhttps://curl.se/mail/lib-2026-04/0036.html,\nhttps://curl.se/docs/CVE-2024-0853.html\n\nIn addition, the attacker would need a mechanism for intercepting or\nredirecting the victim's connection to the Git server, hence this not\nbeing a higher-severity issue. However, I think that in the context of\nDoD systems, this is sufficiently dangerous to warrant remediation,\nand this patch provides that capability.\n\nOn the GitLab side, we already have mechanisms for per-remote\nconfiguration values to be passed, and integrating this would not be a\nmonumental lift.\n\nThank you,\nGrayson\n\n\nOn Wed, Aug 12, 2026 at 2:25 AM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Tue, Aug 11, 2026 at 04:44:07PM -0400, graysongordon-gl wrote:\n> > From: Grayson Gordon <graysongordon1@gmail.com>\n> >\n> > git asks libcurl to verify the peer certificate and the hostname, but it\n> > never sets CURLOPT_SSL_VERIFYSTATUS, so the \"Certificate Status Request\"\n> > TLS extension is never requested and any stapled OCSP response the server\n> > does send is ignored.\n> >\n> > On an OpenSSL-linked build this is silent. OpenSSL hands the stapled\n> > response to the application and takes no view on it:\n> > SSL_CTX_set_tlsext_status_cb(3) says the callback \"should determine\n> > whether the returned OCSP response(s) are acceptable or not\", and libcurl\n> > only installs that callback when CURLOPT_SSL_VERIFYSTATUS is set. So git\n> > will fetch from a server whose own staple says its certificate has been\n> > revoked.\n> >\n> > A GnuTLS-linked build behaves differently, and the difference does not\n> > come from curl. GnuTLS consults a stapled response inside\n> > gnutls_certificate_verify_peers(), so the failure surfaces through the\n> > verifypeer branch of curl's GnuTLS backend (lib/vtls/gtls.c) whether or\n> > not CURLOPT_SSL_VERIFYSTATUS was ever set. The same git, against the same\n>\n> Nit: this is arguably not the same git, as it links against different\n> libraries. It is not exactly unexpected that using different\n> dependencies may cause different behaviour, even though we should of\n> course try to minimize the differences.\n>\n> > server, therefore enforces revocation or not depending only on how its\n> > libcurl was built. That difference is documented here rather than papered\n> > over: this option turns the check on where the backend needs asking, and\n> > setting it to false does not turn the check off on GnuTLS.\n> >\n> > Add an http.sslVerifyStatus boolean that sets CURLOPT_SSL_VERIFYSTATUS.\n> > Because http_options() is the collect_fn of a urlmatch config, the\n> > per-URL form works with no further changes:\n> >\n> >     git config http.https://example.com/.sslVerifyStatus true\n> >\n> > It defaults to false, and has to. The option is fail-closed: libcurl fails\n> > verification when the server staples nothing at all, so turning this on\n> > globally would break every remote that does not staple.\n> >\n> > Leaving the default to libcurl is not an option either. The same\n> > complaint was raised there in https://github.com/curl/curl/issues/15483\n> > and closed as intentional (\"Marked as enhancement since this was done on\n> > purpose\"), with the observation that stapling is expected to see less use\n> > as Let's Encrypt drops OCSP support. If the check is to be reachable at\n> > all, the lever has to come from the application.\n>\n> Okay. One could make the argument that we shouldn't add support for OCSP\n> either if it's being phased out now. But I assume there's still going to\n> be enough servers out there that do use it.\n>\n> The big question to me is why we want to have this change in the first\n> place. It doesn't help to address the behaviour difference between\n> GnuTLS and OpenSSL: if set to \"false\" OpenSSL would continue to ignore\n> OCSP, whereas GnuTLS would still honor it. If set to \"true\", OpenSSL\n> would fail closed, whereas GnuTLS would still behave the same as before.\n> So nothing really changes here, unless I misunderstand something.\n>\n> We don't really gain security, either, because the setting is disabled\n> by default and can only be enabled host-by-host. I doubt anybody out\n> there is really going to do that though, and consequently we haven't\n> really made the world a more secure place :/\n>\n> So is there any specific use case that you're after? Who exactly is this\n> new feature for?\n>\n> Thanks!\n>\n> Patrick\n"},{"id":"550440","messageId":"20260812182509.67358-1-ggordon@gitlab.com","threadId":"66156","inReplyTo":"xmqqldabzamj.fsf@gitster.g","subject":"[PATCH v3] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"graysongordon-gl","fromEmail":"graysongordon1@gmail.com","sentAt":"2026-08-12T18:25:09Z","receivedAt":"2026-08-12T18:25:14Z","isPatch":true,"body":"From: Grayson Gordon <graysongordon1@gmail.com>\n\ngit asks libcurl to verify the peer certificate and the hostname, but it\nnever sets CURLOPT_SSL_VERIFYSTATUS, so the \"Certificate Status Request\"\nTLS extension is never requested and any stapled OCSP response the server\ndoes send is ignored.\n\nOn an OpenSSL-linked build this is silent. OpenSSL hands the stapled\nresponse to the application and takes no view on it:\nSSL_CTX_set_tlsext_status_cb(3) says the callback \"should determine\nwhether the returned OCSP response(s) are acceptable or not\", and libcurl\nonly installs that callback when CURLOPT_SSL_VERIFYSTATUS is set. So git\nwill fetch from a server whose own staple says its certificate has been\nrevoked.\n\nA GnuTLS-linked build behaves differently, and the difference does not\ncome from curl. GnuTLS consults a stapled response inside\ngnutls_certificate_verify_peers(), so the failure surfaces through the\nverifypeer branch of curl's GnuTLS backend (lib/vtls/gtls.c) whether or\nnot CURLOPT_SSL_VERIFYSTATUS was ever set. The same git, against the same\nserver, therefore enforces revocation or not depending only on how its\nlibcurl was built. That difference is documented here rather than papered\nover: this option turns the check on where the backend needs asking, and\nsetting it to false does not turn the check off on GnuTLS.\n\nAdd an http.sslVerifyStatus boolean that sets CURLOPT_SSL_VERIFYSTATUS.\nBecause http_options() is the collect_fn of a urlmatch config, the\nper-URL form works with no further changes:\n\n    git config http.https://example.com/.sslVerifyStatus true\n\nIt defaults to false, and has to. The option is fail-closed: libcurl fails\nverification when the server staples nothing at all, so turning this on\nglobally would break every remote that does not staple.\n\nLeaving the default to libcurl is not an option either. The same\ncomplaint was raised there in https://github.com/curl/curl/issues/15483\nand closed as intentional (\"Marked as enhancement since this was done on\npurpose\"), with the observation that stapling is expected to see less use\nas Let's Encrypt drops OCSP support. If the check is to be reachable at\nall, the lever has to come from the application.\n\nIf the TLS backend cannot check the staple, curl_easy_setopt() returns\nCURLE_NOT_BUILT_IN. Fail loudly there rather than carrying on, since\nsilently not checking is precisely what this option exists to prevent.\n\nCURLOPT_SSL_VERIFYSTATUS has been available since libcurl 7.41.0, well\nbelow the 7.61.0 floor documented in INSTALL, so no version guard is\nneeded.\n\nThe new test exercises the fail-closed path, which needs no CA and no OCSP\nresponder: lib-httpd's server staples nothing, so enabling the option has\nto turn a working fetch into a failing one. Verified against an unpatched\nbuild, where exactly the two assertions that depend on the new option fail\nand the three controls still pass, and against OpenSSL, GnuTLS and\nmbedTLS-linked builds of libcurl.\n\nSigned-off-by: Grayson Gordon <graysongordon1@gmail.com>\n---\nv3: rename the test from t5567 to t5568. t5567 is taken on 'seen' by\n    mm/lib-httpd-cgi-safe. t5568 is free on master, next, seen, jch and\n    maint as of b9720e4723, and sits next to the other http tests. No\n    other change.\n\nv2: drop the block comment above the setopt. What it explained (why the\n    check is needed, and why the default is false) is already in the\n    commit message, which is where \"git blame\" leads anyone debugging\n    this. No code change otherwise.\n\n Documentation/config/http.adoc  | 17 +++++++\n http.c                          | 10 ++++\n t/t5568-http-verify-status.sh   | 72 +++++++++++++++++++++++++++++++\n 3 files changed, 99 insertions(+)\n create mode 100755 t/t5568-http-verify-status.sh\n\ndiff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc\nindex 792a71b413..40b849bf7f 100644\n--- a/Documentation/config/http.adoc\n+++ b/Documentation/config/http.adoc\n@@ -196,6 +196,23 @@ http.sslVerify::\n \tover HTTPS. Defaults to true. Can be overridden by the\n \t`GIT_SSL_NO_VERIFY` environment variable.\n \n+http.sslVerifyStatus::\n+\tWhether to check the revocation status of the server\n+\tcertificate using the stapled OCSP response supplied during\n+\tthe TLS handshake (\"OCSP stapling\"). Defaults to false.\n++\n+This is fail-closed: if the server staples no response, verification\n+fails. Set it per remote, e.g.\n+`http.https://example.com/.sslVerifyStatus`, rather than globally.\n++\n+What it changes depends on the TLS backend libcurl was built against.\n+An OpenSSL-linked build ignores a stapled response unless this is set.\n+A GnuTLS-linked build consults the staple during ordinary certificate\n+verification, so it already rejects a revoked certificate under\n+`http.sslVerify` alone, and setting this to `false` does not disable\n+that. Where a backend cannot check the staple at all, git fails with an\n+error rather than continuing unchecked.\n+\n http.sslCert::\n \tFile containing the SSL certificate when fetching or pushing\n \tover HTTPS. Can be overridden by the `GIT_SSL_CERT` environment\ndiff --git a/http.c b/http.c\nindex 5f0f42fb18..c1a66988e7 100644\n--- a/http.c\n+++ b/http.c\n@@ -44,6 +44,7 @@ static CURL *curl_default;\n char curl_errorstr[CURL_ERROR_SIZE];\n \n static int curl_ssl_verify = -1;\n+static int curl_ssl_verify_status;\n static int curl_ssl_try;\n static char *curl_http_version;\n static char *ssl_cert;\n@@ -400,6 +401,10 @@ static int http_options(const char *var, const char *value,\n \t\tcurl_ssl_verify = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(\"http.sslverifystatus\", var)) {\n+\t\tcurl_ssl_verify_status = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(\"http.sslcipherlist\", var))\n \t\treturn git_config_string(&ssl_cipherlist, var, value);\n \tif (!strcmp(\"http.sslversion\", var))\n@@ -1133,6 +1138,11 @@ static CURL *get_curl_handle(void)\n \t\tcurl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L);\n \t}\n \n+\tif (curl_ssl_verify_status &&\n+\t    curl_easy_setopt(result, CURLOPT_SSL_VERIFYSTATUS, 1L) != CURLE_OK)\n+\t\tdie(_(\"http.sslVerifyStatus is set, but the TLS backend of \"\n+\t\t      \"this libcurl cannot verify certificate status\"));\n+\n     if (curl_http_version) {\n \t\tlong opt;\n \t\tif (!get_curl_http_version_opt(curl_http_version, &opt)) {\ndiff --git a/t/t5568-http-verify-status.sh b/t/t5568-http-verify-status.sh\nnew file mode 100755\nindex 0000000000..c9167a05c2\n--- /dev/null\n+++ b/t/t5568-http-verify-status.sh\n@@ -0,0 +1,72 @@\n+#!/bin/sh\n+\n+test_description='http.sslVerifyStatus'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+LIB_HTTPD_SSL=t\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+start_httpd\n+\n+# The test server staples no OCSP response, and that is what makes this\n+# testable without standing up a CA and a responder: http.sslVerifyStatus is\n+# fail-closed, so turning it on has to turn a working fetch into a failing one.\n+#\n+# lib-httpd.sh exports GIT_SSL_NO_VERIFY for its self-signed certificate. In\n+# libcurl the status check is independent of peer verification, so it still\n+# applies here.\n+\n+test_expect_success 'setup repository' '\n+\techo content >file &&\n+\tgit add file &&\n+\tgit commit -m one\n+'\n+\n+test_expect_success 'create http-accessible bare repository' '\n+\tgit init --bare \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\" &&\n+\tgit remote add public \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\" &&\n+\tgit push public main:main\n+'\n+\n+# A TLS backend that cannot check the staple makes curl_easy_setopt() fail,\n+# which http.c reports with a distinct message. Skip in that case rather than\n+# reporting a failure that really means \"this libcurl was built differently\".\n+# Any other failure leaves the prerequisite satisfied on purpose, so a broken\n+# server makes the tests below fail loudly instead of silently vanishing.\n+test_lazy_prereq SSL_VERIFYSTATUS '\n+\tgit -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" 2>err\n+\t! grep \"cannot verify certificate status\" err\n+'\n+\n+test_expect_success 'ls-remote succeeds with http.sslVerifyStatus unset' '\n+\tgit ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=true fails without a staple' '\n+\ttest_must_fail git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=false is a no-op' '\n+\tgit -c http.sslVerifyStatus=false \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'per-URL configuration applies to a matching URL' '\n+\ttest_must_fail git -c \"http.$HTTPD_URL/.sslVerifyStatus=true\" \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'per-URL configuration is not applied to other URLs' '\n+\tgit -c \"http.https://example.com/.sslVerifyStatus=true\" \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_done\n-- \n2.55.0\n\n"},{"id":"550455","messageId":"xmqq1pc3vx9w.fsf@gitster.g","threadId":"66156","inReplyTo":"20260812182509.67358-1-ggordon@gitlab.com","subject":"Re: [PATCH v3] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-12T21:34:03Z","receivedAt":"2026-08-12T21:34:05Z","isPatch":true,"body":"graysongordon-gl <graysongordon1@gmail.com> writes:\n\n> v3: rename the test from t5567 to t5568. t5567 is taken on 'seen' by\n>     mm/lib-httpd-cgi-safe. t5568 is free on master, next, seen, jch and\n>     maint as of b9720e4723, and sits next to the other http tests. No\n>     other change.\n\nI thought I first asked whether we need a new script before\nsuggesting moving it out of the way because 't5567' was already\ntaken.  It is much better not to waste a scarce, shared resource\nsuch as a test number, and doing so avoids breaking the build if we\nare not careful.\n\nIf we really need to add a new script, you would need to squash in\nat least a patch like this to avoid breaking Meson-based builds.\n\n\n t/meson.build | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git i/t/meson.build w/t/meson.build\nindex 3219264fe7..3d68f67680 100644\n--- i/t/meson.build\n+++ w/t/meson.build\n@@ -707,6 +707,7 @@ integration_tests = [\n   't5564-http-proxy.sh',\n   't5565-push-multiple.sh',\n   't5566-push-group.sh',\n+  't5568-http-verify-status.sh',\n   't5570-git-daemon.sh',\n   't5571-pre-push-hook.sh',\n   't5572-pull-submodule.sh',\n"},{"id":"550533","messageId":"xmqqmruqt36l.fsf@gitster.g","threadId":"66156","inReplyTo":"xmqq1pc3vx9w.fsf@gitster.g","subject":"Re: [PATCH v3] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-13T16:06:58Z","receivedAt":"2026-08-13T16:07:01Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> graysongordon-gl <graysongordon1@gmail.com> writes:\n>\n>> v3: rename the test from t5567 to t5568. t5567 is taken on 'seen' by\n>>     mm/lib-httpd-cgi-safe. t5568 is free on master, next, seen, jch and\n>>     maint as of b9720e4723, and sits next to the other http tests. No\n>>     other change.\n>\n> I thought I first asked whether we need a new script before\n> suggesting moving it out of the way because 't5567' was already\n> taken.  It is much better not to waste a scarce, shared resource\n> such as a test number, and doing so avoids breaking the build if we\n> are not careful.\n>\n> If we really need to add a new script, you would need to squash in\n> at least a patch like this to avoid breaking Meson-based builds.\n>\n>\n>  t/meson.build | 1 +\n>  1 file changed, 1 insertion(+)\n>\n> diff --git i/t/meson.build w/t/meson.build\n> index 3219264fe7..3d68f67680 100644\n> --- i/t/meson.build\n> +++ w/t/meson.build\n> @@ -707,6 +707,7 @@ integration_tests = [\n>    't5564-http-proxy.sh',\n>    't5565-push-multiple.sh',\n>    't5566-push-group.sh',\n> +  't5568-http-verify-status.sh',\n>    't5570-git-daemon.sh',\n>    't5571-pre-push-hook.sh',\n>    't5572-pull-submodule.sh',\n\nBTW, exit status of ls-remote is lost without the following:\n\ndiff --git a/t/t5568-http-verify-status.sh b/t/t5568-http-verify-status.sh\nindex c9167a05c2..7ba70fc8af 100755\n--- a/t/t5568-http-verify-status.sh\n+++ b/t/t5568-http-verify-status.sh\n@@ -38,7 +38,7 @@ test_expect_success 'create http-accessible bare repository' '\n # server makes the tests below fail loudly instead of silently vanishing.\n test_lazy_prereq SSL_VERIFYSTATUS '\n \tgit -c http.sslVerifyStatus=true \\\n-\t\tls-remote \"$HTTPD_URL/smart/repo.git\" 2>err\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" 2>err &&\n \t! grep \"cannot verify certificate status\" err\n '\n \n"},{"id":"550717","messageId":"20260817185242.22736-1-ggordon@gitlab.com","threadId":"66156","inReplyTo":"xmqqmruqt36l.fsf@gitster.g","subject":"[PATCH v4] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"graysongordon-gl","fromEmail":"graysongordon1@gmail.com","sentAt":"2026-08-17T18:52:42Z","receivedAt":"2026-08-17T18:52:48Z","isPatch":true,"body":"From: Grayson Gordon <graysongordon1@gmail.com>\n\ngit asks libcurl to verify the peer certificate and the hostname, but it\nnever sets CURLOPT_SSL_VERIFYSTATUS, so the \"Certificate Status Request\"\nTLS extension is never requested and any stapled OCSP response the server\ndoes send is ignored.\n\nOn an OpenSSL-linked build this is silent. OpenSSL hands the stapled\nresponse to the application and takes no view on it:\nSSL_CTX_set_tlsext_status_cb(3) says the callback \"should determine\nwhether the returned OCSP response(s) are acceptable or not\", and libcurl\nonly installs that callback when CURLOPT_SSL_VERIFYSTATUS is set. So git\nwill fetch from a server whose own staple says its certificate has been\nrevoked.\n\nA GnuTLS-linked build behaves differently, and the difference does not\ncome from curl. GnuTLS consults a stapled response inside\ngnutls_certificate_verify_peers(), so the failure surfaces through the\nverifypeer branch of curl's GnuTLS backend (lib/vtls/gtls.c) whether or\nnot CURLOPT_SSL_VERIFYSTATUS was ever set. The same git, against the same\nserver, therefore enforces revocation or not depending only on how its\nlibcurl was built. That difference is documented here rather than papered\nover: this option turns the check on where the backend needs asking, and\nsetting it to false does not turn the check off on GnuTLS.\n\nAdd an http.sslVerifyStatus boolean that sets CURLOPT_SSL_VERIFYSTATUS.\nBecause http_options() is the collect_fn of a urlmatch config, the\nper-URL form works with no further changes:\n\n    git config http.https://example.com/.sslVerifyStatus true\n\nIt defaults to false, and has to. The option is fail-closed: libcurl fails\nverification when the server staples nothing at all, so turning this on\nglobally would break every remote that does not staple.\n\nLeaving the default to libcurl is not an option either. The same\ncomplaint was raised there in https://github.com/curl/curl/issues/15483\nand closed as intentional (\"Marked as enhancement since this was done on\npurpose\"), with the observation that stapling is expected to see less use\nas Let's Encrypt drops OCSP support. If the check is to be reachable at\nall, the lever has to come from the application.\n\nIf the TLS backend cannot check the staple, curl_easy_setopt() returns\nCURLE_NOT_BUILT_IN. Fail loudly there rather than carrying on, since\nsilently not checking is precisely what this option exists to prevent.\n\nCURLOPT_SSL_VERIFYSTATUS has been available since libcurl 7.41.0, well\nbelow the 7.61.0 floor documented in INSTALL, so no version guard is\nneeded.\n\nThe tests go in t5551 and run only in its https pass, which t5559 provides\nby sourcing t5551 with LIB_HTTPD_SSL set; that is the only https server\nthe suite has. They exercise the fail-closed path, which needs no CA and\nno OCSP responder: lib-httpd's server staples nothing, so enabling the\noption has to turn a working fetch into a failing one. Verified against an\nunpatched build, where exactly the two assertions that depend on the new\noption fail and the two controls still pass, and against OpenSSL, GnuTLS\nand mbedTLS-linked builds of libcurl.\n\nSigned-off-by: Grayson Gordon <graysongordon1@gmail.com>\n---\n\nv4: drop the new test script.  The four tests now live in t5551, keyed on\n$HTTPD_PROTO so they run in the https pass (t5559) only.\n\nTo answer the question I skipped past in v3: no, we do not need a new\nscript.  t5559 is t5551 run with LIB_HTTPD_SSL set, and it is the only\nhttps server the suite has, so a new script would have had to stand up a\nsecond one to reach the same place.  Nothing is left over from t5567 or\nt5568 and no test number is spent.\n\nThat also means the t/meson.build hunk is not squashed in.  t5551 is\nalready listed there.  Adding t5568 to the list now would break configure\nthe other way round, since the list is checked against ls in both\ndirections and errors with \"Test files configured, but not found\".\n\nOn the lost exit status: changed, but to test_might_fail rather than a\nbare &&.  The ls-remote in the prerequisite is expected to fail, that is\nthe premise of the test, so && short-circuits on the expected failure and\nleaves the prerequisite unsatisfied.  Run against the https server both\nways:\n\n    bare &&              ok 51 # skip http.sslVerifyStatus=true fails\n                                 without a staple (missing SSL_VERIFYSTATUS)\n    test_might_fail      ok 51 - http.sslVerifyStatus=true fails\n                                 without a staple\n\nThe first still reports \"passed all 61 test(s)\", which is the failure mode\nthe prerequisite was written to avoid.  test_might_fail keeps the chain\nintact and says the status is ignored on purpose.\n\nAlso dropped the \"ls-remote succeeds with http.sslVerifyStatus unset\"\ntest.  It was a control for the standalone script, and in t5551 the\nsurrounding tests already exercise that URL throughout.\n\nThe rationale that sat in an in-code comment in v3 is in the log now, per\nthe earlier review.\n\nVerified: t5551 over plain http and t5559 over https both pass all 61\ntests, with the four new ones skipping on the former and running on the\nlatter.\n Documentation/config/http.adoc | 17 +++++++++++++++++\n http.c                         | 10 ++++++++++\n t/t5551-http-fetch-smart.sh    | 29 +++++++++++++++++++++++++++++\n 3 files changed, 56 insertions(+)\n\ndiff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc\nindex 792a71b413..40b849bf7f 100644\n--- a/Documentation/config/http.adoc\n+++ b/Documentation/config/http.adoc\n@@ -196,6 +196,23 @@ http.sslVerify::\n \tover HTTPS. Defaults to true. Can be overridden by the\n \t`GIT_SSL_NO_VERIFY` environment variable.\n \n+http.sslVerifyStatus::\n+\tWhether to check the revocation status of the server\n+\tcertificate using the stapled OCSP response supplied during\n+\tthe TLS handshake (\"OCSP stapling\"). Defaults to false.\n++\n+This is fail-closed: if the server staples no response, verification\n+fails. Set it per remote, e.g.\n+`http.https://example.com/.sslVerifyStatus`, rather than globally.\n++\n+What it changes depends on the TLS backend libcurl was built against.\n+An OpenSSL-linked build ignores a stapled response unless this is set.\n+A GnuTLS-linked build consults the staple during ordinary certificate\n+verification, so it already rejects a revoked certificate under\n+`http.sslVerify` alone, and setting this to `false` does not disable\n+that. Where a backend cannot check the staple at all, git fails with an\n+error rather than continuing unchecked.\n+\n http.sslCert::\n \tFile containing the SSL certificate when fetching or pushing\n \tover HTTPS. Can be overridden by the `GIT_SSL_CERT` environment\ndiff --git a/http.c b/http.c\nindex caccf2108e..94f8dd817a 100644\n--- a/http.c\n+++ b/http.c\n@@ -44,6 +44,7 @@ static CURL *curl_default;\n char curl_errorstr[CURL_ERROR_SIZE];\n \n static int curl_ssl_verify = -1;\n+static int curl_ssl_verify_status;\n static int curl_ssl_try;\n static char *curl_http_version;\n static char *ssl_cert;\n@@ -400,6 +401,10 @@ static int http_options(const char *var, const char *value,\n \t\tcurl_ssl_verify = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(\"http.sslverifystatus\", var)) {\n+\t\tcurl_ssl_verify_status = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(\"http.sslcipherlist\", var))\n \t\treturn git_config_string(&ssl_cipherlist, var, value);\n \tif (!strcmp(\"http.sslversion\", var))\n@@ -1133,6 +1138,11 @@ static CURL *get_curl_handle(void)\n \t\tcurl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L);\n \t}\n \n+\tif (curl_ssl_verify_status &&\n+\t    curl_easy_setopt(result, CURLOPT_SSL_VERIFYSTATUS, 1L) != CURLE_OK)\n+\t\tdie(_(\"http.sslVerifyStatus is set, but the TLS backend of \"\n+\t\t      \"this libcurl cannot verify certificate status\"));\n+\n     if (curl_http_version) {\n \t\tlong opt;\n \t\tif (!get_curl_http_version_opt(curl_http_version, &opt)) {\ndiff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh\nindex 805bec025c..c11e96c1ac 100755\n--- a/t/t5551-http-fetch-smart.sh\n+++ b/t/t5551-http-fetch-smart.sh\n@@ -680,6 +680,35 @@ test_expect_success 'passing hostname resolution information works' '\n \tgit -c \"http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1\" ls-remote \"$BOGUS_HTTPD_URL/smart/repo.git\" >/dev/null\n '\n \n+test_lazy_prereq SSL_VERIFYSTATUS '\n+\ttest \"$HTTPD_PROTO\" = \"https\" &&\n+\ttest_might_fail git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" 2>err &&\n+\t! grep \"cannot verify certificate status\" err\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=true fails without a staple' '\n+\ttest_must_fail git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=false is a no-op' '\n+\tgit -c http.sslVerifyStatus=false \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus applies to a matching URL' '\n+\ttest_must_fail git -c \"http.$HTTPD_URL/.sslVerifyStatus=true\" \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus is not applied to other URLs' '\n+\tgit -c \"http.https://example.com/.sslVerifyStatus=true\" \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n # here user%40host is the URL-encoded version of user@host,\n # which is our intentionally-odd username to catch parsing errors\n url_user=$HTTPD_URL_USER/auth/smart/repo.git\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"550720","messageId":"xmqqfr0c4kss.fsf@gitster.g","threadId":"66156","inReplyTo":"20260817185242.22736-1-ggordon@gitlab.com","subject":"Re: [PATCH v4] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-17T19:19:15Z","receivedAt":"2026-08-17T19:19:18Z","isPatch":true,"body":"graysongordon-gl <graysongordon1@gmail.com> writes:\n\n> Verified: t5551 over plain http and t5559 over https both pass all 61\n> tests, with the four new ones skipping on the former and running on the\n> latter.\n>  Documentation/config/http.adoc | 17 +++++++++++++++++\n>  http.c                         | 10 ++++++++++\n>  t/t5551-http-fetch-smart.sh    | 29 +++++++++++++++++++++++++++++\n>  3 files changed, 56 insertions(+)\n\nOK, instead of adding a new test script that weighs 72-line we are\ntesting the feature with 29-line addition, which sounds like a good\neconomy ;-).\n\nThe code changes and the documentation haven't changed since the\nprevious round, both looking good.\n\nWill replace.  Thanks.\n\n> diff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc\n> index 792a71b413..40b849bf7f 100644\n> --- a/Documentation/config/http.adoc\n> +++ b/Documentation/config/http.adoc\n> @@ -196,6 +196,23 @@ http.sslVerify::\n>  \tover HTTPS. Defaults to true. Can be overridden by the\n>  \t`GIT_SSL_NO_VERIFY` environment variable.\n>  \n> +http.sslVerifyStatus::\n> +\tWhether to check the revocation status of the server\n> +\tcertificate using the stapled OCSP response supplied during\n> +\tthe TLS handshake (\"OCSP stapling\"). Defaults to false.\n> ++\n> +This is fail-closed: if the server staples no response, verification\n> +fails. Set it per remote, e.g.\n> +`http.https://example.com/.sslVerifyStatus`, rather than globally.\n> ++\n> +What it changes depends on the TLS backend libcurl was built against.\n> +An OpenSSL-linked build ignores a stapled response unless this is set.\n> +A GnuTLS-linked build consults the staple during ordinary certificate\n> +verification, so it already rejects a revoked certificate under\n> +`http.sslVerify` alone, and setting this to `false` does not disable\n> +that. Where a backend cannot check the staple at all, git fails with an\n> +error rather than continuing unchecked.\n> +\n>  http.sslCert::\n>  \tFile containing the SSL certificate when fetching or pushing\n>  \tover HTTPS. Can be overridden by the `GIT_SSL_CERT` environment\n> diff --git a/http.c b/http.c\n> index caccf2108e..94f8dd817a 100644\n> --- a/http.c\n> +++ b/http.c\n> @@ -44,6 +44,7 @@ static CURL *curl_default;\n>  char curl_errorstr[CURL_ERROR_SIZE];\n>  \n>  static int curl_ssl_verify = -1;\n> +static int curl_ssl_verify_status;\n>  static int curl_ssl_try;\n>  static char *curl_http_version;\n>  static char *ssl_cert;\n> @@ -400,6 +401,10 @@ static int http_options(const char *var, const char *value,\n>  \t\tcurl_ssl_verify = git_config_bool(var, value);\n>  \t\treturn 0;\n>  \t}\n> +\tif (!strcmp(\"http.sslverifystatus\", var)) {\n> +\t\tcurl_ssl_verify_status = git_config_bool(var, value);\n> +\t\treturn 0;\n> +\t}\n>  \tif (!strcmp(\"http.sslcipherlist\", var))\n>  \t\treturn git_config_string(&ssl_cipherlist, var, value);\n>  \tif (!strcmp(\"http.sslversion\", var))\n> @@ -1133,6 +1138,11 @@ static CURL *get_curl_handle(void)\n>  \t\tcurl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L);\n>  \t}\n>  \n> +\tif (curl_ssl_verify_status &&\n> +\t    curl_easy_setopt(result, CURLOPT_SSL_VERIFYSTATUS, 1L) != CURLE_OK)\n> +\t\tdie(_(\"http.sslVerifyStatus is set, but the TLS backend of \"\n> +\t\t      \"this libcurl cannot verify certificate status\"));\n> +\n>      if (curl_http_version) {\n>  \t\tlong opt;\n>  \t\tif (!get_curl_http_version_opt(curl_http_version, &opt)) {\n> diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh\n> index 805bec025c..c11e96c1ac 100755\n> --- a/t/t5551-http-fetch-smart.sh\n> +++ b/t/t5551-http-fetch-smart.sh\n> @@ -680,6 +680,35 @@ test_expect_success 'passing hostname resolution information works' '\n>  \tgit -c \"http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1\" ls-remote \"$BOGUS_HTTPD_URL/smart/repo.git\" >/dev/null\n>  '\n>  \n> +test_lazy_prereq SSL_VERIFYSTATUS '\n> +\ttest \"$HTTPD_PROTO\" = \"https\" &&\n> +\ttest_might_fail git -c http.sslVerifyStatus=true \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\" 2>err &&\n> +\t! grep \"cannot verify certificate status\" err\n> +'\n> +\n> +test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=true fails without a staple' '\n> +\ttest_must_fail git -c http.sslVerifyStatus=true \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n> +'\n> +\n> +test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=false is a no-op' '\n> +\tgit -c http.sslVerifyStatus=false \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n> +\ttest_line_count -gt 0 actual\n> +'\n> +\n> +test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus applies to a matching URL' '\n> +\ttest_must_fail git -c \"http.$HTTPD_URL/.sslVerifyStatus=true\" \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n> +'\n> +\n> +test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus is not applied to other URLs' '\n> +\tgit -c \"http.https://example.com/.sslVerifyStatus=true\" \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n> +\ttest_line_count -gt 0 actual\n> +'\n> +\n>  # here user%40host is the URL-encoded version of user@host,\n>  # which is our intentionally-odd username to catch parsing errors\n>  url_user=$HTTPD_URL_USER/auth/smart/repo.git\n"},{"id":"550733","messageId":"aoQOxISPfEwh-ik2@pks.im","threadId":"66156","inReplyTo":"20260817185242.22736-1-ggordon@gitlab.com","subject":"Re: [PATCH v4] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-18T07:50:28Z","receivedAt":"2026-08-18T07:50:40Z","isPatch":true,"body":"On Mon, Aug 17, 2026 at 02:52:42PM -0400, graysongordon-gl wrote:\n> From: Grayson Gordon <graysongordon1@gmail.com>\n> \n> git asks libcurl to verify the peer certificate and the hostname, but it\n> never sets CURLOPT_SSL_VERIFYSTATUS, so the \"Certificate Status Request\"\n> TLS extension is never requested and any stapled OCSP response the server\n> does send is ignored.\n> \n> On an OpenSSL-linked build this is silent. OpenSSL hands the stapled\n> response to the application and takes no view on it:\n> SSL_CTX_set_tlsext_status_cb(3) says the callback \"should determine\n> whether the returned OCSP response(s) are acceptable or not\", and libcurl\n> only installs that callback when CURLOPT_SSL_VERIFYSTATUS is set. So git\n> will fetch from a server whose own staple says its certificate has been\n> revoked.\n> \n> A GnuTLS-linked build behaves differently, and the difference does not\n> come from curl. GnuTLS consults a stapled response inside\n> gnutls_certificate_verify_peers(), so the failure surfaces through the\n> verifypeer branch of curl's GnuTLS backend (lib/vtls/gtls.c) whether or\n> not CURLOPT_SSL_VERIFYSTATUS was ever set. The same git, against the same\n> server, therefore enforces revocation or not depending only on how its\n> libcurl was built. That difference is documented here rather than papered\n> over: this option turns the check on where the backend needs asking, and\n> setting it to false does not turn the check off on GnuTLS.\n\nThis is only part of the story though: GnuTLS 3.8 introduced\nGNUTLS_NO_STATUS_REQUEST, and curl 8.10 started to set that option in\ncase of `!verifystatus`. So with new-enough versions of both libraries,\nGit behaves the same no matter whether we use OpenSSL or GnuTLS as\nbackend. See also aeb1a281ca (gtls: fix OCSP stapling management,\n2024-08-20) in curl.\n\n> Add an http.sslVerifyStatus boolean that sets CURLOPT_SSL_VERIFYSTATUS.\n> Because http_options() is the collect_fn of a urlmatch config, the\n> per-URL form works with no further changes:\n> \n>     git config http.https://example.com/.sslVerifyStatus true\n> \n> It defaults to false, and has to. The option is fail-closed: libcurl fails\n> verification when the server staples nothing at all, so turning this on\n> globally would break every remote that does not staple.\n> \n> Leaving the default to libcurl is not an option either. The same\n> complaint was raised there in https://github.com/curl/curl/issues/15483\n> and closed as intentional (\"Marked as enhancement since this was done on\n> purpose\"), with the observation that stapling is expected to see less use\n> as Let's Encrypt drops OCSP support. If the check is to be reachable at\n> all, the lever has to come from the application.\n\nBut... don't we still leave the default to libcurl? If\n\"http.sslVerifyStatus\" is not set then we don't touch\n`CURLOPT_SSL_VERIFYSTATUS`, either.\n\nI might be misreading this though, as the whole commit message is quite\nhard to digest. I'd assume that this is because it's generated by AI,\nand it added a lot of the usual weird phrases to the message. It might\nbe a good idea to adapt the message to have a bit more of a human touch\nto it.\n\n> If the TLS backend cannot check the staple, curl_easy_setopt() returns\n> CURLE_NOT_BUILT_IN. Fail loudly there rather than carrying on, since\n> silently not checking is precisely what this option exists to prevent.\n\nMakes sense.\n\n> diff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc\n> index 792a71b413..40b849bf7f 100644\n> --- a/Documentation/config/http.adoc\n> +++ b/Documentation/config/http.adoc\n> @@ -196,6 +196,23 @@ http.sslVerify::\n>  \tover HTTPS. Defaults to true. Can be overridden by the\n>  \t`GIT_SSL_NO_VERIFY` environment variable.\n>  \n> +http.sslVerifyStatus::\n> +\tWhether to check the revocation status of the server\n> +\tcertificate using the stapled OCSP response supplied during\n> +\tthe TLS handshake (\"OCSP stapling\"). Defaults to false.\n> ++\n> +This is fail-closed: if the server staples no response, verification\n> +fails. Set it per remote, e.g.\n> +`http.https://example.com/.sslVerifyStatus`, rather than globally.\n> ++\n> +What it changes depends on the TLS backend libcurl was built against.\n> +An OpenSSL-linked build ignores a stapled response unless this is set.\n> +A GnuTLS-linked build consults the staple during ordinary certificate\n> +verification, so it already rejects a revoked certificate under\n> +`http.sslVerify` alone, and setting this to `false` does not disable\n> +that. Where a backend cannot check the staple at all, git fails with an\n> +error rather than continuing unchecked.\n\nThis information is not accurate because recent GnuTLS+libcurl versions\nhandle this the same as OpenSSL, as mentioned above.\n\nAlso, it might make sense to convert the backend-specific information\ninto a bulleted list as we may add more items to it going forward. Do we\nhave any info how other backends like mbedTLS behave? Or do we know that\nthose all fail.\n\n> diff --git a/http.c b/http.c\n> index caccf2108e..94f8dd817a 100644\n> --- a/http.c\n> +++ b/http.c\n> @@ -400,6 +401,10 @@ static int http_options(const char *var, const char *value,\n>  \t\tcurl_ssl_verify = git_config_bool(var, value);\n>  \t\treturn 0;\n>  \t}\n> +\tif (!strcmp(\"http.sslverifystatus\", var)) {\n> +\t\tcurl_ssl_verify_status = git_config_bool(var, value);\n> +\t\treturn 0;\n> +\t}\n>  \tif (!strcmp(\"http.sslcipherlist\", var))\n>  \t\treturn git_config_string(&ssl_cipherlist, var, value);\n>  \tif (!strcmp(\"http.sslversion\", var))\n> @@ -1133,6 +1138,11 @@ static CURL *get_curl_handle(void)\n>  \t\tcurl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L);\n>  \t}\n>  \n> +\tif (curl_ssl_verify_status &&\n> +\t    curl_easy_setopt(result, CURLOPT_SSL_VERIFYSTATUS, 1L) != CURLE_OK)\n> +\t\tdie(_(\"http.sslVerifyStatus is set, but the TLS backend of \"\n> +\t\t      \"this libcurl cannot verify certificate status\"));\n\nShould we include the output of `curl_easy_strerror()` in the error\nmessage? That'd cause us to include the following error message in case\nwe see CURLE_NOT_BUILT_IN:\n\n  case CURLE_NOT_BUILT_IN:\n    return \"A requested feature, protocol or option was not found built-in in\"\n           \" this libcurl due to a build-time decision.\";\n\nSo we could instead do:\n\n\tif (curl_ssl_verify_status) {\n\t        CURLcode error = curl_easy_setopt(result, CURLOPT_SSL_VERIFYSTATUS, 1L);\n                if (error != CURLE_OK)\n                    die(_(\"http.sslVerifyStatus is set, but could not enable OCSP status verification: %s\"),\n                        curl_easy_strerror(error));\n        }\n\n> diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh\n> index 805bec025c..c11e96c1ac 100755\n> --- a/t/t5551-http-fetch-smart.sh\n> +++ b/t/t5551-http-fetch-smart.sh\n> @@ -680,6 +680,35 @@ test_expect_success 'passing hostname resolution information works' '\n>  \tgit -c \"http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1\" ls-remote \"$BOGUS_HTTPD_URL/smart/repo.git\" >/dev/null\n>  '\n>  \n> +test_lazy_prereq SSL_VERIFYSTATUS '\n> +\ttest \"$HTTPD_PROTO\" = \"https\" &&\n> +\ttest_might_fail git -c http.sslVerifyStatus=true \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\" 2>err &&\n> +\t! grep \"cannot verify certificate status\" err\n> +'\n> +\n> +test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=true fails without a staple' '\n> +\ttest_must_fail git -c http.sslVerifyStatus=true \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n> +'\n> +\n> +test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=false is a no-op' '\n> +\tgit -c http.sslVerifyStatus=false \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n> +\ttest_line_count -gt 0 actual\n> +'\n> +\n> +test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus applies to a matching URL' '\n> +\ttest_must_fail git -c \"http.$HTTPD_URL/.sslVerifyStatus=true\" \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n> +'\n> +\n> +test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus is not applied to other URLs' '\n> +\tgit -c \"http.https://example.com/.sslVerifyStatus=true\" \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n> +\ttest_line_count -gt 0 actual\n> +'\n\nCan we reasonably add tests that send OCSP information and verify that\nenabling \"sslVerifyStatus\" makes this work as expected?\n\nPatrick\n"},{"id":"550758","messageId":"CALgUfNhxLEeTK5xH9Dw9ZPBG+oPq9Fw1qDgt=wbXqrnuEetJyw@mail.gmail.com","threadId":"66156","inReplyTo":"aoQOxISPfEwh-ik2@pks.im","subject":"Re: [PATCH v4] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Grayson Gordon","fromEmail":"graysongordon1@gmail.com","sentAt":"2026-08-18T14:51:08Z","receivedAt":"2026-08-18T14:51:22Z","isPatch":true,"body":"Patrick,\n\nThanks again for the feedback. I'm going to break this into sections\ndelimited by all caps headers to address each thing you mentioned.\n\nON GNUTLS VS OPENSSL DIFFERENCES\n\nI appreciate you including the extra context around GnuTLS 3.8's\nGNUTLS_NO_STATUS_REQUEST flag, and curl 8.10 setting it when\n\"verifystatus\" is false.\n\nFair enough, said another way, if either of these are true:\nCondition 1: curl is being built with GnuTLS version < 3.8. (There's\nno NO_STATUS_REQUEST flag to set.)\nOR\nCondition 2:  curl version < 8.10. (Curl's not using the flag.)\n\nYou'll see a discrepancy in cert verification behavior between the\nversions of git built with GnuTLS vs OpenSSL.\n\nWhile I appreciate the increased precision here, the purpose of my\ncontribution was to expose the functionality that enables git users to\nset it if they so choose. As it stands today, this option is not\npresented. So while the discrepancy is what incited me to look deeper,\nit isn't the central reason I'm here.\n\nYou had a section further down that said: \"This information is not\naccurate because recent GnuTLS+libcurl versions\nhandle this the same as OpenSSL, as mentioned above.\" I think that\nthis response section suffices for both.\n\nI didn't look into any other TLS backends, as curl's docs say that the\nverifystatus option only works with GnuTLS and OpenSSL\nhttps://curl.se/libcurl/c/CURLOPT_SSL_VERIFYSTATUS.html and the other\nbackend options didn't apply to GitLab customers as it relates to our\nCNG charts. There may still be value in looking and capturing it in\nthe git docs somewhere.\n\n---------------\n\nON DEFAULT BEHAVIOR AND THE COMMIT MESSAGE\n\nIn reality we ARE leaving the default to curl without the flag being\nset, so my comment \"Leaving the default to libcurl is not an option\neither.\" wasn't super precise either. A nit.\nAgain the change that's being introduced here is an OPTION for git\nusers to include the \"verifystatus\" flag.\n\nSorry that the commit message feels a bit awkward to you, I can rework\nit to be a bit more direct and not spend as much time on\njustifications if that'll be easier to read.\n\n---------------\n\nON DESCRIPTIVE ERROR MESSAGES\n\nYes, I like this idea. We could give user's a much clearer error that\nway. I'll include that. My tests grep for the current string though,\nso I'll have to update that too.\n\n---------------\n\nON COMPREHENSIVE TESTING\n\nI'll leave this at you and Junio's discretion. I worked with him\nearlier in this thread to avoid introducing another test file and keep\nthe testing succinct.\nRight now these tests are just limited to parsing the config and\napplying it to the user-provided remote.\nWe COULD do the full suite of tests that cover the full range of\ncases/behaviors:\n- The flag is set AND\n    - no staple sent (should fail)\n    - good staple (should pass)\n    - bad staple (should fail)\netc.\n\nWe're going to need a lot of infrax for that though:\n- test CA.\n- test server certificate issued by that CA.\n- OCSP responder which knows the certificate's status.\n- a way for the TLS server to obtain and staple that response.\n- a way to control the response so you can test good vs revoked/invalid.\n\nI set all of that stuff up in my own experiment repo, emulating this\nwith nginx in docker...\n\nIt's feasible, just need to know how you all would like it.\n\n- Grayson\n\nOn Tue, Aug 18, 2026 at 3:50 AM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Mon, Aug 17, 2026 at 02:52:42PM -0400, graysongordon-gl wrote:\n> > From: Grayson Gordon <graysongordon1@gmail.com>\n> >\n> > git asks libcurl to verify the peer certificate and the hostname, but it\n> > never sets CURLOPT_SSL_VERIFYSTATUS, so the \"Certificate Status Request\"\n> > TLS extension is never requested and any stapled OCSP response the server\n> > does send is ignored.\n> >\n> > On an OpenSSL-linked build this is silent. OpenSSL hands the stapled\n> > response to the application and takes no view on it:\n> > SSL_CTX_set_tlsext_status_cb(3) says the callback \"should determine\n> > whether the returned OCSP response(s) are acceptable or not\", and libcurl\n> > only installs that callback when CURLOPT_SSL_VERIFYSTATUS is set. So git\n> > will fetch from a server whose own staple says its certificate has been\n> > revoked.\n> >\n> > A GnuTLS-linked build behaves differently, and the difference does not\n> > come from curl. GnuTLS consults a stapled response inside\n> > gnutls_certificate_verify_peers(), so the failure surfaces through the\n> > verifypeer branch of curl's GnuTLS backend (lib/vtls/gtls.c) whether or\n> > not CURLOPT_SSL_VERIFYSTATUS was ever set. The same git, against the same\n> > server, therefore enforces revocation or not depending only on how its\n> > libcurl was built. That difference is documented here rather than papered\n> > over: this option turns the check on where the backend needs asking, and\n> > setting it to false does not turn the check off on GnuTLS.\n>\n> This is only part of the story though: GnuTLS 3.8 introduced\n> GNUTLS_NO_STATUS_REQUEST, and curl 8.10 started to set that option in\n> case of `!verifystatus`. So with new-enough versions of both libraries,\n> Git behaves the same no matter whether we use OpenSSL or GnuTLS as\n> backend. See also aeb1a281ca (gtls: fix OCSP stapling management,\n> 2024-08-20) in curl.\n>\n> > Add an http.sslVerifyStatus boolean that sets CURLOPT_SSL_VERIFYSTATUS.\n> > Because http_options() is the collect_fn of a urlmatch config, the\n> > per-URL form works with no further changes:\n> >\n> >     git config http.https://example.com/.sslVerifyStatus true\n> >\n> > It defaults to false, and has to. The option is fail-closed: libcurl fails\n> > verification when the server staples nothing at all, so turning this on\n> > globally would break every remote that does not staple.\n> >\n> > Leaving the default to libcurl is not an option either. The same\n> > complaint was raised there in https://github.com/curl/curl/issues/15483\n> > and closed as intentional (\"Marked as enhancement since this was done on\n> > purpose\"), with the observation that stapling is expected to see less use\n> > as Let's Encrypt drops OCSP support. If the check is to be reachable at\n> > all, the lever has to come from the application.\n>\n> But... don't we still leave the default to libcurl? If\n> \"http.sslVerifyStatus\" is not set then we don't touch\n> `CURLOPT_SSL_VERIFYSTATUS`, either.\n>\n> I might be misreading this though, as the whole commit message is quite\n> hard to digest. I'd assume that this is because it's generated by AI,\n> and it added a lot of the usual weird phrases to the message. It might\n> be a good idea to adapt the message to have a bit more of a human touch\n> to it.\n>\n> > If the TLS backend cannot check the staple, curl_easy_setopt() returns\n> > CURLE_NOT_BUILT_IN. Fail loudly there rather than carrying on, since\n> > silently not checking is precisely what this option exists to prevent.\n>\n> Makes sense.\n>\n> > diff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc\n> > index 792a71b413..40b849bf7f 100644\n> > --- a/Documentation/config/http.adoc\n> > +++ b/Documentation/config/http.adoc\n> > @@ -196,6 +196,23 @@ http.sslVerify::\n> >       over HTTPS. Defaults to true. Can be overridden by the\n> >       `GIT_SSL_NO_VERIFY` environment variable.\n> >\n> > +http.sslVerifyStatus::\n> > +     Whether to check the revocation status of the server\n> > +     certificate using the stapled OCSP response supplied during\n> > +     the TLS handshake (\"OCSP stapling\"). Defaults to false.\n> > ++\n> > +This is fail-closed: if the server staples no response, verification\n> > +fails. Set it per remote, e.g.\n> > +`http.https://example.com/.sslVerifyStatus`, rather than globally.\n> > ++\n> > +What it changes depends on the TLS backend libcurl was built against.\n> > +An OpenSSL-linked build ignores a stapled response unless this is set.\n> > +A GnuTLS-linked build consults the staple during ordinary certificate\n> > +verification, so it already rejects a revoked certificate under\n> > +`http.sslVerify` alone, and setting this to `false` does not disable\n> > +that. Where a backend cannot check the staple at all, git fails with an\n> > +error rather than continuing unchecked.\n>\n> This information is not accurate because recent GnuTLS+libcurl versions\n> handle this the same as OpenSSL, as mentioned above.\n>\n> Also, it might make sense to convert the backend-specific information\n> into a bulleted list as we may add more items to it going forward. Do we\n> have any info how other backends like mbedTLS behave? Or do we know that\n> those all fail.\n>\n> > diff --git a/http.c b/http.c\n> > index caccf2108e..94f8dd817a 100644\n> > --- a/http.c\n> > +++ b/http.c\n> > @@ -400,6 +401,10 @@ static int http_options(const char *var, const char *value,\n> >               curl_ssl_verify = git_config_bool(var, value);\n> >               return 0;\n> >       }\n> > +     if (!strcmp(\"http.sslverifystatus\", var)) {\n> > +             curl_ssl_verify_status = git_config_bool(var, value);\n> > +             return 0;\n> > +     }\n> >       if (!strcmp(\"http.sslcipherlist\", var))\n> >               return git_config_string(&ssl_cipherlist, var, value);\n> >       if (!strcmp(\"http.sslversion\", var))\n> > @@ -1133,6 +1138,11 @@ static CURL *get_curl_handle(void)\n> >               curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L);\n> >       }\n> >\n> > +     if (curl_ssl_verify_status &&\n> > +         curl_easy_setopt(result, CURLOPT_SSL_VERIFYSTATUS, 1L) != CURLE_OK)\n> > +             die(_(\"http.sslVerifyStatus is set, but the TLS backend of \"\n> > +                   \"this libcurl cannot verify certificate status\"));\n>\n> Should we include the output of `curl_easy_strerror()` in the error\n> message? That'd cause us to include the following error message in case\n> we see CURLE_NOT_BUILT_IN:\n>\n>   case CURLE_NOT_BUILT_IN:\n>     return \"A requested feature, protocol or option was not found built-in in\"\n>            \" this libcurl due to a build-time decision.\";\n>\n> So we could instead do:\n>\n>         if (curl_ssl_verify_status) {\n>                 CURLcode error = curl_easy_setopt(result, CURLOPT_SSL_VERIFYSTATUS, 1L);\n>                 if (error != CURLE_OK)\n>                     die(_(\"http.sslVerifyStatus is set, but could not enable OCSP status verification: %s\"),\n>                         curl_easy_strerror(error));\n>         }\n>\n> > diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh\n> > index 805bec025c..c11e96c1ac 100755\n> > --- a/t/t5551-http-fetch-smart.sh\n> > +++ b/t/t5551-http-fetch-smart.sh\n> > @@ -680,6 +680,35 @@ test_expect_success 'passing hostname resolution information works' '\n> >       git -c \"http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1\" ls-remote \"$BOGUS_HTTPD_URL/smart/repo.git\" >/dev/null\n> >  '\n> >\n> > +test_lazy_prereq SSL_VERIFYSTATUS '\n> > +     test \"$HTTPD_PROTO\" = \"https\" &&\n> > +     test_might_fail git -c http.sslVerifyStatus=true \\\n> > +             ls-remote \"$HTTPD_URL/smart/repo.git\" 2>err &&\n> > +     ! grep \"cannot verify certificate status\" err\n> > +'\n> > +\n> > +test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=true fails without a staple' '\n> > +     test_must_fail git -c http.sslVerifyStatus=true \\\n> > +             ls-remote \"$HTTPD_URL/smart/repo.git\"\n> > +'\n> > +\n> > +test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=false is a no-op' '\n> > +     git -c http.sslVerifyStatus=false \\\n> > +             ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n> > +     test_line_count -gt 0 actual\n> > +'\n> > +\n> > +test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus applies to a matching URL' '\n> > +     test_must_fail git -c \"http.$HTTPD_URL/.sslVerifyStatus=true\" \\\n> > +             ls-remote \"$HTTPD_URL/smart/repo.git\"\n> > +'\n> > +\n> > +test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus is not applied to other URLs' '\n> > +     git -c \"http.https://example.com/.sslVerifyStatus=true\" \\\n> > +             ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n> > +     test_line_count -gt 0 actual\n> > +'\n>\n> Can we reasonably add tests that send OCSP information and verify that\n> enabling \"sslVerifyStatus\" makes this work as expected?\n>\n> Patrick\n"},{"id":"550766","messageId":"xmqqecfv1iw8.fsf@gitster.g","threadId":"66156","inReplyTo":"aoQOxISPfEwh-ik2@pks.im","subject":"Re: [PATCH v4] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-18T16:40:55Z","receivedAt":"2026-08-18T16:40:57Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> This is only part of the story though: GnuTLS 3.8 introduced\n> GNUTLS_NO_STATUS_REQUEST, and curl 8.10 started to set that option in\n> case of `!verifystatus`. So with new-enough versions of both libraries,\n> Git behaves the same no matter whether we use OpenSSL or GnuTLS as\n> backend. See also aeb1a281ca (gtls: fix OCSP stapling management,\n> 2024-08-20) in curl.\n\nThanks for additional details.\n\n>> Add an http.sslVerifyStatus boolean that sets CURLOPT_SSL_VERIFYSTATUS.\n>> Because http_options() is the collect_fn of a urlmatch config, the\n>> per-URL form works with no further changes:\n>> \n>>     git config http.https://example.com/.sslVerifyStatus true\n>> \n>> It defaults to false, and has to. The option is fail-closed: libcurl fails\n>> verification when the server staples nothing at all, so turning this on\n>> globally would break every remote that does not staple.\n>> \n>> Leaving the default to libcurl is not an option either. The same\n>> complaint was raised there in https://github.com/curl/curl/issues/15483\n>> and closed as intentional (\"Marked as enhancement since this was done on\n>> purpose\"), with the observation that stapling is expected to see less use\n>> as Let's Encrypt drops OCSP support. If the check is to be reachable at\n>> all, the lever has to come from the application.\n>\n> But... don't we still leave the default to libcurl? If\n> \"http.sslVerifyStatus\" is not set then we don't touch\n> `CURLOPT_SSL_VERIFYSTATUS`, either.\n>\n> I might be misreading this though, as the whole commit message is quite\n> hard to digest. I'd assume that this is because it's generated by AI,\n> and it added a lot of the usual weird phrases to the message. It might\n> be a good idea to adapt the message to have a bit more of a human touch\n> to it.\n\nI too had trouble figuring out what the proposed log message really\nwanted to say, but I wrote it off, blaming the difficulty on a\nlanguage barrier.  But as you said, perhaps it is because it was\nwritten by something that does not truly understand what it is\ntalking about.  It may not have to explain things to readers as if\nthey were 5 years old, but it is definitely necessary to explain\nwell to readers as if they were humans with average intelligence\n;-).\n\n"},{"id":"550775","messageId":"20260818193710.56955-1-ggordon@gitlab.com","threadId":"66156","inReplyTo":"xmqqmruqt36l.fsf@gitster.g","subject":"[PATCH v5] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"graysongordon-gl","fromEmail":"graysongordon1@gmail.com","sentAt":"2026-08-18T19:37:10Z","receivedAt":"2026-08-18T19:37:34Z","isPatch":true,"body":"From: Grayson Gordon <graysongordon1@gmail.com>\n\ngit never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the\nOCSP \"Certificate Status Request\" extension and any stapled response a\nserver sends is ignored, including responses that explicitly state the\ncertificate has been revoked.\n\nAdd an http.sslVerifyStatus boolean that maps to\nCURLOPT_SSL_VERIFYSTATUS.\nhttp_options() is already the collect_fn for a urlmatch config, so the\nper-URL form works with no changes:\n\n    git config http.https://example.com/.sslVerifyStatus true\n\nDefaults to false/\"off\". This is due to the nature of the OCSP protocol.\nIf enabled, git would expect to receive OCSP stapled responses. If the\nstapled responses were not present, the connection would be blocked as\nthe status of the server's certificate could not be verified. This would\nbreak connections to legitimate services that don't use OCSP as their\ncertificate revocation mechanism.\n\nIf the backend can't check the staple, curl_easy_setopt() returns\nCURLE_NOT_BUILT_IN. Error message includes curl_easy_strerror() with\nthe option name to enable users to more easily identify a libcurl\nbuilt without status verification.\n\nCURLOPT_SSL_VERIFYSTATUS has existed since libcurl 7.41.0, below our\n7.61.0 floor, so no version guard is needed.\n\nTests are in t5551.\n\nSigned-off-by: Grayson Gordon <graysongordon1@gmail.com>\n---\n Documentation/config/http.adoc |  9 +++++++++\n http.c                         | 14 ++++++++++++++\n t/t5551-http-fetch-smart.sh    | 29 +++++++++++++++++++++++++++++\n 3 files changed, 52 insertions(+)\n\ndiff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc\nindex 792a71b413..6bc2e3823d 100644\n--- a/Documentation/config/http.adoc\n+++ b/Documentation/config/http.adoc\n@@ -196,6 +196,15 @@ http.sslVerify::\n \tover HTTPS. Defaults to true. Can be overridden by the\n \t`GIT_SSL_NO_VERIFY` environment variable.\n \n+http.sslVerifyStatus::\n+\tWhether to check the revocation status of the server\n+\tcertificate using the stapled OCSP response supplied during\n+\tthe TLS handshake (\"OCSP stapling\"). Defaults to false.\n++\n+This is fail-closed: if the server staples no response, verification\n+fails. Set it per remote, e.g.\n+`http.https://example.com/.sslVerifyStatus`, rather than globally.\n+\n http.sslCert::\n \tFile containing the SSL certificate when fetching or pushing\n \tover HTTPS. Can be overridden by the `GIT_SSL_CERT` environment\ndiff --git a/http.c b/http.c\nindex caccf2108e..4a4dd40fe2 100644\n--- a/http.c\n+++ b/http.c\n@@ -44,6 +44,7 @@ static CURL *curl_default;\n char curl_errorstr[CURL_ERROR_SIZE];\n \n static int curl_ssl_verify = -1;\n+static int curl_ssl_verify_status;\n static int curl_ssl_try;\n static char *curl_http_version;\n static char *ssl_cert;\n@@ -400,6 +401,10 @@ static int http_options(const char *var, const char *value,\n \t\tcurl_ssl_verify = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(\"http.sslverifystatus\", var)) {\n+\t\tcurl_ssl_verify_status = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(\"http.sslcipherlist\", var))\n \t\treturn git_config_string(&ssl_cipherlist, var, value);\n \tif (!strcmp(\"http.sslversion\", var))\n@@ -1133,6 +1138,15 @@ static CURL *get_curl_handle(void)\n \t\tcurl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L);\n \t}\n \n+\tif (curl_ssl_verify_status) {\n+\t\tCURLcode ret = curl_easy_setopt(result,\n+\t\t\t\t\t\tCURLOPT_SSL_VERIFYSTATUS, 1L);\n+\t\tif (ret != CURLE_OK)\n+\t\t\tdie(_(\"http.sslVerifyStatus is set, but could not \"\n+\t\t\t      \"enable OCSP status verification: %s\"),\n+\t\t\t    curl_easy_strerror(ret));\n+\t}\n+\n     if (curl_http_version) {\n \t\tlong opt;\n \t\tif (!get_curl_http_version_opt(curl_http_version, &opt)) {\ndiff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh\nindex 805bec025c..75ab07f031 100755\n--- a/t/t5551-http-fetch-smart.sh\n+++ b/t/t5551-http-fetch-smart.sh\n@@ -680,6 +680,35 @@ test_expect_success 'passing hostname resolution information works' '\n \tgit -c \"http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1\" ls-remote \"$BOGUS_HTTPD_URL/smart/repo.git\" >/dev/null\n '\n \n+test_lazy_prereq SSL_VERIFYSTATUS '\n+\ttest \"$HTTPD_PROTO\" = \"https\" &&\n+\ttest_might_fail git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" 2>err &&\n+\t! grep \"http.sslVerifyStatus is set\" err\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=true fails without a staple' '\n+\ttest_must_fail git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=false is a no-op' '\n+\tgit -c http.sslVerifyStatus=false \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus applies to a matching URL' '\n+\ttest_must_fail git -c \"http.$HTTPD_URL/.sslVerifyStatus=true\" \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus is not applied to other URLs' '\n+\tgit -c \"http.https://example.com/.sslVerifyStatus=true\" \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n # here user%40host is the URL-encoded version of user@host,\n # which is our intentionally-odd username to catch parsing errors\n url_user=$HTTPD_URL_USER/auth/smart/repo.git\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"550776","messageId":"xmqqo6ezw5l1.fsf@gitster.g","threadId":"66156","inReplyTo":"20260818193710.56955-1-ggordon@gitlab.com","subject":"Re: [PATCH v5] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-18T20:12:42Z","receivedAt":"2026-08-18T20:12:45Z","isPatch":true,"body":"graysongordon-gl <graysongordon1@gmail.com> writes:\n\n> +http.sslVerifyStatus::\n> +\tWhether to check the revocation status of the server\n> +\tcertificate using the stapled OCSP response supplied during\n> +\tthe TLS handshake (\"OCSP stapling\"). Defaults to false.\n> ++\n> +This is fail-closed: if the server staples no response, verification\n> +fails. Set it per remote, e.g.\n> +`http.https://example.com/.sslVerifyStatus`, rather than globally.\n\nI do not see us describe a knob or setting that can stop the\noperation depending on some condition as \"fail-closed\".  Can we\nrephrase this for regular human beings?  Perhaps\n\n\tWhether to refuse connecting to the server when its\n\tcertificate has been revoked.  Default to false, allowing\n\tconnection even when its certificate is not known to be\n\tstill valid.\n\nor something like that might be a good starting point.  After all,\nthe \"check revocation and/or validity\" is *not* the primary\nobjective from the end-user's point of view.  Ensuring that they do\nnot talk to suspicious servers is.\n\nThanks.\n"},{"id":"550778","messageId":"CALgUfNg1yryPygp_UVp9cGFfiUe7_6Uqx3ExBt=10Qh+PKG2QQ@mail.gmail.com","threadId":"66156","inReplyTo":"xmqqo6ezw5l1.fsf@gitster.g","subject":"Re: [PATCH v5] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Grayson Gordon","fromEmail":"graysongordon1@gmail.com","sentAt":"2026-08-18T21:22:55Z","receivedAt":"2026-08-18T21:23:09Z","isPatch":true,"body":"Junio,\n\nBy \"fail-closed\" I was specifically referring to the case where no\nOCSP stapled response was provided. Failing open in this\ncontext would mean that, despite verifystatus being set, a\nresponse with no stapled response is ALLOWED.\n\nMaybe I'm just a very irregular human being lol.\n\nI'll update the adoc to be similar to what you provided.\nI refer to the edge cases/different behaviors that we talked\nabout earlier in this thread with the older curl and gnutls\nversions in the commit message and keep the adoc\nas simple as possible.\n\n- Grayson\n\n\nOn Tue, Aug 18, 2026 at 4:12 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> graysongordon-gl <graysongordon1@gmail.com> writes:\n>\n> > +http.sslVerifyStatus::\n> > +     Whether to check the revocation status of the server\n> > +     certificate using the stapled OCSP response supplied during\n> > +     the TLS handshake (\"OCSP stapling\"). Defaults to false.\n> > ++\n> > +This is fail-closed: if the server staples no response, verification\n> > +fails. Set it per remote, e.g.\n> > +`http.https://example.com/.sslVerifyStatus`, rather than globally.\n>\n> I do not see us describe a knob or setting that can stop the\n> operation depending on some condition as \"fail-closed\".  Can we\n> rephrase this for regular human beings?  Perhaps\n>\n>         Whether to refuse connecting to the server when its\n>         certificate has been revoked.  Default to false, allowing\n>         connection even when its certificate is not known to be\n>         still valid.\n>\n> or something like that might be a good starting point.  After all,\n> the \"check revocation and/or validity\" is *not* the primary\n> objective from the end-user's point of view.  Ensuring that they do\n> not talk to suspicious servers is.\n>\n> Thanks.\n"},{"id":"550779","messageId":"20260818214858.65122-1-ggordon@gitlab.com","threadId":"66156","inReplyTo":"xmqqmruqt36l.fsf@gitster.g","subject":"[PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"graysongordon-gl","fromEmail":"graysongordon1@gmail.com","sentAt":"2026-08-18T21:48:58Z","receivedAt":"2026-08-18T21:49:02Z","isPatch":true,"body":"From: Grayson Gordon <graysongordon1@gmail.com>\n\ngit never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the\nOCSP \"Certificate Status Request\" extension and any stapled response a\nserver sends is ignored, including responses that explicitly state the\ncertificate has been revoked.\n\nAdd an http.sslVerifyStatus boolean that maps to\nCURLOPT_SSL_VERIFYSTATUS.\nhttp_options() is already the collect_fn for a urlmatch config, so the\nper-URL form works with no changes:\n\n    git config http.https://example.com/.sslVerifyStatus true\n\nDefaults to false/\"off\". This is due to the nature of the OCSP protocol.\nIf enabled, git would expect to receive OCSP stapled responses. If the\nstapled responses were not present, the connection would be blocked as\nthe status of the server's certificate could not be verified. This would\nbreak connections to legitimate services that don't use OCSP as their\ncertificate revocation mechanism.\n\nIf the backend can't check the staple, curl_easy_setopt() returns\nCURLE_NOT_BUILT_IN. Error message includes curl_easy_strerror() with\nthe option name to enable users to more easily identify a libcurl\nbuilt without status verification.\n\nCURLOPT_SSL_VERIFYSTATUS has existed since libcurl 7.41.0, below our\n7.61.0 floor, so no version guard is needed.\n\nTests are in t5551.\n\nAdditional note - I put this in http.adoc:\n\"Defaults to false, which\nallows connections to remotes without validating whether or not\nthe certificate has been revoked by the certificate authority.\"\n\nTechnically, there are cases with older combinations of GnuTLS\nand curl where the revocation logic actually WILL NOT allow\nsuch connections. Search \"OCSP\" in the lore for full details.\n\nSigned-off-by: Grayson Gordon <graysongordon1@gmail.com>\n---\n Documentation/config/http.adoc | 14 ++++++++++++++\n http.c                         | 14 ++++++++++++++\n t/t5551-http-fetch-smart.sh    | 29 +++++++++++++++++++++++++++++\n 3 files changed, 57 insertions(+)\n\ndiff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc\nindex 792a71b413..b54f627969 100644\n--- a/Documentation/config/http.adoc\n+++ b/Documentation/config/http.adoc\n@@ -196,6 +196,20 @@ http.sslVerify::\n \tover HTTPS. Defaults to true. Can be overridden by the\n \t`GIT_SSL_NO_VERIFY` environment variable.\n \n+http.sslVerifyStatus::\n+\tWhether to check the revocation status of the server\n+\tcertificate using the stapled OCSP response supplied during\n+\tthe TLS handshake (\"OCSP stapling\"). Defaults to false, which\n+\tallows connections to servers without validating if the\n+\tcertificate has been revoked by the certificate authority.\n+\tEnabling this option will prevent connections to servers that\n+\thave a certificate status other than \"good\" per RFC 6960.\n+\tConnections to servers that do not return a stapled response\n+\twill also be refused.\n++\n+Set it per remote, e.g.\n+`http.https://example.com/.sslVerifyStatus`, rather than globally.\n+\n http.sslCert::\n \tFile containing the SSL certificate when fetching or pushing\n \tover HTTPS. Can be overridden by the `GIT_SSL_CERT` environment\ndiff --git a/http.c b/http.c\nindex caccf2108e..4a4dd40fe2 100644\n--- a/http.c\n+++ b/http.c\n@@ -44,6 +44,7 @@ static CURL *curl_default;\n char curl_errorstr[CURL_ERROR_SIZE];\n \n static int curl_ssl_verify = -1;\n+static int curl_ssl_verify_status;\n static int curl_ssl_try;\n static char *curl_http_version;\n static char *ssl_cert;\n@@ -400,6 +401,10 @@ static int http_options(const char *var, const char *value,\n \t\tcurl_ssl_verify = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(\"http.sslverifystatus\", var)) {\n+\t\tcurl_ssl_verify_status = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(\"http.sslcipherlist\", var))\n \t\treturn git_config_string(&ssl_cipherlist, var, value);\n \tif (!strcmp(\"http.sslversion\", var))\n@@ -1133,6 +1138,15 @@ static CURL *get_curl_handle(void)\n \t\tcurl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L);\n \t}\n \n+\tif (curl_ssl_verify_status) {\n+\t\tCURLcode ret = curl_easy_setopt(result,\n+\t\t\t\t\t\tCURLOPT_SSL_VERIFYSTATUS, 1L);\n+\t\tif (ret != CURLE_OK)\n+\t\t\tdie(_(\"http.sslVerifyStatus is set, but could not \"\n+\t\t\t      \"enable OCSP status verification: %s\"),\n+\t\t\t    curl_easy_strerror(ret));\n+\t}\n+\n     if (curl_http_version) {\n \t\tlong opt;\n \t\tif (!get_curl_http_version_opt(curl_http_version, &opt)) {\ndiff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh\nindex 805bec025c..75ab07f031 100755\n--- a/t/t5551-http-fetch-smart.sh\n+++ b/t/t5551-http-fetch-smart.sh\n@@ -680,6 +680,35 @@ test_expect_success 'passing hostname resolution information works' '\n \tgit -c \"http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1\" ls-remote \"$BOGUS_HTTPD_URL/smart/repo.git\" >/dev/null\n '\n \n+test_lazy_prereq SSL_VERIFYSTATUS '\n+\ttest \"$HTTPD_PROTO\" = \"https\" &&\n+\ttest_might_fail git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" 2>err &&\n+\t! grep \"http.sslVerifyStatus is set\" err\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=true fails without a staple' '\n+\ttest_must_fail git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=false is a no-op' '\n+\tgit -c http.sslVerifyStatus=false \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus applies to a matching URL' '\n+\ttest_must_fail git -c \"http.$HTTPD_URL/.sslVerifyStatus=true\" \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus is not applied to other URLs' '\n+\tgit -c \"http.https://example.com/.sslVerifyStatus=true\" \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n # here user%40host is the URL-encoded version of user@host,\n # which is our intentionally-odd username to catch parsing errors\n url_user=$HTTPD_URL_USER/auth/smart/repo.git\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"550790","messageId":"aoVmA8jERvRXMsBi@pks.im","threadId":"66156","inReplyTo":"CALgUfNhxLEeTK5xH9Dw9ZPBG+oPq9Fw1qDgt=wbXqrnuEetJyw@mail.gmail.com","subject":"Re: [PATCH v4] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-19T08:14:59Z","receivedAt":"2026-08-19T08:15:16Z","isPatch":true,"body":"Hi,\n\nOn Tue, Aug 18, 2026 at 10:51:08AM -0400, Grayson Gordon wrote:\n> Patrick,\n\none hint: we prefer to not top-post on this mailing list and instead\nanswer inline.\n\n[snip]\n> ON GNUTLS VS OPENSSL DIFFERENCES\n> \n> I appreciate you including the extra context around GnuTLS 3.8's\n> GNUTLS_NO_STATUS_REQUEST flag, and curl 8.10 setting it when\n> \"verifystatus\" is false.\n> \n> Fair enough, said another way, if either of these are true:\n> Condition 1: curl is being built with GnuTLS version < 3.8. (There's\n> no NO_STATUS_REQUEST flag to set.)\n> OR\n> Condition 2:  curl version < 8.10. (Curl's not using the flag.)\n> \n> You'll see a discrepancy in cert verification behavior between the\n> versions of git built with GnuTLS vs OpenSSL.\n> \n> While I appreciate the increased precision here, the purpose of my\n> contribution was to expose the functionality that enables git users to\n> set it if they so choose. As it stands today, this option is not\n> presented. So while the discrepancy is what incited me to look deeper,\n> it isn't the central reason I'm here.\n\nThat's fair, and I think adding support for OCSP is useful indeed. I\njust want us to be more accurate in both the commit message and in the\ndocs, as the way is currently written is only partially true and thus\nmisleading both for developers and for readers of git-config(1).\n\n[snip]\n> ON COMPREHENSIVE TESTING\n> \n> I'll leave this at you and Junio's discretion. I worked with him\n> earlier in this thread to avoid introducing another test file and keep\n> the testing succinct.\n> Right now these tests are just limited to parsing the config and\n> applying it to the user-provided remote.\n> We COULD do the full suite of tests that cover the full range of\n> cases/behaviors:\n> - The flag is set AND\n>     - no staple sent (should fail)\n>     - good staple (should pass)\n>     - bad staple (should fail)\n> etc.\n> \n> We're going to need a lot of infrax for that though:\n> - test CA.\n> - test server certificate issued by that CA.\n> - OCSP responder which knows the certificate's status.\n> - a way for the TLS server to obtain and staple that response.\n> - a way to control the response so you can test good vs revoked/invalid.\n> \n> I set all of that stuff up in my own experiment repo, emulating this\n> with nginx in docker...\n\nYeah, it's certainly non-trivial to set all of this up as there's a\nbunch of pieces to it. Something like the below patch would do it, but\nI'm not a 100% sure whether it's really worth it given the complexity.\n\nPatrick\n\ndiff --git a/t/lib-httpd.sh b/t/lib-httpd.sh\nindex a216e5376f..0af506c950 100644\n--- a/t/lib-httpd.sh\n+++ b/t/lib-httpd.sh\n@@ -25,6 +25,9 @@\n #    LIB_HTTPD_DAV               enable DAV\n #    LIB_HTTPD_SVN               enable SVN at given location (e.g. \"svn\")\n #    LIB_HTTPD_SSL               enable SSL\n+#    LIB_HTTPD_OCSP              enable OCSP stapling (implies SSL); requires\n+#                                the openssl(1) command and needs the caller\n+#                                to run start_ocsp_responder after start_httpd\n #    LIB_HTTPD_PROXY             enable proxy\n #\n # Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at>\n@@ -171,15 +174,26 @@ prepare_httpd() {\n \n \tln -s \"$LIB_HTTPD_MODULE_PATH\" \"$HTTPD_ROOT_PATH/modules\"\n \n+\tif test -n \"$LIB_HTTPD_OCSP\"\n+\tthen\n+\t\tLIB_HTTPD_SSL=t\n+\tfi\n+\n \tif test -n \"$LIB_HTTPD_SSL\"\n \tthen\n \t\tHTTPD_PROTO=https\n \n-\t\tRANDFILE_PATH=\"$HTTPD_ROOT_PATH\"/.rnd openssl req \\\n-\t\t\t-config \"$TEST_PATH/ssl.cnf\" \\\n-\t\t\t-new -x509 -nodes \\\n-\t\t\t-out \"$HTTPD_ROOT_PATH/httpd.pem\" \\\n-\t\t\t-keyout \"$HTTPD_ROOT_PATH/httpd.pem\"\n+\t\tif test -n \"$LIB_HTTPD_OCSP\"\n+\t\tthen\n+\t\t\tprepare_ocsp_stapling\n+\t\t\tHTTPD_PARA=\"$HTTPD_PARA -DOCSP\"\n+\t\telse\n+\t\t\tRANDFILE_PATH=\"$HTTPD_ROOT_PATH\"/.rnd openssl req \\\n+\t\t\t\t-config \"$TEST_PATH/ssl.cnf\" \\\n+\t\t\t\t-new -x509 -nodes \\\n+\t\t\t\t-out \"$HTTPD_ROOT_PATH/httpd.pem\" \\\n+\t\t\t\t-keyout \"$HTTPD_ROOT_PATH/httpd.pem\"\n+\t\tfi\n \t\tGIT_SSL_NO_VERIFY=t\n \t\texport GIT_SSL_NO_VERIFY\n \t\tHTTPD_PARA=\"$HTTPD_PARA -DSSL\"\n@@ -250,6 +264,106 @@ stop_httpd() {\n \t\t-f \"$TEST_PATH/apache.conf\" $HTTPD_PARA -k stop\n }\n \n+restart_httpd () {\n+\thttpd_pid=$(cat \"$HTTPD_ROOT_PATH/httpd.pid\") &&\n+\tstop_httpd &&\n+\twhile kill -0 \"$httpd_pid\" 2>/dev/null\n+\tdo\n+\t\tsleep 1\n+\tdone &&\n+\t\"$LIB_HTTPD_PATH\" -d \"$HTTPD_ROOT_PATH\" \\\n+\t\t-f \"$TEST_PATH/apache.conf\" $HTTPD_PARA \\\n+\t\t-c \"Listen 127.0.0.1:$LIB_HTTPD_PORT\" -k start\n+}\n+\n+# Set up a certificate authority whose certificate httpd.pem is signed\n+# with, such that \"openssl ocsp\" can vouch for (or revoke) it. Used\n+# instead of the self-signed certificate when LIB_HTTPD_OCSP is set.\n+prepare_ocsp_stapling () {\n+\tLIB_HTTPD_OCSP_PORT=$((LIB_HTTPD_PORT + 10000))\n+\n+\t# Referenced by ocsp-ca.cnf.\n+\tOCSP_CA_DIR=\"$HTTPD_ROOT_PATH/ocsp-ca\"\n+\tOCSP_URI=\"http://127.0.0.1:$LIB_HTTPD_OCSP_PORT\"\n+\texport OCSP_CA_DIR OCSP_URI\n+\n+\tmkdir -p \"$OCSP_CA_DIR/newcerts\" &&\n+\t>\"$OCSP_CA_DIR/index.txt\" &&\n+\techo 1000 >\"$OCSP_CA_DIR/serial\" &&\n+\n+\topenssl req -config \"$TEST_PATH/ocsp-ca.cnf\" \\\n+\t\t-new -x509 -nodes -days 2 \\\n+\t\t-subj \"/CN=git-test-ca\" -extensions v3_ca \\\n+\t\t-keyout \"$HTTPD_ROOT_PATH/ca.key\" \\\n+\t\t-out \"$HTTPD_ROOT_PATH/ca.pem\" &&\n+\topenssl req -config \"$TEST_PATH/ocsp-ca.cnf\" \\\n+\t\t-new -nodes \\\n+\t\t-subj \"/CN=127.0.0.1\" \\\n+\t\t-keyout \"$HTTPD_ROOT_PATH/httpd.key\" \\\n+\t\t-out \"$HTTPD_ROOT_PATH/httpd.csr\" &&\n+\topenssl ca -config \"$TEST_PATH/ocsp-ca.cnf\" -batch \\\n+\t\t-cert \"$HTTPD_ROOT_PATH/ca.pem\" \\\n+\t\t-keyfile \"$HTTPD_ROOT_PATH/ca.key\" \\\n+\t\t-in \"$HTTPD_ROOT_PATH/httpd.csr\" \\\n+\t\t-out \"$HTTPD_ROOT_PATH/httpd.crt\" &&\n+\tcat \"$HTTPD_ROOT_PATH/httpd.key\" \"$HTTPD_ROOT_PATH/httpd.crt\" \\\n+\t\t>\"$HTTPD_ROOT_PATH/httpd.pem\"\n+}\n+\n+run_ocsp_responder () {\n+\topenssl ocsp -port \"$LIB_HTTPD_OCSP_PORT\" \\\n+\t\t-index \"$OCSP_CA_DIR/index.txt\" \\\n+\t\t-CA \"$HTTPD_ROOT_PATH/ca.pem\" \\\n+\t\t-rsigner \"$HTTPD_ROOT_PATH/ca.pem\" \\\n+\t\t-rkey \"$HTTPD_ROOT_PATH/ca.key\" \\\n+\t\t-nmin 60 >>\"$HTTPD_ROOT_PATH/ocsp.log\" 2>&1 &\n+\techo $! >\"$HTTPD_ROOT_PATH/ocsp.pid\"\n+\n+\tfor i in $(test_seq 1 10)\n+\tdo\n+\t\tif openssl ocsp -no_nonce \\\n+\t\t\t-CAfile \"$HTTPD_ROOT_PATH/ca.pem\" \\\n+\t\t\t-issuer \"$HTTPD_ROOT_PATH/ca.pem\" \\\n+\t\t\t-cert \"$HTTPD_ROOT_PATH/httpd.crt\" \\\n+\t\t\t-url \"$OCSP_URI\" >/dev/null 2>&1\n+\t\tthen\n+\t\t\treturn 0\n+\t\tfi\n+\t\tsleep 1\n+\tdone\n+\treturn 1\n+}\n+\n+start_ocsp_responder () {\n+\ttest_atexit stop_ocsp_responder\n+\n+\tif ! run_ocsp_responder\n+\tthen\n+\t\tcat \"$HTTPD_ROOT_PATH\"/ocsp.log >&4 2>/dev/null\n+\t\ttest_skip_or_die GIT_TEST_HTTPD \"OCSP responder setup failed\"\n+\tfi\n+}\n+\n+stop_ocsp_responder () {\n+\tif test -f \"$HTTPD_ROOT_PATH/ocsp.pid\"\n+\tthen\n+\t\tkill \"$(cat \"$HTTPD_ROOT_PATH/ocsp.pid\")\" 2>/dev/null\n+\t\trm -f \"$HTTPD_ROOT_PATH/ocsp.pid\"\n+\tfi\n+}\n+\n+# Revoke the certificate used by httpd and make both the OCSP responder\n+# and httpd aware of it.\n+revoke_httpd_cert () {\n+\topenssl ca -config \"$TEST_PATH/ocsp-ca.cnf\" \\\n+\t\t-cert \"$HTTPD_ROOT_PATH/ca.pem\" \\\n+\t\t-keyfile \"$HTTPD_ROOT_PATH/ca.key\" \\\n+\t\t-revoke \"$HTTPD_ROOT_PATH/httpd.crt\" &&\n+\tstop_ocsp_responder &&\n+\trun_ocsp_responder &&\n+\trestart_httpd\n+}\n+\n test_http_push_nonff () {\n \tREMOTE_REPO=$1\n \tLOCAL_REPO=$2\ndiff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf\nindex 4149fc1078..f3287566e0 100644\n--- a/t/lib-httpd/apache.conf\n+++ b/t/lib-httpd/apache.conf\n@@ -242,6 +242,19 @@ SSLSessionCache none\n SSLEngine On\n </IfDefine>\n \n+<IfDefine OCSP>\n+<IfModule !mod_socache_shmcb.c>\n+\tLoadModule socache_shmcb_module modules/mod_socache_shmcb.so\n+</IfModule>\n+\n+SSLCertificateChainFile ca.pem\n+SSLUseStapling On\n+SSLStaplingCache shmcb:ssl_stapling(65536)\n+# Also staple responses whose certificate status is not \"good\", so\n+# that clients get to see e.g. \"revoked\" responses.\n+SSLStaplingReturnResponderErrors On\n+</IfDefine>\n+\n <Location /auth/>\n \tAuthType Basic\n \tAuthName \"git-auth\"\ndiff --git a/t/lib-httpd/ocsp-ca.cnf b/t/lib-httpd/ocsp-ca.cnf\nnew file mode 100644\nindex 0000000000..eec4b5b932\n--- /dev/null\n+++ b/t/lib-httpd/ocsp-ca.cnf\n@@ -0,0 +1,35 @@\n+[ ca ]\n+default_ca              = CA_default\n+\n+[ CA_default ]\n+dir                     = $ENV::OCSP_CA_DIR\n+database                = $dir/index.txt\n+new_certs_dir           = $dir/newcerts\n+serial                  = $dir/serial\n+default_md              = sha256\n+default_days            = 2\n+policy                  = policy_anything\n+email_in_dn             = no\n+unique_subject          = no\n+x509_extensions         = server_cert\n+\n+[ policy_anything ]\n+commonName              = supplied\n+\n+[ req ]\n+default_bits            = 2048\n+distinguished_name      = req_distinguished_name\n+prompt                  = no\n+\n+[ req_distinguished_name ]\n+# The subject is always given on the command line via -subj.\n+\n+[ v3_ca ]\n+basicConstraints        = critical, CA:TRUE\n+keyUsage                = critical, digitalSignature, keyCertSign, cRLSign\n+subjectKeyIdentifier    = hash\n+\n+[ server_cert ]\n+basicConstraints        = CA:FALSE\n+subjectAltName          = IP:127.0.0.1\n+authorityInfoAccess     = OCSP;URI:$ENV::OCSP_URI\ndiff --git a/t/meson.build b/t/meson.build\nindex 2133c840da..1413baed80 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -727,6 +727,7 @@ integration_tests = [\n   't5582-fetch-negative-refspec.sh',\n   't5583-push-branches.sh',\n   't5584-http-429-retry.sh',\n+  't5585-http-ssl-ocsp.sh',\n   't5600-clone-fail-cleanup.sh',\n   't5601-clone.sh',\n   't5602-clone-remote-exec.sh',\ndiff --git a/t/t5585-http-ssl-ocsp.sh b/t/t5585-http-ssl-ocsp.sh\nnew file mode 100755\nindex 0000000000..c279f0c9ca\n--- /dev/null\n+++ b/t/t5585-http-ssl-ocsp.sh\n@@ -0,0 +1,55 @@\n+#!/bin/sh\n+\n+test_description='test verification of stapled OCSP responses via http.sslVerifyStatus'\n+\n+. ./test-lib.sh\n+\n+LIB_HTTPD_OCSP=1\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+\n+start_httpd\n+start_ocsp_responder\n+\n+test_expect_success 'setup repository' '\n+\ttest_commit one &&\n+\tgit init --bare \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\" &&\n+\tgit push \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\" HEAD:refs/heads/main\n+'\n+\n+with_ssl_verification () {\n+\t(\n+\t\tsane_unset GIT_SSL_NO_VERIFY &&\n+\t\tGIT_SSL_CAINFO=\"$HTTPD_ROOT_PATH/ca.pem\" \"$@\"\n+\t)\n+}\n+\n+test_lazy_prereq SSL_VERIFYSTATUS '\n+\ttest_might_fail git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" 2>err &&\n+\t! grep \"http.sslVerifyStatus is set\" err\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'certificate verification works against test CA' '\n+\twith_ssl_verification git ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled \"good\" OCSP response' '\n+\twith_ssl_verification git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'revoked certificate is rejected' '\n+\trevoke_httpd_cert &&\n+\twith_ssl_verification test_must_fail git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" 2>err &&\n+\ttest_grep -i -e \"ocsp\" -e \"revocation\" -e \"revoked\" -e \"certificate status\" err\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'revoked certificate is accepted without http.sslVerifyStatus' '\n+\twith_ssl_verification git ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_done\n"},{"id":"551320","messageId":"xmqqpkz4czhu.fsf@gitster.g","threadId":"66156","inReplyTo":"20260818214858.65122-1-ggordon@gitlab.com","subject":"Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-26T22:01:49Z","receivedAt":"2026-08-26T22:01:52Z","isPatch":true,"body":"graysongordon-gl <graysongordon1@gmail.com> writes:\n\n> From: Grayson Gordon <graysongordon1@gmail.com>\n>\n> git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the\n> OCSP \"Certificate Status Request\" extension and any stapled response a\n> server sends is ignored, including responses that explicitly state the\n> certificate has been revoked.\n>\n> Add an http.sslVerifyStatus boolean that maps to\n> CURLOPT_SSL_VERIFYSTATUS.\n> http_options() is already the collect_fn for a urlmatch config, so the\n> per-URL form works with no changes:\n>\n>     git config http.https://example.com/.sslVerifyStatus true\n>\n> Defaults to false/\"off\". This is due to the nature of the OCSP protocol.\n> If enabled, git would expect to receive OCSP stapled responses. If the\n> stapled responses were not present, the connection would be blocked as\n> the status of the server's certificate could not be verified. This would\n> break connections to legitimate services that don't use OCSP as their\n> certificate revocation mechanism.\n>\n> If the backend can't check the staple, curl_easy_setopt() returns\n> CURLE_NOT_BUILT_IN. Error message includes curl_easy_strerror() with\n> the option name to enable users to more easily identify a libcurl\n> built without status verification.\n>\n> CURLOPT_SSL_VERIFYSTATUS has existed since libcurl 7.41.0, below our\n> 7.61.0 floor, so no version guard is needed.\n>\n> Tests are in t5551.\n>\n> Additional note - I put this in http.adoc:\n> \"Defaults to false, which\n> allows connections to remotes without validating whether or not\n> the certificate has been revoked by the certificate authority.\"\n>\n> Technically, there are cases with older combinations of GnuTLS\n> and curl where the revocation logic actually WILL NOT allow\n> such connections. Search \"OCSP\" in the lore for full details.\n>\n> Signed-off-by: Grayson Gordon <graysongordon1@gmail.com>\n> ---\n>  Documentation/config/http.adoc | 14 ++++++++++++++\n>  http.c                         | 14 ++++++++++++++\n>  t/t5551-http-fetch-smart.sh    | 29 +++++++++++++++++++++++++++++\n>  3 files changed, 57 insertions(+)\n\nAre folks happy with this iteration?  I think we have already\nreached the point of diminishing returns before the thread went\ndark.\n\nThanks.\n"},{"id":"551416","messageId":"CALgUfNjd_y-e-zTKJ31o8_bQuRw8wFWe=sdsf2KJ7LOmmO21aQ@mail.gmail.com","threadId":"66156","inReplyTo":"xmqqpkz4czhu.fsf@gitster.g","subject":"Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Grayson Gordon","fromEmail":"graysongordon1@gmail.com","sentAt":"2026-08-28T13:51:45Z","receivedAt":"2026-08-28T13:51:58Z","isPatch":true,"body":"Junio,\n\nYes, I was hoping for clarity on how thorough we wanted the testing to\nbe. Patrick added a lot of great stuff that I’m happy to use if that’s\nyour preference, but we also talked about wanting to keep the tests\nsuccinct. Please let me know what you feel is most appropriate.\n\n- Grayson\n\nOn Wed, Aug 26, 2026 at 6:01 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> graysongordon-gl <graysongordon1@gmail.com> writes:\n>\n> > From: Grayson Gordon <graysongordon1@gmail.com>\n> >\n> > git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the\n> > OCSP \"Certificate Status Request\" extension and any stapled response a\n> > server sends is ignored, including responses that explicitly state the\n> > certificate has been revoked.\n> >\n> > Add an http.sslVerifyStatus boolean that maps to\n> > CURLOPT_SSL_VERIFYSTATUS.\n> > http_options() is already the collect_fn for a urlmatch config, so the\n> > per-URL form works with no changes:\n> >\n> >     git config http.https://example.com/.sslVerifyStatus true\n> >\n> > Defaults to false/\"off\". This is due to the nature of the OCSP protocol.\n> > If enabled, git would expect to receive OCSP stapled responses. If the\n> > stapled responses were not present, the connection would be blocked as\n> > the status of the server's certificate could not be verified. This would\n> > break connections to legitimate services that don't use OCSP as their\n> > certificate revocation mechanism.\n> >\n> > If the backend can't check the staple, curl_easy_setopt() returns\n> > CURLE_NOT_BUILT_IN. Error message includes curl_easy_strerror() with\n> > the option name to enable users to more easily identify a libcurl\n> > built without status verification.\n> >\n> > CURLOPT_SSL_VERIFYSTATUS has existed since libcurl 7.41.0, below our\n> > 7.61.0 floor, so no version guard is needed.\n> >\n> > Tests are in t5551.\n> >\n> > Additional note - I put this in http.adoc:\n> > \"Defaults to false, which\n> > allows connections to remotes without validating whether or not\n> > the certificate has been revoked by the certificate authority.\"\n> >\n> > Technically, there are cases with older combinations of GnuTLS\n> > and curl where the revocation logic actually WILL NOT allow\n> > such connections. Search \"OCSP\" in the lore for full details.\n> >\n> > Signed-off-by: Grayson Gordon <graysongordon1@gmail.com>\n> > ---\n> >  Documentation/config/http.adoc | 14 ++++++++++++++\n> >  http.c                         | 14 ++++++++++++++\n> >  t/t5551-http-fetch-smart.sh    | 29 +++++++++++++++++++++++++++++\n> >  3 files changed, 57 insertions(+)\n>\n> Are folks happy with this iteration?  I think we have already\n> reached the point of diminishing returns before the thread went\n> dark.\n>\n> Thanks.\n"},{"id":"551428","messageId":"xmqqld9q40ww.fsf@gitster.g","threadId":"66156","inReplyTo":"CALgUfNjd_y-e-zTKJ31o8_bQuRw8wFWe=sdsf2KJ7LOmmO21aQ@mail.gmail.com","subject":"Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-28T17:20:31Z","receivedAt":"2026-08-28T17:20:33Z","isPatch":true,"body":"Grayson Gordon <graysongordon1@gmail.com> writes:\n\n> Junio,\n>\n> Yes, I was hoping for clarity on how thorough we wanted the testing to\n> be. Patrick added a lot of great stuff that I’m happy to use if that’s\n> your preference, but we also talked about wanting to keep the tests\n> succinct. Please let me know what you feel is most appropriate.\n\nIf you can keep them succinct but still test the essential bits,\nthat would be great, but I am not sure if that is a great question\nto ask me ;-)  Patrick?  You said \"not 100% sure given the complexity\",\nbut which parts make you feel iffy?  They do look involved but seem\nto cover the situations we do care about, except we seem not to test\nwhen the server does not explicitly say \"this is still good\", or am\nI not reading the tests correctly?\n\nThanks.\n"},{"id":"551515","messageId":"apUlqvXgChMeCUkp@pks.im","threadId":"66156","inReplyTo":"xmqqld9q40ww.fsf@gitster.g","subject":"Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T06:56:42Z","receivedAt":"2026-08-31T06:56:51Z","isPatch":true,"body":"On Fri, Aug 28, 2026 at 10:20:31AM -0700, Junio C Hamano wrote:\n> Grayson Gordon <graysongordon1@gmail.com> writes:\n> \n> > Junio,\n> >\n> > Yes, I was hoping for clarity on how thorough we wanted the testing to\n> > be. Patrick added a lot of great stuff that I’m happy to use if that’s\n> > your preference, but we also talked about wanting to keep the tests\n> > succinct. Please let me know what you feel is most appropriate.\n> \n> If you can keep them succinct but still test the essential bits,\n> that would be great, but I am not sure if that is a great question\n> to ask me ;-)  Patrick?  You said \"not 100% sure given the complexity\",\n> but which parts make you feel iffy? \n\nSetting up OCSP is quite a pain, and that is what made me feel iffy.\nThat being said, given that this is a security-focussed feature I feel\nlike we should probably bite the bullet and verify that we indeed know\nto reject servers that respond with invalid stapled responses.\n\nAnd given that this whole setup is now getting more complex I feel like\nit's worth it to also allocate a new test number for it.\n\n> They do look involved but seem to cover the situations we do care\n> about, except we seem not to test when the server does not explicitly\n> say \"this is still good\", or am I not reading the tests correctly?\n\nIsn't the following test covering that scenario? Or am I misreading?\n\n    test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled \"good\" OCSP response'\n           with_ssl_verification git -c http.sslVerifyStatus=true \\\n                   ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n           test_line_count -gt 0 actual\n    '\n\nThanks!\n\nPatrick\n"},{"id":"551556","messageId":"xmqqik4qz86h.fsf@gitster.g","threadId":"66156","inReplyTo":"apUlqvXgChMeCUkp@pks.im","subject":"Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-31T14:16:54Z","receivedAt":"2026-08-31T14:16:57Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> They do look involved but seem to cover the situations we do care\n>> about, except we seem not to test when the server does not explicitly\n>> say \"this is still good\", or am I not reading the tests correctly?\n>\n> Isn't the following test covering that scenario? Or am I misreading?\n>\n>     test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled \"good\" OCSP response'\n>            with_ssl_verification git -c http.sslVerifyStatus=true \\\n>                    ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n>            test_line_count -gt 0 actual\n>     '\n\nProbably I misstated.  What I meant was a reaction to \"fail close\"\nfloated earlier.  A server does not explicitly give stapled good,\nand the client says \"this is not known-good\" and not talking to it.\nI.e. 'fetch fails without stapled \"good\"'\n"},{"id":"551558","messageId":"apWOuGbOErZt9jo8@pks.im","threadId":"66156","inReplyTo":"xmqqik4qz86h.fsf@gitster.g","subject":"Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T14:24:56Z","receivedAt":"2026-08-31T14:25:04Z","isPatch":true,"body":"On Mon, Aug 31, 2026 at 07:16:54AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> >> They do look involved but seem to cover the situations we do care\n> >> about, except we seem not to test when the server does not explicitly\n> >> say \"this is still good\", or am I not reading the tests correctly?\n> >\n> > Isn't the following test covering that scenario? Or am I misreading?\n> >\n> >     test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled \"good\" OCSP response'\n> >            with_ssl_verification git -c http.sslVerifyStatus=true \\\n> >                    ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n> >            test_line_count -gt 0 actual\n> >     '\n> \n> Probably I misstated.  What I meant was a reaction to \"fail close\"\n> floated earlier.  A server does not explicitly give stapled good,\n> and the client says \"this is not known-good\" and not talking to it.\n> I.e. 'fetch fails without stapled \"good\"'\n\nAh, I think you're correct, my tests didn't include that. But Grayson's\nalready did as it doesn't require any setup, so that's why I didn't\ninclude it specifically.\n\nPatrick\n"},{"id":"551560","messageId":"xmqqecfez7ie.fsf@gitster.g","threadId":"66156","inReplyTo":"apWOuGbOErZt9jo8@pks.im","subject":"Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-31T14:31:21Z","receivedAt":"2026-08-31T14:31:24Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Mon, Aug 31, 2026 at 07:16:54AM -0700, Junio C Hamano wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>> \n>> >> They do look involved but seem to cover the situations we do care\n>> >> about, except we seem not to test when the server does not explicitly\n>> >> say \"this is still good\", or am I not reading the tests correctly?\n>> >\n>> > Isn't the following test covering that scenario? Or am I misreading?\n>> >\n>> >     test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled \"good\" OCSP response'\n>> >            with_ssl_verification git -c http.sslVerifyStatus=true \\\n>> >                    ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n>> >            test_line_count -gt 0 actual\n>> >     '\n>> \n>> Probably I misstated.  What I meant was a reaction to \"fail close\"\n>> floated earlier.  A server does not explicitly give stapled good,\n>> and the client says \"this is not known-good\" and not talking to it.\n>> I.e. 'fetch fails without stapled \"good\"'\n>\n> Ah, I think you're correct, my tests didn't include that. But Grayson's\n> already did as it doesn't require any setup, so that's why I didn't\n> include it specifically.\n\nAh, I missed that.  So a combined patch taking the best parts from\nboth sides is what we want.  Thanks for helping move the topic\nforward.\n"},{"id":"552209","messageId":"CALgUfNgMzn=enM_vYkn=X9swkZwHovwf00YTXbc0EVN0u6u=HA@mail.gmail.com","threadId":"66156","inReplyTo":"xmqqecfez7ie.fsf@gitster.g","subject":"Re: [PATCH v6] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Grayson Gordon","fromEmail":"graysongordon1@gmail.com","sentAt":"2026-09-08T12:55:06Z","receivedAt":"2026-09-08T12:55:20Z","isPatch":true,"body":"hello all,\n\nSorry I've been away awhile. I'll put together the combined patch and\nshoot it over.\n\n- Grayson\n\nOn Mon, Aug 31, 2026 at 10:31 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Patrick Steinhardt <ps@pks.im> writes:\n>\n> > On Mon, Aug 31, 2026 at 07:16:54AM -0700, Junio C Hamano wrote:\n> >> Patrick Steinhardt <ps@pks.im> writes:\n> >>\n> >> >> They do look involved but seem to cover the situations we do care\n> >> >> about, except we seem not to test when the server does not explicitly\n> >> >> say \"this is still good\", or am I not reading the tests correctly?\n> >> >\n> >> > Isn't the following test covering that scenario? Or am I misreading?\n> >> >\n> >> >     test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled \"good\" OCSP response'\n> >> >            with_ssl_verification git -c http.sslVerifyStatus=true \\\n> >> >                    ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n> >> >            test_line_count -gt 0 actual\n> >> >     '\n> >>\n> >> Probably I misstated.  What I meant was a reaction to \"fail close\"\n> >> floated earlier.  A server does not explicitly give stapled good,\n> >> and the client says \"this is not known-good\" and not talking to it.\n> >> I.e. 'fetch fails without stapled \"good\"'\n> >\n> > Ah, I think you're correct, my tests didn't include that. But Grayson's\n> > already did as it doesn't require any setup, so that's why I didn't\n> > include it specifically.\n>\n> Ah, I missed that.  So a combined patch taking the best parts from\n> both sides is what we want.  Thanks for helping move the topic\n> forward.\n"},{"id":"552760","messageId":"20260915162348.97792-1-ggordon@gitlab.com","threadId":"66156","inReplyTo":"xmqqecfez7ie.fsf@gitster.g","subject":"[PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"graysongordon-gl","fromEmail":"graysongordon1@gmail.com","sentAt":"2026-09-15T16:23:48Z","receivedAt":"2026-09-15T16:23:54Z","isPatch":true,"body":"From: Grayson Gordon <graysongordon1@gmail.com>\n\ngit never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the\nOCSP \"Certificate Status Request\" extension and any stapled response a\nserver sends is ignored, including responses that explicitly state the\ncertificate has been revoked.\n\nAdd an http.sslVerifyStatus boolean that maps to\nCURLOPT_SSL_VERIFYSTATUS. http_options() is already the collect_fn for a\nurlmatch config, so the per-URL form works with no changes:\n\n    git config http.https://example.com/.sslVerifyStatus true\n\nDefaults to false/\"off\". This is due to the nature of the OCSP protocol.\nIf enabled, git would expect to receive OCSP stapled responses. If the\nstapled responses were not present, the connection would be blocked as\nthe status of the server's certificate could not be verified. This would\nbreak connections to legitimate services that don't use OCSP as their\ncertificate revocation mechanism.\n\nIf the backend can't check the staple, curl_easy_setopt() returns\nCURLE_NOT_BUILT_IN. The error message includes curl_easy_strerror()\nalong with the option name, so a libcurl built without status\nverification is easy to identify.\n\nCURLOPT_SSL_VERIFYSTATUS has existed since libcurl 7.41.0, below our\n7.61.0 floor, so no version guard is needed.\n\nThe tests that need no OCSP infrastructure stay in t5551, which t5559\nruns over https. The rest need a certificate authority, a responder to\nanswer for it and a server configured to staple, so lib-httpd gains an\nopt-in LIB_HTTPD_OCSP mode and t5585 uses it to check that a \"good\"\nstaple is accepted, a \"revoked\" one is refused, and that the revoked one\nis ignored when the option is off.\n\nSigned-off-by: Grayson Gordon <graysongordon1@gmail.com>\n---\n\nJunio, Patrick: this is the combined version we discussed. The\ncases that need no OCSP setup stayed in t5551, since t5559 already\nruns that file over https, and everything that needs a responder is\nin the new t5585.\n\nA note on the testing stuff. SSLUseStapling makes apache\ncreate a mutex in a compiled-in system-wide runtime directory.\nI set DefaultRuntimeDir in the OCSP block to keep that\nmutex in the server root, the other way resolved to a path\non my box that didn't exist and prevented the server from starting.\n\nChanges since v6:\n  - added t5585 and LIB_HTTPD_OCSP support in lib-httpd, taken\n    from Patrick's patch\n  - moved the SSL_VERIFYSTATUS prereq into lib-httpd.sh so both\n    files share one definition\n\n Documentation/config/http.adoc |  14 ++++\n http.c                         |  14 ++++\n t/lib-httpd.sh                 | 130 +++++++++++++++++++++++++++++++--\n t/lib-httpd/apache.conf        |  16 ++++\n t/lib-httpd/ocsp-ca.cnf        |  35 +++++++++\n t/meson.build                  |   1 +\n t/t5551-http-fetch-smart.sh    |  22 ++++++\n t/t5585-http-ssl-ocsp.sh       |  55 ++++++++++++++\n 8 files changed, 282 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc\nindex 792a71b413..b54f627969 100644\n--- a/Documentation/config/http.adoc\n+++ b/Documentation/config/http.adoc\n@@ -196,6 +196,20 @@ http.sslVerify::\n \tover HTTPS. Defaults to true. Can be overridden by the\n \t`GIT_SSL_NO_VERIFY` environment variable.\n \n+http.sslVerifyStatus::\n+\tWhether to check the revocation status of the server\n+\tcertificate using the stapled OCSP response supplied during\n+\tthe TLS handshake (\"OCSP stapling\"). Defaults to false, which\n+\tallows connections to servers without validating if the\n+\tcertificate has been revoked by the certificate authority.\n+\tEnabling this option will prevent connections to servers that\n+\thave a certificate status other than \"good\" per RFC 6960.\n+\tConnections to servers that do not return a stapled response\n+\twill also be refused.\n++\n+Set it per remote, e.g.\n+`http.https://example.com/.sslVerifyStatus`, rather than globally.\n+\n http.sslCert::\n \tFile containing the SSL certificate when fetching or pushing\n \tover HTTPS. Can be overridden by the `GIT_SSL_CERT` environment\ndiff --git a/http.c b/http.c\nindex c8fcfd7693..9c2892cafb 100644\n--- a/http.c\n+++ b/http.c\n@@ -44,6 +44,7 @@ static CURL *curl_default;\n char curl_errorstr[CURL_ERROR_SIZE];\n \n static int curl_ssl_verify = -1;\n+static int curl_ssl_verify_status;\n static int curl_ssl_try;\n static char *curl_http_version;\n static char *ssl_cert;\n@@ -400,6 +401,10 @@ static int http_options(const char *var, const char *value,\n \t\tcurl_ssl_verify = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(\"http.sslverifystatus\", var)) {\n+\t\tcurl_ssl_verify_status = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(\"http.sslcipherlist\", var))\n \t\treturn git_config_string(&ssl_cipherlist, var, value);\n \tif (!strcmp(\"http.sslversion\", var))\n@@ -1133,6 +1138,15 @@ static CURL *get_curl_handle(void)\n \t\tcurl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L);\n \t}\n \n+\tif (curl_ssl_verify_status) {\n+\t\tCURLcode ret = curl_easy_setopt(result,\n+\t\t\t\t\t\tCURLOPT_SSL_VERIFYSTATUS, 1L);\n+\t\tif (ret != CURLE_OK)\n+\t\t\tdie(_(\"http.sslVerifyStatus is set, but could not \"\n+\t\t\t      \"enable OCSP status verification: %s\"),\n+\t\t\t    curl_easy_strerror(ret));\n+\t}\n+\n     if (curl_http_version) {\n \t\tlong opt;\n \t\tif (!get_curl_http_version_opt(curl_http_version, &opt)) {\ndiff --git a/t/lib-httpd.sh b/t/lib-httpd.sh\nindex 115455784c..554b0e44fa 100644\n--- a/t/lib-httpd.sh\n+++ b/t/lib-httpd.sh\n@@ -25,6 +25,7 @@\n #    LIB_HTTPD_DAV               enable DAV\n #    LIB_HTTPD_SVN               enable SVN at given location (e.g. \"svn\")\n #    LIB_HTTPD_SSL               enable SSL\n+#    LIB_HTTPD_OCSP              enable OCSP stapling\n #    LIB_HTTPD_PROXY             enable proxy\n #\n # Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at>\n@@ -183,15 +184,26 @@ prepare_httpd() {\n \n \tln -s \"$LIB_HTTPD_MODULE_PATH\" \"$HTTPD_ROOT_PATH/modules\"\n \n+\tif test -n \"$LIB_HTTPD_OCSP\"\n+\tthen\n+\t\tLIB_HTTPD_SSL=t\n+\tfi\n+\n \tif test -n \"$LIB_HTTPD_SSL\"\n \tthen\n \t\tHTTPD_PROTO=https\n \n-\t\tRANDFILE_PATH=\"$HTTPD_ROOT_PATH\"/.rnd openssl req \\\n-\t\t\t-config \"$TEST_PATH/ssl.cnf\" \\\n-\t\t\t-new -x509 -nodes \\\n-\t\t\t-out \"$HTTPD_ROOT_PATH/httpd.pem\" \\\n-\t\t\t-keyout \"$HTTPD_ROOT_PATH/httpd.pem\"\n+\t\tif test -n \"$LIB_HTTPD_OCSP\"\n+\t\tthen\n+\t\t\tprepare_ocsp_stapling\n+\t\t\tHTTPD_PARA=\"$HTTPD_PARA -DOCSP\"\n+\t\telse\n+\t\t\tRANDFILE_PATH=\"$HTTPD_ROOT_PATH\"/.rnd openssl req \\\n+\t\t\t\t-config \"$TEST_PATH/ssl.cnf\" \\\n+\t\t\t\t-new -x509 -nodes \\\n+\t\t\t\t-out \"$HTTPD_ROOT_PATH/httpd.pem\" \\\n+\t\t\t\t-keyout \"$HTTPD_ROOT_PATH/httpd.pem\"\n+\t\tfi\n \t\tGIT_SSL_NO_VERIFY=t\n \t\texport GIT_SSL_NO_VERIFY\n \t\tHTTPD_PARA=\"$HTTPD_PARA -DSSL\"\n@@ -262,6 +274,114 @@ stop_httpd() {\n \t\t-f \"$TEST_PATH/apache.conf\" $HTTPD_PARA -k stop\n }\n \n+restart_httpd () {\n+\thttpd_pid=$(cat \"$HTTPD_ROOT_PATH/httpd.pid\") &&\n+\tstop_httpd &&\n+\twhile kill -0 \"$httpd_pid\" 2>/dev/null\n+\tdo\n+\t\tsleep 1\n+\tdone &&\n+\t\"$LIB_HTTPD_PATH\" -d \"$HTTPD_ROOT_PATH\" \\\n+\t\t-f \"$TEST_PATH/apache.conf\" $HTTPD_PARA \\\n+\t\t-c \"Listen 127.0.0.1:$LIB_HTTPD_PORT\" -k start\n+}\n+\n+# Check if the linked libcurl can verify stapled OCSP responses.\n+test_lazy_prereq SSL_VERIFYSTATUS '\n+\ttest \"$HTTPD_PROTO\" = \"https\" &&\n+\ttest_might_fail git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL\" 2>err &&\n+\t! grep \"http.sslVerifyStatus is set\" err\n+'\n+\n+# Set up a certificate authority. It issues certificate \"httpd.pem\"\n+# and is able to revoke it. Used instead of the self-signed\n+# certificate when LIB_HTTPD_OCSP is set.\n+prepare_ocsp_stapling () {\n+\tLIB_HTTPD_OCSP_PORT=$((LIB_HTTPD_PORT + 10000))\n+\n+\t# Referenced by ocsp-ca.cnf.\n+\tOCSP_CA_DIR=\"$HTTPD_ROOT_PATH/ocsp-ca\"\n+\tOCSP_URI=\"http://127.0.0.1:$LIB_HTTPD_OCSP_PORT\"\n+\texport OCSP_CA_DIR OCSP_URI\n+\n+\tmkdir -p \"$OCSP_CA_DIR/newcerts\" &&\n+\t>\"$OCSP_CA_DIR/index.txt\" &&\n+\techo 1000 >\"$OCSP_CA_DIR/serial\" &&\n+\n+\topenssl req -config \"$TEST_PATH/ocsp-ca.cnf\" \\\n+\t\t-new -x509 -nodes -days 2 \\\n+\t\t-subj \"/CN=git-test-ca\" -extensions v3_ca \\\n+\t\t-keyout \"$HTTPD_ROOT_PATH/ca.key\" \\\n+\t\t-out \"$HTTPD_ROOT_PATH/ca.pem\" &&\n+\topenssl req -config \"$TEST_PATH/ocsp-ca.cnf\" \\\n+\t\t-new -nodes \\\n+\t\t-subj \"/CN=127.0.0.1\" \\\n+\t\t-keyout \"$HTTPD_ROOT_PATH/httpd.key\" \\\n+\t\t-out \"$HTTPD_ROOT_PATH/httpd.csr\" &&\n+\topenssl ca -config \"$TEST_PATH/ocsp-ca.cnf\" -batch \\\n+\t\t-cert \"$HTTPD_ROOT_PATH/ca.pem\" \\\n+\t\t-keyfile \"$HTTPD_ROOT_PATH/ca.key\" \\\n+\t\t-in \"$HTTPD_ROOT_PATH/httpd.csr\" \\\n+\t\t-out \"$HTTPD_ROOT_PATH/httpd.crt\" &&\n+\tcat \"$HTTPD_ROOT_PATH/httpd.key\" \"$HTTPD_ROOT_PATH/httpd.crt\" \\\n+\t\t>\"$HTTPD_ROOT_PATH/httpd.pem\"\n+}\n+\n+run_ocsp_responder () {\n+\topenssl ocsp -port \"$LIB_HTTPD_OCSP_PORT\" \\\n+\t\t-index \"$OCSP_CA_DIR/index.txt\" \\\n+\t\t-CA \"$HTTPD_ROOT_PATH/ca.pem\" \\\n+\t\t-rsigner \"$HTTPD_ROOT_PATH/ca.pem\" \\\n+\t\t-rkey \"$HTTPD_ROOT_PATH/ca.key\" \\\n+\t\t-nmin 60 >>\"$HTTPD_ROOT_PATH/ocsp.log\" 2>&1 &\n+\techo $! >\"$HTTPD_ROOT_PATH/ocsp.pid\"\n+\n+\tfor i in $(test_seq 1 10)\n+\tdo\n+\t\tif openssl ocsp -no_nonce \\\n+\t\t\t-CAfile \"$HTTPD_ROOT_PATH/ca.pem\" \\\n+\t\t\t-issuer \"$HTTPD_ROOT_PATH/ca.pem\" \\\n+\t\t\t-cert \"$HTTPD_ROOT_PATH/httpd.crt\" \\\n+\t\t\t-url \"$OCSP_URI\" >/dev/null 2>&1\n+\t\tthen\n+\t\t\treturn 0\n+\t\tfi\n+\t\tsleep 1\n+\tdone\n+\treturn 1\n+}\n+\n+start_ocsp_responder () {\n+\ttest_atexit stop_ocsp_responder\n+\n+\tif ! run_ocsp_responder\n+\tthen\n+\t\tcat \"$HTTPD_ROOT_PATH\"/ocsp.log >&4 2>/dev/null\n+\t\ttest_skip_or_die GIT_TEST_HTTPD \"OCSP responder setup failed\"\n+\tfi\n+}\n+\n+stop_ocsp_responder () {\n+\tif test -f \"$HTTPD_ROOT_PATH/ocsp.pid\"\n+\tthen\n+\t\tkill \"$(cat \"$HTTPD_ROOT_PATH/ocsp.pid\")\" 2>/dev/null\n+\t\trm -f \"$HTTPD_ROOT_PATH/ocsp.pid\"\n+\tfi\n+}\n+\n+# Revoke the certificate used by httpd and make both the OCSP responder\n+# and httpd aware of it.\n+revoke_httpd_cert () {\n+\topenssl ca -config \"$TEST_PATH/ocsp-ca.cnf\" \\\n+\t\t-cert \"$HTTPD_ROOT_PATH/ca.pem\" \\\n+\t\t-keyfile \"$HTTPD_ROOT_PATH/ca.key\" \\\n+\t\t-revoke \"$HTTPD_ROOT_PATH/httpd.crt\" &&\n+\tstop_ocsp_responder &&\n+\trun_ocsp_responder &&\n+\trestart_httpd\n+}\n+\n test_http_push_nonff () {\n \tREMOTE_REPO=$1\n \tLOCAL_REPO=$2\ndiff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf\nindex 4149fc1078..de5ca45bb8 100644\n--- a/t/lib-httpd/apache.conf\n+++ b/t/lib-httpd/apache.conf\n@@ -242,6 +242,22 @@ SSLSessionCache none\n SSLEngine On\n </IfDefine>\n \n+<IfDefine OCSP>\n+<IfModule !mod_socache_shmcb.c>\n+\tLoadModule socache_shmcb_module modules/mod_socache_shmcb.so\n+</IfModule>\n+\n+SSLCertificateChainFile ca.pem\n+SSLUseStapling On\n+# Stapling needs a mutex, which apache would put in a system-wide\n+# runtime directory that need not be writable. Keep it in the server\n+# root, or httpd refuses to start instead of skipping the tests.\n+DefaultRuntimeDir .\n+SSLStaplingCache shmcb:ssl_stapling(65536)\n+# Staple non-\"good\" responses too, so clients get to see \"revoked\".\n+SSLStaplingReturnResponderErrors On\n+</IfDefine>\n+\n <Location /auth/>\n \tAuthType Basic\n \tAuthName \"git-auth\"\ndiff --git a/t/lib-httpd/ocsp-ca.cnf b/t/lib-httpd/ocsp-ca.cnf\nnew file mode 100644\nindex 0000000000..47a58139b5\n--- /dev/null\n+++ b/t/lib-httpd/ocsp-ca.cnf\n@@ -0,0 +1,35 @@\n+[ ca ]\n+default_ca\t\t= CA_default\n+\n+[ CA_default ]\n+dir\t\t\t= $ENV::OCSP_CA_DIR\n+database\t\t= $dir/index.txt\n+new_certs_dir\t\t= $dir/newcerts\n+serial\t\t\t= $dir/serial\n+default_md\t\t= sha256\n+default_days\t\t= 2\n+policy\t\t\t= policy_anything\n+email_in_dn\t\t= no\n+unique_subject\t\t= no\n+x509_extensions\t\t= server_cert\n+\n+[ policy_anything ]\n+commonName\t\t= supplied\n+\n+[ req ]\n+default_bits\t\t= 2048\n+distinguished_name\t= req_distinguished_name\n+prompt\t\t\t= no\n+\n+[ req_distinguished_name ]\n+# The subject is always given on the command line via -subj.\n+\n+[ v3_ca ]\n+basicConstraints\t= critical, CA:TRUE\n+keyUsage\t\t= critical, digitalSignature, keyCertSign, cRLSign\n+subjectKeyIdentifier\t= hash\n+\n+[ server_cert ]\n+basicConstraints\t= CA:FALSE\n+subjectAltName\t\t= IP:127.0.0.1\n+authorityInfoAccess\t= OCSP;URI:$ENV::OCSP_URI\ndiff --git a/t/meson.build b/t/meson.build\nindex 3ca7b27104..72cbd12d8f 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -728,6 +728,7 @@ integration_tests = [\n   't5582-fetch-negative-refspec.sh',\n   't5583-push-branches.sh',\n   't5584-http-429-retry.sh',\n+  't5585-http-ssl-ocsp.sh',\n   't5600-clone-fail-cleanup.sh',\n   't5601-clone.sh',\n   't5602-clone-remote-exec.sh',\ndiff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh\nindex 805bec025c..c51b14291d 100755\n--- a/t/t5551-http-fetch-smart.sh\n+++ b/t/t5551-http-fetch-smart.sh\n@@ -680,6 +680,28 @@ test_expect_success 'passing hostname resolution information works' '\n \tgit -c \"http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1\" ls-remote \"$BOGUS_HTTPD_URL/smart/repo.git\" >/dev/null\n '\n \n+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=true fails without a staple' '\n+\ttest_must_fail git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=false is a no-op' '\n+\tgit -c http.sslVerifyStatus=false \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus applies to a matching URL' '\n+\ttest_must_fail git -c \"http.$HTTPD_URL/.sslVerifyStatus=true\" \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus is not applied to other URLs' '\n+\tgit -c \"http.https://example.com/.sslVerifyStatus=true\" \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n # here user%40host is the URL-encoded version of user@host,\n # which is our intentionally-odd username to catch parsing errors\n url_user=$HTTPD_URL_USER/auth/smart/repo.git\ndiff --git a/t/t5585-http-ssl-ocsp.sh b/t/t5585-http-ssl-ocsp.sh\nnew file mode 100755\nindex 0000000000..0d1310215f\n--- /dev/null\n+++ b/t/t5585-http-ssl-ocsp.sh\n@@ -0,0 +1,55 @@\n+#!/bin/sh\n+\n+test_description='verification of stapled OCSP responses via http.sslVerifyStatus'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+LIB_HTTPD_OCSP=1\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+\n+start_httpd\n+start_ocsp_responder\n+\n+test_expect_success 'setup repository' '\n+\ttest_commit one &&\n+\tgit init --bare \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\" &&\n+\tgit push \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\" HEAD:refs/heads/main\n+'\n+\n+# lib-httpd.sh exports GIT_SSL_NO_VERIFY, which would keep us from ever\n+# looking at the certificate. Trust our own CA instead.\n+with_ssl_verification () {\n+\t(\n+\t\tsane_unset GIT_SSL_NO_VERIFY &&\n+\t\tGIT_SSL_CAINFO=\"$HTTPD_ROOT_PATH/ca.pem\" \"$@\"\n+\t)\n+}\n+\n+test_expect_success SSL_VERIFYSTATUS 'certificate verification works against test CA' '\n+\twith_ssl_verification git ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled \"good\" OCSP response' '\n+\twith_ssl_verification git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_expect_success SSL_VERIFYSTATUS 'revoked certificate is rejected' '\n+\trevoke_httpd_cert &&\n+\twith_ssl_verification test_must_fail git -c http.sslVerifyStatus=true \\\n+\t\tls-remote \"$HTTPD_URL/smart/repo.git\" 2>err &&\n+\ttest_grep -i -e \"ocsp\" -e \"revocation\" -e \"revoked\" -e \"certificate status\" err\n+'\n+\n+# Depends on the certificate revoked by the preceding test.\n+test_expect_success SSL_VERIFYSTATUS 'revoked certificate is accepted without http.sslVerifyStatus' '\n+\twith_ssl_verification git ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n+\ttest_line_count -gt 0 actual\n+'\n+\n+test_done\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"552797","messageId":"xmqqv785uha7.fsf@gitster.g","threadId":"66156","inReplyTo":"20260915162348.97792-1-ggordon@gitlab.com","subject":"Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-16T19:29:04Z","receivedAt":"2026-09-16T19:29:09Z","isPatch":true,"body":"graysongordon-gl <graysongordon1@gmail.com> writes:\n\n> From: Grayson Gordon <graysongordon1@gmail.com>\n>\n> git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the\n> OCSP \"Certificate Status Request\" extension and any stapled response a\n> server sends is ignored, including responses that explicitly state the\n> certificate has been revoked.\n>\n> Add an http.sslVerifyStatus boolean that maps to\n> CURLOPT_SSL_VERIFYSTATUS. http_options() is already the collect_fn for a\n> urlmatch config, so the per-URL form works with no changes:\n> ---\n>\n> Junio, Patrick: this is the combined version we discussed. The\n> cases that need no OCSP setup stayed in t5551, since t5559 already\n> runs that file over https, and everything that needs a responder is\n> in the new t5585.\n>\n> A note on the testing stuff. SSLUseStapling makes apache\n> create a mutex in a compiled-in system-wide runtime directory.\n> I set DefaultRuntimeDir in the OCSP block to keep that\n> mutex in the server root, the other way resolved to a path\n> on my box that didn't exist and prevented the server from starting.\n>\n> Changes since v6:\n>   - added t5585 and LIB_HTTPD_OCSP support in lib-httpd, taken\n>     from Patrick's patch\n>   - moved the SSL_VERIFYSTATUS prereq into lib-httpd.sh so both\n>     files share one definition\n\nThe updated tests look good; will replace.  Thanks.\n"},{"id":"553044","messageId":"arPI8PfvsKUJSypg@pks.im","threadId":"66156","inReplyTo":"20260915162348.97792-1-ggordon@gitlab.com","subject":"Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-23T12:42:47Z","receivedAt":"2026-09-23T12:43:01Z","isPatch":true,"body":"On Tue, Sep 15, 2026 at 12:23:48PM -0400, graysongordon-gl wrote:\n> From: Grayson Gordon <graysongordon1@gmail.com>\n> \n> git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the\n> OCSP \"Certificate Status Request\" extension and any stapled response a\n> server sends is ignored, including responses that explicitly state the\n> certificate has been revoked.\n> \n> Add an http.sslVerifyStatus boolean that maps to\n> CURLOPT_SSL_VERIFYSTATUS. http_options() is already the collect_fn for a\n> urlmatch config, so the per-URL form works with no changes:\n> \n>     git config http.https://example.com/.sslVerifyStatus true\n> \n> Defaults to false/\"off\". This is due to the nature of the OCSP protocol.\n> If enabled, git would expect to receive OCSP stapled responses. If the\n> stapled responses were not present, the connection would be blocked as\n> the status of the server's certificate could not be verified. This would\n> break connections to legitimate services that don't use OCSP as their\n> certificate revocation mechanism.\n> \n> If the backend can't check the staple, curl_easy_setopt() returns\n> CURLE_NOT_BUILT_IN. The error message includes curl_easy_strerror()\n> along with the option name, so a libcurl built without status\n> verification is easy to identify.\n\nNit: I feel like this paragraph is excessive information, as it doesn't\ngive the reviewer any additional context over what the code already\nstates.\n\n> CURLOPT_SSL_VERIFYSTATUS has existed since libcurl 7.41.0, below our\n> 7.61.0 floor, so no version guard is needed.\n> \n> The tests that need no OCSP infrastructure stay in t5551, which t5559\n> runs over https. The rest need a certificate authority, a responder to\n> answer for it and a server configured to staple, so lib-httpd gains an\n> opt-in LIB_HTTPD_OCSP mode and t5585 uses it to check that a \"good\"\n> staple is accepted, a \"revoked\" one is refused, and that the revoked one\n> is ignored when the option is off.\n\nNit: Likewise, this paragraph doesn't add much value.\n\nOther than that I'm happy with this patch. I'll leave it to you (or\nothers) to decide whether this requires another reroll to address the\ntwo nits.\n\nThanks!\n\nPatrick\n"},{"id":"553116","messageId":"arQ/nOH+o3XwQFD/@szeder.dev","threadId":"66156","inReplyTo":"20260915162348.97792-1-ggordon@gitlab.com","subject":"Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2026-09-23T21:07:40Z","receivedAt":"2026-09-23T21:07:44Z","isPatch":true,"body":"On Tue, Sep 15, 2026 at 12:23:48PM -0400, graysongordon-gl wrote:\n> From: Grayson Gordon <graysongordon1@gmail.com>\n> \n> git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the\n> OCSP \"Certificate Status Request\" extension and any stapled response a\n> server sends is ignored, including responses that explicitly state the\n> certificate has been revoked.\n> \n> Add an http.sslVerifyStatus boolean that maps to\n> CURLOPT_SSL_VERIFYSTATUS. http_options() is already the collect_fn for a\n> urlmatch config, so the per-URL form works with no changes:\n> \n>     git config http.https://example.com/.sslVerifyStatus true\n> \n> Defaults to false/\"off\". This is due to the nature of the OCSP protocol.\n> If enabled, git would expect to receive OCSP stapled responses. If the\n> stapled responses were not present, the connection would be blocked as\n> the status of the server's certificate could not be verified. This would\n> break connections to legitimate services that don't use OCSP as their\n> certificate revocation mechanism.\n> \n> If the backend can't check the staple, curl_easy_setopt() returns\n> CURLE_NOT_BUILT_IN. The error message includes curl_easy_strerror()\n> along with the option name, so a libcurl built without status\n> verification is easy to identify.\n> \n> CURLOPT_SSL_VERIFYSTATUS has existed since libcurl 7.41.0, below our\n> 7.61.0 floor, so no version guard is needed.\n> \n> The tests that need no OCSP infrastructure stay in t5551, which t5559\n> runs over https. The rest need a certificate authority, a responder to\n> answer for it and a server configured to staple, so lib-httpd gains an\n> opt-in LIB_HTTPD_OCSP mode and t5585 uses it to check that a \"good\"\n> staple is accepted, a \"revoked\" one is refused, and that the revoked one\n> is ignored when the option is off.\n> \n> Signed-off-by: Grayson Gordon <graysongordon1@gmail.com>\n> ---\n\nThis patch was merged to 'next' the other day, and the last test in\nthe new t5585 fails on my system.\n\n> diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh\n> index 115455784c..554b0e44fa 100644\n> --- a/t/lib-httpd.sh\n> +++ b/t/lib-httpd.sh\n> @@ -25,6 +25,7 @@\n>  #    LIB_HTTPD_DAV               enable DAV\n>  #    LIB_HTTPD_SVN               enable SVN at given location (e.g. \"svn\")\n>  #    LIB_HTTPD_SSL               enable SSL\n> +#    LIB_HTTPD_OCSP              enable OCSP stapling\n>  #    LIB_HTTPD_PROXY             enable proxy\n>  #\n>  # Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at>\n> @@ -183,15 +184,26 @@ prepare_httpd() {\n>  \n>  \tln -s \"$LIB_HTTPD_MODULE_PATH\" \"$HTTPD_ROOT_PATH/modules\"\n>  \n> +\tif test -n \"$LIB_HTTPD_OCSP\"\n> +\tthen\n> +\t\tLIB_HTTPD_SSL=t\n> +\tfi\n> +\n>  \tif test -n \"$LIB_HTTPD_SSL\"\n>  \tthen\n>  \t\tHTTPD_PROTO=https\n>  \n> -\t\tRANDFILE_PATH=\"$HTTPD_ROOT_PATH\"/.rnd openssl req \\\n> -\t\t\t-config \"$TEST_PATH/ssl.cnf\" \\\n> -\t\t\t-new -x509 -nodes \\\n> -\t\t\t-out \"$HTTPD_ROOT_PATH/httpd.pem\" \\\n> -\t\t\t-keyout \"$HTTPD_ROOT_PATH/httpd.pem\"\n> +\t\tif test -n \"$LIB_HTTPD_OCSP\"\n> +\t\tthen\n> +\t\t\tprepare_ocsp_stapling\n> +\t\t\tHTTPD_PARA=\"$HTTPD_PARA -DOCSP\"\n> +\t\telse\n> +\t\t\tRANDFILE_PATH=\"$HTTPD_ROOT_PATH\"/.rnd openssl req \\\n> +\t\t\t\t-config \"$TEST_PATH/ssl.cnf\" \\\n> +\t\t\t\t-new -x509 -nodes \\\n> +\t\t\t\t-out \"$HTTPD_ROOT_PATH/httpd.pem\" \\\n> +\t\t\t\t-keyout \"$HTTPD_ROOT_PATH/httpd.pem\"\n> +\t\tfi\n>  \t\tGIT_SSL_NO_VERIFY=t\n>  \t\texport GIT_SSL_NO_VERIFY\n>  \t\tHTTPD_PARA=\"$HTTPD_PARA -DSSL\"\n> @@ -262,6 +274,114 @@ stop_httpd() {\n>  \t\t-f \"$TEST_PATH/apache.conf\" $HTTPD_PARA -k stop\n>  }\n>  \n> +restart_httpd () {\n> +\thttpd_pid=$(cat \"$HTTPD_ROOT_PATH/httpd.pid\") &&\n> +\tstop_httpd &&\n> +\twhile kill -0 \"$httpd_pid\" 2>/dev/null\n> +\tdo\n> +\t\tsleep 1\n> +\tdone &&\n> +\t\"$LIB_HTTPD_PATH\" -d \"$HTTPD_ROOT_PATH\" \\\n> +\t\t-f \"$TEST_PATH/apache.conf\" $HTTPD_PARA \\\n> +\t\t-c \"Listen 127.0.0.1:$LIB_HTTPD_PORT\" -k start\n> +}\n> +\n> +# Check if the linked libcurl can verify stapled OCSP responses.\n> +test_lazy_prereq SSL_VERIFYSTATUS '\n> +\ttest \"$HTTPD_PROTO\" = \"https\" &&\n> +\ttest_might_fail git -c http.sslVerifyStatus=true \\\n> +\t\tls-remote \"$HTTPD_URL\" 2>err &&\n> +\t! grep \"http.sslVerifyStatus is set\" err\n> +'\n\nWhen checking this prereq in t5585, I get the following trace:\n\n  mkdir -p \"$TRASH_DIRECTORY/prereq-test-dir-SSL_VERIFYSTATUS\" &&\n  (\n  \tcd \"$TRASH_DIRECTORY/prereq-test-dir-SSL_VERIFYSTATUS\" &&\n  \ttest \"$HTTPD_PROTO\" = \"https\" &&\n  \ttest_might_fail git -c http.sslVerifyStatus=true \\\n  \t\tls-remote \"$HTTPD_URL\" 2>err &&\n  \tcat err && # debug\n  \t! grep \"http.sslVerifyStatus is set\" err\n  \n  )\n  + mkdir -p /home/szeder/src/git/t/trash directory.t5585-http-ssl-ocsp/prereq-test-dir-SSL_VERIFYSTATUS\n  + cd /home/szeder/src/git/t/trash directory.t5585-http-ssl-ocsp/prereq-test-dir-SSL_VERIFYSTATUS\n  + test https = https\n  + test_might_fail git -c http.sslVerifyStatus=true ls-remote https://127.0.0.1:5585\n  + cat err\n  fatal: repository 'https://127.0.0.1:5585/' not found\n  + grep http.sslVerifyStatus is set err\n  prerequisite SSL_VERIFYSTATUS ok\n\nI added that 'cat err' to see the error message.  Turns out that 'git\nls-remote' can't even find the repository on the remote, but the\nprereq is still considered fulfilled.  Is that right?\n\n\nIn t5559 I get the following trace:\n\n  + mkdir -p /home/szeder/src/git/t/trash directory.t5559-http-fetch-smart-http2/prereq-test-dir-SSL_VERIFYSTATUS\n  + cd /home/szeder/src/git/t/trash directory.t5559-http-fetch-smart-http2/prereq-test-dir-SSL_VERIFYSTATUS\n  + test https = https\n  + test_might_fail git -c http.sslVerifyStatus=true ls-remote https://127.0.0.1:5559\n  + cat err\n  fatal: unable to access 'https://127.0.0.1:5559/': No OCSP response received\n  + grep http.sslVerifyStatus is set err\n  prerequisite SSL_VERIFYSTATUS ok\n\nThis time the error message talks about missing OCSP response, but the\nprereq is still considered fulfilled.  Again: is that right?!\n\nInstead of the lack of a certain string in the error message, is\nthere something positive that we can test instead?\n\n> +# Set up a certificate authority. It issues certificate \"httpd.pem\"\n> +# and is able to revoke it. Used instead of the self-signed\n> +# certificate when LIB_HTTPD_OCSP is set.\n> +prepare_ocsp_stapling () {\n> +\tLIB_HTTPD_OCSP_PORT=$((LIB_HTTPD_PORT + 10000))\n> +\n> +\t# Referenced by ocsp-ca.cnf.\n> +\tOCSP_CA_DIR=\"$HTTPD_ROOT_PATH/ocsp-ca\"\n> +\tOCSP_URI=\"http://127.0.0.1:$LIB_HTTPD_OCSP_PORT\"\n> +\texport OCSP_CA_DIR OCSP_URI\n> +\n> +\tmkdir -p \"$OCSP_CA_DIR/newcerts\" &&\n> +\t>\"$OCSP_CA_DIR/index.txt\" &&\n> +\techo 1000 >\"$OCSP_CA_DIR/serial\" &&\n> +\n> +\topenssl req -config \"$TEST_PATH/ocsp-ca.cnf\" \\\n> +\t\t-new -x509 -nodes -days 2 \\\n> +\t\t-subj \"/CN=git-test-ca\" -extensions v3_ca \\\n> +\t\t-keyout \"$HTTPD_ROOT_PATH/ca.key\" \\\n> +\t\t-out \"$HTTPD_ROOT_PATH/ca.pem\" &&\n> +\topenssl req -config \"$TEST_PATH/ocsp-ca.cnf\" \\\n> +\t\t-new -nodes \\\n> +\t\t-subj \"/CN=127.0.0.1\" \\\n> +\t\t-keyout \"$HTTPD_ROOT_PATH/httpd.key\" \\\n> +\t\t-out \"$HTTPD_ROOT_PATH/httpd.csr\" &&\n> +\topenssl ca -config \"$TEST_PATH/ocsp-ca.cnf\" -batch \\\n> +\t\t-cert \"$HTTPD_ROOT_PATH/ca.pem\" \\\n> +\t\t-keyfile \"$HTTPD_ROOT_PATH/ca.key\" \\\n> +\t\t-in \"$HTTPD_ROOT_PATH/httpd.csr\" \\\n> +\t\t-out \"$HTTPD_ROOT_PATH/httpd.crt\" &&\n> +\tcat \"$HTTPD_ROOT_PATH/httpd.key\" \"$HTTPD_ROOT_PATH/httpd.crt\" \\\n> +\t\t>\"$HTTPD_ROOT_PATH/httpd.pem\"\n> +}\n> +\n> +run_ocsp_responder () {\n> +\topenssl ocsp -port \"$LIB_HTTPD_OCSP_PORT\" \\\n> +\t\t-index \"$OCSP_CA_DIR/index.txt\" \\\n> +\t\t-CA \"$HTTPD_ROOT_PATH/ca.pem\" \\\n> +\t\t-rsigner \"$HTTPD_ROOT_PATH/ca.pem\" \\\n> +\t\t-rkey \"$HTTPD_ROOT_PATH/ca.key\" \\\n> +\t\t-nmin 60 >>\"$HTTPD_ROOT_PATH/ocsp.log\" 2>&1 &\n> +\techo $! >\"$HTTPD_ROOT_PATH/ocsp.pid\"\n> +\n> +\tfor i in $(test_seq 1 10)\n> +\tdo\n> +\t\tif openssl ocsp -no_nonce \\\n> +\t\t\t-CAfile \"$HTTPD_ROOT_PATH/ca.pem\" \\\n> +\t\t\t-issuer \"$HTTPD_ROOT_PATH/ca.pem\" \\\n> +\t\t\t-cert \"$HTTPD_ROOT_PATH/httpd.crt\" \\\n> +\t\t\t-url \"$OCSP_URI\" >/dev/null 2>&1\n> +\t\tthen\n> +\t\t\treturn 0\n> +\t\tfi\n> +\t\tsleep 1\n> +\tdone\n> +\treturn 1\n> +}\n> +\n> +start_ocsp_responder () {\n> +\ttest_atexit stop_ocsp_responder\n> +\n> +\tif ! run_ocsp_responder\n> +\tthen\n> +\t\tcat \"$HTTPD_ROOT_PATH\"/ocsp.log >&4 2>/dev/null\n> +\t\ttest_skip_or_die GIT_TEST_HTTPD \"OCSP responder setup failed\"\n> +\tfi\n> +}\n> +\n> +stop_ocsp_responder () {\n> +\tif test -f \"$HTTPD_ROOT_PATH/ocsp.pid\"\n> +\tthen\n> +\t\tkill \"$(cat \"$HTTPD_ROOT_PATH/ocsp.pid\")\" 2>/dev/null\n> +\t\trm -f \"$HTTPD_ROOT_PATH/ocsp.pid\"\n> +\tfi\n> +}\n> +\n> +# Revoke the certificate used by httpd and make both the OCSP responder\n> +# and httpd aware of it.\n> +revoke_httpd_cert () {\n> +\topenssl ca -config \"$TEST_PATH/ocsp-ca.cnf\" \\\n> +\t\t-cert \"$HTTPD_ROOT_PATH/ca.pem\" \\\n> +\t\t-keyfile \"$HTTPD_ROOT_PATH/ca.key\" \\\n> +\t\t-revoke \"$HTTPD_ROOT_PATH/httpd.crt\" &&\n> +\tstop_ocsp_responder &&\n> +\trun_ocsp_responder &&\n> +\trestart_httpd\n> +}\n> +\n>  test_http_push_nonff () {\n>  \tREMOTE_REPO=$1\n>  \tLOCAL_REPO=$2\n> diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf\n> index 4149fc1078..de5ca45bb8 100644\n> --- a/t/lib-httpd/apache.conf\n> +++ b/t/lib-httpd/apache.conf\n> @@ -242,6 +242,22 @@ SSLSessionCache none\n>  SSLEngine On\n>  </IfDefine>\n>  \n> +<IfDefine OCSP>\n> +<IfModule !mod_socache_shmcb.c>\n> +\tLoadModule socache_shmcb_module modules/mod_socache_shmcb.so\n> +</IfModule>\n> +\n> +SSLCertificateChainFile ca.pem\n> +SSLUseStapling On\n> +# Stapling needs a mutex, which apache would put in a system-wide\n> +# runtime directory that need not be writable. Keep it in the server\n> +# root, or httpd refuses to start instead of skipping the tests.\n> +DefaultRuntimeDir .\n> +SSLStaplingCache shmcb:ssl_stapling(65536)\n> +# Staple non-\"good\" responses too, so clients get to see \"revoked\".\n> +SSLStaplingReturnResponderErrors On\n> +</IfDefine>\n> +\n>  <Location /auth/>\n>  \tAuthType Basic\n>  \tAuthName \"git-auth\"\n> diff --git a/t/lib-httpd/ocsp-ca.cnf b/t/lib-httpd/ocsp-ca.cnf\n> new file mode 100644\n> index 0000000000..47a58139b5\n> --- /dev/null\n> +++ b/t/lib-httpd/ocsp-ca.cnf\n> @@ -0,0 +1,35 @@\n> +[ ca ]\n> +default_ca\t\t= CA_default\n> +\n> +[ CA_default ]\n> +dir\t\t\t= $ENV::OCSP_CA_DIR\n> +database\t\t= $dir/index.txt\n> +new_certs_dir\t\t= $dir/newcerts\n> +serial\t\t\t= $dir/serial\n> +default_md\t\t= sha256\n> +default_days\t\t= 2\n> +policy\t\t\t= policy_anything\n> +email_in_dn\t\t= no\n> +unique_subject\t\t= no\n> +x509_extensions\t\t= server_cert\n> +\n> +[ policy_anything ]\n> +commonName\t\t= supplied\n> +\n> +[ req ]\n> +default_bits\t\t= 2048\n> +distinguished_name\t= req_distinguished_name\n> +prompt\t\t\t= no\n> +\n> +[ req_distinguished_name ]\n> +# The subject is always given on the command line via -subj.\n> +\n> +[ v3_ca ]\n> +basicConstraints\t= critical, CA:TRUE\n> +keyUsage\t\t= critical, digitalSignature, keyCertSign, cRLSign\n> +subjectKeyIdentifier\t= hash\n> +\n> +[ server_cert ]\n> +basicConstraints\t= CA:FALSE\n> +subjectAltName\t\t= IP:127.0.0.1\n> +authorityInfoAccess\t= OCSP;URI:$ENV::OCSP_URI\n> diff --git a/t/meson.build b/t/meson.build\n> index 3ca7b27104..72cbd12d8f 100644\n> --- a/t/meson.build\n> +++ b/t/meson.build\n> @@ -728,6 +728,7 @@ integration_tests = [\n>    't5582-fetch-negative-refspec.sh',\n>    't5583-push-branches.sh',\n>    't5584-http-429-retry.sh',\n> +  't5585-http-ssl-ocsp.sh',\n>    't5600-clone-fail-cleanup.sh',\n>    't5601-clone.sh',\n>    't5602-clone-remote-exec.sh',\n> diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh\n> index 805bec025c..c51b14291d 100755\n> --- a/t/t5551-http-fetch-smart.sh\n> +++ b/t/t5551-http-fetch-smart.sh\n> @@ -680,6 +680,28 @@ test_expect_success 'passing hostname resolution information works' '\n>  \tgit -c \"http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1\" ls-remote \"$BOGUS_HTTPD_URL/smart/repo.git\" >/dev/null\n>  '\n>  \n> +test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=true fails without a staple' '\n> +\ttest_must_fail git -c http.sslVerifyStatus=true \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n\nShouldn't we check the error message, to make sure that the command\nfailed for the expected reason (here and in t5585 as well)?\n\n> +'\n> +\n> +test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=false is a no-op' '\n> +\tgit -c http.sslVerifyStatus=false \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n> +\ttest_line_count -gt 0 actual\n> +'\n> +\n> +test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus applies to a matching URL' '\n> +\ttest_must_fail git -c \"http.$HTTPD_URL/.sslVerifyStatus=true\" \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\"\n> +'\n> +\n> +test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus is not applied to other URLs' '\n> +\tgit -c \"http.https://example.com/.sslVerifyStatus=true\" \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n> +\ttest_line_count -gt 0 actual\n> +'\n> +\n>  # here user%40host is the URL-encoded version of user@host,\n>  # which is our intentionally-odd username to catch parsing errors\n>  url_user=$HTTPD_URL_USER/auth/smart/repo.git\n> diff --git a/t/t5585-http-ssl-ocsp.sh b/t/t5585-http-ssl-ocsp.sh\n> new file mode 100755\n> index 0000000000..0d1310215f\n> --- /dev/null\n> +++ b/t/t5585-http-ssl-ocsp.sh\n> @@ -0,0 +1,55 @@\n> +#!/bin/sh\n> +\n> +test_description='verification of stapled OCSP responses via http.sslVerifyStatus'\n> +\n> +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n> +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n> +\n> +. ./test-lib.sh\n> +\n> +LIB_HTTPD_OCSP=1\n> +. \"$TEST_DIRECTORY\"/lib-httpd.sh\n> +\n> +start_httpd\n> +start_ocsp_responder\n> +\n> +test_expect_success 'setup repository' '\n> +\ttest_commit one &&\n> +\tgit init --bare \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\" &&\n> +\tgit push \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\" HEAD:refs/heads/main\n> +'\n> +\n> +# lib-httpd.sh exports GIT_SSL_NO_VERIFY, which would keep us from ever\n> +# looking at the certificate. Trust our own CA instead.\n> +with_ssl_verification () {\n> +\t(\n> +\t\tsane_unset GIT_SSL_NO_VERIFY &&\n> +\t\tGIT_SSL_CAINFO=\"$HTTPD_ROOT_PATH/ca.pem\" \"$@\"\n\nAccording to our CodingGuidelines, a temporary variable assignment\nlike this should not be used for shell functions for portability\nreasons.  In most test cases this is fine, becase \"$@\" is a git\ncommand, but ...\n\n> +\t)\n> +}\n> +\n> +test_expect_success SSL_VERIFYSTATUS 'certificate verification works against test CA' '\n> +\twith_ssl_verification git ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n> +\ttest_line_count -gt 0 actual\n> +'\n> +\n> +test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled \"good\" OCSP response' '\n> +\twith_ssl_verification git -c http.sslVerifyStatus=true \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n> +\ttest_line_count -gt 0 actual\n> +'\n> +\n> +test_expect_success SSL_VERIFYSTATUS 'revoked certificate is rejected' '\n> +\trevoke_httpd_cert &&\n> +\twith_ssl_verification test_must_fail git -c http.sslVerifyStatus=true \\\n> +\t\tls-remote \"$HTTPD_URL/smart/repo.git\" 2>err &&\n> +\ttest_grep -i -e \"ocsp\" -e \"revocation\" -e \"revoked\" -e \"certificate status\" err\n> +'\n\n... in this case \"$@\" is the test_must_fail shell function.\n\nPlease set and then export that variable instead; it's already in a\nsubshell because of the sane_unset anyway.\n\n> +# Depends on the certificate revoked by the preceding test.\n> +test_expect_success SSL_VERIFYSTATUS 'revoked certificate is accepted without http.sslVerifyStatus' '\n> +\twith_ssl_verification git ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n> +\ttest_line_count -gt 0 actual\n> +'\n\nSo this test case fails for me with the following trace output:\n\n  expecting success of 5585.5 'revoked certificate is accepted without http.sslVerifyStatus': \n  \twith_ssl_verification git ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n  \ttest_line_count -gt 0 actual\n  \n  + with_ssl_verification git ls-remote https://127.0.0.1:5585/smart/repo.git\n  + sane_unset GIT_SSL_NO_VERIFY\n  + unset GIT_SSL_NO_VERIFY\n  + return 0\n  + 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\n  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\n  error: last command exited with $?=128\n  not ok 5 - revoked certificate is accepted without http.sslVerifyStatus\n  #\t\n  #\t\twith_ssl_verification git ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n  #\t\ttest_line_count -gt 0 actual\n  #\t\n \nlibcurl is 7.81.0, apache is 2.4.52 (whatever is shipped in this\nslowly aging LTS...)\n"},{"id":"553121","messageId":"xmqq33uz7iee.fsf@gitster.g","threadId":"66156","inReplyTo":"arPI8PfvsKUJSypg@pks.im","subject":"Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-23T21:43:53Z","receivedAt":"2026-09-23T21:43:55Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Nit: Likewise, this paragraph doesn't add much value.\n>\n> Other than that I'm happy with this patch. I'll leave it to you (or\n> others) to decide whether this requires another reroll to address the\n> two nits.\n\nSZEDER reports breakages with this topic.\n\n    https://lore.kernel.org/git/arQ%2FnOH+o3XwQFD%2F@szeder.dev/\n\nSince we are not in a hurry to take this topic, let me revert it out\nof 'next' and give it time to mature.  When the reroll comes, we can\ncritique these overly verbose words without much meaning again.\n\nThanks.\n"},{"id":"553122","messageId":"xmqqwlsb63o9.fsf@gitster.g","threadId":"66156","inReplyTo":"arQ/nOH+o3XwQFD/@szeder.dev","subject":"Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-23T21:47:18Z","receivedAt":"2026-09-23T21:47:22Z","isPatch":true,"body":"SZEDER Gábor <szeder.dev@gmail.com> writes:\n\n> On Tue, Sep 15, 2026 at 12:23:48PM -0400, graysongordon-gl wrote:\n>> From: Grayson Gordon <graysongordon1@gmail.com>\n>> \n>> git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the\n>> OCSP \"Certificate Status Request\" extension and any stapled response a\n>> server sends is ignored, including responses that explicitly state the\n>> certificate has been revoked.\n> ...\n> This patch was merged to 'next' the other day, and the last test in\n> the new t5585 fails on my system.\n\nSorry about a premature merge.  Since we are not in a hurry to take\nthis topic in (or no new feature topic in general), let me revert it\nout of 'next' and give it a clean slate to try again.\n\n> ...\n> I added that 'cat err' to see the error message.  Turns out that 'git\n> ls-remote' can't even find the repository on the remote, but the\n> prereq is still considered fulfilled.  Is that right?\n> ...\n> This time the error message talks about missing OCSP response, but the\n> prereq is still considered fulfilled.  Again: is that right?!\n>\n> Instead of the lack of a certain string in the error message, is\n> there something positive that we can test instead?\n\nOh, that is a very constructive and useful suggestion.  Greatly\nappreciated.\n\n> So this test case fails for me with the following trace output:\n>\n>   expecting success of 5585.5 'revoked certificate is accepted without http.sslVerifyStatus': \n>   \twith_ssl_verification git ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n>   \ttest_line_count -gt 0 actual\n>   \n>   + with_ssl_verification git ls-remote https://127.0.0.1:5585/smart/repo.git\n>   + sane_unset GIT_SSL_NO_VERIFY\n>   + unset GIT_SSL_NO_VERIFY\n>   + return 0\n>   + 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\n>   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\n>   error: last command exited with $?=128\n>   not ok 5 - revoked certificate is accepted without http.sslVerifyStatus\n>   #\t\n>   #\t\twith_ssl_verification git ls-remote \"$HTTPD_URL/smart/repo.git\" >actual &&\n>   #\t\ttest_line_count -gt 0 actual\n>   #\t\n>  \n> libcurl is 7.81.0, apache is 2.4.52 (whatever is shipped in this\n> slowly aging LTS...)\n\nThanks.\n\n"},{"id":"553147","messageId":"arTUNYVvCNwX1pDp@szeder.dev","threadId":"66156","inReplyTo":"xmqqwlsb63o9.fsf@gitster.g","subject":"Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2026-09-24T07:41:41Z","receivedAt":"2026-09-24T07:41:44Z","isPatch":true,"body":"On Wed, Sep 23, 2026 at 02:47:18PM -0700, Junio C Hamano wrote:\n> SZEDER Gábor <szeder.dev@gmail.com> writes:\n> \n> > On Tue, Sep 15, 2026 at 12:23:48PM -0400, graysongordon-gl wrote:\n> >> From: Grayson Gordon <graysongordon1@gmail.com>\n> >> \n> >> git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the\n> >> OCSP \"Certificate Status Request\" extension and any stapled response a\n> >> server sends is ignored, including responses that explicitly state the\n> >> certificate has been revoked.\n> > ...\n> > This patch was merged to 'next' the other day, and the last test in\n> > the new t5585 fails on my system.\n> \n> Sorry about a premature merge.  Since we are not in a hurry to take\n> this topic in (or no new feature topic in general), let me revert it\n> out of 'next' and give it a clean slate to try again.\n\nWell, if you hadn't merged it, we would perhaps still be none the\nwiser, because, alas, I don't have the bandwidth to run tests on the\nseen branch regularly...\n\nHowever, CI does, but I can't seem to find any CI runs that failed\nbecause of this, which makes me worried that something is wrong on my\nend.\n\n> > ...\n> > I added that 'cat err' to see the error message.  Turns out that 'git\n> > ls-remote' can't even find the repository on the remote, but the\n> > prereq is still considered fulfilled.  Is that right?\n> > ...\n> > This time the error message talks about missing OCSP response, but the\n> > prereq is still considered fulfilled.  Again: is that right?!\n> >\n> > Instead of the lack of a certain string in the error message, is\n> > there something positive that we can test instead?\n> \n> Oh, that is a very constructive and useful suggestion.  Greatly\n> appreciated.\n\nAfter having slept on it :) I now start to realize that this\nSSL_VERIFYSTATUS prereq only checks that libcurl supports the\nCURLOPT_SSL_VERIFYSTATUS option, and has nothing to do with the\ncapabilities and configuration of the web server.  If my understanding\nis correct, then I think that:\n\n  - Merely attempting a connection to somewhere is indeed sufficient\n    to check this, and it doesn't matter that the server can't find\n    the requested repository.  \n\n  - Checking for the error message printed after curl_easy_setopt(...,\n    CURLOPT_SSL_VERIFYSTATUS, ...) returns with error is indeed the\n    right thing to do.\n\n    However, in that new error message the second half is much more\n    informative than the first, and if the prereq looked for the\n    absence of \"could not enable OCSP status verification\" instead of\n    \"http.sslVerifyStatus is set\", then I think I would have realized\n    all this sooner.  Perhaps calling the prereq CURL_SSL_VERIFYSTATUS\n    would have helped, too.\n\n"},{"id":"553151","messageId":"arTYVLnW-2GHpGGm@pks.im","threadId":"66156","inReplyTo":"arTUNYVvCNwX1pDp@szeder.dev","subject":"Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-24T07:59:16Z","receivedAt":"2026-09-24T07:59:23Z","isPatch":true,"body":"On Thu, Sep 24, 2026 at 09:41:41AM +0200, SZEDER Gábor wrote:\n> On Wed, Sep 23, 2026 at 02:47:18PM -0700, Junio C Hamano wrote:\n> > SZEDER Gábor <szeder.dev@gmail.com> writes:\n> > \n> > > On Tue, Sep 15, 2026 at 12:23:48PM -0400, graysongordon-gl wrote:\n> > >> From: Grayson Gordon <graysongordon1@gmail.com>\n> > >> \n> > >> git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the\n> > >> OCSP \"Certificate Status Request\" extension and any stapled response a\n> > >> server sends is ignored, including responses that explicitly state the\n> > >> certificate has been revoked.\n> > > ...\n> > > This patch was merged to 'next' the other day, and the last test in\n> > > the new t5585 fails on my system.\n> > \n> > Sorry about a premature merge.  Since we are not in a hurry to take\n> > this topic in (or no new feature topic in general), let me revert it\n> > out of 'next' and give it a clean slate to try again.\n> \n> Well, if you hadn't merged it, we would perhaps still be none the\n> wiser, because, alas, I don't have the bandwidth to run tests on the\n> seen branch regularly...\n> \n> However, CI does, but I can't seem to find any CI runs that failed\n> because of this, which makes me worried that something is wrong on my\n> end.\n\nDo you maybe run with a curl backend that doesn't properly support OCSP?\nBut even if so, our test suite should notice and skip the tests.\n\nPatrick\n"},{"id":"553205","messageId":"xmqqcxu24o1c.fsf@gitster.g","threadId":"66156","inReplyTo":"arTUNYVvCNwX1pDp@szeder.dev","subject":"Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-24T16:22:39Z","receivedAt":"2026-09-24T16:22:41Z","isPatch":true,"body":"SZEDER Gábor <szeder.dev@gmail.com> writes:\n\n>   - Checking for the error message printed after curl_easy_setopt(...,\n>     CURLOPT_SSL_VERIFYSTATUS, ...) returns with error is indeed the\n>     right thing to do.\n>\n>     However, in that new error message the second half is much more\n>     informative than the first, and if the prereq looked for the\n>     absence of \"could not enable OCSP status verification\" instead of\n>     \"http.sslVerifyStatus is set\", then I think I would have realized\n>     all this sooner.  Perhaps calling the prereq CURL_SSL_VERIFYSTATUS\n>     would have helped, too.\n\nYeah, both are understandable.\n\nThanks.\n"},{"id":"553270","messageId":"arY+2p3YZWlyL9Gq@szeder.dev","threadId":"66156","inReplyTo":"arTYVLnW-2GHpGGm@pks.im","subject":"Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2026-09-25T09:28:58Z","receivedAt":"2026-09-25T09:29:01Z","isPatch":true,"body":"On Thu, Sep 24, 2026 at 09:59:16AM +0200, Patrick Steinhardt wrote:\n> On Thu, Sep 24, 2026 at 09:41:41AM +0200, SZEDER Gábor wrote:\n> > On Wed, Sep 23, 2026 at 02:47:18PM -0700, Junio C Hamano wrote:\n> > > SZEDER Gábor <szeder.dev@gmail.com> writes:\n> > > \n> > > > On Tue, Sep 15, 2026 at 12:23:48PM -0400, graysongordon-gl wrote:\n> > > >> From: Grayson Gordon <graysongordon1@gmail.com>\n> > > >> \n> > > >> git never sets CURLOPT_SSL_VERIFYSTATUS, so libcurl never requests the\n> > > >> OCSP \"Certificate Status Request\" extension and any stapled response a\n> > > >> server sends is ignored, including responses that explicitly state the\n> > > >> certificate has been revoked.\n> > > > ...\n> > > > This patch was merged to 'next' the other day, and the last test in\n> > > > the new t5585 fails on my system.\n> > > \n> > > Sorry about a premature merge.  Since we are not in a hurry to take\n> > > this topic in (or no new feature topic in general), let me revert it\n> > > out of 'next' and give it a clean slate to try again.\n> > \n> > Well, if you hadn't merged it, we would perhaps still be none the\n> > wiser, because, alas, I don't have the bandwidth to run tests on the\n> > seen branch regularly...\n> > \n> > However, CI does, but I can't seem to find any CI runs that failed\n> > because of this, which makes me worried that something is wrong on my\n> > end.\n> \n> Do you maybe run with a curl backend that doesn't properly support OCSP?\n> But even if so, our test suite should notice and skip the tests.\n\nApparently I did!  Removing 'libcurl4-gnutls-dev' and installing\n'libcurl4-openssl-dev' instead makes t5585 succeed.  Go figure.\n\nThanks for the hint!\n\n"},{"id":"553291","messageId":"xmqqtsndwbpw.fsf@gitster.g","threadId":"66156","inReplyTo":"arY+2p3YZWlyL9Gq@szeder.dev","subject":"Re: [PATCH v7] http: add http.sslVerifyStatus to check stapled OCSP responses","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-25T16:13:31Z","receivedAt":"2026-09-25T16:13:34Z","isPatch":true,"body":"SZEDER Gábor <szeder.dev@gmail.com> writes:\n\n>> Do you maybe run with a curl backend that doesn't properly support OCSP?\n>> But even if so, our test suite should notice and skip the tests.\n>\n> Apparently I did!  Removing 'libcurl4-gnutls-dev' and installing\n> 'libcurl4-openssl-dev' instead makes t5585 succeed.  Go figure.\n>\n> Thanks for the hint!\n\nThanks for collectively digging down the cause of the issue to (1)\nhelp your set-up to pass the test, and (2) point out that the\nprerequisite setting needs to be improved.\n\n"}]}