[ 
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)

Reply via email to