18 Commits
Author SHA1 Message Date
Michele Balistreri 5ea1a2397b applet can now be loaded also on "fresh" JCOP cards 2018-12-29 11:53:13 +03:00
Michele Balistreri 7511d3ea6c version 2.0.0 2018-12-14 14:44:18 +03:00
Michele Balistreri a9982c5fdc add iml file back to ignored files 2018-12-12 10:26:48 +03:00
Michele Balistreri 73192b3ba0 update README 2018-12-12 10:22:28 +03:00
Bitgamma a22528dc0f Merge pull request #13 from status-im/nfc-spot
bump version to 0.0.7
2018-12-12 10:12:36 +03:00
Andrea Franz ffd5b1a83f bump version to 0.0.7 2018-12-11 15:59:30 +01:00
Bitgamma dc2171f1dd Merge pull request #12 from status-im/nfc-spot
add button to show nfc spot
2018-12-11 17:59:03 +03:00
Andrea Franz c91e4d7848 check if NFCSpot is available 2018-12-11 15:48:12 +01:00
Andrea Franz c8f13db627 add button to show nfc spot 2018-12-11 14:57:55 +01:00
Bitgamma 98caa6106e rename to keycard (#11)
* rename to keycard

* clean imports

* update to latest SDK

* use unified SDK
2018-12-11 14:30:01 +01:00
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
51 changed files with 298 additions and 1724 deletions
+2 -11
View File
@@ -1,6 +1,7 @@
/local.properties
/.idea/modules.xml
/.idea
/build
*.iml
# Built application files
*.apk
@@ -36,16 +37,6 @@ proguard/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
.idea/caches
# Keystore files
# Uncomment the following line if you do not want to check your keystore files in.
#*.jks
-29
View File
@@ -1,29 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</Objective-C-extensions>
</code_scheme>
</component>
-43
View File
@@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<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">
<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">
<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>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+7 -7
View File
@@ -1,11 +1,11 @@
# smartcard-cap-installer-test
# Keycard Installer for Android
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.
This is an Android application to install the [keycard applet](https://github.com/status-im/status-keycard) to a
smartcard via NFC. The compiled cap file of the applet is bundled in the application. Currently, version 2.0 of the
applet is installed.
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`.
If you work with the Status Keycard this is a quick utility to reinstall the applet.
[Download APK](https://github.com/status-im/smartcard-cap-installer-test/releases/download/0.0.1/app-debug.apk)
[Download APK](https://github.com/status-im/keycard-installer-android/releases/download/2.0.0/keycard-installer-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)
![https://github.com/status-im/keycard-installer-android/blob/master/docs/demo.gif?raw=true](https://github.com/status-im/keycard-installer-android/blob/master/docs/demo.gif?raw=true)
+6 -6
View File
@@ -3,11 +3,11 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "im.status.applet_installer_test.appletinstaller"
applicationId "im.status.keycard_installer.installer"
minSdkVersion 19
targetSdkVersion 27
versionCode 5
versionName "0.0.5"
versionCode 201
versionName "2.0.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -22,9 +22,9 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
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.status-keycard-java:android:2.0.0'
implementation 'de.cotech:nfc-sweetspot:1.1'
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'
@@ -1,26 +0,0 @@
package im.status.applet_installer_test.appletinstaller;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("im.status.applet_installer_test.appletinstaller", appContext.getPackageName());
}
}
+7 -2
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="im.status.applet_installer_test.appletinstaller">
package="im.status.keycard.installer">
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc.hce"
@@ -13,13 +13,18 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<activity android:name="im.status.keycard.installer.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="de.cotech.sweetspot.ShowNfcSweetspotActivity"
android:exported="false"
/>
</application>
</manifest>
Binary file not shown.
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;
}
}
@@ -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);
}
}
@@ -1,30 +0,0 @@
package im.status.applet_installer_test.appletinstaller;
public class HexUtils {
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len/2];
for(int i = 0; i < len; i+=2){
data[i/2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16));
}
return data;
}
final protected static char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
public static String byteArrayToHexString(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
int v;
for(int j=0; j < bytes.length; j++) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v>>>4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
}
@@ -1,147 +0,0 @@
package im.status.applet_installer_test.appletinstaller;
import android.content.res.AssetManager;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
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.io.APDUException;
import im.status.hardwallet_lite_android.io.APDUResponse;
import im.status.hardwallet_lite_android.io.CardChannel;
import im.status.hardwallet_lite_android.wallet.WalletAppletCommandSet;
public class Installer {
private CardChannel plainChannel;
private SecureChannel channel;
private Keys cardKeys;
private AssetManager assets;
private String capPath;
private boolean testSecrets;
static final byte[] cardKeyData = HexUtils.hexStringToByteArray("404142434445464748494a4b4c4d4e4f");
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");
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.d("sdaid: " + HexUtils.byteArrayToHexString(sdaid));
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");
Delete deleteNDEFApplet = new Delete(ndefInstanceAID);
Logger.i("sending delete (NDEF applet)");
this.channel.send(deleteNDEFApplet.getCommand());
Delete deleteApplet = new Delete(walletAID);
Logger.i("sending delete (applet)");
this.channel.send(deleteApplet.getCommand());
Delete deletePkg = new Delete(packageAID);
Logger.i("sending delete (pkg)");
this.channel.send(deletePkg.getCommand());
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);
}
InstallForInstall installNDEF = new InstallForInstall(packageAID, ndefAppletAID, ndefInstanceAID, HexUtils.hexStringToByteArray("0024d40f12616e64726f69642e636f6d3a706b67696d2e7374617475732e657468657265756d"));
this.send("perform and make selectable (NDEF)", installNDEF.getCommand());
InstallForInstall install = new InstallForInstall(packageAID, walletAID, walletAID, new byte[0]);
this.send("perform and make selectable (wallet)", install.getCommand());
personalizeApplet();
long duration = System.currentTimeMillis() - startTime;
Logger.i(String.format("installation completed in %d seconds", duration / 1000));
}
private void personalizeApplet() throws NoSuchAlgorithmException, InvalidKeySpecException, APDUException, IOException {
Secrets secrets = testSecrets ? Secrets.testSecrets() : Secrets.generate();
WalletAppletCommandSet cmdSet = new WalletAppletCommandSet(this.plainChannel);
cmdSet.select().checkOK();
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.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;
}
}
@@ -1,132 +0,0 @@
package im.status.applet_installer_test.appletinstaller;
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;
import java.util.Random;
public class PerfTest {
private CardChannel cardChannel;
private WalletAppletCommandSet cmdSet;
private long openSecureChannelTime = 0;
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};
public PerfTest(CardChannel cardChannel) {
this.cardChannel = cardChannel;
}
public void test() throws Exception {
cmdSet = new WalletAppletCommandSet(cardChannel);
cmdSet.select().checkOK();
cmdSet.autoPair(Secrets.testSecrets().getPairingToken());
openSecureChannelTime = System.currentTimeMillis();
cmdSet.autoOpenSecureChannel();
openSecureChannelTime = System.currentTimeMillis() - openSecureChannelTime;
cmdSet.verifyPIN("000000").checkOK();
cmdSet.unpairOthers(); // Recover in case of non-clean termination
Logger.i("Measuring performances. Logging disabled. Please wait");
Logger.setLevel(Log.INFO);
try {
loadKeys();
getStatus();
login();
signTransactions();
} finally {
Logger.setLevel(Log.INFO);
Logger.setUILevel(Log.INFO);
}
Logger.i("Reenabling logging.");
cmdSet.select().checkOK();
cmdSet.autoOpenSecureChannel();
cmdSet.verifyPIN("000000").checkOK();
cmdSet.autoUnpair();
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 {
long time = System.currentTimeMillis();
cmdSet.select().checkOK();
cmdSet.autoOpenSecureChannel();
cmdSet.getStatus(WalletAppletCommandSet.GET_STATUS_P1_APPLICATION).checkOK();
getStatusTime = System.currentTimeMillis() - time;
}
private void login() throws Exception {
long time = System.currentTimeMillis();
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();
loginTime = System.currentTimeMillis() - time;
}
private void loadKeys() throws Exception {
KeyPairGenerator g = keypairGenerator();
KeyPair keyPair = g.generateKeyPair();
byte[] chainCode = new byte[32];
new Random().nextBytes(chainCode);
cmdSet.loadKey(keyPair, false, chainCode).checkOK();
long time = System.currentTimeMillis();
cmdSet.deriveKey(BIP44_PATH, WalletAppletCommandSet.DERIVE_P1_SOURCE_CURRENT).checkOK();
loadKeysTime = System.currentTimeMillis() - time;
}
private void signTransactions() throws Exception {
long time = System.currentTimeMillis();
cmdSet.select().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;
}
private KeyPairGenerator keypairGenerator() throws Exception {
ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1");
KeyPairGenerator g = KeyPairGenerator.getInstance("ECDH");
g.initialize(ecSpec);
return g;
}
}
@@ -1,54 +0,0 @@
package im.status.applet_installer_test.appletinstaller;
import android.support.annotation.NonNull;
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 pin, String puk, String pairingPassword, byte[] pairingToken) {
this.pin = pin;
this.puk = puk;
this.pairingPassword = pairingPassword;
this.pairingToken = pairingToken;
}
@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 pin = String.format("%06d", pinNumber);
String puk = String.format("%012d", pukNumber);
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() {
return puk;
}
public String getPairingPassword() {
return pairingPassword;
}
public byte[] getPairingToken() {
return pairingToken;
}
}
@@ -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,18 +1,15 @@
package im.status.applet_installer_test.appletinstaller;
package im.status.keycard.installer;
import android.content.res.AssetManager;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import im.status.hardwallet_lite_android.io.APDUException;
import im.status.hardwallet_lite_android.io.CardChannel;
import im.status.hardwallet_lite_android.io.OnCardConnectedListener;
import im.status.keycard.io.APDUException;
import im.status.keycard.io.CardChannel;
import im.status.keycard.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 +86,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 +99,9 @@ public class ActionRunner implements OnCardConnectedListener {
Logger.i("no action requested yet");
}
}
@Override
public void onDisconnected() {
Logger.i("card disconnected");
}
}
@@ -0,0 +1,73 @@
package im.status.keycard.installer;
import android.content.res.AssetManager;
import im.status.keycard.applet.KeycardCommandSet;
import im.status.keycard.globalplatform.GlobalPlatformCommandSet;
import im.status.keycard.globalplatform.LoadCallback;
import im.status.keycard.io.APDUException;
import im.status.keycard.io.CardChannel;
import org.bouncycastle.util.encoders.Hex;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
public class Installer {
private CardChannel plainChannel;
private AssetManager assets;
private String capPath;
private GlobalPlatformCommandSet cmdSet;
private boolean testSecrets;
public Installer(CardChannel channel, AssetManager assets, String capPath, boolean testSecrets) {
this.plainChannel = channel;
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();
Logger.i("select ISD...");
cmdSet = new GlobalPlatformCommandSet(this.plainChannel);
cmdSet.select().checkOK();
Logger.i("opening secure channel...");
cmdSet.openSecureChannel();
Logger.i("deleting old version (if present)...");
cmdSet.deleteKeycardInstancesAndPackage();
Logger.i("loading package...");
cmdSet.loadKeycardPackage(this.assets.open(this.capPath), new LoadCallback() {
public void blockLoaded(int loadedBlock, int blockCount) {
Logger.i(String.format("load %d/%d...", loadedBlock, blockCount));
}
});
Logger.i("installing NDEF applet...");
cmdSet.installNDEFApplet(Hex.decode("0024d40f12616e64726f69642e636f6d3a706b67696d2e7374617475732e657468657265756d")).checkOK();
Logger.i("installing Keycard applet...");
cmdSet.installKeycardApplet().checkOK();
this.personalizeApplet();
long duration = System.currentTimeMillis() - startTime;
Logger.i(String.format("\n\ninstallation completed in %d seconds", duration / 1000));
}
private void personalizeApplet() throws NoSuchAlgorithmException, InvalidKeySpecException, APDUException, IOException {
Secrets secrets = testSecrets ? Secrets.testSecrets() : Secrets.generate();
KeycardCommandSet cmdSet = new KeycardCommandSet(this.plainChannel);
cmdSet.select().checkOK();
cmdSet.init(secrets.getPin(), secrets.getPuk(), secrets.getPairingPassword()).checkOK();
Logger.i(String.format("PIN: %s\nPUK: %s\nPairing password: %s", secrets.getPin(), secrets.getPuk(), secrets.getPairingPassword()));
}
}
@@ -1,4 +1,4 @@
package im.status.applet_installer_test.appletinstaller;
package im.status.keycard.installer;
import android.util.Log;
@@ -1,5 +1,6 @@
package im.status.applet_installer_test.appletinstaller;
package im.status.keycard.installer;
import android.content.Intent;
import android.content.res.AssetManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
@@ -9,15 +10,12 @@ import android.view.View;
import android.widget.Button;
import android.widget.ScrollView;
import android.widget.TextView;
import im.status.hardwallet_lite_android.io.CardManager;
import java.security.Security;
import de.cotech.sweetspot.NfcSweetspotData;
import im.status.keycard.android.NFCCardManager;
import de.cotech.sweetspot.ShowNfcSweetspotActivity;
public class MainActivity extends AppCompatActivity implements UILogger {
static {
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
}
private NfcAdapter nfcAdapter;
private TextView textView;
private ScrollView textViewScroll;
@@ -25,8 +23,9 @@ public class MainActivity extends AppCompatActivity implements UILogger {
private Button buttonInstall;
private Button buttonInstallTest;
private Button buttonPerfTest;
private Button buttonNFCSpot;
private ActionRunner actionRunner;
private CardManager cardManager;
private NFCCardManager cardManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -36,9 +35,9 @@ public class MainActivity extends AppCompatActivity implements UILogger {
Logger.setUILogger(this);
AssetManager assets = this.getAssets();
this.actionRunner = new ActionRunner(assets, "wallet.cap");
this.cardManager = new CardManager();
this.cardManager.setOnCardConnectedListener(this.actionRunner);
this.actionRunner = new ActionRunner(assets, "keycard.cap");
this.cardManager = new NFCCardManager();
this.cardManager.setCardListener(this.actionRunner);
cardManager.start();
textViewScroll = (ScrollView) findViewById(R.id.textViewScroll);
@@ -67,6 +66,18 @@ public class MainActivity extends AppCompatActivity implements UILogger {
requestAction(ActionRunner.ACTION_PERFTEST);
}
});
buttonNFCSpot = (Button) findViewById(R.id.buttonNFCSpot);
buttonNFCSpot.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(MainActivity.this, ShowNfcSweetspotActivity.class);
startActivity(i);
}
});
if (NfcSweetspotData.hasSweetspotData()) {
buttonNFCSpot.setEnabled(true);
}
//Logger.setUILevel(Log.INFO);
//Logger.setLevel(Log.INFO);
@@ -0,0 +1,100 @@
package im.status.keycard.installer;
import android.util.Log;
import im.status.keycard.io.CardChannel;
import im.status.keycard.applet.KeycardCommandSet;
import java.security.SecureRandom;
public class PerfTest {
private CardChannel cardChannel;
private KeycardCommandSet cmdSet;
private long openSecureChannelTime = 0;
private long loadKeysTime = 0;
private long loginTime = 0;
private long signTime = 0;
private long getStatusTime = 0;
static final String BIP44_WALLET_PATH = "m/44'/60'/0'/0/0";
public PerfTest(CardChannel cardChannel) {
this.cardChannel = cardChannel;
}
public void test() throws Exception {
cmdSet = new KeycardCommandSet(cardChannel);
cmdSet.select().checkOK();
cmdSet.autoPair(cmdSet.pairingPasswordToSecret(Secrets.testSecrets().getPairingPassword()));
openSecureChannelTime = System.currentTimeMillis();
cmdSet.autoOpenSecureChannel();
openSecureChannelTime = System.currentTimeMillis() - openSecureChannelTime;
cmdSet.verifyPIN("000000").checkOK();
cmdSet.unpairOthers(); // Recover in case of non-clean termination
Logger.i("Measuring performances. Logging disabled. Please wait");
Logger.setLevel(Log.INFO);
try {
loadKeys();
getStatus();
login();
signTransactions();
} finally {
Logger.setLevel(Log.INFO);
Logger.setUILevel(Log.INFO);
}
Logger.i("Reenabling logging.");
cmdSet.select().checkOK();
cmdSet.autoOpenSecureChannel();
cmdSet.verifyPIN("000000").checkOK();
cmdSet.autoUnpair();
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: " + signTime + "ms");
}
private void getStatus() throws Exception {
long time = System.currentTimeMillis();
cmdSet.select().checkOK();
cmdSet.autoOpenSecureChannel();
cmdSet.getStatus(KeycardCommandSet.GET_STATUS_P1_APPLICATION).checkOK();
getStatusTime = System.currentTimeMillis() - time;
}
private void login() throws Exception {
long time = System.currentTimeMillis();
cmdSet.select().checkOK();
cmdSet.autoOpenSecureChannel();
cmdSet.verifyPIN("000000").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;
}
private void loadKeys() throws Exception {
byte[] seed = new byte[64];
new SecureRandom().nextBytes(seed);
cmdSet.loadKey(seed).checkOK();
long time = System.currentTimeMillis();
cmdSet.deriveKey(BIP44_WALLET_PATH).checkOK();
loadKeysTime = System.currentTimeMillis() - time;
}
private void signTransactions() throws Exception {
long time = System.currentTimeMillis();
cmdSet.select().checkOK();
cmdSet.autoOpenSecureChannel();
cmdSet.verifyPIN("000000").checkOK();
cmdSet.sign("any32bytescanbeahashyouknowthat!".getBytes()).checkOK();
signTime = System.currentTimeMillis() - time;
}
}
@@ -0,0 +1,60 @@
package im.status.keycard.installer;
import android.util.Base64;
import im.status.keycard.globalplatform.Crypto;
import java.security.SecureRandom;
import static android.util.Base64.NO_PADDING;
import static im.status.keycard.globalplatform.Crypto.randomLong;
public class Secrets {
private String pin;
private String puk;
private String pairingPassword;
public Secrets(String pin, String puk, String pairingPassword) {
this.pin = pin;
this.puk = puk;
this.pairingPassword = pairingPassword;
}
public static Secrets generate() {
String pairingPassword = randomToken(12);
long pinNumber = randomLong(Crypto.PIN_BOUND);
long pukNumber = randomLong(Crypto.PUK_BOUND);
String pin = String.format("%06d", pinNumber);
String puk = String.format("%012d", pukNumber);
return new Secrets(pin, puk, pairingPassword);
}
public static Secrets testSecrets() {
String pairingPassword = "KeycardTest";
return new Secrets("000000", "123456789012", pairingPassword);
}
public String getPin() {
return pin;
}
public String getPuk() {
return puk;
}
public String getPairingPassword() {
return pairingPassword;
}
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);
}
}
@@ -40,4 +40,11 @@
android:layout_height="wrap_content"
android:text="Perf Test" />
<Button
android:id="@+id/buttonNFCSpot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="NFC Spot"
android:enabled="false" />
</LinearLayout>
+1 -1
View File
@@ -1,3 +1,3 @@
<resources>
<string name="app_name">AppletInstaller</string>
<string name="app_name">KeycardInstaller</string>
</resources>
@@ -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'