FileDocCategorySizeDatePackage
ObexAuth.javaAPI DocphoneME MR2 API (J2ME)17846Wed May 02 18:00:32 BST 2007com.sun.kvem.jsr082.obex

ObexAuth.java

/*
 *
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * 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.
 */
package com.sun.kvem.jsr082.obex;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Vector;
import javax.obex.Authenticator;
import javax.obex.PasswordAuthentication;

/**
 * Obex protocol authentication functinality.
 */
class ObexAuth {

    /** Debug information, should be false for RR. */
    private static final boolean DEBUG = false;

    private static byte[] column = { (byte)':' };

    String realm;
    boolean userID;
    boolean access;
    byte[] nonce;
    private static int counter = 0;

    // used in prepareChallenge, addChallenge
    private byte[] realm_array;
    private int challengeLength;

    static ObexAuth createChallenge(String realm,
            boolean userID, boolean access) {
        return new ObexAuth(realm, null, userID, access);
    }

    private ObexAuth(String realm, byte[] nonce, boolean userID,
		     boolean access) {
        this.realm = realm;
        this.nonce = nonce;
        this.userID = userID;
        this.access = access;
    }

    /**
     * Prepare challenge before adding to packet.
     * @return length of challenge
     */
    int prepareChallenge() {
        if (challengeLength != 0) {
            return challengeLength;
        }
        try {
            int len = 24;
            realm_array = null;
            if (realm != null) {
                realm_array = realm.getBytes("UTF-16BE");
                len += 3 + realm_array.length;
            }
            challengeLength = len;
        } catch (UnsupportedEncodingException e) {
            if (DEBUG) {
                System.out.println("prepareChallenge(): ERROR, no encoding");
            }
            return 0;
        }
        return challengeLength;
    }

    int addChallenge(byte[] packet, int offset) throws IOException {
        int len = prepareChallenge();

        packet[offset] = (byte)ObexPacketStream.HEADER_AUTH_CHALLENGE;
        packet[offset+1] = (byte) (len >> 8);
        packet[offset+2] = (byte) (len & 255);
        packet[offset+3] = (byte) 0;  // nonce tag (0x0)
        packet[offset+4] = (byte) 16; // nonce len (16) (1 bytes)
        nonce = makeNonce();         // 16 byte of nonce
        if (DEBUG) {
            print("addChallenge: nonce", nonce);
        }
        System.arraycopy(nonce, 0, packet, offset + 5, 16);
        packet[offset+21] = (byte) 1; // options tag (0x1)
        packet[offset+22] = (byte) 1; // options length (1)
        packet[offset+23] = (byte) ((userID ? 1 : 0) + (access ? 0 : 2));
        if (realm != null) {
            int realm_len = realm_array.length;
            packet[offset+24] = (byte) 2; // realm tag (0x2)

            // realm length including encoding (1 byte)
            packet[offset+25] = (byte) (realm_len + 1);
            packet[offset+26] = (byte) 0xFF; // realm encoding UNICODE
            System.arraycopy(realm_array, 0, packet, offset+27, realm_len);
        }
        return len;
    }

    private static byte[] makeNonce() throws IOException {
        SSLWrapper md5 = new SSLWrapper();
        byte[] timestamp = createTimestamp();
        md5.update(timestamp, 0, timestamp.length);
        md5.update(column, 0, 1);
        byte[] privateKey = getPrivateKey();
        byte[] nonce = new byte[16];
        md5.doFinal(privateKey, 0, privateKey.length, nonce, 0);
        return nonce;
    }

    /**
     * Creates timestamp.
     * No strict specification for timestamp generation in OBEX 1.2
     * @return timestamp value
     */
    private static byte[] createTimestamp() {
        long time = System.currentTimeMillis();
        byte[] timestamp = new byte[9];
        timestamp[0] = (byte)(time >> 56);
        timestamp[1] = (byte)(time >> 48);
        timestamp[2] = (byte)(time >> 40);
        timestamp[3] = (byte)(time >> 32);
        timestamp[4] = (byte)(time >> 24);
        timestamp[5] = (byte)(time >> 16);
        timestamp[6] = (byte)(time >> 8);
        timestamp[7] = (byte)(time);

        synchronized (ObexAuth.class) {
            timestamp[8] = (byte)(counter++);
        }
        return timestamp;
    }

    private static byte[] privateKey = null;

