From d72a136817fd286c032bbba7636ac8e787dfb6e3 Mon Sep 17 00:00:00 2001 From: John Vasileff Date: Wed, 19 Jul 2023 12:27:07 -0400 Subject: [PATCH] Per RFC 7616, use the provided Response algorithm in the Request For digest authentication, in RFC 7616 section "3.4 The Authorization Header Field": The values of the opaque and algorithm fields must be those supplied in the WWW-Authenticate response header field for the entity being requested. This commit honors that rule, and removes the previous behavior that augmented the request header with "algorithm=MD5" when none was provided in the server's response. Aside from the specification, it also stands to reason that if the server failed to provide "algorithm=..." in its "WWW-Authenticate" header, the server should be fine with the client failing to provide "algorithm=..." in the "Authorization" header. The motivation for this change is that including "algorithm=MD5" in the "Authorization" header causes http requests to fail when made to an embedded system, which I suspect to be a an Espressif ESP32 web server. --- .../client5/http/impl/auth/DigestScheme.java | 16 ++++---- .../http/impl/auth/TestDigestScheme.java | 40 +++++++++++++++++++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java index ff98ab36a..bcee7bb51 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java @@ -245,11 +245,7 @@ public class DigestScheme implements AuthScheme, Serializable { final String realm = this.paramMap.get("realm"); final String nonce = this.paramMap.get("nonce"); final String opaque = this.paramMap.get("opaque"); - String algorithm = this.paramMap.get("algorithm"); - // If an algorithm is not specified, default to MD5. - if (algorithm == null) { - algorithm = "MD5"; - } + final String algorithm = this.paramMap.get("algorithm"); final Set qopset = new HashSet<>(8); QualityOfProtection qop = QualityOfProtection.UNKNOWN; @@ -278,7 +274,8 @@ public class DigestScheme implements AuthScheme, Serializable { final Charset charset = AuthSchemeSupport.parseCharset(paramMap.get("charset"), defaultCharset); String digAlg = algorithm; - if (digAlg.equalsIgnoreCase("MD5-sess")) { + // If an algorithm is not specified, default to MD5. + if (digAlg == null || digAlg.equalsIgnoreCase("MD5-sess")) { digAlg = "MD5"; } @@ -317,7 +314,7 @@ public class DigestScheme implements AuthScheme, Serializable { a1 = null; a2 = null; // 3.2.2.2: Calculating digest - if (algorithm.equalsIgnoreCase("MD5-sess")) { + if ("MD5-sess".equalsIgnoreCase(algorithm)) { // H( unq(username-value) ":" unq(realm-value) ":" passwd ) // ":" unq(nonce-value) // ":" unq(cnonce-value) @@ -401,8 +398,9 @@ public class DigestScheme implements AuthScheme, Serializable { params.add(new BasicNameValuePair("nc", nc)); params.add(new BasicNameValuePair("cnonce", cnonce)); } - // algorithm cannot be null here - params.add(new BasicNameValuePair("algorithm", algorithm)); + if (algorithm != null) { + params.add(new BasicNameValuePair("algorithm", algorithm)); + } if (opaque != null) { params.add(new BasicNameValuePair("opaque", opaque)); } diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestDigestScheme.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestDigestScheme.java index 54cb1da88..8a2671979 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestDigestScheme.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestDigestScheme.java @@ -247,6 +247,46 @@ public class TestDigestScheme { authscheme.generateAuthResponse(host, request, null)); } + @Test + public void testDigestAuthenticationNoAlgorithm() throws Exception { + final HttpRequest request = new BasicHttpRequest("Simple", "/"); + final HttpHost host = new HttpHost("somehost", 80); + final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() + .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray()) + .build(); + + final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\""; + final AuthChallenge authChallenge = parse(challenge); + final DigestScheme authscheme = new DigestScheme(); + authscheme.processChallenge(authChallenge, null); + + final String authResponse = authscheme.generateAuthResponse(host, request, null); + + final Map table = parseAuthResponse(authResponse); + Assertions.assertNull(table.get("algorithm")); + } + + @Test + public void testDigestAuthenticationMD5Algorithm() throws Exception { + final HttpRequest request = new BasicHttpRequest("Simple", "/"); + final HttpHost host = new HttpHost("somehost", 80); + final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() + .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray()) + .build(); + + final String challenge = StandardAuthScheme.DIGEST + + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"" + + ", algorithm=MD5"; + final AuthChallenge authChallenge = parse(challenge); + final DigestScheme authscheme = new DigestScheme(); + authscheme.processChallenge(authChallenge, null); + + final String authResponse = authscheme.generateAuthResponse(host, request, null); + + final Map table = parseAuthResponse(authResponse); + Assertions.assertEquals("MD5", table.get("algorithm")); + } + /** * Test digest authentication using the MD5-sess algorithm. */