10 Commits
Author SHA1 Message Date
Andrea Franz c7fc137724 fix delete test 2018-09-26 15:56:32 +02:00
Andrea Franz 97773b6c37 delete applet before installing. in 2 steps, applet and then pkg 2018-09-26 15:53:44 +02:00
Michele Balistreri 05780c1f9c update cap file 2018-09-26 15:23:50 +02:00
Michele Balistreri 37b7b9ebc8 use unassisted key derivation 2018-09-24 16:24:06 +03:00
Andrea Franz 9c1adc9507 add log level for console and UI logs 2018-09-19 13:58:17 +02:00
Michele Balistreri 99c277021c update wallet file 2018-09-18 11:32:20 +03:00
Andrea Franz 6b493118c5 update demo gif 2018-09-17 12:32:35 +02:00
Andrea Franz 535630f2ae Update README.md 2018-09-16 12:40:26 +02:00
Andrea Franz 7088d3acbf add README 2018-09-16 12:37:21 +02:00
Andrea Franz 9470bc3314 add demo gif 2018-09-16 12:23:33 +02:00
15 changed files with 112 additions and 88 deletions
+2 -14
View File
@@ -1,38 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="9">
<list size="5">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="9">
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
<item index="4" class="java.lang.String" itemvalue="javax.validation.constraints.NotNull" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
</list>
</value>
</option>
+11
View File
@@ -0,0 +1,11 @@
# smartcard-cap-installer-test
This is an android application that implements the basic APDU commands and secure channel to be able to install our [hardware wallet cap](https://github.com/status-im/hardware-wallet)
to a smartcard via NFC.
The Purpose of it is to be able to start testing smartcards and NFC connection with different cards and readers.
This can help with UX/UI design while we implement the final solution in `status-react` and `status-go`.
[Download APK](https://github.com/status-im/smartcard-cap-installer-test/releases/download/0.0.1/app-debug.apk)
![https://github.com/status-im/smartcard-cap-installer-test/blob/master/docs/demo.gif?raw=true](https://github.com/status-im/smartcard-cap-installer-test/blob/master/docs/demo.gif?raw=true)
+1 -1
View File
@@ -7,7 +7,7 @@ android {
minSdkVersion 19
targetSdkVersion 27
versionCode 1
versionName "1.0"
versionName "0.0.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
Binary file not shown.
@@ -8,7 +8,7 @@ public class APDUResponse {
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; // apple may be already installed
public static int SW_CONDITIONS_OF_USE_NOT_SATISFIED = 0x6985; // applet may be already installed
private byte[] apdu;
private byte[] data;
@@ -15,9 +15,9 @@ public class CardChannel implements Channel {
public APDUResponse send(APDUCommand cmd) throws IOException {
byte[] apdu = cmd.serialize();
Logger.log(String.format("COMMAND %s", HexUtils.byteArrayToHexString(apdu)));
Logger.d(String.format("COMMAND %s", HexUtils.byteArrayToHexString(apdu)));
byte[] resp = this.isoDep.transceive(apdu);
Logger.log(String.format("RESPONSE %s %n-----------------------", HexUtils.byteArrayToHexString(resp)));
Logger.d(String.format("RESPONSE %s %n-----------------------", HexUtils.byteArrayToHexString(resp)));
return new APDUResponse(resp);
}
}
@@ -34,16 +34,16 @@ public class CardManager extends Thread implements NfcAdapter.ReaderCallback {
public void requestAction(int actionRequested) {
switch(actionRequested) {
case ACTION_NONE:
Logger.log("cancelling requested action");
Logger.i("cancelling requested action");
break;
case ACTION_INSTALL:
Logger.log("installation requested");
Logger.i("installation requested");
break;
case ACTION_PERFTEST:
Logger.log("performance tests requested");
Logger.i("performance tests requested");
break;
default:
Logger.log("invalid action requested, ignoring");
Logger.i("invalid action requested, ignoring");
return;
}
@@ -58,7 +58,7 @@ public class CardManager extends Thread implements NfcAdapter.ReaderCallback {
this.isoDep.connect();
this.isoDep.setTimeout(120000);
} catch (IOException e) {
Logger.log("error connecting to tag");
Logger.e("error connecting to tag");
}
}
@@ -69,7 +69,7 @@ public class CardManager extends Thread implements NfcAdapter.ReaderCallback {
boolean newConnected = this.isConnected();
if (newConnected != connected) {
connected = newConnected;
Logger.log("tag " + (connected ? "connected" : "disconnected"));
Logger.i("tag " + (connected ? "connected" : "disconnected"));
if (connected) {
this.onCardConnected();
} else {
@@ -87,7 +87,7 @@ public class CardManager extends Thread implements NfcAdapter.ReaderCallback {
try {
this.sleep(50);
} catch (InterruptedException e) {
Logger.log("error in TagManager thread: " + e.getMessage());
Logger.e("error in TagManager thread: " + e.getMessage());
this.interrupt();
}
}
@@ -96,9 +96,9 @@ public class CardManager extends Thread implements NfcAdapter.ReaderCallback {
private void onCardConnected() {
this.cardConnectedAt = System.currentTimeMillis();
if (this.requestedAction != ACTION_NONE) {
Logger.log("waiting 2 seconds to start requested action");
Logger.i("waiting 2 seconds to start requested action");
} else {
Logger.log("no action requested yet");
Logger.i("no action requested yet");
}
}
@@ -108,7 +108,7 @@ public class CardManager extends Thread implements NfcAdapter.ReaderCallback {
}
private void perform() {
Logger.log("starting requested action");
Logger.i("starting requested action");
this.running = true;
try {
CardChannel ch = new CardChannel(this.isoDep);
@@ -127,11 +127,11 @@ public class CardManager extends Thread implements NfcAdapter.ReaderCallback {
}
} catch (IOException e) {
Logger.log("IO exception: " + e.getMessage());
Logger.e("IO exception: " + e.getMessage());
} catch (APDUException e) {
Logger.log("APDU exception: " + e.getMessage());
Logger.e("APDU exception: " + e.getMessage());
} catch (Exception e) {
Logger.log("Other exception: " + e.getMessage());
Logger.e("Other exception: " + e.getMessage());
} finally {
this.running = false;
this.requestedAction = ACTION_NONE;
@@ -8,6 +8,7 @@ import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import im.status.applet_installer_test.appletinstaller.apducommands.Delete;
import im.status.applet_installer_test.appletinstaller.apducommands.ExternalAuthenticate;
import im.status.applet_installer_test.appletinstaller.apducommands.InitializeUpdate;
import im.status.applet_installer_test.appletinstaller.apducommands.InstallForInstall;
@@ -31,13 +32,14 @@ public class Installer {
}
public void start() throws IOException, APDUException, NoSuchAlgorithmException, InvalidKeySpecException {
Logger.i("installation started");
long startTime = System.currentTimeMillis();
Select discover = new Select(new byte[0]);
APDUResponse resp = this.send("discover", discover.getCommand());
byte[] sdaid = this.getSDAID(resp.getData());
Logger.log("sdaid: " + HexUtils.byteArrayToHexString(sdaid));
Logger.d("sdaid: " + HexUtils.byteArrayToHexString(sdaid));
byte[] hostChallenge = InitializeUpdate.generateChallenge();
InitializeUpdate init = new InitializeUpdate(hostChallenge);
@@ -58,10 +60,16 @@ public class Installer {
//resp = this.send("status", status.getCommand());
byte[] aid = HexUtils.hexStringToByteArray("53746174757357616C6C6574");
byte[] appletAID = HexUtils.hexStringToByteArray("53746174757357616C6C6574417070");
//Delete delete = new Delete(aid);
//Logger.log("sending command delete");
//this.channel.send(delete.getCommand());
Delete deleteApplet = new Delete(appletAID);
Logger.i("sending delete (applet)");
this.channel.send(deleteApplet.getCommand());
Delete deletePkg = new Delete(aid);
Logger.i("sending delete (pkg)");
this.channel.send(deletePkg.getCommand());
InstallForLoad preLoad = new InstallForLoad(aid, sdaid);
@@ -79,7 +87,6 @@ public class Installer {
byte[] packageAID = HexUtils.hexStringToByteArray("53746174757357616C6C6574");
byte[] appletAID = HexUtils.hexStringToByteArray("53746174757357616C6C6574417070");
byte[] instanceAID = HexUtils.hexStringToByteArray("53746174757357616C6C6574417070");
Secrets secrets = Secrets.generate();
@@ -90,18 +97,18 @@ public class Installer {
InstallForInstall install = new InstallForInstall(packageAID, appletAID, instanceAID, params.toByteArray());
this.send("perform and make selectable", install.getCommand());
Logger.log(String.format("PUK: %s\nPairing password: %s\nPairing token: %s", secrets.getPuk(), secrets.getPairingPassword(), HexUtils.byteArrayToHexString(secrets.getPairingToken())));
Logger.i(String.format("PUK: %s\nPairing password: %s\nPairing token: %s", secrets.getPuk(), secrets.getPairingPassword(), HexUtils.byteArrayToHexString(secrets.getPairingToken())));
long duration = System.currentTimeMillis() - startTime;
Logger.log(String.format("installation completed in %d seconds", duration / 1000));
Logger.i(String.format("installation completed in %d seconds", duration / 1000));
}
private APDUResponse send(String description, APDUCommand cmd) throws IOException, APDUException {
Logger.log("sending command " + description);
Logger.d("sending command " + description);
APDUResponse resp = this.channel.send(cmd);
if(resp.getSw() == APDUResponse.SW_SECURITY_CONDITION_NOT_SATISFIED) {
Logger.log("SW_SECURITY_CONDITION_NOT_SATISFIED: card might be blocked");
Logger.e("SW_SECURITY_CONDITION_NOT_SATISFIED: card might be blocked");
throw new APDUException(resp.getSw(), "security confition not satisfied. card might be blocked " + description);
}
@@ -8,34 +8,55 @@ interface UILogger {
public class Logger {
private static UILogger uiLogger;
private static boolean mute;
private static int Level = Log.VERBOSE;
private static int UILevel = Log.VERBOSE;
public static void setUILogger(UILogger l) {
uiLogger = l;
}
public static void setMute(boolean m) {
mute = m;
public static void setLevel(int level) {
Level = level;
}
public static void log(String m) {
log(m, true);
public static void setUILevel(int level) {
UILevel = level;
}
public static void log(String m, boolean showInUI) {
if (!mute && m != null) {
Log.d("installer-debug", m);
if (showInUI && uiLogger != null) {
public static void log(int _level, String m, boolean showInUI) {
if (m != null && _level >= Level) {
Log.println(_level, "installer-debug", m);
if (showInUI && uiLogger != null && _level >= UILevel) {
uiLogger.log(m);
}
}
}
public static void log(byte[] m) {
log(m, true);
public static void log(int level, String m) {
log(level, m, true);
}
public static void log(byte[] m, boolean showInUI) {
log(HexUtils.byteArrayToHexString(m), showInUI);
public static void d(String m, boolean showInUI) {
log(Log.DEBUG, m, showInUI);
}
public static void d(String m) {
d(m, true);
}
public static void i(String m, boolean showInUI) {
log(Log.INFO, m, showInUI);
}
public static void i(String m) {
i(m, true);
}
public static void e(String m, boolean showInUI) {
log(Log.ERROR, m, showInUI);
}
public static void e(String m) {
e(m, true);
}
}
@@ -5,6 +5,7 @@ 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;
@@ -54,6 +55,9 @@ public class MainActivity extends AppCompatActivity implements UILogger {
requestAction(CardManager.ACTION_PERFTEST);
}
});
//Logger.setUILevel(Log.INFO);
//Logger.setLevel(Log.INFO);
}
private void logException(Exception e) {
@@ -62,7 +66,7 @@ public class MainActivity extends AppCompatActivity implements UILogger {
msg = "exception without message";
}
Logger.log("exception: " + msg);
Logger.e("exception: " + msg);
}
private void requestAction(int action) {
@@ -1,5 +1,7 @@
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;
@@ -20,8 +22,6 @@ import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Random;
@@ -57,9 +57,7 @@ public class PerfTest {
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_WHISPER = 0x01;
static final byte EXPORT_KEY_P1_DATABASE = 0x02;
static final byte SIGN_P1_DATA = 0x00;
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;
@@ -85,8 +83,8 @@ public class PerfTest {
openSecureChannelTime = System.currentTimeMillis() - openSecureChannelTime;
cmdSet.verifyPIN("000000").checkOK();
cmdSet.unpairOthers(); // Recover in case of non-clean termination
Logger.log("Measuring performances. Logging disabled. Please wait");
Logger.setMute(true);
Logger.i("Measuring performances. Logging disabled. Please wait");
Logger.setLevel(Log.INFO);
try {
loadKeys();
@@ -94,22 +92,23 @@ public class PerfTest {
login();
signTransactions();
} finally {
Logger.setMute(false);
Logger.setLevel(Log.INFO);
Logger.setUILevel(Log.INFO);
}
Logger.log("Reenabling logging.");
Logger.i("Reenabling logging.");
cmdSet.select();
cmdSet.autoOpenSecureChannel();
cmdSet.verifyPIN("000000").checkOK();
cmdSet.autoUnpair();
Logger.log("*************************************************");
Logger.log("Opening Secure Channel: " + openSecureChannelTime + "ms");
Logger.log("Derivation of m/44'/60'/0'/0/0 from master: " + loadKeysTime + "ms");
Logger.log("All following measurements are from application selection to the last needed APDU");
Logger.log("GET STATUS: " + getStatusTime + "ms");
Logger.log("Login: " + loginTime + "ms");
Logger.log("Transaction signature (after login): " + signTime + "ms");
Logger.log("Transaction signature (subsequent): " + (signTime - deriveKeyFromParent) + "ms");
Logger.i("*************************************************");
Logger.i("Opening Secure Channel: " + openSecureChannelTime + "ms");
Logger.i("Derivation of m/44'/60'/0'/0/0 from master: " + loadKeysTime + "ms");
Logger.i("All following measurements are from application selection to the last needed APDU");
Logger.i("GET STATUS: " + getStatusTime + "ms");
Logger.i("Login: " + loginTime + "ms");
Logger.i("Transaction signature (after login): " + signTime + "ms");
Logger.i("Transaction signature (subsequent): " + (signTime - deriveKeyFromParent) + "ms");
}
private void getStatus() throws Exception {
@@ -125,12 +124,10 @@ public class PerfTest {
cmdSet.select();
cmdSet.autoOpenSecureChannel();
cmdSet.verifyPIN("000000").checkOK();
APDUResponse resp = cmdSet.deriveKey(new byte[] { (byte) 0xC0, 0x00, 0x00, 0x00}, DERIVE_P1_SOURCE_PARENT, true, false).checkOK();
cmdSet.deriveKey(derivePublicKey(resp.getData()), DERIVE_P1_SOURCE_CURRENT, true, true).checkOK();
cmdSet.exportKey(EXPORT_KEY_P1_WHISPER, false).checkOK();
resp = cmdSet.deriveKey(new byte[] { (byte) 0xC0, 0x00, 0x00, 0x01}, DERIVE_P1_SOURCE_PARENT, true, false).checkOK();
cmdSet.deriveKey(derivePublicKey(resp.getData()), DERIVE_P1_SOURCE_CURRENT, true, true).checkOK();
cmdSet.exportKey(EXPORT_KEY_P1_DATABASE, false).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();
loginTime = System.currentTimeMillis() - time;
}
@@ -143,11 +140,7 @@ public class PerfTest {
cmdSet.loadKey(keyPair, false, chainCode).checkOK();
long time = System.currentTimeMillis();
for (int i = 0; i < BIP44_PATH.length; i += 4) {
APDUResponse resp = cmdSet.deriveKey(Arrays.copyOfRange(BIP44_PATH, i, i+4), DERIVE_P1_SOURCE_CURRENT, true, false).checkOK();
cmdSet.deriveKey(derivePublicKey(resp.getData()), DERIVE_P1_SOURCE_CURRENT, true, true).checkOK();
}
cmdSet.deriveKey(BIP44_PATH, DERIVE_P1_SOURCE_CURRENT, false, false).checkOK();
loadKeysTime = System.currentTimeMillis() - time;
}
@@ -157,8 +150,7 @@ public class PerfTest {
cmdSet.autoOpenSecureChannel();
cmdSet.verifyPIN("000000").checkOK();
deriveKeyFromParent = System.currentTimeMillis();
APDUResponse resp = cmdSet.deriveKey(new byte[] { (byte) 0x00, 0x00, 0x00, 0x00}, DERIVE_P1_SOURCE_PARENT, true, false).checkOK();
cmdSet.deriveKey(derivePublicKey(resp.getData()), DERIVE_P1_SOURCE_CURRENT, true, true).checkOK();
cmdSet.deriveKey(new byte[] { (byte) 0x00, 0x00, 0x00, 0x00}, DERIVE_P1_SOURCE_PARENT, false, false).checkOK();
deriveKeyFromParent = System.currentTimeMillis() - deriveKeyFromParent;
cmdSet.sign("any32bytescanbeahashyouknowthat!".getBytes(), SIGN_P1_PRECOMPUTED_HASH, true, true).checkOK();
signTime = System.currentTimeMillis() - time;
@@ -14,9 +14,9 @@ public class SecureChannel implements Channel {
}
public APDUResponse send(APDUCommand cmd) throws IOException {
Logger.log(String.format("WRAPPING %s %n", HexUtils.byteArrayToHexString(cmd.serialize())), false);
Logger.d(String.format("WRAPPING %s %n", HexUtils.byteArrayToHexString(cmd.serialize())), false);
APDUCommand wrappedCommand = this.wrapper.wrap(cmd);
Logger.log(String.format("WRAPPED %s %n", HexUtils.byteArrayToHexString(wrappedCommand.serialize())), false);
Logger.d(String.format("WRAPPED %s %n", HexUtils.byteArrayToHexString(wrappedCommand.serialize())), false);
return this.channel.send(wrappedCommand);
}
}
@@ -6,7 +6,8 @@ public class Delete {
private static final int CLA = 0x80;
private static final int INS = 0xE4;
private static final int P1 = 0x00;
private static final int P2 = 0x80; // delete object and related files
//private static final int P2 = 0x80; // delete object and related files
private static final int P2 = 0x00;
private byte[] aid;
@@ -14,7 +14,7 @@ public class DeleteTest {
public void getCommand() throws IOException {
byte[] aid = HexUtils.hexStringToByteArray("53746174757357616C6C6574");
Delete delete = new Delete(aid);
String expected = "80E400800E4F0C53746174757357616C6C6574";
String expected = "80E400000E4F0C53746174757357616C6C6574";
byte[] apdu = delete.getCommand().serialize();
assertEquals(expected, HexUtils.byteArrayToHexString(apdu));
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB