| /* |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| * |
| */ |
| package org.apache.hc.client5.http.impl.auth; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.nio.charset.StandardCharsets; |
| import java.security.MessageDigest; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.hc.client5.http.auth.AuthChallenge; |
| import org.apache.hc.client5.http.auth.AuthScheme; |
| import org.apache.hc.client5.http.auth.AuthScope; |
| import org.apache.hc.client5.http.auth.AuthenticationException; |
| import org.apache.hc.client5.http.auth.ChallengeType; |
| import org.apache.hc.client5.http.auth.CredentialsProvider; |
| import org.apache.hc.client5.http.auth.MalformedChallengeException; |
| import org.apache.hc.client5.http.auth.StandardAuthScheme; |
| import org.apache.hc.core5.http.ClassicHttpRequest; |
| import org.apache.hc.core5.http.ContentType; |
| import org.apache.hc.core5.http.HeaderElement; |
| import org.apache.hc.core5.http.HttpHost; |
| import org.apache.hc.core5.http.HttpRequest; |
| import org.apache.hc.core5.http.ParseException; |
| import org.apache.hc.core5.http.io.entity.InputStreamEntity; |
| import org.apache.hc.core5.http.io.entity.StringEntity; |
| import org.apache.hc.core5.http.message.BasicClassicHttpRequest; |
| import org.apache.hc.core5.http.message.BasicHeaderValueParser; |
| import org.apache.hc.core5.http.message.BasicHttpRequest; |
| import org.apache.hc.core5.http.message.ParserCursor; |
| import org.apache.hc.core5.util.CharArrayBuffer; |
| import org.junit.jupiter.api.Assertions; |
| import org.junit.jupiter.api.Test; |
| |
| /** |
| * Test Methods for DigestScheme Authentication. |
| */ |
| public class TestDigestScheme { |
| |
| private static AuthChallenge parse(final String s) throws ParseException { |
| final CharArrayBuffer buffer = new CharArrayBuffer(s.length()); |
| buffer.append(s); |
| final ParserCursor cursor = new ParserCursor(0, buffer.length()); |
| final List<AuthChallenge> authChallenges = AuthChallengeParser.INSTANCE.parse(ChallengeType.TARGET, buffer, cursor); |
| Assertions.assertEquals(1, authChallenges.size()); |
| return authChallenges.get(0); |
| } |
| |
| @Test |
| public void testDigestAuthenticationEmptyChallenge1() throws Exception { |
| final AuthChallenge authChallenge = parse(StandardAuthScheme.DIGEST); |
| final AuthScheme authscheme = new DigestScheme(); |
| Assertions.assertThrows(MalformedChallengeException.class, () -> |
| authscheme.processChallenge(authChallenge, null)); |
| } |
| |
| @Test |
| public void testDigestAuthenticationEmptyChallenge2() throws Exception { |
| final AuthChallenge authChallenge = parse(StandardAuthScheme.DIGEST + " "); |
| final AuthScheme authscheme = new DigestScheme(); |
| Assertions.assertThrows(MalformedChallengeException.class, () -> |
| authscheme.processChallenge(authChallenge, null)); |
| } |
| |
| @Test |
| public void testDigestAuthenticationWithDefaultCreds() 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); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| Assertions.assertTrue(authscheme.isChallengeComplete()); |
| Assertions.assertFalse(authscheme.isConnectionBased()); |
| |
| final Map<String, String> table = parseAuthResponse(authResponse); |
| Assertions.assertEquals("username", table.get("username")); |
| Assertions.assertEquals("realm1", table.get("realm")); |
| Assertions.assertEquals("/", table.get("uri")); |
| Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); |
| Assertions.assertEquals("e95a7ddf37c2eab009568b1ed134f89a", table.get("response")); |
| } |
| |
| @Test |
| public void testDigestAuthentication() 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); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| |
| final Map<String, String> table = parseAuthResponse(authResponse); |
| Assertions.assertEquals("username", table.get("username")); |
| Assertions.assertEquals("realm1", table.get("realm")); |
| Assertions.assertEquals("/", table.get("uri")); |
| Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); |
| Assertions.assertEquals("e95a7ddf37c2eab009568b1ed134f89a", table.get("response")); |
| } |
| |
| @Test |
| public void testDigestAuthenticationInvalidInput() throws Exception { |
| 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); |
| |
| Assertions.assertThrows(NullPointerException.class, () -> |
| authscheme.isResponseReady(null, credentialsProvider, null)); |
| Assertions.assertThrows(NullPointerException.class, () -> |
| authscheme.isResponseReady(host, null, null)); |
| Assertions.assertThrows(NullPointerException.class, () -> |
| authscheme.generateAuthResponse(host, null, null)); |
| } |
| |
| @Test |
| public void testDigestAuthenticationWithSHA() 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=SHA"; |
| final AuthChallenge authChallenge = parse(challenge); |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| |
| final Map<String, String> table = parseAuthResponse(authResponse); |
| Assertions.assertEquals("username", table.get("username")); |
| Assertions.assertEquals("realm1", table.get("realm")); |
| Assertions.assertEquals("/", table.get("uri")); |
| Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); |
| Assertions.assertEquals("8769e82e4e28ecc040b969562b9050580c6d186d", table.get("response")); |
| } |
| |
| @Test |
| public void testDigestAuthenticationWithQueryStringInDigestURI() throws Exception { |
| final HttpRequest request = new BasicHttpRequest("Simple", "/?param=value"); |
| 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); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| |
| final Map<String, String> table = parseAuthResponse(authResponse); |
| Assertions.assertEquals("username", table.get("username")); |
| Assertions.assertEquals("realm1", table.get("realm")); |
| Assertions.assertEquals("/?param=value", table.get("uri")); |
| Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); |
| Assertions.assertEquals("a847f58f5fef0bc087bcb9c3eb30e042", table.get("response")); |
| } |
| |
| @Test |
| public void testDigestAuthenticationNoRealm() 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 + " no-realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\""; |
| final AuthChallenge authChallenge = parse(challenge); |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| Assertions.assertThrows(AuthenticationException.class, () -> |
| authscheme.generateAuthResponse(host, request, null)); |
| } |
| |
| @Test |
| public void testDigestAuthenticationNoNonce() 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\", no-nonce=\"f2a3f18799759d4f1a1c068b92b573cb\""; |
| final AuthChallenge authChallenge = parse(challenge); |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| Assertions.assertThrows(AuthenticationException.class, () -> |
| 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); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| |
| final Map<String, String> 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); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| |
| final Map<String, String> table = parseAuthResponse(authResponse); |
| Assertions.assertEquals("MD5", table.get("algorithm")); |
| } |
| |
| /** |
| * Test digest authentication using the MD5-sess algorithm. |
| */ |
| @Test |
| public void testDigestAuthenticationMD5Sess() throws Exception { |
| // Example using Digest auth with MD5-sess |
| |
| final String realm="realm"; |
| final String username="username"; |
| final String password="password"; |
| final String nonce="e273f1776275974f1a120d8b92c5b3cb"; |
| |
| final HttpRequest request = new BasicHttpRequest("Simple", "/"); |
| final HttpHost host = new HttpHost("somehost", 80); |
| final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() |
| .add(new AuthScope(host, realm, null), username, password.toCharArray()) |
| .build(); |
| |
| final String challenge=StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", " |
| + "nonce=\"" + nonce + "\", " |
| + "opaque=\"SomeString\", " |
| + "stale=false, " |
| + "algorithm=MD5-sess, " |
| + "qop=\"auth,auth-int\""; // we pass both but expect auth to be used |
| |
| final AuthChallenge authChallenge = parse(challenge); |
| |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| |
| Assertions.assertTrue(authResponse.indexOf("nc=00000001") > 0); // test for quotes |
| Assertions.assertTrue(authResponse.indexOf("qop=auth") > 0); // test for quotes |
| |
| final Map<String, String> table = parseAuthResponse(authResponse); |
| Assertions.assertEquals(username, table.get("username")); |
| Assertions.assertEquals(realm, table.get("realm")); |
| Assertions.assertEquals("MD5-sess", table.get("algorithm")); |
| Assertions.assertEquals("/", table.get("uri")); |
| Assertions.assertEquals(nonce, table.get("nonce")); |
| Assertions.assertEquals(1, Integer.parseInt(table.get("nc"),16)); |
| Assertions.assertNotNull(table.get("cnonce")); |
| Assertions.assertEquals("SomeString", table.get("opaque")); |
| Assertions.assertEquals("auth", table.get("qop")); |
| //@TODO: add better check |
| Assertions.assertNotNull(table.get("response")); |
| } |
| |
| /** |
| * Test digest authentication using the MD5-sess algorithm. |
| */ |
| @Test |
| public void testDigestAuthenticationMD5SessNoQop() throws Exception { |
| // Example using Digest auth with MD5-sess |
| |
| final String realm="realm"; |
| final String username="username"; |
| final String password="password"; |
| final String nonce="e273f1776275974f1a120d8b92c5b3cb"; |
| |
| final HttpRequest request = new BasicHttpRequest("Simple", "/"); |
| final HttpHost host = new HttpHost("somehost", 80); |
| final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() |
| .add(new AuthScope(host, realm, null), username, password.toCharArray()) |
| .build(); |
| |
| final String challenge=StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", " |
| + "nonce=\"" + nonce + "\", " |
| + "opaque=\"SomeString\", " |
| + "stale=false, " |
| + "algorithm=MD5-sess"; |
| |
| final AuthChallenge authChallenge = parse(challenge); |
| |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| |
| final Map<String, String> table = parseAuthResponse(authResponse); |
| Assertions.assertEquals(username, table.get("username")); |
| Assertions.assertEquals(realm, table.get("realm")); |
| Assertions.assertEquals("MD5-sess", table.get("algorithm")); |
| Assertions.assertEquals("/", table.get("uri")); |
| Assertions.assertEquals(nonce, table.get("nonce")); |
| Assertions.assertNull(table.get("nc")); |
| Assertions.assertEquals("SomeString", table.get("opaque")); |
| Assertions.assertNull(table.get("qop")); |
| //@TODO: add better check |
| Assertions.assertNotNull(table.get("response")); |
| } |
| |
| /** |
| * Test digest authentication with unknown qop value |
| */ |
| @Test |
| public void testDigestAuthenticationMD5SessUnknownQop() throws Exception { |
| // Example using Digest auth with MD5-sess |
| |
| final String realm="realm"; |
| final String username="username"; |
| final String password="password"; |
| final String nonce="e273f1776275974f1a120d8b92c5b3cb"; |
| |
| final HttpRequest request = new BasicHttpRequest("Simple", "/"); |
| final HttpHost host = new HttpHost("somehost", 80); |
| final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() |
| .add(new AuthScope(host, realm, null), username, password.toCharArray()) |
| .build(); |
| |
| final String challenge=StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", " |
| + "nonce=\"" + nonce + "\", " |
| + "opaque=\"SomeString\", " |
| + "stale=false, " |
| + "algorithm=MD5-sess, " |
| + "qop=\"stuff\""; |
| |
| final AuthChallenge authChallenge = parse(challenge); |
| |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| Assertions.assertThrows(AuthenticationException.class, () -> |
| authscheme.generateAuthResponse(host, request, null)); |
| } |
| |
| /** |
| * Test digest authentication with unknown qop value |
| */ |
| @Test |
| public void testDigestAuthenticationUnknownAlgo() throws Exception { |
| // Example using Digest auth with MD5-sess |
| |
| final String realm="realm"; |
| final String username="username"; |
| final String password="password"; |
| final String nonce="e273f1776275974f1a120d8b92c5b3cb"; |
| |
| final HttpRequest request = new BasicHttpRequest("Simple", "/"); |
| final HttpHost host = new HttpHost("somehost", 80); |
| final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() |
| .add(new AuthScope(host, realm, null), username, password.toCharArray()) |
| .build(); |
| |
| final String challenge=StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", " |
| + "nonce=\"" + nonce + "\", " |
| + "opaque=\"SomeString\", " |
| + "stale=false, " |
| + "algorithm=stuff, " |
| + "qop=\"auth\""; |
| |
| final AuthChallenge authChallenge = parse(challenge); |
| |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| Assertions.assertThrows(AuthenticationException.class, () -> |
| authscheme.generateAuthResponse(host, request, null)); |
| } |
| |
| @Test |
| public void testDigestAuthenticationWithStaleNonce() throws Exception { |
| final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", " + |
| "nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", stale=\"true\""; |
| final AuthChallenge authChallenge = parse(challenge); |
| final AuthScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| |
| Assertions.assertFalse(authscheme.isChallengeComplete()); |
| } |
| |
| private static Map<String, String> parseAuthResponse(final String authResponse) { |
| if (!authResponse.startsWith(StandardAuthScheme.DIGEST + " ")) { |
| return null; |
| } |
| final String s = authResponse.substring(7); |
| final ParserCursor cursor = new ParserCursor(0, s.length()); |
| final HeaderElement[] elements = BasicHeaderValueParser.INSTANCE.parseElements(s, cursor); |
| final Map<String, String> map = new HashMap<>(elements.length); |
| for (final HeaderElement element : elements) { |
| map.put(element.getName(), element.getValue()); |
| } |
| return map; |
| } |
| |
| @Test |
| public void testDigestNouceCount() throws Exception { |
| final HttpRequest request = new BasicHttpRequest("GET", "/"); |
| final HttpHost host = new HttpHost("somehost", 80); |
| final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() |
| .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray()) |
| .build(); |
| |
| final String challenge1 = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=auth"; |
| final AuthChallenge authChallenge1 = parse(challenge1); |
| |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge1, null); |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse1 = authscheme.generateAuthResponse(host, request, null); |
| |
| final Map<String, String> table1 = parseAuthResponse(authResponse1); |
| Assertions.assertEquals("00000001", table1.get("nc")); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse2 = authscheme.generateAuthResponse(host, request, null); |
| |
| final Map<String, String> table2 = parseAuthResponse(authResponse2); |
| Assertions.assertEquals("00000002", table2.get("nc")); |
| final String challenge2 = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=auth"; |
| final AuthChallenge authChallenge2 = parse(challenge2); |
| authscheme.processChallenge(authChallenge2, null); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse3 = authscheme.generateAuthResponse(host, request, null); |
| |
| final Map<String, String> table3 = parseAuthResponse(authResponse3); |
| Assertions.assertEquals("00000003", table3.get("nc")); |
| final String challenge3 = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"e273f1776275974f1a120d8b92c5b3cb\", qop=auth"; |
| final AuthChallenge authChallenge3 = parse(challenge3); |
| authscheme.processChallenge(authChallenge3, null); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse4 = authscheme.generateAuthResponse(host, request, null); |
| |
| final Map<String, String> table4 = parseAuthResponse(authResponse4); |
| Assertions.assertEquals("00000001", table4.get("nc")); |
| } |
| |
| @Test |
| public void testDigestMD5SessA1AndCnonceConsistency() throws Exception { |
| final HttpHost host = new HttpHost("somehost", 80); |
| final HttpRequest request = new BasicHttpRequest("GET", "/"); |
| final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() |
| .add(new AuthScope(host, "subnet.domain.com", null), "username", "password".toCharArray()) |
| .build(); |
| |
| final String challenge1 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=MD5-sess, nonce=\"1234567890abcdef\", " + |
| "charset=utf-8, realm=\"subnet.domain.com\""; |
| final AuthChallenge authChallenge1 = parse(challenge1); |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge1, null); |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse1 = authscheme.generateAuthResponse(host, request, null); |
| |
| final Map<String, String> table1 = parseAuthResponse(authResponse1); |
| Assertions.assertEquals("00000001", table1.get("nc")); |
| final String cnonce1 = authscheme.getCnonce(); |
| final String sessionKey1 = authscheme.getA1(); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse2 = authscheme.generateAuthResponse(host, request, null); |
| final Map<String, String> table2 = parseAuthResponse(authResponse2); |
| Assertions.assertEquals("00000002", table2.get("nc")); |
| final String cnonce2 = authscheme.getCnonce(); |
| final String sessionKey2 = authscheme.getA1(); |
| |
| Assertions.assertEquals(cnonce1, cnonce2); |
| Assertions.assertEquals(sessionKey1, sessionKey2); |
| |
| final String challenge2 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=MD5-sess, nonce=\"1234567890abcdef\", " + |
| "charset=utf-8, realm=\"subnet.domain.com\""; |
| final AuthChallenge authChallenge2 = parse(challenge2); |
| authscheme.processChallenge(authChallenge2, null); |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse3 = authscheme.generateAuthResponse(host, request, null); |
| final Map<String, String> table3 = parseAuthResponse(authResponse3); |
| Assertions.assertEquals("00000003", table3.get("nc")); |
| |
| final String cnonce3 = authscheme.getCnonce(); |
| final String sessionKey3 = authscheme.getA1(); |
| |
| Assertions.assertEquals(cnonce1, cnonce3); |
| Assertions.assertEquals(sessionKey1, sessionKey3); |
| |
| final String challenge3 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=MD5-sess, nonce=\"fedcba0987654321\", " + |
| "charset=utf-8, realm=\"subnet.domain.com\""; |
| final AuthChallenge authChallenge3 = parse(challenge3); |
| authscheme.processChallenge(authChallenge3, null); |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse4 = authscheme.generateAuthResponse(host, request, null); |
| final Map<String, String> table4 = parseAuthResponse(authResponse4); |
| Assertions.assertEquals("00000001", table4.get("nc")); |
| |
| final String cnonce4 = authscheme.getCnonce(); |
| final String sessionKey4 = authscheme.getA1(); |
| |
| Assertions.assertNotEquals(cnonce1, cnonce4); |
| Assertions.assertNotEquals(sessionKey1, sessionKey4); |
| } |
| |
| @Test |
| public void testHttpEntityDigest() throws Exception { |
| final HttpEntityDigester digester = new HttpEntityDigester(MessageDigest.getInstance("MD5")); |
| Assertions.assertNull(digester.getDigest()); |
| digester.write('a'); |
| digester.write('b'); |
| digester.write('c'); |
| digester.write(0xe4); |
| digester.write(0xf6); |
| digester.write(0xfc); |
| digester.write(new byte[] { 'a', 'b', 'c'}); |
| Assertions.assertNull(digester.getDigest()); |
| digester.close(); |
| Assertions.assertEquals("acd2b59cd01c7737d8069015584c6cac", DigestScheme.formatHex(digester.getDigest())); |
| Assertions.assertThrows(IOException.class, () -> digester.write('a')); |
| Assertions.assertThrows(IOException.class, () -> digester.write(new byte[] { 'a', 'b', 'c'})); |
| } |
| |
| @Test |
| public void testDigestAuthenticationQopAuthInt() throws Exception { |
| final ClassicHttpRequest request = new BasicClassicHttpRequest("Post", "/"); |
| request.setEntity(new StringEntity("abc\u00e4\u00f6\u00fcabc", StandardCharsets.ISO_8859_1)); |
| 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\", " + |
| "qop=\"auth,auth-int\""; |
| final AuthChallenge authChallenge = parse(challenge); |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| |
| Assertions.assertEquals("Post:/:acd2b59cd01c7737d8069015584c6cac", authscheme.getA2()); |
| |
| final Map<String, String> table = parseAuthResponse(authResponse); |
| Assertions.assertEquals("username", table.get("username")); |
| Assertions.assertEquals("realm1", table.get("realm")); |
| Assertions.assertEquals("/", table.get("uri")); |
| Assertions.assertEquals("auth-int", table.get("qop")); |
| Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); |
| } |
| |
| @Test |
| public void testDigestAuthenticationQopAuthIntNullEntity() throws Exception { |
| final HttpRequest request = new BasicHttpRequest("Post", "/"); |
| 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\", " + |
| "qop=\"auth-int\""; |
| final AuthChallenge authChallenge = parse(challenge); |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| |
| Assertions.assertEquals("Post:/:d41d8cd98f00b204e9800998ecf8427e", authscheme.getA2()); |
| |
| final Map<String, String> table = parseAuthResponse(authResponse); |
| Assertions.assertEquals("username", table.get("username")); |
| Assertions.assertEquals("realm1", table.get("realm")); |
| Assertions.assertEquals("/", table.get("uri")); |
| Assertions.assertEquals("auth-int", table.get("qop")); |
| Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); |
| } |
| |
| @Test |
| public void testDigestAuthenticationQopAuthOrAuthIntNonRepeatableEntity() throws Exception { |
| final ClassicHttpRequest request = new BasicClassicHttpRequest("Post", "/"); |
| request.setEntity(new InputStreamEntity(new ByteArrayInputStream(new byte[] {'a'}), -1, ContentType.DEFAULT_TEXT)); |
| 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\", " + |
| "qop=\"auth,auth-int\""; |
| final AuthChallenge authChallenge = parse(challenge); |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| |
| Assertions.assertEquals("Post:/", authscheme.getA2()); |
| |
| final Map<String, String> table = parseAuthResponse(authResponse); |
| Assertions.assertEquals("username", table.get("username")); |
| Assertions.assertEquals("realm1", table.get("realm")); |
| Assertions.assertEquals("/", table.get("uri")); |
| Assertions.assertEquals("auth", table.get("qop")); |
| Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); |
| } |
| |
| @Test |
| public void testParameterCaseSensitivity() throws Exception { |
| final HttpRequest request = new BasicHttpRequest("GET", "/"); |
| final HttpHost host = new HttpHost("somehost", 80); |
| final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() |
| .add(new AuthScope(host, "-", null), "username", "password".toCharArray()) |
| .build(); |
| |
| final String challenge = StandardAuthScheme.DIGEST + " Realm=\"-\", " + |
| "nonce=\"YjYuNGYyYmJhMzUuY2I5ZDhlZDE5M2ZlZDM 1Mjk3NGJkNTIyYjgyNTcwMjQ=\", " + |
| "opaque=\"98700A3D9CE17065E2246B41035C6609\", qop=\"auth\""; |
| final AuthChallenge authChallenge = parse(challenge); |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| Assertions.assertEquals("-", authscheme.getRealm()); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| authscheme.generateAuthResponse(host, request, null); |
| } |
| |
| @Test |
| public void testDigestAuthenticationQopIntOnlyNonRepeatableEntity() throws Exception { |
| final ClassicHttpRequest request = new BasicClassicHttpRequest("Post", "/"); |
| request.setEntity(new InputStreamEntity(new ByteArrayInputStream(new byte[] {'a'}), -1, ContentType.DEFAULT_TEXT)); |
| 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\", " + |
| "qop=\"auth-int\""; |
| final AuthChallenge authChallenge = parse(challenge); |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| Assertions.assertThrows(AuthenticationException.class, () -> |
| authscheme.generateAuthResponse(host, request, null)); |
| } |
| |
| @Test |
| public void testSerialization() throws Exception { |
| final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " + |
| "qop=\"auth,auth-int\""; |
| final AuthChallenge authChallenge = parse(challenge); |
| final DigestScheme digestScheme = new DigestScheme(); |
| digestScheme.processChallenge(authChallenge, null); |
| |
| final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
| final ObjectOutputStream out = new ObjectOutputStream(buffer); |
| out.writeObject(digestScheme); |
| out.flush(); |
| final byte[] raw = buffer.toByteArray(); |
| final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(raw)); |
| final DigestScheme authScheme = (DigestScheme) in.readObject(); |
| |
| Assertions.assertEquals(digestScheme.getName(), authScheme.getName()); |
| Assertions.assertEquals(digestScheme.getRealm(), authScheme.getRealm()); |
| Assertions.assertEquals(digestScheme.isChallengeComplete(), authScheme.isChallengeComplete()); |
| Assertions.assertEquals(digestScheme.getA1(), authScheme.getA1()); |
| Assertions.assertEquals(digestScheme.getA2(), authScheme.getA2()); |
| Assertions.assertEquals(digestScheme.getCnonce(), authScheme.getCnonce()); |
| } |
| |
| |
| @Test |
| public void testDigestAuthenticationWithUserHash() 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(); |
| |
| // Include userhash in the challenge |
| final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", userhash=true"; |
| final AuthChallenge authChallenge = parse(challenge); |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| |
| final Map<String, String> table = parseAuthResponse(authResponse); |
| |
| // Generate expected userhash |
| final MessageDigest md = MessageDigest.getInstance("MD5"); |
| md.update(("username:realm1").getBytes(StandardCharsets.UTF_8)); |
| final String expectedUserhash = bytesToHex(md.digest()); |
| |
| Assertions.assertEquals(expectedUserhash, table.get("username")); |
| Assertions.assertEquals("realm1", table.get("realm")); |
| Assertions.assertEquals("/", table.get("uri")); |
| Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); |
| Assertions.assertEquals("3b6561ceb73e5ffe9314a695179f23f9", table.get("response")); |
| } |
| |
| private static String bytesToHex(final byte[] bytes) { |
| final StringBuilder hexString = new StringBuilder(); |
| for (final byte b : bytes) { |
| final String hex = Integer.toHexString(0xff & b); |
| if (hex.length() == 1) { |
| hexString.append('0'); |
| } |
| hexString.append(hex); |
| } |
| return hexString.toString(); |
| } |
| |
| @Test |
| public void testDigestAuthenticationWithQuotedStringsAndWhitespace() throws Exception { |
| final HttpRequest request = new BasicHttpRequest("Simple", "/"); |
| final HttpHost host = new HttpHost("somehost", 80); |
| final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() |
| .add(new AuthScope(host, "\"myhost@example.com\"", null), "\"Mufasa\"", "\"Circle Of Life\"".toCharArray()) |
| .build(); |
| |
| // Include userhash in the challenge |
| final String challenge = StandardAuthScheme.DIGEST + " realm=\"\\\"myhost@example.com\\\"\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", userhash=true"; |
| final AuthChallenge authChallenge = parse(challenge); |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| |
| final Map<String, String> table = parseAuthResponse(authResponse); |
| |
| // Generate expected A1 hash |
| final MessageDigest md = MessageDigest.getInstance("MD5"); |
| final String a1 = "Mufasa:myhost@example.com:Circle Of Life"; // Note: quotes removed and internal whitespace preserved |
| md.update(a1.getBytes(StandardCharsets.UTF_8)); |
| |
| // Extract the response and validate the A1 hash |
| final String response = table.get("response"); |
| Assertions.assertNotNull(response); |
| } |
| @Test |
| public void testDigestAuthenticationWithInvalidUsernameAndValidUsernameStar() throws Exception { |
| final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/"); |
| final HttpHost host = new HttpHost("somehost", 80); |
| final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() |
| .add(new AuthScope(host, "realm1", null), "invalid\"username", "password".toCharArray()) |
| .build(); |
| |
| final String encodedUsername = "UTF-8''J%C3%A4s%C3%B8n%20Doe"; |
| final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " + |
| "qop=\"auth-int\", username*=\"" + encodedUsername + "\""; |
| final AuthChallenge authChallenge = parse(challenge); |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| Assertions.assertNotNull(authResponse); |
| } |
| |
| @Test |
| public void testDigestAuthenticationWithHighAsciiCharInUsername() throws Exception { |
| final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/"); |
| final HttpHost host = new HttpHost("somehost", 80); |
| // Using a username with a high ASCII character |
| final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() |
| .add(new AuthScope(host, "realm1", null), "high\u007Fchar", "password".toCharArray()) |
| .build(); |
| |
| final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=\"auth-int\""; |
| final AuthChallenge authChallenge = parse(challenge); |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| |
| // Optionally, verify that 'username*' is used in the response |
| Assertions.assertTrue(authResponse.contains("username*")); |
| } |
| |
| |
| @Test |
| public void testDigestAuthenticationWithExtendedAsciiCharInUsername() throws Exception { |
| final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/"); |
| final HttpHost host = new HttpHost("somehost", 80); |
| // Using an extended ASCII character (e.g., 0x80) in the username |
| final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() |
| .add(new AuthScope(host, "realm1", null), "username\u0080", "password".toCharArray()) |
| .build(); |
| |
| final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=\"auth-int\""; |
| final AuthChallenge authChallenge = parse(challenge); |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| |
| |
| Assertions.assertTrue(authResponse.contains("username*")); |
| } |
| |
| |
| @Test |
| public void testDigestAuthenticationWithNonAsciiUsername() throws Exception { |
| final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/"); |
| final HttpHost host = new HttpHost("somehost", 80); |
| // Using a username with non-ASCII characters |
| final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() |
| .add(new AuthScope(host, "realm1", null), "Jäsøn Doe", "password".toCharArray()) |
| .build(); |
| |
| final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=\"auth-int\""; |
| final AuthChallenge authChallenge = parse(challenge); |
| final DigestScheme authscheme = new DigestScheme(); |
| authscheme.processChallenge(authChallenge, null); |
| |
| Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); |
| final String authResponse = authscheme.generateAuthResponse(host, request, null); |
| |
| // Optionally, verify that 'username*' is used in the response |
| Assertions.assertTrue(authResponse.contains("username*")); |
| } |
| |
| |
| } |