    /**
     * Create and return private key.
     * Weak security scheme. Should be rewritten for more secure
     * implementation if used outside emulator.
     */
    private synchronized static byte[] getPrivateKey() throws IOException {
        if (privateKey != null) {
            return privateKey;
        }
        SSLWrapper md5 = new SSLWrapper();
        byte[] keyData = null;

        try {
            keyData = "timestamp = ".getBytes("ISO-8859-1");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        md5.update(keyData, 0, keyData.length);
        byte[] timestamp = createTimestamp();
        privateKey = new byte[16];
        md5.doFinal(timestamp, 0, timestamp.length, privateKey, 0);
        return privateKey;
    }

    static void makeDigest(byte[] buffer, int offset, byte[] nonce,
			   byte[] password)
            throws IOException {
        SSLWrapper md5 = new SSLWrapper();
        md5.update(nonce, 0, 16);
        md5.update(column, 0, 1);
        md5.doFinal(password, 0, password.length, buffer, offset);
    }

    static ObexAuth parseAuthChallenge(byte[] buffer, int packetOffset,
				       int length)
            throws IOException {
        if (DEBUG) {
            System.out.println("ObexAuth.parseAuthChallenge()");
        }

        // default values
        boolean readonly = false;
        boolean needUserid = false;
        byte[] nonce = null;
        String realm = null;

        // skiping header type and length
        int offset = packetOffset + 3;
        length += packetOffset;

        // decoding data in buffer
        while (offset < length) {
            int tag = buffer[offset] & 0xFF;
            int len = buffer[offset + 1] & 0xFF;
            offset += 2;


            switch (tag) {
                case 0x0: // nonce
                    if (len != 16 || nonce != null) {
                        throw new IOException("protocol error");
                    }
                    nonce = new byte[16];
                    System.arraycopy(buffer, offset, nonce, 0, 16);
                    if (DEBUG) {
                        print("got challenge: nonce", nonce);
                    }
                    break;
                case 0x1: // options
                    if (len != 1) {
                        throw new IOException("protocol error");
                    }
                    int options = buffer[offset];
                    readonly = ((options & 2) != 0);
                    needUserid = ((options & 1) != 0);
                    break;
                case 0x2: // realm
                    try {
                        int encodingID = buffer[offset] & 0xFF;
                        String encoding = null;
                        if (encodingID == 255) encoding = "UTF-16BE";
                        else if (encodingID == 0) encoding = "US-ASCII";
                        else if (encodingID < 10) encoding = "ISO-8859-" 
			    + encoding;
                        else throw new UnsupportedEncodingException();

                        realm = new String(buffer, offset + 1,
                            len - 1, encoding);
                    } catch (UnsupportedEncodingException e) {
                        // already: realm = null;
                    }
            }
            offset += len;
        }
        if (offset != length) {
            throw new IOException("protocol error");
        }
        return new ObexAuth(realm, nonce, needUserid, !readonly);
    }

    int replyAuthChallenge(byte[] buffer, int packetOffset,
            Authenticator authenticator) throws IOException {

        if (DEBUG) {
            System.out.println("ObexAuth.replyAuthChallenge()");
        }

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

        byte[] password = null;
        byte[] username = null;

        try {
            PasswordAuthentication pass =
                authenticator.onAuthenticationChallenge(realm, userID, access);

            password = pass.getPassword();
            int uidLen = 0; 
	    // userid subheader length with subheader <tag> and <len>

            username = pass.getUserName();
            if (userID || username != null) {

                // username is required but not provided
                if (userID && username.length == 0) {
                    if (DEBUG) {
                        System.out.println("ObexAuth.replyAuthChallenge():"
                                + " required username not provided");
                    }
                    throw new Exception();
                }
                uidLen = 2 + username.length;

                // maximum supported username length = 20
                if (uidLen > 22) uidLen = 22;
            }

            int len = 39 + uidLen;
            // byte[] response = new byte[len];
            buffer[packetOffset + 0] = (byte)ObexPacketStream
		.HEADER_AUTH_RESPONSE;
            buffer[packetOffset + 1] = (byte) (len >> 8);
            buffer[packetOffset + 2] = (byte) (len & 255);
            buffer[packetOffset + 3] = 0x0; // tag (Request-Digest)
            buffer[packetOffset + 4] = 16;  // digest len (16)
            makeDigest(buffer, packetOffset + 5, nonce, password);
            buffer[packetOffset + 21] = 0x02; // tag nonce
            buffer[packetOffset + 22] = 16;   // nonce len (16)
            System.arraycopy(nonce, 0, buffer, packetOffset + 23, 16);
            if (DEBUG) {
                print("send response: nonce", nonce);
            }
            if (uidLen > 2) {
                buffer[packetOffset + 39] = 0x01; // tag userid
                buffer[packetOffset + 40] = (byte) (uidLen - 2); // userid len
                System.arraycopy(username, 0, buffer, packetOffset + 41,
				 uidLen - 2);
            }

            if (DEBUG) {
                System.out.println("ObexAuth.replyAuthChallenge():"
                        + " response generated");
            }
            return len;

            // need to create authentication response
            // we should resend previous packet with the authentication response

        } catch (Throwable t) {
            if (DEBUG) {
                System.out.println("ObexAuth.replyAuthChallenge(): exception");
                t.printStackTrace();
            }
            // will caught NullPointerException if authenticator 
	    //  was not provided
            // will caught exceptions in handler
            // will caught NullPointerException exception 
	    //  if username == null and needUserid
            //
            // wrong response from client application, 
	    //  ignoring authentication challenge
            // client will receive UNAUTHORIZED
        }
        return 0;
    }

    private static void print(String msg, byte[] array) {
        if (DEBUG) {
            System.out.println(msg);
            if (array == null) {
                System.out.println("[0] = NULL");
                return;
            }
            System.out.print("[" + array.length + "]");
            for (int i = 0; i < array.length; i++) {
                System.out.print(" " + Integer.toHexString(array[i] & 0xFF));
            }
            System.out.println("");
        }
    }

    private static boolean compare(byte[] src1, byte[] src2) {
        for (int i = 0; i < 16; i++) {
            if (src1[i] != src2[i])
		return false;
        }
        return true;
    }

    static boolean checkAuthResponse(byte[] buffer, int packetOffset,
            int length, ObexPacketStream stream, Vector challenges)
            throws IOException {

        if (DEBUG) {
            System.out.println("ObexAuth.parseAuthResponse()");
        }

        // skiping header type and length
        int offset = packetOffset + 3;
        length += packetOffset;

        byte[] digest = null;
        byte[] username = null;
        byte[] nonce = null;

        // decoding data in buffer
        while (offset < length) {
            int tag = buffer[offset] & 0xFF;
            int len = buffer[offset + 1] & 0xFF;
            offset += 2;

            switch (tag) {
                case 0x0: // digest
                    if (DEBUG) {
                        System.out.println("got digest");
                    }
                    if (len != 16 || digest != null) {
                        throw new IOException("protocol error (1)");
                    }
                    digest = new byte[16];
                    System.arraycopy(buffer, offset, digest, 0, 16);
                    if (DEBUG) {
                        print("got response: digest", digest);
                    }
                    break;
                case 0x1: // username
                    if (DEBUG) {
                        System.out.println("got username");
                    }
                    if (len > 20 || len == 0 || username != null) {
                        throw new IOException("protocol error (2)");
                    }
                    username = new byte[len];
                    System.arraycopy(buffer, offset, username, 0, len);
                    break;
                case 0x2: // nonce
                    if (DEBUG) {
                        System.out.println("got nonce");
                    }
                    if (len != 16 || nonce != null) {
                        throw new IOException("protocol error (3)");
                    }
                    nonce = new byte[16];
                    System.arraycopy(buffer, offset, nonce, 0, 16);
                    if (DEBUG) {
                        print("got response: nonce", nonce);
                    }
                    break;
                default:
                    if (DEBUG) {
                        System.out.println("unknown tag = " + tag);
                    }
            }
            offset += len;
        }

        if (offset != length) {
            throw new IOException("protocol error (4)");
        }


        // check nonce and select auth object
        ObexAuth auth = null;

        if (nonce == null) {
            if (DEBUG) {
                System.out.println("no nonce received, using first auth");
            }
            if (challenges.size() == 0) {
                return false;
            }
            auth = (ObexAuth) challenges.elementAt(0);
            nonce = auth.nonce;
            challenges.removeElementAt(0);
        } else {
            if (DEBUG) {
                System.out.println("nonce provided, searching for auth");
                System.out.println("challenges = " + challenges.size());
            }
            for (int i = 0; i < challenges.size(); i++) {
                ObexAuth a = (ObexAuth) challenges.elementAt(i);
                if (compare(nonce, a.nonce)) {
                    if (DEBUG) {
                        System.out.println("nonce is in " + i + " challenge");
                    }
                    auth = a;
                    challenges.removeElementAt(i);
                    break;
                }
            }
            if (DEBUG) {
                System.out.println("auth = " + auth);
            }
            if (auth == null)
		return false;
        }

        // check username existance
        if (auth.userID && username == null) {
            if (DEBUG) {
                System.out.println("need username!");
            }
            // NOTE: may be too strict
            stream.onAuthenticationFailure(username);
            return false;
        }

        // ask password from authenticator and check digest
        try {
            if (DEBUG) {
                System.out.println("running onAuthenticationResponse()...");
            }
            byte[] password =
                stream.authenticator.onAuthenticationResponse(username);
            byte[] localDigest = new byte[16];
            makeDigest(localDigest, 0, nonce, password);
            if (DEBUG) {
                System.out.println("digest created");
            }
            boolean res = compare(localDigest, digest);

            if (res == false) {
                if (DEBUG) {
                    System.out.println("Calling onAuthenticationFailure()..");
                }
                stream.onAuthenticationFailure(username);
            }
            return res;
        } catch (Throwable t) {
            // catch localDigest = null, crypto and user code exception
            if (DEBUG) {
                System.out.println("exception");
            }
            stream.onAuthenticationFailure(username);
            return false;
        }
    }
}