Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb8c5c8e2b | ||
|
|
00057359ce | ||
|
|
ea170172b5 | ||
|
|
2fdb108303 | ||
|
|
c7fc137724 | ||
|
|
97773b6c37 | ||
|
|
05780c1f9c | ||
|
|
37b7b9ebc8 | ||
|
|
9c1adc9507 |
Generated
-3
@@ -1,8 +1,5 @@
|
||||
<?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" />
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ android {
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 27
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
versionName "0.0.3"
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
+24
-16
@@ -10,7 +10,8 @@ import java.io.IOException;
|
||||
public class CardManager extends Thread implements NfcAdapter.ReaderCallback {
|
||||
public final static int ACTION_NONE = 0;
|
||||
public final static int ACTION_INSTALL = 1;
|
||||
public final static int ACTION_PERFTEST = 2;
|
||||
public final static int ACTION_INSTALL_TEST = 2;
|
||||
public final static int ACTION_PERFTEST = 3;
|
||||
|
||||
private NfcAdapter nfcAdapter;
|
||||
private AssetManager assets;
|
||||
@@ -34,16 +35,19 @@ 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_INSTALL_TEST:
|
||||
Logger.i("installation with test secrets 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 +62,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 +73,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 +91,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 +100,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,14 +112,18 @@ 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);
|
||||
|
||||
switch (requestedAction) {
|
||||
case ACTION_INSTALL:
|
||||
Installer installer = new Installer(ch, this.assets, this.capPath);
|
||||
case ACTION_INSTALL:
|
||||
Installer installer = new Installer(ch, this.assets, this.capPath, false);
|
||||
installer.start();
|
||||
break;
|
||||
case ACTION_INSTALL_TEST:
|
||||
installer = new Installer(ch, this.assets, this.capPath, true);
|
||||
installer.start();
|
||||
break;
|
||||
case ACTION_PERFTEST:
|
||||
@@ -127,11 +135,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;
|
||||
|
||||
@@ -23,6 +23,7 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
public class Crypto {
|
||||
public static final byte[] NullBytes8 = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
public static long PIN_BOUND = 999999L;
|
||||
public static long PUK_BOUND = 999999999999L;
|
||||
|
||||
public static byte[] deriveKey(byte[] cardKey, byte[] seq, byte[] purposeData) {
|
||||
|
||||
@@ -7,37 +7,38 @@ 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.ExternalAuthenticate;
|
||||
import im.status.applet_installer_test.appletinstaller.apducommands.InitializeUpdate;
|
||||
import im.status.applet_installer_test.appletinstaller.apducommands.InstallForInstall;
|
||||
import im.status.applet_installer_test.appletinstaller.apducommands.InstallForLoad;
|
||||
import im.status.applet_installer_test.appletinstaller.apducommands.Load;
|
||||
import im.status.applet_installer_test.appletinstaller.apducommands.Select;
|
||||
import im.status.applet_installer_test.appletinstaller.apducommands.*;
|
||||
|
||||
public class Installer {
|
||||
private Channel plainChannel;
|
||||
private Channel channel;
|
||||
private Keys cardKeys;
|
||||
private AssetManager assets;
|
||||
private String capPath;
|
||||
private boolean testSecrets;
|
||||
|
||||
static final byte[] cardKeyData = HexUtils.hexStringToByteArray("404142434445464748494a4b4c4d4e4f");
|
||||
|
||||
public Installer(Channel channel, AssetManager assets, String capPath) {
|
||||
public Installer(Channel 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;
|
||||
this.testSecrets = testSecrets;
|
||||
}
|
||||
|
||||
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 +59,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);
|
||||
@@ -74,34 +81,41 @@ public class Installer {
|
||||
|
||||
APDUCommand loadCmd;
|
||||
while((loadCmd = load.getCommand()) != null) {
|
||||
this.send("load " + load.getCount() + "/31", loadCmd);
|
||||
this.send("load " + load.getCount() + "/37", loadCmd);
|
||||
}
|
||||
|
||||
|
||||
byte[] packageAID = HexUtils.hexStringToByteArray("53746174757357616C6C6574");
|
||||
byte[] appletAID = HexUtils.hexStringToByteArray("53746174757357616C6C6574417070");
|
||||
byte[] instanceAID = HexUtils.hexStringToByteArray("53746174757357616C6C6574417070");
|
||||
|
||||
Secrets secrets = Secrets.generate();
|
||||
ByteArrayOutputStream params = new ByteArrayOutputStream();
|
||||
params.write(secrets.getPuk().getBytes());
|
||||
params.write(secrets.getPairingToken());
|
||||
|
||||
InstallForInstall install = new InstallForInstall(packageAID, appletAID, instanceAID, params.toByteArray());
|
||||
InstallForInstall install = new InstallForInstall(packageAID, appletAID, instanceAID, new byte[0]);
|
||||
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())));
|
||||
|
||||
installSecrets();
|
||||
|
||||
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 void installSecrets() 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);
|
||||
cmdSet.init(secrets.getPin(), secrets.getPuk(), secrets.getPairingToken()).checkOK();
|
||||
|
||||
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.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);
|
||||
}
|
||||
}
|
||||
|
||||
+13
-1
@@ -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;
|
||||
@@ -21,6 +22,7 @@ public class MainActivity extends AppCompatActivity implements UILogger {
|
||||
private ScrollView textViewScroll;
|
||||
|
||||
private Button buttonInstall;
|
||||
private Button buttonInstallTest;
|
||||
private Button buttonPerfTest;
|
||||
private CardManager cardManager;
|
||||
|
||||
@@ -47,6 +49,13 @@ public class MainActivity extends AppCompatActivity implements UILogger {
|
||||
requestAction(CardManager.ACTION_INSTALL);
|
||||
}
|
||||
});
|
||||
buttonInstallTest = (Button) findViewById(R.id.buttonInstallTest);
|
||||
buttonInstallTest.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
requestAction(CardManager.ACTION_INSTALL_TEST);
|
||||
}
|
||||
});
|
||||
buttonPerfTest = (Button) findViewById(R.id.buttonPerfTest);
|
||||
buttonPerfTest.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@@ -54,6 +63,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 +74,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;
|
||||
|
||||
@@ -58,7 +58,6 @@ public class PerfTest {
|
||||
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_DATA = 0x00;
|
||||
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;
|
||||
@@ -84,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();
|
||||
@@ -93,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 {
|
||||
@@ -124,11 +124,9 @@ 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.deriveKey(new byte[] { (byte) 0xC0, 0x00, 0x00, 0x00}, DERIVE_P1_SOURCE_PARENT, false, false).checkOK();
|
||||
cmdSet.exportKey(EXPORT_KEY_P1_HIGH, 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.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;
|
||||
}
|
||||
@@ -142,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;
|
||||
}
|
||||
|
||||
@@ -156,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;
|
||||
|
||||
@@ -6,11 +6,13 @@ import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
public class Secrets {
|
||||
private String pin;
|
||||
private String puk;
|
||||
private String pairingPassword;
|
||||
private byte[] pairingToken;
|
||||
|
||||
public Secrets(String puk, String pairingPassword, byte[] pairingToken) {
|
||||
public Secrets(String pin, String puk, String pairingPassword, byte[] pairingToken) {
|
||||
this.pin = pin;
|
||||
this.puk = puk;
|
||||
this.pairingPassword = pairingPassword;
|
||||
this.pairingToken = pairingToken;
|
||||
@@ -20,10 +22,22 @@ public class Secrets {
|
||||
public static Secrets generate() throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
String pairingPassword = Crypto.randomToken(12);
|
||||
byte[] pairingToken = Crypto.generatePairingKey(pairingPassword.toCharArray());
|
||||
long pinNumber = Crypto.randomLong(Crypto.PIN_BOUND);
|
||||
long pukNumber = Crypto.randomLong(Crypto.PUK_BOUND);
|
||||
String pin = String.format("%06d", pinNumber);
|
||||
String puk = String.format("%012d", pukNumber);
|
||||
|
||||
return new Secrets(puk, pairingPassword, pairingToken);
|
||||
return new Secrets(pin, puk, pairingPassword, pairingToken);
|
||||
}
|
||||
|
||||
public static Secrets testSecrets() throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
String pairingPassword = "WalletAppletTest";
|
||||
byte[] pairingToken = Crypto.generatePairingKey(pairingPassword.toCharArray());
|
||||
return new Secrets("000000", "123456789012", pairingPassword, pairingToken);
|
||||
}
|
||||
|
||||
public String getPin() {
|
||||
return pin;
|
||||
}
|
||||
|
||||
public String getPuk() {
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
|
||||
+33
-3
@@ -57,6 +57,12 @@ public class SecureChannelSession {
|
||||
* @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");
|
||||
@@ -74,9 +80,7 @@ public class SecureChannelSession {
|
||||
|
||||
keyAgreement.doPhase(cardKey, true);
|
||||
secret = keyAgreement.generateSecret();
|
||||
|
||||
open = false;
|
||||
} catch(Exception e) {
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Is BouncyCastle in the classpath?", e);
|
||||
}
|
||||
}
|
||||
@@ -416,6 +420,32 @@ public class SecureChannelSession {
|
||||
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.
|
||||
|
||||
+60
-2
@@ -11,6 +11,7 @@ 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
|
||||
@@ -18,6 +19,7 @@ import java.security.PrivateKey;
|
||||
* 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;
|
||||
@@ -25,6 +27,7 @@ public class WalletAppletCommandSet {
|
||||
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;
|
||||
@@ -174,6 +177,19 @@ public class WalletAppletCommandSet {
|
||||
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.
|
||||
@@ -191,12 +207,26 @@ public class WalletAppletCommandSet {
|
||||
* 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(String pin) throws IOException {
|
||||
APDUCommand changePIN = secureChannel.protectedCommand(0x80, INS_CHANGE_PIN, 0, 0, pin.getBytes());
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -362,6 +392,17 @@ public class WalletAppletCommandSet {
|
||||
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
|
||||
@@ -438,4 +479,21 @@ public class WalletAppletCommandSet {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,16 @@
|
||||
</ScrollView>
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonInstall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Install" />
|
||||
android:id="@+id/buttonInstall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Install Secure"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonInstallTest"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Install with test PIN/PUK"/>
|
||||
<Button
|
||||
android:id="@+id/buttonPerfTest"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
+1
-1
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user