1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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.core5.http.ClassicHttpRequest;
49 import org.apache.hc.core5.http.ContentType;
50 import org.apache.hc.core5.http.HeaderElement;
51 import org.apache.hc.core5.http.HttpHost;
52 import org.apache.hc.core5.http.HttpRequest;
53 import org.apache.hc.core5.http.ParseException;
54 import org.apache.hc.core5.http.io.entity.InputStreamEntity;
55 import org.apache.hc.core5.http.io.entity.StringEntity;
56 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
57 import org.apache.hc.core5.http.message.BasicHeaderValueParser;
58 import org.apache.hc.core5.http.message.BasicHttpRequest;
59 import org.apache.hc.core5.http.message.ParserCursor;
60 import org.apache.hc.core5.util.CharArrayBuffer;
61 import org.junit.jupiter.api.Assertions;
62 import org.junit.jupiter.api.Test;
63
64
65
66
67 class TestDigestScheme {
68
69 private static AuthChallenge parse(final String s) throws ParseException {
70 final CharArrayBuffer buffer = new CharArrayBuffer(s.length());
71 buffer.append(s);
72 final ParserCursor cursor = new ParserCursor(0, buffer.length());
73 final List<AuthChallenge> authChallenges = AuthChallengeParser.INSTANCE.parse(ChallengeType.TARGET, buffer, cursor);
74 Assertions.assertEquals(1, authChallenges.size());
75 return authChallenges.get(0);
76 }
77
78 @Test
79 void testDigestAuthenticationEmptyChallenge1() throws Exception {
80 final AuthChallenge authChallenge = parse(StandardAuthScheme.DIGEST);
81 final AuthScheme authscheme = new DigestScheme();
82 Assertions.assertThrows(MalformedChallengeException.class, () ->
83 authscheme.processChallenge(authChallenge, null));
84 }
85
86 @Test
87 void testDigestAuthenticationEmptyChallenge2() throws Exception {
88 final AuthChallenge authChallenge = parse(StandardAuthScheme.DIGEST + " ");
89 final AuthScheme authscheme = new DigestScheme();
90 Assertions.assertThrows(MalformedChallengeException.class, () ->
91 authscheme.processChallenge(authChallenge, null));
92 }
93
94 @Test
95 void testDigestAuthenticationWithDefaultCreds() throws Exception {
96 final HttpRequest request = new BasicHttpRequest("Simple", "/");
97 final HttpHost host = new HttpHost("somehost", 80);
98 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
99 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
100 .build();
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 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
108 final String authResponse = authscheme.generateAuthResponse(host, request, null);
109 Assertions.assertTrue(authscheme.isChallengeComplete());
110 Assertions.assertFalse(authscheme.isConnectionBased());
111
112 final Map<String, String> table = parseAuthResponse(authResponse);
113 Assertions.assertEquals("username", table.get("username"));
114 Assertions.assertEquals("realm1", table.get("realm"));
115 Assertions.assertEquals("/", table.get("uri"));
116 Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
117 Assertions.assertEquals("e95a7ddf37c2eab009568b1ed134f89a", table.get("response"));
118 }
119
120 @Test
121 void testDigestAuthentication() throws Exception {
122 final HttpRequest request = new BasicHttpRequest("Simple", "/");
123 final HttpHost host = new HttpHost("somehost", 80);
124 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
125 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
126 .build();
127
128 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
129 final AuthChallenge authChallenge = parse(challenge);
130 final DigestScheme authscheme = new DigestScheme();
131 authscheme.processChallenge(authChallenge, null);
132
133 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
134 final String authResponse = authscheme.generateAuthResponse(host, request, null);
135
136 final Map<String, String> table = parseAuthResponse(authResponse);
137 Assertions.assertEquals("username", table.get("username"));
138 Assertions.assertEquals("realm1", table.get("realm"));
139 Assertions.assertEquals("/", table.get("uri"));
140 Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
141 Assertions.assertEquals("e95a7ddf37c2eab009568b1ed134f89a", table.get("response"));
142 }
143
144 @Test
145 void testDigestAuthenticationInvalidInput() throws Exception {
146 final HttpHost host = new HttpHost("somehost", 80);
147 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
148 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
149 .build();
150
151 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
152 final AuthChallenge authChallenge = parse(challenge);
153 final DigestScheme authscheme = new DigestScheme();
154 authscheme.processChallenge(authChallenge, null);
155
156 Assertions.assertThrows(NullPointerException.class, () ->
157 authscheme.isResponseReady(null, credentialsProvider, null));
158 Assertions.assertThrows(NullPointerException.class, () ->
159 authscheme.isResponseReady(host, null, null));
160 Assertions.assertThrows(NullPointerException.class, () ->
161 authscheme.generateAuthResponse(host, null, null));
162 }
163
164 @Test
165 void testDigestAuthenticationWithSHA() throws Exception {
166 final HttpRequest request = new BasicHttpRequest("Simple", "/");
167 final HttpHost host = new HttpHost("somehost", 80);
168 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
169 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
170 .build();
171
172 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", " +
173 "nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
174 "algorithm=SHA";
175 final AuthChallenge authChallenge = parse(challenge);
176 final DigestScheme authscheme = new DigestScheme();
177 authscheme.processChallenge(authChallenge, null);
178
179 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
180 final String authResponse = authscheme.generateAuthResponse(host, request, null);
181
182 final Map<String, String> table = parseAuthResponse(authResponse);
183 Assertions.assertEquals("username", table.get("username"));
184 Assertions.assertEquals("realm1", table.get("realm"));
185 Assertions.assertEquals("/", table.get("uri"));
186 Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
187 Assertions.assertEquals("8769e82e4e28ecc040b969562b9050580c6d186d", table.get("response"));
188 }
189
190 @Test
191 void testDigestAuthenticationWithQueryStringInDigestURI() throws Exception {
192 final HttpRequest request = new BasicHttpRequest("Simple", "/?param=value");
193 final HttpHost host = new HttpHost("somehost", 80);
194 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
195 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
196 .build();
197
198 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
199 final AuthChallenge authChallenge = parse(challenge);
200 final DigestScheme authscheme = new DigestScheme();
201 authscheme.processChallenge(authChallenge, null);
202
203 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
204 final String authResponse = authscheme.generateAuthResponse(host, request, null);
205
206 final Map<String, String> table = parseAuthResponse(authResponse);
207 Assertions.assertEquals("username", table.get("username"));
208 Assertions.assertEquals("realm1", table.get("realm"));
209 Assertions.assertEquals("/?param=value", table.get("uri"));
210 Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
211 Assertions.assertEquals("a847f58f5fef0bc087bcb9c3eb30e042", table.get("response"));
212 }
213
214 @Test
215 void testDigestAuthenticationNoRealm() throws Exception {
216 final HttpRequest request = new BasicHttpRequest("Simple", "/");
217 final HttpHost host = new HttpHost("somehost", 80);
218 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
219 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
220 .build();
221
222 final String challenge = StandardAuthScheme.DIGEST + " no-realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
223 final AuthChallenge authChallenge = parse(challenge);
224 final DigestScheme authscheme = new DigestScheme();
225 authscheme.processChallenge(authChallenge, null);
226
227 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
228 Assertions.assertThrows(AuthenticationException.class, () ->
229 authscheme.generateAuthResponse(host, request, null));
230 }
231
232 @Test
233 void testDigestAuthenticationNoNonce() throws Exception {
234 final HttpRequest request = new BasicHttpRequest("Simple", "/");
235 final HttpHost host = new HttpHost("somehost", 80);
236 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
237 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
238 .build();
239
240 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", no-nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
241 final AuthChallenge authChallenge = parse(challenge);
242 final DigestScheme authscheme = new DigestScheme();
243 authscheme.processChallenge(authChallenge, null);
244
245 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
246 Assertions.assertThrows(AuthenticationException.class, () ->
247 authscheme.generateAuthResponse(host, request, null));
248 }
249
250 @Test
251 void testDigestAuthenticationNoAlgorithm() throws Exception {
252 final HttpRequest request = new BasicHttpRequest("Simple", "/");
253 final HttpHost host = new HttpHost("somehost", 80);
254 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
255 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
256 .build();
257
258 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
259 final AuthChallenge authChallenge = parse(challenge);
260 final DigestScheme authscheme = new DigestScheme();
261 authscheme.processChallenge(authChallenge, null);
262
263 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
264 final String authResponse = authscheme.generateAuthResponse(host, request, null);
265
266 final Map<String, String> table = parseAuthResponse(authResponse);
267 Assertions.assertNull(table.get("algorithm"));
268 }
269
270 @Test
271 void testDigestAuthenticationMD5Algorithm() throws Exception {
272 final HttpRequest request = new BasicHttpRequest("Simple", "/");
273 final HttpHost host = new HttpHost("somehost", 80);
274 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
275 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
276 .build();
277
278 final String challenge = StandardAuthScheme.DIGEST
279 + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\""
280 + ", algorithm=MD5";
281 final AuthChallenge authChallenge = parse(challenge);
282 final DigestScheme authscheme = new DigestScheme();
283 authscheme.processChallenge(authChallenge, null);
284
285 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
286 final String authResponse = authscheme.generateAuthResponse(host, request, null);
287
288 final Map<String, String> table = parseAuthResponse(authResponse);
289 Assertions.assertEquals("MD5", table.get("algorithm"));
290 }
291
292
293
294
295 @Test
296 void testDigestAuthenticationMD5Sess() throws Exception {
297
298
299 final String realm="realm";
300 final String username="username";
301 final String password="password";
302 final String nonce="e273f1776275974f1a120d8b92c5b3cb";
303
304 final HttpRequest request = new BasicHttpRequest("Simple", "/");
305 final HttpHost host = new HttpHost("somehost", 80);
306 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
307 .add(new AuthScope(host, realm, null), username, password.toCharArray())
308 .build();
309
310 final String challenge=StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", "
311 + "nonce=\"" + nonce + "\", "
312 + "opaque=\"SomeString\", "
313 + "stale=false, "
314 + "algorithm=MD5-sess, "
315 + "qop=\"auth,auth-int\"";
316
317 final AuthChallenge authChallenge = parse(challenge);
318
319 final DigestScheme authscheme = new DigestScheme();
320 authscheme.processChallenge(authChallenge, null);
321
322 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
323 final String authResponse = authscheme.generateAuthResponse(host, request, null);
324
325 Assertions.assertTrue(authResponse.indexOf("nc=00000001") > 0);
326 Assertions.assertTrue(authResponse.indexOf("qop=auth") > 0);
327
328 final Map<String, String> table = parseAuthResponse(authResponse);
329 Assertions.assertEquals(username, table.get("username"));
330 Assertions.assertEquals(realm, table.get("realm"));
331 Assertions.assertEquals("MD5-sess", table.get("algorithm"));
332 Assertions.assertEquals("/", table.get("uri"));
333 Assertions.assertEquals(nonce, table.get("nonce"));
334 Assertions.assertEquals(1, Integer.parseInt(table.get("nc"),16));
335 Assertions.assertNotNull(table.get("cnonce"));
336 Assertions.assertEquals("SomeString", table.get("opaque"));
337 Assertions.assertEquals("auth", table.get("qop"));
338
339 Assertions.assertNotNull(table.get("response"));
340 }
341
342
343
344
345 @Test
346 void testDigestAuthenticationMD5SessNoQop() throws Exception {
347
348
349 final String realm="realm";
350 final String username="username";
351 final String password="password";
352 final String nonce="e273f1776275974f1a120d8b92c5b3cb";
353
354 final HttpRequest request = new BasicHttpRequest("Simple", "/");
355 final HttpHost host = new HttpHost("somehost", 80);
356 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
357 .add(new AuthScope(host, realm, null), username, password.toCharArray())
358 .build();
359
360 final String challenge=StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", "
361 + "nonce=\"" + nonce + "\", "
362 + "opaque=\"SomeString\", "
363 + "stale=false, "
364 + "algorithm=MD5-sess";
365
366 final AuthChallenge authChallenge = parse(challenge);
367
368 final DigestScheme authscheme = new DigestScheme();
369 authscheme.processChallenge(authChallenge, null);
370 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
371 final String authResponse = authscheme.generateAuthResponse(host, request, null);
372
373 final Map<String, String> table = parseAuthResponse(authResponse);
374 Assertions.assertEquals(username, table.get("username"));
375 Assertions.assertEquals(realm, table.get("realm"));
376 Assertions.assertEquals("MD5-sess", table.get("algorithm"));
377 Assertions.assertEquals("/", table.get("uri"));
378 Assertions.assertEquals(nonce, table.get("nonce"));
379 Assertions.assertNull(table.get("nc"));
380 Assertions.assertEquals("SomeString", table.get("opaque"));
381 Assertions.assertNull(table.get("qop"));
382
383 Assertions.assertNotNull(table.get("response"));
384 }
385
386
387
388
389 @Test
390 void testDigestAuthenticationMD5SessUnknownQop() throws Exception {
391
392
393 final String realm="realm";
394 final String username="username";
395 final String password="password";
396 final String nonce="e273f1776275974f1a120d8b92c5b3cb";
397
398 final HttpRequest request = new BasicHttpRequest("Simple", "/");
399 final HttpHost host = new HttpHost("somehost", 80);
400 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
401 .add(new AuthScope(host, realm, null), username, password.toCharArray())
402 .build();
403
404 final String challenge=StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", "
405 + "nonce=\"" + nonce + "\", "
406 + "opaque=\"SomeString\", "
407 + "stale=false, "
408 + "algorithm=MD5-sess, "
409 + "qop=\"stuff\"";
410
411 final AuthChallenge authChallenge = parse(challenge);
412
413 final DigestScheme authscheme = new DigestScheme();
414 authscheme.processChallenge(authChallenge, null);
415
416 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
417 Assertions.assertThrows(AuthenticationException.class, () ->
418 authscheme.generateAuthResponse(host, request, null));
419 }
420
421
422
423
424 @Test
425 void testDigestAuthenticationUnknownAlgo() throws Exception {
426
427
428 final String realm="realm";
429 final String username="username";
430 final String password="password";
431 final String nonce="e273f1776275974f1a120d8b92c5b3cb";
432
433 final HttpRequest request = new BasicHttpRequest("Simple", "/");
434 final HttpHost host = new HttpHost("somehost", 80);
435 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
436 .add(new AuthScope(host, realm, null), username, password.toCharArray())
437 .build();
438
439 final String challenge=StandardAuthScheme.DIGEST + " realm=\"" + realm + "\", "
440 + "nonce=\"" + nonce + "\", "
441 + "opaque=\"SomeString\", "
442 + "stale=false, "
443 + "algorithm=stuff, "
444 + "qop=\"auth\"";
445
446 final AuthChallenge authChallenge = parse(challenge);
447
448 final DigestScheme authscheme = new DigestScheme();
449 authscheme.processChallenge(authChallenge, null);
450
451 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
452 Assertions.assertThrows(AuthenticationException.class, () ->
453 authscheme.generateAuthResponse(host, request, null));
454 }
455
456 @Test
457 void testDigestAuthenticationWithStaleNonce() throws Exception {
458 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", " +
459 "nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", stale=\"true\"";
460 final AuthChallenge authChallenge = parse(challenge);
461 final AuthScheme authscheme = new DigestScheme();
462 authscheme.processChallenge(authChallenge, null);
463
464 Assertions.assertFalse(authscheme.isChallengeComplete());
465 }
466
467 private static Map<String, String> parseAuthResponse(final String authResponse) {
468 if (!authResponse.startsWith(StandardAuthScheme.DIGEST + " ")) {
469 return null;
470 }
471 final String s = authResponse.substring(7);
472 final ParserCursor cursor = new ParserCursor(0, s.length());
473 final HeaderElement[] elements = BasicHeaderValueParser.INSTANCE.parseElements(s, cursor);
474 final Map<String, String> map = new HashMap<>(elements.length);
475 for (final HeaderElement element : elements) {
476 map.put(element.getName(), element.getValue());
477 }
478 return map;
479 }
480
481 @Test
482 void testDigestNouceCount() throws Exception {
483 final HttpRequest request = new BasicHttpRequest("GET", "/");
484 final HttpHost host = new HttpHost("somehost", 80);
485 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
486 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
487 .build();
488
489 final String challenge1 = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=auth";
490 final AuthChallenge authChallenge1 = parse(challenge1);
491
492 final DigestScheme authscheme = new DigestScheme();
493 authscheme.processChallenge(authChallenge1, null);
494 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
495 final String authResponse1 = authscheme.generateAuthResponse(host, request, null);
496
497 final Map<String, String> table1 = parseAuthResponse(authResponse1);
498 Assertions.assertEquals("00000001", table1.get("nc"));
499
500 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
501 final String authResponse2 = authscheme.generateAuthResponse(host, request, null);
502
503 final Map<String, String> table2 = parseAuthResponse(authResponse2);
504 Assertions.assertEquals("00000002", table2.get("nc"));
505 final String challenge2 = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=auth";
506 final AuthChallenge authChallenge2 = parse(challenge2);
507 authscheme.processChallenge(authChallenge2, null);
508
509 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
510 final String authResponse3 = authscheme.generateAuthResponse(host, request, null);
511
512 final Map<String, String> table3 = parseAuthResponse(authResponse3);
513 Assertions.assertEquals("00000003", table3.get("nc"));
514 final String challenge3 = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"e273f1776275974f1a120d8b92c5b3cb\", qop=auth";
515 final AuthChallenge authChallenge3 = parse(challenge3);
516 authscheme.processChallenge(authChallenge3, null);
517
518 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
519 final String authResponse4 = authscheme.generateAuthResponse(host, request, null);
520
521 final Map<String, String> table4 = parseAuthResponse(authResponse4);
522 Assertions.assertEquals("00000001", table4.get("nc"));
523 }
524
525 @Test
526 void testDigestMD5SessA1AndCnonceConsistency() throws Exception {
527 final HttpHost host = new HttpHost("somehost", 80);
528 final HttpRequest request = new BasicHttpRequest("GET", "/");
529 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
530 .add(new AuthScope(host, "subnet.domain.com", null), "username", "password".toCharArray())
531 .build();
532
533 final String challenge1 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=MD5-sess, nonce=\"1234567890abcdef\", " +
534 "charset=utf-8, realm=\"subnet.domain.com\"";
535 final AuthChallenge authChallenge1 = parse(challenge1);
536 final DigestScheme authscheme = new DigestScheme();
537 authscheme.processChallenge(authChallenge1, null);
538 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
539 final String authResponse1 = authscheme.generateAuthResponse(host, request, null);
540
541 final Map<String, String> table1 = parseAuthResponse(authResponse1);
542 Assertions.assertEquals("00000001", table1.get("nc"));
543 final String cnonce1 = authscheme.getCnonce();
544 final String sessionKey1 = authscheme.getA1();
545
546 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
547 final String authResponse2 = authscheme.generateAuthResponse(host, request, null);
548 final Map<String, String> table2 = parseAuthResponse(authResponse2);
549 Assertions.assertEquals("00000002", table2.get("nc"));
550 final String cnonce2 = authscheme.getCnonce();
551 final String sessionKey2 = authscheme.getA1();
552
553 Assertions.assertEquals(cnonce1, cnonce2);
554 Assertions.assertEquals(sessionKey1, sessionKey2);
555
556 final String challenge2 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=MD5-sess, nonce=\"1234567890abcdef\", " +
557 "charset=utf-8, realm=\"subnet.domain.com\"";
558 final AuthChallenge authChallenge2 = parse(challenge2);
559 authscheme.processChallenge(authChallenge2, null);
560 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
561 final String authResponse3 = authscheme.generateAuthResponse(host, request, null);
562 final Map<String, String> table3 = parseAuthResponse(authResponse3);
563 Assertions.assertEquals("00000003", table3.get("nc"));
564
565 final String cnonce3 = authscheme.getCnonce();
566 final String sessionKey3 = authscheme.getA1();
567
568 Assertions.assertEquals(cnonce1, cnonce3);
569 Assertions.assertEquals(sessionKey1, sessionKey3);
570
571 final String challenge3 = StandardAuthScheme.DIGEST + " qop=\"auth\", algorithm=MD5-sess, nonce=\"fedcba0987654321\", " +
572 "charset=utf-8, realm=\"subnet.domain.com\"";
573 final AuthChallenge authChallenge3 = parse(challenge3);
574 authscheme.processChallenge(authChallenge3, null);
575 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
576 final String authResponse4 = authscheme.generateAuthResponse(host, request, null);
577 final Map<String, String> table4 = parseAuthResponse(authResponse4);
578 Assertions.assertEquals("00000001", table4.get("nc"));
579
580 final String cnonce4 = authscheme.getCnonce();
581 final String sessionKey4 = authscheme.getA1();
582
583 Assertions.assertNotEquals(cnonce1, cnonce4);
584 Assertions.assertNotEquals(sessionKey1, sessionKey4);
585 }
586
587 @Test
588 void testHttpEntityDigest() throws Exception {
589 final HttpEntityDigester digester = new HttpEntityDigester(MessageDigest.getInstance("MD5"));
590 Assertions.assertNull(digester.getDigest());
591 digester.write('a');
592 digester.write('b');
593 digester.write('c');
594 digester.write(0xe4);
595 digester.write(0xf6);
596 digester.write(0xfc);
597 digester.write(new byte[] { 'a', 'b', 'c'});
598 Assertions.assertNull(digester.getDigest());
599 digester.close();
600 Assertions.assertEquals("acd2b59cd01c7737d8069015584c6cac", DigestScheme.formatHex(digester.getDigest()));
601 Assertions.assertThrows(IOException.class, () -> digester.write('a'));
602 Assertions.assertThrows(IOException.class, () -> digester.write(new byte[] { 'a', 'b', 'c'}));
603 }
604
605 @Test
606 void testDigestAuthenticationQopAuthInt() throws Exception {
607 final ClassicHttpRequest request = new BasicClassicHttpRequest("Post", "/");
608 request.setEntity(new StringEntity("abc\u00e4\u00f6\u00fcabc", StandardCharsets.ISO_8859_1));
609 final HttpHost host = new HttpHost("somehost", 80);
610 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
611 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
612 .build();
613
614 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
615 "qop=\"auth,auth-int\"";
616 final AuthChallenge authChallenge = parse(challenge);
617 final DigestScheme authscheme = new DigestScheme();
618 authscheme.processChallenge(authChallenge, null);
619 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
620 final String authResponse = authscheme.generateAuthResponse(host, request, null);
621
622 Assertions.assertEquals("Post:/:acd2b59cd01c7737d8069015584c6cac", authscheme.getA2());
623
624 final Map<String, String> table = parseAuthResponse(authResponse);
625 Assertions.assertEquals("username", table.get("username"));
626 Assertions.assertEquals("realm1", table.get("realm"));
627 Assertions.assertEquals("/", table.get("uri"));
628 Assertions.assertEquals("auth-int", table.get("qop"));
629 Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
630 }
631
632 @Test
633 void testDigestAuthenticationQopAuthIntNullEntity() throws Exception {
634 final HttpRequest request = new BasicHttpRequest("Post", "/");
635 final HttpHost host = new HttpHost("somehost", 80);
636 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
637 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
638 .build();
639
640 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
641 "qop=\"auth-int\"";
642 final AuthChallenge authChallenge = parse(challenge);
643 final DigestScheme authscheme = new DigestScheme();
644 authscheme.processChallenge(authChallenge, null);
645 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
646 final String authResponse = authscheme.generateAuthResponse(host, request, null);
647
648 Assertions.assertEquals("Post:/:d41d8cd98f00b204e9800998ecf8427e", authscheme.getA2());
649
650 final Map<String, String> table = parseAuthResponse(authResponse);
651 Assertions.assertEquals("username", table.get("username"));
652 Assertions.assertEquals("realm1", table.get("realm"));
653 Assertions.assertEquals("/", table.get("uri"));
654 Assertions.assertEquals("auth-int", table.get("qop"));
655 Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
656 }
657
658 @Test
659 void testDigestAuthenticationQopAuthOrAuthIntNonRepeatableEntity() throws Exception {
660 final ClassicHttpRequest request = new BasicClassicHttpRequest("Post", "/");
661 request.setEntity(new InputStreamEntity(new ByteArrayInputStream(new byte[] {'a'}), -1, ContentType.DEFAULT_TEXT));
662 final HttpHost host = new HttpHost("somehost", 80);
663 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
664 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
665 .build();
666
667 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
668 "qop=\"auth,auth-int\"";
669 final AuthChallenge authChallenge = parse(challenge);
670 final DigestScheme authscheme = new DigestScheme();
671 authscheme.processChallenge(authChallenge, null);
672 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
673 final String authResponse = authscheme.generateAuthResponse(host, request, null);
674
675 Assertions.assertEquals("Post:/", authscheme.getA2());
676
677 final Map<String, String> table = parseAuthResponse(authResponse);
678 Assertions.assertEquals("username", table.get("username"));
679 Assertions.assertEquals("realm1", table.get("realm"));
680 Assertions.assertEquals("/", table.get("uri"));
681 Assertions.assertEquals("auth", table.get("qop"));
682 Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
683 }
684
685 @Test
686 void testParameterCaseSensitivity() throws Exception {
687 final HttpRequest request = new BasicHttpRequest("GET", "/");
688 final HttpHost host = new HttpHost("somehost", 80);
689 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
690 .add(new AuthScope(host, "-", null), "username", "password".toCharArray())
691 .build();
692
693 final String challenge = StandardAuthScheme.DIGEST + " Realm=\"-\", " +
694 "nonce=\"YjYuNGYyYmJhMzUuY2I5ZDhlZDE5M2ZlZDM 1Mjk3NGJkNTIyYjgyNTcwMjQ=\", " +
695 "opaque=\"98700A3D9CE17065E2246B41035C6609\", qop=\"auth\"";
696 final AuthChallenge authChallenge = parse(challenge);
697 final DigestScheme authscheme = new DigestScheme();
698 authscheme.processChallenge(authChallenge, null);
699 Assertions.assertEquals("-", authscheme.getRealm());
700
701 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
702 authscheme.generateAuthResponse(host, request, null);
703 }
704
705 @Test
706 void testDigestAuthenticationQopIntOnlyNonRepeatableEntity() throws Exception {
707 final ClassicHttpRequest request = new BasicClassicHttpRequest("Post", "/");
708 request.setEntity(new InputStreamEntity(new ByteArrayInputStream(new byte[] {'a'}), -1, ContentType.DEFAULT_TEXT));
709 final HttpHost host = new HttpHost("somehost", 80);
710 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
711 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
712 .build();
713
714 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
715 "qop=\"auth-int\"";
716 final AuthChallenge authChallenge = parse(challenge);
717 final DigestScheme authscheme = new DigestScheme();
718 authscheme.processChallenge(authChallenge, null);
719
720 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
721 Assertions.assertThrows(AuthenticationException.class, () ->
722 authscheme.generateAuthResponse(host, request, null));
723 }
724
725 @Test
726 void testSerialization() throws Exception {
727 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
728 "qop=\"auth,auth-int\"";
729 final AuthChallenge authChallenge = parse(challenge);
730 final DigestScheme digestScheme = new DigestScheme();
731 digestScheme.processChallenge(authChallenge, null);
732
733 final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
734 final ObjectOutputStream out = new ObjectOutputStream(buffer);
735 out.writeObject(digestScheme);
736 out.flush();
737 final byte[] raw = buffer.toByteArray();
738 final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(raw));
739 final DigestScheme authScheme = (DigestScheme) in.readObject();
740
741 Assertions.assertEquals(digestScheme.getName(), authScheme.getName());
742 Assertions.assertEquals(digestScheme.getRealm(), authScheme.getRealm());
743 Assertions.assertEquals(digestScheme.isChallengeComplete(), authScheme.isChallengeComplete());
744 Assertions.assertEquals(digestScheme.getA1(), authScheme.getA1());
745 Assertions.assertEquals(digestScheme.getA2(), authScheme.getA2());
746 Assertions.assertEquals(digestScheme.getCnonce(), authScheme.getCnonce());
747 }
748
749
750 @Test
751 void testDigestAuthenticationWithUserHash() throws Exception {
752 final HttpRequest request = new BasicHttpRequest("Simple", "/");
753 final HttpHost host = new HttpHost("somehost", 80);
754 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
755 .add(new AuthScope(host, "realm1", null), "username", "password".toCharArray())
756 .build();
757
758
759 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", userhash=true";
760 final AuthChallenge authChallenge = parse(challenge);
761 final DigestScheme authscheme = new DigestScheme();
762 authscheme.processChallenge(authChallenge, null);
763
764 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
765 final String authResponse = authscheme.generateAuthResponse(host, request, null);
766
767 final Map<String, String> table = parseAuthResponse(authResponse);
768
769
770 final MessageDigest md = MessageDigest.getInstance("MD5");
771 md.update(("username:realm1").getBytes(StandardCharsets.UTF_8));
772 final String expectedUserhash = bytesToHex(md.digest());
773
774 Assertions.assertEquals(expectedUserhash, table.get("username"));
775 Assertions.assertEquals("realm1", table.get("realm"));
776 Assertions.assertEquals("/", table.get("uri"));
777 Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
778 Assertions.assertEquals("3b6561ceb73e5ffe9314a695179f23f9", table.get("response"));
779 }
780
781 private static String bytesToHex(final byte[] bytes) {
782 final StringBuilder hexString = new StringBuilder();
783 for (final byte b : bytes) {
784 final String hex = Integer.toHexString(0xff & b);
785 if (hex.length() == 1) {
786 hexString.append('0');
787 }
788 hexString.append(hex);
789 }
790 return hexString.toString();
791 }
792
793 @Test
794 void testDigestAuthenticationWithQuotedStringsAndWhitespace() throws Exception {
795 final HttpRequest request = new BasicHttpRequest("Simple", "/");
796 final HttpHost host = new HttpHost("somehost", 80);
797 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
798 .add(new AuthScope(host, "\"myhost@example.com\"", null), "\"Mufasa\"", "\"Circle Of Life\"".toCharArray())
799 .build();
800
801
802 final String challenge = StandardAuthScheme.DIGEST + " realm=\"\\\"myhost@example.com\\\"\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", userhash=true";
803 final AuthChallenge authChallenge = parse(challenge);
804 final DigestScheme authscheme = new DigestScheme();
805 authscheme.processChallenge(authChallenge, null);
806
807 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
808
809 final String authResponse = authscheme.generateAuthResponse(host, request, null);
810
811 final Map<String, String> table = parseAuthResponse(authResponse);
812
813
814 final MessageDigest md = MessageDigest.getInstance("MD5");
815 final String a1 = "Mufasa:myhost@example.com:Circle Of Life";
816 md.update(a1.getBytes(StandardCharsets.UTF_8));
817
818
819 final String response = table.get("response");
820 Assertions.assertNotNull(response);
821 }
822 @Test
823 void testDigestAuthenticationWithInvalidUsernameAndValidUsernameStar() throws Exception {
824 final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/");
825 final HttpHost host = new HttpHost("somehost", 80);
826 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
827 .add(new AuthScope(host, "realm1", null), "invalid\"username", "password".toCharArray())
828 .build();
829
830 final String encodedUsername = "UTF-8''J%C3%A4s%C3%B8n%20Doe";
831 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
832 "qop=\"auth-int\", username*=\"" + encodedUsername + "\"";
833 final AuthChallenge authChallenge = parse(challenge);
834 final DigestScheme authscheme = new DigestScheme();
835 authscheme.processChallenge(authChallenge, null);
836
837 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
838
839 final String authResponse = authscheme.generateAuthResponse(host, request, null);
840 Assertions.assertNotNull(authResponse);
841 }
842
843 @Test
844 void testDigestAuthenticationWithHighAsciiCharInUsername() throws Exception {
845 final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/");
846 final HttpHost host = new HttpHost("somehost", 80);
847
848 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
849 .add(new AuthScope(host, "realm1", null), "high\u007Fchar", "password".toCharArray())
850 .build();
851
852 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=\"auth-int\"";
853 final AuthChallenge authChallenge = parse(challenge);
854 final DigestScheme authscheme = new DigestScheme();
855 authscheme.processChallenge(authChallenge, null);
856
857 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
858 final String authResponse = authscheme.generateAuthResponse(host, request, null);
859
860
861 Assertions.assertTrue(authResponse.contains("username*"));
862 }
863
864
865 @Test
866 void testDigestAuthenticationWithExtendedAsciiCharInUsername() throws Exception {
867 final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/");
868 final HttpHost host = new HttpHost("somehost", 80);
869
870 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
871 .add(new AuthScope(host, "realm1", null), "username\u0080", "password".toCharArray())
872 .build();
873
874 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=\"auth-int\"";
875 final AuthChallenge authChallenge = parse(challenge);
876 final DigestScheme authscheme = new DigestScheme();
877 authscheme.processChallenge(authChallenge, null);
878
879 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
880 final String authResponse = authscheme.generateAuthResponse(host, request, null);
881
882
883 Assertions.assertTrue(authResponse.contains("username*"));
884 }
885
886
887 @Test
888 void testDigestAuthenticationWithNonAsciiUsername() throws Exception {
889 final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/");
890 final HttpHost host = new HttpHost("somehost", 80);
891
892 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
893 .add(new AuthScope(host, "realm1", null), "Jäsøn Doe", "password".toCharArray())
894 .build();
895
896 final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=\"auth-int\"";
897 final AuthChallenge authChallenge = parse(challenge);
898 final DigestScheme authscheme = new DigestScheme();
899 authscheme.processChallenge(authChallenge, null);
900
901 Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
902 final String authResponse = authscheme.generateAuthResponse(host, request, null);
903
904
905 Assertions.assertTrue(authResponse.contains("username*"));
906 }
907
908
909 }