24
24
25
25
import java .nio .charset .Charset ;
26
26
import java .security .MessageDigest ;
27
+ import java .util .Arrays ;
27
28
import java .util .Map ;
28
29
import java .util .concurrent .ThreadLocalRandom ;
29
30
30
31
import static java .nio .charset .StandardCharsets .ISO_8859_1 ;
31
32
import static java .nio .charset .StandardCharsets .UTF_8 ;
32
33
import static java .util .Objects .requireNonNull ;
33
34
import static org .asynchttpclient .util .HttpConstants .Methods .GET ;
34
- import static org .asynchttpclient .util .MessageDigestUtils .pooledMd5MessageDigest ;
35
35
import static org .asynchttpclient .util .MiscUtils .isNonEmpty ;
36
36
import static org .asynchttpclient .util .StringUtils .appendBase16 ;
37
37
import static org .asynchttpclient .util .StringUtils .toHexString ;
38
+ import org .asynchttpclient .util .MessageDigestUtils ;
38
39
39
40
/**
40
41
* This class is required when authentication is needed. The class support
@@ -275,13 +276,15 @@ public static class Builder {
275
276
private String ntlmHost = "localhost" ;
276
277
private boolean useAbsoluteURI ;
277
278
private boolean omitQuery ;
279
+ private Charset digestCharset = ISO_8859_1 ; // RFC default
278
280
/**
279
281
* Kerberos/Spnego properties
280
282
*/
281
283
private @ Nullable Map <String , String > customLoginConfig ;
282
284
private @ Nullable String servicePrincipalName ;
283
285
private boolean useCanonicalHostname ;
284
286
private @ Nullable String loginContextName ;
287
+ private @ Nullable String cs ;
285
288
286
289
public Builder () {
287
290
principal = null ;
@@ -424,6 +427,10 @@ public Builder parseWWWAuthenticateHeader(String headerLine) {
424
427
.setOpaque (match (headerLine , "opaque" ))
425
428
.setScheme (isNonEmpty (nonce ) ? AuthScheme .DIGEST : AuthScheme .BASIC );
426
429
String algorithm = match (headerLine , "algorithm" );
430
+ String cs = match (headerLine , "charset" );
431
+ if ("UTF-8" .equalsIgnoreCase (cs )) {
432
+ this .digestCharset = UTF_8 ;
433
+ }
427
434
if (isNonEmpty (algorithm )) {
428
435
setAlgorithm (algorithm );
429
436
}
@@ -452,62 +459,68 @@ public Builder parseProxyAuthenticateHeader(String headerLine) {
452
459
return this ;
453
460
}
454
461
455
- private void newCnonce (MessageDigest md ) {
456
- byte [] b = new byte [8 ];
457
- ThreadLocalRandom .current ().nextBytes (b );
458
- b = md .digest (b );
459
- cnonce = toHexString (b );
460
- }
461
-
462
462
/**
463
- * TODO: A Pattern/Matcher may be better.
463
+ * Extracts the value of a token from a WWW-Authenticate or Proxy-Authenticate header line.
464
+ * Example: match('Digest realm="test", nonce="abc"', "realm") returns "test"
464
465
*/
465
466
private static @ Nullable String match (String headerLine , String token ) {
466
- if (headerLine == null ) {
467
- return null ;
468
- }
469
-
470
- int match = headerLine .indexOf (token );
471
- if (match <= 0 ) {
472
- return null ;
473
- }
467
+ if (headerLine == null || token == null ) return null ;
468
+ String pattern = token + "=\" " ;
469
+ int start = headerLine .indexOf (pattern );
470
+ if (start == -1 ) return null ;
471
+ start += pattern .length ();
472
+ int end = headerLine .indexOf ('"' , start );
473
+ if (end == -1 ) return null ;
474
+ return headerLine .substring (start , end );
475
+ }
474
476
475
- // = to skip
476
- match += token .length () + 1 ;
477
- int trailingComa = headerLine .indexOf (',' , match );
478
- String value = headerLine .substring (match , trailingComa > 0 ? trailingComa : headerLine .length ());
479
- value = value .length () > 0 && value .charAt (value .length () - 1 ) == '"'
480
- ? value .substring (0 , value .length () - 1 )
481
- : value ;
482
- return value .charAt (0 ) == '"' ? value .substring (1 ) : value ;
477
+ private void newCnonce (MessageDigest md ) {
478
+ byte [] b = new byte [8 ];
479
+ ThreadLocalRandom .current ().nextBytes (b );
480
+ byte [] full = md .digest (b );
481
+ // trim to first 8 bytes → 16 hex chars
482
+ byte [] small = Arrays .copyOf (full , Math .min (8 , full .length ));
483
+ cnonce = toHexString (small );
483
484
}
484
485
485
- private static byte [] md5FromRecycledStringBuilder (StringBuilder sb , MessageDigest md ) {
486
- md .update (StringUtils .charSequence2ByteBuffer (sb , ISO_8859_1 ));
486
+ private static byte [] digestFromRecycledStringBuilder (StringBuilder sb , MessageDigest md , Charset enc ) {
487
+ md .update (StringUtils .charSequence2ByteBuffer (sb , enc ));
487
488
sb .setLength (0 );
488
489
return md .digest ();
489
490
}
490
491
492
+ private static MessageDigest getDigestInstance (String algorithm ) {
493
+ if ("SHA-512/256" .equalsIgnoreCase (algorithm )) algorithm = "SHA-512-256" ;
494
+ if (algorithm == null || "MD5" .equalsIgnoreCase (algorithm ) || "MD5-sess" .equalsIgnoreCase (algorithm )) {
495
+ return MessageDigestUtils .pooledMd5MessageDigest ();
496
+ } else if ("SHA-256" .equalsIgnoreCase (algorithm ) || "SHA-256-sess" .equalsIgnoreCase (algorithm )) {
497
+ return MessageDigestUtils .pooledSha256MessageDigest ();
498
+ } else if ("SHA-512-256" .equalsIgnoreCase (algorithm ) || "SHA-512-256-sess" .equalsIgnoreCase (algorithm )) {
499
+ return MessageDigestUtils .pooledSha512_256MessageDigest ();
500
+ } else {
501
+ throw new UnsupportedOperationException ("Digest algorithm not supported: " + algorithm );
502
+ }
503
+ }
504
+
491
505
private byte [] ha1 (StringBuilder sb , MessageDigest md ) {
492
506
// if algorithm is "MD5" or is unspecified => A1 = username ":" realm-value ":"
493
507
// passwd
494
508
// if algorithm is "MD5-sess" => A1 = MD5( username-value ":" realm-value ":"
495
509
// passwd ) ":" nonce-value ":" cnonce-value
496
510
497
511
sb .append (principal ).append (':' ).append (realmName ).append (':' ).append (password );
498
- byte [] core = md5FromRecycledStringBuilder (sb , md );
512
+ byte [] core = digestFromRecycledStringBuilder (sb , md , digestCharset );
499
513
500
- if (algorithm == null || "MD5" .equals (algorithm )) {
514
+ if (algorithm == null || "MD5" .equalsIgnoreCase ( algorithm ) || "SHA-256" . equalsIgnoreCase ( algorithm ) || "SHA-512-256" . equalsIgnoreCase (algorithm )) {
501
515
// A1 = username ":" realm-value ":" passwd
502
516
return core ;
503
517
}
504
- if ("MD5-sess" .equals (algorithm )) {
505
- // A1 = MD5 (username ":" realm-value ":" passwd ) ":" nonce ":" cnonce
518
+ if ("MD5-sess" .equalsIgnoreCase ( algorithm ) || "SHA-256-sess" . equalsIgnoreCase ( algorithm ) || "SHA-512-256-sess" . equalsIgnoreCase (algorithm )) {
519
+ // A1 = HASH (username ":" realm-value ":" passwd ) ":" nonce ":" cnonce
506
520
appendBase16 (sb , core );
507
521
sb .append (':' ).append (nonce ).append (':' ).append (cnonce );
508
- return md5FromRecycledStringBuilder (sb , md );
522
+ return digestFromRecycledStringBuilder (sb , md , digestCharset );
509
523
}
510
-
511
524
throw new UnsupportedOperationException ("Digest algorithm not supported: " + algorithm );
512
525
}
513
526
@@ -526,7 +539,7 @@ private byte[] ha2(StringBuilder sb, String digestUri, MessageDigest md) {
526
539
throw new UnsupportedOperationException ("Digest qop not supported: " + qop );
527
540
}
528
541
529
- return md5FromRecycledStringBuilder (sb , md );
542
+ return digestFromRecycledStringBuilder (sb , md , digestCharset );
530
543
}
531
544
532
545
private void appendMiddlePart (StringBuilder sb ) {
@@ -553,7 +566,7 @@ private void newResponse(MessageDigest md) {
553
566
appendMiddlePart (sb );
554
567
appendBase16 (sb , ha2 );
555
568
556
- byte [] responseDigest = md5FromRecycledStringBuilder (sb , md );
569
+ byte [] responseDigest = digestFromRecycledStringBuilder (sb , md , digestCharset );
557
570
response = toHexString (responseDigest );
558
571
}
559
572
}
@@ -567,7 +580,9 @@ public Realm build() {
567
580
568
581
// Avoid generating
569
582
if (isNonEmpty (nonce )) {
570
- MessageDigest md = pooledMd5MessageDigest ();
583
+ // Defensive: if algorithm is null, default to MD5
584
+ String algo = (algorithm != null ) ? algorithm : "MD5" ;
585
+ MessageDigest md = getDigestInstance (algo );
571
586
newCnonce (md );
572
587
newResponse (md );
573
588
}
@@ -585,7 +600,7 @@ public Realm build() {
585
600
cnonce ,
586
601
uri ,
587
602
usePreemptive ,
588
- charset ,
603
+ ( scheme == AuthScheme . DIGEST ? digestCharset : charset ) ,
589
604
ntlmDomain ,
590
605
ntlmHost ,
591
606
useAbsoluteURI ,
0 commit comments