blob: d97dd2fe64608713c57eed71e42e517e4653d67c [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.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.hardware.secure_element.V1_0.ISecureElement;
import android.hardware.secure_element.V1_0.ISecureElementHalCallback;
import android.hardware.secure_element.V1_0.LogicalChannelResponse;
import android.hardware.secure_element.V1_0.SecureElementStatus;
import android.os.Build;
import android.os.Handler;
import android.os.HwBinder;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.se.omapi.ISecureElementListener;
import android.se.omapi.ISecureElementReader;
import android.se.omapi.ISecureElementSession;
import android.se.omapi.SEService;
import android.util.Log;
import com.android.se.SecureElementService.SecureElementSession;
import com.android.se.internal.ByteArrayConverter;
import com.android.se.security.AccessControlEnforcer;
import com.android.se.security.ChannelAccess;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.NoSuchElementException;
/**
* Each Terminal represents a Secure Element.
* Communicates to the SE via SecureElement HAL.
*/
public class Terminal {
private final String mTag;
private final Map<Integer, Channel> mChannels = new HashMap<Integer, Channel>();
private final Object mLock = new Object();
private final String mName;
public boolean mIsConnected = false;
private Context mContext;
private boolean mDefaultApplicationSelectedOnBasicChannel = true;
private static final boolean DEBUG = Build.IS_DEBUGGABLE;
private static final int GET_SERVICE_DELAY_MILLIS = 4 * 1000;
private static final int EVENT_GET_HAL = 1;
private final int mMaxGetHalRetryCount = 5;
private int mGetHalRetryCount = 0;
private ISecureElement mSEHal;
private android.hardware.secure_element.V1_2.ISecureElement mSEHal12;
/** For each Terminal there will be one AccessController object. */
private AccessControlEnforcer mAccessControlEnforcer;
private static final String SECURE_ELEMENT_PRIVILEGED_OPERATION_PERMISSION =
"android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION";
public static final byte[] ISD_R_AID =
new byte[]{
(byte) 0xA0,
(byte) 0x00,
(byte) 0x00,
(byte) 0x05,
(byte) 0x59,
(byte) 0x10,
(byte) 0x10,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0x89,
(byte) 0x00,
(byte) 0x00,
(byte) 0x01,
(byte) 0x00,
};
private ISecureElementHalCallback.Stub mHalCallback = new ISecureElementHalCallback.Stub() {
@Override
public void onStateChange(boolean state) {
stateChange(state, "");
}
};
private android.hardware.secure_element.V1_1.ISecureElementHalCallback.Stub mHalCallback11 =
new android.hardware.secure_element.V1_1.ISecureElementHalCallback.Stub() {
@Override
public void onStateChange_1_1(boolean state, String reason) {
stateChange(state, reason);
}
public void onStateChange(boolean state) {
return;
}
};
private void stateChange(boolean state, String reason) {
synchronized (mLock) {
Log.i(mTag, "OnStateChange:" + state + " reason:" + reason);
mIsConnected = state;
if (!state) {
if (mAccessControlEnforcer != null) {
mAccessControlEnforcer.reset();
}
SecureElementStatsLog.write(
SecureElementStatsLog.SE_STATE_CHANGED,
SecureElementStatsLog.SE_STATE_CHANGED__STATE__DISCONNECTED,
reason,
mName);
} else {
// If any logical channel in use is in the channel list, it should be closed
// because the access control enfocer allowed to open it by checking the access
// rules retrieved before. Now we are going to retrieve the rules again and
// the new rules can be different from the previous ones.
closeChannels();
try {
initializeAccessControl();
} catch (Exception e) {
// ignore
}
mDefaultApplicationSelectedOnBasicChannel = true;
SecureElementStatsLog.write(
SecureElementStatsLog.SE_STATE_CHANGED,
SecureElementStatsLog.SE_STATE_CHANGED__STATE__CONNECTED,
reason,
mName);
}
sendStateChangedBroadcast(state);
}
}
private void sendStateChangedBroadcast(boolean state) {
Intent intent = new Intent(SEService.ACTION_SECURE_ELEMENT_STATE_CHANGED);
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
intent.putExtra(SEService.EXTRA_READER_NAME, mName);
intent.putExtra(SEService.EXTRA_READER_STATE, state);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
};
class SecureElementDeathRecipient implements HwBinder.DeathRecipient {
@Override
public void serviceDied(long cookie) {
Log.e(mTag, mName + " died");
SecureElementStatsLog.write(
SecureElementStatsLog.SE_STATE_CHANGED,
SecureElementStatsLog.SE_STATE_CHANGED__STATE__HALCRASH,
"HALCRASH",
mName);
synchronized (mLock) {
mIsConnected = false;
if (mAccessControlEnforcer != null) {
mAccessControlEnforcer.reset();
}
}
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_GET_HAL, 0),
GET_SERVICE_DELAY_MILLIS);
}
}
private HwBinder.DeathRecipient mDeathRecipient = new SecureElementDeathRecipient();
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case EVENT_GET_HAL:
try {
if (mName.startsWith(SecureElementService.ESE_TERMINAL)) {
initialize(true);
} else {
initialize(false);
}
} catch (Exception e) {
Log.e(mTag, mName + " could not be initialized again");
if (mGetHalRetryCount < mMaxGetHalRetryCount) {
mGetHalRetryCount++;
sendMessageDelayed(obtainMessage(EVENT_GET_HAL, 0),
GET_SERVICE_DELAY_MILLIS);
} else {
Log.e(mTag, mName + " reach maximum retry count");
}
}
break;
default:
break;
}
}
};
public Terminal(String name, Context context) {
mContext = context;
mName = name;
mTag = "SecureElement-Terminal-" + getName();
}
/**
* Initializes the terminal
*
* @throws NoSuchElementException if there is no HAL implementation for the specified SE name
* @throws RemoteException if there is a failure communicating with the remote
*/
public void initialize(boolean retryOnFail) throws NoSuchElementException, RemoteException {
android.hardware.secure_element.V1_1.ISecureElement mSEHal11 = null;
synchronized (mLock) {
try {
mSEHal = mSEHal11 = mSEHal12 =
android.hardware.secure_element.V1_2.ISecureElement.getService(mName,
retryOnFail);
} catch (Exception e) {
Log.d(mTag, "SE Hal V1.2 is not supported");
}
if (mSEHal12 == null) {
try {
mSEHal = mSEHal11 =
android.hardware.secure_element.V1_1.ISecureElement.getService(mName,
retryOnFail);
} catch (Exception e) {
Log.d(mTag, "SE Hal V1.1 is not supported");
}
if (mSEHal11 == null) {
mSEHal = ISecureElement.getService(mName, retryOnFail);
if (mSEHal == null) {
throw new NoSuchElementException("No HAL is provided for " + mName);
}
}
}
if (mSEHal11 != null || mSEHal12 != null) {
mSEHal11.init_1_1(mHalCallback11);
} else {
mSEHal.init(mHalCallback);
}
mSEHal.linkToDeath(mDeathRecipient, 0);
}
Log.i(mTag, mName + " was initialized");
SecureElementStatsLog.write(
SecureElementStatsLog.SE_STATE_CHANGED,
SecureElementStatsLog.SE_STATE_CHANGED__STATE__INITIALIZED,
"INIT",
mName);
}
private ArrayList<Byte> byteArrayToArrayList(byte[] array) {
ArrayList<Byte> list = new ArrayList<Byte>();
if (array == null) {
return list;
}
for (Byte b : array) {
list.add(b);
}
return list;
}
private byte[] arrayListToByteArray(ArrayList<Byte> list) {
Byte[] byteArray = list.toArray(new Byte[list.size()]);
int i = 0;
byte[] result = new byte[list.size()];
for (Byte b : byteArray) {
result[i++] = b.byteValue();
}
return result;
}
/**
* Closes the given channel
*/
public void closeChannel(Channel channel) {
if (channel == null) {
return;
}
synchronized (mLock) {
if (mIsConnected) {
try {
byte status = mSEHal.closeChannel((byte) channel.getChannelNumber());
/* For Basic Channels, errors are expected.
* Underlying implementations use this call as an indication when there
* aren't any users actively using the channel, and the chip can go
* into low power state.
*/
if (!channel.isBasicChannel() && status != SecureElementStatus.SUCCESS) {
Log.e(mTag, "Error closing channel " + channel.getChannelNumber());
}
} catch (RemoteException e) {
Log.e(mTag, "Exception in closeChannel() " + e);
}
}
mChannels.remove(channel.getChannelNumber(), channel);
if (mChannels.get(channel.getChannelNumber()) != null) {
Log.e(mTag, "Removing channel failed");
}
}
}
/**
* Cleans up all the channels in use.
*/
public void closeChannels() {
synchronized (mLock) {
Collection<Channel> col = mChannels.values();
Channel[] channelList = col.toArray(new Channel[col.size()]);
for (Channel channel : channelList) {
channel.close();
}
}
}
/**
* Closes the terminal.
*/
public void close() {
synchronized (mLock) {
if (mSEHal != null) {
try {
mSEHal.unlinkToDeath(mDeathRecipient);
} catch (RemoteException e) {
// ignore
}
}
}
}
public String getName() {
return mName;
}
/**
* Returns the ATR of the Secure Element, or null if not available.
*/
public byte[] getAtr() {
if (!mIsConnected) {
return null;
}
try {
ArrayList<Byte> responseList = mSEHal.getAtr();
if (responseList.isEmpty()) {
return null;
}
byte[] atr = arrayListToByteArray(responseList);
if (DEBUG) {
Log.i(mTag, "ATR : " + ByteArrayConverter.byteArrayToHexString(atr));
}
return atr;
} catch (RemoteException e) {
Log.e(mTag, "Exception in getAtr()" + e);
return null;
}
}
/**
* Selects the default application on the basic channel.
*
* If there is an exception selecting the default application, select
* is performed with the default access control aid.
*/
public void selectDefaultApplication() {
try {
select(null);
} catch (NoSuchElementException e) {
if (getAccessControlEnforcer() != null) {
try {
select(mAccessControlEnforcer.getDefaultAccessControlAid());
} catch (Exception ignore) {
}
}
} catch (Exception ignore) {
}
}
private void select(byte[] aid) throws IOException {
int commandSize = (aid == null ? 0 : aid.length) + 5;
byte[] selectCommand = new byte[commandSize];
selectCommand[0] = 0x00;
selectCommand[1] = (byte) 0xA4;
selectCommand[2] = 0x04;
selectCommand[3] = 0x00;
if (aid != null && aid.length != 0) {
selectCommand[4] = (byte) aid.length;
System.arraycopy(aid, 0, selectCommand, 5, aid.length);
} else {
selectCommand[4] = 0x00;
}
byte[] selectResponse = transmit(selectCommand);
if (selectResponse.length < 2) {
selectResponse = null;
throw new NoSuchElementException("Response length is too small");
}
int sw1 = selectResponse[selectResponse.length - 2] & 0xFF;
int sw2 = selectResponse[selectResponse.length - 1] & 0xFF;
if (sw1 != 0x90 || sw2 != 0x00) {
selectResponse = null;
throw new NoSuchElementException("Status word is incorrect");
}
}
/**
* Opens a Basic Channel with the given AID and P2 paramters
*/
public Channel openBasicChannel(SecureElementSession session, byte[] aid, byte p2,
ISecureElementListener listener, String packageName, int pid) throws IOException,
NoSuchElementException {
if (aid != null && aid.length == 0) {
aid = null;
} else if (aid != null && (aid.length < 5 || aid.length > 16)) {
throw new IllegalArgumentException("AID out of range");
} else if (!mIsConnected) {
throw new IOException("Secure Element is not connected");
}
ChannelAccess channelAccess = null;
if (packageName != null) {
Log.w(mTag, "Enable access control on basic channel for " + packageName);
SecureElementStatsLog.write(
SecureElementStatsLog.SE_OMAPI_REPORTED,
SecureElementStatsLog.SE_OMAPI_REPORTED__OPERATION__OPEN_CHANNEL,
mName,
packageName);
try {
// For application without privilege permission or carrier privilege,
// openBasicChannel with UICC terminals should be rejected.
channelAccess = setUpChannelAccess(aid, packageName, pid, true);
} catch (MissingResourceException e) {
return null;
}
}
synchronized (mLock) {
if (mChannels.get(0) != null) {
Log.e(mTag, "basic channel in use");
return null;
}
if (aid == null && !mDefaultApplicationSelectedOnBasicChannel) {
Log.e(mTag, "default application is not selected");
return null;
}
ArrayList<byte[]> responseList = new ArrayList<byte[]>();
byte[] status = new byte[1];
try {
mSEHal.openBasicChannel(byteArrayToArrayList(aid), p2,
new ISecureElement.openBasicChannelCallback() {
@Override
public void onValues(ArrayList<Byte> responseObject, byte halStatus) {
status[0] = halStatus;
responseList.add(arrayListToByteArray(responseObject));
return;
}
});
} catch (RemoteException e) {
throw new IOException(e.getMessage());
}
byte[] selectResponse = responseList.get(0);
if (status[0] == SecureElementStatus.CHANNEL_NOT_AVAILABLE) {
return null;
} else if (status[0] == SecureElementStatus.UNSUPPORTED_OPERATION) {
throw new UnsupportedOperationException("OpenBasicChannel() failed");
} else if (status[0] == SecureElementStatus.IOERROR) {
throw new IOException("OpenBasicChannel() failed");
} else if (status[0] == SecureElementStatus.NO_SUCH_ELEMENT_ERROR) {
throw new NoSuchElementException("OpenBasicChannel() failed");
}
Channel basicChannel = new Channel(session, this, 0, selectResponse, aid,
listener);
basicChannel.setChannelAccess(channelAccess);
if (aid != null) {
mDefaultApplicationSelectedOnBasicChannel = false;
}
mChannels.put(0, basicChannel);
return basicChannel;
}
}
/**
* Opens a logical Channel without Channel Access initialization.
*/
public Channel openLogicalChannelWithoutChannelAccess(byte[] aid) throws IOException,
NoSuchElementException {
return openLogicalChannel(null, aid, (byte) 0x00, null, null, 0);
}
/**
* Opens a logical Channel with AID.
*/
public Channel openLogicalChannel(SecureElementSession session, byte[] aid, byte p2,
ISecureElementListener listener, String packageName, int pid) throws IOException,
NoSuchElementException {
if (aid != null && aid.length == 0) {
aid = null;
} else if (aid != null && (aid.length < 5 || aid.length > 16)) {
throw new IllegalArgumentException("AID out of range");
} else if (!mIsConnected) {
throw new IOException("Secure Element is not connected");
}
ChannelAccess channelAccess = null;
if (packageName != null) {
Log.w(mTag, "Enable access control on logical channel for " + packageName);
SecureElementStatsLog.write(
SecureElementStatsLog.SE_OMAPI_REPORTED,
SecureElementStatsLog.SE_OMAPI_REPORTED__OPERATION__OPEN_CHANNEL,
mName,
packageName);
try {
channelAccess = setUpChannelAccess(aid, packageName, pid, false);
} catch (MissingResourceException | UnsupportedOperationException e) {
return null;
}
}
synchronized (mLock) {
LogicalChannelResponse[] responseArray = new LogicalChannelResponse[1];
byte[] status = new byte[1];
try {
mSEHal.openLogicalChannel(byteArrayToArrayList(aid), p2,
new ISecureElement.openLogicalChannelCallback() {
@Override
public void onValues(LogicalChannelResponse response, byte halStatus) {
status[0] = halStatus;
responseArray[0] = response;
return;
}
});
} catch (RemoteException e) {
throw new IOException(e.getMessage());
}
if (status[0] == SecureElementStatus.CHANNEL_NOT_AVAILABLE) {
return null;
} else if (status[0] == SecureElementStatus.UNSUPPORTED_OPERATION) {
throw new UnsupportedOperationException("OpenLogicalChannel() failed");
} else if (status[0] == SecureElementStatus.IOERROR) {
throw new IOException("OpenLogicalChannel() failed");
} else if (status[0] == SecureElementStatus.NO_SUCH_ELEMENT_ERROR) {
throw new NoSuchElementException("OpenLogicalChannel() failed");
}
if (responseArray[0].channelNumber <= 0 || status[0] != SecureElementStatus.SUCCESS) {
return null;
}
int channelNumber = responseArray[0].channelNumber;
byte[] selectResponse = arrayListToByteArray(responseArray[0].selectResponse);
Channel logicalChannel = new Channel(session, this, channelNumber,
selectResponse, aid, listener);
logicalChannel.setChannelAccess(channelAccess);
mChannels.put(channelNumber, logicalChannel);
return logicalChannel;
}
}
/**
* Returns true if the given AID can be selected on the Terminal
*/
public boolean isAidSelectable(byte[] aid) {
if (aid == null) {
throw new NullPointerException("aid must not be null");
} else if (!mIsConnected) {
Log.e(mTag, "Secure Element is not connected");
return false;
}
synchronized (mLock) {
LogicalChannelResponse[] responseArray = new LogicalChannelResponse[1];
byte[] status = new byte[1];
try {
mSEHal.openLogicalChannel(byteArrayToArrayList(aid), (byte) 0x00,
new ISecureElement.openLogicalChannelCallback() {
@Override
public void onValues(LogicalChannelResponse response, byte halStatus) {
status[0] = halStatus;
responseArray[0] = response;
return;
}
});
if (status[0] == SecureElementStatus.SUCCESS) {
mSEHal.closeChannel(responseArray[0].channelNumber);
return true;
}
return false;
} catch (RemoteException e) {
Log.e(mTag, "Error in isAidSelectable() returning false" + e);
return false;
}
}
}
/**
* Transmits the specified command and returns the response.
*
* @param cmd the command APDU to be transmitted.
* @return the response received.
*/
public byte[] transmit(byte[] cmd) throws IOException {
if (!mIsConnected) {
Log.e(mTag, "Secure Element is not connected");
throw new IOException("Secure Element is not connected");
}
byte[] rsp = transmitInternal(cmd);
int sw1 = rsp[rsp.length - 2] & 0xFF;
int sw2 = rsp[rsp.length - 1] & 0xFF;
if (sw1 == 0x6C) {
cmd[cmd.length - 1] = rsp[rsp.length - 1];
rsp = transmit(cmd);
} else if (sw1 == 0x61) {
do {
byte[] getResponseCmd = new byte[]{
cmd[0], (byte) 0xC0, 0x00, 0x00, (byte) sw2
};
byte[] tmp = transmitInternal(getResponseCmd);
byte[] aux = rsp;
rsp = new byte[aux.length + tmp.length - 2];
System.arraycopy(aux, 0, rsp, 0, aux.length - 2);
System.arraycopy(tmp, 0, rsp, aux.length - 2, tmp.length);
sw1 = rsp[rsp.length - 2] & 0xFF;
sw2 = rsp[rsp.length - 1] & 0xFF;
} while (sw1 == 0x61);
}
return rsp;
}
private byte[] transmitInternal(byte[] cmd) throws IOException {
ArrayList<Byte> response;
try {
response = mSEHal.transmit(byteArrayToArrayList(cmd));
} catch (RemoteException e) {
throw new IOException(e.getMessage());
}
if (response.isEmpty()) {
throw new IOException("Error in transmit()");
}
byte[] rsp = arrayListToByteArray(response);
if (DEBUG) {
Log.i(mTag, "Sent : " + ByteArrayConverter.byteArrayToHexString(cmd));
Log.i(mTag, "Received : " + ByteArrayConverter.byteArrayToHexString(rsp));
}
return rsp;
}
/**
* Checks if the application is authorized to receive the transaction event.
*/
public boolean[] isNfcEventAllowed(PackageManager packageManager, byte[] aid,
String[] packageNames) {
if (!mIsConnected) {
// Return if not connected
return null;
}
// Return if the access control enforcer failed in previous attempt or no rule was found.
if (mAccessControlEnforcer == null || mAccessControlEnforcer.isNoRuleFound()) {
Log.i(mTag, "isNfcEventAllowed: No access rules for checking.");
return null;
}
mAccessControlEnforcer.setPackageManager(packageManager);
synchronized (mLock) {
try {
return mAccessControlEnforcer.isNfcEventAllowed(aid, packageNames);
} catch (Exception e) {
Log.i(mTag, "isNfcEventAllowed Exception: " + e.getMessage());
return null;
}
}
}
/**
* Returns true if the Secure Element is present
*/
public boolean isSecureElementPresent() {
try {
return mSEHal.isCardPresent();
} catch (RemoteException e) {
Log.e(mTag, "Error in isSecureElementPresent() " + e);
return false;
}
}
/**
* Reset the Secure Element. Return true if success, false otherwise.
*/
public boolean reset() {
if (mSEHal12 == null) {
return false;
}
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION,
"Need SECURE_ELEMENT_PRIVILEGED_OPERATION permission");
try {
byte status = mSEHal12.reset();
// Successfully trigger reset. HAL service should send onStateChange
// after secure element reset and initialization process complete
if (status == SecureElementStatus.SUCCESS) {
return true;
}
Log.e(mTag, "Error reseting terminal " + mName);
} catch (RemoteException e) {
Log.e(mTag, "Exception in reset()" + e);
}
return false;
}
/**
* Initialize the Access Control and set up the channel access.
*/
private ChannelAccess setUpChannelAccess(byte[] aid, String packageName, int pid,
boolean isBasicChannel) throws IOException, MissingResourceException {
boolean checkRefreshTag = true;
if (isPrivilegedApplication(packageName)) {
return ChannelAccess.getPrivilegeAccess(packageName, pid);
}
// Attempt to initialize the access control enforcer if it failed
// due to a kind of temporary failure or no rule was found in the previous attempt.
// For privilege access, do not attempt to initialize the access control enforcer
// if no rule was found in the previous attempt.
if (mAccessControlEnforcer == null || mAccessControlEnforcer.isNoRuleFound()) {
initializeAccessControl();
// Just finished to initialize the access control enforcer.
// It is too much to check the refresh tag in this case.
checkRefreshTag = false;
}
mAccessControlEnforcer.setPackageManager(mContext.getPackageManager());
// Check carrier privilege when AID is not ISD-R
if (getName().startsWith(SecureElementService.UICC_TERMINAL)
&& !Arrays.equals(aid, ISD_R_AID)) {
try {
PackageManager pm = mContext.getPackageManager();
if (pm != null) {
PackageInfo pkgInfo =
pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
// Do not check the refresh tag for carrier privilege
if (mAccessControlEnforcer.checkCarrierPrivilege(pkgInfo, false)) {
Log.i(mTag, "setUp PrivilegeAccess for CarrierPrivilegeApplication. ");
return ChannelAccess.getCarrierPrivilegeAccess(packageName, pid);
}
}
} catch (NameNotFoundException ne) {
Log.e(mTag, "checkCarrierPrivilege(): packageInfo is not found. ");
} catch (Exception e) {
Log.e(mTag, "checkCarrierPrivilege() Exception: " + e.getMessage());
}
if (isBasicChannel) {
throw new MissingResourceException("openBasicChannel is not allowed.", "", "");
} else if (aid == null) {
// openLogicalChannel with null aid is only allowed for privilege applications
throw new UnsupportedOperationException(
"null aid is not accepted in UICC terminal.");
}
}
synchronized (mLock) {
try {
ChannelAccess channelAccess =
mAccessControlEnforcer.setUpChannelAccess(aid, packageName,
checkRefreshTag);
channelAccess.setCallingPid(pid);
return channelAccess;
} catch (IOException | MissingResourceException e) {
throw e;
} catch (Exception e) {
throw new SecurityException("Exception in setUpChannelAccess()" + e);
}
}
}
/**
* Initializes the Access Control for this Terminal
*/
private synchronized void initializeAccessControl() throws IOException,
MissingResourceException {
synchronized (mLock) {
if (mAccessControlEnforcer == null) {
mAccessControlEnforcer = new AccessControlEnforcer(this);
}
try {
mAccessControlEnforcer.initialize();
} catch (IOException | MissingResourceException e) {
// Retrieving access rules failed because of an IO error happened between
// the terminal and the secure element or the lack of a logical channel available.
// It might be a temporary failure, so the terminal shall attempt to cache
// the access rules again later.
mAccessControlEnforcer = null;
throw e;
}
}
}
/**
* Checks if Secure Element Privilege permission exists for the given package
*/
private boolean isPrivilegedApplication(String packageName) {
PackageManager pm = mContext.getPackageManager();
if (pm != null) {
return (pm.checkPermission(SECURE_ELEMENT_PRIVILEGED_OPERATION_PERMISSION,
packageName) == PackageManager.PERMISSION_GRANTED);
}
return false;
}
public AccessControlEnforcer getAccessControlEnforcer() {
return mAccessControlEnforcer;
}
public Context getContext() {
return mContext;
}
/**
* Checks if Carrier Privilege exists for the given package
*/
public boolean checkCarrierPrivilegeRules(PackageInfo pInfo) {
boolean checkRefreshTag = true;
if (mAccessControlEnforcer == null || mAccessControlEnforcer.isNoRuleFound()) {
try {
initializeAccessControl();
} catch (IOException e) {
return false;
}
checkRefreshTag = false;
}
mAccessControlEnforcer.setPackageManager(mContext.getPackageManager());
synchronized (mLock) {
try {
return mAccessControlEnforcer.checkCarrierPrivilege(pInfo, checkRefreshTag);
} catch (Exception e) {
Log.i(mTag, "checkCarrierPrivilege() Exception: " + e.getMessage());
return false;
}
}
}
/** Dump data for debug purpose . */
public void dump(PrintWriter writer) {
writer.println("SECURE ELEMENT SERVICE TERMINAL: " + mName);
writer.println();
writer.println("mIsConnected:" + mIsConnected);
writer.println();
/* Dump the list of currunlty openned channels */
writer.println("List of open channels:");
for (Channel channel : mChannels.values()) {
writer.println("channel " + channel.getChannelNumber() + ": ");
writer.println("package: " + channel.getChannelAccess().getPackageName());
writer.println("pid: " + channel.getChannelAccess().getCallingPid());
writer.println("aid selected: " + channel.hasSelectedAid());
writer.println("basic channel: " + channel.isBasicChannel());
writer.println();
}
writer.println();
/* Dump ACE data */
if (mAccessControlEnforcer != null) {
mAccessControlEnforcer.dump(writer);
}
}
// Implementation of the SecureElement Reader interface according to OMAPI.
final class SecureElementReader extends ISecureElementReader.Stub {
private final SecureElementService mService;
private final ArrayList<SecureElementSession> mSessions =
new ArrayList<SecureElementSession>();
SecureElementReader(SecureElementService service) {
mService = service;
}
public byte[] getAtr() {
return Terminal.this.getAtr();
}
@Override
public boolean isSecureElementPresent() throws RemoteException {
return Terminal.this.isSecureElementPresent();
}
@Override
public void closeSessions() {
synchronized (mLock) {
while (mSessions.size() > 0) {
try {
mSessions.get(0).close();
} catch (Exception ignore) {
}
}
mSessions.clear();
}
}
public void removeSession(SecureElementSession session) {
if (session == null) {
throw new NullPointerException("session is null");
}
synchronized (mLock) {
mSessions.remove(session);
if (mSessions.size() == 0) {
mDefaultApplicationSelectedOnBasicChannel = true;
}
}
}
@Override
public ISecureElementSession openSession() throws RemoteException {
if (!isSecureElementPresent()) {
throw new ServiceSpecificException(SEService.IO_ERROR,
"Secure Element is not present.");
}
synchronized (mLock) {
SecureElementSession session = mService.new SecureElementSession(this);
mSessions.add(session);
return session;
}
}
Terminal getTerminal() {
return Terminal.this;
}
@Override
public boolean reset() {
return Terminal.this.reset();
}
}
}