Merge branch 'gg/http-ssl-verify-status' into seen
The HTTP transport has been taught to check the revocation status of the server certificate using the stapled OCSP response during the TLS handshake via a new 'http.sslVerifyStatus' configuration variable. * gg/http-ssl-verify-status: http: add http.sslVerifyStatus to check stapled OCSP responses
commit
2c47028e63
|
|
@ -196,6 +196,20 @@ http.sslVerify::
|
|||
over HTTPS. Defaults to true. Can be overridden by the
|
||||
`GIT_SSL_NO_VERIFY` environment variable.
|
||||
|
||||
http.sslVerifyStatus::
|
||||
Whether to check the revocation status of the server
|
||||
certificate using the stapled OCSP response supplied during
|
||||
the TLS handshake ("OCSP stapling"). Defaults to false, which
|
||||
allows connections to servers without validating if the
|
||||
certificate has been revoked by the certificate authority.
|
||||
Enabling this option will prevent connections to servers that
|
||||
have a certificate status other than "good" per RFC 6960.
|
||||
Connections to servers that do not return a stapled response
|
||||
will also be refused.
|
||||
+
|
||||
Set it per remote, e.g.
|
||||
`http.https://example.com/.sslVerifyStatus`, rather than globally.
|
||||
|
||||
http.sslCert::
|
||||
File containing the SSL certificate when fetching or pushing
|
||||
over HTTPS. Can be overridden by the `GIT_SSL_CERT` environment
|
||||
|
|
|
|||
14
http.c
14
http.c
|
|
@ -44,6 +44,7 @@ static CURL *curl_default;
|
|||
char curl_errorstr[CURL_ERROR_SIZE];
|
||||
|
||||
static int curl_ssl_verify = -1;
|
||||
static int curl_ssl_verify_status;
|
||||
static int curl_ssl_try;
|
||||
static char *curl_http_version;
|
||||
static char *ssl_cert;
|
||||
|
|
@ -400,6 +401,10 @@ static int http_options(const char *var, const char *value,
|
|||
curl_ssl_verify = git_config_bool(var, value);
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp("http.sslverifystatus", var)) {
|
||||
curl_ssl_verify_status = git_config_bool(var, value);
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp("http.sslcipherlist", var))
|
||||
return git_config_string(&ssl_cipherlist, var, value);
|
||||
if (!strcmp("http.sslversion", var))
|
||||
|
|
@ -1133,6 +1138,15 @@ static CURL *get_curl_handle(void)
|
|||
curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2L);
|
||||
}
|
||||
|
||||
if (curl_ssl_verify_status) {
|
||||
CURLcode ret = curl_easy_setopt(result,
|
||||
CURLOPT_SSL_VERIFYSTATUS, 1L);
|
||||
if (ret != CURLE_OK)
|
||||
die(_("http.sslVerifyStatus is set, but could not "
|
||||
"enable OCSP status verification: %s"),
|
||||
curl_easy_strerror(ret));
|
||||
}
|
||||
|
||||
if (curl_http_version) {
|
||||
long opt;
|
||||
if (!get_curl_http_version_opt(curl_http_version, &opt)) {
|
||||
|
|
|
|||
130
t/lib-httpd.sh
130
t/lib-httpd.sh
|
|
@ -25,6 +25,7 @@
|
|||
# LIB_HTTPD_DAV enable DAV
|
||||
# LIB_HTTPD_SVN enable SVN at given location (e.g. "svn")
|
||||
# LIB_HTTPD_SSL enable SSL
|
||||
# LIB_HTTPD_OCSP enable OCSP stapling
|
||||
# LIB_HTTPD_PROXY enable proxy
|
||||
#
|
||||
# Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at>
|
||||
|
|
@ -183,15 +184,26 @@ prepare_httpd() {
|
|||
|
||||
ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules"
|
||||
|
||||
if test -n "$LIB_HTTPD_OCSP"
|
||||
then
|
||||
LIB_HTTPD_SSL=t
|
||||
fi
|
||||
|
||||
if test -n "$LIB_HTTPD_SSL"
|
||||
then
|
||||
HTTPD_PROTO=https
|
||||
|
||||
RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \
|
||||
-config "$TEST_PATH/ssl.cnf" \
|
||||
-new -x509 -nodes \
|
||||
-out "$HTTPD_ROOT_PATH/httpd.pem" \
|
||||
-keyout "$HTTPD_ROOT_PATH/httpd.pem"
|
||||
if test -n "$LIB_HTTPD_OCSP"
|
||||
then
|
||||
prepare_ocsp_stapling
|
||||
HTTPD_PARA="$HTTPD_PARA -DOCSP"
|
||||
else
|
||||
RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \
|
||||
-config "$TEST_PATH/ssl.cnf" \
|
||||
-new -x509 -nodes \
|
||||
-out "$HTTPD_ROOT_PATH/httpd.pem" \
|
||||
-keyout "$HTTPD_ROOT_PATH/httpd.pem"
|
||||
fi
|
||||
GIT_SSL_NO_VERIFY=t
|
||||
export GIT_SSL_NO_VERIFY
|
||||
HTTPD_PARA="$HTTPD_PARA -DSSL"
|
||||
|
|
@ -262,6 +274,114 @@ stop_httpd() {
|
|||
-f "$TEST_PATH/apache.conf" $HTTPD_PARA -k stop
|
||||
}
|
||||
|
||||
restart_httpd () {
|
||||
httpd_pid=$(cat "$HTTPD_ROOT_PATH/httpd.pid") &&
|
||||
stop_httpd &&
|
||||
while kill -0 "$httpd_pid" 2>/dev/null
|
||||
do
|
||||
sleep 1
|
||||
done &&
|
||||
"$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \
|
||||
-f "$TEST_PATH/apache.conf" $HTTPD_PARA \
|
||||
-c "Listen 127.0.0.1:$LIB_HTTPD_PORT" -k start
|
||||
}
|
||||
|
||||
# Check if the linked libcurl can verify stapled OCSP responses.
|
||||
test_lazy_prereq SSL_VERIFYSTATUS '
|
||||
test "$HTTPD_PROTO" = "https" &&
|
||||
test_might_fail git -c http.sslVerifyStatus=true \
|
||||
ls-remote "$HTTPD_URL" 2>err &&
|
||||
! grep "http.sslVerifyStatus is set" err
|
||||
'
|
||||
|
||||
# Set up a certificate authority. It issues certificate "httpd.pem"
|
||||
# and is able to revoke it. Used instead of the self-signed
|
||||
# certificate when LIB_HTTPD_OCSP is set.
|
||||
prepare_ocsp_stapling () {
|
||||
LIB_HTTPD_OCSP_PORT=$((LIB_HTTPD_PORT + 10000))
|
||||
|
||||
# Referenced by ocsp-ca.cnf.
|
||||
OCSP_CA_DIR="$HTTPD_ROOT_PATH/ocsp-ca"
|
||||
OCSP_URI="http://127.0.0.1:$LIB_HTTPD_OCSP_PORT"
|
||||
export OCSP_CA_DIR OCSP_URI
|
||||
|
||||
mkdir -p "$OCSP_CA_DIR/newcerts" &&
|
||||
>"$OCSP_CA_DIR/index.txt" &&
|
||||
echo 1000 >"$OCSP_CA_DIR/serial" &&
|
||||
|
||||
openssl req -config "$TEST_PATH/ocsp-ca.cnf" \
|
||||
-new -x509 -nodes -days 2 \
|
||||
-subj "/CN=git-test-ca" -extensions v3_ca \
|
||||
-keyout "$HTTPD_ROOT_PATH/ca.key" \
|
||||
-out "$HTTPD_ROOT_PATH/ca.pem" &&
|
||||
openssl req -config "$TEST_PATH/ocsp-ca.cnf" \
|
||||
-new -nodes \
|
||||
-subj "/CN=127.0.0.1" \
|
||||
-keyout "$HTTPD_ROOT_PATH/httpd.key" \
|
||||
-out "$HTTPD_ROOT_PATH/httpd.csr" &&
|
||||
openssl ca -config "$TEST_PATH/ocsp-ca.cnf" -batch \
|
||||
-cert "$HTTPD_ROOT_PATH/ca.pem" \
|
||||
-keyfile "$HTTPD_ROOT_PATH/ca.key" \
|
||||
-in "$HTTPD_ROOT_PATH/httpd.csr" \
|
||||
-out "$HTTPD_ROOT_PATH/httpd.crt" &&
|
||||
cat "$HTTPD_ROOT_PATH/httpd.key" "$HTTPD_ROOT_PATH/httpd.crt" \
|
||||
>"$HTTPD_ROOT_PATH/httpd.pem"
|
||||
}
|
||||
|
||||
run_ocsp_responder () {
|
||||
openssl ocsp -port "$LIB_HTTPD_OCSP_PORT" \
|
||||
-index "$OCSP_CA_DIR/index.txt" \
|
||||
-CA "$HTTPD_ROOT_PATH/ca.pem" \
|
||||
-rsigner "$HTTPD_ROOT_PATH/ca.pem" \
|
||||
-rkey "$HTTPD_ROOT_PATH/ca.key" \
|
||||
-nmin 60 >>"$HTTPD_ROOT_PATH/ocsp.log" 2>&1 &
|
||||
echo $! >"$HTTPD_ROOT_PATH/ocsp.pid"
|
||||
|
||||
for i in $(test_seq 1 10)
|
||||
do
|
||||
if openssl ocsp -no_nonce \
|
||||
-CAfile "$HTTPD_ROOT_PATH/ca.pem" \
|
||||
-issuer "$HTTPD_ROOT_PATH/ca.pem" \
|
||||
-cert "$HTTPD_ROOT_PATH/httpd.crt" \
|
||||
-url "$OCSP_URI" >/dev/null 2>&1
|
||||
then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
start_ocsp_responder () {
|
||||
test_atexit stop_ocsp_responder
|
||||
|
||||
if ! run_ocsp_responder
|
||||
then
|
||||
cat "$HTTPD_ROOT_PATH"/ocsp.log >&4 2>/dev/null
|
||||
test_skip_or_die GIT_TEST_HTTPD "OCSP responder setup failed"
|
||||
fi
|
||||
}
|
||||
|
||||
stop_ocsp_responder () {
|
||||
if test -f "$HTTPD_ROOT_PATH/ocsp.pid"
|
||||
then
|
||||
kill "$(cat "$HTTPD_ROOT_PATH/ocsp.pid")" 2>/dev/null
|
||||
rm -f "$HTTPD_ROOT_PATH/ocsp.pid"
|
||||
fi
|
||||
}
|
||||
|
||||
# Revoke the certificate used by httpd and make both the OCSP responder
|
||||
# and httpd aware of it.
|
||||
revoke_httpd_cert () {
|
||||
openssl ca -config "$TEST_PATH/ocsp-ca.cnf" \
|
||||
-cert "$HTTPD_ROOT_PATH/ca.pem" \
|
||||
-keyfile "$HTTPD_ROOT_PATH/ca.key" \
|
||||
-revoke "$HTTPD_ROOT_PATH/httpd.crt" &&
|
||||
stop_ocsp_responder &&
|
||||
run_ocsp_responder &&
|
||||
restart_httpd
|
||||
}
|
||||
|
||||
test_http_push_nonff () {
|
||||
REMOTE_REPO=$1
|
||||
LOCAL_REPO=$2
|
||||
|
|
|
|||
|
|
@ -243,6 +243,22 @@ SSLSessionCache none
|
|||
SSLEngine On
|
||||
</IfDefine>
|
||||
|
||||
<IfDefine OCSP>
|
||||
<IfModule !mod_socache_shmcb.c>
|
||||
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
|
||||
</IfModule>
|
||||
|
||||
SSLCertificateChainFile ca.pem
|
||||
SSLUseStapling On
|
||||
# Stapling needs a mutex, which apache would put in a system-wide
|
||||
# runtime directory that need not be writable. Keep it in the server
|
||||
# root, or httpd refuses to start instead of skipping the tests.
|
||||
DefaultRuntimeDir .
|
||||
SSLStaplingCache shmcb:ssl_stapling(65536)
|
||||
# Staple non-"good" responses too, so clients get to see "revoked".
|
||||
SSLStaplingReturnResponderErrors On
|
||||
</IfDefine>
|
||||
|
||||
<Location /auth/>
|
||||
AuthType Basic
|
||||
AuthName "git-auth"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
[ ca ]
|
||||
default_ca = CA_default
|
||||
|
||||
[ CA_default ]
|
||||
dir = $ENV::OCSP_CA_DIR
|
||||
database = $dir/index.txt
|
||||
new_certs_dir = $dir/newcerts
|
||||
serial = $dir/serial
|
||||
default_md = sha256
|
||||
default_days = 2
|
||||
policy = policy_anything
|
||||
email_in_dn = no
|
||||
unique_subject = no
|
||||
x509_extensions = server_cert
|
||||
|
||||
[ policy_anything ]
|
||||
commonName = supplied
|
||||
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
distinguished_name = req_distinguished_name
|
||||
prompt = no
|
||||
|
||||
[ req_distinguished_name ]
|
||||
# The subject is always given on the command line via -subj.
|
||||
|
||||
[ v3_ca ]
|
||||
basicConstraints = critical, CA:TRUE
|
||||
keyUsage = critical, digitalSignature, keyCertSign, cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
|
||||
[ server_cert ]
|
||||
basicConstraints = CA:FALSE
|
||||
subjectAltName = IP:127.0.0.1
|
||||
authorityInfoAccess = OCSP;URI:$ENV::OCSP_URI
|
||||
|
|
@ -730,6 +730,7 @@ integration_tests = [
|
|||
't5582-fetch-negative-refspec.sh',
|
||||
't5583-push-branches.sh',
|
||||
't5584-http-429-retry.sh',
|
||||
't5585-http-ssl-ocsp.sh',
|
||||
't5600-clone-fail-cleanup.sh',
|
||||
't5601-clone.sh',
|
||||
't5602-clone-remote-exec.sh',
|
||||
|
|
|
|||
|
|
@ -680,6 +680,28 @@ test_expect_success 'passing hostname resolution information works' '
|
|||
git -c "http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1" ls-remote "$BOGUS_HTTPD_URL/smart/repo.git" >/dev/null
|
||||
'
|
||||
|
||||
test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=true fails without a staple' '
|
||||
test_must_fail git -c http.sslVerifyStatus=true \
|
||||
ls-remote "$HTTPD_URL/smart/repo.git"
|
||||
'
|
||||
|
||||
test_expect_success SSL_VERIFYSTATUS 'http.sslVerifyStatus=false is a no-op' '
|
||||
git -c http.sslVerifyStatus=false \
|
||||
ls-remote "$HTTPD_URL/smart/repo.git" >actual &&
|
||||
test_line_count -gt 0 actual
|
||||
'
|
||||
|
||||
test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus applies to a matching URL' '
|
||||
test_must_fail git -c "http.$HTTPD_URL/.sslVerifyStatus=true" \
|
||||
ls-remote "$HTTPD_URL/smart/repo.git"
|
||||
'
|
||||
|
||||
test_expect_success SSL_VERIFYSTATUS 'per-URL sslVerifyStatus is not applied to other URLs' '
|
||||
git -c "http.https://example.com/.sslVerifyStatus=true" \
|
||||
ls-remote "$HTTPD_URL/smart/repo.git" >actual &&
|
||||
test_line_count -gt 0 actual
|
||||
'
|
||||
|
||||
# here user%40host is the URL-encoded version of user@host,
|
||||
# which is our intentionally-odd username to catch parsing errors
|
||||
url_user=$HTTPD_URL_USER/auth/smart/repo.git
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='verification of stapled OCSP responses via http.sslVerifyStatus'
|
||||
|
||||
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
|
||||
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
LIB_HTTPD_OCSP=1
|
||||
. "$TEST_DIRECTORY"/lib-httpd.sh
|
||||
|
||||
start_httpd
|
||||
start_ocsp_responder
|
||||
|
||||
test_expect_success 'setup repository' '
|
||||
test_commit one &&
|
||||
git init --bare "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
|
||||
git push "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" HEAD:refs/heads/main
|
||||
'
|
||||
|
||||
# lib-httpd.sh exports GIT_SSL_NO_VERIFY, which would keep us from ever
|
||||
# looking at the certificate. Trust our own CA instead.
|
||||
with_ssl_verification () {
|
||||
(
|
||||
sane_unset GIT_SSL_NO_VERIFY &&
|
||||
GIT_SSL_CAINFO="$HTTPD_ROOT_PATH/ca.pem" "$@"
|
||||
)
|
||||
}
|
||||
|
||||
test_expect_success SSL_VERIFYSTATUS 'certificate verification works against test CA' '
|
||||
with_ssl_verification git ls-remote "$HTTPD_URL/smart/repo.git" >actual &&
|
||||
test_line_count -gt 0 actual
|
||||
'
|
||||
|
||||
test_expect_success SSL_VERIFYSTATUS 'fetch succeeds with stapled "good" OCSP response' '
|
||||
with_ssl_verification git -c http.sslVerifyStatus=true \
|
||||
ls-remote "$HTTPD_URL/smart/repo.git" >actual &&
|
||||
test_line_count -gt 0 actual
|
||||
'
|
||||
|
||||
test_expect_success SSL_VERIFYSTATUS 'revoked certificate is rejected' '
|
||||
revoke_httpd_cert &&
|
||||
with_ssl_verification test_must_fail git -c http.sslVerifyStatus=true \
|
||||
ls-remote "$HTTPD_URL/smart/repo.git" 2>err &&
|
||||
test_grep -i -e "ocsp" -e "revocation" -e "revoked" -e "certificate status" err
|
||||
'
|
||||
|
||||
# Depends on the certificate revoked by the preceding test.
|
||||
test_expect_success SSL_VERIFYSTATUS 'revoked certificate is accepted without http.sslVerifyStatus' '
|
||||
with_ssl_verification git ls-remote "$HTTPD_URL/smart/repo.git" >actual &&
|
||||
test_line_count -gt 0 actual
|
||||
'
|
||||
|
||||
test_done
|
||||
Loading…
Reference in New Issue