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; private byte[] omapiAid = null; //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; } omapiAid = DEFAULT_AID; 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); omapiAid = DEFAULT_AID; } @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); omapiAid = null; } @Override public byte[] vpcdTransact(byte[] tpdu) { Log.i("PROXY", "Exchanging TPDU...\n"); //In case the TPDU contains a SELECT by DF-Name, which is forbidden by OMAPI by design, we must //find an alternative solution: In case the SELECT targets the currently selected application, //we just use the FID 7FFF, which is an alias for the currently selected application. In cas the //AID is different, we close the OMAPI channel and re-open it with the new AID. If this fails, we //we just pretend that we haven't found the file. if (Arrays.equals(Arrays.copyOf( tpdu,3), Utils.h2b("00A404") ) ) { byte[] aidReq = Arrays.copyOfRange(tpdu, 5, tpdu.length - 1); int compareLength = 0; if (omapiAid != null) { if (aidReq.length < omapiAid.length) compareLength = aidReq.length; else compareLength = omapiAid.length; } if (omapiAid != null && Arrays.equals(Arrays.copyOf(omapiAid, compareLength), Arrays.copyOf(aidReq, compareLength))) { Log.i("PROXY", String.format("Selecting the currently selected ADF (%s), as a replacement for SELECT by DF-Name...\n", Utils.b2h(aidReq))); try { return omapi.transact(omapiChannel, Utils.h2b("00a40004027fff")); } catch (Exception e) { sendErrorMessage(e); } } else { 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); response = omapi.transact(newOmapiChannel, Utils.h2b("00a40004027fff")); 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); omapiAid = aidReq; 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") ) ) { return (Utils.h2b("6D00")); } //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(); } }