/*
 *   
 *
 * Copyright  1990-2006 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.satsa.acl;

import java.io.IOException;

import java.util.Vector;
import com.sun.satsa.util.*;

import com.sun.satsa.util.pkcs15.*;
import com.sun.satsa.security.SecurityInitializer;
import com.sun.midp.io.j2me.apdu.*;
import com.sun.midp.security.ImplicitlyTrustedClass;
import com.sun.midp.security.SecurityToken;

import javax.microedition.io.ConnectionNotFoundException;
/**
 * This class represent the ACL Card slot abstraction
 */
public class ACSlot{

    /**
     * Inner class to request security token from SecurityInitializer.
     * SecurityInitializer should be able to check this inner class name.
     */
    static private class SecurityTrusted
        implements ImplicitlyTrustedClass {};

    /** This class has a different security domain than the MIDlet suite */
    private static SecurityToken classSecurityToken =
        SecurityInitializer.requestToken(new SecurityTrusted());

    /**
     * Value of OID from the spceification (A.4.2.1 Location of Access
     * Control Files)
     */
    private byte[] ACIFOID = {0x2b, 0x06, 0x01, 0x04, 0x01, 0x2a, 0x02, 0x6e,
                             0x03, 0x01, 0x01, 0x01};

    /** Path to ODF. */
    short[] ODF = {0x5031};
    /** Pathes to the standard PKCS15 files */
    /** Path to PrKDF. */
    public static final short[] PrKDF = {0x5200};
    /** Path to PuKDF. */
    public static final short[] PuKDF = {0x5201};
    /** Path to Trusted PuKDF. */
    public static final short[] TPuKDF = {0x5202};
    /** Path to SeKDF. */
    public static final short[] SeKDF = {0x5203};
    /** Path to PuKDF. */
    public static final short[] CDF = {0x5204};
    /** Path to CDF for trusted certificates. */
    public static final short[] TrCDF = {0x5205};
    /** Path to CDF for useful certificates. */
    public static final short[] UsCDF = {0x5206};
    /** Path to DODF. */
    public static final short[] DODF = {0x5207};
    /** Path to AODF. */
    public static final short[] AODF = {0x5208};

    /** Path to ACIF */
    public static final short[] ACIFILE = {0x5300};
    /** path to the first ACF */
    public static final short[] ACFILE = {0x5310};

    /** Command for the PKCS15 application select */
    static byte[] selectPKCSApp = {
                                  0x00, (byte) 0xA4, 0x04, 0x00, 0x0C,
                                  (byte) 0xa0, 0x0, 0x0, 0x0, 0x63, 0x50, 0x4b,
                                  0x43, 0x53, 0x2d, 0x31, 0x35
    };
    /** Command for the MF select */
    static byte[] selectMF = {0x00, (byte) 0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00,
                              0x7F};

    /** Command for the DIR file select */
    static byte[] selectDIR = {
        0x00, (byte)0xA4, 0x02, 0x00, 0x02, 0x2F, 0x00, 0x7F};
    /** File not found return code */
    static short FILE_NOT_FOUND = 0x6A82;
    /**
     * The list of ACL objects.
     */
    private Vector ACLists = new Vector();
    /**
     * The list of PIN data objects.
     */
    private Vector PINAttrs = new Vector();
    /** File system object. */
    private AclFileSystem files;
    /** Connection object. */
    private static Connection apdu;
    /** Indicates if all permissions granted */
    private boolean allGranted;
    /** Indicates if all permissions revoked */
    private boolean allRevoked;
    /**
     * Constructs an instance of an access control file object.
     */
    public ACSlot() {
    }
    /**
     * Creates new ACSlot object for pointed connection.
     * @param apdu Connection Required connection.
     * @throws IOException any IO exception
     */
    private ACSlot(Connection apdu) throws IOException {
        this.apdu = apdu;
        files = new AclFileSystem(apdu);
    }

    /**
     * Creates new ACSlot object with pointed state of permissions.
     * @param allGranted boolean state of all permissions:
     *        true - all permissions granted,
     *        false - all permissions revoked.
     */
    private ACSlot(boolean allGranted) {
        this.allGranted = allGranted;
        this.allRevoked = !allGranted;
    }

