You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
402 lines
15 KiB
402 lines
15 KiB
/*
|
|
* 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) 2014-2017, The Linux Foundation.
|
|
*/
|
|
/*
|
|
* Contributed by: Giesecke & Devrient GmbH.
|
|
*/
|
|
|
|
package com.android.se;
|
|
|
|
import android.app.Service;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.content.pm.PackageManager;
|
|
import android.os.Binder;
|
|
import android.os.Build;
|
|
import android.os.IBinder;
|
|
import android.os.RemoteException;
|
|
import android.os.ServiceManager;
|
|
import android.os.ServiceSpecificException;
|
|
import android.se.omapi.ISecureElementChannel;
|
|
import android.se.omapi.ISecureElementListener;
|
|
import android.se.omapi.ISecureElementReader;
|
|
import android.se.omapi.ISecureElementService;
|
|
import android.se.omapi.ISecureElementSession;
|
|
import android.se.omapi.SEService;
|
|
import android.telephony.TelephonyManager;
|
|
import android.util.Log;
|
|
|
|
import com.android.se.Terminal.SecureElementReader;
|
|
import com.android.se.internal.ByteArrayConverter;
|
|
|
|
import java.io.FileDescriptor;
|
|
import java.io.IOException;
|
|
import java.io.PrintWriter;
|
|
import java.security.AccessControlException;
|
|
import java.util.ArrayList;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.NoSuchElementException;
|
|
|
|
/**
|
|
* Underlying implementation for OMAPI SEService
|
|
*/
|
|
public final class SecureElementService extends Service {
|
|
|
|
public static final String UICC_TERMINAL = "SIM";
|
|
public static final String ESE_TERMINAL = "eSE";
|
|
private final String mTag = "SecureElementService";
|
|
private static final boolean DEBUG = Build.IS_DEBUGGABLE;
|
|
// LinkedHashMap will maintain the order of insertion
|
|
private LinkedHashMap<String, Terminal> mTerminals = new LinkedHashMap<String, Terminal>();
|
|
private int mActiveSimCount = 0;
|
|
private final ISecureElementService.Stub mSecureElementServiceBinder =
|
|
new ISecureElementService.Stub() {
|
|
|
|
@Override
|
|
public String[] getReaders() throws RemoteException {
|
|
return mTerminals.keySet().toArray(new String[mTerminals.size()]);
|
|
}
|
|
|
|
@Override
|
|
public ISecureElementReader getReader(String reader)
|
|
throws RemoteException {
|
|
Log.d(mTag, "getReader() " + reader);
|
|
Terminal terminal = getTerminal(reader);
|
|
return terminal.new SecureElementReader(SecureElementService.this);
|
|
}
|
|
|
|
@Override
|
|
public synchronized boolean[] isNFCEventAllowed(String reader, byte[] aid,
|
|
String[] packageNames)
|
|
throws RemoteException {
|
|
if (aid == null || aid.length == 0) {
|
|
aid = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00};
|
|
}
|
|
if (aid.length < 5 || aid.length > 16) {
|
|
throw new IllegalArgumentException("AID out of range");
|
|
}
|
|
if (packageNames == null || packageNames.length == 0) {
|
|
throw new IllegalArgumentException("package names not specified");
|
|
}
|
|
Terminal terminal = getTerminal(reader);
|
|
return terminal.isNfcEventAllowed(getPackageManager(), aid, packageNames);
|
|
}
|
|
|
|
@Override
|
|
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
|
|
for (Terminal terminal : mTerminals.values()) {
|
|
terminal.dump(writer);
|
|
}
|
|
}
|
|
};
|
|
|
|
public SecureElementService() {
|
|
super();
|
|
}
|
|
|
|
private void initialize() {
|
|
// listen for events
|
|
IntentFilter intentFilter = new IntentFilter();
|
|
intentFilter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
|
|
this.registerReceiver(mMultiSimConfigChangedReceiver, intentFilter);
|
|
}
|
|
|
|
/** Returns the terminal from the Reader name. */
|
|
private Terminal getTerminal(String reader) {
|
|
if (reader == null) {
|
|
throw new NullPointerException("reader must not be null");
|
|
}
|
|
if (reader.equals("SIM")) {
|
|
reader = "SIM1";
|
|
}
|
|
Terminal terminal = mTerminals.get(reader);
|
|
if (terminal == null) {
|
|
throw new IllegalArgumentException("Reader: " + reader + " doesn't exist");
|
|
}
|
|
return terminal;
|
|
}
|
|
|
|
@Override
|
|
public IBinder onBind(Intent intent) {
|
|
Log.i(mTag, Thread.currentThread().getName() + " onBind");
|
|
if (ISecureElementService.class.getName().equals(intent.getAction())) {
|
|
return mSecureElementServiceBinder;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void onCreate() {
|
|
Log.i(mTag, Thread.currentThread().getName() + " onCreate");
|
|
initialize();
|
|
createTerminals();
|
|
ServiceManager.addService(Context.SECURE_ELEMENT_SERVICE, mSecureElementServiceBinder);
|
|
}
|
|
|
|
/**
|
|
* In case the onDestroy is called, we free the memory and
|
|
* close all the channels.
|
|
*/
|
|
public void onDestroy() {
|
|
Log.i(mTag, "onDestroy");
|
|
for (Terminal terminal : mTerminals.values()) {
|
|
terminal.closeChannels();
|
|
terminal.close();
|
|
}
|
|
if (mMultiSimConfigChangedReceiver != null) {
|
|
this.unregisterReceiver(mMultiSimConfigChangedReceiver);
|
|
}
|
|
}
|
|
|
|
private void addTerminals(String terminalName) {
|
|
int index = 1;
|
|
String name = null;
|
|
if (terminalName.startsWith(SecureElementService.UICC_TERMINAL)) {
|
|
index = mActiveSimCount + 1;
|
|
}
|
|
try {
|
|
do {
|
|
name = terminalName + Integer.toString(index);
|
|
Terminal terminal = new Terminal(name, this);
|
|
|
|
Log.i(mTag, "Check if terminal " + name + " is available.");
|
|
// Only retry on fail for the first terminal of each type.
|
|
terminal.initialize(index == 1);
|
|
mTerminals.put(name, terminal);
|
|
if (terminalName.equals(UICC_TERMINAL)) {
|
|
mActiveSimCount = index;
|
|
}
|
|
} while (++index > 0);
|
|
} catch (NoSuchElementException e) {
|
|
Log.i(mTag, "No HAL implementation for " + name);
|
|
} catch (RemoteException | RuntimeException e) {
|
|
Log.e(mTag, "Error in getService() for " + name);
|
|
}
|
|
}
|
|
|
|
private void createTerminals() {
|
|
// Check for all SE HAL implementations
|
|
addTerminals(ESE_TERMINAL);
|
|
addTerminals(UICC_TERMINAL);
|
|
}
|
|
|
|
private void refreshUiccTerminals(int activeSimCount) {
|
|
String name = null;
|
|
synchronized (this) {
|
|
if (activeSimCount < mActiveSimCount) {
|
|
// Remove non-supported UICC terminals
|
|
for (int i = activeSimCount + 1; i <= mActiveSimCount; i++) {
|
|
name = UICC_TERMINAL + Integer.toString(i);
|
|
Terminal terminal = mTerminals.get(name);
|
|
if (terminal != null) {
|
|
terminal.closeChannels();
|
|
terminal.close();
|
|
}
|
|
mTerminals.remove(name);
|
|
Log.i(mTag, name + " is removed from available Terminals");
|
|
}
|
|
mActiveSimCount = activeSimCount;
|
|
} else if (activeSimCount > mActiveSimCount) {
|
|
// Try to initialize new UICC terminals
|
|
addTerminals(UICC_TERMINAL);
|
|
}
|
|
}
|
|
}
|
|
|
|
private String getPackageNameFromCallingUid(int uid) {
|
|
PackageManager packageManager = getPackageManager();
|
|
if (packageManager != null) {
|
|
String[] packageName = packageManager.getPackagesForUid(uid);
|
|
if (packageName != null && packageName.length > 0) {
|
|
return packageName[0];
|
|
}
|
|
}
|
|
throw new AccessControlException("PackageName can not be determined");
|
|
}
|
|
|
|
final class SecureElementSession extends ISecureElementSession.Stub {
|
|
|
|
private final SecureElementReader mReader;
|
|
/** List of open channels in use of by this client. */
|
|
private final List<Channel> mChannels = new ArrayList<>();
|
|
private final Object mLock = new Object();
|
|
private boolean mIsClosed;
|
|
private byte[] mAtr;
|
|
|
|
SecureElementSession(SecureElementReader reader) {
|
|
if (reader == null) {
|
|
throw new NullPointerException("SecureElementReader cannot be null");
|
|
}
|
|
mReader = reader;
|
|
mAtr = mReader.getAtr();
|
|
mIsClosed = false;
|
|
}
|
|
|
|
public ISecureElementReader getReader() throws RemoteException {
|
|
return mReader;
|
|
}
|
|
|
|
@Override
|
|
public byte[] getAtr() throws RemoteException {
|
|
return mAtr;
|
|
}
|
|
|
|
@Override
|
|
public void close() throws RemoteException {
|
|
closeChannels();
|
|
mReader.removeSession(this);
|
|
synchronized (mLock) {
|
|
mIsClosed = true;
|
|
}
|
|
}
|
|
|
|
void removeChannel(Channel channel) {
|
|
synchronized (mLock) {
|
|
if (mChannels != null) {
|
|
mChannels.remove(channel);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void closeChannels() throws RemoteException {
|
|
synchronized (mLock) {
|
|
while (mChannels.size() > 0) {
|
|
try {
|
|
mChannels.get(0).close();
|
|
} catch (Exception ignore) {
|
|
Log.e(mTag, "SecureElementSession Channel - close Exception "
|
|
+ ignore.getMessage());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isClosed() throws RemoteException {
|
|
synchronized (mLock) {
|
|
return mIsClosed;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ISecureElementChannel openBasicChannel(byte[] aid, byte p2,
|
|
ISecureElementListener listener) throws RemoteException {
|
|
if (DEBUG) {
|
|
Log.i(mTag, "openBasicChannel() AID = "
|
|
+ ByteArrayConverter.byteArrayToHexString(aid) + ", P2 = " + p2);
|
|
}
|
|
if (isClosed()) {
|
|
throw new IllegalStateException("Session is closed");
|
|
} else if (listener == null) {
|
|
throw new NullPointerException("listener must not be null");
|
|
} else if ((p2 != 0x00) && (p2 != 0x04) && (p2 != 0x08)
|
|
&& (p2 != (byte) 0x0C)) {
|
|
throw new UnsupportedOperationException("p2 not supported: "
|
|
+ String.format("%02x ", p2 & 0xFF));
|
|
}
|
|
|
|
String packageName = getPackageNameFromCallingUid(Binder.getCallingUid());
|
|
Channel channel = null;
|
|
|
|
try {
|
|
channel = mReader.getTerminal().openBasicChannel(this, aid, p2, listener,
|
|
packageName, Binder.getCallingPid());
|
|
} catch (IOException e) {
|
|
throw new ServiceSpecificException(SEService.IO_ERROR, e.getMessage());
|
|
} catch (NoSuchElementException e) {
|
|
throw new ServiceSpecificException(SEService.NO_SUCH_ELEMENT_ERROR, e.getMessage());
|
|
}
|
|
|
|
if (channel == null) {
|
|
Log.i(mTag, "OpenBasicChannel() - returning null");
|
|
return null;
|
|
}
|
|
Log.i(mTag, "Open basic channel success. Channel: "
|
|
+ channel.getChannelNumber());
|
|
|
|
synchronized (mLock) {
|
|
mChannels.add(channel);
|
|
}
|
|
return channel.new SecureElementChannel();
|
|
}
|
|
|
|
@Override
|
|
public ISecureElementChannel openLogicalChannel(byte[] aid, byte p2,
|
|
ISecureElementListener listener) throws RemoteException {
|
|
if (DEBUG) {
|
|
Log.i(mTag, "openLogicalChannel() AID = "
|
|
+ ByteArrayConverter.byteArrayToHexString(aid) + ", P2 = " + p2);
|
|
}
|
|
if (isClosed()) {
|
|
throw new IllegalStateException("Session is closed");
|
|
} else if (listener == null) {
|
|
throw new NullPointerException("listener must not be null");
|
|
} else if ((p2 != 0x00) && (p2 != 0x04) && (p2 != 0x08)
|
|
&& (p2 != (byte) 0x0C)) {
|
|
throw new UnsupportedOperationException("p2 not supported: "
|
|
+ String.format("%02x ", p2 & 0xFF));
|
|
}
|
|
|
|
String packageName = getPackageNameFromCallingUid(Binder.getCallingUid());
|
|
Channel channel = null;
|
|
|
|
try {
|
|
channel = mReader.getTerminal().openLogicalChannel(this, aid, p2, listener,
|
|
packageName, Binder.getCallingPid());
|
|
} catch (IOException e) {
|
|
throw new ServiceSpecificException(SEService.IO_ERROR, e.getMessage());
|
|
} catch (NoSuchElementException e) {
|
|
throw new ServiceSpecificException(SEService.NO_SUCH_ELEMENT_ERROR, e.getMessage());
|
|
}
|
|
|
|
if (channel == null) {
|
|
Log.i(mTag, "openLogicalChannel() - returning null");
|
|
return null;
|
|
}
|
|
Log.i(mTag, "openLogicalChannel() Success. Channel: "
|
|
+ channel.getChannelNumber());
|
|
|
|
synchronized (mLock) {
|
|
mChannels.add(channel);
|
|
}
|
|
return channel.new SecureElementChannel();
|
|
}
|
|
}
|
|
|
|
private final BroadcastReceiver mMultiSimConfigChangedReceiver = new BroadcastReceiver() {
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
String action = intent.getAction();
|
|
if (action.equals(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED)) {
|
|
int activeSimCount =
|
|
intent.getIntExtra(TelephonyManager.EXTRA_ACTIVE_SIM_SUPPORTED_COUNT, 1);
|
|
Log.i(mTag, "received action MultiSimConfigChanged. Refresh UICC terminals");
|
|
Log.i(mTag, "Current ActiveSimCount:" + activeSimCount
|
|
+ ". Previous ActiveSimCount:" + mActiveSimCount);
|
|
|
|
// Check for any change to UICC SE HAL implementations
|
|
refreshUiccTerminals(activeSimCount);
|
|
}
|
|
}
|
|
};
|
|
}
|