FileDocCategorySizeDatePackage
DigestScheme.javaAPI DocAndroid 1.5 API17493Wed May 06 22:41:10 BST 2009org.apache.http.impl.auth

DigestScheme

public class DigestScheme extends RFC2617Scheme

Digest authentication scheme as defined in RFC 2617. Both MD5 (default) and MD5-sess are supported. Currently only qop=auth or no qop is supported. qop=auth-int is unsupported. If auth and auth-int are provided, auth is used.

Credential charset is configured via the {@link org.apache.http.auth.params.AuthPNames#CREDENTIAL_CHARSET credential charset} parameter. Since the digest username is included as clear text in the generated Authentication header, the charset of the username must be compatible with the {@link org.apache.http.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET http element charset}.

author
Remy Maucherat
author
Rodney Waldhoff
author
Jeff Dever
author
Ortwin Glueck
author
Sean C. Sullivan
author
Adrian Sutton
author
Mike Bowler
author
Oleg Kalnichevski
since
4.0

Fields Summary
private static final char[]
HEXADECIMAL
Hexa values used when creating 32 character long digest in HTTP DigestScheme in case of authentication.
private boolean
complete
Whether the digest authentication process is complete
private static final String
NC
private static final int
QOP_MISSING
private static final int
QOP_AUTH_INT
private static final int
QOP_AUTH
private int
qopVariant
private String
cnonce
Constructors Summary
public DigestScheme()
Default constructor for the digest authetication scheme.


                
      
        super();
        this.complete = false;
    
Methods Summary
public org.apache.http.Headerauthenticate(org.apache.http.auth.Credentials credentials, org.apache.http.HttpRequest request)
Produces a digest authorization string for the given set of {@link Credentials}, method name and URI.

param
credentials A set of credentials to be used for athentication
param
request The request being authenticated
throws
org.apache.http.auth.InvalidCredentialsException if authentication credentials are not valid or not applicable for this authentication scheme
throws
AuthenticationException if authorization string cannot be generated due to an authentication failure
return
a digest authorization string


        if (credentials == null) {
            throw new IllegalArgumentException("Credentials may not be null");
        }
        if (request == null) {
            throw new IllegalArgumentException("HTTP request may not be null");
        }
        
        // Add method name and request-URI to the parameter map
        getParameters().put("methodname", request.getRequestLine().getMethod());
        getParameters().put("uri", request.getRequestLine().getUri());
        String charset = getParameter("charset");
        if (charset == null) {
            charset = AuthParams.getCredentialCharset(request.getParams());
            getParameters().put("charset", charset);
        }
        String digest = createDigest(credentials);
        return createDigestHeader(credentials, digest);
    
public static java.lang.StringcreateCnonce()
Creates a random cnonce value based on the current time.

return
The cnonce value as String.
throws
UnsupportedDigestAlgorithmException if MD5 algorithm is not supported.

        String cnonce;

        MessageDigest md5Helper = createMessageDigest("MD5");

        cnonce = Long.toString(System.currentTimeMillis());
        cnonce = encode(md5Helper.digest(EncodingUtils.getAsciiBytes(cnonce)));

        return cnonce;
    
private java.lang.StringcreateDigest(org.apache.http.auth.Credentials credentials)
Creates an MD5 response digest.

return
The created digest as string. This will be the response tag's value in the Authentication HTTP header.
throws
AuthenticationException when MD5 is an unsupported algorithm

        // Collecting required tokens
        String uri = getParameter("uri");
        String realm = getParameter("realm");
        String nonce = getParameter("nonce");
        String method = getParameter("methodname");
        String algorithm = getParameter("algorithm");
        if (uri == null) {
            throw new IllegalStateException("URI may not be null");
        }
        if (realm == null) {
            throw new IllegalStateException("Realm may not be null");
        }
        if (nonce == null) {
            throw new IllegalStateException("Nonce may not be null");
        }
        // If an algorithm is not specified, default to MD5.
        if (algorithm == null) {
            algorithm = "MD5";
        }
        // If an charset is not specified, default to ISO-8859-1.
        String charset = getParameter("charset");
        if (charset == null) {
            charset = "ISO-8859-1";
        }

        if (qopVariant == QOP_AUTH_INT) {
            throw new AuthenticationException(
                "Unsupported qop in HTTP Digest authentication");   
        }

        MessageDigest md5Helper = createMessageDigest("MD5");

        String uname = credentials.getUserPrincipal().getName();
        String pwd = credentials.getPassword();
        
        // 3.2.2.2: Calculating digest
        StringBuilder tmp = new StringBuilder(uname.length() + realm.length() + pwd.length() + 2);
        tmp.append(uname);
        tmp.append(':");
        tmp.append(realm);
        tmp.append(':");
        tmp.append(pwd);
        // unq(username-value) ":" unq(realm-value) ":" passwd
        String a1 = tmp.toString();
        
        //a1 is suitable for MD5 algorithm
        if(algorithm.equals("MD5-sess")) {
            // H( unq(username-value) ":" unq(realm-value) ":" passwd )
            //      ":" unq(nonce-value)
            //      ":" unq(cnonce-value)

            String cnonce = getCnonce();
            
            String tmp2=encode(md5Helper.digest(EncodingUtils.getBytes(a1, charset)));
            StringBuilder tmp3 = new StringBuilder(tmp2.length() + nonce.length() + cnonce.length() + 2);
            tmp3.append(tmp2);
            tmp3.append(':");
            tmp3.append(nonce);
            tmp3.append(':");
            tmp3.append(cnonce);
            a1 = tmp3.toString();
        } else if (!algorithm.equals("MD5")) {
            throw new AuthenticationException("Unhandled algorithm " + algorithm + " requested");
        }
        String md5a1 = encode(md5Helper.digest(EncodingUtils.getBytes(a1, charset)));

        String a2 = null;
        if (qopVariant == QOP_AUTH_INT) {
            // Unhandled qop auth-int
            //we do not have access to the entity-body or its hash
            //TODO: add Method ":" digest-uri-value ":" H(entity-body)      
        } else {
            a2 = method + ':" + uri;
        }
        String md5a2 = encode(md5Helper.digest(EncodingUtils.getAsciiBytes(a2)));

        // 3.2.2.1
        String serverDigestValue;
        if (qopVariant == QOP_MISSING) {
            StringBuilder tmp2 = new StringBuilder(md5a1.length() + nonce.length() + md5a2.length());
            tmp2.append(md5a1);
            tmp2.append(':");
            tmp2.append(nonce);
            tmp2.append(':");
            tmp2.append(md5a2);
            serverDigestValue = tmp2.toString();
        } else {
            String qopOption = getQopVariantString();
            String cnonce = getCnonce();
            
            StringBuilder tmp2 = new StringBuilder(md5a1.length() + nonce.length()
                + NC.length() + cnonce.length() + qopOption.length() + md5a2.length() + 5);
            tmp2.append(md5a1);
            tmp2.append(':");
            tmp2.append(nonce);
            tmp2.append(':");
            tmp2.append(NC);
            tmp2.append(':");
            tmp2.append(cnonce);
            tmp2.append(':");
            tmp2.append(qopOption);
            tmp2.append(':");
            tmp2.append(md5a2); 
            serverDigestValue = tmp2.toString();
        }

        String serverDigest =
            encode(md5Helper.digest(EncodingUtils.getAsciiBytes(serverDigestValue)));

        return serverDigest;
    
private org.apache.http.HeadercreateDigestHeader(org.apache.http.auth.Credentials credentials, java.lang.String digest)
Creates digest-response header as defined in RFC2617.

param
credentials User credentials
param
digest The response tag's value as String.
return
The digest-response as String.

        
        CharArrayBuffer buffer = new CharArrayBuffer(128);
        if (isProxy()) {
            buffer.append(AUTH.PROXY_AUTH_RESP);
        } else {
            buffer.append(AUTH.WWW_AUTH_RESP);
        }
        buffer.append(": Digest ");
        
        String uri = getParameter("uri");
        String realm = getParameter("realm");
        String nonce = getParameter("nonce");
        String opaque = getParameter("opaque");
        String response = digest;
        String algorithm = getParameter("algorithm");

        String uname = credentials.getUserPrincipal().getName();
        
        List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>(20);
        params.add(new BasicNameValuePair("username", uname));
        params.add(new BasicNameValuePair("realm", realm));
        params.add(new BasicNameValuePair("nonce", nonce));
        params.add(new BasicNameValuePair("uri", uri));
        params.add(new BasicNameValuePair("response", response));
        
        if (qopVariant != QOP_MISSING) {
            params.add(new BasicNameValuePair("qop", getQopVariantString()));
            params.add(new BasicNameValuePair("nc", NC));
            params.add(new BasicNameValuePair("cnonce", getCnonce()));
        }
        if (algorithm != null) {
            params.add(new BasicNameValuePair("algorithm", algorithm));
        }    
        if (opaque != null) {
            params.add(new BasicNameValuePair("opaque", opaque));
        }

        for (int i = 0; i < params.size(); i++) {
            BasicNameValuePair param = params.get(i);
            if (i > 0) {
                buffer.append(", ");
            }
            boolean noQuotes = "nc".equals(param.getName()) ||
                               "qop".equals(param.getName());
            BasicHeaderValueFormatter.DEFAULT
                .formatNameValuePair(buffer, param, !noQuotes);
        }
        return new BufferedHeader(buffer);
    
private static java.security.MessageDigestcreateMessageDigest(java.lang.String digAlg)

        try {
            return MessageDigest.getInstance(digAlg);
        } catch (Exception e) {
            throw new UnsupportedDigestAlgorithmException(
              "Unsupported algorithm in HTTP Digest authentication: "
               + digAlg);
        }
    
private static java.lang.Stringencode(byte[] binaryData)
Encodes the 128 bit (16 bytes) MD5 digest into a 32 characters long String according to RFC 2617.

param
binaryData array containing the digest
return
encoded MD5, or null if encoding failed

        if (binaryData.length != 16) {
            return null;
        } 

        char[] buffer = new char[32];
        for (int i = 0; i < 16; 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);
    
private java.lang.StringgetCnonce()

        if (this.cnonce == null) {
            this.cnonce = createCnonce();
        }
        return this.cnonce; 
    
private java.lang.StringgetQopVariantString()

        String qopOption;
        if (qopVariant == QOP_AUTH_INT) {
            qopOption = "auth-int";   
        } else {
            qopOption = "auth";
        }
        return qopOption;            
    
public java.lang.StringgetSchemeName()
Returns textual designation of the digest authentication scheme.

return
digest

        return "digest";
    
public booleanisComplete()
Tests if the Digest authentication process has been completed.

return
true if Digest authorization has been processed, false otherwise.

        String s = getParameter("stale");
        if ("true".equalsIgnoreCase(s)) {
            return false;
        } else {
            return this.complete;
        }
    
public booleanisConnectionBased()
Returns false. Digest authentication scheme is request based.

return
false.

        return false;    
    
public voidoverrideParamter(java.lang.String name, java.lang.String value)

        getParameters().put(name, value);
    
public voidprocessChallenge(org.apache.http.Header header)
Processes the Digest challenge.

param
header the challenge header
throws
MalformedChallengeException is thrown if the authentication challenge is malformed

        super.processChallenge(header);
        
        if (getParameter("realm") == null) {
            throw new MalformedChallengeException("missing realm in challange");
        }
        if (getParameter("nonce") == null) {
            throw new MalformedChallengeException("missing nonce in challange");   
        }
        
        boolean unsupportedQop = false;
        // qop parsing
        String qop = getParameter("qop");
        if (qop != null) {
            StringTokenizer tok = new StringTokenizer(qop,",");
            while (tok.hasMoreTokens()) {
                String variant = tok.nextToken().trim();
                if (variant.equals("auth")) {
                    qopVariant = QOP_AUTH;
                    break; //that's our favourite, because auth-int is unsupported
                } else if (variant.equals("auth-int")) {
                    qopVariant = QOP_AUTH_INT;               
                } else {
                    unsupportedQop = true;
                }     
            }
        }        
        
        if (unsupportedQop && (qopVariant == QOP_MISSING)) {
            throw new MalformedChallengeException("None of the qop methods is supported");   
        }
        // Reset cnonce
        this.cnonce = null;
        this.complete = true;