You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@cxf.apache.org by "Evgeny Shakin (JIRA)" <ji...@apache.org> on 2013/10/30 10:40:26 UTC
[jira] [Comment Edited] (CXF-5366) Authorization header is not set
correctly in CXF HTTP digest authentication
[ https://issues.apache.org/jira/browse/CXF-5366?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=13808893#comment-13808893 ]
Evgeny Shakin edited comment on CXF-5366 at 10/30/13 9:40 AM:
--------------------------------------------------------------
Sure, here is the code of my customized DigestAuthSupplier, the changes vis-a-vis the original DigestAuthSupplier are hardly noticeable, please watch for //ES: comments. This auth supplier is set on the HTTPConduit as follows: conduit.setAuthSupplier(new CustomAuthSupplier());
public class CustomAuthSupplier implements HttpAuthSupplier {
private static final char[] HEXADECIMAL = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
final MessageDigest md5Helper;
Map<URI, DigestInfo> authInfo = new ConcurrentHashMap<URI, DigestInfo>();
public CustomAuthSupplier() {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
//ignore - set to null
}
md5Helper = md;
}
/**
* {@inheritDoc}
* With digest, the nonce could expire and thus a rechallenge will be issued.
* Thus, we need requests cached to be able to handle that
*/
public boolean requiresRequestCaching() {
return true;
}
public String getAuthorization(AuthorizationPolicy authPolicy,
URI currentURI,
Message message,
String fullHeader) {
if (authPolicy == null || (authPolicy.getUserName() == null && authPolicy.getPassword() == null)) {
return null;
}
if (fullHeader == null) {
DigestInfo di = authInfo.get(currentURI);
if (di != null) {
/* Preemptive authentication is only possible if we have a cached
* challenge
*/
return di.generateAuth(currentURI.getPath(),
authPolicy.getUserName(),
authPolicy.getPassword());
} else {
return null;
}
}
HttpAuthHeader authHeader = new HttpAuthHeader(fullHeader);
if (authHeader.authTypeIsDigest()) {
Map<String, String> map = authHeader.getParams();
if ("auth".equals(map.get("qop"))
\|| !map.containsKey("qop")) {
DigestInfo di = new DigestInfo();
di.qop = map.get("qop");
di.realm = map.get("realm");
di.nonce = map.get("nonce");
di.opaque = map.get("opaque");
if (map.containsKey("algorithm")) {
di.algorithm = map.get("algorithm");
}
if (map.containsKey("charset")) {
di.charset = map.get("charset");
}
di.method = (String)message.get(Message.HTTP_REQUEST_METHOD);
if (di.method == null) {
di.method = "POST";
}
authInfo.put(currentURI, di);
return di.generateAuth(currentURI.getPath(),
authPolicy.getUserName(),
authPolicy.getPassword());
}
}
return null;
}
public String createCnonce() throws UnsupportedEncodingException {
String cnonce = Long.toString(System.currentTimeMillis());
byte[] bytes = cnonce.getBytes("US-ASCII");
synchronized (md5Helper) {
bytes = md5Helper.digest(bytes);
}
return encode(bytes);
}
class DigestInfo {
String qop;
String realm;
String nonce;
String opaque;
int nc;
String algorithm = "MD5";
String charset = "ISO-8859-1";
String method = "POST";
synchronized String generateAuth(String uri, String username, String password) {
try {
nc++;
String ncstring = String.format("%08d", nc);
String cnonce = createCnonce();
String digAlg = algorithm;
if (digAlg.equalsIgnoreCase("MD5-sess")) {
digAlg = "MD5";
}
MessageDigest digester = MessageDigest.getInstance(digAlg);
String a1 = username + ":" + realm + ":" + password;
if ("MD5-sess".equalsIgnoreCase(algorithm)) {
// ES:removed
// algorithm = "MD5";
String tmp2 = encode(digester.digest(a1.getBytes(charset)));
a1 = tmp2 + ':' + nonce + ':' + cnonce;
}
String hasha1 = encode(digester.digest(a1.getBytes(charset)));
String a2 = method + ":" + uri;
String hasha2 = encode(digester.digest(a2.getBytes("US-ASCII")));
String serverDigestValue = null;
if (qop == null) {
serverDigestValue = hasha1 + ":" + nonce + ":" + hasha2;
} else {
serverDigestValue = hasha1 + ":" + nonce + ":" + ncstring + ":" + cnonce + ":"
+ qop + ":" + hasha2;
}
String response = encode(digester.digest(serverDigestValue.getBytes("US-ASCII")));
Map<String, String> outParams = new HashMap<String, String>();
outParams.put("username", username);
outParams.put("realm", realm);
outParams.put("nonce", nonce);
outParams.put("nc", ncstring);
outParams.put("uri", uri);
outParams.put("response", response);
//ES: added
outParams.put("algorithm", algorithm);
outParams.put("cnonce", cnonce);
if (qop != null) {
outParams.put("qop", "auth");
}
outParams.put("opaque", opaque);
return new HttpAuthHeader(HttpAuthHeader.AUTH_TYPE_DIGEST, outParams).getFullHeader();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
/**
* Encodes the 128 bit (16 bytes) MD5 digest into a 32 characters long
* <CODE>String</CODE> according to RFC 2617.
*
* @param binaryData array containing the digest
* @return encoded MD5, or <CODE>null</CODE> if encoding failed
*/
private static String encode(byte[] binaryData) {
int n = binaryData.length;
char[] buffer = new char[n * 2];
for (int i = 0; i < n; i++) {
int low = binaryData[i] & 0x0f;
int high = (binaryData[i] & 0xf0) >> 4;
buffer[i * 2] = HEXADECIMAL[high];
buffer[(i * 2) + 1] = HEXADECIMAL[low];
}
return new String(buffer);
}
}
was (Author: chakine):
Sure, here is the code of my customized DigestAuthSupplier, the changes vis-a-vis the original DigestAuthSupplier are hardly noticeable, please watch for //ES: comments. This auth supplier is set on the HTTPConduit as follows: conduit.setAuthSupplier(new CustomAuthSupplier());
public class CustomAuthSupplier implements HttpAuthSupplier {
private static final char[] HEXADECIMAL = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
final MessageDigest md5Helper;
Map<URI, DigestInfo> authInfo = new ConcurrentHashMap<URI, DigestInfo>();
public CustomAuthSupplier() {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
//ignore - set to null
}
md5Helper = md;
}
/**
* {@inheritDoc}
* With digest, the nonce could expire and thus a rechallenge will be issued.
* Thus, we need requests cached to be able to handle that
*/
public boolean requiresRequestCaching() {
return true;
}
public String getAuthorization(AuthorizationPolicy authPolicy,
URI currentURI,
Message message,
String fullHeader) {
if (authPolicy == null || (authPolicy.getUserName() == null && authPolicy.getPassword() == null)) {
return null;
}
if (fullHeader == null) {
DigestInfo di = authInfo.get(currentURI);
if (di != null) {
/* Preemptive authentication is only possible if we have a cached
* challenge
*/
return di.generateAuth(currentURI.getPath(),
authPolicy.getUserName(),
authPolicy.getPassword());
} else {
return null;
}
}
HttpAuthHeader authHeader = new HttpAuthHeader(fullHeader);
if (authHeader.authTypeIsDigest()) {
Map<String, String> map = authHeader.getParams();
if ("auth".equals(map.get("qop"))
|| !map.containsKey("qop")) {
DigestInfo di = new DigestInfo();
di.qop = map.get("qop");
di.realm = map.get("realm");
di.nonce = map.get("nonce");
di.opaque = map.get("opaque");
if (map.containsKey("algorithm")) {
di.algorithm = map.get("algorithm");
}
if (map.containsKey("charset")) {
di.charset = map.get("charset");
}
di.method = (String)message.get(Message.HTTP_REQUEST_METHOD);
if (di.method == null) {
di.method = "POST";
}
authInfo.put(currentURI, di);
return di.generateAuth(currentURI.getPath(),
authPolicy.getUserName(),
authPolicy.getPassword());
}
}
return null;
}
public String createCnonce() throws UnsupportedEncodingException {
String cnonce = Long.toString(System.currentTimeMillis());
byte[] bytes = cnonce.getBytes("US-ASCII");
synchronized (md5Helper) {
bytes = md5Helper.digest(bytes);
}
return encode(bytes);
}
class DigestInfo {
String qop;
String realm;
String nonce;
String opaque;
int nc;
String algorithm = "MD5";
String charset = "ISO-8859-1";
String method = "POST";
synchronized String generateAuth(String uri, String username, String password) {
try {
nc++;
String ncstring = String.format("%08d", nc);
String cnonce = createCnonce();
String digAlg = algorithm;
if (digAlg.equalsIgnoreCase("MD5-sess")) {
digAlg = "MD5";
}
MessageDigest digester = MessageDigest.getInstance(digAlg);
String a1 = username + ":" + realm + ":" + password;
if ("MD5-sess".equalsIgnoreCase(algorithm)) {
// ES:removed
// algorithm = "MD5";
String tmp2 = encode(digester.digest(a1.getBytes(charset)));
a1 = tmp2 + ':' + nonce + ':' + cnonce;
}
String hasha1 = encode(digester.digest(a1.getBytes(charset)));
String a2 = method + ":" + uri;
String hasha2 = encode(digester.digest(a2.getBytes("US-ASCII")));
String serverDigestValue = null;
if (qop == null) {
serverDigestValue = hasha1 + ":" + nonce + ":" + hasha2;
} else {
serverDigestValue = hasha1 + ":" + nonce + ":" + ncstring + ":" + cnonce + ":"
+ qop + ":" + hasha2;
}
String response = encode(digester.digest(serverDigestValue.getBytes("US-ASCII")));
Map<String, String> outParams = new HashMap<String, String>();
outParams.put("username", username);
outParams.put("realm", realm);
outParams.put("nonce", nonce);
outParams.put("nc", ncstring);
outParams.put("uri", uri);
outParams.put("response", response);
//ES: added
outParams.put("algorithm", algorithm);
outParams.put("cnonce", cnonce);
if (qop != null) {
outParams.put("qop", "auth");
}
outParams.put("opaque", opaque);
return new HttpAuthHeader(HttpAuthHeader.AUTH_TYPE_DIGEST, outParams).getFullHeader();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
/**
* Encodes the 128 bit (16 bytes) MD5 digest into a 32 characters long
* <CODE>String</CODE> according to RFC 2617.
*
* @param binaryData array containing the digest
* @return encoded MD5, or <CODE>null</CODE> if encoding failed
*/
private static String encode(byte[] binaryData) {
int n = binaryData.length;
char[] buffer = new char[n * 2];
for (int i = 0; i < n; i++) {
int low = binaryData[i] & 0x0f;
int high = (binaryData[i] & 0xf0) >> 4;
buffer[i * 2] = HEXADECIMAL[high];
buffer[(i * 2) + 1] = HEXADECIMAL[low];
}
return new String(buffer);
}
}
> Authorization header is not set correctly in CXF HTTP digest authentication
> ----------------------------------------------------------------------------
>
> Key: CXF-5366
> URL: https://issues.apache.org/jira/browse/CXF-5366
> Project: CXF
> Issue Type: Bug
> Components: Core
> Affects Versions: 2.7.4, 2.7.5, 2.7.6, 2.7.7
> Environment: Windows 7 64 bit, Java 1.6.0_29, CXF 2.7.4, calling MS Dynamics WS.
> Reporter: Evgeny Shakin
>
> When performing the digest HTTP authentication the generated Authorization header is missing the "algorithm" element. Also if the algorithm is "MD5-sess" it should appear in the Authorization header as is and not as "MD5". To get around the issue it is possible to use a customized DigestAuthSupplier for the affected CXF versions. The result of WS invocation without "algorithm" in the Authorization header is 400-Bad request.
> The issue relates to versions of CXF 2.7.4 and later, earlier versions work fine.
> Sample request:
> POST /XXXXXXX HTTP/1.1
> Content-Type: text/xml; charset=UTF-8
> Accept: */*
> SOAPAction: "http://schemas.microsoft.com/dynamics/XXXXXXX"
> User-Agent: Apache CXF 2.7.4
> Cache-Control: no-cache
> Pragma: no-cache
> Host: XXXXX
> Connection: keep-alive
> Content-Length: 542
> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body>XXXXX</soap:Body></soap:Envelope>
> POST /XXXXX HTTP/1.1
> Content-Type: text/xml; charset=UTF-8
> Accept: */*
> Authorization: Digest response="541f8d073f2be81deae8e2f1065725b2", cnonce="46f26ffb6cf32b66873bf6e5e955bae8", username="XXXXX", nc="00000001", nonce="+Upgraded+v126a0f6047dd70851ab2155a14d09d56aacd7cd4a87d1ce01d77d4709393a1585490f57bdd6026b2c339c1f27bc03f4e47400ad20e8208244", realm="Digest", qop="auth", uri="/XXXXXXX"
> SOAPAction: "http://schemas.microsoft.com/dynamics/XXXXXXX"
> User-Agent: Apache CXF 2.7.4
> Cache-Control: no-cache
> Pragma: no-cache
> Host: localhost:8887
> Connection: keep-alive
> Content-Length: 542
> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body>XXXXXX</soap:Body></soap:Envelope>
> Sample response:
> HTTP/1.1 401 Unauthorized
> Content-Length: 0
> Server: Microsoft-HTTPAPI/2.0
> WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v126a0f6047dd70851ab2155a14d09d56af26b5ad2f0d3ce0169267269a2cfa168709705665fd13f9adf81235595c672ec1623b17e470ccaef",charset=utf-8,realm="Digest"
> Date: Mon, 28 Oct 2013 15:17:31 GMT
> HTTP/1.1 400 Bad Request
> Content-Length: 0
> Server: Microsoft-HTTPAPI/2.0
> Date: Mon, 28 Oct 2013 15:17:31 GMT
--
This message was sent by Atlassian JIRA
(v6.1#6144)