diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt index 9106f7c61..e02d1a968 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.txt @@ -1,3 +1,9 @@ +Changes since 4.1 + +* [HTTPCLIENT-1053] Fixed the way DigestScheme generates nonce-count values. + Contributed by Oleg Kalnichevski + + Release 4.1 ------------------- diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java b/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java index bbd6c36f4..6b4735066 100644 --- a/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java +++ b/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java @@ -28,7 +28,9 @@ package org.apache.http.impl.auth; import java.security.MessageDigest; import java.util.ArrayList; +import java.util.Formatter; import java.util.List; +import java.util.Locale; import java.util.StringTokenizer; import org.apache.http.annotation.NotThreadSafe; @@ -88,14 +90,15 @@ public class DigestScheme extends RFC2617Scheme { /** Whether the digest authentication process is complete */ private boolean complete; - //TODO: supply a real nonce-count, currently a server will interprete a repeated request as a replay - private static final String NC = "00000001"; //nonce-count is always 1 private static final int QOP_MISSING = 0; private static final int QOP_AUTH_INT = 1; private static final int QOP_AUTH = 2; private int qopVariant = QOP_MISSING; + private String lastNonce; + private long nounceCount; private String cnonce; + private String nc; /** * Default constructor for the digest authetication scheme. @@ -146,8 +149,6 @@ public class DigestScheme extends RFC2617Scheme { if (unsupportedQop && (qopVariant == QOP_MISSING)) { throw new MalformedChallengeException("None of the qop methods is supported"); } - // Reset cnonce - this.cnonce = null; this.complete = true; } @@ -195,6 +196,16 @@ public class DigestScheme extends RFC2617Scheme { return this.cnonce; } + private String getNc() { + if (this.nc == null) { + StringBuilder sb = new StringBuilder(); + Formatter formatter = new Formatter(sb, Locale.US); + formatter.format("%08x", this.nounceCount); + this.nc = sb.toString(); + } + return this.nc; + } + /** * Produces a digest authorization string for the given set of * {@link Credentials}, method name and URI. @@ -266,6 +277,11 @@ public class DigestScheme extends RFC2617Scheme { if (nonce == null) { throw new IllegalStateException("Nonce may not be null"); } + + // Reset + this.cnonce = null; + this.nc = null; + // If an algorithm is not specified, default to MD5. if (algorithm == null) { algorithm = "MD5"; @@ -285,6 +301,14 @@ public class DigestScheme extends RFC2617Scheme { if (digAlg.equalsIgnoreCase("MD5-sess")) { digAlg = "MD5"; } + + if (nonce.equals(this.lastNonce)) { + this.nounceCount++; + } else { + this.nounceCount = 1; + this.lastNonce = nonce; + } + MessageDigest digester = createMessageDigest(digAlg); String uname = credentials.getUserPrincipal().getName(); @@ -345,14 +369,14 @@ public class DigestScheme extends RFC2617Scheme { } else { String qopOption = getQopVariantString(); String cnonce = getCnonce(); - + String nc = getNc(); StringBuilder tmp2 = new StringBuilder(hasha1.length() + nonce.length() - + NC.length() + cnonce.length() + qopOption.length() + hasha2.length() + 5); + + nc.length() + cnonce.length() + qopOption.length() + hasha2.length() + 5); tmp2.append(hasha1); tmp2.append(':'); tmp2.append(nonce); tmp2.append(':'); - tmp2.append(NC); + tmp2.append(nc); tmp2.append(':'); tmp2.append(cnonce); tmp2.append(':'); @@ -406,7 +430,7 @@ public class DigestScheme extends RFC2617Scheme { if (qopVariant != QOP_MISSING) { params.add(new BasicNameValuePair("qop", getQopVariantString())); - params.add(new BasicNameValuePair("nc", NC)); + params.add(new BasicNameValuePair("nc", getNc())); params.add(new BasicNameValuePair("cnonce", getCnonce())); } if (algorithm != null) { diff --git a/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java b/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java index bc9e8e17b..f4dcf61f4 100644 --- a/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java +++ b/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java @@ -304,4 +304,32 @@ public class TestDigestScheme { return map; } + @Test + public void testDigestNouceCount() throws Exception { + String challenge1 = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=auth"; + Header authChallenge1 = new BasicHeader(AUTH.WWW_AUTH, challenge1); + HttpRequest request = new BasicHttpRequest("Simple", "/"); + Credentials cred = new UsernamePasswordCredentials("username","password"); + DigestScheme authscheme = new DigestScheme(); + authscheme.processChallenge(authChallenge1); + Header authResponse1 = authscheme.authenticate(cred, request); + Map table1 = parseAuthResponse(authResponse1); + Assert.assertEquals("00000001", table1.get("nc")); + Header authResponse2 = authscheme.authenticate(cred, request); + Map table2 = parseAuthResponse(authResponse2); + Assert.assertEquals("00000002", table2.get("nc")); + String challenge2 = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=auth"; + Header authChallenge2 = new BasicHeader(AUTH.WWW_AUTH, challenge2); + authscheme.processChallenge(authChallenge2); + Header authResponse3 = authscheme.authenticate(cred, request); + Map table3 = parseAuthResponse(authResponse3); + Assert.assertEquals("00000003", table3.get("nc")); + String challenge3 = "Digest realm=\"realm1\", nonce=\"e273f1776275974f1a120d8b92c5b3cb\", qop=auth"; + Header authChallenge3 = new BasicHeader(AUTH.WWW_AUTH, challenge3); + authscheme.processChallenge(authChallenge3); + Header authResponse4 = authscheme.authenticate(cred, request); + Map table4 = parseAuthResponse(authResponse4); + Assert.assertEquals("00000001", table4.get("nc")); + } + }