View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.client5.http.impl.auth;
28  
29  import java.io.ByteArrayInputStream;
30  import java.io.ByteArrayOutputStream;
31  import java.io.IOException;
32  import java.io.ObjectInputStream;
33  import java.io.ObjectOutputStream;
34  import java.nio.charset.StandardCharsets;
35  import java.security.MessageDigest;
36  import java.util.HashMap;
37  import java.util.List;
38  import java.util.Map;
39  
40  import org.apache.hc.client5.http.auth.AuthChallenge;
41  import org.apache.hc.client5.http.auth.AuthScheme;
42  import org.apache.hc.client5.http.auth.AuthScope;
43  import org.apache.hc.client5.http.auth.AuthenticationException;
44  import org.apache.hc.client5.http.auth.ChallengeType;
45  import org.apache.hc.client5.http.auth.CredentialsProvider;
46  import org.apache.hc.client5.http.auth.MalformedChallengeException;
47  import org.apache.hc.client5.http.auth.StandardAuthScheme;
48  import org.apache.hc.client5.http.protocol.HttpClientContext;
49  import org.apache.hc.core5.http.ClassicHttpRequest;
50  import org.apache.hc.core5.http.ContentType;
51  import org.apache.hc.core5.http.HeaderElement;
52  import org.apache.hc.core5.http.HttpHost;
53  import org.apache.hc.core5.http.HttpRequest;
54  import org.apache.hc.core5.http.ParseException;
55  import org.apache.hc.core5.http.io.entity.InputStreamEntity;
56  import org.apache.hc.core5.http.io.entity.StringEntity;
57  import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
58  import org.apache.hc.core5.http.message.BasicHeaderValueParser;
59  import org.apache.hc.core5.http.message.BasicHttpRequest;
60  import org.apache.hc.core5.http.message.ParserCursor;
61  import org.apache.hc.core5.util.CharArrayBuffer;
62  import org.junit.jupiter.api.Assertions;
63  import org.junit.jupiter.api.Test;
64  
65  /**
66   * Test Methods for DigestScheme Authentication.
67   */
68  class TestDigestScheme {
69  
70      private static AuthChallenge parse(final String s) throws ParseException {
71          final CharArrayBuffer buffer = new CharArrayBuffer(s.length());
72          buffer.append(s);
73          final ParserCursor cursor = new ParserCursor(0, buffer.length());
74          final List<AuthChallenge> authChallenges = AuthChallengeParser.INSTANCE.parse(ChallengeType.TARGET, buffer, cursor);
75          Assertions.assertEquals(1, authChallenges.size());
76          return authChallenges.get(0);
77      }
78  
79      @Test
80      void testDigestAuthenticationEmptyChallenge1() throws Exception {
81          final AuthChallenge authChallenge = parse(StandardAuthScheme.DIGEST);
82          final AuthScheme authscheme = new DigestScheme();
83          Assertions.assertThrows(MalformedChallengeException.class, () ->
84                  authscheme.processChallenge(authChallenge, null));
85      }
86  
87      @Test
88      void testDigestAuthenticationEmptyChallenge2() throws Exception {
89          final AuthChallenge authChallenge = parse(StandardAuthScheme.DIGEST + " ");
90          final AuthScheme authscheme = new DigestScheme();
91          Assertions.assertThrows(MalformedChallengeException.class, () ->
92                  authscheme.processChallenge(authChallenge, null));
93      }
94  
95      @Test
96      void testDigestAuthenticationWithDefaultCreds() throws Exception {
97          final HttpRequest request = new BasicHttpRequest("Simple", "/");
98          final HttpHost host = new HttpHost("somehost", 80);
99          final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
100                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
101                 .build();
102 
103         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
104         final AuthChallenge authChallenge = parse(challenge);
105         final DigestScheme authscheme = new DigestScheme();
106         authscheme.processChallenge(authChallenge, null);
107 
108         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
109         final String authResponse = authscheme.generateAuthResponse(host, request, null);
110         Assertions.assertTrue(authscheme.isChallengeComplete());
111         Assertions.assertFalse(authscheme.isConnectionBased());
112 
113         final Map<String, String> table = parseAuthResponse(authResponse);
114         Assertions.assertEquals("username", table.get("username"));
115         Assertions.assertEquals("realm1", table.get("realm"));
116         Assertions.assertEquals("/", table.get("uri"));
117         Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
118         Assertions.assertEquals("e95a7ddf37c2eab009568b1ed134f89a", table.get("response"));
119     }
120 
121     @Test
122     void testDigestAuthentication() throws Exception {
123         final HttpRequest request = new BasicHttpRequest("Simple", "/");
124         final HttpHost host = new HttpHost("somehost", 80);
125         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
126                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
127                 .build();
128 
129         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
130         final AuthChallenge authChallenge = parse(challenge);
131         final DigestScheme authscheme = new DigestScheme();
132         authscheme.processChallenge(authChallenge, null);
133 
134         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
135         final String authResponse = authscheme.generateAuthResponse(host, request, null);
136 
137         final Map<String, String> table = parseAuthResponse(authResponse);
138         Assertions.assertEquals("username", table.get("username"));
139         Assertions.assertEquals("realm1", table.get("realm"));
140         Assertions.assertEquals("/", table.get("uri"));
141         Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
142         Assertions.assertEquals("e95a7ddf37c2eab009568b1ed134f89a", table.get("response"));
143     }
144 
145     @Test
146     void testDigestAuthenticationInvalidInput() throws Exception {
147         final HttpHost host = new HttpHost("somehost", 80);
148         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
149                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
150                 .build();
151 
152         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
153         final AuthChallenge authChallenge = parse(challenge);
154         final DigestScheme authscheme = new DigestScheme();
155         authscheme.processChallenge(authChallenge, null);
156 
157         Assertions.assertThrows(NullPointerException.class, () ->
158                 authscheme.isResponseReady(null, credentialsProvider, null));
159         Assertions.assertThrows(NullPointerException.class, () ->
160                 authscheme.isResponseReady(host, null, null));
161         Assertions.assertThrows(NullPointerException.class, () ->
162                 authscheme.generateAuthResponse(host, null, null));
163     }
164 
165     @Test
166     void testDigestAuthenticationWithSHA() throws Exception {
167         final HttpRequest request = new BasicHttpRequest("Simple", "/");
168         final HttpHost host = new HttpHost("somehost", 80);
169         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
170                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
171                 .build();
172 
173         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", " +
174                 "nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
175                 "algorithm=SHA";
176         final AuthChallenge authChallenge = parse(challenge);
177         final DigestScheme authscheme = new DigestScheme();
178         authscheme.processChallenge(authChallenge, null);
179 
180         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
181 
182         Assertions.assertThrows(AuthenticationException.class, () ->
183                 authscheme.generateAuthResponse(host, request, null), "Expected UnsupportedDigestAlgorithmException for unsupported 'SHA' algorithm");
184     }
185 
186     @Test
187     void testDigestAuthenticationWithQueryStringInDigestURI() throws Exception {
188         final HttpRequest request = new BasicHttpRequest("Simple", "/?param=value");
189         final HttpHost host = new HttpHost("somehost", 80);
190         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
191                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
192                 .build();
193 
194         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
195         final AuthChallenge authChallenge = parse(challenge);
196         final DigestScheme authscheme = new DigestScheme();
197         authscheme.processChallenge(authChallenge, null);
198 
199         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
200         final String authResponse = authscheme.generateAuthResponse(host, request, null);
201 
202         final Map<String, String> table = parseAuthResponse(authResponse);
203         Assertions.assertEquals("username", table.get("username"));
204         Assertions.assertEquals("realm1", table.get("realm"));
205         Assertions.assertEquals("/?param=value", table.get("uri"));
206         Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
207         Assertions.assertEquals("a847f58f5fef0bc087bcb9c3eb30e042", table.get("response"));
208     }
209 
210     @Test
211     void testDigestAuthenticationNoRealm() throws Exception {
212         final HttpRequest request = new BasicHttpRequest("Simple", "/");
213         final HttpHost host = new HttpHost("somehost", 80);
214         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
215                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
216                 .build();
217 
218         final String challenge = StandardAuthScheme.DIGEST + " no-realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
219         final AuthChallenge authChallenge = parse(challenge);
220         final DigestScheme authscheme = new DigestScheme();
221         authscheme.processChallenge(authChallenge, null);
222 
223         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
224         Assertions.assertThrows(AuthenticationException.class, () ->
225                 authscheme.generateAuthResponse(host, request, null));
226     }
227 
228     @Test
229     void testDigestAuthenticationNoNonce() throws Exception {
230         final HttpRequest request = new BasicHttpRequest("Simple", "/");
231         final HttpHost host = new HttpHost("somehost", 80);
232         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
233                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
234                 .build();
235 
236         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", no-nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
237         final AuthChallenge authChallenge = parse(challenge);
238         final DigestScheme authscheme = new DigestScheme();
239         authscheme.processChallenge(authChallenge, null);
240 
241         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
242         Assertions.assertThrows(AuthenticationException.class, () ->
243                 authscheme.generateAuthResponse(host, request, null));
244     }
245 
246     @Test
247     void testDigestAuthenticationNoAlgorithm() throws Exception {
248         final HttpRequest request = new BasicHttpRequest("Simple", "/");
249         final HttpHost host = new HttpHost("somehost", 80);
250         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
251                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
252                 .build();
253 
254         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
255         final AuthChallenge authChallenge = parse(challenge);
256         final DigestScheme authscheme = new DigestScheme();
257         authscheme.processChallenge(authChallenge, null);
258 
259         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
260         final String authResponse = authscheme.generateAuthResponse(host, request, null);
261 
262         final Map<String, String> table = parseAuthResponse(authResponse);
263         Assertions.assertNull(table.get("algorithm"));
264     }
265 
266     @Test
267     void testDigestAuthenticationMD5Algorithm() throws Exception {
268         final HttpRequest request = new BasicHttpRequest("Simple", "/");
269         final HttpHost host = new HttpHost("somehost", 80);
270         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
271                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
272                 .build();
273 
274         final String challenge = StandardAuthScheme.DIGEST
275                 + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\""
276                 + ", algorithm=MD5";
277         final AuthChallenge authChallenge = parse(challenge);
278         final DigestScheme authscheme = new DigestScheme();
279         authscheme.processChallenge(authChallenge, null);
280 
281         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
282         final String authResponse = authscheme.generateAuthResponse(host, request, null);
283 
284         final Map<String, String> table = parseAuthResponse(authResponse);
285         Assertions.assertEquals("MD5", table.get("algorithm"));
286     }
287 
288     /**
289      * Test digest authentication using the MD5-sess algorithm.
290      */
291     @Test
292     void testDigestAuthenticationMD5Sess() throws Exception {
293         // Example using Digest auth with MD5-sess
294 
295         final String realm = "realm";
296         final String username = "username";
297         final String password = "password";
298         final String nonce = "e273f1776275974f1a120d8b92c5b3cb";
299 
300         final HttpRequest request = new BasicHttpRequest("Simple", "/");
301         final HttpHost host = new HttpHost("somehost", 80);
302         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
303                 .add(new AuthScope(host, realm, null), username, password.toCharArray())
304                 .build();
305 
306         final String challenge = StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", "
307             + "nonce=\"" + nonce + "\", "
308             + "opaque=\"SomeString\", "
309             + "stale=false, "
310             + "algorithm=MD5-sess, "
311             + "qop=\"auth,auth-int\""; // we pass both but expect auth to be used
312 
313         final AuthChallenge authChallenge = parse(challenge);
314 
315         final DigestScheme authscheme = new DigestScheme();
316         authscheme.processChallenge(authChallenge, null);
317 
318         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
319         final String authResponse = authscheme.generateAuthResponse(host, request, null);
320 
321         Assertions.assertTrue(authResponse.indexOf("nc=00000001") > 0); // test for quotes
322         Assertions.assertTrue(authResponse.indexOf("qop=auth") > 0); // test for quotes
323 
324         final Map<String, String> table = parseAuthResponse(authResponse);
325         Assertions.assertEquals(username, table.get("username"));
326         Assertions.assertEquals(realm, table.get("realm"));
327         Assertions.assertEquals("MD5-sess", table.get("algorithm"));
328         Assertions.assertEquals("/", table.get("uri"));
329         Assertions.assertEquals(nonce, table.get("nonce"));
330         Assertions.assertEquals(1, Integer.parseInt(table.get("nc"), 16));
331         Assertions.assertNotNull(table.get("cnonce"));
332         Assertions.assertEquals("SomeString", table.get("opaque"));
333         Assertions.assertEquals("auth", table.get("qop"));
334         //@TODO: add better check
335         Assertions.assertNotNull(table.get("response"));
336     }
337 
338     /**
339      * Test digest authentication using the MD5-sess algorithm.
340      */
341     @Test
342     void testDigestAuthenticationMD5SessNoQop() throws Exception {
343         // Example using Digest auth with MD5-sess
344 
345         final String realm = "realm";
346         final String username = "username";
347         final String password = "password";
348         final String nonce = "e273f1776275974f1a120d8b92c5b3cb";
349 
350         final HttpRequest request = new BasicHttpRequest("Simple", "/");
351         final HttpHost host = new HttpHost("somehost", 80);
352         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
353                 .add(new AuthScope(host, realm, null), username, password.toCharArray())
354                 .build();
355 
356         final String challenge = StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", "
357             + "nonce=\"" + nonce + "\", "
358             + "opaque=\"SomeString\", "
359             + "stale=false, "
360             + "algorithm=MD5-sess";
361 
362         final AuthChallenge authChallenge = parse(challenge);
363 
364         final DigestScheme authscheme = new DigestScheme();
365         authscheme.processChallenge(authChallenge, null);
366         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
367         final String authResponse = authscheme.generateAuthResponse(host, request, null);
368 
369         final Map<String, String> table = parseAuthResponse(authResponse);
370         Assertions.assertEquals(username, table.get("username"));
371         Assertions.assertEquals(realm, table.get("realm"));
372         Assertions.assertEquals("MD5-sess", table.get("algorithm"));
373         Assertions.assertEquals("/", table.get("uri"));
374         Assertions.assertEquals(nonce, table.get("nonce"));
375         Assertions.assertNull(table.get("nc"));
376         Assertions.assertEquals("SomeString", table.get("opaque"));
377         Assertions.assertNull(table.get("qop"));
378         //@TODO: add better check
379         Assertions.assertNotNull(table.get("response"));
380     }
381 
382     /**
383      * Test digest authentication with unknown qop value
384      */
385     @Test
386     void testDigestAuthenticationMD5SessUnknownQop() throws Exception {
387         // Example using Digest auth with MD5-sess
388 
389         final String realm = "realm";
390         final String username = "username";
391         final String password = "password";
392         final String nonce = "e273f1776275974f1a120d8b92c5b3cb";
393 
394         final HttpRequest request = new BasicHttpRequest("Simple", "/");
395         final HttpHost host = new HttpHost("somehost", 80);
396         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
397                 .add(new AuthScope(host, realm, null), username, password.toCharArray())
398                 .build();
399 
400         final String challenge = StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", "
401             + "nonce=\"" + nonce + "\", "
402             + "opaque=\"SomeString\", "
403             + "stale=false, "
404             + "algorithm=MD5-sess, "
405             + "qop=\"stuff\"";
406 
407         final AuthChallenge authChallenge = parse(challenge);
408 
409         final DigestScheme authscheme = new DigestScheme();
410         authscheme.processChallenge(authChallenge, null);
411 
412         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
413         Assertions.assertThrows(AuthenticationException.class, () ->
414                 authscheme.generateAuthResponse(host, request, null));
415     }
416 
417     /**
418      * Test digest authentication with unknown qop value
419      */
420     @Test
421     void testDigestAuthenticationUnknownAlgo() throws Exception {
422         // Example using Digest auth with MD5-sess
423 
424         final String realm = "realm";
425         final String username = "username";
426         final String password = "password";
427         final String nonce = "e273f1776275974f1a120d8b92c5b3cb";
428 
429         final HttpRequest request = new BasicHttpRequest("Simple", "/");
430         final HttpHost host = new HttpHost("somehost", 80);
431         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
432                 .add(new AuthScope(host, realm, null), username, password.toCharArray())
433                 .build();
434 
435         final String challenge = StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", "
436             + "nonce=\"" + nonce + "\", "
437             + "opaque=\"SomeString\", "
438             + "stale=false, "
439             + "algorithm=stuff, "
440             + "qop=\"auth\"";
441 
442         final AuthChallenge authChallenge = parse(challenge);
443 
444         final DigestScheme authscheme = new DigestScheme();
445         authscheme.processChallenge(authChallenge, null);
446 
447         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
448         Assertions.assertThrows(AuthenticationException.class, () ->
449                 authscheme.generateAuthResponse(host, request, null));
450     }
451 
452     @Test
453     void testDigestAuthenticationWithStaleNonce() throws Exception {
454         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", " +
455                 "nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", stale=\"true\"";
456         final AuthChallenge authChallenge = parse(challenge);
457         final AuthScheme authscheme = new DigestScheme();
458         authscheme.processChallenge(authChallenge, null);
459 
460         Assertions.assertFalse(authscheme.isChallengeComplete());
461     }
462 
463     private static Map<String, String> parseAuthResponse(final String authResponse) {
464         if (!authResponse.startsWith(StandardAuthScheme.DIGEST + " ")) {
465             return null;
466         }
467         final String s = authResponse.substring(7);
468         final ParserCursor cursor = new ParserCursor(0, s.length());
469         final HeaderElement[] elements = BasicHeaderValueParser.INSTANCE.parseElements(s, cursor);
470         final Map<String, String> map = new HashMap<>(elements.length);
471         for (final HeaderElement element : elements) {
472             map.put(element.getName(), element.getValue());
473         }
474         return map;
475     }
476 
477     @Test
478     void testDigestNouceCount() throws Exception {
479         final HttpRequest request = new BasicHttpRequest("GET", "/");
480         final HttpHost host = new HttpHost("somehost", 80);
481         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
482                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
483                 .build();
484 
485         final String challenge1 = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=auth";
486         final AuthChallenge authChallenge1 = parse(challenge1);
487 
488         final DigestScheme authscheme = new DigestScheme();
489         authscheme.processChallenge(authChallenge1, null);
490         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
491         final String authResponse1 = authscheme.generateAuthResponse(host, request, null);
492 
493         final Map<String, String> table1 = parseAuthResponse(authResponse1);
494         Assertions.assertEquals("00000001", table1.get("nc"));
495 
496         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
497         final String authResponse2 = authscheme.generateAuthResponse(host, request, null);
498 
499         final Map<String, String> table2 = parseAuthResponse(authResponse2);
500         Assertions.assertEquals("00000002", table2.get("nc"));
501         final String challenge2 = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=auth";
502         final AuthChallenge authChallenge2 = parse(challenge2);
503         authscheme.processChallenge(authChallenge2, null);
504 
505         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
506         final String authResponse3 = authscheme.generateAuthResponse(host, request, null);
507 
508         final Map<String, String> table3 = parseAuthResponse(authResponse3);
509         Assertions.assertEquals("00000003", table3.get("nc"));
510         final String challenge3 = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"e273f1776275974f1a120d8b92c5b3cb\", qop=auth";
511         final AuthChallenge authChallenge3 = parse(challenge3);
512         authscheme.processChallenge(authChallenge3, null);
513 
514         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
515         final String authResponse4 = authscheme.generateAuthResponse(host, request, null);
516 
517         final Map<String, String> table4 = parseAuthResponse(authResponse4);
518         Assertions.assertEquals("00000001", table4.get("nc"));
519     }
520 
521     @Test
522     void testDigestMD5SessA1AndCnonceConsistency() throws Exception {
523         final HttpHost host = new HttpHost("somehost", 80);
524         final HttpRequest request = new BasicHttpRequest("GET", "/");
525         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
526                 .add(new AuthScope(host, "subnet.domain.com", null), "username", "password".toCharArray())
527                 .build();
528 
529         final String challenge1 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=MD5-sess, nonce=\"1234567890abcdef\", " +
530                 "charset=utf-8, realm=\"subnet.domain.com\"";
531         final AuthChallenge authChallenge1 = parse(challenge1);
532         final DigestScheme authscheme = new DigestScheme();
533         authscheme.processChallenge(authChallenge1, null);
534         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
535         final String authResponse1 = authscheme.generateAuthResponse(host, request, null);
536 
537         final Map<String, String> table1 = parseAuthResponse(authResponse1);
538         Assertions.assertEquals("00000001", table1.get("nc"));
539         final String cnonce1 = authscheme.getCnonce();
540         final String sessionKey1 = authscheme.getA1();
541 
542         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
543         final String authResponse2 = authscheme.generateAuthResponse(host, request, null);
544         final Map<String, String> table2 = parseAuthResponse(authResponse2);
545         Assertions.assertEquals("00000002", table2.get("nc"));
546         final String cnonce2 = authscheme.getCnonce();
547         final String sessionKey2 = authscheme.getA1();
548 
549         Assertions.assertEquals(cnonce1, cnonce2);
550         Assertions.assertEquals(sessionKey1, sessionKey2);
551 
552         final String challenge2 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=MD5-sess, nonce=\"1234567890abcdef\", " +
553                 "charset=utf-8, realm=\"subnet.domain.com\"";
554         final AuthChallenge authChallenge2 = parse(challenge2);
555         authscheme.processChallenge(authChallenge2, null);
556         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
557         final String authResponse3 = authscheme.generateAuthResponse(host, request, null);
558         final Map<String, String> table3 = parseAuthResponse(authResponse3);
559         Assertions.assertEquals("00000003", table3.get("nc"));
560 
561         final String cnonce3 = authscheme.getCnonce();
562         final String sessionKey3 = authscheme.getA1();
563 
564         Assertions.assertEquals(cnonce1, cnonce3);
565         Assertions.assertEquals(sessionKey1, sessionKey3);
566 
567         final String challenge3 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=MD5-sess, nonce=\"fedcba0987654321\", " +
568                 "charset=utf-8, realm=\"subnet.domain.com\"";
569         final AuthChallenge authChallenge3 = parse(challenge3);
570         authscheme.processChallenge(authChallenge3, null);
571         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
572         final String authResponse4 = authscheme.generateAuthResponse(host, request, null);
573         final Map<String, String> table4 = parseAuthResponse(authResponse4);
574         Assertions.assertEquals("00000001", table4.get("nc"));
575 
576         final String cnonce4 = authscheme.getCnonce();
577         final String sessionKey4 = authscheme.getA1();
578 
579         Assertions.assertNotEquals(cnonce1, cnonce4);
580         Assertions.assertNotEquals(sessionKey1, sessionKey4);
581     }
582 
583     @Test
584     void testHttpEntityDigest() throws Exception {
585         final HttpEntityDigester digester = new HttpEntityDigester(MessageDigest.getInstance("MD5"));
586         Assertions.assertNull(digester.getDigest());
587         digester.write('a');
588         digester.write('b');
589         digester.write('c');
590         digester.write(0xe4);
591         digester.write(0xf6);
592         digester.write(0xfc);
593         digester.write(new byte[]{'a', 'b', 'c'});
594         Assertions.assertNull(digester.getDigest());
595         digester.close();
596         Assertions.assertEquals("acd2b59cd01c7737d8069015584c6cac", DigestScheme.formatHex(digester.getDigest()));
597         Assertions.assertThrows(IOException.class, () -> digester.write('a'));
598         Assertions.assertThrows(IOException.class, () -> digester.write(new byte[]{'a', 'b', 'c'}));
599     }
600 
601     @Test
602     void testDigestAuthenticationQopAuthInt() throws Exception {
603         final ClassicHttpRequest request = new BasicClassicHttpRequest("Post", "/");
604         request.setEntity(new StringEntity("abc\u00e4\u00f6\u00fcabc", StandardCharsets.ISO_8859_1));
605         final HttpHost host = new HttpHost("somehost", 80);
606         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
607                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
608                 .build();
609 
610         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
611                 "qop=\"auth,auth-int\"";
612         final AuthChallenge authChallenge = parse(challenge);
613         final DigestScheme authscheme = new DigestScheme();
614         authscheme.processChallenge(authChallenge, null);
615         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
616         final String authResponse = authscheme.generateAuthResponse(host, request, null);
617 
618         Assertions.assertEquals("Post:/:acd2b59cd01c7737d8069015584c6cac", authscheme.getA2());
619 
620         final Map<String, String> table = parseAuthResponse(authResponse);
621         Assertions.assertEquals("username", table.get("username"));
622         Assertions.assertEquals("realm1", table.get("realm"));
623         Assertions.assertEquals("/", table.get("uri"));
624         Assertions.assertEquals("auth-int", table.get("qop"));
625         Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
626     }
627 
628     @Test
629     void testDigestAuthenticationQopAuthIntNullEntity() throws Exception {
630         final HttpRequest request = new BasicHttpRequest("Post", "/");
631         final HttpHost host = new HttpHost("somehost", 80);
632         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
633                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
634                 .build();
635 
636         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
637                 "qop=\"auth-int\"";
638         final AuthChallenge authChallenge = parse(challenge);
639         final DigestScheme authscheme = new DigestScheme();
640         authscheme.processChallenge(authChallenge, null);
641         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
642         final String authResponse = authscheme.generateAuthResponse(host, request, null);
643 
644         Assertions.assertEquals("Post:/:d41d8cd98f00b204e9800998ecf8427e", authscheme.getA2());
645 
646         final Map<String, String> table = parseAuthResponse(authResponse);
647         Assertions.assertEquals("username", table.get("username"));
648         Assertions.assertEquals("realm1", table.get("realm"));
649         Assertions.assertEquals("/", table.get("uri"));
650         Assertions.assertEquals("auth-int", table.get("qop"));
651         Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
652     }
653 
654     @Test
655     void testDigestAuthenticationQopAuthOrAuthIntNonRepeatableEntity() throws Exception {
656         final ClassicHttpRequest request = new BasicClassicHttpRequest("Post", "/");
657         request.setEntity(new InputStreamEntity(new ByteArrayInputStream(new byte[]{'a'}), -1, ContentType.DEFAULT_TEXT));
658         final HttpHost host = new HttpHost("somehost", 80);
659         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
660                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
661                 .build();
662 
663         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
664                 "qop=\"auth,auth-int\"";
665         final AuthChallenge authChallenge = parse(challenge);
666         final DigestScheme authscheme = new DigestScheme();
667         authscheme.processChallenge(authChallenge, null);
668         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
669         final String authResponse = authscheme.generateAuthResponse(host, request, null);
670 
671         Assertions.assertEquals("Post:/", authscheme.getA2());
672 
673         final Map<String, String> table = parseAuthResponse(authResponse);
674         Assertions.assertEquals("username", table.get("username"));
675         Assertions.assertEquals("realm1", table.get("realm"));
676         Assertions.assertEquals("/", table.get("uri"));
677         Assertions.assertEquals("auth", table.get("qop"));
678         Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
679     }
680 
681     @Test
682     void testParameterCaseSensitivity() throws Exception {
683         final HttpRequest request = new BasicHttpRequest("GET", "/");
684         final HttpHost host = new HttpHost("somehost", 80);
685         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
686                 .add(new AuthScope(host, "-", null), "username", "password".toCharArray())
687                 .build();
688 
689         final String challenge = StandardAuthScheme.DIGEST + " Realm=\"-\", " +
690                 "nonce=\"YjYuNGYyYmJhMzUuY2I5ZDhlZDE5M2ZlZDM 1Mjk3NGJkNTIyYjgyNTcwMjQ=\", " +
691                 "opaque=\"98700A3D9CE17065E2246B41035C6609\", qop=\"auth\"";
692         final AuthChallenge authChallenge = parse(challenge);
693         final DigestScheme authscheme = new DigestScheme();
694         authscheme.processChallenge(authChallenge, null);
695         Assertions.assertEquals("-", authscheme.getRealm());
696 
697         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
698         authscheme.generateAuthResponse(host, request, null);
699     }
700 
701     @Test
702     void testDigestAuthenticationQopIntOnlyNonRepeatableEntity() throws Exception {
703         final ClassicHttpRequest request = new BasicClassicHttpRequest("Post", "/");
704         request.setEntity(new InputStreamEntity(new ByteArrayInputStream(new byte[]{'a'}), -1, ContentType.DEFAULT_TEXT));
705         final HttpHost host = new HttpHost("somehost", 80);
706         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
707                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
708                 .build();
709 
710         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
711                 "qop=\"auth-int\"";
712         final AuthChallenge authChallenge = parse(challenge);
713         final DigestScheme authscheme = new DigestScheme();
714         authscheme.processChallenge(authChallenge, null);
715 
716         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
717         Assertions.assertThrows(AuthenticationException.class, () ->
718                 authscheme.generateAuthResponse(host, request, null));
719     }
720 
721     @Test
722     void testSerialization() throws Exception {
723         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
724                 "qop=\"auth,auth-int\"";
725         final AuthChallenge authChallenge = parse(challenge);
726         final DigestScheme digestScheme = new DigestScheme();
727         digestScheme.processChallenge(authChallenge, null);
728 
729         final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
730         final ObjectOutputStream out = new ObjectOutputStream(buffer);
731         out.writeObject(digestScheme);
732         out.flush();
733         final byte[] raw = buffer.toByteArray();
734         final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(raw));
735         final DigestScheme authScheme = (DigestScheme) in.readObject();
736 
737         Assertions.assertEquals(digestScheme.getName(), authScheme.getName());
738         Assertions.assertEquals(digestScheme.getRealm(), authScheme.getRealm());
739         Assertions.assertEquals(digestScheme.isChallengeComplete(), authScheme.isChallengeComplete());
740         Assertions.assertEquals(digestScheme.getA1(), authScheme.getA1());
741         Assertions.assertEquals(digestScheme.getA2(), authScheme.getA2());
742         Assertions.assertEquals(digestScheme.getCnonce(), authScheme.getCnonce());
743     }
744 
745 
746     @Test
747     void testDigestAuthenticationWithUserHash() throws Exception {
748         final HttpRequest request = new BasicHttpRequest("Simple", "/");
749         final HttpHost host = new HttpHost("somehost", 80);
750         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
751                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
752                 .build();
753 
754         // Include userhash in the challenge
755         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", userhash=true";
756         final AuthChallenge authChallenge = parse(challenge);
757         final DigestScheme authscheme = new DigestScheme();
758         authscheme.processChallenge(authChallenge, null);
759 
760         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
761         final String authResponse = authscheme.generateAuthResponse(host, request, null);
762 
763         final Map<String, String> table = parseAuthResponse(authResponse);
764 
765         // Generate expected userhash
766         final MessageDigest md = MessageDigest.getInstance("MD5");
767         md.update("username:realm1".getBytes(StandardCharsets.UTF_8));
768         final String expectedUserhash = bytesToHex(md.digest());
769 
770         Assertions.assertEquals(expectedUserhash, table.get("username"));
771         Assertions.assertEquals("realm1", table.get("realm"));
772         Assertions.assertEquals("/", table.get("uri"));
773         Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
774         Assertions.assertEquals("3b6561ceb73e5ffe9314a695179f23f9", table.get("response"));
775     }
776 
777     private static String bytesToHex(final byte[] bytes) {
778         final StringBuilder hexString = new StringBuilder();
779         for (final byte b : bytes) {
780             final String hex = Integer.toHexString(0xff & b);
781             if (hex.length() == 1) {
782                 hexString.append('0');
783             }
784             hexString.append(hex);
785         }
786         return hexString.toString();
787     }
788 
789     @Test
790     void testDigestAuthenticationWithQuotedStringsAndWhitespace() throws Exception {
791         final HttpRequest request = new BasicHttpRequest("Simple", "/");
792         final HttpHost host = new HttpHost("somehost", 80);
793         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
794                 .add(new AuthScope(host, "\"myhost@example.com\"", null), "\"Mufasa\"", "\"Circle Of Life\"".toCharArray())
795                 .build();
796 
797         // Include userhash in the challenge
798         final String challenge = StandardAuthScheme.DIGEST + " realm=\"\\\"myhost@example.com\\\"\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", userhash=true";
799         final AuthChallenge authChallenge = parse(challenge);
800         final DigestScheme authscheme = new DigestScheme();
801         authscheme.processChallenge(authChallenge, null);
802 
803         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
804 
805         final String authResponse = authscheme.generateAuthResponse(host, request, null);
806 
807         final Map<String, String> table = parseAuthResponse(authResponse);
808 
809         // Generate expected A1 hash
810         final MessageDigest md = MessageDigest.getInstance("MD5");
811         final String a1 = "Mufasa:myhost@example.com:Circle Of Life"; // Note: quotes removed and internal whitespace preserved
812         md.update(a1.getBytes(StandardCharsets.UTF_8));
813 
814         // Extract the response and validate the A1 hash
815         final String response = table.get("response");
816         Assertions.assertNotNull(response);
817     }
818 
819     @Test
820     void testDigestAuthenticationWithInvalidUsernameAndValidUsernameStar() throws Exception {
821         final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/");
822         final HttpHost host = new HttpHost("somehost", 80);
823         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
824                 .add(new AuthScope(host, "realm1", null), "invalid\"username", "password".toCharArray())
825                 .build();
826 
827         final String encodedUsername = "UTF-8''J%C3%A4s%C3%B8n%20Doe";
828         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
829                 "qop=\"auth-int\", username*=\"" + encodedUsername + "\"";
830         final AuthChallenge authChallenge = parse(challenge);
831         final DigestScheme authscheme = new DigestScheme();
832         authscheme.processChallenge(authChallenge, null);
833 
834         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
835 
836         final String authResponse = authscheme.generateAuthResponse(host, request, null);
837         Assertions.assertNotNull(authResponse);
838     }
839 
840     @Test
841     void testDigestAuthenticationWithHighAsciiCharInUsername() throws Exception {
842         final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/");
843         final HttpHost host = new HttpHost("somehost", 80);
844         // Using a username with a high ASCII character
845         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
846                 .add(new AuthScope(host, "realm1", null), "high\u007Fchar", "password".toCharArray())
847                 .build();
848 
849         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=\"auth-int\"";
850         final AuthChallenge authChallenge = parse(challenge);
851         final DigestScheme authscheme = new DigestScheme();
852         authscheme.processChallenge(authChallenge, null);
853 
854         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
855         final String authResponse = authscheme.generateAuthResponse(host, request, null);
856 
857         // Optionally, verify that 'username*' is used in the response
858         Assertions.assertTrue(authResponse.contains("username*"));
859     }
860 
861 
862     @Test
863     void testDigestAuthenticationWithExtendedAsciiCharInUsername() throws Exception {
864         final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/");
865         final HttpHost host = new HttpHost("somehost", 80);
866         // Using an extended ASCII character (e.g., 0x80) in the username
867         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
868                 .add(new AuthScope(host, "realm1", null), "username\u0080", "password".toCharArray())
869                 .build();
870 
871         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=\"auth-int\"";
872         final AuthChallenge authChallenge = parse(challenge);
873         final DigestScheme authscheme = new DigestScheme();
874         authscheme.processChallenge(authChallenge, null);
875 
876         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
877         final String authResponse = authscheme.generateAuthResponse(host, request, null);
878 
879 
880         Assertions.assertTrue(authResponse.contains("username*"));
881     }
882 
883 
884     @Test
885     void testDigestAuthenticationWithNonAsciiUsername() throws Exception {
886         final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/");
887         final HttpHost host = new HttpHost("somehost", 80);
888         // Using a username with non-ASCII characters
889         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
890                 .add(new AuthScope(host, "realm1", null), "Jäsøn Doe", "password".toCharArray())
891                 .build();
892 
893         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=\"auth-int\"";
894         final AuthChallenge authChallenge = parse(challenge);
895         final DigestScheme authscheme = new DigestScheme();
896         authscheme.processChallenge(authChallenge, null);
897 
898         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
899         final String authResponse = authscheme.generateAuthResponse(host, request, null);
900 
901         // Optionally, verify that 'username*' is used in the response
902         Assertions.assertTrue(authResponse.contains("username*"));
903     }
904 
905     @Test
906     void testRspAuthFieldAndQuoting() throws Exception {
907         final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/");
908         final HttpHost host = new HttpHost("somehost", 80);
909         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
910                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
911                 .build();
912 
913         // Challenge with qop set to "auth-int" to trigger rspauth field
914         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=\"auth-int\"";
915         final AuthChallenge authChallenge = parse(challenge);
916         final DigestScheme authscheme = new DigestScheme();
917         authscheme.processChallenge(authChallenge, null);
918 
919         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
920         final String authResponse = authscheme.generateAuthResponse(host, request, null);
921 
922         final Map<String, String> table = parseAuthResponse(authResponse);
923 
924         Assertions.assertNotNull(table.get("rspauth"));
925     }
926 
927     @Test
928     void testNextNonceUsageFromContext() throws Exception {
929         final HttpRequest request = new BasicHttpRequest("GET", "/");
930         final HttpHost host = new HttpHost("somehost", 80);
931         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
932                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
933                 .build();
934 
935         final HttpClientContext context = HttpClientContext.create();
936         context.setNextNonce("sampleNextNonce"); // Set `nextNonce` in the context
937 
938         final DigestScheme authscheme = new DigestScheme();
939         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"initialNonce\"";
940         final AuthChallenge authChallenge = parse(challenge);
941         authscheme.processChallenge(authChallenge, context);
942 
943         // Simulate credentials validation and generate auth response
944         authscheme.isResponseReady(host, credentialsProvider, context);
945         final String authResponse = authscheme.generateAuthResponse(host, request, context);
946 
947         // Validate that `sampleNextNonce` (from context) is used in place of the initial nonce
948         final Map<String, String> paramMap = parseAuthResponse(authResponse);
949         Assertions.assertEquals("sampleNextNonce", paramMap.get("nonce"), "The nonce should match 'auth-nextnonce' from the context.");
950 
951         // Verify that `auth-nextnonce` was removed from context after use
952         Assertions.assertNull(context.getAttribute("auth-nextnonce"), "The next nonce should be removed from the context after use.");
953     }
954 
955 
956     @Test
957     void testNoNextNonceUsageFromContext() throws Exception {
958         final HttpRequest request = new BasicHttpRequest("GET", "/");
959         final HttpHost host = new HttpHost("somehost", 80);
960         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
961                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
962                 .build();
963 
964         final HttpClientContext context = HttpClientContext.create();
965 
966         // Initialize DigestScheme without setting any `nextNonce`
967         final DigestScheme authscheme = new DigestScheme();
968         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"initialNonce\"";
969         final AuthChallenge authChallenge = parse(challenge);
970         authscheme.processChallenge(authChallenge, context);
971 
972         // Simulate credentials validation and generate auth response
973         authscheme.isResponseReady(host, credentialsProvider, context);
974         final String authResponse = authscheme.generateAuthResponse(host, request, context);
975 
976         // Validate that the nonce in the response matches the initial nonce, not a nextNonce
977         final Map<String, String> paramMap = parseAuthResponse(authResponse);
978         Assertions.assertEquals("initialNonce", paramMap.get("nonce"), "The nonce should match the initial nonce provided in the challenge.");
979 
980         // Verify that the context does not contain any `nextNonce` value set
981         Assertions.assertNull(context.getAttribute("auth-nextnonce"), "The context should not contain a nextNonce attribute.");
982     }
983 
984 
985     @Test
986     void testDigestAuthenticationWithSHA256() throws Exception {
987         final HttpRequest request = new BasicHttpRequest("Simple", "/");
988         final HttpHost host = new HttpHost("somehost", 80);
989         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
990                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
991                 .build();
992 
993         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", algorithm=SHA-256";
994         final AuthChallenge authChallenge = parse(challenge);
995         final DigestScheme authscheme = new DigestScheme();
996         authscheme.processChallenge(authChallenge, null);
997 
998         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
999         final String authResponse = authscheme.generateAuthResponse(host, request, null);
1000 
1001         final Map<String, String> table = parseAuthResponse(authResponse);
1002         Assertions.assertEquals("username", table.get("username"));
1003         Assertions.assertEquals("realm1", table.get("realm"));
1004         Assertions.assertEquals("/", table.get("uri"));
1005         Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
1006         Assertions.assertEquals("SHA-256", table.get("algorithm"));
1007         Assertions.assertNotNull(table.get("response"));
1008 
1009     }
1010 
1011     @Test
1012     void testDigestAuthenticationWithSHA512_256() throws Exception {
1013         final HttpRequest request = new BasicHttpRequest("Simple", "/");
1014         final HttpHost host = new HttpHost("somehost", 80);
1015         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
1016                 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
1017                 .build();
1018 
1019             final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", algorithm=SHA-512-256";
1020         final AuthChallenge authChallenge = parse(challenge);
1021         final DigestScheme authscheme = new DigestScheme();
1022         authscheme.processChallenge(authChallenge, null);
1023 
1024         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
1025         final String authResponse = authscheme.generateAuthResponse(host, request, null);
1026 
1027         final Map<String, String> table = parseAuthResponse(authResponse);
1028         Assertions.assertEquals("username", table.get("username"));
1029         Assertions.assertEquals("realm1", table.get("realm"));
1030         Assertions.assertEquals("/", table.get("uri"));
1031         Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
1032         Assertions.assertEquals("SHA-512-256", table.get("algorithm"));
1033         Assertions.assertNotNull(table.get("response"));
1034     }
1035 
1036     @Test
1037     void testDigestSHA256SessA1AndCnonceConsistency() throws Exception {
1038         final HttpHost host = new HttpHost("somehost", 80);
1039         final HttpRequest request = new BasicHttpRequest("GET", "/");
1040         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
1041                 .add(new AuthScope(host, "subnet.domain.com", null), "username", "password".toCharArray())
1042                 .build();
1043 
1044         final String challenge1 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=SHA-256-sess, nonce=\"1234567890abcdef\", " +
1045                 "charset=utf-8, realm=\"subnet.domain.com\"";
1046         final AuthChallenge authChallenge1 = parse(challenge1);
1047         final DigestScheme authscheme = new DigestScheme();
1048         authscheme.processChallenge(authChallenge1, null);
1049         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
1050         final String authResponse1 = authscheme.generateAuthResponse(host, request, null);
1051 
1052         final Map<String, String> table1 = parseAuthResponse(authResponse1);
1053         Assertions.assertEquals("00000001", table1.get("nc"));
1054         final String cnonce1 = authscheme.getCnonce();
1055         final String sessionKey1 = authscheme.getA1();
1056 
1057         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
1058         final String authResponse2 = authscheme.generateAuthResponse(host, request, null);
1059         final Map<String, String> table2 = parseAuthResponse(authResponse2);
1060         Assertions.assertEquals("00000002", table2.get("nc"));
1061         final String cnonce2 = authscheme.getCnonce();
1062         final String sessionKey2 = authscheme.getA1();
1063 
1064         Assertions.assertEquals(cnonce1, cnonce2);
1065         Assertions.assertEquals(sessionKey1, sessionKey2);
1066 
1067         final String challenge2 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=SHA-256-sess, nonce=\"1234567890abcdef\", " +
1068                 "charset=utf-8, realm=\"subnet.domain.com\"";
1069         final AuthChallenge authChallenge2 = parse(challenge2);
1070         authscheme.processChallenge(authChallenge2, null);
1071         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
1072         final String authResponse3 = authscheme.generateAuthResponse(host, request, null);
1073         final Map<String, String> table3 = parseAuthResponse(authResponse3);
1074         Assertions.assertEquals("00000003", table3.get("nc"));
1075 
1076         final String cnonce3 = authscheme.getCnonce();
1077         final String sessionKey3 = authscheme.getA1();
1078 
1079         Assertions.assertEquals(cnonce1, cnonce3);
1080         Assertions.assertEquals(sessionKey1, sessionKey3);
1081 
1082         final String challenge3 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=SHA-256-sess, nonce=\"fedcba0987654321\", " +
1083                 "charset=utf-8, realm=\"subnet.domain.com\"";
1084         final AuthChallenge authChallenge3 = parse(challenge3);
1085         authscheme.processChallenge(authChallenge3, null);
1086         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
1087         final String authResponse4 = authscheme.generateAuthResponse(host, request, null);
1088         final Map<String, String> table4 = parseAuthResponse(authResponse4);
1089         Assertions.assertEquals("00000001", table4.get("nc"));
1090 
1091         final String cnonce4 = authscheme.getCnonce();
1092         final String sessionKey4 = authscheme.getA1();
1093 
1094         Assertions.assertNotEquals(cnonce1, cnonce4);
1095         Assertions.assertNotEquals(sessionKey1, sessionKey4);
1096     }
1097 
1098 
1099     @Test
1100     void testDigestSHA512256SessA1AndCnonceConsistency() throws Exception {
1101         final HttpHost host = new HttpHost("somehost", 80);
1102         final HttpRequest request = new BasicHttpRequest("GET", "/");
1103         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
1104                 .add(new AuthScope(host, "subnet.domain.com", null), "username", "password".toCharArray())
1105                 .build();
1106 
1107         final String challenge1 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=SHA-512-256-sess, nonce=\"1234567890abcdef\", " +
1108                 "charset=utf-8, realm=\"subnet.domain.com\"";
1109         final AuthChallenge authChallenge1 = parse(challenge1);
1110         final DigestScheme authscheme = new DigestScheme();
1111         authscheme.processChallenge(authChallenge1, null);
1112         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
1113         final String authResponse1 = authscheme.generateAuthResponse(host, request, null);
1114 
1115         final Map<String, String> table1 = parseAuthResponse(authResponse1);
1116         Assertions.assertEquals("00000001", table1.get("nc"));
1117         final String cnonce1 = authscheme.getCnonce();
1118         final String sessionKey1 = authscheme.getA1();
1119 
1120         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
1121         final String authResponse2 = authscheme.generateAuthResponse(host, request, null);
1122         final Map<String, String> table2 = parseAuthResponse(authResponse2);
1123         Assertions.assertEquals("00000002", table2.get("nc"));
1124         final String cnonce2 = authscheme.getCnonce();
1125         final String sessionKey2 = authscheme.getA1();
1126 
1127         Assertions.assertEquals(cnonce1, cnonce2);
1128         Assertions.assertEquals(sessionKey1, sessionKey2);
1129 
1130         final String challenge2 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=SHA-512-256-sess, nonce=\"1234567890abcdef\", " +
1131                 "charset=utf-8, realm=\"subnet.domain.com\"";
1132         final AuthChallenge authChallenge2 = parse(challenge2);
1133         authscheme.processChallenge(authChallenge2, null);
1134         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
1135         final String authResponse3 = authscheme.generateAuthResponse(host, request, null);
1136         final Map<String, String> table3 = parseAuthResponse(authResponse3);
1137         Assertions.assertEquals("00000003", table3.get("nc"));
1138 
1139         final String cnonce3 = authscheme.getCnonce();
1140         final String sessionKey3 = authscheme.getA1();
1141 
1142         Assertions.assertEquals(cnonce1, cnonce3);
1143         Assertions.assertEquals(sessionKey1, sessionKey3);
1144 
1145         final String challenge3 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=SHA-512-256-sess, nonce=\"fedcba0987654321\", " +
1146                 "charset=utf-8, realm=\"subnet.domain.com\"";
1147         final AuthChallenge authChallenge3 = parse(challenge3);
1148         authscheme.processChallenge(authChallenge3, null);
1149         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
1150         final String authResponse4 = authscheme.generateAuthResponse(host, request, null);
1151         final Map<String, String> table4 = parseAuthResponse(authResponse4);
1152         Assertions.assertEquals("00000001", table4.get("nc"));
1153 
1154         final String cnonce4 = authscheme.getCnonce();
1155         final String sessionKey4 = authscheme.getA1();
1156 
1157         Assertions.assertNotEquals(cnonce1, cnonce4);
1158         Assertions.assertNotEquals(sessionKey1, sessionKey4);
1159     }
1160 
1161 
1162 
1163 }