2424
2525import java .nio .charset .Charset ;
2626import java .security .MessageDigest ;
27+ import java .util .Arrays ;
2728import java .util .Map ;
2829import java .util .concurrent .ThreadLocalRandom ;
2930
3031import static java .nio .charset .StandardCharsets .ISO_8859_1 ;
3132import static java .nio .charset .StandardCharsets .UTF_8 ;
3233import static java .util .Objects .requireNonNull ;
3334import static org .asynchttpclient .util .HttpConstants .Methods .GET ;
34- import static org .asynchttpclient .util .MessageDigestUtils .pooledMd5MessageDigest ;
3535import static org .asynchttpclient .util .MiscUtils .isNonEmpty ;
3636import static org .asynchttpclient .util .StringUtils .appendBase16 ;
3737import static org .asynchttpclient .util .StringUtils .toHexString ;
38+ import org .asynchttpclient .util .MessageDigestUtils ;
3839
3940/**
4041 * This class is required when authentication is needed. The class support
@@ -275,13 +276,15 @@ public static class Builder {
275276 private String ntlmHost = "localhost" ;
276277 private boolean useAbsoluteURI ;
277278 private boolean omitQuery ;
279+ private Charset digestCharset = ISO_8859_1 ; // RFC default
278280 /**
279281 * Kerberos/Spnego properties
280282 */
281283 private @ Nullable Map <String , String > customLoginConfig ;
282284 private @ Nullable String servicePrincipalName ;
283285 private boolean useCanonicalHostname ;
284286 private @ Nullable String loginContextName ;
287+ private @ Nullable String cs ;
285288
286289 public Builder () {
287290 principal = null ;
@@ -424,6 +427,10 @@ public Builder parseWWWAuthenticateHeader(String headerLine) {
424427 .setOpaque (match (headerLine , "opaque" ))
425428 .setScheme (isNonEmpty (nonce ) ? AuthScheme .DIGEST : AuthScheme .BASIC );
426429 String algorithm = match (headerLine , "algorithm" );
430+ String cs = match (headerLine , "charset" );
431+ if ("UTF-8" .equalsIgnoreCase (cs )) {
432+ this .digestCharset = UTF_8 ;
433+ }
427434 if (isNonEmpty (algorithm )) {
428435 setAlgorithm (algorithm );
429436 }
@@ -452,62 +459,68 @@ public Builder parseProxyAuthenticateHeader(String headerLine) {
452459 return this ;
453460 }
454461
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-
462462 /**
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"
464465 */
465466 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+ }
474476
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 );
483484 }
484485
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 ));
487488 sb .setLength (0 );
488489 return md .digest ();
489490 }
490491
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+
491505 private byte [] ha1 (StringBuilder sb , MessageDigest md ) {
492506 // if algorithm is "MD5" or is unspecified => A1 = username ":" realm-value ":"
493507 // passwd
494508 // if algorithm is "MD5-sess" => A1 = MD5( username-value ":" realm-value ":"
495509 // passwd ) ":" nonce-value ":" cnonce-value
496510
497511 sb .append (principal ).append (':' ).append (realmName ).append (':' ).append (password );
498- byte [] core = md5FromRecycledStringBuilder (sb , md );
512+ byte [] core = digestFromRecycledStringBuilder (sb , md , digestCharset );
499513
500- if (algorithm == null || "MD5" .equals (algorithm )) {
514+ if (algorithm == null || "MD5" .equalsIgnoreCase ( algorithm ) || "SHA-256" . equalsIgnoreCase ( algorithm ) || "SHA-512-256" . equalsIgnoreCase (algorithm )) {
501515 // A1 = username ":" realm-value ":" passwd
502516 return core ;
503517 }
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
506520 appendBase16 (sb , core );
507521 sb .append (':' ).append (nonce ).append (':' ).append (cnonce );
508- return md5FromRecycledStringBuilder (sb , md );
522+ return digestFromRecycledStringBuilder (sb , md , digestCharset );
509523 }
510-
511524 throw new UnsupportedOperationException ("Digest algorithm not supported: " + algorithm );
512525 }
513526
@@ -526,7 +539,7 @@ private byte[] ha2(StringBuilder sb, String digestUri, MessageDigest md) {
526539 throw new UnsupportedOperationException ("Digest qop not supported: " + qop );
527540 }
528541
529- return md5FromRecycledStringBuilder (sb , md );
542+ return digestFromRecycledStringBuilder (sb , md , digestCharset );
530543 }
531544
532545 private void appendMiddlePart (StringBuilder sb ) {
@@ -553,7 +566,7 @@ private void newResponse(MessageDigest md) {
553566 appendMiddlePart (sb );
554567 appendBase16 (sb , ha2 );
555568
556- byte [] responseDigest = md5FromRecycledStringBuilder (sb , md );
569+ byte [] responseDigest = digestFromRecycledStringBuilder (sb , md , digestCharset );
557570 response = toHexString (responseDigest );
558571 }
559572 }
@@ -567,7 +580,9 @@ public Realm build() {
567580
568581 // Avoid generating
569582 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 );
571586 newCnonce (md );
572587 newResponse (md );
573588 }
@@ -585,7 +600,7 @@ public Realm build() {
585600 cnonce ,
586601 uri ,
587602 usePreemptive ,
588- charset ,
603+ ( scheme == AuthScheme . DIGEST ? digestCharset : charset ) ,
589604 ntlmDomain ,
590605 ntlmHost ,
591606 useAbsoluteURI ,
0 commit comments