blob: d6df4151e09f84ea6b04872c0c257455a126ce42 [file] [log] [blame]
/*
* Copyright (C) 2017 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.
*/
/*
* Copyright (c) 2015-2017, The Linux Foundation.
*/
/*
* Contributed by: Giesecke & Devrient GmbH.
*/
package com.android.se;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.se.omapi.ISecureElementChannel;
import android.se.omapi.ISecureElementListener;
import android.se.omapi.SEService;
import android.util.Log;
import com.android.se.SecureElementService.SecureElementSession;
import com.android.se.security.ChannelAccess;
import java.io.IOException;
/**
* Represents a Channel opened with the Secure Element
*/
public class Channel implements IBinder.DeathRecipient {
private final String mTag = "SecureElement-Channel";
private final int mChannelNumber;
private final Object mLock = new Object();
private IBinder mBinder = null;
private boolean mIsClosed;
private SecureElementSession mSession;
private Terminal mTerminal;
private byte[] mSelectResponse;
private ChannelAccess mChannelAccess = null;
private int mCallingPid = 0;
private byte[] mAid = null;
Channel(SecureElementSession session, Terminal terminal, int channelNumber,
byte[] selectResponse, byte[] aid, ISecureElementListener listener) {
if (terminal == null) {
throw new IllegalArgumentException("Arguments can't be null");
}
mSession = session;
mTerminal = terminal;
mIsClosed = false;
mSelectResponse = selectResponse;
mChannelNumber = channelNumber;
mAid = aid;
if (listener != null) {
try {
mBinder = listener.asBinder();
mBinder.linkToDeath(this, 0);
} catch (RemoteException e) {
Log.e(mTag, "Failed to register client listener");
}
}
}
/**
* Close this channel if the client died.
*/
public void binderDied() {
try {
Log.e(mTag, Thread.currentThread().getName() + " Client "
+ mBinder.toString() + " died");
close();
} catch (Exception ignore) {
}
}
/**
* Closes the channel.
*/
public void close() {
synchronized (mLock) {
if (isClosed())
return;
mIsClosed = true;
}
if (isBasicChannel()) {
Log.i(mTag, "Close basic channel - Select without AID ...");
mTerminal.selectDefaultApplication();
}
mTerminal.closeChannel(this);
if (mBinder != null) {
mBinder.unlinkToDeath(this, 0);
}
if (mSession != null) {
mSession.removeChannel(this);
}
}
/**
* Transmits the given byte and returns the response.
*/
public byte[] transmit(byte[] command) throws IOException {
if (isClosed()) {
throw new IllegalStateException("Channel is closed");
}
if (command == null) {
throw new NullPointerException("Command must not be null");
}
if (mChannelAccess == null) {
throw new SecurityException("Channel access not set");
}
if (mChannelAccess.getCallingPid() != mCallingPid) {
throw new SecurityException("Wrong Caller PID.");
}
// Validate the APDU command format and throw IllegalArgumentException, if necessary.
CommandApduValidator.execute(command);
if (((command[0] & (byte) 0x80) == 0)
&& ((command[0] & (byte) 0x60) != (byte) 0x20)) {
// ISO command
if (command[1] == (byte) 0x70) {
throw new SecurityException("MANAGE CHANNEL command not allowed");
}
if ((command[1] == (byte) 0xA4) && (command[2] == (byte) 0x04)) {
// SELECT by DF name is only allowed for CarrierPrivilege applications
// or system privilege applications
if (ChannelAccess.ACCESS.ALLOWED != mChannelAccess.getPrivilegeAccess()) {
throw new SecurityException("SELECT by DF name command not allowed");
}
}
}
checkCommand(command);
synchronized (mLock) {
// set channel number bits
command[0] = setChannelToClassByte(command[0], mChannelNumber);
return mTerminal.transmit(command);
}
}
private boolean selectNext() throws IOException {
if (isClosed()) {
throw new IllegalStateException("Channel is closed");
} else if (mChannelAccess == null) {
throw new IllegalStateException("Channel access not set.");
} else if (mChannelAccess.getCallingPid() != mCallingPid) {
throw new SecurityException("Wrong Caller PID.");
} else if (mAid == null || mAid.length == 0) {
throw new UnsupportedOperationException("No aid given");
}
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);
byte[] bufferSelectResponse = mTerminal.transmit(selectCommand);
if (bufferSelectResponse.length < 2) {
throw new UnsupportedOperationException("Transmit failed");
}
int sw1 = bufferSelectResponse[bufferSelectResponse.length - 2] & 0xFF;
int sw2 = bufferSelectResponse[bufferSelectResponse.length - 1] & 0xFF;
int sw = (sw1 << 8) | sw2;
if (((sw & 0xF000) == 0x9000) || ((sw & 0xFF00) == 0x6200)
|| ((sw & 0xFF00) == 0x6300)) {
mSelectResponse = bufferSelectResponse.clone();
return true;
} else if ((sw & 0xFF00) == 0x6A00) {
return false;
} else {
throw new UnsupportedOperationException("Unsupported operation");
}
}
/**
* Returns a copy of the given CLA byte where the channel number bits are set
* as specified by the given channel number
*
* <p>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 inter-industry class byte
* coding) or [4..19] (for further inter-industry 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 ChannelAccess getChannelAccess() {
return this.mChannelAccess;
}
public void setChannelAccess(ChannelAccess channelAccess) {
this.mChannelAccess = channelAccess;
}
private void setCallingPid(int pid) {
mCallingPid = pid;
}
private void checkCommand(byte[] command) {
if (mTerminal.getAccessControlEnforcer() != null) {
// check command if it complies to the access rules.
// if not an exception is thrown
mTerminal.getAccessControlEnforcer().checkCommand(this, command);
} else {
// Allow access to Privileged App even if Access Control Enforcer is
// not initialized
if (ChannelAccess.ACCESS.ALLOWED != mChannelAccess.getPrivilegeAccess()) {
throw new SecurityException("Access Controller not set for Terminal: "
+ mTerminal.getName());
}
}
}
/**
* true if aid could be selected during opening the channel
* false if aid could not be or was not selected.
*
* @return boolean.
*/
public boolean hasSelectedAid() {
return (mAid != null);
}
public int getChannelNumber() {
return mChannelNumber;
}
/**
* 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 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 (hasSelectedAid() ? mSelectResponse : null);
}
public boolean isBasicChannel() {
return (mChannelNumber == 0) ? true : false;
}
public boolean isClosed() {
return mIsClosed;
}
// Implementation of the SecureElement Channel interface according to OMAPI.
final class SecureElementChannel extends ISecureElementChannel.Stub {
@Override
public void close() throws RemoteException {
Channel.this.close();
}
@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 byte[] transmit(byte[] command) throws RemoteException {
Channel.this.setCallingPid(Binder.getCallingPid());
try {
return Channel.this.transmit(command);
} catch (IOException e) {
throw new ServiceSpecificException(SEService.IO_ERROR, e.getMessage());
}
}
@Override
public boolean selectNext() throws RemoteException {
Channel.this.setCallingPid(Binder.getCallingPid());
try {
return Channel.this.selectNext();
} catch (IOException e) {
throw new ServiceSpecificException(SEService.IO_ERROR, e.getMessage());
}
}
}
}