    /**
     * Load access control information.
     * @param slotNumber card slot number.
     * @return object that contains access control information or null if
     * this information doesn't exist or contains errors.
     */
    public static ACSlot load(int slotNumber) {
        Handle h;
        boolean isDIR = false;
        try {
            /* attempt to select the MF file */
            h = APDUManager.openACLConnection(selectMF, slotNumber,
                    classSecurityToken);
            byte[] res = APDUManager.exchangeAPDU(h, selectDIR);
            if (Utils.getShort(res, 0) == FILE_NOT_FOUND) {
                throw new ConnectionNotFoundException("DIR is not found");
            }
            isDIR = true;
        } catch (ConnectionNotFoundException ce) {
            /* DIR is not found */
            try {
                /* attempt to select the PKCS15 application */
                h = APDUManager.openACLConnection(selectPKCSApp,
                        slotNumber, classSecurityToken);
                isDIR = false;
            } catch (ConnectionNotFoundException ce1) {
                /* PKCS15Application is not found */
                return null;  /* DIR & PKCS15App are not found => */
                              /* all permissions granted */
            } catch (Exception ie) { 
                /* Something wrong during ACL treatment => */
                /* all permissions revoked */
                ACSlot s = new ACSlot(false);
                return s;
            }
        } catch (Exception e) { /* Something wrong during ACL treatment => */
                                /* all permissions revoked */
            ACSlot s = new ACSlot(false);
            return s;
        }

        try {
            Connection con = new Connection(h);
            ACSlot s = new ACSlot(con);
            s.init(isDIR);
            con.done();
            return s;
        } catch (Exception e) { /* Something wrong during ACL treatment => */
                                /* all permissions revoked */
            ACSlot s = new ACSlot(false);
            return s;
        }
    }

    /**
     * Initializes ACSlot object.
     * @param dirIsUsed boolean indicates if the card file system and DIR file
     *                          is used
     * @throws IOException  any IO exception
     * @throws TLVException any TLV exception
     */
    private void init(boolean dirIsUsed) throws IOException, TLVException {
        if (dirIsUsed) {
            int res = files.DIR.setPKCSRoot();
            if (res < 0) {
                throw new IOException("Wrong DIR file");
            }
            allGranted = (res == files.DIR.AID_NOT_FOUND);
            allRevoked = (res == files.DIR.ROOT_NOT_SELECTED);
        }
        ODF odf = new ODF(files);
        odf.load();
        Vector ACIFs = odf.getDOFs(ACIFOID);
        for (int i = 0; i < ACIFs.size(); i++) {
            ACIF acif = new ACIF(files.pathToLocation((TLV) ACIFs.elementAt(i)),
                                 files);
            acif.load();
            for (int j = 0; j < acif.getACFCount(); j++) {
                ACLists.addElement(new ACList(acif.getAID(j),
                                              acif.getACFPath(j), files));
            }
        }
        /*  loadPINs(); */
        for (int i = 0; i < odf.getAODFCount(); i++) {
            AODF aodf = new AODF(odf.getAODFPath(i), files);
            aodf.load();
            for (int j = 0; j < aodf.getEntryCount(); j++) {
                PINAttrs.addElement(new PINAttributes(aodf.getEntry(j)));
            }
        }
    }

    /**
     * Return PIN attributes.
     * @param id PIN identifier.
     * @return PIN attributes.
     */
    PINAttributes getPINAttributes(int id) {

        for (int j = 0; j < PINAttrs.size(); j++) {

            PINAttributes p = (PINAttributes) PINAttrs.elementAt(j);
            if (p.id == id) {
                return p;
            }
        }
        return null;
    }

    /**
     * Returns object that should be used for access control verification.
     * @param isAPDU true for APDU connection, false for JCRMI.
     * @param selectAPDU SELECT APDU command data.
     * @param root name of CA that authorized the suite.
     * @return object that can be used to check permissions.
     */
    ACLPermissions getACLPermissions(boolean isAPDU, byte[] selectAPDU,
                                     String root) {

        Vector permissions = new Vector();
        Vector pins = new Vector();
        boolean found = false;
        boolean allow = false;

        ACLPermissions perm;

        if (isAPDU) {
            perm = new APDUPermissions(this);
        } else {
            perm = new JCRMIPermissions(this);
        }

        if (allGranted) {
            /* Previously was decided to grant all permissions */
            perm.setType(ACLPermissions.ALLOW);
            return perm;
        }
        if (allRevoked) {
            /* Previously was decided to revoke all permissions */
            perm.setType(ACLPermissions.DISALLOW);
            return perm;
        }

        for (int i = 0; i < ACLists.size(); i++) {
            ACList acl = (ACList) ACLists.elementAt(i);
            if (!acl.match(selectAPDU)) {
                continue;
            }
            found = true;
            ACEntry ace = acl.getACEntries();
            if (ace == null) {
                /*
                 * Case when there is the aid identifies the application in the
                 * ACIF file, but an ACF related to the application is absent
                 * in this case the application does not have access to the card
                 */
                continue;
            }
            acl.getPINs(isAPDU, pins);
            if (!ace.verifyPrincipal(root)) {
                continue;
            }
            if (!ace.hasPermissions()) {
                allow = true;
                continue;
            }
            ace.getPermissions(isAPDU, permissions);
        }

        if (pins.size() != 0) {
            PINData[] data = new PINData[pins.size()];
            pins.copyInto(data);
            perm.setPINData(data);
        }

        if (!found || allow) {
            perm.setType(ACLPermissions.ALLOW);
        } else {
            if (permissions.size() == 0) {
                throw new SecurityException("Access denied.");
            } else {
                perm.setPermissions(permissions);
                perm.setType(ACLPermissions.CHECK);
            }
        }
        return perm;
    }
}
