diff --git a/app/build.gradle b/app/build.gradle index eb6d673..6e55802 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,6 +24,7 @@ dependencies { implementation 'com.android.support.constraint:constraint-layout:1.1.2' implementation 'com.madgag.spongycastle:core:1.58.0.0' implementation 'com.madgag.spongycastle:prov:1.58.0.0' + implementation 'com.github.status-im:hardwallet-lite-android:059125a' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' diff --git a/app/src/main/assets/wallet.cap b/app/src/main/assets/wallet.cap index c0d73ca..73bd2fa 100644 Binary files a/app/src/main/assets/wallet.cap and b/app/src/main/assets/wallet.cap differ diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/APDUCommand.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/APDUCommand.java deleted file mode 100644 index 854d4b5..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/APDUCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -package im.status.applet_installer_test.appletinstaller; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -public class APDUCommand { - protected int cla; - protected int ins; - protected int p1; - protected int p2; - protected int lc; - protected byte[] data; - protected boolean needsLE; - - public APDUCommand(int cla, int ins, int p1, int p2, byte[] data) { - this(cla, ins, p1, p2, data, false); - } - - public APDUCommand(int cla, int ins, int p1, int p2, byte[] data, boolean needsLE) { - this.cla = cla; - this.ins = ins; - this.p1 = p1; - this.p2 = p2; - this.data = data; - this.needsLE = needsLE; - } - - public byte[] serialize() throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - out.write(this.cla); - out.write(this.ins); - out.write(this.p1); - out.write(this.p2); - out.write(this.data.length); - out.write(this.data); - - if (this.needsLE) { - out.write(0); // Response length - } - - return out.toByteArray(); - } - - public int getCla() { - return cla; - } - - public int getIns() { - return ins; - } - - public int getP1() { - return p1; - } - - public int getP2() { - return p2; - } - - public byte[] getData() { - return data; - } - - public boolean getNeedsLE() { - return this.needsLE; - } -} diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/APDUException.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/APDUException.java deleted file mode 100644 index 537814c..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/APDUException.java +++ /dev/null @@ -1,15 +0,0 @@ -package im.status.applet_installer_test.appletinstaller; - -public class APDUException extends Exception { - public final int sw; - - public APDUException(int sw, String message) { - super(message + ", 0x" + String.format("%04X", sw)); - this.sw = sw; - } - - public APDUException(String message) { - super(message); - this.sw = 0; - } -} diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/APDUResponse.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/APDUResponse.java deleted file mode 100644 index e78670f..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/APDUResponse.java +++ /dev/null @@ -1,69 +0,0 @@ -package im.status.applet_installer_test.appletinstaller; - -import java.io.IOException; - -public class APDUResponse { - public static int SW_OK = 0x9000; - public static int SW_SECURITY_CONDITION_NOT_SATISFIED = 0x6982; - public static int SW_AUTHENTICATION_METHOD_BLOCKED = 0x6983; - public static int SW_CARD_LOCKED = 0x6283; - public static int SW_REFERENCED_DATA_NOT_FOUND = 0x6A88; - public static int SW_CONDITIONS_OF_USE_NOT_SATISFIED = 0x6985; // applet may be already installed - - private byte[] apdu; - private byte[] data; - private int sw; - private int sw1; - private int sw2; - - public APDUResponse(byte[] apdu) { - if (apdu.length < 2) { - throw new IllegalArgumentException("APDU response must be at least 2 bytes"); - } - this.apdu = apdu; - this.parse(); - } - - private void parse() { - int length = this.apdu.length; - - this.sw1 = this.apdu[length - 2] & 0xff; - this.sw2 = this.apdu[length - 1] & 0xff; - this.sw = (this.sw1 << 8) | this.sw2; - - this.data = new byte[length - 2]; - System.arraycopy(this.apdu, 0, this.data, 0, length - 2); - } - - public boolean isOK() { - return this.sw == SW_OK; - } - - public APDUResponse checkOK() throws APDUException { - if (!isOK()) { - throw new APDUException(this.getSw(), "Unexpected error SW"); - } - - return this; - } - - public byte[] getData() { - return this.data; - } - - public int getSw() { - return this.sw; - } - - public int getSw1() { - return this.sw1; - } - - public int getSw2() { - return this.sw2; - } - - public byte[] getBytes() { - return this.apdu; - } -} diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/APDUWrapper.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/APDUWrapper.java index 09d9635..8ebf01a 100644 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/APDUWrapper.java +++ b/app/src/main/java/im/status/applet_installer_test/appletinstaller/APDUWrapper.java @@ -1,6 +1,8 @@ package im.status.applet_installer_test.appletinstaller; +import im.status.hardwallet_lite_android.io.APDUCommand; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.reflect.Array; diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/CardManager.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/ActionRunner.java similarity index 56% rename from app/src/main/java/im/status/applet_installer_test/appletinstaller/CardManager.java rename to app/src/main/java/im/status/applet_installer_test/appletinstaller/ActionRunner.java index 7c8c900..a13344b 100644 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/CardManager.java +++ b/app/src/main/java/im/status/applet_installer_test/appletinstaller/ActionRunner.java @@ -4,34 +4,31 @@ import android.content.res.AssetManager; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.tech.IsoDep; +import im.status.hardwallet_lite_android.io.APDUException; +import im.status.hardwallet_lite_android.io.CardChannel; +import im.status.hardwallet_lite_android.io.OnCardConnectedListener; import java.io.IOException; +import java.util.Timer; +import java.util.TimerTask; -public class CardManager extends Thread implements NfcAdapter.ReaderCallback { +public class ActionRunner implements OnCardConnectedListener { public final static int ACTION_NONE = 0; public final static int ACTION_INSTALL = 1; public final static int ACTION_INSTALL_TEST = 2; public final static int ACTION_PERFTEST = 3; - private NfcAdapter nfcAdapter; private AssetManager assets; private String capPath; - private IsoDep isoDep; private int requestedAction; - private long cardConnectedAt; - private boolean running; - public CardManager(NfcAdapter nfcAdapter, AssetManager assets, String capPath) { - this.nfcAdapter = nfcAdapter; + + public ActionRunner(AssetManager assets, String capPath) { this.assets = assets; this.capPath = capPath; this.requestedAction = ACTION_NONE; } - public boolean isConnected() { - return this.isoDep != null && this.isoDep.isConnected(); - } - public void requestAction(int actionRequested) { switch(actionRequested) { case ACTION_NONE: @@ -54,68 +51,13 @@ public class CardManager extends Thread implements NfcAdapter.ReaderCallback { this.requestedAction = actionRequested; } - @Override - public void onTagDiscovered(Tag tag) { - this.isoDep = IsoDep.get(tag); - try { - this.isoDep = IsoDep.get(tag); - this.isoDep.connect(); - this.isoDep.setTimeout(120000); - } catch (IOException e) { - Logger.e("error connecting to tag"); - } - } - - public void run() { - boolean connected = this.isConnected(); - - while(true) { - boolean newConnected = this.isConnected(); - if (newConnected != connected) { - connected = newConnected; - Logger.i("tag " + (connected ? "connected" : "disconnected")); - if (connected) { - this.onCardConnected(); - } else { - this.onCardDisconnected(); - } - } - - if (connected && (this.requestedAction != ACTION_NONE) && !this.running) { - long now = System.currentTimeMillis(); - if (now - this.cardConnectedAt > 2000) { - this.perform(); - } - } - - try { - this.sleep(50); - } catch (InterruptedException e) { - Logger.e("error in TagManager thread: " + e.getMessage()); - this.interrupt(); - } - } - } - - private void onCardConnected() { - this.cardConnectedAt = System.currentTimeMillis(); - if (this.requestedAction != ACTION_NONE) { - Logger.i("waiting 2 seconds to start requested action"); - } else { - Logger.i("no action requested yet"); - } - } - - private void onCardDisconnected() { - this.cardConnectedAt = 0; - this.isoDep = null; - } - - private void perform() { + private void perform(CardChannel ch) { Logger.i("starting requested action"); - this.running = true; try { - CardChannel ch = new CardChannel(this.isoDep); + if (!ch.isConnected()) { + Logger.i("tag disconnected"); + return; + } switch (requestedAction) { case ACTION_INSTALL: @@ -141,9 +83,22 @@ public class CardManager extends Thread implements NfcAdapter.ReaderCallback { } catch (Exception e) { Logger.e("Other exception: " + e.getMessage()); } finally { - this.running = false; this.requestedAction = ACTION_NONE; - this.cardConnectedAt = 0; + } + } + + @Override + public void onConnected(final CardChannel channel) { + if (this.requestedAction != ACTION_NONE) { + Logger.i("waiting 2 seconds to start requested action"); + new Timer().schedule(new TimerTask() { + @Override + public void run() { + perform(channel); + } + }, 2000); + } else { + Logger.i("no action requested yet"); } } } diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/CardChannel.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/CardChannel.java deleted file mode 100644 index d92915a..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/CardChannel.java +++ /dev/null @@ -1,23 +0,0 @@ -package im.status.applet_installer_test.appletinstaller; - -import android.nfc.tech.IsoDep; - -import java.io.IOException; - -import im.status.applet_installer_test.appletinstaller.CardManager; - -public class CardChannel implements Channel { - private IsoDep isoDep; - - public CardChannel(IsoDep isoDep) { - this.isoDep = isoDep; - } - - public APDUResponse send(APDUCommand cmd) throws IOException { - byte[] apdu = cmd.serialize(); - Logger.d(String.format("COMMAND %s", HexUtils.byteArrayToHexString(apdu))); - byte[] resp = this.isoDep.transceive(apdu); - Logger.d(String.format("RESPONSE %s %n-----------------------", HexUtils.byteArrayToHexString(resp))); - return new APDUResponse(resp); - } -} diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/Channel.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/Channel.java deleted file mode 100644 index 4caf7d5..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/Channel.java +++ /dev/null @@ -1,7 +0,0 @@ -package im.status.applet_installer_test.appletinstaller; - -import java.io.IOException; - -public interface Channel { - APDUResponse send(APDUCommand cmd) throws IOException; -} diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/Installer.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/Installer.java index f136c5e..341e6f7 100644 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/Installer.java +++ b/app/src/main/java/im/status/applet_installer_test/appletinstaller/Installer.java @@ -2,18 +2,21 @@ package im.status.applet_installer_test.appletinstaller; import android.content.res.AssetManager; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; import im.status.applet_installer_test.appletinstaller.apducommands.*; +import im.status.hardwallet_lite_android.io.APDUCommand; +import im.status.hardwallet_lite_android.io.APDUException; +import im.status.hardwallet_lite_android.io.APDUResponse; +import im.status.hardwallet_lite_android.io.CardChannel; +import im.status.hardwallet_lite_android.wallet.WalletAppletCommandSet; public class Installer { - private Channel plainChannel; - private Channel channel; + private CardChannel plainChannel; + private SecureChannel channel; private Keys cardKeys; private AssetManager assets; private String capPath; @@ -21,9 +24,8 @@ public class Installer { static final byte[] cardKeyData = HexUtils.hexStringToByteArray("404142434445464748494a4b4c4d4e4f"); - public Installer(Channel channel, AssetManager assets, String capPath, boolean testSecrets) { + public Installer(CardChannel channel, AssetManager assets, String capPath, boolean testSecrets) { this.plainChannel = channel; - this.channel = channel; this.cardKeys = new Keys(cardKeyData, cardKeyData); this.assets = assets; this.capPath = capPath; @@ -47,7 +49,7 @@ public class Installer { Session session = init.verifyResponse(this.cardKeys, resp); Keys sessionKeys = session.getKeys(); - this.channel = new SecureChannel(this.channel, sessionKeys); + this.channel = new SecureChannel(this.plainChannel, sessionKeys); ExternalAuthenticate auth = new ExternalAuthenticate(sessionKeys.getEncKeyData(), session.getCardChallenge(), hostChallenge); resp = this.send("external auth", auth.getCommand()); @@ -87,7 +89,7 @@ public class Installer { APDUCommand loadCmd; while((loadCmd = load.getCommand()) != null) { - this.send("load " + load.getCount() + "/37", loadCmd); + this.send("load " + load.getCount() + "/35", loadCmd); } InstallForInstall installNDEF = new InstallForInstall(packageAID, ndefAppletAID, ndefInstanceAID, new byte[0]); @@ -96,27 +98,32 @@ public class Installer { InstallForInstall install = new InstallForInstall(packageAID, walletAID, walletAID, new byte[0]); this.send("perform and make selectable (wallet)", install.getCommand()); - installSecrets(); + personalizeApplet(); long duration = System.currentTimeMillis() - startTime; Logger.i(String.format("installation completed in %d seconds", duration / 1000)); } - private void installSecrets() throws NoSuchAlgorithmException, InvalidKeySpecException, APDUException, IOException { + private void personalizeApplet() throws NoSuchAlgorithmException, InvalidKeySpecException, APDUException, IOException { Secrets secrets = testSecrets ? Secrets.testSecrets() : Secrets.generate(); - WalletAppletCommandSet cmdSet = new WalletAppletCommandSet((CardChannel) this.plainChannel); - byte[] ecKey = cmdSet.select().checkOK().getData(); - SecureChannelSession secureChannel = new SecureChannelSession(Arrays.copyOfRange(ecKey, 2, ecKey.length)); - cmdSet.setSecureChannel(secureChannel); + WalletAppletCommandSet cmdSet = new WalletAppletCommandSet(this.plainChannel); + cmdSet.select().checkOK(); cmdSet.init(secrets.getPin(), secrets.getPuk(), secrets.getPairingToken()).checkOK(); + cmdSet.select().checkOK(); + cmdSet.autoPair(secrets.getPairingPassword()); + cmdSet.autoOpenSecureChannel(); + cmdSet.verifyPIN(secrets.getPin()).checkOK(); + cmdSet.setNDEF(HexUtils.hexStringToByteArray("0024d40f12616e64726f69642e636f6d3a706b67696d2e7374617475732e657468657265756d")).checkOK(); + cmdSet.autoUnpair(); + Logger.i(String.format("PIN: %s\nPUK: %s\nPairing password: %s\nPairing token: %s", secrets.getPin(), secrets.getPuk(), secrets.getPairingPassword(), HexUtils.byteArrayToHexString(secrets.getPairingToken()))); } private APDUResponse send(String description, APDUCommand cmd) throws IOException, APDUException { Logger.d("sending command " + description); - APDUResponse resp = this.channel.send(cmd); + APDUResponse resp = this.channel == null ? this.plainChannel.send(cmd) : this.channel.send(cmd); if(resp.getSw() == APDUResponse.SW_SECURITY_CONDITION_NOT_SATISFIED) { Logger.e("SW_SECURITY_CONDITION_NOT_SATISFIED: card might be blocked"); diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/MainActivity.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/MainActivity.java index 04d7d2b..173671f 100644 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/MainActivity.java +++ b/app/src/main/java/im/status/applet_installer_test/appletinstaller/MainActivity.java @@ -5,11 +5,12 @@ import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.nfc.NfcAdapter; import android.text.method.ScrollingMovementMethod; -import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ScrollView; import android.widget.TextView; +import im.status.hardwallet_lite_android.io.CardManager; + import java.security.Security; public class MainActivity extends AppCompatActivity implements UILogger { @@ -24,6 +25,7 @@ public class MainActivity extends AppCompatActivity implements UILogger { private Button buttonInstall; private Button buttonInstallTest; private Button buttonPerfTest; + private ActionRunner actionRunner; private CardManager cardManager; @Override @@ -34,8 +36,10 @@ public class MainActivity extends AppCompatActivity implements UILogger { Logger.setUILogger(this); AssetManager assets = this.getAssets(); - this.cardManager = new CardManager(nfcAdapter, assets, "wallet.cap"); - this.cardManager.start(); + this.actionRunner = new ActionRunner(assets, "wallet.cap"); + this.cardManager = new CardManager(); + this.cardManager.setOnCardConnectedListener(this.actionRunner); + cardManager.start(); textViewScroll = (ScrollView) findViewById(R.id.textViewScroll); @@ -46,21 +50,21 @@ public class MainActivity extends AppCompatActivity implements UILogger { buttonInstall.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - requestAction(CardManager.ACTION_INSTALL); + requestAction(ActionRunner.ACTION_INSTALL); } }); buttonInstallTest = (Button) findViewById(R.id.buttonInstallTest); buttonInstallTest.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - requestAction(CardManager.ACTION_INSTALL_TEST); + requestAction(ActionRunner.ACTION_INSTALL_TEST); } }); buttonPerfTest = (Button) findViewById(R.id.buttonPerfTest); buttonPerfTest.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - requestAction(CardManager.ACTION_PERFTEST); + requestAction(ActionRunner.ACTION_PERFTEST); } }); @@ -78,9 +82,9 @@ public class MainActivity extends AppCompatActivity implements UILogger { } private void requestAction(int action) { - if (this.cardManager != null) { + if (this.actionRunner != null) { clearTextView(); - this.cardManager.requestAction(action); + this.actionRunner.requestAction(action); } } diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/PerfTest.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/PerfTest.java index 95c51cb..ffbb6af 100644 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/PerfTest.java +++ b/app/src/main/java/im/status/applet_installer_test/appletinstaller/PerfTest.java @@ -2,33 +2,22 @@ package im.status.applet_installer_test.appletinstaller; import android.util.Log; -import im.status.applet_installer_test.appletinstaller.apducommands.SecureChannelSession; -import im.status.applet_installer_test.appletinstaller.apducommands.WalletAppletCommandSet; -import org.spongycastle.asn1.ASN1InputStream; -import org.spongycastle.asn1.ASN1Integer; -import org.spongycastle.asn1.DLSequence; +import im.status.hardwallet_lite_android.io.CardChannel; +import im.status.hardwallet_lite_android.wallet.WalletAppletCommandSet; import org.spongycastle.asn1.x9.X9ECParameters; import org.spongycastle.crypto.ec.CustomNamedCurves; import org.spongycastle.crypto.params.ECDomainParameters; -import org.spongycastle.crypto.params.ECPublicKeyParameters; -import org.spongycastle.crypto.signers.ECDSASigner; import org.spongycastle.jce.ECNamedCurveTable; import org.spongycastle.jce.spec.ECParameterSpec; -import org.spongycastle.math.ec.ECPoint; import org.spongycastle.math.ec.FixedPointUtil; -import java.io.IOException; -import java.math.BigInteger; import java.security.KeyPair; import java.security.KeyPairGenerator; -import java.security.MessageDigest; -import java.util.Arrays; import java.util.Random; public class PerfTest { private CardChannel cardChannel; private WalletAppletCommandSet cmdSet; - private SecureChannelSession secureChannel; private long openSecureChannelTime = 0; private long loadKeysTime = 0; @@ -42,42 +31,19 @@ public class PerfTest { static { FixedPointUtil.precompute(CURVE_PARAMS.getG(), 12); CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(), CURVE_PARAMS.getH()); - - byte[] tmp; - - try { - tmp = Crypto.generatePairingKey(new char[] {'W', 'a', 'l', 'l', 'e', 't', 'A','p', 'p', 'l', 'e', 't', 'T', 'e', 's', 't'}); - } catch (Exception e) { - tmp = null; - } - - SHARED_SECRET = tmp; } - static final byte DERIVE_P1_SOURCE_MASTER = (byte) 0x00; - static final byte DERIVE_P1_SOURCE_PARENT = (byte) 0x40; - static final byte DERIVE_P1_SOURCE_CURRENT = (byte) 0x80; - static final byte EXPORT_KEY_P1_HIGH = 0x01; - static final byte SIGN_P1_PRECOMPUTED_HASH = 0x01; - static final byte GET_STATUS_P1_APPLICATION = 0x00; - static final byte GET_STATUS_P1_KEY_PATH = 0x01; - // m/44'/60'/0'/0/0 static final byte[] BIP44_PATH = new byte[] { (byte) 0x80, 0x00, 0x00, 0x2c, (byte) 0x80, 0x00, 0x00, 0x3c, (byte) 0x80, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}; - // TODO: Make this an input - public static final byte[] SHARED_SECRET; - public PerfTest(CardChannel cardChannel) { this.cardChannel = cardChannel; } public void test() throws Exception { cmdSet = new WalletAppletCommandSet(cardChannel); - byte[] keyData = extractPublicKeyFromSelect(cmdSet.select().getData()); - secureChannel = new SecureChannelSession(keyData); - cmdSet.setSecureChannel(secureChannel); - cmdSet.autoPair(SHARED_SECRET); + cmdSet.select().checkOK(); + cmdSet.autoPair(Secrets.testSecrets().getPairingToken()); openSecureChannelTime = System.currentTimeMillis(); cmdSet.autoOpenSecureChannel(); openSecureChannelTime = System.currentTimeMillis() - openSecureChannelTime; @@ -97,7 +63,7 @@ public class PerfTest { } Logger.i("Reenabling logging."); - cmdSet.select(); + cmdSet.select().checkOK(); cmdSet.autoOpenSecureChannel(); cmdSet.verifyPIN("000000").checkOK(); cmdSet.autoUnpair(); @@ -113,21 +79,21 @@ public class PerfTest { private void getStatus() throws Exception { long time = System.currentTimeMillis(); - cmdSet.select(); + cmdSet.select().checkOK(); cmdSet.autoOpenSecureChannel(); - cmdSet.getStatus(GET_STATUS_P1_APPLICATION).checkOK(); + cmdSet.getStatus(WalletAppletCommandSet.GET_STATUS_P1_APPLICATION).checkOK(); getStatusTime = System.currentTimeMillis() - time; } private void login() throws Exception { long time = System.currentTimeMillis(); - cmdSet.select(); + cmdSet.select().checkOK(); cmdSet.autoOpenSecureChannel(); cmdSet.verifyPIN("000000").checkOK(); - cmdSet.deriveKey(new byte[] { (byte) 0xC0, 0x00, 0x00, 0x00}, DERIVE_P1_SOURCE_PARENT, false, false).checkOK(); - cmdSet.exportKey(EXPORT_KEY_P1_HIGH, false).checkOK(); - cmdSet.deriveKey(new byte[] { (byte) 0xC0, 0x00, 0x00, 0x01}, DERIVE_P1_SOURCE_PARENT, false, false).checkOK(); - cmdSet.exportKey(EXPORT_KEY_P1_HIGH, false).checkOK(); + cmdSet.deriveKey(new byte[] { (byte) 0xC0, 0x00, 0x00, 0x00}, WalletAppletCommandSet.DERIVE_P1_SOURCE_PARENT).checkOK(); + cmdSet.exportKey(WalletAppletCommandSet.EXPORT_KEY_P1_HIGH, false).checkOK(); + cmdSet.deriveKey(new byte[] { (byte) 0xC0, 0x00, 0x00, 0x01}, WalletAppletCommandSet.DERIVE_P1_SOURCE_PARENT).checkOK(); + cmdSet.exportKey(WalletAppletCommandSet.EXPORT_KEY_P1_HIGH, false).checkOK(); loginTime = System.currentTimeMillis() - time; } @@ -140,19 +106,19 @@ public class PerfTest { cmdSet.loadKey(keyPair, false, chainCode).checkOK(); long time = System.currentTimeMillis(); - cmdSet.deriveKey(BIP44_PATH, DERIVE_P1_SOURCE_CURRENT, false, false).checkOK(); + cmdSet.deriveKey(BIP44_PATH, WalletAppletCommandSet.DERIVE_P1_SOURCE_CURRENT).checkOK(); loadKeysTime = System.currentTimeMillis() - time; } private void signTransactions() throws Exception { long time = System.currentTimeMillis(); - cmdSet.select(); + cmdSet.select().checkOK(); cmdSet.autoOpenSecureChannel(); cmdSet.verifyPIN("000000").checkOK(); deriveKeyFromParent = System.currentTimeMillis(); - cmdSet.deriveKey(new byte[] { (byte) 0x00, 0x00, 0x00, 0x00}, DERIVE_P1_SOURCE_PARENT, false, false).checkOK(); + cmdSet.deriveKey(new byte[] { (byte) 0x00, 0x00, 0x00, 0x00}, WalletAppletCommandSet.DERIVE_P1_SOURCE_PARENT).checkOK(); deriveKeyFromParent = System.currentTimeMillis() - deriveKeyFromParent; - cmdSet.sign("any32bytescanbeahashyouknowthat!".getBytes(), SIGN_P1_PRECOMPUTED_HASH, true, true).checkOK(); + cmdSet.sign("any32bytescanbeahashyouknowthat!".getBytes()).checkOK(); signTime = System.currentTimeMillis() - time; } @@ -163,73 +129,4 @@ public class PerfTest { return g; } - - private byte[] extractPublicKeyFromSelect(byte[] select) { - return Arrays.copyOfRange(select, 22, 22 + select[21]); - } - - private byte[] derivePublicKey(byte[] data) throws Exception { - byte[] pubKey = Arrays.copyOfRange(data, 3, 4 + data[3]); - byte[] signature = Arrays.copyOfRange(data, 4 + data[3], data.length); - byte[] hash = MessageDigest.getInstance("SHA256").digest("STATUS KEY DERIVATION".getBytes()); - - pubKey[0] = 0x02; - - ECPoint candidate = CURVE.getCurve().decodePoint(pubKey); - if (!verifySig(hash, signature, candidate)) { - pubKey[0] = 0x03; - candidate = CURVE.getCurve().decodePoint(pubKey); - if (!verifySig(hash, signature, candidate)) { - throw new Exception("Public key is incorrect"); - } - } - - return candidate.getEncoded(false); - } - - private boolean verifySig(byte[] hash, byte[] signature, ECPoint pub) { - ECDSASigner signer = new ECDSASigner(); - ECPublicKeyParameters params = new ECPublicKeyParameters(pub, CURVE); - signer.init(false, params); - ECDSASignature sig = ECDSASignature.decodeFromDER(signature); - return signer.verifySignature(hash, sig.r, sig.s); - } - - static class ECDSASignature { - /** The two components of the signature. */ - public final BigInteger r, s; - - /** - * Constructs a signature with the given components. Does NOT automatically canonicalise the signature. - */ - public ECDSASignature(BigInteger r, BigInteger s) { - this.r = r; - this.s = s; - } - - public static ECDSASignature decodeFromDER(byte[] bytes) { - ASN1InputStream decoder = null; - try { - decoder = new ASN1InputStream(bytes); - DLSequence seq = (DLSequence) decoder.readObject(); - if (seq == null) - throw new RuntimeException("Reached past end of ASN.1 stream."); - ASN1Integer r, s; - try { - r = (ASN1Integer) seq.getObjectAt(0); - s = (ASN1Integer) seq.getObjectAt(1); - } catch (ClassCastException e) { - throw new IllegalArgumentException(e); - } - // OpenSSL deviates from the DER spec by interpreting these values as unsigned, though they should not be - // Thus, we always use the positive versions. See: http://r6.ca/blog/20111119T211504Z.html - return new ECDSASignature(r.getPositiveValue(), s.getPositiveValue()); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (decoder != null) - try { decoder.close(); } catch (IOException x) {} - } - } - } } diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/SecureChannel.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/SecureChannel.java index 84530f4..dc22c76 100644 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/SecureChannel.java +++ b/app/src/main/java/im/status/applet_installer_test/appletinstaller/SecureChannel.java @@ -1,14 +1,16 @@ package im.status.applet_installer_test.appletinstaller; -import android.provider.Settings; +import im.status.hardwallet_lite_android.io.APDUCommand; +import im.status.hardwallet_lite_android.io.APDUResponse; +import im.status.hardwallet_lite_android.io.CardChannel; import java.io.IOException; -public class SecureChannel implements Channel { - private Channel channel; +public class SecureChannel { + private CardChannel channel; private APDUWrapper wrapper; - public SecureChannel(Channel channel, Keys keys) { + public SecureChannel(CardChannel channel, Keys keys) { this.channel = channel; this.wrapper = new APDUWrapper(keys.getMacKeyData()); } diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Delete.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Delete.java index 62117b6..397faa1 100644 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Delete.java +++ b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Delete.java @@ -1,6 +1,6 @@ package im.status.applet_installer_test.appletinstaller.apducommands; -import im.status.applet_installer_test.appletinstaller.APDUCommand; +import im.status.hardwallet_lite_android.io.APDUCommand; public class Delete { private static final int CLA = 0x80; diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/ExternalAuthenticate.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/ExternalAuthenticate.java index 1e4858c..c96df1a 100644 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/ExternalAuthenticate.java +++ b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/ExternalAuthenticate.java @@ -1,8 +1,8 @@ package im.status.applet_installer_test.appletinstaller.apducommands; -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.APDUResponse; import im.status.applet_installer_test.appletinstaller.Crypto; +import im.status.hardwallet_lite_android.io.APDUCommand; +import im.status.hardwallet_lite_android.io.APDUResponse; public class ExternalAuthenticate { public static int CLA = 0x84; diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdate.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdate.java index db75714..df5cd44 100644 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdate.java +++ b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdate.java @@ -1,15 +1,11 @@ package im.status.applet_installer_test.appletinstaller.apducommands; -import java.security.SecureRandom; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.APDUException; -import im.status.applet_installer_test.appletinstaller.APDUResponse; import im.status.applet_installer_test.appletinstaller.Crypto; -import im.status.applet_installer_test.appletinstaller.HexUtils; import im.status.applet_installer_test.appletinstaller.Keys; -import im.status.applet_installer_test.appletinstaller.Logger; import im.status.applet_installer_test.appletinstaller.Session; +import im.status.hardwallet_lite_android.io.APDUCommand; +import im.status.hardwallet_lite_android.io.APDUException; +import im.status.hardwallet_lite_android.io.APDUResponse; public class InitializeUpdate { public static final int CLA = 0x80; diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstall.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstall.java index 88a7846..11f8882 100644 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstall.java +++ b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstall.java @@ -1,10 +1,10 @@ package im.status.applet_installer_test.appletinstaller.apducommands; +import im.status.hardwallet_lite_android.io.APDUCommand; + import java.io.ByteArrayOutputStream; import java.io.IOException; -import im.status.applet_installer_test.appletinstaller.APDUCommand; - public class InstallForInstall { public static final int CLA = 0x80; public static final int INS = 0xE6; diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoad.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoad.java index 7e13622..4da8a1e 100644 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoad.java +++ b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoad.java @@ -1,10 +1,10 @@ package im.status.applet_installer_test.appletinstaller.apducommands; +import im.status.hardwallet_lite_android.io.APDUCommand; + import java.io.ByteArrayOutputStream; import java.io.IOException; -import im.status.applet_installer_test.appletinstaller.APDUCommand; - public class InstallForLoad { public static final int CLA = 0x80; public static final int INS = 0xE6; diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Load.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Load.java index a14d85e..9168505 100644 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Load.java +++ b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Load.java @@ -1,8 +1,8 @@ package im.status.applet_installer_test.appletinstaller.apducommands; -import java.io.ByteArrayInputStream; +import im.status.hardwallet_lite_android.io.APDUCommand; + import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -11,9 +11,6 @@ import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.HexUtils; - public class Load { public static final int CLA = 0x80; public static final int INS = 0xE8; diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/SecureChannelSession.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/SecureChannelSession.java deleted file mode 100644 index 15f984d..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/SecureChannelSession.java +++ /dev/null @@ -1,474 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import im.status.applet_installer_test.appletinstaller.*; -import org.spongycastle.crypto.engines.AESEngine; -import org.spongycastle.crypto.macs.CBCBlockCipherMac; -import org.spongycastle.crypto.params.KeyParameter; -import org.spongycastle.jce.ECNamedCurveTable; -import org.spongycastle.jce.interfaces.ECPublicKey; -import org.spongycastle.jce.spec.ECParameterSpec; -import org.spongycastle.jce.spec.ECPublicKeySpec; - -import javax.crypto.Cipher; -import javax.crypto.KeyAgreement; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -import java.io.IOException; -import java.security.*; -import java.util.Arrays; - -/** - * Handles a SecureChannel session with the card. - */ -public class SecureChannelSession { - public static final short SC_SECRET_LENGTH = 32; - public static final short SC_BLOCK_SIZE = 16; - - public static final byte INS_OPEN_SECURE_CHANNEL = 0x10; - public static final byte INS_MUTUALLY_AUTHENTICATE = 0x11; - public static final byte INS_PAIR = 0x12; - public static final byte INS_UNPAIR = 0x13; - - public static final byte PAIR_P1_FIRST_STEP = 0x00; - public static final byte PAIR_P1_LAST_STEP = 0x01; - - public static final int PAYLOAD_MAX_SIZE = 223; - - static final byte PAIRING_MAX_CLIENT_COUNT = 5; - - - private byte[] secret; - private byte[] publicKey; - private byte[] pairingKey; - private byte[] iv; - private byte pairingIndex; - private Cipher sessionCipher; - private CBCBlockCipherMac sessionMac; - private SecretKeySpec sessionEncKey; - private KeyParameter sessionMacKey; - private SecureRandom random; - private boolean open; - - /** - * Constructs a SecureChannel session on the client. The client should generate a fresh key pair for each session. - * The public key of the card is used as input for the EC-DH algorithm. The output is stored as the secret. - * - * @param keyData the public key returned by the applet as response to the SELECT command - */ - public SecureChannelSession(byte[] keyData) { - random = new SecureRandom(); - generateSecret(keyData); - open = false; - } - - public void generateSecret(byte[] keyData) { - try { - random = new SecureRandom(); - ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1"); - KeyPairGenerator g = KeyPairGenerator.getInstance("ECDH"); - g.initialize(ecSpec, random); - - KeyPair keyPair = g.generateKeyPair(); - - publicKey = ((ECPublicKey) keyPair.getPublic()).getQ().getEncoded(false); - KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH"); - keyAgreement.init(keyPair.getPrivate()); - - ECPublicKeySpec cardKeySpec = new ECPublicKeySpec(ecSpec.getCurve().decodePoint(keyData), ecSpec); - ECPublicKey cardKey = (ECPublicKey) KeyFactory.getInstance("ECDSA").generatePublic(cardKeySpec); - - keyAgreement.doPhase(cardKey, true); - secret = keyAgreement.generateSecret(); - } catch (Exception e) { - throw new RuntimeException("Is BouncyCastle in the classpath?", e); - } - } - - /** - * Returns the public key - * @return the public key - */ - public byte[] getPublicKey() { - return publicKey; - } - - /** - * Returns the pairing index - * @return the pairing index - */ - public byte getPairingIndex() { - return pairingIndex; - } - - /** - * Establishes a Secure Channel with the card. The command parameters are the public key generated in the first step. - * Follows the specifications from the SECURE_CHANNEL.md document. - * - * @param apduChannel the apdu channel - * @return the card response - * @throws IOException communication error - */ - public void autoOpenSecureChannel(CardChannel apduChannel) throws IOException { - APDUResponse response = openSecureChannel(apduChannel, pairingIndex, publicKey); - - if (response.getSw() != 0x9000) { - throw new IOException("OPEN SECURE CHANNEL failed"); - } - - processOpenSecureChannelResponse(response); - - response = mutuallyAuthenticate(apduChannel); - - if (response.getSw() != 0x9000) { - throw new IOException("MUTUALLY AUTHENTICATE failed"); - } - - if(!verifyMutuallyAuthenticateResponse(response)) { - throw new IOException("Invalid authentication data from the card"); - } - } - - /** - * Processes the response from OPEN SECURE CHANNEL. This initialize the session keys, Cipher and MAC internally. - * - * @param response the card response - */ - public void processOpenSecureChannelResponse(APDUResponse response) { - try { - MessageDigest md = MessageDigest.getInstance("SHA512"); - md.update(secret); - md.update(pairingKey); - byte[] data = response.getData(); - byte[] keyData = md.digest(Arrays.copyOf(data, SC_SECRET_LENGTH)); - iv = Arrays.copyOfRange(data, SC_SECRET_LENGTH, data.length); - - sessionEncKey = new SecretKeySpec(Arrays.copyOf(keyData, SC_SECRET_LENGTH), "AES"); - sessionMacKey = new KeyParameter(keyData, SC_SECRET_LENGTH, SC_SECRET_LENGTH); - sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding"); - sessionMac = new CBCBlockCipherMac(new AESEngine(), 128, null); - open = true; - } catch(Exception e) { - throw new RuntimeException("Is BouncyCastle in the classpath?", e); - } - } - - /** - * Verify that the response from MUTUALLY AUTHENTICATE is correct. - * - * @param response the card response - * @return true if response is correct, false otherwise - */ - public boolean verifyMutuallyAuthenticateResponse(APDUResponse response) { - return response.getData().length == SC_SECRET_LENGTH; - } - - /** - * Handles the entire pairing procedure in order to be able to use the secure channel - * - * @param apduChannel the apdu channel - * @throws IOException communication error - */ - public void autoPair(CardChannel apduChannel, byte[] sharedSecret) throws IOException { - byte[] challenge = new byte[32]; - random.nextBytes(challenge); - APDUResponse resp = pair(apduChannel, PAIR_P1_FIRST_STEP, challenge); - - if (resp.getSw() != 0x9000) { - throw new IOException("Pairing failed on step 1"); - } - - byte[] respData = resp.getData(); - byte[] cardCryptogram = Arrays.copyOf(respData, 32); - byte[] cardChallenge = Arrays.copyOfRange(respData, 32, respData.length); - byte[] checkCryptogram; - - MessageDigest md; - - try { - md = MessageDigest.getInstance("SHA256"); - } catch(Exception e) { - throw new RuntimeException("Is BouncyCastle in the classpath?", e); - } - - md.update(sharedSecret); - checkCryptogram = md.digest(challenge); - - if (!Arrays.equals(checkCryptogram, cardCryptogram)) { - throw new IOException("Invalid card cryptogram"); - } - - md.update(sharedSecret); - checkCryptogram = md.digest(cardChallenge); - - resp = pair(apduChannel, PAIR_P1_LAST_STEP, checkCryptogram); - - if (resp.getSw() != 0x9000) { - throw new IOException("Pairing failed on step 2"); - } - - respData = resp.getData(); - md.update(sharedSecret); - pairingKey = md.digest(Arrays.copyOfRange(respData, 1, respData.length)); - pairingIndex = respData[0]; - } - - /** - * Unpairs the current paired key - * - * @param apduChannel the apdu channel - * @throws IOException communication error - */ - public void autoUnpair(CardChannel apduChannel) throws IOException { - APDUResponse resp = unpair(apduChannel, pairingIndex); - - if (resp.getSw() != 0x9000) { - throw new IOException("Unpairing failed"); - } - } - - /** - * Sends a OPEN SECURE CHANNEL APDU. - * - * @param apduChannel the apdu channel - * @param index the P1 parameter - * @param data the data - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse openSecureChannel(CardChannel apduChannel, byte index, byte[] data) throws IOException { - open = false; - APDUCommand openSecureChannel = new APDUCommand(0x80, INS_OPEN_SECURE_CHANNEL, index, 0, data); - return apduChannel.send(openSecureChannel); - } - - /** - * Sends a MUTUALLY AUTHENTICATE APDU. The data is generated automatically - * - * @param apduChannel the apdu channel - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse mutuallyAuthenticate(CardChannel apduChannel) throws IOException { - byte[] data = new byte[SC_SECRET_LENGTH]; - random.nextBytes(data); - - return mutuallyAuthenticate(apduChannel, data); - } - - /** - * Sends a MUTUALLY AUTHENTICATE APDU. - * - * @param apduChannel the apdu channel - * @param data the data - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse mutuallyAuthenticate(CardChannel apduChannel, byte[] data) throws IOException { - APDUCommand mutuallyAuthenticate = protectedCommand(0x80, INS_MUTUALLY_AUTHENTICATE, 0, 0, data); - return transmit(apduChannel, mutuallyAuthenticate); - } - - /** - * Sends a PAIR APDU. - * - * @param apduChannel the apdu channel - * @param p1 the P1 parameter - * @param data the data - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse pair(CardChannel apduChannel, byte p1, byte[] data) throws IOException { - APDUCommand openSecureChannel = new APDUCommand(0x80, INS_PAIR, p1, 0, data); - return transmit(apduChannel, openSecureChannel); - } - - /** - * Sends a UNPAIR APDU. - * - * @param apduChannel the apdu channel - * @param p1 the P1 parameter - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse unpair(CardChannel apduChannel, byte p1) throws IOException { - APDUCommand openSecureChannel = protectedCommand(0x80, INS_UNPAIR, p1, 0, new byte[0]); - return transmit(apduChannel, openSecureChannel); - } - - /** - * Unpair all other clients - * - * @param apduChannel the apdu channel - * @return the raw card response - * @throws IOException communication error - */ - public void unpairOthers(CardChannel apduChannel) throws IOException, APDUException { - for (int i = 0; i < PAIRING_MAX_CLIENT_COUNT; i++) { - if (i != pairingIndex) { - APDUCommand openSecureChannel = protectedCommand(0x80, INS_UNPAIR, i, 0, new byte[0]); - transmit(apduChannel, openSecureChannel).checkOK(); - } - } - } - - /** - * Encrypts the plaintext data using the session key. The maximum plaintext size is 223 bytes. The returned ciphertext - * already includes the IV and padding and can be sent as-is in the APDU payload. If the input is an empty byte array - * the returned data will still contain the IV and padding. - * - * @param data the plaintext data - * @return the encrypted data - */ - private byte[] encryptAPDU(byte[] data) { - assert data.length <= PAYLOAD_MAX_SIZE; - - try { - IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); - - sessionCipher.init(Cipher.ENCRYPT_MODE, sessionEncKey, ivParameterSpec); - return sessionCipher.doFinal(data); - } catch(Exception e) { - throw new RuntimeException("Is BouncyCastle in the classpath?", e); - } - } - - /** - * Decrypts the response from the card using the session key. The returned data is already stripped from IV and padding - * and can be potentially empty. - * - * @param data the ciphetext - * @return the plaintext - */ - private byte[] decryptAPDU(byte[] data) { - try { - IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); - sessionCipher.init(Cipher.DECRYPT_MODE, sessionEncKey, ivParameterSpec); - return sessionCipher.doFinal(data); - } catch(Exception e) { - throw new RuntimeException("Is BouncyCastle in the classpath?", e); - } - } - - /** - * Returns a command APDU with MAC and encrypted data. - * - * @param cla the CLA byte - * @param ins the INS byte - * @param p1 the P1 byte - * @param p2 the P2 byte - * @param data the data, can be an empty array but not null - * @return the command APDU - */ - public APDUCommand protectedCommand(int cla, int ins, int p1, int p2, byte[] data) { - byte[] finalData; - - if (open) { - data = encryptAPDU(data); - byte[] meta = new byte[]{(byte) cla, (byte) ins, (byte) p1, (byte) p2, (byte) (data.length + SC_BLOCK_SIZE), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - updateIV(meta, data); - - finalData = Arrays.copyOf(iv, iv.length + data.length); - System.arraycopy(data, 0, finalData, iv.length, data.length); - } else { - finalData = data; - } - - return new APDUCommand(cla, ins, p1, p2, finalData); - } - - /** - * Transmits a protected command APDU and unwraps the response data. The MAC is verified, the data decrypted and the - * SW read from the payload. - * - * @param apduChannel the APDU channel - * @param apdu the APDU to send - * @return the unwrapped response APDU - * @throws IOException transmission error - */ - public APDUResponse transmit(CardChannel apduChannel, APDUCommand apdu) throws IOException { - APDUResponse resp = apduChannel.send(apdu); - - if (resp.getSw() == 0x6982) { - open = false; - } - - if (open) { - byte[] data = resp.getData(); - byte[] meta = new byte[]{(byte) data.length, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - byte[] mac = Arrays.copyOf(data, iv.length); - data = Arrays.copyOfRange(data, iv.length, data.length); - - byte[] plainData = decryptAPDU(data); - - updateIV(meta, data); - - if (!Arrays.equals(iv, mac)) { - throw new IOException("Invalid MAC"); - } - - return new APDUResponse(plainData); - } else { - return resp; - } - } - - /** - * Marks the SecureChannel as closed - */ - public void reset() { - open = false; - } - - /** - * Encrypts the payload for the INIT command - * @param initData the payload for the INIT command - * - * @return the encrypted buffer - */ - public byte[] oneShotEncrypt(byte[] initData) { - try { - iv = new byte[SC_BLOCK_SIZE]; - random.nextBytes(iv); - IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); - sessionEncKey = new SecretKeySpec(secret, "AES"); - sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding"); - sessionCipher.init(Cipher.ENCRYPT_MODE, sessionEncKey, ivParameterSpec); - initData = sessionCipher.doFinal(initData); - byte[] encrypted = new byte[1 + publicKey.length + iv.length + initData.length]; - encrypted[0] = (byte) publicKey.length; - System.arraycopy(publicKey, 0, encrypted, 1, publicKey.length); - System.arraycopy(iv, 0, encrypted, (1 + publicKey.length), iv.length); - System.arraycopy(initData, 0, encrypted, (1 + publicKey.length + iv.length), initData.length); - return encrypted; - } catch (Exception e) { - throw new RuntimeException("Is BouncyCastle in the classpath?", e); - } - } - - /** - * Marks the SecureChannel as open. Only to be used when writing tests for the SecureChannel, in normal operation this - * would only make things wrong. - * - */ - void setOpen() { - open = true; - } - - /** - * Calculates a CMAC from the metadata and data provided and sets it as the IV for the next message. - * - * @param meta metadata - * @param data data - */ - private void updateIV(byte[] meta, byte[] data) { - try { - sessionMac.init(sessionMacKey); - sessionMac.update(meta, 0, meta.length); - sessionMac.update(data, 0, data.length); - sessionMac.doFinal(iv, 0); - } catch (Exception e) { - throw new RuntimeException("Is BouncyCastle in the classpath?", e); - } - } -} diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Select.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Select.java index f977d04..7de4e23 100644 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Select.java +++ b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Select.java @@ -1,6 +1,6 @@ package im.status.applet_installer_test.appletinstaller.apducommands; -import im.status.applet_installer_test.appletinstaller.APDUCommand; +import im.status.hardwallet_lite_android.io.APDUCommand; public class Select { private static final int CLA = 0x00; diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Status.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Status.java index d2e59dc..09ced90 100644 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Status.java +++ b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Status.java @@ -1,6 +1,6 @@ package im.status.applet_installer_test.appletinstaller.apducommands; -import im.status.applet_installer_test.appletinstaller.APDUCommand; +import im.status.hardwallet_lite_android.io.APDUCommand; public class Status { private static final int CLA = 0x80; diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/WalletAppletCommandSet.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/WalletAppletCommandSet.java deleted file mode 100644 index 60874b4..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/WalletAppletCommandSet.java +++ /dev/null @@ -1,499 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.APDUException; -import im.status.applet_installer_test.appletinstaller.APDUResponse; -import im.status.applet_installer_test.appletinstaller.CardChannel; -import org.spongycastle.jce.interfaces.ECPrivateKey; -import org.spongycastle.jce.interfaces.ECPublicKey; -import org.spongycastle.util.encoders.Hex; - -import java.io.IOException; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.util.Arrays; - -/** - * This class is used to send APDU to the applet. Each method corresponds to an APDU as defined in the APPLICATION.md - * file. Some APDUs map to multiple methods for the sake of convenience since their payload or response require some - * pre/post processing. - */ -public class WalletAppletCommandSet { - static final byte INS_INIT = (byte) 0xFE; - static final byte INS_GET_STATUS = (byte) 0xF2; - static final byte INS_VERIFY_PIN = (byte) 0x20; - static final byte INS_CHANGE_PIN = (byte) 0x21; - static final byte INS_UNBLOCK_PIN = (byte) 0x22; - static final byte INS_LOAD_KEY = (byte) 0xD0; - static final byte INS_DERIVE_KEY = (byte) 0xD1; - static final byte INS_GENERATE_MNEMONIC = (byte) 0xD2; - static final byte INS_REMOVE_KEY = (byte) 0xD3; - static final byte INS_SIGN = (byte) 0xC0; - static final byte INS_SET_PINLESS_PATH = (byte) 0xC1; - static final byte INS_EXPORT_KEY = (byte) 0xC2; - - static final byte GET_STATUS_P1_APPLICATION = 0x00; - - static final byte LOAD_KEY_P1_EC = 0x01; - static final byte LOAD_KEY_P1_EXT_EC = 0x02; - static final byte LOAD_KEY_P1_SEED = 0x03; - - static final byte DERIVE_P1_ASSISTED_MASK = 0x01; - static final byte DERIVE_P1_SOURCE_MASTER = (byte) 0x00; - - static final byte DERIVE_P2_KEY_PATH = 0x00; - static final byte DERIVE_P2_PUBLIC_KEY = 0x01; - - static final byte EXPORT_KEY_P2_PRIVATE_AND_PUBLIC = 0x00; - static final byte EXPORT_KEY_P2_PUBLIC_ONLY = 0x01; - - static final byte TLV_PUB_KEY = (byte) 0x80; - static final byte TLV_PRIV_KEY = (byte) 0x81; - static final byte TLV_CHAIN_CODE = (byte) 0x82; - - public static final String APPLET_AID = "53746174757357616C6C6574417070"; - public static final byte[] APPLET_AID_BYTES = Hex.decode(APPLET_AID); - - private final CardChannel apduChannel; - private SecureChannelSession secureChannel; - - public WalletAppletCommandSet(CardChannel apduChannel) { - this.apduChannel = apduChannel; - } - - public void setSecureChannel(SecureChannelSession secureChannel) { - this.secureChannel = secureChannel; - } - - /** - * Selects the applet. The applet is assumed to have been installed with its default AID. The returned data is a - * public key which must be used to initialize the secure channel. - * - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse select() throws IOException { - if (secureChannel != null) { - secureChannel.reset(); - } - - APDUCommand selectApplet = new APDUCommand(0x00, 0xA4, 4, 0, APPLET_AID_BYTES); - return apduChannel.send(selectApplet); - } - - /** - * Opens the secure channel. Calls the corresponding method of the SecureChannel class. - * - * @return the raw card response - * @throws IOException communication error - */ - public void autoOpenSecureChannel() throws IOException { - secureChannel.autoOpenSecureChannel(apduChannel); - } - - /** - * Automatically pairs. Calls the corresponding method of the SecureChannel class. - * - * @throws IOException communication error - */ - public void autoPair(byte[] sharedSecret) throws IOException { - secureChannel.autoPair(apduChannel, sharedSecret); - } - - /** - * Automatically unpairs. Calls the corresponding method of the SecureChannel class. - * - * @throws IOException communication error - */ - public void autoUnpair() throws IOException { - secureChannel.autoUnpair(apduChannel); - } - - /** - * Sends a OPEN SECURE CHANNEL APDU. Calls the corresponding method of the SecureChannel class. - */ - public APDUResponse openSecureChannel(byte index, byte[] data) throws IOException { - return secureChannel.openSecureChannel(apduChannel, index, data); - } - - /** - * Sends a MUTUALLY AUTHENTICATE APDU. Calls the corresponding method of the SecureChannel class. - */ - public APDUResponse mutuallyAuthenticate() throws IOException { - return secureChannel.mutuallyAuthenticate(apduChannel); - } - - /** - * Sends a MUTUALLY AUTHENTICATE APDU. Calls the corresponding method of the SecureChannel class. - */ - public APDUResponse mutuallyAuthenticate(byte[] data) throws IOException { - return secureChannel.mutuallyAuthenticate(apduChannel, data); - } - - /** - * Sends a PAIR APDU. Calls the corresponding method of the SecureChannel class. - */ - public APDUResponse pair(byte p1, byte[] data) throws IOException { - return secureChannel.pair(apduChannel, p1, data); - } - - /** - * Sends a UNPAIR APDU. Calls the corresponding method of the SecureChannel class. - */ - public APDUResponse unpair(byte p1) throws IOException { - return secureChannel.unpair(apduChannel, p1); - } - - /** - * Unpair all other clients. - */ - public void unpairOthers() throws IOException, APDUException { - secureChannel.unpairOthers(apduChannel); - } - - /** - * Sends a GET STATUS APDU. The info byte is the P1 parameter of the command, valid constants are defined in the applet - * class itself. - * - * @param info the P1 of the APDU - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse getStatus(byte info) throws IOException { - APDUCommand getStatus = secureChannel.protectedCommand(0x80, INS_GET_STATUS, info, 0, new byte[0]); - return secureChannel.transmit(apduChannel, getStatus); - } - - /** - * Sends a GET STATUS APDU to retrieve the APPLICATION STATUS template and reads the byte indicating public key - * derivation support. - * - * @return whether public key derivation is supported or not - * @throws IOException communication error - */ - public boolean getPublicKeyDerivationSupport() throws IOException { - APDUResponse resp = getStatus(GET_STATUS_P1_APPLICATION); - byte[] data = resp.getData(); - return data[data.length - 1] != 0x00; - } - - /** - * Sends a GET STATUS APDU to retrieve the APPLICATION STATUS template and reads the byte indicating key initialization - * status - * - * @return whether public key derivation is supported or not - * @throws IOException communication error - */ - public boolean getKeyInitializationStatus() throws IOException { - APDUResponse resp = getStatus(GET_STATUS_P1_APPLICATION); - byte[] data = resp.getData(); - return data[data.length - 4] != 0x00; - } - - /** - * Sends a VERIFY PIN APDU. The raw bytes of the given string are encrypted using the secure channel and used as APDU - * data. - * - * @param pin the pin - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse verifyPIN(String pin) throws IOException { - APDUCommand verifyPIN = secureChannel.protectedCommand(0x80, INS_VERIFY_PIN, 0, 0, pin.getBytes()); - return secureChannel.transmit(apduChannel, verifyPIN); - } - - /** - * Sends a CHANGE PIN APDU. The raw bytes of the given string are encrypted using the secure channel and used as APDU - * data. - * - * @param pinType the PIN type - * @param pin the new PIN - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse changePIN(int pinType, String pin) throws IOException { - return changePIN(pinType, pin.getBytes()); - } - - /** - * Sends a CHANGE PIN APDU. The raw bytes of the given string are encrypted using the secure channel and used as APDU - * data. - * - * @param pinType the PIN type - * @param pin the new PIN - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse changePIN(int pinType, byte[] pin) throws IOException { - APDUCommand changePIN = secureChannel.protectedCommand(0x80, INS_CHANGE_PIN, pinType, 0, pin); - return secureChannel.transmit(apduChannel, changePIN); - } - - /** - * Sends an UNBLOCK PIN APDU. The PUK and PIN are concatenated and the raw bytes are encrypted using the secure - * channel and used as APDU data. - * - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse unblockPIN(String puk, String newPin) throws IOException { - APDUCommand unblockPIN = secureChannel.protectedCommand(0x80, INS_UNBLOCK_PIN, 0, 0, (puk + newPin).getBytes()); - return secureChannel.transmit(apduChannel, unblockPIN); - } - - /** - * Sends a LOAD KEY APDU. The given private key and chain code are formatted as a raw binary seed and the P1 of - * the command is set to LOAD_KEY_P1_SEED (0x03). This works on cards which support public key derivation. - * The loaded keyset is extended and support further key derivation. - * - * @param aPrivate a private key - * @param chainCode the chain code - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse loadKey(PrivateKey aPrivate, byte[] chainCode) throws IOException { - byte[] privateKey = ((ECPrivateKey) aPrivate).getD().toByteArray(); - - int privLen = privateKey.length; - int privOff = 0; - - if(privateKey[0] == 0x00) { - privOff++; - privLen--; - } - - byte[] data = new byte[chainCode.length + privLen]; - System.arraycopy(privateKey, privOff, data, 0, privLen); - System.arraycopy(chainCode, 0, data, privLen, chainCode.length); - - return loadKey(data, LOAD_KEY_P1_SEED); - } - - /** - * Sends a LOAD KEY APDU. The key is sent in TLV format, includes the public key and no chain code, meaning that - * the card will not be able to do further key derivation. - * - * @param ecKeyPair a key pair - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse loadKey(KeyPair ecKeyPair) throws IOException { - return loadKey(ecKeyPair, false, null); - } - - /** - * Sends a LOAD KEY APDU. The key is sent in TLV format. The public key is included or not depending on the value - * of the omitPublicKey parameter. The chain code is included if the chainCode is not null. P1 is set automatically - * to either LOAD_KEY_P1_EC or LOAD_KEY_P1_EXT_EC depending on the presence of the chainCode. - * - * @param keyPair a key pair - * @param omitPublicKey whether the public key is sent or not - * @param chainCode the chain code - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse loadKey(KeyPair keyPair, boolean omitPublicKey, byte[] chainCode) throws IOException { - byte[] publicKey = omitPublicKey ? null : ((ECPublicKey) keyPair.getPublic()).getQ().getEncoded(false); - byte[] privateKey = ((ECPrivateKey) keyPair.getPrivate()).getD().toByteArray(); - - return loadKey(publicKey, privateKey, chainCode); - } - - /** - * Sends a LOAD KEY APDU. The key is sent in TLV format. The public key is included if not null. The chain code is - * included if not null. P1 is set automatically to either LOAD_KEY_P1_EC or - * LOAD_KEY_P1_EXT_EC depending on the presence of the chainCode. - * - * @param publicKey a raw public key - * @param privateKey a raw private key - * @param chainCode the chain code - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse loadKey(byte[] publicKey, byte[] privateKey, byte[] chainCode) throws IOException { - int privLen = privateKey.length; - int privOff = 0; - - if(privateKey[0] == 0x00) { - privOff++; - privLen--; - } - - int off = 0; - int totalLength = publicKey == null ? 0 : (publicKey.length + 2); - totalLength += (privLen + 2); - totalLength += chainCode == null ? 0 : (chainCode.length + 2); - - if (totalLength > 127) { - totalLength += 3; - } else { - totalLength += 2; - } - - byte[] data = new byte[totalLength]; - data[off++] = (byte) 0xA1; - - if (totalLength > 127) { - data[off++] = (byte) 0x81; - data[off++] = (byte) (totalLength - 3); - } else { - data[off++] = (byte) (totalLength - 2); - } - - if (publicKey != null) { - data[off++] = TLV_PUB_KEY; - data[off++] = (byte) publicKey.length; - System.arraycopy(publicKey, 0, data, off, publicKey.length); - off += publicKey.length; - } - - data[off++] = TLV_PRIV_KEY; - data[off++] = (byte) privLen; - System.arraycopy(privateKey, privOff, data, off, privLen); - off += privLen; - - byte p1; - - if (chainCode != null) { - p1 = LOAD_KEY_P1_EXT_EC; - data[off++] = (byte) TLV_CHAIN_CODE; - data[off++] = (byte) chainCode.length; - System.arraycopy(chainCode, 0, data, off, chainCode.length); - } else { - p1 = LOAD_KEY_P1_EC; - } - - return loadKey(data, p1); - } - - /** - * Sends a LOAD KEY APDU. The data is encrypted and sent as-is. The keyType parameter is used as P1. - * - * @param data key data - * @param keyType the P1 parameter - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse loadKey(byte[] data, byte keyType) throws IOException { - APDUCommand loadKey = secureChannel.protectedCommand(0x80, INS_LOAD_KEY, keyType, 0, data); - return secureChannel.transmit(apduChannel, loadKey); - } - - /** - * Sends a GENERATE MNEMONIC APDU. The cs parameter is the length of the checksum and is used as P1. - * - * @param cs the P1 parameter - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse generateMnemonic(int cs) throws IOException { - APDUCommand generateMnemonic = secureChannel.protectedCommand(0x80, INS_GENERATE_MNEMONIC, cs, 0, new byte[0]); - return secureChannel.transmit(apduChannel, generateMnemonic); - } - - /** - * Sends a REMOVE KEY APDU. - * - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse removeKey() throws IOException { - APDUCommand removeKey = secureChannel.protectedCommand(0x80, INS_REMOVE_KEY, 0, 0, new byte[0]); - return secureChannel.transmit(apduChannel, removeKey); - } - - /** - * Sends a SIGN APDU. The dataType is P1 as defined in the applet. The isFirst and isLast arguments are used to form - * the P2 parameter. The data is the data to sign, or part of it. Only when sending the last block a signature is - * generated and thus returned. When signing a precomputed hash it must be done in a single block, so isFirst and - * isLast will always be true at the same time. - * - * @param data the data to sign - * @param dataType the P1 parameter - * @param isFirst whether this is the first block of the command or not - * @param isLast whether this is the last block of the command or not - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse sign(byte[] data, byte dataType, boolean isFirst, boolean isLast) throws IOException { - byte p2 = (byte) ((isFirst ? 0x01 : 0x00) | (isLast ? 0x80 : 0x00)); - APDUCommand sign = secureChannel.protectedCommand(0x80, INS_SIGN, dataType, p2, data); - return secureChannel.transmit(apduChannel, sign); - } - - /** - * Sends a DERIVE KEY APDU. The data is encrypted and sent as-is. The P1 and P2 parameters are forced to 0, meaning - * that the derivation starts from the master key and is non-assisted. - * - * @param data the raw key path - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse deriveKey(byte[] data) throws IOException { - return deriveKey(data, DERIVE_P1_SOURCE_MASTER, false, false); - } - - /** - * Sends a DERIVE KEY APDU. The data is encrypted and sent as-is. The reset and assisted parameters are combined to - * form P1. The isPublicKey parameter is used for P2. - * - * @param data the raw key path or a public key - * @param source the source to start derivation - * @param assisted whether we are doing assisted derivation or not - * @param isPublicKey whether we are sending a public key or a key path (only make sense during assisted derivation) - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse deriveKey(byte[] data, int source, boolean assisted, boolean isPublicKey) throws IOException { - byte p1 = assisted ? DERIVE_P1_ASSISTED_MASK : 0; - p1 |= source; - byte p2 = isPublicKey ? DERIVE_P2_PUBLIC_KEY : DERIVE_P2_KEY_PATH; - - APDUCommand deriveKey = secureChannel.protectedCommand(0x80, INS_DERIVE_KEY, p1, p2, data); - return secureChannel.transmit(apduChannel, deriveKey); - } - - /** - * Sends a SET PINLESS PATH APDU. The data is encrypted and sent as-is. - * - * @param data the raw key path - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse setPinlessPath(byte [] data) throws IOException { - APDUCommand setPinlessPath = secureChannel.protectedCommand(0x80, INS_SET_PINLESS_PATH, 0x00, 0x00, data); - return secureChannel.transmit(apduChannel, setPinlessPath); - } - - /** - * Sends an EXPORT KEY APDU. The keyPathIndex is used as P1. Valid values are defined in the applet itself - * - * @param keyPathIndex the P1 parameter - * @param publicOnly the P2 parameter - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse exportKey(byte keyPathIndex, boolean publicOnly) throws IOException { - byte p2 = publicOnly ? EXPORT_KEY_P2_PUBLIC_ONLY : EXPORT_KEY_P2_PRIVATE_AND_PUBLIC; - APDUCommand exportKey = secureChannel.protectedCommand(0x80, INS_EXPORT_KEY, keyPathIndex, p2, new byte[0]); - return secureChannel.transmit(apduChannel, exportKey); - } - - /** - * Sends the INIT command to the card. - * - * @param pin the PIN - * @param puk the PUK - * @param sharedSecret the shared secret for pairing - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse init(String pin, String puk, byte[] sharedSecret) throws IOException { - byte[] initData = Arrays.copyOf(pin.getBytes(), pin.length() + puk.length() + sharedSecret.length); - System.arraycopy(puk.getBytes(), 0, initData, pin.length(), puk.length()); - System.arraycopy(sharedSecret, 0, initData, pin.length() + puk.length(), sharedSecret.length); - APDUCommand init = new APDUCommand(0x80, INS_INIT, 0, 0, secureChannel.oneShotEncrypt(initData)); - return apduChannel.send(init); - } -} diff --git a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdateTest.java b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdateTest.java index 8bf37d6..baf9707 100644 --- a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdateTest.java +++ b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdateTest.java @@ -4,9 +4,6 @@ import org.junit.Test; import java.io.IOException; -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.APDUException; -import im.status.applet_installer_test.appletinstaller.APDUResponse; import im.status.applet_installer_test.appletinstaller.HexUtils; import im.status.applet_installer_test.appletinstaller.Keys; diff --git a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstallTest.java b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstallTest.java index 47b0f02..68befd0 100644 --- a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstallTest.java +++ b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstallTest.java @@ -4,8 +4,6 @@ import org.junit.Test; import java.io.IOException; -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.APDUCommandTest; import im.status.applet_installer_test.appletinstaller.HexUtils; import static org.junit.Assert.*; diff --git a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoadTest.java b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoadTest.java index 46751a9..40cb2e9 100644 --- a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoadTest.java +++ b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoadTest.java @@ -4,7 +4,6 @@ import org.junit.Test; import java.io.IOException; -import im.status.applet_installer_test.appletinstaller.APDUCommand; import im.status.applet_installer_test.appletinstaller.HexUtils; import static org.junit.Assert.*; diff --git a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/LoadTest.java b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/LoadTest.java index 43a6605..ce1c573 100644 --- a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/LoadTest.java +++ b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/LoadTest.java @@ -3,13 +3,6 @@ package im.status.applet_installer_test.appletinstaller.apducommands; import org.junit.Test; import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.HexUtils; - -import static org.junit.Assert.*; public class LoadTest { @Test diff --git a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/SelectTest.java b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/SelectTest.java index 479a053..9e29a72 100644 --- a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/SelectTest.java +++ b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/SelectTest.java @@ -4,7 +4,6 @@ import org.junit.Test; import java.io.IOException; -import im.status.applet_installer_test.appletinstaller.APDUCommand; import im.status.applet_installer_test.appletinstaller.HexUtils; import static org.junit.Assert.*; diff --git a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/StatusTest.java b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/StatusTest.java index d402123..a671104 100644 --- a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/StatusTest.java +++ b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/StatusTest.java @@ -4,8 +4,6 @@ import org.junit.Test; import java.io.IOException; -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.APDUWrapper; import im.status.applet_installer_test.appletinstaller.HexUtils; import static org.junit.Assert.*; diff --git a/build.gradle b/build.gradle index 077cb2f..bef99bb 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,7 @@ allprojects { repositories { google() jcenter() + maven { url 'https://jitpack.io' } } }