package com.ledger.tbase.comm; import android.content.Context; import android.util.Log; import com.btchip.BTChipConstants; import com.btchip.BTChipException; import com.btchip.comm.BTChipTransport; import com.btchip.comm.android.BTChipTransportAndroid; import com.btchip.utils.Dump; import com.btchip.utils.FutureUtils; import com.ledger.wallet.service.ILedgerWalletService; import com.ledger.wallet.service.ServiceResult; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.util.concurrent.Future; public class LedgerTransportTEEProxy implements BTChipTransport { public static final String TAG="LedgerTransportTEEProxy"; private Context context; private ILedgerWalletService service; private byte[] session; private byte[] nvm; private boolean debug; private static final byte PROTOCOL_CARD = (byte)0x01; private static final byte[] APDU_INIT[] = { Dump.hexToBin("D020000038000000000000000118F43F95A217EFEDE0A8D98DAC357E3B2501E79C3958B9D7E15238D43A6807C397680EB805BC0E95E2B65D9E49B1B045"), Dump.hexToBin("D02200002B000000020000000120B25006C589F0DCF1BBB75BAA1542A5E6CF300995F0046DE59CC641C0798D9D489006") }; private static final int SW_OK = 0x9000; private static final int SW_CONDITIONS_NOT_SATISFIED = 0x6985; public LedgerTransportTEEProxy(Context context, ILedgerWalletService service) { this.context = context; this.service = service; nvm = new byte[0]; } public LedgerTransportTEEProxy(Context context) { this(context, null); } public byte[] getNVM() { return nvm; } public void setNVM(byte[] nvm) { this.nvm = nvm; } public void setService(ILedgerWalletService service) { this.service = service; } public ILedgerWalletService getService() { return service; } public boolean init() { ServiceResult result; if (service == null) { Log.d(TAG, "Cannot initialize until service is available"); return false; } try { result = service.openDefault(); } catch(Exception e) { Log.d(TAG, "Failed to open application (internal)", e); return false; } if (result.getExceptionMessage() != null) { Log.d(TAG, "Failed to open application (service) " + result.getExceptionMessage()); return false; } session = result.getResult(); try { result = service.initStorage(session, nvm); } catch(Exception e) { Log.d(TAG, "Failed to initialize NVM (internal)", e); try { close(); } catch(Exception e1) { } return false; } if (result.getExceptionMessage() != null) { Log.d(TAG, "Failed to initialize NVM (service) " + result.getExceptionMessage()); try { close(); } catch(Exception e1) { } return false; } try { for (byte[] apdu : APDU_INIT) { int sw; byte[] response = exchange(apdu).get(); if ((response != null) && (response.length > 2)) { sw = ((response[response.length - 2] & 0xff) << 8) | (response[response.length - 1] & 0xff); if ((sw != SW_OK) && (sw != SW_CONDITIONS_NOT_SATISFIED)) { throw new BTChipException("Invalid response status " + Integer.toHexString(sw)); } } } } catch(Exception e) { try { close(); } catch(Exception e1) { } Log.d(TAG, "Init failed", e); return false; } return true; } private boolean needExternalUI(byte[] commandParam) { // Some commands need to call the Trusted UI : // SETUP, VERIFY PIN, HASH INPUT FINALIZE, HASH INPUT FINALIZE FULL byte ins = commandParam[1]; switch(ins) { case BTChipConstants.BTCHIP_INS_SETUP: case BTChipConstants.BTCHIP_INS_VERIFY_PIN: case BTChipConstants.BTCHIP_INS_HASH_INPUT_FINALIZE: case BTChipConstants.BTCHIP_INS_HASH_INPUT_FINALIZE_FULL: return true; default: return false; } } @Override public Future<byte[]> exchange(byte[] command) throws BTChipException { ServiceResult result; if (debug) { Log.d(BTChipTransportAndroid.LOG_STRING, "=> " + Dump.dump(command)); } if (service == null) { throw new BTChipException("Service is not available"); } if (session == null) { throw new BTChipException("Session is not open"); } try { if (needExternalUI(command)) { result = service.exchangeExtendedUI(session, command); } else { result = service.exchange(session, command); } } catch(Exception e) { throw new BTChipException("Exception calling service", e); } if (result.getExceptionMessage() != null) { Log.d(TAG, "Exchange failed " + result.getExceptionMessage()); return null; } Log.d(BTChipTransportAndroid.LOG_STRING, "<= " + Dump.dump(result.getResult())); return FutureUtils.getDummyFuture(result.getResult()); } @Override public void close() throws BTChipException { if (service == null) { throw new BTChipException("Service is not available"); } if (session == null) { return; } try { service.close(session); } catch(Exception e) { throw new BTChipException("Exception calling service", e); } session = null; } @Override public void setDebug(boolean debugFlag) { this.debug = debugFlag; } public Future<byte[]> requestNVM() throws BTChipException { if (service == null) { throw new BTChipException("Service is not available"); } if (session == null) { throw new BTChipException("Session is not open"); } ServiceResult result; try { result = service.getStorage(session); } catch(Exception e) { throw new BTChipException("Exception calling service", e); } return FutureUtils.getDummyFuture(result.getResult()); } public byte[] loadNVM(String nvmFile) { try { FileInputStream in = context.openFileInput(nvmFile); byte[] nvm = new byte[in.available()]; in.read(nvm); return nvm; } catch(Exception e) { if (e instanceof FileNotFoundException) { Log.d(TAG, "Unable to load NVM"); } else { Log.d(TAG, "Unable to load NVM", e); } return null; } } public void writeNVM(String nvmFile, byte[] nvm) throws BTChipException { try { FileOutputStream out = context.openFileOutput(nvmFile, Context.MODE_PRIVATE); out.write(nvm); out.flush(); out.close(); } catch(Exception e) { throw new BTChipException("Unable to write NVM", e); } } }