net.lightbody.bmp.proxy.jetty.http.DigestAuthenticator.java Source code

Java tutorial

Introduction

Here is the source code for net.lightbody.bmp.proxy.jetty.http.DigestAuthenticator.java

Source

// ========================================================================
// $Id: DigestAuthenticator.java,v 1.16 2005/08/13 00:01:24 gregwilkins Exp $
// Copyright 2002-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at 
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================

package net.lightbody.bmp.proxy.jetty.http;

import net.lightbody.bmp.proxy.jetty.log.LogFactory;
import net.lightbody.bmp.proxy.jetty.util.*;
import org.apache.commons.logging.Log;

import java.io.IOException;
import java.security.MessageDigest;
import java.security.Principal;

/* ------------------------------------------------------------ */
/** DIGEST authentication.
 *
 * @version $Id: DigestAuthenticator.java,v 1.16 2005/08/13 00:01:24 gregwilkins Exp $
 * @author Greg Wilkins (gregw)
 */
public class DigestAuthenticator implements Authenticator {
    static Log log = LogFactory.getLog(DigestAuthenticator.class);

    protected long maxNonceAge = 0;
    protected long nonceSecret = this.hashCode() ^ System.currentTimeMillis();
    protected boolean useStale = false;

    /* ------------------------------------------------------------ */
    /** 
     * @return UserPrinciple if authenticated or null if not. If
     * Authentication fails, then the authenticator may have committed
     * the response as an auth challenge or redirect.
     * @exception IOException 
     */
    public Principal authenticate(UserRealm realm, String pathInContext, HttpRequest request, HttpResponse response)
            throws IOException {
        // Get the user if we can
        boolean stale = false;
        Principal user = null;
        String credentials = request.getField(HttpFields.__Authorization);

        if (credentials != null) {
            if (log.isDebugEnabled())
                log.debug("Credentials: " + credentials);
            QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(credentials, "=, ", true, false);
            Digest digest = new Digest(request.getMethod());
            String last = null;
            String name = null;

            loop: while (tokenizer.hasMoreTokens()) {
                String tok = tokenizer.nextToken();
                char c = (tok.length() == 1) ? tok.charAt(0) : '\0';

                switch (c) {
                case '=':
                    name = last;
                    last = tok;
                    break;
                case ',':
                    name = null;
                case ' ':
                    break;

                default:
                    last = tok;
                    if (name != null) {
                        if ("username".equalsIgnoreCase(name))
                            digest.username = tok;
                        else if ("realm".equalsIgnoreCase(name))
                            digest.realm = tok;
                        else if ("nonce".equalsIgnoreCase(name))
                            digest.nonce = tok;
                        else if ("nc".equalsIgnoreCase(name))
                            digest.nc = tok;
                        else if ("cnonce".equalsIgnoreCase(name))
                            digest.cnonce = tok;
                        else if ("qop".equalsIgnoreCase(name))
                            digest.qop = tok;
                        else if ("uri".equalsIgnoreCase(name))
                            digest.uri = tok;
                        else if ("response".equalsIgnoreCase(name))
                            digest.response = tok;
                        break;
                    }
                }
            }

            int n = checkNonce(digest.nonce, request);
            if (n > 0)
                user = realm.authenticate(digest.username, digest, request);
            else if (n == 0)
                stale = true;

            if (user == null)
                log.warn("AUTH FAILURE: user " + digest.username);
            else {
                request.setAuthType(SecurityConstraint.__DIGEST_AUTH);
                request.setAuthUser(digest.username);
                request.setUserPrincipal(user);
            }
        }

        // Challenge if we have no user
        if (user == null && response != null)
            sendChallenge(realm, request, response, stale);

        return user;
    }

    /* ------------------------------------------------------------ */
    public String getAuthMethod() {
        return SecurityConstraint.__DIGEST_AUTH;
    }

    /* ------------------------------------------------------------ */
    public void sendChallenge(UserRealm realm, HttpRequest request, HttpResponse response, boolean stale)
            throws IOException {
        response.setField(HttpFields.__WwwAuthenticate,
                "Digest realm=\"" + realm.getName() + "\", domain=\"" + response.getHttpContext().getContextPath()
                        + "\", nonce=\"" + newNonce(request) + "\", algorithm=MD5, qop=\"auth\""
                        + (useStale ? (" stale=" + stale) : ""));

        response.sendError(HttpResponse.__401_Unauthorized);
    }

    /* ------------------------------------------------------------ */
    public String newNonce(HttpRequest request) {
        long ts = request.getTimeStamp();
        long sk = nonceSecret;

        byte[] nounce = new byte[24];
        for (int i = 0; i < 8; i++) {
            nounce[i] = (byte) (ts & 0xff);
            ts = ts >> 8;
            nounce[8 + i] = (byte) (sk & 0xff);
            sk = sk >> 8;
        }

        byte[] hash = null;
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.reset();
            md.update(nounce, 0, 16);
            hash = md.digest();
        } catch (Exception e) {
            log.fatal(this, e);
        }

