/*
*
*
* 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.midp.io.j2me.apdu;
import javax.microedition.io.*;
import javax.microedition.apdu.*;
import com.sun.midp.midlet.*;
import com.sun.midp.midletsuite.*;
import com.sun.midp.security.ImplicitlyTrustedClass;
import com.sun.midp.security.SecurityToken;
import com.sun.cldc.io.ConnectionBaseInterface;
import com.sun.satsa.acl.ACLPermissions;
import com.sun.satsa.acl.AccessControlManager;
import com.sun.satsa.acl.APDUPermissions;
import com.sun.satsa.util.Utils;
import com.sun.satsa.security.SecurityInitializer;
import java.io.*;
import com.sun.midp.security.Permissions;
/**
* This is the implementation class for APDUConnection interface and provides
* a high-level API to the J2ME applications allowing them to connect and
* communicate with the card applications. An instance of this class is
* created when Connector.open method is called with 'apdu' as protocol. An
* instance of this class is only returned to the calling J2ME application if
* the card application selection is successful. If there are any errors that
* occur during the card application selection, IOException is thrown.
* The application calls <tt>Connector.open</tt> with an APDU URL string and
* obtains a {@link javax.microedition.apdu.APDUConnection} object.
*
*/
public class Protocol implements APDUConnection, ConnectionBaseInterface,
StreamConnection {
/**
* 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());
/**
* This object verifies access rights of the MIDlet.
*/
private APDUPermissions verifier;
/**
* This flag is to indicate if this APDU connection is to be used for
* communicating with SAT.
*/
private boolean openForSAT;
/**
* Connection handle.
*/
private Handle h;
/**
* Opens a connection.
*
* @param name the target of the connection
* @param mode indicates whether the caller
* intends to write to the connection. Currently,
* this parameter is ignored.
* @param timeouts indicates whether the caller
* wants timeout exceptions. Currently,
* this parameter is ignored.
* @return this connection
* @throws IOException if the connection is closed or unavailable
* @throws SecurityException if access is restricted by ACL
*/
public Connection openPrim(String name, int mode, boolean timeouts)
throws IOException {
// parse the URI for slot number and target
int slotIndex = name.indexOf(":");
int targetIndex = name.indexOf(";target=");
if (targetIndex < 0) {
throw new IllegalArgumentException(
"Target missing in connection URL");
}
int slot;
if (targetIndex == slotIndex + 1) {
slot = 0;
} else {
try {
slot = Integer.parseInt(
name.substring(slotIndex + 1, targetIndex), 16);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid slot number");
}
}
String target = name.substring(targetIndex + 8);
boolean isSAT = target.equals("SAT");
// verify MIDlet permissions
MIDletSuite ms =
MIDletStateHandler.getMidletStateHandler().getMIDletSuite();
try {
if (isSAT) {
ms.checkForPermission(Permissions.APDU_CHANNEL0,
"apdu:satopen");
} else {
ms.checkForPermission(Permissions.APDU_CONNECTION,
"apdu:open");
}
} catch (InterruptedException ie) {
throw new InterruptedIOException(
"Interrupted while trying to ask the user permission");
}
// open connection
if (isSAT) {
boolean satSlot;
try {
satSlot = APDUManager.isSatSlot(slot);
} catch (IllegalArgumentException e) {
satSlot = false;
}
if (!satSlot) {
throw new ConnectionNotFoundException("Invalid slot for SIM");
}
h = APDUManager.openSATConnection(slot, classSecurityToken);
openForSAT = true;
} else {
APDUManager.checkSlotNumber(slot);
byte[] apdu = new byte[32];
apdu[1] = (byte) 0xa4;
apdu[2] = 4;
boolean ok;
try {
int len = APDUManager.parseDottedBytes(target, apdu, 5);
apdu[4] = (byte) len;
apdu[5 + len] = 127;
ok = len > 4 && len < 17;
} catch (NullPointerException npe) {
ok = false;
} catch (IndexOutOfBoundsException iobe) {
ok = false;
} catch (NumberFormatException nfe) {
ok = false;
} catch (IllegalArgumentException iae) {
ok = false;
}
if (! ok) {
throw new IllegalArgumentException("Invalid AID");
}
APDUManager.initACL(slot, classSecurityToken);
verifier = AccessControlManager
.getAPDUPermissions(slot,
apdu,
((MIDletSuiteImpl)ms).getInstallInfo().getCA());
h = APDUManager.selectApplication(apdu, slot,
classSecurityToken);
}
return this;
}
/**
* Closes the connection.
* @exception IOException if an I/O error occurs
*/
public void close() throws IOException {
/*
* IMPL_NOTE: To pass JDTS test
* com.sun.satsa.apdu.exchange.interruptedIOClosed next lines
* should be uncommented
*/
// try {Thread.sleep(1000); }
// catch (InterruptedException ignored) {}
if (h != null) {
Handle w = h;
h = null;
APDUManager.closeConnection(w);
}
}
/**
* Exchanges an APDU command with a smart card application.
* Communication to a smart card device is synchronous.
* This method will block until the response has been received
* from the smart card application, or is interrupted.
* The interruption could be due to the card being removed from
* the card access device, the operation may timeout, or the
* connection may be closed from another thread accessing this
* connection.
*
* @param commandAPDU a byte encoded command for the smart card
* application
* @return a byte encoded response to the requested operation
* @exception IOException is thrown if the operation was not
* successful, or if the connection was already closed
* @throws InterruptedIOException if a timeout occurs while
* either trying to send the command or if this <code>Connection</code>
* object is closed during this exchange operation
* @throws NullPointerException if the parameter is null
* @throws SecurityException if the application does not
* have permission to exchange the message
*/
public byte[] exchangeAPDU(byte[] commandAPDU) throws
IOException, InterruptedIOException {
checkHandle(h);
if (commandAPDU == null || commandAPDU.length < 4) {
throw new IllegalArgumentException();
}
int nibble = commandAPDU[0] & 0xF0;
boolean channelEncoded = (nibble == 0 ||
(nibble >= 0x80 && nibble <= 0xA0));
if (channelEncoded) {
// mask off the channel information
commandAPDU[0] &= 0xFC;
}
int command = Utils.getInt(commandAPDU, 0);
if (openForSAT) {
// check if this is an envelope by checking the INS, P1 and
// P2 which should have values 0xC2, 0 and 0 respectively.
if ((command & 0xffffff) != 0xC20000) {
throw new IllegalArgumentException("Non-envelope APDU");
}
commandAPDU[0] = (byte) 0x80;
} else {
int cmd = command >> 8;
if (cmd == 0xA404 || cmd == 0x7000 || cmd == 0x7080) {
throw new IllegalArgumentException
("Selection or channel management APDUs are not allowed");
}
// if channel is non-zero, valid CLA bytes are only:
// 0x0X, 0x8X, 0x9X or 0xAX i.e. the one that can have
// the channel information encoded in it
if (h.channel != 0 && ! channelEncoded) {
throw new IllegalArgumentException
("Invalid CLA byte for a non-zero channel");
}
// check if allowed by the ACL
verifier.checkPermission(command);
if (channelEncoded) {
// set channel bits in two LSB
commandAPDU[0] |= h.channel;
}
}
return APDUManager.exchangeAPDU(h, commandAPDU);
}
/**
* Returns the ATR message sent by smart card in response to the
* reset operation.
* @return the ATR response message, or <code>null</code>
* if there is no message available
*/
public byte[] getATR() {
byte[] result = null;
try {
checkHandle(h);
result = h.getATR();
} catch (IOException e) {
result = null;
}
return result;
}
/**
* This method always throw <code>IllegalArgumentException</code>.
* @return An input stream
* @exception IOException If an I/O error occurs
* @exception IllegalArgumentException is thrown for all requests
*/
public InputStream openInputStream() throws IOException {
throw new IllegalArgumentException("Not supported");
}
/**
* This method always throw <code>IllegalArgumentException</code>.
* @return An input stream
* @exception IOException If an I/O error occurs
* @exception IllegalArgumentException is thrown for all requests
*/
public DataInputStream openDataInputStream() throws IOException {
throw new IllegalArgumentException("Not supported");
}
/**
* This method always throw
* <code>IllegalArgumentException</code>.
* @return An output stream
* @exception IOException If an I/O error occurs
* @exception IllegalArgumentException is thrown for all requests
*/
public OutputStream openOutputStream() throws IOException {
throw new IllegalArgumentException("Not supported");
}
/**
* This method always throw
* <code>IllegalArgumentException</code>.
*
* @return An output stream
* @exception IOException If an I/O error occurs
* @exception IllegalArgumentException is thrown for all requests
*/
public DataOutputStream openDataOutputStream() throws IOException {
throw new IllegalArgumentException("Not supported");
}
/**
* A call to enterPin method pops up a UI that requests the PIN
* from the user. The pinID field indicates which PIN must be
* requested from the user. The user can
* either cancel the request
* or continue. If the user enters the PIN and chooses to continue the
* implementation is responsible
* for presenting the PIN value to the card for verification.
* @param pinID the type of PIN the implementation is suppose to prompt
* the user to enter.
* @return result of PIN verification which is the status word
* recived from the smart card in the form of a byte array. This method
* would return null if the user cancels the request.
* @exception IOException is thrown if the PIN could not be communicated
* with the card due to IO problems such as if the connection was
* closed before the command could be completed successfully.
* @exception InterruptedIOException is thrown if the connection object
* is closed before a reply from the card is received.
* @exception SecurityException is thrown if the J2ME application does
* not have appropriate rights to ask for PIN verification.
*/
public byte[] enterPin(int pinID) throws IOException {
return doEnterPin(pinID, 0, ACLPermissions.CMD_VERIFY);
}
/**
* A call to <code>changePin</code> method pops up a UI that requests the
* the user for an old or existing PIN value and the new PIN value
* to change the value of the PIN. The pinID field indicates which PIN is
* to be changed. The user can
* either cancel the request
* or continue. If the user enters the PIN values and chooses to
* continue the
* implementation is responsible
* for presenting the PIN value to the card to the card.
* @param pinID the type of PIN the implementation is suppose to prompt
* the user to change.
* @return result of changing the PIN value which is the status word
* recived from the smart card in the form of a byte array. This method
* would return null if the user cancels the request.
* @exception IOException is thrown if the PIN could not be communicated
* with the card due to IO problems such as if the connection was
* closed before the command could be completed successfully.
* @exception InterruptedIOException is thrown if the connection object
* is closed before a reply from the card is received.
* @exception SecurityException is thrown if the J2ME application does
* not have appropriate rights to ask for changing the PIN value.
*/
public byte [] changePin(int pinID) throws IOException {
return doEnterPin(pinID, 0, ACLPermissions.CMD_CHANGE);
}
/**
* A call to <code>disablePin</code> method pops up a UI that requests the
* the user to enter the value for the PIN that is to be disabled.
* The pinID field
* indicates which PIN is to be disabled. The user can
* either cancel the request
* or continue. If the user enters the PIN and chooses to continue the
* implementation is responsible
* for presenting the PIN value to the card to disable PIN.
* @param pinID the type of PIN the implementation is required to prompt
* the user to enter.
* @return result of disabling the PIN value which is the status word
* recived from the smart card in the form of a byte array. This method
* would return null if the user cancels the request.
* @exception IOException is thrown if the PIN could not be communicated
* with the card due to IO problems such as if the connection was
* closed before the command could be completed successfully.
* @exception InterruptedIOException is thrown if the connection object
* is closed before a reply from the card is received.
* @exception SecurityException is thrown if the J2ME application does
* not have appropriate rights to ask for disabling the PIN.
*/
public byte [] disablePin(int pinID) throws IOException {
return doEnterPin(pinID, 0, ACLPermissions.CMD_DISABLE);
}
/**
* A call to <code>enablePin</code> method pops up a UI that requests the
* the user to enter the value for the PIN that is to be enabled.
* The pinID field
* indicates which PIN is to be enabled. The user can
* either cancel the request
* or continue. If the user enters the PIN and chooses to continue the
* implementation is responsible
* for presenting the PIN value to the card for enabling the PIN.
* @param pinID the type of PIN the implementation is required to prompt
* the user to enter.
* @return result of enabling the PIN value which is the status word
* recived from the smart card in the form of a byte array. This method
* would return null if the user cancels the request.
* @exception IOException is thrown if the PIN could not be communicated
* with the card due to IO problems such as if the connection was
* closed before the command could be completed successfully.
* @exception InterruptedIOException is thrown if the connection object
* is closed before a reply from the card is received.
* @exception SecurityException is thrown if the J2ME application does
* not have appropriate rights to ask for enabling the PIN.
*/
public byte [] enablePin(int pinID) throws IOException {
return doEnterPin(pinID, 0, ACLPermissions.CMD_ENABLE);
}
/**
* This is a high-level method that lets the J2ME application
* ask the user to enter the value for an unblocking PIN,
* and the new value for the blocked PIN and send
* these to the card.
* A call to <code>unblockPin</code> method pops up a UI that requests
* the user to enter the value for the unblocking PIN and the
* new value for the blocked PIN.
* The <code>unblockingPinID</code> field indicates which unblocking
* PIN is to be
* used to unblock the blocked PIN which is indicated by the field
* <code>blockedPinId</code>.
* The unblockingPinID field indicates which PIN is to be unblocked.
* The user can either cancel the request
* or continue. If the user enters the PIN values and chooses to continue,
* the implementation is responsible
* for presenting the PIN values to the card for unblocking the
* blocked PIN.
* If padding is required for either of the PIN values, the
* implementation is responsible for providing appropriate padding.
* @param blockedPinID the Id of PIN that is to be unblocked.
* @param unblockingPinId the Id of unblocking PIN.
* @return result of unblocking the PIN value which is the status word
* received from the smart card in the form of a byte array. This method
* would return null if the user cancels the request.
* @exception IOException is thrown if the PIN could not be communicated
* with the card because the connection was
* closed before this method was called or because
* of communication problems.
* @throws InterruptedIOException can be thrown in any of these situations:
* <ul>
* <li>if this <code>Connection</code>
* object is closed during the exchange
* operation</li>
* <li>if the card is removed after connection is established and
* then reinserted, and attempt is made to unblock PIN
* without re-establishing the connection</li>
* </ul>
* @exception SecurityException is thrown if the J2ME application does
* not have appropriate rights to ask for unblocking the PIN.
*/
public byte [] unblockPin(int blockedPinID, int unblockingPinId)
throws IOException {
return doEnterPin(blockedPinID, unblockingPinId,
ACLPermissions.CMD_UNBLOCK);
}
/**
* Performs PIN entry operation.
* @param pinID PIN identifier.
* @param uPinID unblocking PIN identifier.
* @param action PIN operation identifier.
* @return result of PIN verification which is the status word
* received from the smart card in the form of a byte array. This method
* would return null if the user cancels the request.
* @exception IOException is thrown if the PIN could not be communicated
* with the card due to IO problems such as if the connection was
* closed before the command could be completed successfully.
* @exception InterruptedIOException is thrown if the connection object
* is closed before a reply from the card is received.
* @exception SecurityException is thrown if the J2ME application does
* not have appropriate rights to ask for PIN verification.
*/
private byte[] doEnterPin(int pinID, int uPinID, int action)
throws IOException {
checkHandle(h);
if (openForSAT) {
throw new SecurityException();
}
int header = verifier.preparePIN(pinID, uPinID, action);
Object[] pins = verifier.enterPIN(classSecurityToken, action);
if (pins == null) {
return null;
}
byte[] pin1 = (byte[]) pins[0];
byte[] pin2 = (pins.length == 2) ? (byte[]) pins[1] : null;
int dataSize = pin1.length;
if (pin2 != null) {
dataSize += pin2.length;
}
byte[] command = new byte[6 + dataSize];
command[0] = (byte) (header >> 24);
command[1] = (byte) (header >> 16);
command[2] = (byte) (header >> 8);
command[3] = (byte) header;
command[4] = (byte) dataSize;
System.arraycopy(pin1, 0, command, 5, pin1.length);
if (pin2 != null) {
System.arraycopy(pin2, 0, command, 5 + pin1.length, pin2.length);
}
command[5 + dataSize] = 0x7f;
byte[] result;
try {
// CLA can be incompatible with this logical channel
result = exchangeAPDU(command);
} catch (IllegalArgumentException e) {
throw new IOException(e.getMessage());
}
byte[] out = new byte[2];
System.arraycopy(result, result.length - 2, out, 0, 2);
return out;
}
/**
* Checks if given handle is still valid.
* @param h Handle to be checked.
* @exception IOException is thrown if connection is closed
* @exception InterruptedIOException is thrown if connection
* closed and re-established again.
*/
private void checkHandle(Handle h) throws IOException {
if (h == null || !h.opened
|| h.getCardSessionId() == -1) {
throw new IOException("Connection closed");
}
if (h.getCardSessionId() != h.cardSessionId) {
throw new InterruptedIOException("Connection closed");
}
}
}
|