Digest is now listening to correct headers

This commit is contained in:
Ziver Koc 2016-10-28 17:15:22 +02:00
parent c4efab0609
commit 2c7a9a6eff
5 changed files with 107 additions and 29 deletions

View file

@ -195,7 +195,7 @@ public class StringUtil {
} }
private static ArrayList<String> SPACES = new ArrayList<String>(); private static ArrayList<String> SPACES = new ArrayList<>();
/** /**
* @return A string containing a specific amount of spaces * @return A string containing a specific amount of spaces
*/ */

View file

@ -148,19 +148,22 @@ public class HttpHeader {
} }
protected void setIsRequest(boolean request) { this.request = request; } public void setIsRequest(boolean request) { this.request = request; }
protected void setRequestType(String type){ public void setRequestType(String type){
this.type = type; this.type = type;
} }
protected void setHTTPVersion(float version){ public void setHTTPVersion(float version){
this.version = version; this.version = version;
} }
protected void setHTTPCode(int code){ public void setHTTPCode(int code){
this.httpCode = code; this.httpCode = code;
} }
protected void setRequestURL(String url){ public void setRequestURL(String url){
this.url = url.trim().replaceAll("//", "/"); this.url = url.trim().replaceAll("//", "/");
} }
public void setHeader(String key, String value){
this.headers.put(key.toUpperCase(), value);
}
protected void setInputStream(InputStream in){ protected void setInputStream(InputStream in){
this.in = in; this.in = in;
} }

View file

@ -158,6 +158,7 @@ public class HttpHeaderParser {
* stores them in a HashMap. If a pair only contain a key the the value * stores them in a HashMap. If a pair only contain a key the the value
* will be set as a empty string. * 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 map the Map where key and values will be stored.
* @param headerValue the raw header value String that will be parsed. * @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) * @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){ for(String cookie : tmpArr){
String[] tmpStr = PATTERN_EQUAL.split(cookie, 2); String[] tmpStr = PATTERN_EQUAL.split(cookie, 2);
map.put( map.put(
tmpStr[0].trim(), // Key tmpStr[0].trim(), // Key
StringUtil.trim((tmpStr.length>1 ? tmpStr[1] : "").trim(), '\"')); //Value StringUtil.trim((tmpStr.length>1 ? tmpStr[1] : "").trim(), '\"')); //Value
} }
} }
} }

View file

@ -1,35 +1,45 @@
package zutil.net.http.page; package zutil.net.http.page;
import zutil.Encrypter;
import zutil.Hasher;
import zutil.net.http.HttpHeader; import zutil.net.http.HttpHeader;
import zutil.net.http.HttpPage; import zutil.net.http.HttpPage;
import zutil.net.http.HttpPrintStream; import zutil.net.http.HttpPrintStream;
import java.io.IOException; import java.io.IOException;
import java.security.SecureRandom;
import java.util.Map; import java.util.Map;
/** /**
* A abstract page that requires HTTP Digest authentication * A abstract page that requires HTTP Digest authentication
* to access the subclass HttpPage. * to access the subclass HttpPage.
* *
* @see <a href="https://tools.ietf.org/html/rfc2617">rfc2617</a> * @see <a href="https://tools.ietf.org/html/rfc2069">rfc2069</a>
* @author Ziver * @author Ziver
*/ */
public abstract class HttpDigestAuthPage implements HttpPage{ 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_AUTH_HEADER = "WWW-Authenticate";
private static final String HTTP_CLIENT_HEADER = "Authorization";
private static final String AUTH_TYPE = "Digest"; private static final String AUTH_TYPE = "Digest";
private static final String AUTH_REALM = "realm"; 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_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_USERNAME = "username";
private static final String AUTH_URI = "uri"; private static final String AUTH_URI = "uri";
private static final String AUTH_CNONCE = "cnonce";
private static final String AUTH_RESPONSE = "response"; 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 @Override
@ -39,20 +49,61 @@ public abstract class HttpDigestAuthPage implements HttpPage{
Map<String, String> cookie, Map<String, String> cookie,
Map<String, String> request) throws IOException { Map<String, String> request) throws IOException {
out.setStatusCode(401); if (headers.getHeader(HTTP_CLIENT_HEADER) == null) {
out.setHeader(HTTP_AUTH_HEADER, generateAuthHeader()); 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(); StringBuilder str = new StringBuilder();
str.append(AUTH_TYPE).append(' '); str.append(AUTH_TYPE).append(' ');
str.append(AUTH_REALM).append("=\"").append("ll").append("\", "); str.append(AUTH_REALM).append("=\"").append(realm).append("\", ");
str.append(AUTH_QUALITY_OF_PROTECTION).append("=\"").append("ll").append("\", "); str.append(AUTH_NONCE).append("=\"").append(nonce).append("\", ");
str.append(AUTH_NONCE).append("=\"").append("ll").append("\", "); //str.append(AUTH_OPAQUE).append("=\"").append("ll").append("\"");
str.append(AUTH_OPAQUE).append("=\"").append("ll").append("\"");
return str.toString(); 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, public abstract void authRespond(HttpPrintStream out,

View file

@ -1,6 +1,7 @@
package zutil.net.http.page; package zutil.net.http.page;
import org.junit.Test; import org.junit.Test;
import zutil.Hasher;
import zutil.io.StringOutputStream; import zutil.io.StringOutputStream;
import zutil.net.http.HttpHeader; import zutil.net.http.HttpHeader;
import zutil.net.http.HttpHeaderParser; import zutil.net.http.HttpHeaderParser;
@ -20,20 +21,42 @@ public class HttpDigestAuthPageTest {
@Test @Test
public void cleanRequest() throws IOException { public void cleanRequest() throws IOException {
HttpHeader header = new HttpHeader(); HttpHeader rspHeader = makeRequest(new HttpHeader());
HttpHeader output = makeRequest(header);
assertEquals(401, output.getHTTPCode()); assertEquals(401, rspHeader.getHTTPCode());
assertTrue(output.getHeader("WWW-Authenticate") != null); assertTrue(rspHeader.getHeader("WWW-Authenticate") != null);
assertEquals("Digest", parseAuthType(output)); assertEquals("Digest", parseAuthType(rspHeader));
Map<String,String> authHeader = parseAuthHeader(output); Map<String,String> authHeader = parseAuthHeader(rspHeader);
assertTrue(authHeader.containsKey("realm")); assertTrue(authHeader.containsKey("realm"));
assertTrue(authHeader.containsKey("qop"));
assertTrue(authHeader.containsKey("nonce")); assertTrue(authHeader.containsKey("nonce"));
assertTrue(authHeader.containsKey("opaque"));
} }
@Test
public void authenticate() throws IOException {
HttpHeader reqHeader = new HttpHeader();
HttpHeader rspHeader = makeRequest(reqHeader);
Map<String,String> 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());
}