Maven Repo DIGEST authentication fails in M8/M8a/M9

Upgrading from M6 to M8-9 results in me not being able to download from my Maven Repo. Error: Received status code 401 from server: Authorization Required The http headers seem exactly the same in the debug log between m6 and m9. Basic Auth seems to work. Is there something going on with Digest auth that has changed from M6 in the work that has been done for all the cache refresh/offline/NTLM? As we currently can’t change from digest to basic on our repo we are blocked from upgrading. Here is a debug fragment which all looks ok and is exactly the same between 6 and 8a 10:15:55.818 [DEBUG] [org.apache.http.impl.client.ContentEncodingHttpClient] Connection can be kept alive for 15000 MILLISECONDS 10:15:55.818 [DEBUG] [org.apache.http.impl.client.ContentEncodingHttpClient] Target requested authentication 10:15:55.819 [DEBUG] [org.apache.http.impl.client.ContentEncodingHttpClient] Authorization challenge processed 10:15:55.819 [DEBUG] [org.apache.http.impl.client.ContentEncodingHttpClient] Authentication scope: DIGEST ‘Maven’@maven.mycompany.com:443 10:15:55.819 [DEBUG] [org.apache.http.impl.client.ContentEncodingHttpClient] Authentication failed 10:15:55.820 [DEBUG] [org.apache.http.impl.conn.SingleClientConnManager] Releasing connection org.apache.http.impl.conn.SingleClientConnManager$ConnAdapter@3f677737 10:15:55.820 [INFO] [org.gradle.api.internal.artifacts.repositories.transport.http.HttpResourceCollection] Request for checksum at https://myrepo.com/repositories/internal/oracle/ojdbc6/11.2.0.1.0/ojdbc6-11.2.0.1.0.pom.sha1 failed: HTTP/1.1 401 Authorization Required 10:15:55.820 [INFO] [org.gradle.api.internal.artifacts.repositories.transport.http.HttpResourceCollection] Checksum SHA-1 unavailable. [HTTP GET: https://myrepo.com/repositories/internal/oracle/ojdbc6/11.2.0.1.0/ojdbc6-11.2.0.1.0.pom.sha1] 10:15:55.821 [DEBUG] [org.gradle.api.internal.artifacts.repositories.transport.http.HttpResourceCollection] Performing HTTP GET: https://myrepo.com/repositories/internal/oracle/ojdbc6/11.2.0.1.0/ojdbc6-11.2.0.1.0.pom 10:15:55.821 [DEBUG] [org.apache.http.impl.conn.SingleClientConnManager] Get connection for route HttpRoute[{s}->https://myrepo.com] 10:15:55.822 [DEBUG] [org.apache.http.impl.client.ContentEncodingHttpClient] Stale connection check 10:15:55.823 [DEBUG] [org.apache.http.client.protocol.RequestAddCookies] CookieSpec selected: best-match 10:15:55.824 [DEBUG] [org.apache.http.client.protocol.RequestAuthCache] Re-using cached ‘digest’ auth scheme for https://myrepo.com 10:15:55.824 [DEBUG] [org.apache.http.impl.client.ContentEncodingHttpClient] Attempt 1 to execute request 10:15:55.824 [DEBUG] [org.apache.http.impl.conn.DefaultClientConnection] Sending request: GET /repositories/internal/oracle/ojdbc6/11.2.0.1.0/ojdbc6-11.2.0.1.0.pom HTTP/1.1 10:15:55.825 [DEBUG] [org.apache.http.headers] >> GET /repositories/internal/oracle/ojdbc6/11.2.0.1.0/ojdbc6-11.2.0.1.0.pom HTTP/1.1 10:15:55.825 [DEBUG] [org.apache.http.headers] >> User-Agent: Gradle/1.0-milestone-8a 10:15:55.825 [DEBUG] [org.apache.http.headers] >> Authorization: Basic bWF2ZW4sKA5FMDlsNTBETDZuM1Z0Sw== 10:15:55.826 [DEBUG] [org.apache.http.headers] >> Host: myrepo.com 10:15:55.826 [DEBUG] [org.apache.http.headers] >> Connection: Keep-Alive 10:15:55.826 [DEBUG] [org.apache.http.headers] >> Accept-Encoding: gzip,deflate 10:15:55.838 [DEBUG] [org.apache.http.impl.conn.DefaultClientConnection] Receiving response: HTTP/1.1 401 Authorization Required 10:15:55.838 [DEBUG] [org.apache.http.headers] << HTTP/1.1 401 Authorization Required 10:15:55.839 [DEBUG] [org.apache.http.headers] << Date: Wed, 21 Mar 2012 10:15:44 GMT 10:15:55.839 [DEBUG] [org.apache.http.headers] << Server: Apache 10:15:55.839 [DEBUG] [org.apache.http.headers] << WWW-Authenticate: Digest realm=“Maven”, nonce="+coDE767BAA=b50a801debdcb202ac6dcf2ed6847acb85b304d40", algorithm=MD5, domain=“http://myrepo.com https://myrepo.com”, qop=“auth” 10:15:55.840 [DEBUG] [org.apache.http.headers] << Content-Length: 476 10:15:55.840 [DEBUG] [org.apache.http.headers] << Keep-Alive: timeout=15, max=89 10:15:55.840 [DEBUG] [org.apache.http.headers] << Connection: Keep-Alive 10:15:55.841 [DEBUG] [org.apache.http.headers] << Content-Type: text/html; charset=iso-8859-1 Any help appreciated

