/* * 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 * *
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()); } } } }