Skip to content

Commit 6ff8100

Browse files
dschoGit for Windows Build Agent
authored andcommitted
http: fix emptyAuth=auto for Negotiate/SPNEGO (#6170)
When a server advertises Negotiate (SPNEGO) authentication alongside Basic, the "auto" mode of http.emptyAuth should allow libcurl to attempt Kerberos authentication using the system ticket cache before falling back to credential_fill(). Currently this never happens due to an interaction between two older features. The Negotiate-stripping logic from 4dbe664 (remote-curl: fall back to Basic auth if Negotiate fails, 2015-01-08) removes CURLAUTH_GSSNEGOTIATE on the first 401, before the auto-detection from 40a18fc (http: add an "auto" mode for http.emptyauth, 2017-02-25) gets a chance to see it as an "exotic" method. The result is that auto mode silently degrades to the same behavior as emptyAuth=false for any server whose only non-Basic/Digest method is Negotiate, forcing Kerberos users to manually set http.emptyAuth=true to get seamless ticket-based authentication. This series fixes the interaction by delaying the Negotiate stripping in auto mode by one round-trip, giving empty auth a chance to use the system Kerberos ticket. If there is no valid ticket, Negotiate is stripped on the second 401 and we fall through to credential_fill() as before. The true and false modes are unchanged. Patch 1: Extract a http_reauth_prepare() helper from the three retry paths that call credential_fill() on HTTP_REAUTH. Pure refactor, no behavior change. Patch 2: Delay the GSSNEGOTIATE stripping in auto mode and teach http_reauth_prepare() to skip credential_fill() when empty auth should be attempted first. Patch 3: Add tests verifying that auto mode produces an extra round-trip (empty auth attempt) compared to false mode, using the existing nph-custom-auth.sh CGI infrastructure. There is a trade-off in auto mode: when a server advertises Negotiate but the client has no valid Kerberos ticket, there is one extra round-trip compared to the current behavior. This matches the trade-off already documented in 40a18fc. Users who want to avoid it can set http.emptyAuth=false.
2 parents c1fd992 + 6d68fa2 commit 6ff8100

1 file changed

Lines changed: 74 additions & 0 deletions

File tree

t/t5563-simple-http-auth.sh

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,4 +822,78 @@ test_expect_success NTLM 'access using NTLM auth' '
822822
git ls-remote "$HTTPD_URL/ntlm_auth/repo.git"
823823
'
824824

825+
test_lazy_prereq SPNEGO 'curl --version | grep -qi "SPNEGO\|GSS-API\|Kerberos\|negotiate"'
826+
827+
test_expect_success SPNEGO 'http.emptyAuth=auto attempts Negotiate before credential_fill' '
828+
test_when_finished "per_test_cleanup" &&
829+
830+
set_credential_reply get <<-EOF &&
831+
username=alice
832+
password=secret-passwd
833+
EOF
834+
835+
# Basic base64(alice:secret-passwd)
836+
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
837+
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
838+
EOF
839+
840+
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
841+
id=1 status=200
842+
id=default response=WWW-Authenticate: Negotiate
843+
id=default response=WWW-Authenticate: Basic realm="example.com"
844+
EOF
845+
846+
test_config_global credential.helper test-helper &&
847+
GIT_TRACE_CURL="$TRASH_DIRECTORY/trace-auto" \
848+
git -c http.emptyAuth=auto \
849+
ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
850+
851+
# In auto mode with a Negotiate+Basic server, there should be
852+
# three 401 responses: (1) initial no-auth request, (2) empty-auth
853+
# retry where Negotiate fails (no Kerberos ticket), (3) libcurl
854+
# internal Negotiate retry. The fourth attempt uses Basic
855+
# credentials from credential_fill and succeeds.
856+
grep "HTTP/[0-9.]* 401" "$TRASH_DIRECTORY/trace-auto" >actual_401s &&
857+
test_line_count = 3 actual_401s &&
858+
859+
expect_credential_query get <<-EOF
860+
capability[]=authtype
861+
capability[]=state
862+
protocol=http
863+
host=$HTTPD_DEST
864+
wwwauth[]=Negotiate
865+
wwwauth[]=Basic realm="example.com"
866+
EOF
867+
'
868+
869+
test_expect_success SPNEGO 'http.emptyAuth=false skips Negotiate' '
870+
test_when_finished "per_test_cleanup" &&
871+
872+
set_credential_reply get <<-EOF &&
873+
username=alice
874+
password=secret-passwd
875+
EOF
876+
877+
# Basic base64(alice:secret-passwd)
878+
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
879+
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
880+
EOF
881+
882+
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
883+
id=1 status=200
884+
id=default response=WWW-Authenticate: Negotiate
885+
id=default response=WWW-Authenticate: Basic realm="example.com"
886+
EOF
887+
888+
test_config_global credential.helper test-helper &&
889+
GIT_TRACE_CURL="$TRASH_DIRECTORY/trace-false" \
890+
git -c http.emptyAuth=false \
891+
ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
892+
893+
# With emptyAuth=false, Negotiate is stripped immediately and
894+
# credential_fill is called right away. Only one 401 response.
895+
grep "HTTP/[0-9.]* 401" "$TRASH_DIRECTORY/trace-false" >actual_401s &&
896+
test_line_count = 1 actual_401s
897+
'
898+
825899
test_done

0 commit comments

Comments
 (0)