/* * 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 mTerminals = new LinkedHashMap(); 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 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); } } }; }