FileDocCategorySizeDatePackage
DigestClientAuthentication.javaAPI DocphoneME MR2 API (J2ME)23360Wed May 02 18:00:42 BST 2007gov.nist.siplite.stack.authentication

DigestClientAuthentication.java

/*
 * Portions Copyright  2000-2007 Sun Microsystems, Inc. All Rights
 * Reserved.  Use is subject to license terms.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */
/*
 * DigestClientAuthentication.java
 *
 * Created on January 7, 2003, 10:45 AM
 */

package gov.nist.siplite.stack.authentication;

import java.util.Enumeration;
import java.util.Vector;

import gov.nist.core.*;
import gov.nist.siplite.*;
import gov.nist.siplite.parser.Lexer;
import gov.nist.siplite.message.*;
import gov.nist.siplite.header.*;

import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;

/**
 * Digest Client Authentication.
 */
public class DigestClientAuthentication
    implements AuthenticationListener {
    /** MD5 value. */
    private static final String MD5 = "MD5";
    /** MD5-sess value. */
    private static final String MD5_SESS = "MD5-sess";
    /** Authorization category. */
    private String realm;
    /** Algorithm name. */
    private String algorithm;
    /** URI to be validated. */
    private String uri;
    /** Nonce. */
    private String nonce;
    /** Authorization method. */
    private String method;
    /** Client nonce. */
    private String cnonce;
    /** Qop. */
    private String qop;
    /** Cnonce counter value. */
    private String nonceCountPar;
    /** Credentials containing the keys. */
    private Vector credentials;

    /**
     * Constructor with initial credentials.
     * @param credentials array of credentials
     */
    public DigestClientAuthentication(Vector credentials) {
        this.credentials = credentials;
        /*
         * need revisit
                 try {
            rs = RecordStore.openRecordStore("pass", true);
                 }
                 catch (Exception e) {
            if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                    "DigestClientAuthentication, " +
                    "exception raised: " + e.getMessage());
            }
                 }
         */
    }

    /**
     * Creates a new ProxyAuthorizationHeader based on the newly supplied
     * scheme value.
     *
     * @param scheme - the new string value of the scheme.
     * @throws ParseException which signals that an error has been reached
     * unexpectedly while parsing the scheme value.
     * @return the newly created ProxyAuthorizationHeader object.
     */
    public ProxyAuthorizationHeader createProxyAuthorizationHeader
        (String scheme) throws ParseException {
        if (scheme == null) {
            throw new NullPointerException("bad scheme arg");
        }

        ProxyAuthorizationHeader p = new ProxyAuthorizationHeader();
        p.setScheme(scheme);

        return p;
    }

    /**
     * Creates a new AuthorizationHeader based on the newly supplied
     * scheme value.
     *
     * @param scheme - the new string value of the scheme.
     * @throws ParseException which signals that an error has been reached
     * unexpectedly while parsing the scheme value.
     * @return the newly created AuthorizationHeader object.
     */
    public AuthorizationHeader createAuthorizationHeader(String scheme)
        throws ParseException {
        if (scheme == null) {
            throw new NullPointerException("null arg scheme ");
        }

        AuthorizationHeader auth = new AuthorizationHeader();
        auth.setScheme(scheme);

        return auth;
    }

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

    /**
     * Converts an array of bytes to an hexadecimal string.
     * @return a string
     * @param b bytes array to convert to a hexadecimal
     * string
     */
    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);
    }

    /**
     * Creates a new request.
     * @param sipStack the curent SIP stack context
     * @param originalRequest initiating request
     * @param response reply to original request
     * @param count number of request for nonce-count
     * (please see RFC 2617, 3.2.2)
     * @return the new request object with authentication
     * headers
     */
    public Request createNewRequest
        (SipStack sipStack, Request originalRequest,
         Response response, int count) {
        Exception ex = null;
        try {
            Request newRequest = (Request) originalRequest.clone();
            CSeqHeader cseqHeader = newRequest.getCSeqHeader();
            cseqHeader.setSequenceNumber(cseqHeader.getSequenceNumber() + 1);

            // Proxy-Authenticate header:
            ProxyAuthenticateHeader proxyAuthHeader =
                (ProxyAuthenticateHeader)
                response.getHeader(ProxyAuthenticateHeader.NAME);

            // WWWAuthenticate header:
            WWWAuthenticateHeader wwwAuthenticateHeader =
                (WWWAuthenticateHeader)
                response.getHeader(WWWAuthenticateHeader.NAME);

            // Cseq header:
            cseqHeader = response.getCSeqHeader();
            method = cseqHeader.getMethod();

            // RFC 2617, 3.2.2:
            // digest-uri
            //   The URI from Request-URI of the Request-Line
            uri = originalRequest.getRequestURI().toString();

            String opaque = null;

            if (proxyAuthHeader == null) {
                if (wwwAuthenticateHeader == null) {
                    if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                        Logging.report(Logging.ERROR,
                                       LogChannels.LC_JSR180,
                                       "DigestClientAuthentication, " +
                                       "ERROR: No ProxyAuthenticate header " +
                                       "or WWWAuthenticateHeader " +
                                       "in the response!");
                    }
                    return null;
                }

                algorithm = wwwAuthenticateHeader.getAlgorithm();

                nonce = wwwAuthenticateHeader.getNonce();
                realm = wwwAuthenticateHeader.getRealm();

                if (realm == null) {
                    if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                        Logging.report(Logging.ERROR,
                                       LogChannels.LC_JSR180,
                                       "DigestClientAuthentication, " +
                                       "ERROR: the realm is not part " +
                                       "of the 401 response!");
                    }
                    return null;
                }

                qop = wwwAuthenticateHeader.getParameter("qop");
                opaque = wwwAuthenticateHeader.getParameter("opaque");
            } else {

                algorithm = proxyAuthHeader.getAlgorithm();
                nonce = proxyAuthHeader.getNonce();
                realm = proxyAuthHeader.getRealm();

                if (realm == null) {
                    if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                        Logging.report(Logging.ERROR,
                                       LogChannels.LC_JSR180,
                                       "DigestClientAuthentication, " +
                                       "ERROR: the realm is not part " +
                                       "of the 407 response!");
                    }
                    return null;
                }

                qop = proxyAuthHeader.getParameter("qop");
                opaque = proxyAuthHeader.getParameter("opaque");
            }

            if (algorithm == null) {
                algorithm = MD5; // default value
            }

            if (!algorithm.equalsIgnoreCase(MD5) &&
                !algorithm.equalsIgnoreCase(MD5_SESS)) {
                if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                    Logging.report(Logging.ERROR,
                                 LogChannels.LC_JSR180,
                                 "Algorithm parameter is wrong: " + algorithm);
                }
                return null;
            }

            Credentials credentials = getCredentials(realm);
            if (credentials == null) {
                if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                    Logging.report(Logging.ERROR,
                                   LogChannels.LC_JSR180,
                                   "DigestClientAuthentication, " +
                                   "ERROR: unable to retrieve " +
                                   "the credentials from RMS!");
                }
                return null;
            }

            if (nonce == null) {
                nonce = "";
            }

            String digestEntityBody = null;
            if (qop != null) {
                cnonce = toHexString(Utils.digest(
                    ("" + System.currentTimeMillis() + ":ETag:" +
                     credentials.getPassword()).getBytes()));
                if (qop.equalsIgnoreCase("auth-int")) {
                    String entityBody =
                        new String(originalRequest.getRawContent());
                    if (entityBody == null) {
                        entityBody = "";
                    }
                    digestEntityBody = toHexString(
                        Utils.digest(entityBody.getBytes()));
                }
            }

            AuthenticationHeader header = null;

            if (proxyAuthHeader == null) {
                header =
                    createAuthorizationHeader("Digest");
            } else {
                header =
                    createProxyAuthorizationHeader("Digest");
            }

            header.setParameter("username",
                                credentials.getUserName());
            header.setParameter("realm", realm);
            header.setParameter("uri", uri);
            header.setParameter("algorithm", algorithm);
            header.setParameter("nonce", nonce);

            if (qop != null) {
                // RFC 2617, 3.2.2
                // qop contains a comma-separated list
                // we should find "auth" or "auth-int"
                Lexer qopLexer = new Lexer("qop", qop);
                boolean foundAuth = false;
                String currToken;
                while (qopLexer.lookAhead(0) != '\0') {
                    currToken =
                        qopLexer.byteStringNoComma().toLowerCase();
                    if (currToken.equals("auth") ||
                        currToken.equals("auth-int")) {
                        foundAuth = true;
                        qop = currToken;
                        break;
                    }
                }
                if (!foundAuth) { // wrong qop value
                    if (Logging.REPORT_LEVEL <= Logging.WARNING) {
                        Logging.report(Logging.WARNING,
                                       LogChannels.LC_JSR180,
                                       "DigestClientAuthentication, " +
                                       "the digest response is null " +
                                       "for the Authorization header!");
                    }
                    return null;
                }
                header.setParameter("qop", qop);
                // RFC 2617, 3.2.2
                header.setParameter("cnonce", cnonce);
                // constructing 8LHEX
                String nonceCount = Integer.toHexString(count);
                nonceCountPar = "";
                int lengthNonceCount = nonceCount.length();
                if (lengthNonceCount < 8) {
                    for (int i = lengthNonceCount; i < 8; i++) {
                        nonceCountPar += "0";
                    }
                }
                nonceCountPar += nonceCount;
                header.setParameter("nc", nonceCountPar);
            }

            String digestResponse =
                generateResponse(
                    credentials.getUserName(),
                    credentials.getPassword(),
                    digestEntityBody);

            if (digestResponse == null) {
                if (Logging.REPORT_LEVEL <= Logging.WARNING) {
                    Logging.report(Logging.WARNING,
                                   LogChannels.LC_JSR180,
                                   "DigestClientAuthentication, " +
                                   "the digest response is null " +
                                   "for the Authorization header!");
                }
                return null;
            }

            header.setParameter("response", digestResponse);

            if (opaque != null) {
                header.setParameter("opaque", opaque);
            }

            newRequest.setHeader(header);
            return newRequest;
        } catch (ParseException pe) {
            ex = pe;
        } catch (javax.microedition.sip.SipException se) {
            ex = se;
        }
        if (ex != null) {
            if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                               "DigestClientAuthentication, " +
                               "createNewRequest() " +
                               "exception raised: " + ex.getMessage());
            }
        }
        return null;
    }

    /**
     * Generates the response message.
     * @param userName user name for authentication
     * @param password password for authentication
     * @param digestEntityBody MD5 value of body
     * @return the new response message
     */
    private String generateResponse(String userName, String password,
                                    String digestEntityBody) {
        if (userName == null) {
            if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                               "DigestClientAuthentication, " +
                               "generateResponse(): " +
                               "ERROR: no userName parameter");
            }
            return null;
        }

        if (realm == null) {
            if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                               "DigestClientAuthentication, " +
                               "generateResponse(): " +
                               "ERROR: no realm parameter");
            }
            return null;
        }

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                           "DigestClientAuthentication, " +
                           "generateResponse(): " +
                           "Trying to generate a response " +
                           "for the user: " +
                           userName +
                           " , with " +
                           "the realm: " +
                           realm);
        }

        if (password == null) {
            if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                               "DigestClientAuthentication, " +
                               "generateResponse(): " +
                               "ERROR: no password parameter");
            }
            return null;
        }

        if (method == null) {
            if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                               "DigestClientAuthentication, " +
                               "generateResponse(): " +
                               "ERROR: no method parameter");
            }
            return null;
        }

        if (uri == null) {
            if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                               "DigestClientAuthentication, " +
                               "generateResponse(): " +
                               "ERROR: no uri parameter");
            }
            return null;
        }

        if (nonce == null) {
            if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                               "DigestClientAuthentication, " +
                               "generateResponse(): " +
                               "ERROR: no nonce parameter");
            }
            return null;
        }

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                           "DigestClientAuthentication, " +
                           "generateResponse(), userName: " +
                           userName + "!");
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                           "DigestClientAuthentication, " +
                           "generateResponse(), realm: " + realm + "!");
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                           "DigestClientAuthentication, " +
                           "generateResponse(), password: " + password + "!");
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                           "DigestClientAuthentication, " +
                           "generateResponse(), uri: " + uri + "!");
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                           "DigestClientAuthentication, " +
                           "generateResponse(), nonce: " + nonce + "!");
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                           "DigestClientAuthentication, " +
                           "generateResponse(), method: " + method + "!");
        }

        // RFC 2617, 3.2.2.2
        String A1 = userName + ":" + realm + ":" + password;
        if (algorithm.equalsIgnoreCase(MD5_SESS)) {
            byte[] A1bytes = Utils.digest(A1.getBytes());
            byte[] tmp = (":" + nonce + ":" + cnonce).getBytes();
            byte[] join = new byte[A1bytes.length + tmp.length];
            System.arraycopy(A1bytes, 0, join, 0, A1bytes.length);
            System.arraycopy(tmp, 0, join, A1bytes.length, tmp.length);
            A1 = new String(join);
        }
        String A2 = method.toUpperCase() + ":" + uri;

        // RFC 2617, 3.2.2.3 - body is empty
        if (qop != null) {
            if (qop.equalsIgnoreCase("auth-int")) {
                A2 += ":" + digestEntityBody;
            }
        }

        byte mdbytes[] = Utils.digest(A1.getBytes());

        String HA1 = toHexString(mdbytes);

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                           "DigestClientAuthentication, " +
                           "generateResponse(), HA1:" + HA1 + "!");
        }

        mdbytes = Utils.digest(A2.getBytes());

        String HA2 = toHexString(mdbytes);

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                           "DigestClientAuthentication, " +
                           "generateResponse(), HA2: " + HA2 + "!");
        }

        String KD = HA1 + ":" + nonce;
        if (qop != null) { // RFC 2617, 3.2.2.1
            KD += ":" + nonceCountPar + ":" + cnonce + ":" + qop;
        }
        KD += ":" + HA2;

        mdbytes = Utils.digest(KD.getBytes());
        String response = toHexString(mdbytes);

        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                           "DigestClientAuthentication, " +
                           "generateResponse(): " +
                           "response generated: " + response);
        }

        return response;
    }

    /**
     * Gets the credentials to use int the authentication request.
     * @param realm the domain of the requested credentials
     * @return the requested credentials
     */
    public Credentials getCredentials(String realm) {
        Enumeration e = credentials.elements();

        while (e.hasMoreElements()) {
            Credentials credentials = (Credentials) e.nextElement();
            if (credentials.getRealm().equals(realm)) {
                return credentials;
            }
        }

        return null;

        /*
         * need revisit
                 try {
            byte[] recData = new byte[200];
            int len;

            for (int i = 1; i <= rs.getNumRecords(); i++) {
                len = rs.getRecord(i, recData, 0);
                String data = new String(recData, 0, len);

                if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                    Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                        "DigestClientAuthentication, " +
                        "data recovered from RMS: " + data);
                }

                int realmIndex = data.indexOf(":");
                String rmsRealm = data.substring(0, realmIndex);
                int userNameIndex = data.indexOf(":", realmIndex+1);
                String userName = data.substring(realmIndex+1, userNameIndex);

                if (realm.equals(rmsRealm.trim())) {
                    Credentials credentials =
                    new Credentials(realm, userName,
                            data.substring(userNameIndex + 1));

                    return credentials;
                }
            }
            return null;
                 }
                 catch (Exception e) {
            if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                    "DigestClientAuthentication, getCredentials() " +
                    "exception raised:", e.getMessage());
            }
            return null;
                 }
         */
    }
}