package org.osmocom.androidApduProxy; import android.os.Handler; import android.os.Message; import android.util.Log; import java.text.MessageFormat; import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; //OMAPI callback handler that implements the VPCD <---> OMAPI proxy class OmapiCallbackHandlerVpcd implements OmapiCallback { private Vpcd vpcd = null; private String omapiReader = null; private String remoteHost = null; private int remotePort = 0; private Handler uiHandler = null; private int omapiChannel = -1; //When we open the OMPI channel the first time, we must provide an AID. The following AID is //the prefix (RID) of 3GPP (see also ETSI TS 101 220, section 4.1) This prefix should select //the first 3GPP card application (usually ADF.USIM). private static final byte[] DEFAULT_AID = Utils.h2b("a000000087"); OmapiCallbackHandlerVpcd(Handler uiHandler, String omapiReader, String remoteHost, Integer remotePort) { this.omapiReader = omapiReader; this.remoteHost = remoteHost; this.remotePort = remotePort; this.uiHandler = uiHandler; } public void omapiConnected(Omapi omapi) { ExecutorService executor = Executors.newSingleThreadExecutor(); class VpcdCallbackHandler implements VpcdCallback { private void sendMessageInd(int indicator) { Message ind = uiHandler.obtainMessage(indicator); ind.sendToTarget(); } private void sendErrorMessage(Exception e) { Message ind = uiHandler.obtainMessage(MainActivity.IND_ERROR, e.getMessage()); ind.sendToTarget(); } @Override public byte[] vpcdAtr() { sendMessageInd(MainActivity.IND_ATR_TRAFFIC); byte[] atr = null; try { atr = omapi.getAtr(omapiReader); } catch (Exception e) { sendErrorMessage(e); } if (atr != null) return Utils.adjustATR(atr); //Since this code runs only when OMAPI is connected, this code location //should not be reached, except there is some problem with the card Log.e("PROXY", "No ATR!, unresponsive card?!\n"); return null; } @Override public void vpcdReset() { //A reset effectively translates into the closing and re-opening of the omapi channel Log.i("PROXY", "Re-Opening OMAPI channel as an alternative to reset...\n"); omapi.close(omapiChannel); sendMessageInd(MainActivity.IND_CHANNEL_CLOSE); omapiChannel = 0; try { omapiChannel = omapi.open(omapiReader, DEFAULT_AID); } catch (Exception e) { sendErrorMessage(e); return; } sendMessageInd(MainActivity.IND_CHANNEL_OPEN); } @Override public void vpcdPwrOn() { Log.i("PROXY", "Re-Opening OMAPI channel as an alternative to power-on...\n"); omapi.close(omapiChannel); omapiChannel = 0; try { omapiChannel = omapi.open(omapiReader, DEFAULT_AID); } catch (Exception e) { sendErrorMessage(e); return; } sendMessageInd(MainActivity.IND_CHANNEL_OPEN); } @Override public void vpcdPwrOff() { Log.i("PROXY", "Closing OMAPI channel as an alternative to power-off...\n"); omapi.close(omapiChannel); sendMessageInd(MainActivity.IND_CHANNEL_CLOSE); } @Override public byte[] vpcdTransact(byte[] tpdu) { Log.i("PROXY", "Exchanging TPDU...\n"); //All TPDUs that we receive here should have a minimum length of 5 bytes. Under normal conditins, short //TPDUs should not occurr as they should already be filtered out by the layers that call this method. //To ensure seamless operation, let's check the TPDU length and reject short TPDUs immediately. if (tpdu.length < 5) { Log.e("PROXY", String.format("Rejecting short TPDU (%s)...\n", Utils.b2h(tpdu))); //see also ISO/IEC 7816-4, table 5 (wrong length; no further indication) return (Utils.h2b("6700")); } //In case the TPDU contains a SELECT by DF-Name (AID), we cannot transparently pass on the TPDU through //the OMAPI channel since OMAPI will block such TPDUs for security reasons. The only way to archive a //similar result is to close the existing OMAPI channel to and re-open a new OMAPI channel under the //DF-Name (AID) given in in the TPDU. if (Arrays.equals(Arrays.copyOf(tpdu, 3), Utils.h2b("00A404"))) { //Make sure that the Lc field of the TPDU does not exceed the TPDU length if (tpdu[4] > tpdu.length - 5) { Log.e("PROXY", String.format("SELECT by DF-Name with invalid length field, rejecting TPDU (%s)...\n", Utils.b2h(tpdu))); //see also ISO/IEC 7816-4, table 5 (wrong length; no further indication) return (Utils.h2b("6700")); } //Extract the DF-Name (AID) from the TPDU. byte[] aidReq; if (tpdu[4] > 0) { //The DF-Name (AID) does not have to represent a full AID, a shortened (right truncated) AID //is sufficient (see also ETSI TS 102 221, section 11.1.1.2). aidReq = Arrays.copyOfRange(tpdu, 5, tpdu[4] + 5); } else { //ETSI TS 102 221, section 11.1.1.2 vaguely indicates that the DF-Name (AID) may also be //left out entirely. GlobalPlatform Card Specification 2.1.1, section 9.9.2.3 is more //concrete. According to GlobalPlatform, the ISD shall be selected in case no DF-Name is //supplied. This is also coherent to Open Mobile API Specification – Public Review //v3.2.0.13, section 4.2.7.8. aidReq = new byte[0]; } //Compare the given DF-Name to the AID of the current OMAPI channel. If the DF-Name still matches //the AID of the current OMAPI channel, we stay on the current OMAPI channel. If the DF-Name //references the AID of a different application, we will close the current OMAPI channel and open a //new one. Log.i("PROXY", String.format("Opening new channel for AID (%s) as a replacement for SELECT by DF-Name...\n", Utils.b2h(aidReq))); try { int newOmapiChannel; byte[] response; newOmapiChannel = omapi.open(omapiReader, aidReq, tpdu[3]); response = omapi.getSelRes(newOmapiChannel); Log.i("PROXY", String.format("Opening new channel (%d) for AID (%s) was successful, now closing the old channel (%d)...\n", newOmapiChannel, Utils.b2h(aidReq), omapiChannel)); omapi.close(omapiChannel); omapiChannel = newOmapiChannel; return response; } catch (Exception e) { Log.i("PROXY", String.format("Opening new channel for new AID (%s) was not successful, pretending that the file was not found...\n", Utils.b2h(aidReq))); return (Utils.h2b("6A82")); } } //Block all attempts to manage a channel, this is a feature we do not support here. //(OMAPI also does not support the MANAGE CHANNEL command) if (Arrays.equals(Arrays.copyOf(tpdu, 2), Utils.h2b("0070"))) { Log.e("PROXY", String.format("Rejecting unsupported MANAGE CHANNEL (%s) command...\n", Utils.b2h(tpdu))); //see also ISO/IEC 7816-4, table 6 (logical channel not supported) return (Utils.h2b("6881")); } //Normal APDU/TPDU exchange //Since we are using T=1, we may treat APDUs and TPDUs equal sendMessageInd(MainActivity.IND_APDU_TRAFFIC); try { return omapi.transact(omapiChannel, tpdu); } catch (Exception e) { sendErrorMessage(e); return null; } } } vpcd = new Vpcd(new VpcdCallbackHandler()); executor.execute(new Runnable() { @Override public void run() { try { vpcd.open(remoteHost, remotePort); } catch (Exception e) { Log.e("PROXY", e.getMessage() + "\n"); Message ind = uiHandler.obtainMessage(MainActivity.IND_ERROR, e.getMessage()); ind.sendToTarget(); } uiHandler.post(new Runnable() { @Override public void run() { Message ind = uiHandler.obtainMessage(MainActivity.IND_SHUTDOWN); ind.sendToTarget(); } }); } }); } @Override public void omapiDisconnected() { Log.i("PROXY", "OMAPI layer is shutting down, shutting down VPCD connection as well..."); vpcd.close(); } }