26
26
import java .util .concurrent .ThreadLocalRandom ;
27
27
28
28
import org .asynchttpclient .uri .Uri ;
29
+ import org .asynchttpclient .util .AuthenticatorUtils ;
29
30
import org .asynchttpclient .util .StringUtils ;
30
31
31
32
/**
34
35
public class Realm {
35
36
36
37
private static final String DEFAULT_NC = "00000001" ;
38
+ private static final String EMPTY_ENTITY_MD5 = "d41d8cd98f00b204e9800998ecf8427e" ;
37
39
38
40
private final String principal ;
39
41
private final String password ;
@@ -251,24 +253,24 @@ public static class RealmBuilder {
251
253
// This code is already Apache licenced.
252
254
//
253
255
254
- private String principal = "" ;
255
- private String password = "" ;
256
+ private String principal ;
257
+ private String password ;
256
258
private AuthScheme scheme = AuthScheme .NONE ;
257
- private String realmName = "" ;
258
- private String nonce = "" ;
259
- private String algorithm = "MD5" ;
260
- private String response = "" ;
261
- private String opaque = "" ;
262
- private String qop = "auth" ;
259
+ private String realmName ;
260
+ private String nonce ;
261
+ private String algorithm ;
262
+ private String response ;
263
+ private String opaque ;
264
+ private String qop ;
263
265
private String nc = DEFAULT_NC ;
264
- private String cnonce = "" ;
266
+ private String cnonce ;
265
267
private Uri uri ;
266
268
private String methodName = "GET" ;
267
269
private boolean usePreemptive ;
268
270
private String ntlmDomain = System .getProperty ("http.auth.ntlm.domain" , "" );
269
271
private Charset charset = UTF_8 ;
270
272
private String ntlmHost = "localhost" ;
271
- private boolean useAbsoluteURI = true ;
273
+ private boolean useAbsoluteURI = false ;
272
274
private boolean omitQuery ;
273
275
private boolean targetProxy ;
274
276
@@ -378,7 +380,9 @@ public String getQop() {
378
380
}
379
381
380
382
public RealmBuilder setQop (String qop ) {
381
- this .qop = qop ;
383
+ if (isNonEmpty (qop )) {
384
+ this .qop = qop ;
385
+ }
382
386
return this ;
383
387
}
384
388
@@ -444,6 +448,26 @@ public RealmBuilder setTargetProxy(boolean targetProxy) {
444
448
this .targetProxy = targetProxy ;
445
449
return this ;
446
450
}
451
+ private String parseRawQop (String rawQop ) {
452
+ String [] rawServerSupportedQops = rawQop .split ("," );
453
+ String [] serverSupportedQops = new String [rawServerSupportedQops .length ];
454
+ for (int i = 0 ; i < rawServerSupportedQops .length ; i ++) {
455
+ serverSupportedQops [i ] = rawServerSupportedQops [i ].trim ();
456
+ }
457
+
458
+ // prefer auth over auth-int
459
+ for (String rawServerSupportedQop : serverSupportedQops ) {
460
+ if (rawServerSupportedQop .equals ("auth" ))
461
+ return rawServerSupportedQop ;
462
+ }
463
+
464
+ for (String rawServerSupportedQop : serverSupportedQops ) {
465
+ if (rawServerSupportedQop .equals ("auth-int" ))
466
+ return rawServerSupportedQop ;
467
+ }
468
+
469
+ return null ;
470
+ }
447
471
448
472
public RealmBuilder parseWWWAuthenticateHeader (String headerLine ) {
449
473
setRealmName (match (headerLine , "realm" ));
@@ -453,7 +477,12 @@ public RealmBuilder parseWWWAuthenticateHeader(String headerLine) {
453
477
setAlgorithm (algorithm );
454
478
}
455
479
setOpaque (match (headerLine , "opaque" ));
456
- setQop (match (headerLine , "qop" ));
480
+
481
+ String rawQop = match (headerLine , "qop" );
482
+ if (rawQop != null ) {
483
+ setQop (parseRawQop (rawQop ));
484
+ }
485
+
457
486
if (isNonEmpty (getNonce ())) {
458
487
setScheme (AuthScheme .DIGEST );
459
488
} else {
@@ -466,6 +495,10 @@ public RealmBuilder parseProxyAuthenticateHeader(String headerLine) {
466
495
setRealmName (match (headerLine , "realm" ));
467
496
setNonce (match (headerLine , "nonce" ));
468
497
setOpaque (match (headerLine , "opaque" ));
498
+ String algorithm = match (headerLine , "algorithm" );
499
+ if (isNonEmpty (algorithm )) {
500
+ setAlgorithm (algorithm );
501
+ }
469
502
setQop (match (headerLine , "qop" ));
470
503
if (isNonEmpty (getNonce ())) {
471
504
setScheme (AuthScheme .DIGEST );
@@ -477,25 +510,24 @@ public RealmBuilder parseProxyAuthenticateHeader(String headerLine) {
477
510
}
478
511
479
512
public RealmBuilder clone (Realm clone ) {
480
- setRealmName (clone .getRealmName ());
481
- setAlgorithm (clone .getAlgorithm ());
482
- setMethodName (clone .getMethodName ());
483
- setNc (clone .getNc ());
484
- setNonce (clone .getNonce ());
485
- setPassword (clone .getPassword ());
486
- setPrincipal (clone .getPrincipal ());
487
- setCharset (clone .getCharset ());
488
- setOpaque (clone .getOpaque ());
489
- setQop (clone .getQop ());
490
- setScheme (clone .getScheme ());
491
- setUri (clone .getUri ());
492
- setUsePreemptiveAuth (clone .getUsePreemptiveAuth ());
493
- setNtlmDomain (clone .getNtlmDomain ());
494
- setNtlmHost (clone .getNtlmHost ());
495
- setUseAbsoluteURI (clone .isUseAbsoluteURI ());
496
- setOmitQuery (clone .isOmitQuery ());
497
- setTargetProxy (clone .isTargetProxy ());
498
- return this ;
513
+ return setRealmName (clone .getRealmName ())//
514
+ .setAlgorithm (clone .getAlgorithm ())//
515
+ .setMethodName (clone .getMethodName ())//
516
+ .setNc (clone .getNc ())//
517
+ .setNonce (clone .getNonce ())//
518
+ .setPassword (clone .getPassword ())//
519
+ .setPrincipal (clone .getPrincipal ())//
520
+ .setCharset (clone .getCharset ())//
521
+ .setOpaque (clone .getOpaque ())//
522
+ .setQop (clone .getQop ())//
523
+ .setScheme (clone .getScheme ())//
524
+ .setUri (clone .getUri ())//
525
+ .setUsePreemptiveAuth (clone .getUsePreemptiveAuth ())//
526
+ .setNtlmDomain (clone .getNtlmDomain ())//
527
+ .setNtlmHost (clone .getNtlmHost ())//
528
+ .setUseAbsoluteURI (clone .isUseAbsoluteURI ())//
529
+ .setOmitQuery (clone .isOmitQuery ())//
530
+ .setTargetProxy (clone .isTargetProxy ());
499
531
}
500
532
501
533
private void newCnonce (MessageDigest md ) {
@@ -510,12 +542,12 @@ private void newCnonce(MessageDigest md) {
510
542
*/
511
543
private String match (String headerLine , String token ) {
512
544
if (headerLine == null ) {
513
- return "" ;
545
+ return null ;
514
546
}
515
547
516
548
int match = headerLine .indexOf (token );
517
549
if (match <= 0 )
518
- return "" ;
550
+ return null ;
519
551
520
552
// = to skip
521
553
match += token .length () + 1 ;
@@ -534,48 +566,64 @@ public RealmBuilder setCharset(Charset charset) {
534
566
return this ;
535
567
}
536
568
537
- private void newResponse (MessageDigest md ) {
538
- // BEWARE: compute first as it used the cached StringBuilder
539
- String url = uri .toUrl ();
540
-
541
- StringBuilder sb = StringUtils .stringBuilder ();
542
- sb .append (principal )
543
- .append (":" )
544
- .append (realmName )
545
- .append (":" )
546
- .append (password );
569
+ private byte [] md5FromRecycledStringBuilder (StringBuilder sb , MessageDigest md ) {
547
570
md .update (StringUtils .charSequence2ByteBuffer (sb , ISO_8859_1 ));
548
571
sb .setLength (0 );
549
- byte [] ha1 = md .digest ();
572
+ return md .digest ();
573
+ }
574
+
575
+ private byte [] secretDigest (StringBuilder sb , MessageDigest md ) {
576
+
577
+ sb .append (principal ).append (':' ).append (realmName ).append (':' ).append (password );
578
+ byte [] ha1 = md5FromRecycledStringBuilder (sb , md );
579
+
580
+ if (algorithm == null || algorithm .equals ("MD5" )) {
581
+ return ha1 ;
582
+ } else if ("MD5-sess" .equals (algorithm )) {
583
+ appendBase16 (sb , ha1 );
584
+ sb .append (':' ).append (nonce ).append (':' ).append (cnonce );
585
+ return md5FromRecycledStringBuilder (sb , md );
586
+ }
550
587
551
- //HA2 if qop is auth-int is methodName:url:md5(entityBody)
552
- sb .append (methodName )
553
- .append (':' )
554
- .append (url );
588
+ throw new UnsupportedOperationException ("Digest algorithm not supported: " + algorithm );
589
+ }
555
590
556
- md .update (StringUtils .charSequence2ByteBuffer (sb , ISO_8859_1 ));
557
- sb .setLength (0 );
558
- byte [] ha2 = md .digest ();
591
+ private byte [] dataDigest (StringBuilder sb , String digestUri , MessageDigest md ) {
592
+
593
+ sb .append (methodName ).append (':' ).append (digestUri );
594
+ if ("auth-int" .equals (qop )) {
595
+ sb .append (':' ).append (EMPTY_ENTITY_MD5 );
559
596
560
- appendBase16 (sb , ha1 );
561
- sb .append (':' ).append (nonce ).append (':' );
597
+ } else if (qop != null && !qop .equals ("auth" )) {
598
+ throw new UnsupportedOperationException ("Digest qop not supported: " + qop );
599
+ }
562
600
563
- if (isNonEmpty (qop )) {
564
- //qop ="auth" or "auth-int"
565
- sb .append (nc )//
566
- .append (':' )//
567
- .append (cnonce )//
568
- .append (':' )//
569
- .append (qop )//
570
- .append (':' );
601
+ return md5FromRecycledStringBuilder (sb , md );
602
+ }
603
+
604
+ private void appendDataBase (StringBuilder sb ) {
605
+ sb .append (':' ).append (nonce ).append (':' );
606
+ if ("auth" .equals (qop ) || "auth-int" .equals (qop )) {
607
+ sb .append (nc ).append (':' ).append (cnonce ).append (':' ).append (qop ).append (':' );
571
608
}
572
-
573
- appendBase16 (sb , ha2 );
574
- md .update (StringUtils .charSequence2ByteBuffer (sb , ISO_8859_1 ));
575
- sb .setLength (0 );
576
- byte [] digest = md .digest ();
577
-
578
- response = toHexString (digest );
609
+ }
610
+
611
+ private void newResponse (MessageDigest md ) {
612
+ // BEWARE: compute first as it used the cached StringBuilder
613
+ String digestUri = AuthenticatorUtils .computeRealmURI (uri , useAbsoluteURI , omitQuery );
614
+
615
+ StringBuilder sb = StringUtils .stringBuilder ();
616
+
617
+ // WARNING: DON'T MOVE, BUFFER IS RECYCLED!!!!
618
+ byte [] secretDigest = secretDigest (sb , md );
619
+ byte [] dataDigest = dataDigest (sb , digestUri , md );
620
+
621
+ appendBase16 (sb , secretDigest );
622
+ appendDataBase (sb );
623
+ appendBase16 (sb , dataDigest );
624
+
625
+ byte [] responseDigest = md5FromRecycledStringBuilder (sb , md );
626
+ response = toHexString (responseDigest );
579
627
}
580
628
581
629
private static String toHexString (byte [] data ) {
0 commit comments