Skip to content

Commit

Permalink
HTTP Direct Digest Authenticator
Browse files Browse the repository at this point in the history
-use apache Hex api
-rely on parent equals, hash functions
-use CommonHelper funciton to compose nonce random
-else statement on same line with closing if }
-enhance token parsing in Digest extractor instead of DigestCredentials - this is a better
separation of functionality because the extractor should be in charge with extracting data (same as basic extractor is)
DigestCredentials keep extending TokenCredentials, but now the token is actually the client response instead the full
Digest authorization header

User digest validation changes to: digestCredentials.getToken().equals(digestCredentials.calculateServerDigest(true, pwd))
  • Loading branch information
Mircea Carasel committed Feb 19, 2016
1 parent fd68c66 commit 0a9556b
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 87 deletions.
@@ -1,13 +1,11 @@
package org.pac4j.http.client.direct; package org.pac4j.http.client.direct;


import org.pac4j.core.context.WebContext; import org.pac4j.core.context.WebContext;
import org.pac4j.core.exception.CredentialsException;
import org.pac4j.core.exception.RequiresHttpAction; import org.pac4j.core.exception.RequiresHttpAction;
import org.pac4j.core.util.CommonHelper;
import org.pac4j.http.credentials.CredentialUtil; import org.pac4j.http.credentials.CredentialUtil;
import org.pac4j.http.credentials.DigestCredentials; import org.pac4j.http.credentials.DigestCredentials;
import org.pac4j.http.credentials.authenticator.DigestAuthenticator; import org.pac4j.http.credentials.authenticator.DigestAuthenticator;
import org.pac4j.http.credentials.authenticator.UsernamePasswordAuthenticator;
import org.pac4j.http.credentials.extractor.BasicAuthExtractor;
import org.pac4j.http.credentials.extractor.DigestAuthExtractor; import org.pac4j.http.credentials.extractor.DigestAuthExtractor;
import org.pac4j.http.profile.creator.ProfileCreator; import org.pac4j.http.profile.creator.ProfileCreator;


Expand Down Expand Up @@ -70,8 +68,6 @@ private String calculateNonce() {
Date d = new Date(); Date d = new Date();
SimpleDateFormat f = new SimpleDateFormat("yyyy:MM:dd:hh:mm:ss"); SimpleDateFormat f = new SimpleDateFormat("yyyy:MM:dd:hh:mm:ss");
String fmtDate = f.format(d); String fmtDate = f.format(d);
Random rand = new Random(100000); return CredentialUtil.H(fmtDate + CommonHelper.randomString(10));
Integer randomInt = rand.nextInt();
return CredentialUtil.H(fmtDate + randomInt.toString());
} }
} }
@@ -1,5 +1,8 @@
package org.pac4j.http.credentials; package org.pac4j.http.credentials;


import static java.lang.String.copyValueOf;

import org.apache.commons.codec.binary.Hex;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;


