Merge pull request #30 from status-im/keycard-rename

rename to keycard
This commit is contained in:
Bitgamma 2018-12-07 17:18:28 +03:00 committed by GitHub
commit 9209fee228
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 117 additions and 124 deletions

View File

@ -1,4 +1,4 @@
# Status Wallet Application
# Status Keycard Application
## Version

View File

@ -1,11 +1,10 @@
# Notes for client implementation
This document should help client application developers to integrate support for the hardware wallet in their
applications.
This document should help client application developers to integrate support for the Status Keycard in their applications.
## Low-level communication
The hardware wallet is a JavaCard application and as such is deployed on ISO7816 compatible SmartCards. Communication
The Status Keycard is a JavaCard application and as such is deployed on ISO7816 compatible SmartCards. Communication
will happen exchanging APDUs using either the T=0 or preferably the T=1 protocol. Most operating systems use an
implementation of [this Microsoft API](https://msdn.microsoft.com/en-us/library/windows/desktop/aa374731(v=vs.85).aspx#smart_card_functions)
like [PCSC lite](http://pcsclite.alioth.debian.org/pcsclite.html). Your language of choice might provide bindings for
@ -18,18 +17,18 @@ A few things to keep in mind when communicating with SmartCards
messing with the card while you are using it.
3. A SmartCard can have multiple applications installed. If using only the basic channel (recommended for our use-case)
only a single application can be selected at the time. This must be done explicitly on each reset by issuing the
SELECT command with the AID of the wallet application.
SELECT command with the AID of the keycard application.
4. Since we are not using extended APDUs, the maximum size of the data field of the APDU is 255 bytes.
## Wallet management and security
Before thinking about the application-specific communication (i.e: actually using the wallet applet to derive keys and
Before thinking about the application-specific communication (i.e: actually using the Keycard applet to derive keys and
sign transactions) the client must be able to actually talk with the card using its [Secure Channel protocol](SECURE_CHANNEL.MD).
The first step, after an APDU channel is available is to [SELECT](APPLICATION.MD) the wallet application on the card.
The wallet will return its Instance UID and public key for Secure Channel establishment. Although both values are unique,
only use the Instance UID to identify the wallet since only this value is guaranteed not to change over the lifetime of
the card. If your application has already performed pairing with the wallet with this Instance UID, you can establish
The first step, after an APDU channel is available is to [SELECT](APPLICATION.MD) the Keycard application on the card.
The Keycard will return its Instance UID and public key for Secure Channel establishment. Although both values are unique,
only use the Instance UID to identify the Keycard since only this value is guaranteed not to change over the lifetime of
the card. If your application has already performed pairing with the Keycard with this Instance UID, you can establish
a Secure Channel session (described later). Otherwise you should proceed with pairing.
For pairing, the client must show that it knows the pairing code. For this reason the user must be prompted
@ -71,10 +70,10 @@ the user has been authenticated or not in order to avoid repeatedly asking for t
error to any APDU requiring user authentication if the user has not been authenticated in the current application
session.
## Wallet features and workflow
## Keycard features and workflow
Now that the client can finally talk with the applet and provide user authentication facilities, it is time to look on
how to actually use the wallet. The wallet applet allows management of a single HD wallet as described in the
how to actually use the Keycard. The Keycard applet allows management of a single HD wallet as described in the
[BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) specifications. It provides the following features
1. Source of entropy to generate master seeds (GENERATE MNEMONIC). No PIN required (has no effect on card state).
@ -136,5 +135,5 @@ must use DERIVE KEY before exporting the desired key.
key derivation, but you shouldn't use it because, as explained above, it wouldn't work on the card.
5. If using jCardSim, only use our fork, since some of the needed algorithms are unsupported in the upstream version.
6. The pairing code is a randomly generated password (using whatever password generation algorithm is desired). This
password must be converted to a 256-bit key using PBKDF2 with the salt "Status Hardware Wallet Lite" and 50000 iterations.
password must be converted to a 256-bit key using PBKDF2 with the salt "Keycard Pairing Password Salt" and 50000 iterations.

View File

@ -1,6 +1,4 @@
# JavaCard Hardware Wallet
The status.im Hardware Wallet. At the moment Secure Channel and PIN management/verification are implemented.
# Status Keycard
The project is built using Gradle with the [Fidesmo Javacard Gradle plugin](https://github.com/fidesmo/gradle-javacard).
You can set the JavaCard HOME not only through the environment but also creating a gradle.properties file with the
@ -15,7 +13,7 @@ installed on the system. The gradle.properties file must contain the following p
* im.status.gradle.gpshell.enc_key = the ENC key for the ISD
* im.status.gradle.gpshell.kek_key = the KEK key for the ISD
* im.status.gradle.gpshell.kvn = the Key Version Number for the ISD
* im.status.wallet.test.simulated = true if the test should run on the simulator, false (or anything else) otherwise
* im.status.keycard.test.simulated = true if the test should run on the simulator, false (or anything else) otherwise
Testing is done with JUnit and performed either on a real card or on [jCardSim](https://github.com/status-im/jcardsim).
Although the tests are comprehensive, debugging on the real card is not easy because raw APDUs are not shown in the test
@ -23,7 +21,7 @@ log and there is no way to set breakpoints in the applet.
In order to test with the simulator with an IDE, you need to pass these additional parameters to the JVM
```-noverify -Dim.status.wallet.test.simulated=true```
```-noverify -Dim.status.keycard.test.simulated=true```
## Compilation
1. Download and install the JavaCard 3.0.4 SDK from [Oracle](http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-javame-419430.html#java_card_kit-classic-3_0_4-rr-bin-do)
@ -52,7 +50,7 @@ im.status.gradle.gpshell.mac_key=404142434445464748494a4b4c4d4e4f
im.status.gradle.gpshell.enc_key=404142434445464748494a4b4c4d4e4f
im.status.gradle.gpshell.kek_key=404142434445464748494a4b4c4d4e4f
im.status.gradle.gpshell.kvn=0
im.status.wallet.test.simulated=false
im.status.keycard.test.simulated=false
```
## Implementation notes

View File

@ -1,6 +1,6 @@
# Status Wallet UX guidelines
# Status Keycard UX guidelines
The scope of this document is to describe the interactions between the hardware wallet, the user and client applications.
The scope of this document is to describe the interactions between the keycard, the user and client applications.
Technical details about the commands mentioned here are to be found in the [APPLICATION.MD](APPLICATION.MD) file.
## Physical interaction
@ -20,7 +20,7 @@ PIN, PUK and pairing password can be changed at any time by the user, after auth
## Application selection
When the client detects a SmartCard, it should try to select the Status Wallet application by sending a selection command.
When the client detects a SmartCard, it should try to select the Status Keycard application by sending a selection command.
The applet will respond providing details useful for the next steps.
## PIN sessions

View File

@ -18,10 +18,10 @@ javacard {
cap {
aid = '0x53:0x74:0x61:0x74:0x75:0x73:0x57:0x61:0x6c:0x6c:0x65:0x74'
packageName = 'im.status.wallet'
packageName = 'im.status.keycard'
applet {
aid = '0x53:0x74:0x61:0x74:0x75:0x73:0x57:0x61:0x6c:0x6c:0x65:0x74:0x41:0x70:0x70'
className = 'WalletApplet'
className = 'KeycardApplet'
}
applet {
aid = '0x53:0x74:0x61:0x74:0x75:0x73:0x57:0x61:0x6c:0x6c:0x65:0x74:0x4e:0x46:0x43'
@ -42,7 +42,7 @@ dependencies {
testCompile('org.web3j:core:2.3.1')
testCompile('org.bitcoinj:bitcoinj-core:0.14.5')
testCompile("org.bouncycastle:bcprov-jdk15on:1.58")
testCompile("com.github.status-im:hardwallet-lite-sdk:482e32c")
testCompile("com.github.status-im:status-keycard-desktop:42086f6")
testCompile("org.junit.jupiter:junit-jupiter-api:5.1.1")
testRuntime("org.junit.jupiter:junit-jupiter-engine:5.1.1")
}
@ -69,7 +69,7 @@ task install(type: Exec) {
open_sc -security 1 -keyind 0 -keyver ${project.properties['im.status.gradle.gpshell.kvn']} -mac_key ${project.properties['im.status.gradle.gpshell.mac_key']} -enc_key ${project.properties['im.status.gradle.gpshell.enc_key']} -kek_key ${project.properties['im.status.gradle.gpshell.kek_key']}
send_apdu_nostop -sc 1 -APDU 80E400800E4F0C53746174757357616C6C6574
install_for_load -pkgAID 53746174757357616C6C6574
load -file build/javacard/im/status/wallet/javacard/wallet.cap
load -file build/javacard/im/status/keycard/javacard/keycard.cap
install_for_install -AID 53746174757357616C6C6574417070 -pkgAID 53746174757357616C6C6574 -instAID 53746174757357616C6C6574417070
install_for_install -AID 53746174757357616C6C65744e4643 -pkgAID 53746174757357616C6C6574 -instAID D2760000850101
card_disconnect
@ -80,7 +80,7 @@ task install(type: Exec) {
standardInput new ByteArrayInputStream(gpShellScript.getBytes("UTF-8"))
}
if (project.properties['im.status.wallet.test.simulated'] != 'true') {
if (project.properties['im.status.keycard.test.simulated'] != 'true') {
tasks.install.dependsOn(convertJavacard)
tasks.test.dependsOn(install)
}
@ -96,8 +96,8 @@ compileTestJava {
}
afterEvaluate {
if (project.properties['im.status.wallet.test.simulated'] == 'true') {
if (project.properties['im.status.keycard.test.simulated'] == 'true') {
def junitPlatformTestTask = tasks.getByName('junitPlatformTest')
junitPlatformTestTask.jvmArgs(['-noverify', '-Dim.status.wallet.test.simulated=true'])
junitPlatformTestTask.jvmArgs(['-noverify', '-Dim.status.keycard.test.simulated=true'])
}
}

View File

@ -1,4 +1,4 @@
package im.status.wallet;
package im.status.keycard;
import javacard.framework.JCSystem;
import javacard.framework.Util;

View File

@ -1,4 +1,4 @@
package im.status.wallet;
package im.status.keycard;
import javacard.framework.*;
import javacard.security.*;
@ -7,7 +7,7 @@ import javacardx.crypto.Cipher;
/**
* The applet's main class. All incoming commands a processed by this class.
*/
public class WalletApplet extends Applet {
public class KeycardApplet extends Applet {
static final short APPLICATION_VERSION = (short) 0x0200;
static final byte INS_GET_STATUS = (byte) 0xF2;
@ -132,7 +132,7 @@ public class WalletApplet extends Applet {
* @param bLength length of the installation parameters
*/
public static void install(byte[] bArray, short bOffset, byte bLength) {
new WalletApplet(bArray, bOffset, bLength);
new KeycardApplet(bArray, bOffset, bLength);
}
/**
@ -146,7 +146,7 @@ public class WalletApplet extends Applet {
* @param bOffset offset where the installation parameters begin
* @param bLength length of the installation parameters
*/
public WalletApplet(byte[] bArray, short bOffset, byte bLength) {
public KeycardApplet(byte[] bArray, short bOffset, byte bLength) {
crypto = new Crypto();
secp256k1 = new SECP256k1(crypto);

View File

@ -1,4 +1,4 @@
package im.status.wallet;
package im.status.keycard;
import javacard.framework.*;

View File

@ -1,4 +1,4 @@
package im.status.wallet;
package im.status.keycard;
import javacard.security.ECKey;
import javacard.security.ECPrivateKey;

View File

@ -1,4 +1,4 @@
package im.status.wallet;
package im.status.keycard;
import javacard.framework.*;
import javacard.security.*;

View File

@ -1,4 +1,4 @@
package im.status.wallet;
package im.status.keycard;
/**
* Keep references to data structures shared across applet instances of this package.

View File

@ -1,9 +1,8 @@
package im.status.wallet;
package im.status.keycard;
import com.licel.jcardsim.smartcardio.CardSimulator;
import com.licel.jcardsim.smartcardio.CardTerminalSimulator;
import com.licel.jcardsim.utils.AIDUtil;
import im.status.hardwallet.lite.WalletAppletCommandSet;
import javacard.framework.AID;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.crypto.ChildNumber;
@ -42,21 +41,21 @@ import java.util.Random;
import static org.apache.commons.codec.digest.DigestUtils.sha256;
import static org.junit.jupiter.api.Assertions.*;
@DisplayName("Test the Wallet Applet")
public class WalletAppletTest {
// Psiring key is WalletAppletTest
public static final byte[] SHARED_SECRET = new byte[] { (byte) 0xe9, (byte) 0x29, (byte) 0xd4, (byte) 0x25, (byte) 0xd7, (byte) 0xf7, (byte) 0x3c, (byte) 0x2a, (byte) 0x0a, (byte) 0x24, (byte) 0xff, (byte) 0xef, (byte) 0xad, (byte) 0x87, (byte) 0xb6, (byte) 0x5e, (byte) 0x9b, (byte) 0x2e, (byte) 0xe9, (byte) 0x66, (byte) 0x03, (byte) 0xea, (byte) 0xb3, (byte) 0x4d, (byte) 0x64, (byte) 0x08, (byte) 0x8b, (byte) 0x5a, (byte) 0xae, (byte) 0x2a, (byte) 0x02, (byte) 0x6f };
@DisplayName("Test the Keycard Applet")
public class KeycardTest {
// Psiring key is KeycardTest
public static final byte[] SHARED_SECRET = Hex.decode("2194524CF8A99C34B9F6C6894D07245AA2D5CFE6327C27D5ACDCC95DA203ED28");
private static CardTerminal cardTerminal;
private static CardChannel apduChannel;
private static CardSimulator simulator;
private TestSecureChannelSession secureChannel;
private TestWalletAppletCommandSet cmdSet;
private TestKeycardCommandSet cmdSet;
private static final boolean USE_SIMULATOR;
static {
USE_SIMULATOR = !System.getProperty("im.status.wallet.test.simulated", "false").equals("false");
USE_SIMULATOR = !System.getProperty("im.status.keycard.test.simulated", "false").equals("false");
}
@BeforeAll
@ -65,8 +64,8 @@ public class WalletAppletTest {
if (USE_SIMULATOR) {
simulator = new CardSimulator();
AID appletAID = AIDUtil.create(WalletAppletCommandSet.APPLET_AID);
simulator.installApplet(appletAID, WalletApplet.class);
AID appletAID = AIDUtil.create(KeycardCommandSet.APPLET_AID);
simulator.installApplet(appletAID, KeycardApplet.class);
cardTerminal = CardTerminalSimulator.terminal(simulator);
} else {
TerminalFactory tf = TerminalFactory.getDefault();
@ -86,19 +85,19 @@ public class WalletAppletTest {
}
private static void initIfNeeded() throws CardException {
WalletAppletCommandSet cmdSet = new WalletAppletCommandSet(apduChannel);
KeycardCommandSet cmdSet = new KeycardCommandSet(apduChannel);
byte[] data = cmdSet.select().getData();
if (data[0] == WalletApplet.TLV_APPLICATION_INFO_TEMPLATE) return;
if (data[0] == KeycardApplet.TLV_APPLICATION_INFO_TEMPLATE) return;
assertEquals(0x9000, cmdSet.init("000000", "123456789012", SHARED_SECRET).getSW());
}
@BeforeEach
void init() throws CardException {
reset();
cmdSet = new TestWalletAppletCommandSet(apduChannel);
cmdSet = new TestKeycardCommandSet(apduChannel);
secureChannel = new TestSecureChannelSession();
cmdSet.setSecureChannel(secureChannel);
WalletAppletCommandSet.checkOK(cmdSet.select());
KeycardCommandSet.checkOK(cmdSet.select());
cmdSet.setSecureChannel(secureChannel);
cmdSet.autoPair(SHARED_SECRET);
}
@ -121,14 +120,14 @@ public class WalletAppletTest {
ResponseAPDU response = cmdSet.select();
assertEquals(0x9000, response.getSW());
byte[] data = response.getData();
assertEquals(WalletApplet.TLV_APPLICATION_INFO_TEMPLATE, data[0]);
assertEquals(WalletApplet.TLV_UID, data[2]);
assertEquals(WalletApplet.TLV_PUB_KEY, data[20]);
assertEquals(WalletApplet.TLV_INT, data[22 + data[21]]);
assertEquals(WalletApplet.APPLICATION_VERSION >> 8, data[24 + data[21]]);
assertEquals(WalletApplet.APPLICATION_VERSION & 0xFF, data[25 + data[21]]);
assertEquals(WalletApplet.TLV_INT, data[26 + data[21]]);
assertEquals(WalletApplet.TLV_KEY_UID, data[29 + data[21]]);
assertEquals(KeycardApplet.TLV_APPLICATION_INFO_TEMPLATE, data[0]);
assertEquals(KeycardApplet.TLV_UID, data[2]);
assertEquals(KeycardApplet.TLV_PUB_KEY, data[20]);
assertEquals(KeycardApplet.TLV_INT, data[22 + data[21]]);
assertEquals(KeycardApplet.APPLICATION_VERSION >> 8, data[24 + data[21]]);
assertEquals(KeycardApplet.APPLICATION_VERSION & 0xFF, data[25 + data[21]]);
assertEquals(KeycardApplet.TLV_INT, data[26 + data[21]]);
assertEquals(KeycardApplet.TLV_KEY_UID, data[29 + data[21]]);
}
@Test
@ -150,7 +149,7 @@ public class WalletAppletTest {
// Send command before MUTUALLY AUTHENTICATE
secureChannel.reset();
response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION);
response = cmdSet.getStatus(KeycardApplet.GET_STATUS_P1_APPLICATION);
assertEquals(0x6985, response.getSW());
// Perform mutual authentication
@ -160,7 +159,7 @@ public class WalletAppletTest {
assertTrue(secureChannel.verifyMutuallyAuthenticateResponse(response));
// Verify that the channel is open
response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION);
response = cmdSet.getStatus(KeycardApplet.GET_STATUS_P1_APPLICATION);
assertEquals(0x9000, response.getSW());
// Verify that the keys are changed correctly. Since we do not know the internal counter we just iterate until that
@ -211,13 +210,13 @@ public class WalletAppletTest {
cmdSet.autoOpenSecureChannel();
// MUTUALLY AUTHENTICATE has no effect on an already open secure channel
response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION);
response = cmdSet.getStatus(KeycardApplet.GET_STATUS_P1_APPLICATION);
assertEquals(0x9000, response.getSW());
response = cmdSet.mutuallyAuthenticate();
assertEquals(0x6985, response.getSW());
response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION);
response = cmdSet.getStatus(KeycardApplet.GET_STATUS_P1_APPLICATION);
assertEquals(0x9000, response.getSW());
}
@ -318,33 +317,33 @@ public class WalletAppletTest {
@DisplayName("GET STATUS command")
void getStatusTest() throws CardException {
// Security condition violation: SecureChannel not open
ResponseAPDU response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION);
ResponseAPDU response = cmdSet.getStatus(KeycardApplet.GET_STATUS_P1_APPLICATION);
assertEquals(0x6985, response.getSW());
cmdSet.autoOpenSecureChannel();
// Good case. Since the order of test execution is undefined, the test cannot know if the keys are initialized or not.
// Additionally, support for public key derivation is hw dependent.
response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION);
response = cmdSet.getStatus(KeycardApplet.GET_STATUS_P1_APPLICATION);
assertEquals(0x9000, response.getSW());
byte[] data = response.getData();
assertTrue(Hex.toHexString(data).matches("a30c0201030201050101[0f][0f]"));
response = cmdSet.verifyPIN("123456");
assertEquals(0x63C2, response.getSW());
response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION);
response = cmdSet.getStatus(KeycardApplet.GET_STATUS_P1_APPLICATION);
assertEquals(0x9000, response.getSW());
data = response.getData();
assertTrue(Hex.toHexString(data).matches("a30c0201020201050101[0f][0f]"));
response = cmdSet.verifyPIN("000000");
assertEquals(0x9000, response.getSW());
response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION);
response = cmdSet.getStatus(KeycardApplet.GET_STATUS_P1_APPLICATION);
assertEquals(0x9000, response.getSW());
data = response.getData();
assertTrue(Hex.toHexString(data).matches("a30c0201030201050101[0f][0f]"));
// Check that key path is empty
response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_KEY_PATH);
response = cmdSet.getStatus(KeycardApplet.GET_STATUS_P1_KEY_PATH);
assertEquals(0x9000, response.getSW());
data = response.getData();
assertEquals(0, data.length);
@ -423,13 +422,13 @@ public class WalletAppletTest {
@DisplayName("CHANGE PIN command")
void changePinTest() throws CardException {
// Security condition violation: SecureChannel not open
ResponseAPDU response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "123456");
ResponseAPDU response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_USER_PIN, "123456");
assertEquals(0x6985, response.getSW());
cmdSet.autoOpenSecureChannel();
// Security condition violation: PIN n ot verified
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "123456");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_USER_PIN, "123456");
assertEquals(0x6985, response.getSW());
response = cmdSet.verifyPIN("000000");
@ -440,37 +439,37 @@ public class WalletAppletTest {
assertEquals(0x6a86, response.getSW());
// Test wrong PIN formats (non-digits, too short, too long)
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "654a21");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_USER_PIN, "654a21");
assertEquals(0x6A80, response.getSW());
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "54321");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_USER_PIN, "54321");
assertEquals(0x6A80, response.getSW());
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "7654321");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_USER_PIN, "7654321");
assertEquals(0x6A80, response.getSW());
// Test wrong PUK formats
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PUK, "210987654a21");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PUK, "210987654a21");
assertEquals(0x6A80, response.getSW());
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PUK, "10987654321");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PUK, "10987654321");
assertEquals(0x6A80, response.getSW());
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PUK, "3210987654321");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PUK, "3210987654321");
assertEquals(0x6A80, response.getSW());
// Test wrong pairing secret format (too long, too short)
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PAIRING_SECRET, "abcdefghilmnopqrstuvz123456789012");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PAIRING_SECRET, "abcdefghilmnopqrstuvz123456789012");
assertEquals(0x6A80, response.getSW());
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PAIRING_SECRET, "abcdefghilmnopqrstuvz1234567890");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PAIRING_SECRET, "abcdefghilmnopqrstuvz1234567890");
assertEquals(0x6A80, response.getSW());
// Change PIN correctly, check that after PIN change the PIN remains validated
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "123456");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_USER_PIN, "123456");
assertEquals(0x9000, response.getSW());
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "654321");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_USER_PIN, "654321");
assertEquals(0x9000, response.getSW());
// Reset card and verify that the new PIN has really been set
@ -480,7 +479,7 @@ public class WalletAppletTest {
assertEquals(0x9000, response.getSW());
// Change PUK
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PUK, "210987654321");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PUK, "210987654321");
assertEquals(0x9000, response.getSW());
resetAndSelectAndOpenSC();
@ -500,11 +499,11 @@ public class WalletAppletTest {
assertEquals(0x9000, response.getSW());
// Reset PUK
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PUK, "123456789012");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PUK, "123456789012");
assertEquals(0x9000, response.getSW());
// Change the pairing secret
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PAIRING_SECRET, "abcdefghilmnopqrstuvz12345678901");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PAIRING_SECRET, "abcdefghilmnopqrstuvz12345678901");
assertEquals(0x9000, response.getSW());
cmdSet.autoUnpair();
reset();
@ -518,7 +517,7 @@ public class WalletAppletTest {
response = cmdSet.verifyPIN("000000");
assertEquals(0x9000, response.getSW());
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PAIRING_SECRET, SHARED_SECRET);
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PAIRING_SECRET, SHARED_SECRET);
assertEquals(0x9000, response.getSW());
}
@ -567,7 +566,7 @@ public class WalletAppletTest {
assertEquals(0x9000, response.getSW());
// Reset the PIN to make further tests possible
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "000000");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_USER_PIN, "000000");
assertEquals(0x9000, response.getSW());
}
@ -595,14 +594,14 @@ public class WalletAppletTest {
assertEquals(0x6A86, response.getSW());
// Wrong data (wrong template, missing private key, invalid keys)
response = cmdSet.loadKey(new byte[]{(byte) 0xAA, 0x02, (byte) 0x80, 0x00}, WalletApplet.LOAD_KEY_P1_EC);
response = cmdSet.loadKey(new byte[]{(byte) 0xAA, 0x02, (byte) 0x80, 0x00}, KeycardApplet.LOAD_KEY_P1_EC);
assertEquals(0x6A80, response.getSW());
response = cmdSet.loadKey(new byte[]{(byte) 0xA1, 0x02, (byte) 0x80, 0x00}, WalletApplet.LOAD_KEY_P1_EC);
response = cmdSet.loadKey(new byte[]{(byte) 0xA1, 0x02, (byte) 0x80, 0x00}, KeycardApplet.LOAD_KEY_P1_EC);
assertEquals(0x6A80, response.getSW());
if (!USE_SIMULATOR) { // the simulator does not check the key format
response = cmdSet.loadKey(new byte[]{(byte) 0xA1, 0x06, (byte) 0x80, 0x01, 0x01, (byte) 0x81, 0x01, 0x02}, WalletApplet.LOAD_KEY_P1_EC);
response = cmdSet.loadKey(new byte[]{(byte) 0xA1, 0x06, (byte) 0x80, 0x01, 0x01, (byte) 0x81, 0x01, 0x02}, KeycardApplet.LOAD_KEY_P1_EC);
assertEquals(0x6A80, response.getSW());
}
@ -791,7 +790,7 @@ public class WalletAppletTest {
verifyKeyDerivation(keyPair, chainCode, new int[]{1, 0x80000000, 2});
// From parent
response = cmdSet.deriveKey(new byte[]{0x00, 0x00, 0x00, 0x03}, WalletApplet.DERIVE_P1_SOURCE_PARENT);
response = cmdSet.deriveKey(new byte[]{0x00, 0x00, 0x00, 0x03}, KeycardApplet.DERIVE_P1_SOURCE_PARENT);
assertEquals(0x9000, response.getSW());
verifyKeyDerivation(keyPair, chainCode, new int[]{1, 0x80000000, 3});
@ -801,15 +800,15 @@ public class WalletAppletTest {
verifyKeyDerivation(keyPair, chainCode, new int[0]);
// Try parent when none available
response = cmdSet.deriveKey(new byte[]{0x00, 0x00, 0x00, 0x03}, WalletApplet.DERIVE_P1_SOURCE_PARENT);
response = cmdSet.deriveKey(new byte[]{0x00, 0x00, 0x00, 0x03}, KeycardApplet.DERIVE_P1_SOURCE_PARENT);
assertEquals(0x6B00, response.getSW());
// 3 levels with hardened key using separate commands
response = cmdSet.deriveKey(new byte[]{0x00, 0x00, 0x00, 0x01}, WalletApplet.DERIVE_P1_SOURCE_MASTER);
response = cmdSet.deriveKey(new byte[]{0x00, 0x00, 0x00, 0x01}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(new byte[]{(byte) 0x80, 0x00, 0x00, 0x00}, WalletApplet.DERIVE_P1_SOURCE_CURRENT);
response = cmdSet.deriveKey(new byte[]{(byte) 0x80, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_CURRENT);
assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(new byte[]{0x00, 0x00, 0x00, 0x02}, WalletApplet.DERIVE_P1_SOURCE_CURRENT);
response = cmdSet.deriveKey(new byte[]{0x00, 0x00, 0x00, 0x02}, KeycardApplet.DERIVE_P1_SOURCE_CURRENT);
assertEquals(0x9000, response.getSW());
verifyKeyDerivation(keyPair, chainCode, new int[]{1, 0x80000000, 2});
@ -890,7 +889,7 @@ public class WalletAppletTest {
// Wrong data
response = cmdSet.setPinlessPath(new byte[] {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00});
assertEquals(0x6a80, response.getSW());
response = cmdSet.setPinlessPath(new byte[(WalletApplet.KEY_PATH_MAX_DEPTH + 1)* 4]);
response = cmdSet.setPinlessPath(new byte[(KeycardApplet.KEY_PATH_MAX_DEPTH + 1)* 4]);
assertEquals(0x6a80, response.getSW());
// Correct
@ -901,11 +900,11 @@ public class WalletAppletTest {
resetAndSelectAndOpenSC();
response = cmdSet.sign(hash);
assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01}, WalletApplet.DERIVE_P1_SOURCE_MASTER);
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
assertEquals(0x9000, response.getSW());
response = cmdSet.sign(hash);
assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, WalletApplet.DERIVE_P1_SOURCE_CURRENT);
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, KeycardApplet.DERIVE_P1_SOURCE_CURRENT);
assertEquals(0x9000, response.getSW());
response = cmdSet.sign(hash);
assertEquals(0x9000, response.getSW());
@ -919,7 +918,7 @@ public class WalletAppletTest {
response = cmdSet.sign(hash);
assertEquals(0x6985, response.getSW());
assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01}, WalletApplet.DERIVE_P1_SOURCE_MASTER);
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
assertEquals(0x9000, response.getSW());
response = cmdSet.sign(hash);
assertEquals(0x9000, response.getSW());
@ -932,7 +931,7 @@ public class WalletAppletTest {
resetAndSelectAndOpenSC();
response = cmdSet.sign(hash);
assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, WalletApplet.DERIVE_P1_SOURCE_MASTER);
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
assertEquals(0x6985, response.getSW());
}
@ -963,12 +962,12 @@ public class WalletAppletTest {
response = cmdSet.exportCurrentKey(false);
assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2c, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, WalletApplet.DERIVE_P1_SOURCE_MASTER);
response = cmdSet.deriveKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2c, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
assertEquals(0x9000, response.getSW());
response = cmdSet.exportCurrentKey(false);
assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00}, WalletApplet.DERIVE_P1_SOURCE_MASTER);
response = cmdSet.deriveKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
assertEquals(0x9000, response.getSW());
response = cmdSet.exportCurrentKey(false);
assertEquals(0x6985, response.getSW());
@ -981,17 +980,17 @@ public class WalletAppletTest {
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000 }, true, false);
// Derive & Make current
response = cmdSet.exportKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, WalletApplet.DERIVE_P1_SOURCE_MASTER,true,false);
response = cmdSet.exportKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER,true,false);
assertEquals(0x9000, response.getSW());
keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000000 }, false, false);
// Derive without making current
response = cmdSet.exportKey(new byte[] {(byte) 0x00, 0x00, 0x00, 0x01}, WalletApplet.DERIVE_P1_SOURCE_PARENT, false,false);
response = cmdSet.exportKey(new byte[] {(byte) 0x00, 0x00, 0x00, 0x01}, KeycardApplet.DERIVE_P1_SOURCE_PARENT, false,false);
assertEquals(0x9000, response.getSW());
keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000001 }, false, true);
response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_KEY_PATH);
response = cmdSet.getStatus(KeycardApplet.GET_STATUS_P1_KEY_PATH);
assertEquals(0x9000, response.getSW());
assertArrayEquals(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, response.getData());
@ -1002,7 +1001,7 @@ public class WalletAppletTest {
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000000 }, false, false);
// Reset
response = cmdSet.deriveKey(new byte[0], WalletApplet.DERIVE_P1_SOURCE_MASTER);
response = cmdSet.deriveKey(new byte[0], KeycardApplet.DERIVE_P1_SOURCE_MASTER);
assertEquals(0x9000, response.getSW());
}
@ -1175,7 +1174,7 @@ public class WalletAppletTest {
for (int i = 0; i < SAMPLE_COUNT; i++) {
time = System.currentTimeMillis();
response = cmdSet.deriveKey(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}, WalletApplet.DERIVE_P1_SOURCE_MASTER);
response = cmdSet.deriveKey(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}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
deriveAccount += System.currentTimeMillis() - time;
assertEquals(0x9000, response.getSW());
}
@ -1184,7 +1183,7 @@ public class WalletAppletTest {
for (int i = 0; i < SAMPLE_COUNT; i++) {
time = System.currentTimeMillis();
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, (byte) i}, WalletApplet.DERIVE_P1_SOURCE_PARENT);
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, (byte) i}, KeycardApplet.DERIVE_P1_SOURCE_PARENT);
deriveParent += System.currentTimeMillis() - time;
assertEquals(0x9000, response.getSW());
}
@ -1193,7 +1192,7 @@ public class WalletAppletTest {
for (int i = 0; i < SAMPLE_COUNT; i++) {
time = System.currentTimeMillis();
response = cmdSet.deriveKey(new byte[] {(byte) 0x80, 0x00, 0x00, (byte) i}, WalletApplet.DERIVE_P1_SOURCE_PARENT);
response = cmdSet.deriveKey(new byte[] {(byte) 0x80, 0x00, 0x00, (byte) i}, KeycardApplet.DERIVE_P1_SOURCE_PARENT);
deriveParentHardened += System.currentTimeMillis() - time;
assertEquals(0x9000, response.getSW());
}
@ -1219,17 +1218,17 @@ public class WalletAppletTest {
}
private byte[] extractPublicKeyFromSignature(byte[] sig) {
assertEquals(WalletApplet.TLV_SIGNATURE_TEMPLATE, sig[0]);
assertEquals(KeycardApplet.TLV_SIGNATURE_TEMPLATE, sig[0]);
assertEquals((byte) 0x81, sig[1]);
assertEquals(WalletApplet.TLV_PUB_KEY, sig[3]);
assertEquals(KeycardApplet.TLV_PUB_KEY, sig[3]);
return Arrays.copyOfRange(sig, 5, 5 + sig[4]);
}
private byte[] extractPublicKeyFromSelect(byte[] select) {
assertEquals(WalletApplet.TLV_APPLICATION_INFO_TEMPLATE, select[0]);
assertEquals(WalletApplet.TLV_UID, select[2]);
assertEquals(WalletApplet.TLV_PUB_KEY, select[20]);
assertEquals(KeycardApplet.TLV_APPLICATION_INFO_TEMPLATE, select[0]);
assertEquals(KeycardApplet.TLV_UID, select[2]);
assertEquals(KeycardApplet.TLV_PUB_KEY, select[20]);
return Arrays.copyOfRange(select, 22, 22 + select[21]);
}
@ -1291,7 +1290,7 @@ public class WalletAppletTest {
assertTrue(key.verify(hash, sig));
assertArrayEquals(key.getPubKeyPoint().getEncoded(false), publicKey);
resp = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_KEY_PATH);
resp = cmdSet.getStatus(KeycardApplet.GET_STATUS_P1_KEY_PATH);
assertEquals(0x9000, resp.getSW());
byte[] rawPath = resp.getData();
@ -1306,11 +1305,11 @@ public class WalletAppletTest {
private void verifyExportedKey(byte[] keyTemplate, KeyPair keyPair, byte[] chainCode, int[] path, boolean publicOnly, boolean noPubKey) {
ECKey key = deriveKey(keyPair, chainCode, path).decompress();
assertEquals(WalletApplet.TLV_KEY_TEMPLATE, keyTemplate[0]);
assertEquals(KeycardApplet.TLV_KEY_TEMPLATE, keyTemplate[0]);
int pubKeyLen = 0;
if (!noPubKey) {
assertEquals(WalletApplet.TLV_PUB_KEY, keyTemplate[2]);
assertEquals(KeycardApplet.TLV_PUB_KEY, keyTemplate[2]);
byte[] pubKey = Arrays.copyOfRange(keyTemplate, 4, 4 + keyTemplate[3]);
assertArrayEquals(key.getPubKey(), pubKey);
pubKeyLen = 2 + pubKey.length;
@ -1320,7 +1319,7 @@ public class WalletAppletTest {
assertEquals(pubKeyLen, keyTemplate[1]);
assertEquals(pubKeyLen + 2, keyTemplate.length);
} else {
assertEquals(WalletApplet.TLV_PRIV_KEY, keyTemplate[2 + pubKeyLen]);
assertEquals(KeycardApplet.TLV_PRIV_KEY, keyTemplate[2 + pubKeyLen]);
byte[] privateKey = Arrays.copyOfRange(keyTemplate, 4 + pubKeyLen, 4 + pubKeyLen + keyTemplate[3 + pubKeyLen]);
byte[] tPrivKey = key.getPrivKey().toByteArray();

View File

@ -1,14 +1,13 @@
package im.status.wallet;
package im.status.keycard;
import im.status.hardwallet.lite.WalletAppletCommandSet;
import org.web3j.crypto.ECKeyPair;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.ResponseAPDU;
public class TestWalletAppletCommandSet extends WalletAppletCommandSet {
public TestWalletAppletCommandSet(CardChannel apduChannel) {
public class TestKeycardCommandSet extends KeycardCommandSet {
public TestKeycardCommandSet(CardChannel apduChannel) {
super(apduChannel);
}

View File

@ -1,6 +1,4 @@
package im.status.wallet;
import im.status.hardwallet.lite.SecureChannelSession;
package im.status.keycard;
public class TestSecureChannelSession extends SecureChannelSession {
public void setOpen() {