blob: 5396b0e3a1c1189999c70a9c480cc28254a8c911 [file] [log] [blame]
/*
* Copyright (C) 2011, The Android Open Source Project
*
* 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.
*/
/*
* Contributed by: Giesecke & Devrient GmbH.
*/
package org.simalliance.openmobileapi.service;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import org.simalliance.openmobileapi.service.ISmartcardServiceCallback;
import org.simalliance.openmobileapi.service.SmartcardService.SmartcardServiceSession;
import android.util.Log;
import java.security.AccessControlException;
import java.util.NoSuchElementException;
import org.simalliance.openmobileapi.service.security.AccessControlEnforcer;
import org.simalliance.openmobileapi.service.security.ChannelAccess;
/**
* Smartcard service base class for channel resources.
*/
class Channel implements IChannel, IBinder.DeathRecipient {
protected final int mChannelNumber;
protected boolean mIsClosed;
protected long mHandle;
protected ISmartcardServiceSession mSession;
protected Terminal mTerminal;
protected byte[] mSelectResponse;
protected final IBinder mBinder;
protected ChannelAccess mChannelAccess = null;
protected int mCallingPid = 0;
protected ISmartcardServiceCallback mCallback;
protected boolean mHasSelectedAid = false;
protected byte[] mAid = null;
Channel(SmartcardServiceSession session, Terminal terminal, int channelNumber, ISmartcardServiceCallback callback) {
this.mChannelNumber = channelNumber;
this.mSession = session;
this.mTerminal = terminal;
this.mCallback = callback;
this.mBinder = callback.asBinder();
this.mSelectResponse = terminal.getSelectResponse();
this.mIsClosed = false;
try {
mBinder.linkToDeath(this, 0);
} catch (RemoteException e) {
Log.e(SmartcardService._TAG, "Failed to register client callback");
}
}
public void binderDied() {
// Close this channel if the client died.
try {
Log.e(SmartcardService._TAG, Thread.currentThread().getName()
+ " Client " + mBinder.toString() + " died");
close();
} catch (Exception ignore) {
}
}
public synchronized void close() throws CardException {
Terminal terminal = getTerminal();
if( terminal == null ){
throw new IllegalStateException( "channel is not attached to a terminal");
}
if (isBasicChannel() && hasSelectedAid()) {
try {
Log.v(SmartcardService._TAG, "Close basic channel - Select with out AID ...");
terminal.select();
} catch (NoSuchElementException exp) {
//#if defined(sec)
// Selection of the default application fails
try {
Log.v(SmartcardService._TAG, "Close basic channel - Exception : " + exp.getLocalizedMessage());
AccessControlEnforcer access = terminal.getAccessControlEnforcer();
if( access != null ){
terminal.select(AccessControlEnforcer.getDefaultAccessControlAid());
}
} catch (NoSuchElementException exp2) {
// Access Control Applet not available => Don't care
}
//#endif
}
}
try {
terminal.closeChannel(this);
this.mIsClosed = true;
} finally {
mBinder.unlinkToDeath(this, 0);
}
}
public int getChannelNumber() {
return mChannelNumber;
}
/**
* Returns if this channel is a basic channel
*
* @return true if this channel is a basic channel
*/
public boolean isBasicChannel() {
return (mChannelNumber == 0) ? true : false;
}
public ISmartcardServiceCallback getCallback() {
return mCallback;
}
/**
* Returns the handle assigned to this channel.
*
* @return the handle assigned to this channel.
*/
long getHandle() {
return mHandle;
}
/**
* Returns the associated terminal.
*
* @return the associated terminal.
*/
public Terminal getTerminal() {
return mTerminal;
}
/**
* Assigns the channel handle.
*
* @param handle the channel handle to be assigned.
*/
void setHandle(long handle) {
this.mHandle = handle;
}
public byte[] transmit(byte[] command) throws CardException {
if( mChannelAccess == null ){
throw new AccessControlException( " Channel access not set.");
}
if (mChannelAccess.getCallingPid() != mCallingPid) {
throw new AccessControlException(" Wrong Caller PID. ");
}
checkCommand(command);
if (command.length < 4) {
throw new IllegalArgumentException(
" command must not be smaller than 4 bytes");
}
if (((command[0] & (byte) 0x80) == 0)
&& ((byte) (command[0] & (byte) 0x60) != (byte) 0x20)) {
// ISO command
if (command[1] == (byte) 0x70) {
throw new IllegalArgumentException(
"MANAGE CHANNEL command not allowed");
}
if ((command[1] == (byte) 0xA4) && (command[2] == (byte) 0x04)) {
throw new IllegalArgumentException("SELECT command not allowed");
}
} else {
// GlobalPlatform command
}
// set channel number bits
command[0] = setChannelToClassByte(command[0], mChannelNumber);
byte[] rsp = getTerminal().transmit(command, 2, 0, 0, null);
return rsp;
}
public boolean selectNext() throws CardException {
if( mChannelAccess == null ){
throw new AccessControlException( " Channel access not set.");
}
if (mChannelAccess.getCallingPid() != mCallingPid) {
throw new AccessControlException(" Wrong Caller PID. ");
}
if( mAid == null || mAid.length == 0){
throw new CardException(" no aid given");
}
mSelectResponse = null;
byte[] selectCommand = new byte[5 + mAid.length];
selectCommand[0] = 0x00;
selectCommand[1] = (byte) 0xA4;
selectCommand[2] = 0x04;
selectCommand[3] = 0x02; // next occurrence
selectCommand[4] = (byte)mAid.length;
System.arraycopy(mAid, 0, selectCommand, 5, mAid.length);
// set channel number bits
selectCommand[0] = setChannelToClassByte(selectCommand[0], mChannelNumber);
mSelectResponse = getTerminal().transmit(selectCommand, 2, 0, 0, "SELECT NEXT");
int sw1 = mSelectResponse[mSelectResponse.length - 2] & 0xFF;
int sw2 = mSelectResponse[mSelectResponse.length - 1] & 0xFF;
int sw = (sw1 << 8) | sw2;
if( (sw & 0xF000) == 0x9000 ){
return true;
} else if( sw == 0x6a82 ) {
return false;
} else {
throw new CardException(" invalid action" );
}
}
/**
* Returns a copy of the given CLA byte where the channel number bits are
* set as specified by the given channel number
*
* See GlobalPlatform Card Specification 2.2.0.7: 11.1.4 Class Byte Coding
*
* @param cla
* the CLA byte. Won't be modified
* @param channelNumber
* within [0..3] (for first interindustry class byte coding) or
* [4..19] (for further interindustry class byte coding)
* @return the CLA byte with set channel number bits. The seventh bit
* indicating the used coding (first/further interindustry class
* byte coding) might be modified
*/
private byte setChannelToClassByte(byte cla, int channelNumber) {
if (channelNumber < 4) {
// b7 = 0 indicates the first interindustry class byte coding
cla = (byte) ((cla & 0xBC) | channelNumber);
} else if (channelNumber < 20) {
// b7 = 1 indicates the further interindustry class byte coding
boolean isSM = (cla & 0x0C) != 0;
cla = (byte) ((cla & 0xB0) | 0x40 | (channelNumber - 4));
if (isSM) cla |= 0x20;
} else {
throw new IllegalArgumentException(
"Channel number must be within [0..19]");
}
return cla;
}
public void setChannelAccess(ChannelAccess channelAccess) {
this.mChannelAccess = channelAccess;
}
public ChannelAccess getChannelAccess(){
return this.mChannelAccess;
}
public void setCallingPid( int pid) {
mCallingPid = pid;
}
private void checkCommand( byte[] command ) {
if( getTerminal().getAccessControlEnforcer() != null ) {
// check command if it complies to the access rules.
// if not an exception is thrown
getTerminal().getAccessControlEnforcer().checkCommand(this, command);
} else {
throw new AccessControlException( "FATAL: Access Controller not set for Terminal: " + getTerminal().getName());
}
}
/**
* true if aid during open xxx channel coud be selected.
* false if aid could not be or was not selected.
* @return boolean.
*/
public boolean hasSelectedAid() {
return mHasSelectedAid;
}
/**
* set selected aid flag and aid (may be null)
*/
public void hasSelectedAid(boolean has, byte[] aid) {
mHasSelectedAid = has;
mAid = aid;
}
/**
* Returns the data as received from the application select command inclusively the status word.
* The returned byte array contains the data bytes in the following order:
* [<first data byte>, ..., <last data byte>, <sw1>, <sw2>]
* @return The data as returned by the application select command inclusively the status word.
* @return Only the status word if the application select command has no returned data.
* @return null if an application select command has not been performed or the selection response can not
* be retrieved by the reader implementation.
*/
public byte[] getSelectResponse()
{
return mSelectResponse;
}
boolean isClosed(){
return mIsClosed;
}
void setClosed(){
mIsClosed = true;
}
/**
* Implementation of the SmartcardService Channel interface according to OMAPI.
*
*
*/
final class SmartcardServiceChannel extends ISmartcardServiceChannel.Stub {
private final SmartcardServiceSession mSession;
public SmartcardServiceChannel( SmartcardServiceSession session ){
mSession = session;
}
@Override
public void close(SmartcardError error) throws RemoteException {
SmartcardService.clearError(error);
try {
Channel.this.close();
} catch (Exception e) {
SmartcardService.setError(error, e);
} finally {
if( mSession != null )
mSession.removeChannel(Channel.this);
}
}
public void setClosed(){
Channel.this.setClosed();
}
@Override
public boolean isClosed() throws RemoteException {
return Channel.this.isClosed();
}
@Override
public boolean isBasicChannel()
throws RemoteException {
return Channel.this.isBasicChannel();
}
@Override
public byte[] getSelectResponse()
throws RemoteException {
return Channel.this.getSelectResponse();
}
@Override
public ISmartcardServiceSession getSession()
throws RemoteException {
return Channel.this.mSession;
}
@Override
public byte[] transmit(byte[] command, SmartcardError error)
throws RemoteException {
SmartcardService.clearError(error);
try {
if (isClosed()) {
SmartcardService.setError(error, IllegalStateException.class, "channel is closed");
return null;
}
if (command == null) {
SmartcardService.setError(error, NullPointerException.class, "command must not be null");
return null;
}
if (command.length < 4) {
SmartcardService.setError(error, IllegalArgumentException.class,
"command must have at least 4 bytes");
return null;
}
Channel.this.setCallingPid(Binder.getCallingPid());
byte[] response = Channel.this.transmit(command);
return response;
} catch (Exception e) {
Log.v(SmartcardService._TAG, "transmit Exception: " + e.getMessage() + " (Command: " + Util.bytesToString(command) + ")");
SmartcardService.setError(error, e);
return null;
}
}
@Override
public boolean selectNext(SmartcardError error)
throws RemoteException {
SmartcardService.clearError(error);
try {
if (isClosed()) {
SmartcardService.setError(error, IllegalStateException.class, "channel is closed");
return false;
}
Channel.this.setCallingPid(Binder.getCallingPid());
boolean response = Channel.this.selectNext();
return response;
} catch (Exception e) {
SmartcardService.setError(error, e);
return false;
}
}
};
}