Expand All @@ -11,10 +14,6 @@
*/ */
public class CredentialUtil { public class CredentialUtil {


private static final char[] toHex = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};

/** /**
* Defined in rfc 2617 as H(data) = MD5(data); * Defined in rfc 2617 as H(data) = MD5(data);
* *
Expand All @@ -24,30 +23,13 @@ public class CredentialUtil {
public static String H(String data) { public static String H(String data) {
try { try {
MessageDigest digest = MessageDigest.getInstance("MD5"); MessageDigest digest = MessageDigest.getInstance("MD5");

return copyValueOf(Hex.encodeHex(digest.digest(data.getBytes())));
return toHexString(digest.digest(data.getBytes()));
} catch (NoSuchAlgorithmException ex) { } catch (NoSuchAlgorithmException ex) {
// shouldn't happen // shouldn't happen
throw new RuntimeException("Failed to instantiate an MD5 algorithm", ex); throw new RuntimeException("Failed to instantiate an MD5 algorithm", ex);
} }
} }


/**
* Converts b[] to hex string.
*
* @param b the bte array to convert
* @return a Hex representation of b.
*/
public static String toHexString(byte b[]) {
int pos = 0;
char[] c = new char[b.length * 2];
for (int i = 0; i < b.length; i++) {
c[pos++] = toHex[(b[i] >> 4) & 0x0F];
c[pos++] = toHex[b[i] & 0x0f];
}
return new String(c);
}

/** /**
* Defined in rfc 2617 as KD(secret, data) = H(concat(secret, ":", data)) * Defined in rfc 2617 as KD(secret, data) = H(concat(secret, ":", data))
* *
Expand Down
Expand Up @@ -20,7 +20,6 @@
public class DigestCredentials extends TokenCredentials { public class DigestCredentials extends TokenCredentials {


private String username; private String username;
private String response;


private String realm; private String realm;
private String nonce; private String nonce;
Expand All @@ -31,76 +30,36 @@ public class DigestCredentials extends TokenCredentials {


private String httpMethod; private String httpMethod;


public DigestCredentials(final String token, final String httpMethod, final String clientName) { public DigestCredentials(final String token, final String httpMethod, final String clientName, String username, String realm, String nonce, String uri, String cnonce, String nc, String qop) {
//the token represents the client response attribute value in digest authorization header
super(token, clientName); super(token, clientName);
Map<String, String> valueMap = parseTokenValue(token);


username = valueMap.get("username"); this.username = username;
response = valueMap.get("response"); this.realm = realm;

this.nonce = nonce;
if (CommonHelper.isBlank(username) || CommonHelper.isBlank(response)) { this.uri = uri;
throw new CredentialsException("Bad format of the digest auth header"); this.cnonce = cnonce;
} this.nc = nc;
realm = valueMap.get("realm"); this.qop = qop;
nonce = valueMap.get("nonce");
uri = valueMap.get("uri");
cnonce = valueMap.get("cnonce");
nc = valueMap.get("nc");
qop = valueMap.get("qop");
this.httpMethod = httpMethod; this.httpMethod = httpMethod;
} }


public String getUsername() { public String getUsername() {
return username; return username;
} }


public String getResponse() {
return response;
}

public String calculateServerDigest(boolean passwordAlreadyEncoded, String password) { public String calculateServerDigest(boolean passwordAlreadyEncoded, String password) {
return generateDigest(passwordAlreadyEncoded, username, return generateDigest(passwordAlreadyEncoded, username,
realm, password, httpMethod, uri, qop, nonce, nc, cnonce); realm, password, httpMethod, uri, qop, nonce, nc, cnonce);
} }


@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

DigestCredentials that = (DigestCredentials) o;

if (username != null ? !username.equals(that.username) : that.username != null) return false;
return !(response != null ? !response.equals(that.response) : that.response != null);
}

@Override
public int hashCode() {
int result = username != null ? username.hashCode() : 0;
result = 31 * result + (response != null ? response.hashCode() : 0);
return result;
}

@Override @Override
public String toString() { public String toString() {
return CommonHelper.toString(this.getClass(), "username", this.username, "response", "[PROTECTED]", return CommonHelper.toString(this.getClass(), "username", this.username, "response", "[PROTECTED]",
"clientName", getClientName()); "clientName", getClientName());
} }


private Map<String, String> parseTokenValue(String token) {
StringTokenizer tokenizer = new StringTokenizer(token, ", ");
String keyval;
Map map = new HashMap<String, String>();
while (tokenizer.hasMoreElements()) {
keyval = tokenizer.nextToken();
if (keyval.contains("=")) {
String key = keyval.substring(0, keyval.indexOf("="));
String value = keyval.substring(keyval.indexOf("=") + 1);
map.put(key.trim(), value.replaceAll("\"", "").trim());
}
}
return map;
}


private String generateDigest(boolean passwordAlreadyEncoded, String username, private String generateDigest(boolean passwordAlreadyEncoded, String username,
String realm, String password, String httpMethod, String uri, String qop, String realm, String password, String httpMethod, String uri, String qop,
Expand All @@ -111,8 +70,7 @@ private String generateDigest(boolean passwordAlreadyEncoded, String username,


if (passwordAlreadyEncoded) { if (passwordAlreadyEncoded) {
ha1 = password; ha1 = password;
} } else {
else {
ha1 = CredentialUtil.H(username + ":" + realm + ":" +password); ha1 = CredentialUtil.H(username + ":" + realm + ":" +password);
} }


Expand All @@ -121,12 +79,10 @@ private String generateDigest(boolean passwordAlreadyEncoded, String username,
if (qop == null) { if (qop == null) {
// as per RFC 2069 compliant clients (also reaffirmed by RFC 2617) // as per RFC 2069 compliant clients (also reaffirmed by RFC 2617)
digest = CredentialUtil.KD(ha1, nonce + ":" + ha2); digest = CredentialUtil.KD(ha1, nonce + ":" + ha2);
} } else if ("auth".equals(qop)) {
else if ("auth".equals(qop)) {
// As per RFC 2617 compliant clients // As per RFC 2617 compliant clients
digest = CredentialUtil.KD(ha1, nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + ha2); digest = CredentialUtil.KD(ha1, nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + ha2);
} } else {
else {
throw new IllegalArgumentException("Invalid qop: '" throw new IllegalArgumentException("Invalid qop: '"
+ qop + "'"); + qop + "'");
} }
Expand Down
Expand Up @@ -44,9 +44,37 @@ public DigestCredentials extract(WebContext context) {
return null; return null;
} }


String value = credentials.getToken(); String token = credentials.getToken();
Map<String, String> valueMap = parseTokenValue(token);
String username = valueMap.get("username");
String response = valueMap.get("response");

if (CommonHelper.isBlank(username) || CommonHelper.isBlank(response)) {
throw new CredentialsException("Bad format of the digest auth header");
}
String realm = valueMap.get("realm");
String nonce = valueMap.get("nonce");
String uri = valueMap.get("uri");
String cnonce = valueMap.get("cnonce");
String nc = valueMap.get("nc");
String qop = valueMap.get("qop");
String method = context.getRequestMethod(); String method = context.getRequestMethod();


return new DigestCredentials(value, method, clientName); return new DigestCredentials(response, method, clientName, username, realm, nonce, uri, cnonce, nc, qop);
}

private Map<String, String> parseTokenValue(String token) {
StringTokenizer tokenizer = new StringTokenizer(token, ", ");
String keyval;
Map map = new HashMap<String, String>();
while (tokenizer.hasMoreElements()) {
keyval = tokenizer.nextToken();
if (keyval.contains("=")) {
String key = keyval.substring(0, keyval.indexOf("="));
String value = keyval.substring(keyval.indexOf("=") + 1);
map.put(key.trim(), value.replaceAll("\"", "").trim());
}
}
return map;
} }
} }

0 comments on commit 0a9556b

Please sign in to comment.