From 2c7a9a6effabf04678fd31631b638a1f23d73b9b Mon Sep 17 00:00:00 2001 From: Ziver Koc Date: Fri, 28 Oct 2016 17:15:22 +0200 Subject: [PATCH] Digest is now listening to correct headers --- src/zutil/StringUtil.java | 2 +- src/zutil/net/http/HttpHeader.java | 13 ++-- src/zutil/net/http/HttpHeaderParser.java | 5 +- .../net/http/page/HttpDigestAuthPage.java | 77 +++++++++++++++---- .../net/http/page/HttpDigestAuthPageTest.java | 39 ++++++++-- 5 files changed, 107 insertions(+), 29 deletions(-) diff --git a/src/zutil/StringUtil.java b/src/zutil/StringUtil.java index 3b975d5..d4ed203 100755 --- a/src/zutil/StringUtil.java +++ b/src/zutil/StringUtil.java @@ -195,7 +195,7 @@ public class StringUtil { } - private static ArrayList SPACES = new ArrayList(); + private static ArrayList SPACES = new ArrayList<>(); /** * @return A string containing a specific amount of spaces */ diff --git a/src/zutil/net/http/HttpHeader.java b/src/zutil/net/http/HttpHeader.java index af856b7..cc89e93 100755 --- a/src/zutil/net/http/HttpHeader.java +++ b/src/zutil/net/http/HttpHeader.java @@ -148,19 +148,22 @@ public class HttpHeader { } - protected void setIsRequest(boolean request) { this.request = request; } - protected void setRequestType(String type){ + public void setIsRequest(boolean request) { this.request = request; } + public void setRequestType(String type){ this.type = type; } - protected void setHTTPVersion(float version){ + public void setHTTPVersion(float version){ this.version = version; } - protected void setHTTPCode(int code){ + public void setHTTPCode(int code){ this.httpCode = code; } - protected void setRequestURL(String url){ + public void setRequestURL(String url){ this.url = url.trim().replaceAll("//", "/"); } + public void setHeader(String key, String value){ + this.headers.put(key.toUpperCase(), value); + } protected void setInputStream(InputStream in){ this.in = in; } diff --git a/src/zutil/net/http/HttpHeaderParser.java b/src/zutil/net/http/HttpHeaderParser.java index 982dac9..994ee37 100755 --- a/src/zutil/net/http/HttpHeaderParser.java +++ b/src/zutil/net/http/HttpHeaderParser.java @@ -158,6 +158,7 @@ public class HttpHeaderParser { * stores them in a HashMap. If a pair only contain a key the the value * will be set as a empty string. * + * TODO: method is not quote aware * @param map the Map where key and values will be stored. * @param headerValue the raw header value String that will be parsed. * @param delimiter the delimiter that separates key and value pairs (e.g. ';' for Cookies or ',' for Cache-Control) @@ -168,8 +169,8 @@ public class HttpHeaderParser { for(String cookie : tmpArr){ String[] tmpStr = PATTERN_EQUAL.split(cookie, 2); map.put( - tmpStr[0].trim(), // Key - StringUtil.trim((tmpStr.length>1 ? tmpStr[1] : "").trim(), '\"')); //Value + tmpStr[0].trim(), // Key + StringUtil.trim((tmpStr.length>1 ? tmpStr[1] : "").trim(), '\"')); //Value } } } diff --git a/src/zutil/net/http/page/HttpDigestAuthPage.java b/src/zutil/net/http/page/HttpDigestAuthPage.java index dd20c21..1662013 100755 --- a/src/zutil/net/http/page/HttpDigestAuthPage.java +++ b/src/zutil/net/http/page/HttpDigestAuthPage.java @@ -1,35 +1,45 @@ package zutil.net.http.page; +import zutil.Encrypter; +import zutil.Hasher; import zutil.net.http.HttpHeader; import zutil.net.http.HttpPage; import zutil.net.http.HttpPrintStream; import java.io.IOException; +import java.security.SecureRandom; import java.util.Map; /** * A abstract page that requires HTTP Digest authentication * to access the subclass HttpPage. * - * @see rfc2617 + * @see rfc2069 * @author Ziver */ public abstract class HttpDigestAuthPage implements HttpPage{ + private static final String DEAFULT_REALM = "Login"; private static final String HTTP_AUTH_HEADER = "WWW-Authenticate"; + private static final String HTTP_CLIENT_HEADER = "Authorization"; private static final String AUTH_TYPE = "Digest"; private static final String AUTH_REALM = "realm"; - private static final String AUTH_QUALITY_OF_PROTECTION = "qop"; private static final String AUTH_NONCE = "nonce"; - private static final String AUTH_OPAQUE = "opaque"; - + private static final String AUTH_OPAQUE = "opaque"; // OPTIONAL can be used as session data private static final String AUTH_USERNAME = "username"; private static final String AUTH_URI = "uri"; - private static final String AUTH_CNONCE = "cnonce"; private static final String AUTH_RESPONSE = "response"; - private String realm; + private String realm = DEAFULT_REALM; + private SecureRandom secRandom = new SecureRandom(); + + + + public void setRealm(String realm){ + this.realm = realm; + } + @Override @@ -39,20 +49,61 @@ public abstract class HttpDigestAuthPage implements HttpPage{ Map cookie, Map request) throws IOException { - out.setStatusCode(401); - out.setHeader(HTTP_AUTH_HEADER, generateAuthHeader()); + if (headers.getHeader(HTTP_CLIENT_HEADER) == null) { + session.put(AUTH_NONCE, generateNonce()); + out.setStatusCode(401); + out.setHeader(HTTP_AUTH_HEADER, generateAuthHeader((String) session.get(AUTH_NONCE))); + } + else{ + authRespond(out, headers, session, cookie, request); + } } - private String generateAuthHeader(){ + + private String generateAuthHeader(String nonce){ StringBuilder str = new StringBuilder(); str.append(AUTH_TYPE).append(' '); - str.append(AUTH_REALM).append("=\"").append("ll").append("\", "); - str.append(AUTH_QUALITY_OF_PROTECTION).append("=\"").append("ll").append("\", "); - str.append(AUTH_NONCE).append("=\"").append("ll").append("\", "); - str.append(AUTH_OPAQUE).append("=\"").append("ll").append("\""); + str.append(AUTH_REALM).append("=\"").append(realm).append("\", "); + str.append(AUTH_NONCE).append("=\"").append(nonce).append("\", "); + //str.append(AUTH_OPAQUE).append("=\"").append("ll").append("\""); return str.toString(); } + private String generateNonce(){ + byte[] buff = new byte[128/8]; + secRandom.nextBytes(buff); + return Hasher.SHA1(buff); + } + + private static String generateH1(String username, String password, String realm) { + String ha1 = null; + // If the algorithm directive's value is "MD5" or unspecified, then HA1 is + // HA1=MD5(username:realm:password) + ha1 = Hasher.MD5(username +":"+ realm +":"+ password); + // If the algorithm directive's value is "MD5-sess", then HA1 is + // HA1=MD5(MD5(username:realm:password):nonce:cnonce) + return ha1; + } + + private static String generateH2(String uri) { + String ha2; + // If the qop directive's value is "auth" or is unspecified, then HA2 is + // HA2=MD5(method:digestURI) + ha2 = Hasher.MD5("MD5:"+ uri); + // If the qop directive's value is "auth-int", then HA2 is + // HA2=MD5(method:digestURI:MD5(entityBody)) + return ha2; + } + + private static String generateResponseHash(String ha1, String ha2, String nonce){ + String response; + // If the qop directive's value is "auth" or "auth-int", then compute the response as follows: + // response=MD5(HA1:nonce:nonceCount:cnonce:qop:HA2) + // If the qop directive is unspecified, then compute the response as follows: + // response=MD5(HA1:nonce:HA2) + response = Hasher.MD5(ha1 +":"+ nonce +":"+ ha2); + return response; + } public abstract void authRespond(HttpPrintStream out, diff --git a/test/zutil/net/http/page/HttpDigestAuthPageTest.java b/test/zutil/net/http/page/HttpDigestAuthPageTest.java index e490223..79134ed 100755 --- a/test/zutil/net/http/page/HttpDigestAuthPageTest.java +++ b/test/zutil/net/http/page/HttpDigestAuthPageTest.java @@ -1,6 +1,7 @@ package zutil.net.http.page; import org.junit.Test; +import zutil.Hasher; import zutil.io.StringOutputStream; import zutil.net.http.HttpHeader; import zutil.net.http.HttpHeaderParser; @@ -20,20 +21,42 @@ public class HttpDigestAuthPageTest { @Test public void cleanRequest() throws IOException { - HttpHeader header = new HttpHeader(); - HttpHeader output = makeRequest(header); + HttpHeader rspHeader = makeRequest(new HttpHeader()); - assertEquals(401, output.getHTTPCode()); - assertTrue(output.getHeader("WWW-Authenticate") != null); - assertEquals("Digest", parseAuthType(output)); - Map authHeader = parseAuthHeader(output); + assertEquals(401, rspHeader.getHTTPCode()); + assertTrue(rspHeader.getHeader("WWW-Authenticate") != null); + assertEquals("Digest", parseAuthType(rspHeader)); + Map authHeader = parseAuthHeader(rspHeader); assertTrue(authHeader.containsKey("realm")); - assertTrue(authHeader.containsKey("qop")); assertTrue(authHeader.containsKey("nonce")); - assertTrue(authHeader.containsKey("opaque")); } + @Test + public void authenticate() throws IOException { + HttpHeader reqHeader = new HttpHeader(); + HttpHeader rspHeader = makeRequest(reqHeader); + Map authHeader = parseAuthHeader(rspHeader); + reqHeader = new HttpHeader(); + + String realm = authHeader.get("realm"); + String nonce = authHeader.get("nonce"); + String uri = "/login"; + + String ha1 = Hasher.MD5("username:password"); + String ha2 = Hasher.MD5("MD5:/" +uri); + String response = Hasher.MD5(ha1 +":"+ nonce +":"+ ha2); + reqHeader.setHeader("Authorization", "Digest username=\"username\", " + + "realm=\""+realm+"\", " + + "nonce=\""+nonce+"\", " + + "uri=\""+uri+"\", " + + "response=\""+response+"\""); + rspHeader = makeRequest(reqHeader); + assertEquals(200, rspHeader.getHTTPCode()); + } + + +