credential: advertise NTLM suppression and allow helpers to re-enable

The previous commits disabled NTLM authentication by default due to its
cryptographic weaknesses. Users can re-enable it via the config setting
http.<url>.allowNTLMAuth, but this requires manual intervention.

Credential helpers may have knowledge about which servers are trusted
for NTLM authentication (e.g., known on-prem Azure DevOps instances).
To allow them to signal this trust, introduce a simple negotiation:
when NTLM is suppressed and the server offered it, Git advertises
ntlm=suppressed to the credential helper. The helper can respond with
ntlm=allow to re-enable NTLM for this request.

This happens precisely at the point where we would otherwise warn the
user about NTLM being suppressed, ensuring the capability is only
advertised when relevant.

Helped-by: Matthew John Cheetham <mjcheetham@outlook.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
This commit is contained in:
Johannes Schindelin
2026-02-09 18:21:48 +01:00
parent b24f37778a
commit 816db62d10
4 changed files with 32 additions and 3 deletions

View File

@@ -360,6 +360,9 @@ int credential_read(struct credential *c, FILE *fp,
credential_set_capability(&c->capa_authtype, op_type);
else if (!strcmp(value, "state"))
credential_set_capability(&c->capa_state, op_type);
} else if (!strcmp(key, "ntlm")) {
if (!strcmp(value, "allow"))
c->ntlm_allow = 1;
} else if (!strcmp(key, "continue")) {
c->multistage = !!git_config_bool("continue", value);
} else if (!strcmp(key, "password_expiry_utc")) {
@@ -420,6 +423,8 @@ void credential_write(const struct credential *c, FILE *fp,
if (c->ephemeral)
credential_write_item(c, fp, "ephemeral", "1", 0);
}
if (c->ntlm_suppressed)
credential_write_item(c, fp, "ntlm", "suppressed", 0);
credential_write_item(c, fp, "protocol", c->protocol, 1);
credential_write_item(c, fp, "host", c->host, 1);
credential_write_item(c, fp, "path", c->path, 0);

View File

@@ -177,6 +177,9 @@ struct credential {
struct credential_capability capa_authtype;
struct credential_capability capa_state;
unsigned ntlm_suppressed:1,
ntlm_allow:1;
char *username;
char *password;
char *credential;

14
http.c
View File

@@ -638,6 +638,11 @@ static void init_curl_http_auth(CURL *result)
credential_fill(the_repository, &http_auth, 1);
if (http_auth.ntlm_allow && !(http_auth_methods & CURLAUTH_NTLM)) {
http_auth_methods |= CURLAUTH_NTLM;
curl_easy_setopt(result, CURLOPT_HTTPAUTH, http_auth_methods);
}
if (http_auth.password) {
if (always_auth_proactively()) {
/*
@@ -1865,6 +1870,12 @@ static int handle_curl_result(struct slot_results *results)
} else if (missing_target(results))
return HTTP_MISSING_TARGET;
else if (results->http_code == 401) {
http_auth.ntlm_suppressed = (results->auth_avail & CURLAUTH_NTLM) &&
!(http_auth_any & CURLAUTH_NTLM);
if (http_auth.ntlm_suppressed && http_auth.ntlm_allow) {
http_auth_methods |= CURLAUTH_NTLM;
return HTTP_REAUTH;
}
if ((http_auth.username && http_auth.password) ||\
(http_auth.authtype && http_auth.credential)) {
if (http_auth.multistage) {
@@ -1874,8 +1885,7 @@ static int handle_curl_result(struct slot_results *results)
credential_reject(the_repository, &http_auth);
if (always_auth_proactively())
http_proactive_auth = PROACTIVE_AUTH_NONE;
if ((results->auth_avail & CURLAUTH_NTLM) &&
!(http_auth_any & CURLAUTH_NTLM)) {
if (http_auth.ntlm_suppressed) {
warning(_("Due to its cryptographic weaknesses, "
"NTLM authentication has been\n"
"disabled in Git by default. You can "

View File

@@ -688,8 +688,19 @@ test_expect_success NTLM 'access using NTLM auth' '
test_must_fail env GIT_TRACE_CURL=1 git \
ls-remote "$HTTPD_URL/ntlm_auth/repo.git" 2>err &&
test_grep "allowNTLMAuth" err &&
# Can be enabled via config
GIT_TRACE_CURL=1 git -c http.$HTTPD_URL.allowNTLMAuth=true \
ls-remote "$HTTPD_URL/ntlm_auth/repo.git"
ls-remote "$HTTPD_URL/ntlm_auth/repo.git" &&
# Or via credential helper responding with ntlm=allow
set_credential_reply get <<-EOF &&
username=user
password=pwd
ntlm=allow
EOF
git ls-remote "$HTTPD_URL/ntlm_auth/repo.git"
'
test_done