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.StandardAuthScheme;
43  import org.apache.hc.client5.http.auth.AuthScope;
44  import org.apache.hc.client5.http.auth.AuthenticationException;
45  import org.apache.hc.client5.http.auth.ChallengeType;
46  import org.apache.hc.client5.http.auth.Credentials;
47  import org.apache.hc.client5.http.auth.MalformedChallengeException;
48  import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
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.Assert;
63  import org.junit.Test;
64  
65  /**
66   * Test Methods for DigestScheme Authentication.
67   */
68  public 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          Assert.assertEquals(1, authChallenges.size());
76          return authChallenges.get(0);
77      }
78  
79      @Test(expected=MalformedChallengeException.class)
80      public void testDigestAuthenticationEmptyChallenge1() throws Exception {
81          final AuthChallenge authChallenge = parse(StandardAuthScheme.DIGEST);
82          final AuthScheme authscheme = new DigestScheme();
83          authscheme.processChallenge(authChallenge, null);
84      }
85  
86      @Test(expected=MalformedChallengeException.class)
87      public void testDigestAuthenticationEmptyChallenge2() throws Exception {
88          final AuthChallenge authChallenge = parse(StandardAuthScheme.DIGEST + " ");
89          final AuthScheme authscheme = new DigestScheme();
90          authscheme.processChallenge(authChallenge, null);
91      }
92  
93      @Test
94      public void testDigestAuthenticationWithDefaultCreds() throws Exception {
95          final HttpRequest request = new BasicHttpRequest("Simple", "/");
96          final HttpHost host = new HttpHost("somehost", 80);
97          final AuthScope authScope = new AuthScope(host, "realm1", null);
98          final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
99          final Credentials creds = new UsernamePasswordCredentials("username","password".toCharArray());
100         credentialsProvider.setCredentials(authScope, creds);
101 
102         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
103         final AuthChallenge authChallenge = parse(challenge);
104         final DigestScheme authscheme = new DigestScheme();
105         authscheme.processChallenge(authChallenge, null);
106 
107         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
108         final String authResponse = authscheme.generateAuthResponse(host, request, null);
109         Assert.assertTrue(authscheme.isChallengeComplete());
110         Assert.assertFalse(authscheme.isConnectionBased());
111 
112         final Map<String, String> table = parseAuthResponse(authResponse);
113         Assert.assertEquals("username", table.get("username"));
114         Assert.assertEquals("realm1", table.get("realm"));
115         Assert.assertEquals("/", table.get("uri"));
116         Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
117         Assert.assertEquals("e95a7ddf37c2eab009568b1ed134f89a", table.get("response"));
118     }
119 
120     @Test
121     public void testDigestAuthentication() throws Exception {
122         final HttpRequest request = new BasicHttpRequest("Simple", "/");
123         final HttpHost host = new HttpHost("somehost", 80);
124         final AuthScope authScope = new AuthScope(host, "realm1", null);
125         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
126         final Credentials creds = new UsernamePasswordCredentials("username","password".toCharArray());
127         credentialsProvider.setCredentials(authScope, creds);
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         Assert.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         Assert.assertEquals("username", table.get("username"));
139         Assert.assertEquals("realm1", table.get("realm"));
140         Assert.assertEquals("/", table.get("uri"));
141         Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
142         Assert.assertEquals("e95a7ddf37c2eab009568b1ed134f89a", table.get("response"));
143     }
144 
145     @Test
146     public void testDigestAuthenticationInvalidInput() throws Exception {
147         final HttpHost host = new HttpHost("somehost", 80);
148         final AuthScope authScope = new AuthScope(host, "realm1", null);
149         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
150         final Credentials creds = new UsernamePasswordCredentials("username","password".toCharArray());
151         credentialsProvider.setCredentials(authScope, creds);
152 
153         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
154         final AuthChallenge authChallenge = parse(challenge);
155         final DigestScheme authscheme = new DigestScheme();
156         authscheme.processChallenge(authChallenge, null);
157 
158         try {
159             authscheme.isResponseReady(null, credentialsProvider, null);
160             Assert.fail("NullPointerException should have been thrown");
161         } catch (final NullPointerException ex) {
162         }
163         try {
164             authscheme.isResponseReady(host, null, null);
165             Assert.fail("NullPointerException should have been thrown");
166         } catch (final NullPointerException ex) {
167         }
168         try {
169             authscheme.generateAuthResponse(host, null, null);
170             Assert.fail("NullPointerException should have been thrown");
171         } catch (final NullPointerException ex) {
172         }
173     }
174 
175     @Test
176     public void testDigestAuthenticationWithSHA() throws Exception {
177         final HttpRequest request = new BasicHttpRequest("Simple", "/");
178         final HttpHost host = new HttpHost("somehost", 80);
179         final AuthScope authScope = new AuthScope(host, "realm1", null);
180         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
181         final Credentials creds = new UsernamePasswordCredentials("username","password".toCharArray());
182         credentialsProvider.setCredentials(authScope, creds);
183 
184         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", " +
185                 "nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
186                 "algorithm=SHA";
187         final AuthChallenge authChallenge = parse(challenge);
188         final DigestScheme authscheme = new DigestScheme();
189         authscheme.processChallenge(authChallenge, null);
190 
191         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
192         final String authResponse = authscheme.generateAuthResponse(host, request, null);
193 
194         final Map<String, String> table = parseAuthResponse(authResponse);
195         Assert.assertEquals("username", table.get("username"));
196         Assert.assertEquals("realm1", table.get("realm"));
197         Assert.assertEquals("/", table.get("uri"));
198         Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
199         Assert.assertEquals("8769e82e4e28ecc040b969562b9050580c6d186d", table.get("response"));
200     }
201 
202     @Test
203     public void testDigestAuthenticationWithQueryStringInDigestURI() throws Exception {
204         final HttpRequest request = new BasicHttpRequest("Simple", "/?param=value");
205         final HttpHost host = new HttpHost("somehost", 80);
206         final AuthScope authScope = new AuthScope(host, "realm1", null);
207         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
208         final Credentials creds = new UsernamePasswordCredentials("username","password".toCharArray());
209         credentialsProvider.setCredentials(authScope, creds);
210 
211         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
212         final AuthChallenge authChallenge = parse(challenge);
213         final DigestScheme authscheme = new DigestScheme();
214         authscheme.processChallenge(authChallenge, null);
215 
216         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
217         final String authResponse = authscheme.generateAuthResponse(host, request, null);
218 
219         final Map<String, String> table = parseAuthResponse(authResponse);
220         Assert.assertEquals("username", table.get("username"));
221         Assert.assertEquals("realm1", table.get("realm"));
222         Assert.assertEquals("/?param=value", table.get("uri"));
223         Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
224         Assert.assertEquals("a847f58f5fef0bc087bcb9c3eb30e042", table.get("response"));
225     }
226 
227     @Test(expected=AuthenticationException.class)
228     public void testDigestAuthenticationNoRealm() throws Exception {
229         final HttpRequest request = new BasicHttpRequest("Simple", "/");
230         final HttpHost host = new HttpHost("somehost", 80);
231         final AuthScope authScope = new AuthScope(host, "realm1", null);
232         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
233         final Credentials creds = new UsernamePasswordCredentials("username","password".toCharArray());
234         credentialsProvider.setCredentials(authScope, creds);
235 
236         final String challenge = StandardAuthScheme.DIGEST + " no-realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
237         final AuthChallenge authChallenge = parse(challenge);
238         final DigestScheme authscheme = new DigestScheme();
239         authscheme.processChallenge(authChallenge, null);
240 
241         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
242         authscheme.generateAuthResponse(host, request, null);
243     }
244 
245     @Test(expected=AuthenticationException.class)
246     public void testDigestAuthenticationNoNonce() throws Exception {
247         final HttpRequest request = new BasicHttpRequest("Simple", "/");
248         final HttpHost host = new HttpHost("somehost", 80);
249         final AuthScope authScope = new AuthScope(host, "realm1", null);
250         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
251         final Credentials creds = new UsernamePasswordCredentials("username","password".toCharArray());
252         credentialsProvider.setCredentials(authScope, creds);
253 
254         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", no-nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
255         final AuthChallenge authChallenge = parse(challenge);
256         final DigestScheme authscheme = new DigestScheme();
257         authscheme.processChallenge(authChallenge, null);
258 
259         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
260         authscheme.generateAuthResponse(host, request, null);
261     }
262 
263     /**
264      * Test digest authentication using the MD5-sess algorithm.
265      */
266     @Test
267     public void testDigestAuthenticationMD5Sess() throws Exception {
268         // Example using Digest auth with MD5-sess
269 
270         final String realm="realm";
271         final String username="username";
272         final String password="password";
273         final String nonce="e273f1776275974f1a120d8b92c5b3cb";
274 
275         final HttpRequest request = new BasicHttpRequest("Simple", "/");
276         final HttpHost host = new HttpHost("somehost", 80);
277         final AuthScope authScope = new AuthScope(host, realm, null);
278         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
279         final Credentials creds = new UsernamePasswordCredentials(username, password.toCharArray());
280         credentialsProvider.setCredentials(authScope, creds);
281 
282         final String challenge=StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", "
283             + "nonce=\"" + nonce + "\", "
284             + "opaque=\"SomeString\", "
285             + "stale=false, "
286             + "algorithm=MD5-sess, "
287             + "qop=\"auth,auth-int\""; // we pass both but expect auth to be used
288 
289         final AuthChallenge authChallenge = parse(challenge);
290 
291         final DigestScheme authscheme = new DigestScheme();
292         authscheme.processChallenge(authChallenge, null);
293 
294         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
295         final String authResponse = authscheme.generateAuthResponse(host, request, null);
296 
297         Assert.assertTrue(authResponse.indexOf("nc=00000001") > 0); // test for quotes
298         Assert.assertTrue(authResponse.indexOf("qop=auth") > 0); // test for quotes
299 
300         final Map<String, String> table = parseAuthResponse(authResponse);
301         Assert.assertEquals(username, table.get("username"));
302         Assert.assertEquals(realm, table.get("realm"));
303         Assert.assertEquals("MD5-sess", table.get("algorithm"));
304         Assert.assertEquals("/", table.get("uri"));
305         Assert.assertEquals(nonce, table.get("nonce"));
306         Assert.assertEquals(1, Integer.parseInt(table.get("nc"),16));
307         Assert.assertTrue(null != table.get("cnonce"));
308         Assert.assertEquals("SomeString", table.get("opaque"));
309         Assert.assertEquals("auth", table.get("qop"));
310         //@TODO: add better check
311         Assert.assertTrue(null != table.get("response"));
312     }
313 
314     /**
315      * Test digest authentication using the MD5-sess algorithm.
316      */
317     @Test
318     public void testDigestAuthenticationMD5SessNoQop() throws Exception {
319         // Example using Digest auth with MD5-sess
320 
321         final String realm="realm";
322         final String username="username";
323         final String password="password";
324         final String nonce="e273f1776275974f1a120d8b92c5b3cb";
325 
326         final HttpRequest request = new BasicHttpRequest("Simple", "/");
327         final HttpHost host = new HttpHost("somehost", 80);
328         final AuthScope authScope = new AuthScope(host, realm, null);
329         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
330         final Credentials creds = new UsernamePasswordCredentials(username, password.toCharArray());
331         credentialsProvider.setCredentials(authScope, creds);
332 
333         final String challenge=StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", "
334             + "nonce=\"" + nonce + "\", "
335             + "opaque=\"SomeString\", "
336             + "stale=false, "
337             + "algorithm=MD5-sess";
338 
339         final AuthChallenge authChallenge = parse(challenge);
340 
341         final DigestScheme authscheme = new DigestScheme();
342         authscheme.processChallenge(authChallenge, null);
343         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
344         final String authResponse = authscheme.generateAuthResponse(host, request, null);
345 
346         final Map<String, String> table = parseAuthResponse(authResponse);
347         Assert.assertEquals(username, table.get("username"));
348         Assert.assertEquals(realm, table.get("realm"));
349         Assert.assertEquals("MD5-sess", table.get("algorithm"));
350         Assert.assertEquals("/", table.get("uri"));
351         Assert.assertEquals(nonce, table.get("nonce"));
352         Assert.assertTrue(null == table.get("nc"));
353         Assert.assertEquals("SomeString", table.get("opaque"));
354         Assert.assertTrue(null == table.get("qop"));
355         //@TODO: add better check
356         Assert.assertTrue(null != table.get("response"));
357     }
358 
359     /**
360      * Test digest authentication with unknown qop value
361      */
362     @Test(expected=AuthenticationException.class)
363     public void testDigestAuthenticationMD5SessUnknownQop() throws Exception {
364         // Example using Digest auth with MD5-sess
365 
366         final String realm="realm";
367         final String username="username";
368         final String password="password";
369         final String nonce="e273f1776275974f1a120d8b92c5b3cb";
370 
371         final HttpRequest request = new BasicHttpRequest("Simple", "/");
372         final HttpHost host = new HttpHost("somehost", 80);
373         final AuthScope authScope = new AuthScope(host, realm, null);
374         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
375         final Credentials creds = new UsernamePasswordCredentials(username, password.toCharArray());
376         credentialsProvider.setCredentials(authScope, creds);
377 
378         final String challenge=StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", "
379             + "nonce=\"" + nonce + "\", "
380             + "opaque=\"SomeString\", "
381             + "stale=false, "
382             + "algorithm=MD5-sess, "
383             + "qop=\"stuff\"";
384 
385         final AuthChallenge authChallenge = parse(challenge);
386 
387         final DigestScheme authscheme = new DigestScheme();
388         authscheme.processChallenge(authChallenge, null);
389 
390         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
391         authscheme.generateAuthResponse(host, request, null);
392     }
393 
394     /**
395      * Test digest authentication with unknown qop value
396      */
397     @Test(expected=AuthenticationException.class)
398     public void testDigestAuthenticationUnknownAlgo() throws Exception {
399         // Example using Digest auth with MD5-sess
400 
401         final String realm="realm";
402         final String username="username";
403         final String password="password";
404         final String nonce="e273f1776275974f1a120d8b92c5b3cb";
405 
406         final HttpRequest request = new BasicHttpRequest("Simple", "/");
407         final HttpHost host = new HttpHost("somehost", 80);
408         final AuthScope authScope = new AuthScope(host, realm, null);
409         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
410         final Credentials creds = new UsernamePasswordCredentials(username, password.toCharArray());
411         credentialsProvider.setCredentials(authScope, creds);
412 
413         final String challenge=StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", "
414             + "nonce=\"" + nonce + "\", "
415             + "opaque=\"SomeString\", "
416             + "stale=false, "
417             + "algorithm=stuff, "
418             + "qop=\"auth\"";
419 
420         final AuthChallenge authChallenge = parse(challenge);
421 
422         final DigestScheme authscheme = new DigestScheme();
423         authscheme.processChallenge(authChallenge, null);
424 
425         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
426         authscheme.generateAuthResponse(host, request, null);
427     }
428 
429     @Test
430     public void testDigestAuthenticationWithStaleNonce() throws Exception {
431         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", " +
432                 "nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", stale=\"true\"";
433         final AuthChallenge authChallenge = parse(challenge);
434         final AuthScheme authscheme = new DigestScheme();
435         authscheme.processChallenge(authChallenge, null);
436 
437         Assert.assertFalse(authscheme.isChallengeComplete());
438     }
439 
440     private static Map<String, String> parseAuthResponse(final String authResponse) {
441         if (!authResponse.startsWith(StandardAuthScheme.DIGEST + " ")) {
442             return null;
443         }
444         final String s = authResponse.substring(7);
445         final ParserCursor cursor = new ParserCursor(0, s.length());
446         final HeaderElement[] elements = BasicHeaderValueParser.INSTANCE.parseElements(s, cursor);
447         final Map<String, String> map = new HashMap<>(elements.length);
448         for (final HeaderElement element : elements) {
449             map.put(element.getName(), element.getValue());
450         }
451         return map;
452     }
453 
454     @Test
455     public void testDigestNouceCount() throws Exception {
456         final HttpRequest request = new BasicHttpRequest("GET", "/");
457         final HttpHost host = new HttpHost("somehost", 80);
458         final AuthScope authScope = new AuthScope(host, "realm1", null);
459         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
460         final Credentials creds = new UsernamePasswordCredentials("username","password".toCharArray());
461         credentialsProvider.setCredentials(authScope, creds);
462 
463         final String challenge1 = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=auth";
464         final AuthChallenge authChallenge1 = parse(challenge1);
465 
466         final DigestScheme authscheme = new DigestScheme();
467         authscheme.processChallenge(authChallenge1, null);
468         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
469         final String authResponse1 = authscheme.generateAuthResponse(host, request, null);
470 
471         final Map<String, String> table1 = parseAuthResponse(authResponse1);
472         Assert.assertEquals("00000001", table1.get("nc"));
473 
474         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
475         final String authResponse2 = authscheme.generateAuthResponse(host, request, null);
476 
477         final Map<String, String> table2 = parseAuthResponse(authResponse2);
478         Assert.assertEquals("00000002", table2.get("nc"));
479         final String challenge2 = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=auth";
480         final AuthChallenge authChallenge2 = parse(challenge2);
481         authscheme.processChallenge(authChallenge2, null);
482 
483         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
484         final String authResponse3 = authscheme.generateAuthResponse(host, request, null);
485 
486         final Map<String, String> table3 = parseAuthResponse(authResponse3);
487         Assert.assertEquals("00000003", table3.get("nc"));
488         final String challenge3 = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"e273f1776275974f1a120d8b92c5b3cb\", qop=auth";
489         final AuthChallenge authChallenge3 = parse(challenge3);
490         authscheme.processChallenge(authChallenge3, null);
491 
492         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
493         final String authResponse4 = authscheme.generateAuthResponse(host, request, null);
494 
495         final Map<String, String> table4 = parseAuthResponse(authResponse4);
496         Assert.assertEquals("00000001", table4.get("nc"));
497     }
498 
499     @Test
500     public void testDigestMD5SessA1AndCnonceConsistency() throws Exception {
501         final HttpHost host = new HttpHost("somehost", 80);
502         final AuthScope authScope = new AuthScope(host, "subnet.domain.com", null);
503         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
504         final HttpRequest request = new BasicHttpRequest("GET", "/");
505         final Credentials creds = new UsernamePasswordCredentials("username","password".toCharArray());
506         credentialsProvider.setCredentials(authScope, creds);
507 
508         final String challenge1 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=MD5-sess, nonce=\"1234567890abcdef\", " +
509                 "charset=utf-8, realm=\"subnet.domain.com\"";
510         final AuthChallenge authChallenge1 = parse(challenge1);
511         final DigestScheme authscheme = new DigestScheme();
512         authscheme.processChallenge(authChallenge1, null);
513         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
514         final String authResponse1 = authscheme.generateAuthResponse(host, request, null);
515 
516         final Map<String, String> table1 = parseAuthResponse(authResponse1);
517         Assert.assertEquals("00000001", table1.get("nc"));
518         final String cnonce1 = authscheme.getCnonce();
519         final String sessionKey1 = authscheme.getA1();
520 
521         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
522         final String authResponse2 = authscheme.generateAuthResponse(host, request, null);
523         final Map<String, String> table2 = parseAuthResponse(authResponse2);
524         Assert.assertEquals("00000002", table2.get("nc"));
525         final String cnonce2 = authscheme.getCnonce();
526         final String sessionKey2 = authscheme.getA1();
527 
528         Assert.assertEquals(cnonce1, cnonce2);
529         Assert.assertEquals(sessionKey1, sessionKey2);
530 
531         final String challenge2 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=MD5-sess, nonce=\"1234567890abcdef\", " +
532             "charset=utf-8, realm=\"subnet.domain.com\"";
533         final AuthChallenge authChallenge2 = parse(challenge2);
534         authscheme.processChallenge(authChallenge2, null);
535         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
536         final String authResponse3 = authscheme.generateAuthResponse(host, request, null);
537         final Map<String, String> table3 = parseAuthResponse(authResponse3);
538         Assert.assertEquals("00000003", table3.get("nc"));
539 
540         final String cnonce3 = authscheme.getCnonce();
541         final String sessionKey3 = authscheme.getA1();
542 
543         Assert.assertEquals(cnonce1, cnonce3);
544         Assert.assertEquals(sessionKey1, sessionKey3);
545 
546         final String challenge3 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=MD5-sess, nonce=\"fedcba0987654321\", " +
547             "charset=utf-8, realm=\"subnet.domain.com\"";
548         final AuthChallenge authChallenge3 = parse(challenge3);
549         authscheme.processChallenge(authChallenge3, null);
550         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
551         final String authResponse4 = authscheme.generateAuthResponse(host, request, null);
552         final Map<String, String> table4 = parseAuthResponse(authResponse4);
553         Assert.assertEquals("00000001", table4.get("nc"));
554 
555         final String cnonce4 = authscheme.getCnonce();
556         final String sessionKey4 = authscheme.getA1();
557 
558         Assert.assertFalse(cnonce1.equals(cnonce4));
559         Assert.assertFalse(sessionKey1.equals(sessionKey4));
560     }
561 
562     @Test
563     public void testHttpEntityDigest() throws Exception {
564         final HttpEntityDigester digester = new HttpEntityDigester(MessageDigest.getInstance("MD5"));
565         Assert.assertNull(digester.getDigest());
566         digester.write('a');
567         digester.write('b');
568         digester.write('c');
569         digester.write(0xe4);
570         digester.write(0xf6);
571         digester.write(0xfc);
572         digester.write(new byte[] { 'a', 'b', 'c'});
573         Assert.assertNull(digester.getDigest());
574         digester.close();
575         Assert.assertEquals("acd2b59cd01c7737d8069015584c6cac", DigestScheme.formatHex(digester.getDigest()));
576         try {
577             digester.write('a');
578             Assert.fail("IOException should have been thrown");
579         } catch (final IOException ex) {
580         }
581         try {
582             digester.write(new byte[] { 'a', 'b', 'c'});
583             Assert.fail("IOException should have been thrown");
584         } catch (final IOException ex) {
585         }
586     }
587 
588     @Test
589     public void testDigestAuthenticationQopAuthInt() throws Exception {
590         final ClassicHttpRequest request = new BasicClassicHttpRequest("Post", "/");
591         request.setEntity(new StringEntity("abc\u00e4\u00f6\u00fcabc", StandardCharsets.ISO_8859_1));
592         final HttpHost host = new HttpHost("somehost", 80);
593         final AuthScope authScope = new AuthScope(host, "realm1", null);
594         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
595         final Credentials creds = new UsernamePasswordCredentials("username","password".toCharArray());
596         credentialsProvider.setCredentials(authScope, creds);
597 
598         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
599                 "qop=\"auth,auth-int\"";
600         final AuthChallenge authChallenge = parse(challenge);
601         final DigestScheme authscheme = new DigestScheme();
602         authscheme.processChallenge(authChallenge, null);
603         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
604         final String authResponse = authscheme.generateAuthResponse(host, request, null);
605 
606         Assert.assertEquals("Post:/:acd2b59cd01c7737d8069015584c6cac", authscheme.getA2());
607 
608         final Map<String, String> table = parseAuthResponse(authResponse);
609         Assert.assertEquals("username", table.get("username"));
610         Assert.assertEquals("realm1", table.get("realm"));
611         Assert.assertEquals("/", table.get("uri"));
612         Assert.assertEquals("auth-int", table.get("qop"));
613         Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
614     }
615 
616     @Test
617     public void testDigestAuthenticationQopAuthIntNullEntity() throws Exception {
618         final HttpRequest request = new BasicHttpRequest("Post", "/");
619         final HttpHost host = new HttpHost("somehost", 80);
620         final AuthScope authScope = new AuthScope(host, "realm1", null);
621         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
622         final Credentials creds = new UsernamePasswordCredentials("username","password".toCharArray());
623         credentialsProvider.setCredentials(authScope, creds);
624 
625         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
626                 "qop=\"auth-int\"";
627         final AuthChallenge authChallenge = parse(challenge);
628         final DigestScheme authscheme = new DigestScheme();
629         authscheme.processChallenge(authChallenge, null);
630         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
631         final String authResponse = authscheme.generateAuthResponse(host, request, null);
632 
633         Assert.assertEquals("Post:/:d41d8cd98f00b204e9800998ecf8427e", authscheme.getA2());
634 
635         final Map<String, String> table = parseAuthResponse(authResponse);
636         Assert.assertEquals("username", table.get("username"));
637         Assert.assertEquals("realm1", table.get("realm"));
638         Assert.assertEquals("/", table.get("uri"));
639         Assert.assertEquals("auth-int", table.get("qop"));
640         Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
641     }
642 
643     @Test
644     public void testDigestAuthenticationQopAuthOrAuthIntNonRepeatableEntity() throws Exception {
645         final ClassicHttpRequest request = new BasicClassicHttpRequest("Post", "/");
646         request.setEntity(new InputStreamEntity(new ByteArrayInputStream(new byte[] {'a'}), -1, ContentType.DEFAULT_TEXT));
647         final HttpHost host = new HttpHost("somehost", 80);
648         final AuthScope authScope = new AuthScope(host, "realm1", null);
649         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
650         final Credentials creds = new UsernamePasswordCredentials("username","password".toCharArray());
651         credentialsProvider.setCredentials(authScope, creds);
652 
653         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
654                 "qop=\"auth,auth-int\"";
655         final AuthChallenge authChallenge = parse(challenge);
656         final DigestScheme authscheme = new DigestScheme();
657         authscheme.processChallenge(authChallenge, null);
658         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
659         final String authResponse = authscheme.generateAuthResponse(host, request, null);
660 
661         Assert.assertEquals("Post:/", authscheme.getA2());
662 
663         final Map<String, String> table = parseAuthResponse(authResponse);
664         Assert.assertEquals("username", table.get("username"));
665         Assert.assertEquals("realm1", table.get("realm"));
666         Assert.assertEquals("/", table.get("uri"));
667         Assert.assertEquals("auth", table.get("qop"));
668         Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
669     }
670 
671     @Test
672     public void testParameterCaseSensitivity() throws Exception {
673         final HttpRequest request = new BasicHttpRequest("GET", "/");
674         final HttpHost host = new HttpHost("somehost", 80);
675         final AuthScope authScope = new AuthScope(host, "-", null);
676         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
677         final Credentials creds = new UsernamePasswordCredentials("username","password".toCharArray());
678         credentialsProvider.setCredentials(authScope, creds);
679 
680         final String challenge = StandardAuthScheme.DIGEST + " Realm=\"-\", " +
681                 "nonce=\"YjYuNGYyYmJhMzUuY2I5ZDhlZDE5M2ZlZDM 1Mjk3NGJkNTIyYjgyNTcwMjQ=\", " +
682                 "opaque=\"98700A3D9CE17065E2246B41035C6609\", qop=\"auth\"";
683         final AuthChallenge authChallenge = parse(challenge);
684         final DigestScheme authscheme = new DigestScheme();
685         authscheme.processChallenge(authChallenge, null);
686         Assert.assertEquals("-", authscheme.getRealm());
687 
688         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
689         authscheme.generateAuthResponse(host, request, null);
690     }
691 
692     @Test(expected=AuthenticationException.class)
693     public void testDigestAuthenticationQopIntOnlyNonRepeatableEntity() throws Exception {
694         final ClassicHttpRequest request = new BasicClassicHttpRequest("Post", "/");
695         request.setEntity(new InputStreamEntity(new ByteArrayInputStream(new byte[] {'a'}), -1, ContentType.DEFAULT_TEXT));
696         final HttpHost host = new HttpHost("somehost", 80);
697         final AuthScope authScope = new AuthScope(host, "realm1", null);
698         final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
699         final Credentials creds = new UsernamePasswordCredentials("username","password".toCharArray());
700         credentialsProvider.setCredentials(authScope, creds);
701 
702         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
703                 "qop=\"auth-int\"";
704         final AuthChallenge authChallenge = parse(challenge);
705         final DigestScheme authscheme = new DigestScheme();
706         authscheme.processChallenge(authChallenge, null);
707 
708         Assert.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
709         authscheme.generateAuthResponse(host, request, null);
710     }
711 
712     @Test
713     public void testSerialization() throws Exception {
714         final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
715                 "qop=\"auth,auth-int\"";
716         final AuthChallenge authChallenge = parse(challenge);
717         final DigestScheme digestScheme = new DigestScheme();
718         digestScheme.processChallenge(authChallenge, null);
719 
720         final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
721         final ObjectOutputStream out = new ObjectOutputStream(buffer);
722         out.writeObject(digestScheme);
723         out.flush();
724         final byte[] raw = buffer.toByteArray();
725         final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(raw));
726         final DigestScheme authScheme = (DigestScheme) in.readObject();
727 
728         Assert.assertEquals(digestScheme.getName(), authScheme.getName());
729         Assert.assertEquals(digestScheme.getRealm(), authScheme.getRealm());
730         Assert.assertEquals(digestScheme.isChallengeComplete(), authScheme.isChallengeComplete());
731         Assert.assertEquals(digestScheme.getA1(), authScheme.getA1());
732         Assert.assertEquals(digestScheme.getA2(), authScheme.getA2());
733         Assert.assertEquals(digestScheme.getCnonce(), authScheme.getCnonce());
734     }
735 
736 }