        for (int i = 0; i < hash.length; i++) {
            nounce[8 + i] = hash[i];
            if (i == 23)
                break;
        }

        return new String(B64Code.encode(nounce));
    }

    /**
     * @param nonce
     * @param request
     * @return -1 for a bad nonce, 0 for a stale none, 1 for a good nonce
     */
    /* ------------------------------------------------------------ */
    public int checkNonce(String nonce, HttpRequest request) {
        try {
            byte[] n = B64Code.decode(nonce.toCharArray());
            if (n.length != 24)
                return -1;

            long ts = 0;
            long sk = nonceSecret;
            byte[] n2 = new byte[16];
            for (int i = 0; i < 8; i++) {
                n2[i] = n[i];
                n2[8 + i] = (byte) (sk & 0xff);
                sk = sk >> 8;
                ts = (ts << 8) + (0xff & (long) n[7 - i]);
            }

            long age = request.getTimeStamp() - ts;
            if (log.isDebugEnabled())
                log.debug("age=" + age);

            byte[] hash = null;
            try {
                MessageDigest md = MessageDigest.getInstance("MD5");
                md.reset();
                md.update(n2, 0, 16);
                hash = md.digest();
            } catch (Exception e) {
                log.fatal(this, e);
            }

            for (int i = 0; i < 16; i++)
                if (n[i + 8] != hash[i])
                    return -1;

            if (maxNonceAge > 0 && (age < 0 || age > maxNonceAge))
                return 0; // stale

            return 1;
        } catch (Exception e) {
            log.debug("", e);
        }
        return -1;
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    private static class Digest extends Credential {
        String method = null;
        String username = null;
        String realm = null;
        String nonce = null;
        String nc = null;
        String cnonce = null;
        String qop = null;
        String uri = null;
        String response = null;

        /* ------------------------------------------------------------ */
        Digest(String m) {
            method = m;
        }

        /* ------------------------------------------------------------ */
        public boolean check(Object credentials) {
            String password = (credentials instanceof String) ? (String) credentials : credentials.toString();

            try {
                MessageDigest md = MessageDigest.getInstance("MD5");
                byte[] ha1;
                if (credentials instanceof Credential.MD5) {
                    // Credentials are already a MD5 digest - assume it's in
                    // form user:realm:password (we have no way to know since 
                    // it's a digest, alright?)
                    ha1 = ((Credential.MD5) credentials).getDigest();
                } else {
                    // calc A1 digest
                    md.update(username.getBytes(StringUtil.__ISO_8859_1));
                    md.update((byte) ':');
                    md.update(realm.getBytes(StringUtil.__ISO_8859_1));
                    md.update((byte) ':');
                    md.update(password.getBytes(StringUtil.__ISO_8859_1));
                    ha1 = md.digest();
                }
                // calc A2 digest
                md.reset();
                md.update(method.getBytes(StringUtil.__ISO_8859_1));
                md.update((byte) ':');
                md.update(uri.getBytes(StringUtil.__ISO_8859_1));
                byte[] ha2 = md.digest();

                // calc digest
                // request-digest  = <"> < KD ( H(A1), unq(nonce-value) ":" nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" H(A2) ) <">
                // request-digest  = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">

                md.update(TypeUtil.toString(ha1, 16).getBytes(StringUtil.__ISO_8859_1));
                md.update((byte) ':');
                md.update(nonce.getBytes(StringUtil.__ISO_8859_1));
                md.update((byte) ':');
                md.update(nc.getBytes(StringUtil.__ISO_8859_1));
                md.update((byte) ':');
                md.update(cnonce.getBytes(StringUtil.__ISO_8859_1));
                md.update((byte) ':');
                md.update(qop.getBytes(StringUtil.__ISO_8859_1));
                md.update((byte) ':');
                md.update(TypeUtil.toString(ha2, 16).getBytes(StringUtil.__ISO_8859_1));
                byte[] digest = md.digest();

                // check digest
                return (TypeUtil.toString(digest, 16).equalsIgnoreCase(response));
            } catch (Exception e) {
                log.warn(LogSupport.EXCEPTION, e);
            }

            return false;
        }

        public String toString() {
            return username + "," + response;
        }

    }

    /**
     * @return Returns the maxNonceAge.
     */
    public long getMaxNonceAge() {
        return maxNonceAge;
    }

    /**
     * @param maxNonceAge The maxNonceAge to set.
     */
    public void setMaxNonceAge(long maxNonceAge) {
        this.maxNonceAge = maxNonceAge;
    }

    /**
     * @return Returns the nonceSecret.
     */
    public long getNonceSecret() {
        return nonceSecret;
    }

    /**
     * @param nonceSecret The nonceSecret to set.
     */
    public void setNonceSecret(long nonceSecret) {
        this.nonceSecret = nonceSecret;
    }

    public void setUseStale(boolean us) {
        this.useStale = us;
    }

    public boolean getUseStale() {
        return useStale;
    }
}