8 Commits
Author SHA1 Message Date
Michele Balistreri 85177fe5d3 update applet and PerfTest 2018-11-30 11:35:01 +03:00
Andrea Franz 0045a2259a Merge pull request #10 from status-im/globalplatform-from-sdk
add missing file Secrets.java
2018-11-27 13:59:28 +01:00
Andrea Franz 2e6a10fe81 update Secrets 2018-11-27 13:52:45 +01:00
Bitgamma e708ef9419 Merge pull request #9 from status-im/globalplatform-from-sdk
Globalplatform from sdk
2018-11-27 15:08:11 +03:00
Andrea Franz 0cd03ca32f remove all globsalplatform code and use the sdk commandset 2018-11-27 12:57:36 +01:00
Andrea Franz 084baa5501 import local hardwallet-lite-android 2018-11-21 14:56:40 +01:00
Bitgamma 3f8a6c94dc Merge pull request #8 from status-im/use-sdk
simplify NDEF configuration
2018-11-13 14:28:29 +03:00
Bitgamma a13b70d2ac Merge pull request #7 from status-im/use-sdk
use the SDK instead of duplicating classes
2018-11-12 19:45:49 +03:00
37 changed files with 113 additions and 1337 deletions
+4 -2
View File
@@ -5,7 +5,7 @@
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="9">
<list size="10">
<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" />
@@ -15,12 +15,13 @@
<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" />
<item index="9" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="9">
<list size="10">
<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" />
@@ -30,6 +31,7 @@
<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" />
<item index="9" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
</list>
</value>
</option>
+4 -3
View File
@@ -6,8 +6,8 @@ android {
applicationId "im.status.applet_installer_test.appletinstaller"
minSdkVersion 19
targetSdkVersion 27
versionCode 5
versionName "0.0.5"
versionCode 6
versionName "0.0.6"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -24,7 +24,8 @@ 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:29bf055'
implementation 'com.github.status-im:hardwallet-lite-android:c749e5a'
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'
Binary file not shown.
@@ -1,62 +0,0 @@
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;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
public class APDUWrapper {
private byte[] macKeyData;
private byte[] icv;
public APDUWrapper(byte[] macKeyData) {
this.macKeyData = macKeyData;
this.icv = Crypto.NullBytes8.clone();
}
public APDUCommand wrap(APDUCommand cmd) {
try {
int cla = (cmd.getCla() | 0x04) & 0xff;
byte[] data = cmd.getData();
ByteArrayOutputStream macData = new ByteArrayOutputStream();
macData.write(cla);
macData.write(cmd.getIns());
macData.write(cmd.getP1());
macData.write(cmd.getP2());
macData.write(data.length + 8);
macData.write(data);
byte[] icv;
if (Arrays.equals(this.icv, Crypto.NullBytes8)) {
icv = this.icv;
} else {
icv = Crypto.encryptICV(this.macKeyData, this.icv);
}
byte[] mac = Crypto.macFull3des(this.macKeyData, Crypto.appendDESPadding(macData.toByteArray()), icv);
byte[] newData = new byte[data.length + mac.length];
System.arraycopy(data, 0, newData, 0, data.length );
System.arraycopy(mac, 0, newData, data.length, mac.length );
APDUCommand wrapped = new APDUCommand(cla, cmd.getIns(), cmd.getP1(), cmd.getP2(), newData, cmd.getNeedsLE());
this.icv = mac.clone();
return wrapped;
} catch (IOException e) {
throw new RuntimeException("error wrapping APDU command.", e);
}
}
public byte[] getICV() {
return this.icv;
}
}
@@ -6,13 +6,13 @@ 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 im.status.hardwallet_lite_android.io.CardListener;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
public class ActionRunner implements OnCardConnectedListener {
public class ActionRunner implements CardListener {
public final static int ACTION_NONE = 0;
public final static int ACTION_INSTALL = 1;
public final static int ACTION_INSTALL_TEST = 2;
@@ -89,6 +89,7 @@ public class ActionRunner implements OnCardConnectedListener {
@Override
public void onConnected(final CardChannel channel) {
Logger.i("card connected");
if (this.requestedAction != ACTION_NONE) {
Logger.i("waiting 2 seconds to start requested action");
new Timer().schedule(new TimerTask() {
@@ -101,4 +102,9 @@ public class ActionRunner implements OnCardConnectedListener {
Logger.i("no action requested yet");
}
}
@Override
public void onDisconnected() {
Logger.i("card disconnected");
}
}
@@ -1,165 +0,0 @@
package im.status.applet_installer_test.appletinstaller;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import android.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import static android.util.Base64.NO_PADDING;
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) {
byte[] key24 = resizeKey24(cardKey);
try {
byte[] derivationData = new byte[16];
// 2 bytes constant
System.arraycopy(purposeData, 0, derivationData, 0, 2);
// 2 bytes sequence counter + 12 bytes 0x00
System.arraycopy(seq, 0, derivationData, 2, 2);
SecretKeySpec tmpKey = new SecretKeySpec(key24, "DESede");
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, tmpKey, new IvParameterSpec(NullBytes8));
return cipher.doFinal(derivationData);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException("error generating session keys.", e);
} catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
throw new RuntimeException("error generating session keys.", e);
}
}
public static byte[] appendDESPadding(byte[] data) {
int length = data.length + 1;
for (; length % 8 != 0; length++){
}
byte[] newData = new byte[length];
System.arraycopy(data, 0, newData, 0, data.length);
newData[data.length] = (byte)0x80;
return newData;
}
public static boolean verifyCryptogram(byte[] key, byte[] hostChallenge, byte[] cardChallenge, byte[] cardCryptogram) {
byte[] data = new byte[hostChallenge.length + cardChallenge.length];
System.arraycopy(hostChallenge, 0, data, 0, hostChallenge.length);
System.arraycopy(cardChallenge, 0, data, hostChallenge.length, cardChallenge.length);
byte[] paddedData = appendDESPadding(data);
byte[] calculated = mac3des(key, paddedData, NullBytes8);
return Arrays.equals(calculated , cardCryptogram);
}
public static byte[] mac3des(byte[] keyData, byte[] data, byte[] iv) {
try {
SecretKeySpec key = new SecretKeySpec(resizeKey24(keyData), "DESede");
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] result = cipher.doFinal(data, 0, 24);
byte[] tail = new byte[8];
System.arraycopy(result, 16, tail, 0, 8);
return tail;
} catch (GeneralSecurityException e) {
throw new RuntimeException("error calculating mac.", e);
}
}
public static byte[] macFull3des(byte[] keyData, byte[] data, byte[] iv) {
try {
SecretKeySpec keyDes = new SecretKeySpec(resizeKey8(keyData), "DES");
Cipher cipherDes = Cipher.getInstance("DES/CBC/NoPadding");
cipherDes.init(Cipher.ENCRYPT_MODE, keyDes, new IvParameterSpec(iv));
SecretKeySpec keyDes3 = new SecretKeySpec(resizeKey24(keyData), "DESede");
Cipher cipherDes3 = Cipher.getInstance("DESede/CBC/NoPadding");
byte[] des3Iv = iv.clone();
if (data.length > 8) {
byte[] tmp = cipherDes.doFinal(data, 0, data.length - 8);
System.arraycopy(tmp, tmp.length - 8, des3Iv, 0, 8);
}
cipherDes3.init(Cipher.ENCRYPT_MODE, keyDes3, new IvParameterSpec(des3Iv));
byte[] result = cipherDes3.doFinal(data, data.length - 8, 8);
byte[] tail = new byte[8];
System.arraycopy(result, result.length - 8, tail, 0, 8);
return tail;
} catch (GeneralSecurityException e) {
throw new RuntimeException("error generating full triple DES MAC.", e);
}
}
public static byte[] resizeKey24(byte[] keyData) {
byte[] key = new byte[24];
System.arraycopy(keyData, 0, key, 0, 16);
System.arraycopy(keyData, 0, key, 16, 8);
return key;
}
public static byte[] resizeKey8(byte[] keyData) {
byte[] key = new byte[8];
System.arraycopy(keyData, 0, key, 0, 8);
return key;
}
public static byte[] encryptICV(byte[] macKeyData, byte[] mac) {
try {
Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");
SecretKeySpec key = new SecretKeySpec(resizeKey8(macKeyData), "DES");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(mac);
} catch (GeneralSecurityException e) {
throw new RuntimeException("error generating ICV.", e);
}
}
public static byte[] generatePairingKey(char[] pairing) throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
String salt = "Status Hardware Wallet Lite";
PBEKeySpec spec = new PBEKeySpec(pairing, salt.getBytes(), 50000, 32*8);
SecretKey key = skf.generateSecret(spec);
return key.getEncoded();
}
public static byte[] randomBytes(int length) {
SecureRandom random = new SecureRandom();
byte data[] = new byte[length];
random.nextBytes(data);
return data;
}
public static long randomLong(long bound) {
SecureRandom random = new SecureRandom();
return Math.abs(random.nextLong()) % bound;
}
public static String randomToken(int length) {
return Base64.encodeToString(randomBytes(length),NO_PADDING);
}
}
@@ -5,103 +5,90 @@ import android.content.res.AssetManager;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import im.status.applet_installer_test.appletinstaller.apducommands.*;
import im.status.hardwallet_lite_android.io.APDUCommand;
import im.status.hardwallet_lite_android.globalplatform.Load;
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.globalplatform.ApplicationID;
import im.status.hardwallet_lite_android.globalplatform.GlobalPlatformCommandSet;
import im.status.hardwallet_lite_android.wallet.WalletAppletCommandSet;
public class Installer {
private CardChannel plainChannel;
private SecureChannel channel;
private Keys cardKeys;
private AssetManager assets;
private String capPath;
private boolean testSecrets;
static final byte[] cardKeyData = HexUtils.hexStringToByteArray("404142434445464748494a4b4c4d4e4f");
static final byte[] PACKAGE_AID = HexUtils.hexStringToByteArray("53746174757357616C6C6574");
static final byte[] WALLET_AID = HexUtils.hexStringToByteArray("53746174757357616C6C6574417070");
static final byte[] NDEF_APPLET_AID = HexUtils.hexStringToByteArray("53746174757357616C6C65744E4643");
static final byte[] NDEF_INSTANCE_AID = HexUtils.hexStringToByteArray("D2760000850101");
private GlobalPlatformCommandSet cmdSet;
private boolean testSecrets;
public Installer(CardChannel channel, AssetManager assets, String capPath, boolean testSecrets) {
this.plainChannel = 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");
Logger.i("installation started...");
long startTime = System.currentTimeMillis();
Select discover = new Select(new byte[0]);
APDUResponse resp = this.send("discover", discover.getCommand());
Logger.i("auto select sdaid...");
cmdSet = new GlobalPlatformCommandSet(this.plainChannel);
ApplicationID sdaid = new ApplicationID(cmdSet.select().checkOK().getData());
byte[] sdaid = this.getSDAID(resp.getData());
Logger.d("sdaid: " + HexUtils.byteArrayToHexString(sdaid));
SecureRandom random = new SecureRandom();
byte hostChallenge[] = new byte[8];
random.nextBytes(hostChallenge);
Logger.i("initialize update...");
cmdSet.initializeUpdate(hostChallenge).checkOK();
byte[] hostChallenge = InitializeUpdate.generateChallenge();
InitializeUpdate init = new InitializeUpdate(hostChallenge);
resp = this.send("init update", init.getCommand());
Session session = init.verifyResponse(this.cardKeys, resp);
Keys sessionKeys = session.getKeys();
this.channel = new SecureChannel(this.plainChannel, sessionKeys);
ExternalAuthenticate auth = new ExternalAuthenticate(sessionKeys.getEncKeyData(), session.getCardChallenge(), hostChallenge);
resp = this.send("external auth", auth.getCommand());
if (!auth.checkResponse(resp)) {
throw new APDUException(resp.getSw(), "bad external authenticate response");
}
//Status status = new Status(Status.P1_EXECUTABLE_LOAD_FILES_AND_MODULES);
//resp = this.send("status", status.getCommand());
byte[] packageAID = HexUtils.hexStringToByteArray("53746174757357616C6C6574");
byte[] walletAID = HexUtils.hexStringToByteArray("53746174757357616C6C6574417070");
byte[] ndefAppletAID = HexUtils.hexStringToByteArray("53746174757357616C6C65744E4643");
byte[] ndefInstanceAID = HexUtils.hexStringToByteArray("D2760000850101");
Logger.i("external authenticate...");
cmdSet.externalAuthenticate(hostChallenge).checkOK();
Delete deleteNDEFApplet = new Delete(ndefInstanceAID);
Logger.i("sending delete (NDEF applet)");
this.channel.send(deleteNDEFApplet.getCommand());
Logger.i("delete NDEF instance AID...");
cmdSet.delete(NDEF_INSTANCE_AID).checkSW(APDUResponse.SW_OK, APDUResponse.SW_REFERENCED_DATA_NOT_FOUND);
Delete deleteApplet = new Delete(walletAID);
Logger.i("sending delete (applet)");
this.channel.send(deleteApplet.getCommand());
Logger.i("delete wallet AID...");
cmdSet.delete(WALLET_AID).checkSW(APDUResponse.SW_OK, APDUResponse.SW_REFERENCED_DATA_NOT_FOUND);
Delete deletePkg = new Delete(packageAID);
Logger.i("sending delete (pkg)");
this.channel.send(deletePkg.getCommand());
Logger.i("delete package AID...");
cmdSet.delete(PACKAGE_AID).checkSW(APDUResponse.SW_OK, APDUResponse.SW_REFERENCED_DATA_NOT_FOUND);
Logger.i("install for load...");
cmdSet.installForLoad(PACKAGE_AID, sdaid.getAID()).checkSW(APDUResponse.SW_OK, APDUResponse.SW_REFERENCED_DATA_NOT_FOUND);
InstallForLoad preLoad = new InstallForLoad(packageAID, sdaid);
this.send("perform for load", preLoad.getCommand());
//URL url = this.getClass().getClassLoader().getResource("wallet.cap");
InputStream in = this.assets.open(this.capPath);
Load load = new Load(in);
APDUCommand loadCmd;
while((loadCmd = load.getCommand()) != null) {
this.send("load " + load.getCount() + "/35", loadCmd);
byte[] block;
int steps = load.blocksCount();
while((block = load.nextDataBlock()) != null) {
int count = load.getCount() - 1;
Logger.i(String.format("load %d/%d...", count + 1, steps));
cmdSet.load(block, count, load.hasMore()).checkOK();
}
InstallForInstall installNDEF = new InstallForInstall(packageAID, ndefAppletAID, ndefInstanceAID, HexUtils.hexStringToByteArray("0024d40f12616e64726f69642e636f6d3a706b67696d2e7374617475732e657468657265756d"));
this.send("perform and make selectable (NDEF)", installNDEF.getCommand());
Logger.i("install for install ndef...");
byte[] params = HexUtils.hexStringToByteArray("0024d40f12616e64726f69642e636f6d3a706b67696d2e7374617475732e657468657265756d");
cmdSet.installForInstall(PACKAGE_AID, NDEF_APPLET_AID, NDEF_INSTANCE_AID, params).checkOK();
InstallForInstall install = new InstallForInstall(packageAID, walletAID, walletAID, new byte[0]);
this.send("perform and make selectable (wallet)", install.getCommand());
Logger.i("install for install wallet...");
cmdSet.installForInstall(PACKAGE_AID, WALLET_AID, WALLET_AID, new byte[0]).checkOK();
personalizeApplet();
this.personalizeApplet();
long duration = System.currentTimeMillis() - startTime;
Logger.i(String.format("installation completed in %d seconds", duration / 1000));
Logger.i(String.format("\n\ninstallation completed in %d seconds", duration / 1000));
}
private void personalizeApplet() throws NoSuchAlgorithmException, InvalidKeySpecException, APDUException, IOException {
@@ -113,35 +100,4 @@ public class Installer {
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 == 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");
throw new APDUException(resp.getSw(), "security confition not satisfied. card might be blocked " + description);
}
if (!resp.isOK()) {
throw new APDUException(resp.getSw(), "bad response for command " + description);
}
return resp;
}
private byte[] getSDAID(byte[] data) throws IOException, APDUException {
Tlv tlv = new Tlv(data);
tlv = tlv.find((byte) 0x6F);
if (tlv == null) {
throw new APDUException("error searching for tag 0x6F in discover response");
}
tlv = tlv.find((byte) 0x84);
if (tlv == null) {
throw new APDUException("error searching for tag 0x84 in discover response");
}
return tlv.getValue();
}
}
@@ -1,19 +0,0 @@
package im.status.applet_installer_test.appletinstaller;
public class Keys {
public byte[] encKeyData;
public byte[] macKeyData;
public Keys(byte[] encKeyData, byte[] macKeyData) {
this.encKeyData = encKeyData;
this.macKeyData = macKeyData;
}
public byte[] getEncKeyData() {
return encKeyData;
}
public byte[] getMacKeyData() {
return macKeyData;
}
}
@@ -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;
@@ -38,7 +39,7 @@ public class MainActivity extends AppCompatActivity implements UILogger {
AssetManager assets = this.getAssets();
this.actionRunner = new ActionRunner(assets, "wallet.cap");
this.cardManager = new CardManager();
this.cardManager.setOnCardConnectedListener(this.actionRunner);
this.cardManager.setCardListener(this.actionRunner);
cardManager.start();
textViewScroll = (ScrollView) findViewById(R.id.textViewScroll);
@@ -4,12 +4,8 @@ import android.util.Log;
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.jce.ECNamedCurveTable;
import org.spongycastle.jce.spec.ECParameterSpec;
import org.spongycastle.math.ec.FixedPointUtil;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -23,18 +19,9 @@ public class PerfTest {
private long loadKeysTime = 0;
private long loginTime = 0;
private long signTime = 0;
private long deriveKeyFromParent = 0;
private long getStatusTime = 0;
private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
public static final ECDomainParameters CURVE;
static {
FixedPointUtil.precompute(CURVE_PARAMS.getG(), 12);
CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(), CURVE_PARAMS.getH());
}
// 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};
static final String BIP44_WALLET_PATH = "m/44'/60'/0'/0/0";
public PerfTest(CardChannel cardChannel) {
this.cardChannel = cardChannel;
@@ -43,7 +30,9 @@ public class PerfTest {
public void test() throws Exception {
cmdSet = new WalletAppletCommandSet(cardChannel);
cmdSet.select().checkOK();
cmdSet.autoPair(Secrets.testSecrets().getPairingToken());
String pairingPassword = "WalletAppletTest";
cmdSet.autoPair(cmdSet.pairingPasswordToSecret(pairingPassword));
openSecureChannelTime = System.currentTimeMillis();
cmdSet.autoOpenSecureChannel();
openSecureChannelTime = System.currentTimeMillis() - openSecureChannelTime;
@@ -73,8 +62,7 @@ public class PerfTest {
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");
Logger.i("Transaction signature: " + signTime + "ms");
}
private void getStatus() throws Exception {
@@ -90,10 +78,8 @@ public class PerfTest {
cmdSet.select().checkOK();
cmdSet.autoOpenSecureChannel();
cmdSet.verifyPIN("000000").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();
cmdSet.exportKey("m/43'/60'/1581'/0'/0",false, false).checkOK();
cmdSet.exportKey("m/43'/60'/1581'/1'/0",false, false).checkOK();
loginTime = System.currentTimeMillis() - time;
}
@@ -106,7 +92,7 @@ public class PerfTest {
cmdSet.loadKey(keyPair, false, chainCode).checkOK();
long time = System.currentTimeMillis();
cmdSet.deriveKey(BIP44_PATH, WalletAppletCommandSet.DERIVE_P1_SOURCE_CURRENT).checkOK();
cmdSet.deriveKey(BIP44_WALLET_PATH).checkOK();
loadKeysTime = System.currentTimeMillis() - time;
}
@@ -115,9 +101,6 @@ public class PerfTest {
cmdSet.select().checkOK();
cmdSet.autoOpenSecureChannel();
cmdSet.verifyPIN("000000").checkOK();
deriveKeyFromParent = System.currentTimeMillis();
cmdSet.deriveKey(new byte[] { (byte) 0x00, 0x00, 0x00, 0x00}, WalletAppletCommandSet.DERIVE_P1_SOURCE_PARENT).checkOK();
deriveKeyFromParent = System.currentTimeMillis() - deriveKeyFromParent;
cmdSet.sign("any32bytescanbeahashyouknowthat!".getBytes()).checkOK();
signTime = System.currentTimeMillis() - time;
}
@@ -1,16 +1,26 @@
package im.status.applet_installer_test.appletinstaller;
import android.support.annotation.NonNull;
import android.util.Base64;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import static android.util.Base64.NO_PADDING;
public class Secrets {
private String pin;
private String puk;
private String pairingPassword;
private byte[] pairingToken;
private static long PIN_BOUND = 999999L;
private static long PUK_BOUND = 999999999999L;
public Secrets(String pin, String puk, String pairingPassword, byte[] pairingToken) {
this.pin = pin;
this.puk = puk;
@@ -20,10 +30,10 @@ public class Secrets {
@NonNull
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 pairingPassword = randomToken(12);
byte[] pairingToken = generatePairingKey(pairingPassword.toCharArray());
long pinNumber = randomLong(PIN_BOUND);
long pukNumber = randomLong(PUK_BOUND);
String pin = String.format("%06d", pinNumber);
String puk = String.format("%012d", pukNumber);
@@ -32,7 +42,7 @@ public class Secrets {
public static Secrets testSecrets() throws NoSuchAlgorithmException, InvalidKeySpecException {
String pairingPassword = "WalletAppletTest";
byte[] pairingToken = Crypto.generatePairingKey(pairingPassword.toCharArray());
byte[] pairingToken = generatePairingKey(pairingPassword.toCharArray());
return new Secrets("000000", "123456789012", pairingPassword, pairingToken);
}
@@ -51,4 +61,30 @@ public class Secrets {
public byte[] getPairingToken() {
return pairingToken;
}
}
public static byte[] randomBytes(int length) {
SecureRandom random = new SecureRandom();
byte data[] = new byte[length];
random.nextBytes(data);
return data;
}
public static String randomToken(int length) {
return Base64.encodeToString(randomBytes(length), NO_PADDING);
}
public static long randomLong(long bound) {
SecureRandom random = new SecureRandom();
return Math.abs(random.nextLong()) % bound;
}
public static byte[] generatePairingKey(char[] pairing) throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
String salt = "Status Hardware Wallet Lite";
PBEKeySpec spec = new PBEKeySpec(pairing, salt.getBytes(), 50000, 32*8);
SecretKey key = skf.generateSecret(spec);
return key.getEncoded();
}
}
@@ -1,24 +0,0 @@
package im.status.applet_installer_test.appletinstaller;
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 {
private CardChannel channel;
private APDUWrapper wrapper;
public SecureChannel(CardChannel channel, Keys keys) {
this.channel = channel;
this.wrapper = new APDUWrapper(keys.getMacKeyData());
}
public APDUResponse send(APDUCommand cmd) throws IOException {
Logger.d(String.format("WRAPPING %s %n", HexUtils.byteArrayToHexString(cmd.serialize())), false);
APDUCommand wrappedCommand = this.wrapper.wrap(cmd);
Logger.d(String.format("WRAPPED %s %n", HexUtils.byteArrayToHexString(wrappedCommand.serialize())), false);
return this.channel.send(wrappedCommand);
}
}
@@ -1,19 +0,0 @@
package im.status.applet_installer_test.appletinstaller;
public class Session {
private Keys keys;
private byte[] cardChallenge;
public Session(Keys keys, byte[] cardChallenge) {
this.keys = keys;
this.cardChallenge = cardChallenge;
}
public Keys getKeys() {
return keys;
}
public byte[] getCardChallenge() {
return cardChallenge;
}
}
@@ -1,52 +0,0 @@
package im.status.applet_installer_test.appletinstaller;
import java.io.IOException;
public class Tlv {
private byte tag;
private byte[] value;
public Tlv(byte[] value) {
this((byte) 0x00, value);
}
public Tlv(byte tag, byte[] value) {
this.tag = tag;
this.value = value;
}
public Tlv find(byte tag) throws IOException {
int offset = 0;
while(offset + 2 < value.length) {
byte childTag = this.value[offset];
offset++;
int childLength = this.value[offset] & 0xff;
offset++;
if (offset + childLength - 1 >= this.value.length) {
return null;
}
byte[] data = new byte[childLength];
System.arraycopy(this.value, offset, data, 0, childLength);
offset += childLength;
if (childTag == tag) {
return new Tlv(childTag, data);
}
}
return null;
}
public byte[] getValue() {
return value;
}
public byte getTag() {
return tag;
}
}
@@ -1,26 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import im.status.hardwallet_lite_android.io.APDUCommand;
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 = 0x00;
private byte[] aid;
public Delete(byte[] aid) {
this.aid = aid;
}
public APDUCommand getCommand() {
byte[] data = new byte[this.aid.length + 2];
data[0] = 0x4F;
data[1] = (byte) this.aid.length;
System.arraycopy(this.aid, 0, data, 2, this.aid.length);
return new APDUCommand(CLA, INS, P1, P2, data);
}
}
@@ -1,39 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
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;
public static int INS = 0x82;
public static int P1 = 0x01;
public static int P2 = 0x00;
private byte[] encKeyData;
private byte[] cardChallenge;
private byte[] hostChallenge;
public ExternalAuthenticate(byte[] encKeyData, byte[] cardChallenge, byte[] hostChallenge) {
this.encKeyData = encKeyData;
this.cardChallenge = cardChallenge;
this.hostChallenge = hostChallenge;
}
public APDUCommand getCommand() {
return new APDUCommand(CLA, INS, P1, P2, this.getHostCryptogram());
}
public byte[] getHostCryptogram() {
byte[] data = new byte[this.cardChallenge.length + this.hostChallenge.length];
System.arraycopy(cardChallenge, 0, data, 0, cardChallenge.length);
System.arraycopy(hostChallenge, 0, data, cardChallenge.length, hostChallenge.length);
byte[] paddedData = Crypto.appendDESPadding(data);
return Crypto.mac3des(this.encKeyData, paddedData, Crypto.NullBytes8);
}
public boolean checkResponse(APDUResponse resp) {
return resp.getSw() == APDUResponse.SW_OK;
}
}
@@ -1,8 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
public class GetData {
public static final int CLA = 0x80;
public static final int INS = 0x50;
public static final int P1 = 0;
public static final int P2 = 0;
}
@@ -1,70 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import im.status.applet_installer_test.appletinstaller.Crypto;
import im.status.applet_installer_test.appletinstaller.Keys;
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;
public static final int INS = 0x50;
public static final int P1 = 0;
public static final int P2 = 0;
public static byte[] DERIVATION_PURPOSE_ENC = new byte[]{(byte) 0x01, (byte) 0x82};
public static byte[] DERIVATION_PURPOSE_MAC = new byte[]{(byte) 0x01, (byte) 0x01};
public static byte[] DERIVATION_PURPOSE_DEK = new byte[]{(byte) 0x01, (byte) 0x81};
private byte[] hostChallenge;
public InitializeUpdate(byte[] challenge) {
this.hostChallenge = challenge;
}
public APDUCommand getCommand() {
return new APDUCommand(CLA, INS, P1, P2, this.hostChallenge, true);
}
public static byte[] generateChallenge() {
return Crypto.randomBytes(8);
}
public Session verifyResponse(Keys cardKeys, APDUResponse resp) throws APDUException {
if (resp.getSw() == APDUResponse.SW_SECURITY_CONDITION_NOT_SATISFIED) {
throw new APDUException(resp.getSw(), "security confition not satisfied");
}
if (resp.getSw() == APDUResponse.SW_AUTHENTICATION_METHOD_BLOCKED) {
throw new APDUException(resp.getSw(), "authentication method blocked");
}
byte[] data = resp.getData();
if (data.length != 28) {
throw new APDUException(resp.getSw(), String.format("bad data length, expected 28, got %d", data.length));
}
byte[] cardChallenge = new byte[8];
System.arraycopy(data, 12, cardChallenge, 0, 8);
byte[] cardCryptogram = new byte[8];
System.arraycopy(data, 20, cardCryptogram, 0, 8);
byte[] seq = new byte[2];
System.arraycopy(data, 12, seq, 0, 2);
byte[] sessionEncKey = Crypto.deriveKey(cardKeys.getEncKeyData(), seq, DERIVATION_PURPOSE_ENC);
byte[] sessionMacKey = Crypto.deriveKey(cardKeys.getMacKeyData(), seq, DERIVATION_PURPOSE_MAC);
Keys sessionKeys = new Keys(sessionEncKey, sessionMacKey);
boolean verified = Crypto.verifyCryptogram(sessionKeys.getEncKeyData(), this.hostChallenge, cardChallenge, cardCryptogram);
if (!verified) {
throw new APDUException("error verifying card cryptogram.");
}
return new Session(sessionKeys, cardChallenge);
}
}
@@ -1,52 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import im.status.hardwallet_lite_android.io.APDUCommand;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class InstallForInstall {
public static final int CLA = 0x80;
public static final int INS = 0xE6;
public static final int P1 = 0x0C;
public static final int P2 = 0;
private byte[] packageAID;
private byte[] appletAID;
private byte[] instanceAID;
private byte[] params;
public InstallForInstall(byte[] packageAID, byte[] appletAID, byte[] instanceAID, byte[] params) {
this.packageAID = packageAID;
this.appletAID = appletAID;
this.instanceAID = instanceAID;
this.params = params;
}
public APDUCommand getCommand() throws IOException {
ByteArrayOutputStream data = new ByteArrayOutputStream();
data.write(this.packageAID.length);
data.write(this.packageAID);
data.write(this.appletAID.length);
data.write(this.appletAID);
data.write(this.instanceAID.length);
data.write(this.instanceAID);
byte[] privileges = new byte[]{0x00};
data.write(privileges.length);
data.write(privileges);
byte[] fullParams = new byte[2 + params.length];
fullParams[0] = (byte) 0xC9;
fullParams[1] = (byte) params.length;
System.arraycopy(params, 0, fullParams, 2, params.length);
data.write(fullParams.length);
data.write(fullParams);
// empty perform token
data.write(0x00);
return new APDUCommand(CLA, INS, P1, P2, data.toByteArray() );
}
}
@@ -1,36 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import im.status.hardwallet_lite_android.io.APDUCommand;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class InstallForLoad {
public static final int CLA = 0x80;
public static final int INS = 0xE6;
public static final int P1 = 0x02;
public static final int P2 = 0;
private byte[] aid;
private byte[] sdaid;
public InstallForLoad(byte[] aid, byte[] sdaid) {
this.aid = aid;
this.sdaid = sdaid;
}
public APDUCommand getCommand() throws IOException {
ByteArrayOutputStream data = new ByteArrayOutputStream();
data.write(this.aid.length);
data.write(this.aid);
data.write(this.sdaid.length);
data.write(this.sdaid);
// empty hash length and hash
data.write(0x00);
data.write(0x00);
data.write(0x00);
return new APDUCommand(CLA, INS, P1, P2, data.toByteArray());
}
}
@@ -1,132 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import im.status.hardwallet_lite_android.io.APDUCommand;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class Load {
public static final int CLA = 0x80;
public static final int INS = 0xE8;
private static String[] fileNames = {"Header", "Directory", "Import", "Applet",
"Class", "Method", "StaticField", "Export", "ConstantPool", "RefLocation"};
private String path;
private int offset;
private int count;
private byte[] fullData;
public Load(InputStream in) throws FileNotFoundException, IOException {
this.path = path;
this.offset = 0;
this.count = 0;
Map<String, byte[]> files = this.loadFiles(in);
in.close();
this.fullData = this.getCode(files);
}
public Map<String, byte[]> loadFiles(InputStream in) throws IOException {
Map<String, byte[]> files = new LinkedHashMap<>();
ZipInputStream zip = new ZipInputStream(in);
ZipEntry entry = zip.getNextEntry();
while(entry != null) {
ByteArrayOutputStream data = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int count;
while ((count = zip.read(buf)) != -1) {
data.write(buf, 0, count);
}
String name = baseName(entry.getName());
files.put(name, data.toByteArray());
entry = zip.getNextEntry();
}
return files;
}
private String baseName(String path) {
String[] parts = path.split("[/.]");
return parts[parts.length - 2];
}
public APDUCommand getCommand() {
int blockSize = 247; // 255 - 8 bytes for MAC
if (this.offset >= this.fullData.length) {
return null;
}
int rangeEnd = this.offset + blockSize;
if (rangeEnd >= this.fullData.length) {
rangeEnd = this.fullData.length;
}
int size = rangeEnd - offset;
byte[] data = new byte[size];
System.arraycopy(this.fullData, this.offset, data, 0, size);
boolean isLast = this.offset + size >= this.fullData.length;
int p1 = isLast ? 0x80 : 0;
APDUCommand cmd = new APDUCommand(CLA, INS, p1, this.count, data);
this.offset += size;
this.count++;
return cmd;
}
private byte[] encodeFullLength(int length) {
if (length < 0x80) {
return new byte[]{(byte) length};
} else if (length < 0xFF) {
return new byte[]{(byte) 0x81, (byte) length};
} else if (length < 0xFFFF) {
return new byte[]{
(byte) 0x82,
(byte) ((length & 0xFF00) >> 8),
(byte) (length & 0xFF),
};
} else {
return new byte[]{
(byte) 0x83,
(byte) ((length & 0xFF0000) >> 16),
(byte) ((length & 0xFF00) >> 8),
(byte) (length & 0xFF),
};
}
}
public byte[] getCode(Map<String, byte[]> files) throws IOException {
ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
for (String name : fileNames) {
byte[] fileData = files.get(name);
if (fileData == null) {
continue;
}
dataStream.write(fileData);
}
byte[] data = dataStream.toByteArray();
byte[] encodedFullLength = encodeFullLength(data.length);
byte[] fullData = new byte[1 + encodedFullLength.length + data.length];
fullData[0] = (byte) 0xC4;
System.arraycopy(encodedFullLength, 0, fullData, 1, encodedFullLength.length);
System.arraycopy(data, 0, fullData, 1 + encodedFullLength.length, data.length);
return fullData;
}
public int getCount() {
return count;
}
}
@@ -1,20 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import im.status.hardwallet_lite_android.io.APDUCommand;
public class Select {
private static final int CLA = 0x00;
private static final int INS = 0xA4;
private static final int P1 = 0x04;
private static final int P2 = 0x00; // first occurrence
private byte[] aid;
public Select(byte[] aid) {
this.aid = aid;
}
public APDUCommand getCommand() {
return new APDUCommand(CLA, INS, P1, P2, this.aid);
}
}
@@ -1,26 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import im.status.hardwallet_lite_android.io.APDUCommand;
public class Status {
private static final int CLA = 0x80;
private static final int INS = 0xF2;
public static final int P1_ISSUER_SECURITY_DOMAIN = 0x80;
public static final int P1_APPLICATIONS = 0x40;
public static final int P1_EXECUTABLE_LOAD_FILES = 0x20;
public static final int P1_EXECUTABLE_LOAD_FILES_AND_MODULES = 0x10;
private static final int P2 = 0x02;
private int p1;
public Status(int p1) {
this.p1 = p1;
}
public APDUCommand getCommand() {
byte[] data = new byte[]{0x4F, 0x00};
return new APDUCommand(CLA, INS, this.p1, P2, data, true);
}
}
@@ -1,28 +0,0 @@
package im.status.applet_installer_test.appletinstaller;
import org.junit.Test;
import java.io.IOException;
import static org.junit.Assert.*;
public class APDUCommandTest {
@Test
public void serialize() throws IOException {
int cla = 0x80;
int ins = 0x50;
int p1 = 0;
int p2 = 0;
byte[] data = HexUtils.hexStringToByteArray("84762336c5187fe8");
APDUCommand c = new APDUCommand(cla, ins, p1, p2, (byte[])data, true);
String expected = "805000000884762336C5187FE800";
String actual = HexUtils.byteArrayToHexString(c.serialize());
assertEquals(expected, actual);
c = new APDUCommand(cla, ins, p1, p2, (byte[])data);
expected = "805000000884762336C5187FE8";
actual = HexUtils.byteArrayToHexString(c.serialize());
assertEquals(expected, actual);
}
}
@@ -1,21 +0,0 @@
package im.status.applet_installer_test.appletinstaller;
import org.junit.Test;
import static org.junit.Assert.*;
public class APDUResponseTest {
@Test
public void parsing() {
byte[] apdu = HexUtils.hexStringToByteArray("000002650183039536622002003b5e508f751c0af3016e3fbc23d3a69000");
APDUResponse resp = new APDUResponse(apdu);
assertEquals(0x9000, resp.getSw());
assertEquals(0x90, resp.getSw1());
assertEquals(0x00, resp.getSw2());
String expected = "000002650183039536622002003B5E508F751C0AF3016E3FBC23D3A6";
assertEquals(expected, HexUtils.byteArrayToHexString(resp.getData()));
assertTrue(resp.isOK());
}
}
@@ -1,54 +0,0 @@
package im.status.applet_installer_test.appletinstaller;
import org.junit.Test;
import java.io.IOException;
import static org.junit.Assert.*;
public class APDUWrapperTest {
@Test
public void wrap() throws IOException {
byte[] macKeyData = HexUtils.hexStringToByteArray("2983BA77D709C2DAA1E6000ABCCAC951");
byte[] data = HexUtils.hexStringToByteArray("1d4de92eaf7a2c9f");
APDUCommand cmd = new APDUCommand(0x84, 0x82, 0x01, 0x00, data);
APDUWrapper w = new APDUWrapper(macKeyData);
// check null icv
assertEquals(HexUtils.byteArrayToHexString(Crypto.NullBytes8), HexUtils.byteArrayToHexString(w.getICV()));
APDUCommand wrapped = w.wrap(cmd);
byte[] result = wrapped.serialize();
String expected = "84820100101D4DE92EAF7A2C9F8F9B0DF681C1D3EC";
assertEquals(expected, HexUtils.byteArrayToHexString(result));
// second command
// check icv from previous mac
assertEquals("8F9B0DF681C1D3EC", HexUtils.byteArrayToHexString(w.getICV()));
data = HexUtils.hexStringToByteArray("4F00");
cmd = new APDUCommand(0x80, 0xF2, 0x80, 0x02, data, true);
wrapped = w.wrap(cmd);
result = wrapped.serialize();
expected = "84F280020A4F0030F149209E17B39700";
assertEquals(expected, HexUtils.byteArrayToHexString(result));
// third command
// check icv from previous mac
assertEquals("30F149209E17B397", HexUtils.byteArrayToHexString(w.getICV()));
data = HexUtils.hexStringToByteArray("4F00");
cmd = new APDUCommand(0x80, 0xF2, 0x40, 0x02, data, true);
wrapped = w.wrap(cmd);
result = wrapped.serialize();
expected = "84F240020A4F000D9B2D4E4365B5BD00";
assertEquals(expected, HexUtils.byteArrayToHexString(result));
}
}
@@ -1,63 +0,0 @@
package im.status.applet_installer_test.appletinstaller;
import org.junit.Test;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import static org.junit.Assert.*;
import im.status.applet_installer_test.appletinstaller.apducommands.InitializeUpdate;
public class CryptoTest {
@Test
public void deriveKey() {
byte[] cardKey = HexUtils.hexStringToByteArray("404142434445464748494a4b4c4d4e4f");
byte[] seq = HexUtils.hexStringToByteArray("0065");
byte[] encKey = Crypto.deriveKey(cardKey, seq, InitializeUpdate.DERIVATION_PURPOSE_ENC);
String expectedEncKey = "85E72AAF47874218A202BF5EF891DD21";
assertEquals(expectedEncKey, HexUtils.byteArrayToHexString(encKey));
byte[] macKey = Crypto.deriveKey(cardKey, seq, InitializeUpdate.DERIVATION_PURPOSE_MAC);
String expectedMacKey = "309CF99E164F3A97F3E5017FF540A79F";
assertEquals(expectedMacKey, HexUtils.byteArrayToHexString(macKey));
byte[] dekKey = Crypto.deriveKey(cardKey, seq, InitializeUpdate.DERIVATION_PURPOSE_DEK);
String expectedDekKey = "93D08F8025242C4D775D69B9F16C939B";
assertEquals(expectedDekKey, HexUtils.byteArrayToHexString(dekKey));
}
@Test
public void appendDESPadding() {
byte[] data = HexUtils.hexStringToByteArray("AABB");
byte[] result = Crypto.appendDESPadding(data);
String expected = "AABB800000000000";
assertEquals(expected, HexUtils.byteArrayToHexString(result));
}
@Test
public void verifyCryptogram() {
byte[] encKey = HexUtils.hexStringToByteArray("16B5867FF50BE7239C2BF1245B83A362");
byte[] hostChallenge = HexUtils.hexStringToByteArray("32da078d7aac1cff");
byte[] cardChallenge = HexUtils.hexStringToByteArray("007284f64a7d6465");
byte[] cardCryptogram = HexUtils.hexStringToByteArray("05c4bb8a86014e22");
assertTrue(Crypto.verifyCryptogram(encKey, hostChallenge, cardChallenge, cardCryptogram));
}
@Test
public void macFull3des() {
byte[] key = HexUtils.hexStringToByteArray("5b02e75ad63190aece0622936f11abab");
byte[] data = HexUtils.hexStringToByteArray("8482010010810b098a8fbb88da800000");
String expected = "5271D7174A5A166A";
byte[] result = Crypto.macFull3des(key, data, Crypto.NullBytes8);
assertEquals(expected, HexUtils.byteArrayToHexString(result));
}
@Test
public void generatePairingKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
String pairing = "zoynhcfz1xJjYqxO";
byte[] key = Crypto.generatePairingKey(pairing.toCharArray());
String expected = "BF8D606E2FE9292B633DF5E31563AF88928EEBB71FDBCBEF6CADECAA00D7874F";
assertEquals(expected, HexUtils.byteArrayToHexString(key));
}
}
@@ -1,46 +0,0 @@
package im.status.applet_installer_test.appletinstaller;
import java.io.IOException;
import org.junit.Test;
import static org.junit.Assert.*;
public class TlvTest {
@Test
public void find() throws IOException {
byte[] data = HexUtils.hexStringToByteArray("C102BBCCC203DDEE11C30201");
Tlv tlv = new Tlv(data);
Tlv child1 = tlv.find((byte) 0xC1);
String expected = "BBCC";
assertEquals(expected, HexUtils.byteArrayToHexString(child1.getValue()));
Tlv child2 = tlv.find((byte) 0xC2);
expected = "DDEE11";
assertEquals(expected, HexUtils.byteArrayToHexString(child2.getValue()));
// tag exists, length is bigger than available data
assertNull(tlv.find((byte) 0xC3));
// not found
assertNull(tlv.find((byte) 0xFF));
// not found
assertNull(child2.find((byte) 0xFF));
}
@Test
public void findAID() throws IOException {
byte[] data = HexUtils.hexStringToByteArray("6f5c8408a000000151000000a550734a06072a864886fc6b01600c060a2a864886fc6b02020201630906072a864886fc6b03640b06092a864886fc6b040255650b06092a864886fc6b020103660c060a2b060104012a026e01039f6501ff9000");
Tlv tlv = new Tlv(data);
Tlv child1 = tlv.find((byte) 0x6F);
assertNotNull(child1);
Tlv child2 = child1.find((byte) 0x84);
assertNotNull(child2);
String expected = "A000000151000000";
assertEquals(expected, HexUtils.byteArrayToHexString(child2.getValue()));
}
}
@@ -1,21 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import org.junit.Test;
import org.spongycastle.util.encoders.Hex;
import java.io.IOException;
import im.status.applet_installer_test.appletinstaller.HexUtils;
import static org.junit.Assert.*;
public class DeleteTest {
@Test
public void getCommand() throws IOException {
byte[] aid = HexUtils.hexStringToByteArray("53746174757357616C6C6574");
Delete delete = new Delete(aid);
String expected = "80E400000E4F0C53746174757357616C6C6574";
byte[] apdu = delete.getCommand().serialize();
assertEquals(expected, HexUtils.byteArrayToHexString(apdu));
}
}
@@ -1,38 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import org.junit.Test;
import java.io.IOException;
import im.status.applet_installer_test.appletinstaller.HexUtils;
import static org.junit.Assert.*;
public class ExternalAuthenticateTest {
@Test
public void getHostCryptogram() {
byte[] encKeyData = HexUtils.hexStringToByteArray("0EF72A1065236DD6CAC718D5E3F379A4");
byte[] cardChallenge = HexUtils.hexStringToByteArray("0076a6c0d55e9535");
byte[] hostChallenge = HexUtils.hexStringToByteArray("266195e638da1b95");
ExternalAuthenticate auth = new ExternalAuthenticate(encKeyData, cardChallenge, hostChallenge);
String expectedHostCryptogram = "45A5F48DAE68203C";
byte[] hostCryptogram = auth.getHostCryptogram();
assertEquals(expectedHostCryptogram, HexUtils.byteArrayToHexString(hostCryptogram));
}
@Test
public void getCommand() throws IOException {
byte[] encKeyData = HexUtils.hexStringToByteArray("8D289AFE0AB9C45B1C76DEEA182966F4");
byte[] cardChallenge = HexUtils.hexStringToByteArray("000f3fd65d4d6e45");
byte[] hostChallenge = HexUtils.hexStringToByteArray("cf307b6719bf224d");
ExternalAuthenticate auth = new ExternalAuthenticate(encKeyData, cardChallenge, hostChallenge);
String expectedAPDU = "84820100087702AC6CE46A47F0";
byte[] apdu = auth.getCommand().serialize();
assertEquals(expectedAPDU, HexUtils.byteArrayToHexString(apdu));
}
}
@@ -1,59 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import org.junit.Test;
import java.io.IOException;
import im.status.applet_installer_test.appletinstaller.HexUtils;
import im.status.applet_installer_test.appletinstaller.Keys;
import static org.junit.Assert.*;
public class InitializeUpdateTest {
@Test
public void getCommand() throws IOException {
byte[] challenge = HexUtils.hexStringToByteArray("2d315d5ffc616d10");
InitializeUpdate init = new InitializeUpdate(challenge);
APDUCommand cmd = init.getCommand();
assertEquals(0x80, cmd.getCla());
assertEquals(0x50, cmd.getIns());
assertEquals(0, cmd.getP1());
assertEquals(0, cmd.getP2());
assertEquals(challenge, cmd.getData());
String expectedAPDU = "80500000082D315D5FFC616D1000";
byte[] apdu = cmd.serialize();
assertEquals(expectedAPDU, HexUtils.byteArrayToHexString(apdu));
}
@Test
public void validateResponse_BadResponse() throws APDUException {
byte[] apdu = HexUtils.hexStringToByteArray("000002650183039536622002003b5e508f751c0af3016e3fbc23d3a66982");
APDUResponse resp = new APDUResponse(apdu);
byte[] challenge = InitializeUpdate.generateChallenge();
InitializeUpdate init = new InitializeUpdate(challenge);
try {
init.verifyResponse(new Keys(new byte[]{}, new byte[]{}), resp);
fail("expected APDUException to be thrown");
} catch (APDUException e) {
assertEquals(0x6982, e.sw);
}
}
//TODO: reimplement test
//@Test
//public void validateResponse_GoodResponse() throws APDUException {
// byte[] encKey = HexUtils.hexStringToByteArray("16B5867FF50BE7239C2BF1245B83A362");
// byte[] challenge = HexUtils.hexStringToByteArray("f0467f908e5ca23f");
// InitializeUpdate init = new InitializeUpdate(challenge);
// byte[] apdu = HexUtils.hexStringToByteArray("000002650183039536622002000de9c62ba1c4c8e55fcb91b6654ce49000");
// APDUResponse resp = new APDUResponse(apdu);
// init.verifyResponse(new Keys(), resp);
//}
}
@@ -1,24 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import org.junit.Test;
import java.io.IOException;
import im.status.applet_installer_test.appletinstaller.HexUtils;
import static org.junit.Assert.*;
public class InstallForInstallTest {
@Test
public void forInstall() throws IOException {
byte[] packageAID = HexUtils.hexStringToByteArray("53746174757357616C6C6574");
byte[] appletAID = HexUtils.hexStringToByteArray("53746174757357616C6C6574417070");
byte[] instanceAID = HexUtils.hexStringToByteArray("53746174757357616C6C6574417070");
byte[] params = HexUtils.hexStringToByteArray("AABBCC");
InstallForInstall install = new InstallForInstall(packageAID, appletAID, instanceAID, params);
APDUCommand cmd = install.getCommand();
byte[] apdu = cmd.serialize();
String expected = "80E60C00360C53746174757357616C6C65740F53746174757357616C6C65744170700F53746174757357616C6C6574417070010005C903AABBCC00";
assertEquals(expected, HexUtils.byteArrayToHexString(apdu));
}
}
@@ -1,23 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import org.junit.Test;
import java.io.IOException;
import im.status.applet_installer_test.appletinstaller.HexUtils;
import static org.junit.Assert.*;
public class InstallForLoadTest {
@Test
public void forLoad() throws IOException {
byte[] aid = HexUtils.hexStringToByteArray("53746174757357616C6C6574");
byte[] sdaid = HexUtils.hexStringToByteArray("A000000151000000");
InstallForLoad install = new InstallForLoad(aid, sdaid);
APDUCommand cmd = install.getCommand();
byte[] apdu = cmd.serialize();
String expected = "80E60200190C53746174757357616C6C657408A000000151000000000000";
assertEquals(expected, HexUtils.byteArrayToHexString(apdu));
}
}
@@ -1,45 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import org.junit.Test;
import java.io.IOException;
public class LoadTest {
@Test
public void getCommand() throws IOException {
//URL url = this.getClass().getClassLoader().getResource("wallet.cap");
//Load load = new Load(url.getPath());
//ArrayList<APDUCommand> commands = new ArrayList<APDUCommand>();
//APDUCommand cmd;
//while((cmd = load.getCommand()) != null) {
// commands.add(cmd);
//}
//assertEquals(31, commands.size());
//// Command 1
//cmd = commands.get(0);
//assertEquals(0, cmd.getP1());
//assertEquals(0, cmd.getP2());
//String expectedData = "C4821D74010027DECAFFED02020401010C53746174757357616C6C657410696D2F7374617475732F77616C6C6574020021002700210013002902B600401581015D0301000006EB372C0024000A013504010004002904000107A0000000620001050107A0000000620102050107A0000000620101050107A0000000620201030013010F53746174757357616C6C65744170700937060040000000800000FF000100000000800000FF00010000000080000D000A010A000004EB05A005F307140739080C08D608EB0908090D008203160010070100000A7B07158106005A801A0076003003808009038B00300457800904620030051F8010";
//byte[] data = cmd.getData();
//assertEquals(expectedData, HexUtils.byteArrayToHexString(data));
//// Command 2
//cmd = commands.get(1);
//assertEquals(0, cmd.getP1());
//assertEquals(1, cmd.getP2());
//expectedData = "053100440A9380AB0B4000750EBF80930F5400300110188C002F7A0302058D00327F003307038D00347F003506038D00377F00381006038D00347F003B032F101B038D00407F004110141020038D0042940000437F005E700B2C017F00411100802F10651C41048D005F7F00607A0861032906181D2510805310806B1E7B00601606590601033816061A7B006016068E03006113412906702B1B7B0060038E03006213044329067B0060037B00601606250453600506700305381606054704412906181D7B00601606078D006329061504160510207B00600316067B006016068D00647B006016067B0065038D0066620403781A7B0060";
//data = cmd.getData();
//assertEquals(expectedData, HexUtils.byteArrayToHexString(data));
//// Last command
//cmd = commands.get(30);
//assertEquals(0x80, cmd.getP1());
//assertEquals(30, cmd.getP2());
//expectedData = "080707080C08090A04050A0A06070706081A07085029031512201209190C0B0B0503060D09030E0B0C0C080A0A0603070706104622070914240A1611130D080B0F0A0D10110905040B2E271205030E0D0D0D0D0807140808030E1E10050321032C2A070606080D140C2723180B081D0A0707060811030D0407070608201B07091408252C2E39";
//data = cmd.getData();
//assertEquals(expectedData, HexUtils.byteArrayToHexString(data));
}
}
@@ -1,19 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import org.junit.Test;
import java.io.IOException;
import im.status.applet_installer_test.appletinstaller.HexUtils;
import static org.junit.Assert.*;
public class SelectTest {
@Test
public void getCode() throws IOException {
Select s = new Select(new byte[0]);
byte[] apdu = s.getCommand().serialize();
String expected = "00A4040000";
assertEquals(expected, HexUtils.byteArrayToHexString(apdu));
}
}
@@ -1,19 +0,0 @@
package im.status.applet_installer_test.appletinstaller.apducommands;
import org.junit.Test;
import java.io.IOException;
import im.status.applet_installer_test.appletinstaller.HexUtils;
import static org.junit.Assert.*;
public class StatusTest {
@Test
public void getCommand() throws IOException {
Status status = new Status(Status.P1_ISSUER_SECURITY_DOMAIN);
String expectedAPDU = "80F28002024F0000";
byte[] apdu = status.getCommand().serialize();
assertEquals(expectedAPDU, HexUtils.byteArrayToHexString(apdu));
}
}
+1
View File
@@ -1 +1,2 @@
include ':app'