Skip to content

Commit a5763a6

Browse files
pratt4hyperxpro
andauthored
Digest auth new (#2098)
This is build on top of #2089 and still some changes are required around new testcases and failing testcases closes #2068 --------- Co-authored-by: Aayush Atharva <aayush@shieldblaze.com>
1 parent ecb80f8 commit a5763a6

File tree

8 files changed

+1149
-68
lines changed

8 files changed

+1149
-68
lines changed

client/src/main/java/org/asynchttpclient/Realm.java

Lines changed: 52 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,18 @@
2424

2525
import java.nio.charset.Charset;
2626
import java.security.MessageDigest;
27+
import java.util.Arrays;
2728
import java.util.Map;
2829
import java.util.concurrent.ThreadLocalRandom;
2930

3031
import static java.nio.charset.StandardCharsets.ISO_8859_1;
3132
import static java.nio.charset.StandardCharsets.UTF_8;
3233
import static java.util.Objects.requireNonNull;
3334
import static org.asynchttpclient.util.HttpConstants.Methods.GET;
34-
import static org.asynchttpclient.util.MessageDigestUtils.pooledMd5MessageDigest;
3535
import static org.asynchttpclient.util.MiscUtils.isNonEmpty;
3636
import static org.asynchttpclient.util.StringUtils.appendBase16;
3737
import 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

Comments
 (0)