Between M7 and M8 we upgraded from Apache HttpClient 3.1 -> 4.1. While it brought some goodness, this was a much bigger change than originally anticipated, and it has resulted in a number of regressions.

Can you please send the exception you see when running with --stacktrace?

I have also encountered the problem that Warren describes. I access an internal authenticated Maven repository that is configured to use HTTP Digest Authentication. Prior to 1.0M7 when the switch was made to use the Apache httpclient libraries I was able to access this repository without any problems using the credentials {…} DSL that was added in 1.0M6 (and prior to that by using the clunkier org.apache.ivy.util.url.CredentialsStore method)

Maven repositories using no HTTP authentication or Basic HTTP authentication work fine, but Digest authentication fails. (I haven’t tested the NTLM support that was recently added)

I’ve created an issue in JIRA detailing the problem along with the root cause and a suggested fix http://issues.gradle.org/browse/GRADLE-2191

The problem arises due to an attempt to perform pre-emptive authentication which forces the Apache HTTPClient library to always use Basic auth - irrespective of what auth type the server has requested.

Here’s what the Gradle --> Server request <-> response trace SHOULD look like…

Gradle : HTTP GET /some/repository/resource Server : HTTP 401 - Not Authorized. Please use DIGEST Authentication.

Gradle : Server want authentication and has asked for DIGEST Auth Gradle : I have credentials and will retry the request with DIGEST auth Gradle : HTTP GET /some/reposiotory/resource - DIGEST Auth credentals=foo Server : HTTP 200 - Here’s your resource. Kthxbye.

Here’s what ACTUALLY happens…

Gradle : HTTP GET /some/repository/resource Server : HTTP 401 - Not Authorized. Please use DIGEST Authentication.

Gradle : Server want authentication and has asked for DIGEST Auth Gradle : I have credentials and will retry the request with DIGEST auth Gradle : HTTP GET /some/reposiotory/resource - BASIC Auth credentals=foo Server : HTTP 401 - Not Authorized. Please use DIGEST Authentication.

The org.gradle.api.internal.artifacts.repositories.transport.http.HttpClientConfigurer has code that sets up a pre-emptive authentication header using BASIC auth if any credentials are found. This then overrides the Apache HTTPClient’s handling of the server response where it recognises that DIGEST auth has been requested.

Pre-emptive HTTP authentication is generally not recommended, and the HTTPClient docs explicitly state that they don’t support pre-emptive auth for this very reason.

http://hc.apache.org/httpcomponents-client-ga/tutorial/html/authentication.html The pre-emptive sending of a user’s credentials via BASIC auth poses a security risk for those that have configured servers to use the more secure DIGEST auth.

My suggested fix is to remove the pre-emptive auth code and allow the Apache HTTPClient library to negotiate the authentication challenge/response. Only the initial request will cause multiple HTTP requests, as the HTTPClient library caches the auth challenge/response for use in future requests. A single additional HTTP roundtrip shouldn’t introduce any significant performance overhead. Alternatively you could make the use of pre-emptive auth optional, defaulting to off, so that those using BASIC auth who are happy with its security constraints could turn on this option.

Pre-emptive authentication was added specifically to deal with uploading to authenticated HTTP repositories. Without pre-emptive auth, we were sending the entire file, only to get a 401 response and having to send again.

There will be a better way to deal with this, either by doing an initial HEAD before POST, or by only doing pre-emptive auth for POST requests. Thanks for adding the bug report, we’ll get to it in due course.