add data in cash applet
This commit is contained in:
parent
1b716790f0
commit
4cc3f1576c
|
@ -9,7 +9,7 @@ buildscript {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.fidesmo:gradle-javacard:0.2.7'
|
classpath 'com.fidesmo:gradle-javacard:0.2.7'
|
||||||
classpath 'com.github.status-im.status-keycard-java:desktop:4a69788'
|
classpath 'com.github.status-im.status-keycard-java:desktop:8cb43e6'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ javacard {
|
||||||
aid = '0xA0:0x00:0x00:0x08:0x04:0x00:0x01:0x03'
|
aid = '0xA0:0x00:0x00:0x08:0x04:0x00:0x01:0x03'
|
||||||
className = 'CashApplet'
|
className = 'CashApplet'
|
||||||
}
|
}
|
||||||
version = '2.3'
|
version = '3.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ dependencies {
|
||||||
testCompile(files("../jcardsim/jcardsim-3.0.5-SNAPSHOT.jar"))
|
testCompile(files("../jcardsim/jcardsim-3.0.5-SNAPSHOT.jar"))
|
||||||
testCompile('org.web3j:core:2.3.1')
|
testCompile('org.web3j:core:2.3.1')
|
||||||
testCompile('org.bitcoinj:bitcoinj-core:0.14.5')
|
testCompile('org.bitcoinj:bitcoinj-core:0.14.5')
|
||||||
testCompile('com.github.status-im.status-keycard-java:desktop:4a69788')
|
testCompile('com.github.status-im.status-keycard-java:desktop:8cb43e6')
|
||||||
testCompile('org.bouncycastle:bcprov-jdk15on:1.60')
|
testCompile('org.bouncycastle:bcprov-jdk15on:1.60')
|
||||||
testCompile("org.junit.jupiter:junit-jupiter-api:5.1.1")
|
testCompile("org.junit.jupiter:junit-jupiter-api:5.1.1")
|
||||||
testRuntime("org.junit.jupiter:junit-jupiter-engine:5.1.1")
|
testRuntime("org.junit.jupiter:junit-jupiter-engine:5.1.1")
|
||||||
|
|
|
@ -4,5 +4,5 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.github.status-im.status-keycard-java:desktop:4a69788'
|
compile 'com.github.status-im.status-keycard-java:desktop:8cb43e6'
|
||||||
}
|
}
|
|
@ -5,6 +5,7 @@ import javacard.security.*;
|
||||||
|
|
||||||
public class CashApplet extends Applet {
|
public class CashApplet extends Applet {
|
||||||
private static final short SIGN_OUT_OFF = ISO7816.OFFSET_CDATA + MessageDigest.LENGTH_SHA_256;
|
private static final short SIGN_OUT_OFF = ISO7816.OFFSET_CDATA + MessageDigest.LENGTH_SHA_256;
|
||||||
|
private static final byte TLV_PUB_DATA = (byte) 0x82;
|
||||||
|
|
||||||
private KeyPair keypair;
|
private KeyPair keypair;
|
||||||
private ECPublicKey publicKey;
|
private ECPublicKey publicKey;
|
||||||
|
@ -51,6 +52,14 @@ public class CashApplet extends Applet {
|
||||||
signature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false);
|
signature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false);
|
||||||
signature.init(privateKey, Signature.MODE_SIGN);
|
signature.init(privateKey, Signature.MODE_SIGN);
|
||||||
|
|
||||||
|
short c9Off = (short)(bOffset + bArray[bOffset] + 1); // Skip AID
|
||||||
|
c9Off += (short)(bArray[c9Off] + 1); // Skip Privileges and parameter length
|
||||||
|
|
||||||
|
short dataLen = Util.makeShort((byte) 0x00, bArray[c9Off]);
|
||||||
|
if (dataLen > 0) {
|
||||||
|
Util.arrayCopyNonAtomic(bArray, c9Off, SharedMemory.cashDataFile, (short) 0, (short)(dataLen + 1));
|
||||||
|
}
|
||||||
|
|
||||||
register(bArray, (short) (bOffset + 1), bArray[bOffset]);
|
register(bArray, (short) (bOffset + 1), bArray[bOffset]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +108,11 @@ public class CashApplet extends Applet {
|
||||||
Util.setShort(apduBuffer, off, KeycardApplet.APPLICATION_VERSION);
|
Util.setShort(apduBuffer, off, KeycardApplet.APPLICATION_VERSION);
|
||||||
off += 2;
|
off += 2;
|
||||||
|
|
||||||
|
apduBuffer[off++] = TLV_PUB_DATA;
|
||||||
|
apduBuffer[off++] = SharedMemory.cashDataFile[0];
|
||||||
|
Util.arrayCopyNonAtomic(SharedMemory.cashDataFile, (short) 1, apduBuffer, off, SharedMemory.cashDataFile[0]);
|
||||||
|
off += SharedMemory.cashDataFile[0];
|
||||||
|
|
||||||
apduBuffer[lenoff] = (byte)(off - lenoff - 1);
|
apduBuffer[lenoff] = (byte)(off - lenoff - 1);
|
||||||
apdu.setOutgoingAndSend((short) 0, off);
|
apdu.setOutgoingAndSend((short) 0, off);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,15 @@ import javacard.framework.*;
|
||||||
import javacard.security.*;
|
import javacard.security.*;
|
||||||
import javacardx.crypto.Cipher;
|
import javacardx.crypto.Cipher;
|
||||||
|
|
||||||
|
import static javacard.framework.ISO7816.OFFSET_P1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The applet's main class. All incoming commands a processed by this class.
|
* The applet's main class. All incoming commands a processed by this class.
|
||||||
*/
|
*/
|
||||||
public class KeycardApplet extends Applet {
|
public class KeycardApplet extends Applet {
|
||||||
static final short APPLICATION_VERSION = (short) 0x0203;
|
static final short APPLICATION_VERSION = (short) 0x0300;
|
||||||
|
|
||||||
static final byte INS_GET_STATUS = (byte) 0xF2;
|
static final byte INS_GET_STATUS = (byte) 0xF2;
|
||||||
static final byte INS_SET_NDEF = (byte) 0xF3;
|
|
||||||
static final byte INS_INIT = (byte) 0xFE;
|
static final byte INS_INIT = (byte) 0xFE;
|
||||||
static final byte INS_VERIFY_PIN = (byte) 0x20;
|
static final byte INS_VERIFY_PIN = (byte) 0x20;
|
||||||
static final byte INS_CHANGE_PIN = (byte) 0x21;
|
static final byte INS_CHANGE_PIN = (byte) 0x21;
|
||||||
|
@ -74,6 +75,10 @@ public class KeycardApplet extends Applet {
|
||||||
static final byte EXPORT_KEY_P2_PRIVATE_AND_PUBLIC = 0x00;
|
static final byte EXPORT_KEY_P2_PRIVATE_AND_PUBLIC = 0x00;
|
||||||
static final byte EXPORT_KEY_P2_PUBLIC_ONLY = 0x01;
|
static final byte EXPORT_KEY_P2_PUBLIC_ONLY = 0x01;
|
||||||
|
|
||||||
|
static final byte STORE_DATA_P1_PUBLIC = 0x00;
|
||||||
|
static final byte STORE_DATA_P1_NDEF = 0x01;
|
||||||
|
static final byte STORE_DATA_P1_CASH = 0x02;
|
||||||
|
|
||||||
static final byte TLV_SIGNATURE_TEMPLATE = (byte) 0xA0;
|
static final byte TLV_SIGNATURE_TEMPLATE = (byte) 0xA0;
|
||||||
|
|
||||||
static final byte TLV_KEY_TEMPLATE = (byte) 0xA1;
|
static final byte TLV_KEY_TEMPLATE = (byte) 0xA1;
|
||||||
|
@ -239,9 +244,6 @@ public class KeycardApplet extends Applet {
|
||||||
case INS_GET_STATUS:
|
case INS_GET_STATUS:
|
||||||
getStatus(apdu);
|
getStatus(apdu);
|
||||||
break;
|
break;
|
||||||
case INS_SET_NDEF:
|
|
||||||
setNDEF(apdu);
|
|
||||||
break;
|
|
||||||
case INS_VERIFY_PIN:
|
case INS_VERIFY_PIN:
|
||||||
verifyPIN(apdu);
|
verifyPIN(apdu);
|
||||||
break;
|
break;
|
||||||
|
@ -434,9 +436,9 @@ public class KeycardApplet extends Applet {
|
||||||
|
|
||||||
short len;
|
short len;
|
||||||
|
|
||||||
if (apduBuffer[ISO7816.OFFSET_P1] == GET_STATUS_P1_APPLICATION) {
|
if (apduBuffer[OFFSET_P1] == GET_STATUS_P1_APPLICATION) {
|
||||||
len = getApplicationStatus(apduBuffer, SecureChannel.SC_OUT_OFFSET);
|
len = getApplicationStatus(apduBuffer, SecureChannel.SC_OUT_OFFSET);
|
||||||
} else if (apduBuffer[ISO7816.OFFSET_P1] == GET_STATUS_P1_KEY_PATH) {
|
} else if (apduBuffer[OFFSET_P1] == GET_STATUS_P1_KEY_PATH) {
|
||||||
len = getKeyStatus(apduBuffer, SecureChannel.SC_OUT_OFFSET);
|
len = getKeyStatus(apduBuffer, SecureChannel.SC_OUT_OFFSET);
|
||||||
} else {
|
} else {
|
||||||
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
|
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
|
||||||
|
@ -446,36 +448,6 @@ public class KeycardApplet extends Applet {
|
||||||
secureChannel.respond(apdu, len, ISO7816.SW_NO_ERROR);
|
secureChannel.respond(apdu, len, ISO7816.SW_NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the content of the NDEF data file returned by the NDEF applet. Requires a secure channel to be already open
|
|
||||||
* and the PIN to be verified.
|
|
||||||
*
|
|
||||||
* @param apdu the JCRE-owned APDU object.
|
|
||||||
*/
|
|
||||||
private void setNDEF(APDU apdu) {
|
|
||||||
byte[] apduBuffer = apdu.getBuffer();
|
|
||||||
secureChannel.preprocessAPDU(apduBuffer);
|
|
||||||
|
|
||||||
if (!pin.isValidated()) {
|
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
|
||||||
}
|
|
||||||
|
|
||||||
short dataLen = Util.makeShort((byte) 0x00, apduBuffer[ISO7816.OFFSET_LC]);
|
|
||||||
short offset;
|
|
||||||
|
|
||||||
if (Util.makeShort(apduBuffer[ISO7816.OFFSET_CDATA], apduBuffer[(short)(ISO7816.OFFSET_CDATA + 1)]) != (short)(dataLen - 2)) {
|
|
||||||
offset = ISO7816.OFFSET_P2;
|
|
||||||
apduBuffer[ISO7816.OFFSET_P2] = 0;
|
|
||||||
dataLen += 2;
|
|
||||||
} else {
|
|
||||||
offset = ISO7816.OFFSET_CDATA;
|
|
||||||
}
|
|
||||||
|
|
||||||
JCSystem.beginTransaction();
|
|
||||||
Util.arrayCopy(apduBuffer, offset, SharedMemory.ndefDataFile, (short) 0, dataLen);
|
|
||||||
JCSystem.commitTransaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the Application Status Template to the APDU buffer. Invoked internally by the getStatus method. This
|
* Writes the Application Status Template to the APDU buffer. Invoked internally by the getStatus method. This
|
||||||
* template is useful to understand if the card is blocked, if it has valid keys and if public key derivation is
|
* template is useful to understand if the card is blocked, if it has valid keys and if public key derivation is
|
||||||
|
@ -550,7 +522,7 @@ public class KeycardApplet extends Applet {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(apduBuffer[ISO7816.OFFSET_P1]) {
|
switch(apduBuffer[OFFSET_P1]) {
|
||||||
case CHANGE_PIN_P1_USER_PIN:
|
case CHANGE_PIN_P1_USER_PIN:
|
||||||
changeUserPIN(apduBuffer, len);
|
changeUserPIN(apduBuffer, len);
|
||||||
break;
|
break;
|
||||||
|
@ -653,7 +625,7 @@ public class KeycardApplet extends Applet {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (apduBuffer[ISO7816.OFFSET_P1]) {
|
switch (apduBuffer[OFFSET_P1]) {
|
||||||
case LOAD_KEY_P1_EC:
|
case LOAD_KEY_P1_EC:
|
||||||
case LOAD_KEY_P1_EXT_EC:
|
case LOAD_KEY_P1_EXT_EC:
|
||||||
loadKeyPair(apduBuffer);
|
loadKeyPair(apduBuffer);
|
||||||
|
@ -814,7 +786,7 @@ public class KeycardApplet extends Applet {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
doDerive(apduBuffer, (short) 0, len, apduBuffer[ISO7816.OFFSET_P1], true);
|
doDerive(apduBuffer, (short) 0, len, apduBuffer[OFFSET_P1], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -968,7 +940,7 @@ public class KeycardApplet extends Applet {
|
||||||
byte[] apduBuffer = apdu.getBuffer();
|
byte[] apduBuffer = apdu.getBuffer();
|
||||||
secureChannel.preprocessAPDU(apduBuffer);
|
secureChannel.preprocessAPDU(apduBuffer);
|
||||||
|
|
||||||
short csLen = apduBuffer[ISO7816.OFFSET_P1];
|
short csLen = apduBuffer[OFFSET_P1];
|
||||||
|
|
||||||
if (csLen < GENERATE_MNEMONIC_P1_CS_MIN || csLen > GENERATE_MNEMONIC_P1_CS_MAX) {
|
if (csLen < GENERATE_MNEMONIC_P1_CS_MIN || csLen > GENERATE_MNEMONIC_P1_CS_MAX) {
|
||||||
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
|
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
|
||||||
|
@ -1105,7 +1077,7 @@ public class KeycardApplet extends Applet {
|
||||||
ECPrivateKey signingKey;
|
ECPrivateKey signingKey;
|
||||||
ECPublicKey outputKey;
|
ECPublicKey outputKey;
|
||||||
|
|
||||||
switch((byte) (apduBuffer[ISO7816.OFFSET_P1] & ~DERIVE_P1_SOURCE_MASK)) {
|
switch((byte) (apduBuffer[OFFSET_P1] & ~DERIVE_P1_SOURCE_MASK)) {
|
||||||
case SIGN_P1_CURRENT_KEY:
|
case SIGN_P1_CURRENT_KEY:
|
||||||
signingKey = privateKey;
|
signingKey = privateKey;
|
||||||
outputKey = publicKey;
|
outputKey = publicKey;
|
||||||
|
@ -1154,7 +1126,7 @@ public class KeycardApplet extends Applet {
|
||||||
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte derivationSource = (byte) (apduBuffer[ISO7816.OFFSET_P1] & DERIVE_P1_SOURCE_MASK);
|
byte derivationSource = (byte) (apduBuffer[OFFSET_P1] & DERIVE_P1_SOURCE_MASK);
|
||||||
doDerive(apduBuffer, MessageDigest.LENGTH_SHA_256, pathLen, derivationSource, makeCurrent);
|
doDerive(apduBuffer, MessageDigest.LENGTH_SHA_256, pathLen, derivationSource, makeCurrent);
|
||||||
} else {
|
} else {
|
||||||
if (len != MessageDigest.LENGTH_SHA_256) {
|
if (len != MessageDigest.LENGTH_SHA_256) {
|
||||||
|
@ -1257,9 +1229,9 @@ public class KeycardApplet extends Applet {
|
||||||
|
|
||||||
boolean derive = false;
|
boolean derive = false;
|
||||||
boolean makeCurrent = false;
|
boolean makeCurrent = false;
|
||||||
byte derivationSource = (byte) (apduBuffer[ISO7816.OFFSET_P1] & DERIVE_P1_SOURCE_MASK);
|
byte derivationSource = (byte) (apduBuffer[OFFSET_P1] & DERIVE_P1_SOURCE_MASK);
|
||||||
|
|
||||||
switch ((byte) (apduBuffer[ISO7816.OFFSET_P1] & ~DERIVE_P1_SOURCE_MASK)) {
|
switch ((byte) (apduBuffer[OFFSET_P1] & ~DERIVE_P1_SOURCE_MASK)) {
|
||||||
case EXPORT_KEY_P1_CURRENT:
|
case EXPORT_KEY_P1_CURRENT:
|
||||||
break;
|
break;
|
||||||
case EXPORT_KEY_P1_DERIVE_AND_MAKE_CURRENT:
|
case EXPORT_KEY_P1_DERIVE_AND_MAKE_CURRENT:
|
||||||
|
@ -1339,8 +1311,25 @@ public class KeycardApplet extends Applet {
|
||||||
secureChannel.preprocessAPDU(apduBuffer);
|
secureChannel.preprocessAPDU(apduBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
short outLen = Util.makeShort((byte) 0x00, data[0]);
|
byte[] dst;
|
||||||
Util.arrayCopyNonAtomic(data, (short) 1, apduBuffer, SecureChannel.SC_OUT_OFFSET, outLen);
|
|
||||||
|
switch (apduBuffer[OFFSET_P1]) {
|
||||||
|
case STORE_DATA_P1_PUBLIC:
|
||||||
|
dst = data;
|
||||||
|
break;
|
||||||
|
case STORE_DATA_P1_NDEF:
|
||||||
|
dst = SharedMemory.ndefDataFile;
|
||||||
|
break;
|
||||||
|
case STORE_DATA_P1_CASH:
|
||||||
|
dst = SharedMemory.cashDataFile;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
short outLen = Util.makeShort((byte) 0x00, dst[0]);
|
||||||
|
Util.arrayCopyNonAtomic(dst, (short) 1, apduBuffer, SecureChannel.SC_OUT_OFFSET, outLen);
|
||||||
|
|
||||||
if (secureChannel.isOpen()) {
|
if (secureChannel.isOpen()) {
|
||||||
secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR);
|
secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR);
|
||||||
|
@ -1362,14 +1351,31 @@ public class KeycardApplet extends Applet {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
byte[] dst;
|
||||||
|
|
||||||
|
switch (apduBuffer[OFFSET_P1]) {
|
||||||
|
case STORE_DATA_P1_PUBLIC:
|
||||||
|
dst = data;
|
||||||
|
break;
|
||||||
|
case STORE_DATA_P1_NDEF:
|
||||||
|
dst = SharedMemory.ndefDataFile;
|
||||||
|
break;
|
||||||
|
case STORE_DATA_P1_CASH:
|
||||||
|
dst = SharedMemory.cashDataFile;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
short dataLen = Util.makeShort((byte) 0x00, apduBuffer[ISO7816.OFFSET_LC]);
|
short dataLen = Util.makeShort((byte) 0x00, apduBuffer[ISO7816.OFFSET_LC]);
|
||||||
|
|
||||||
if (dataLen > MAX_DATA_LENGTH) {
|
if (dataLen >= (short) dst.length) {
|
||||||
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
JCSystem.beginTransaction();
|
JCSystem.beginTransaction();
|
||||||
Util.arrayCopy(apduBuffer, ISO7816.OFFSET_LC, data, (short) 0, (short)(dataLen + 1));
|
Util.arrayCopy(apduBuffer, ISO7816.OFFSET_LC, dst, (short) 0, (short)(dataLen + 1));
|
||||||
JCSystem.commitTransaction();
|
JCSystem.commitTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,8 @@ public class NDEFApplet extends Applet {
|
||||||
private static final short NDEF_READ_SIZE = (short) 0xff;
|
private static final short NDEF_READ_SIZE = (short) 0xff;
|
||||||
|
|
||||||
private static final byte[] NDEF_CAPS_FILE = {
|
private static final byte[] NDEF_CAPS_FILE = {
|
||||||
(byte) 0x00, (byte) 0x0f, (byte) 0x20, (byte) 0x00, (byte) 0xff, (byte) 0x00, (byte) 0x01, (byte) 0x04,
|
(byte) 0x0f, (byte) 0x00, (byte) 0x0f, (byte) 0x20, (byte) 0x00, (byte) 0xff, (byte) 0x00, (byte) 0x01,
|
||||||
(byte) 0x06, (byte) 0xe1, (byte) 0x04, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0xff
|
(byte) 0x04, (byte) 0x06, (byte) 0xe1, (byte) 0x04, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0xff
|
||||||
};
|
};
|
||||||
|
|
||||||
private short selectedFile;
|
private short selectedFile;
|
||||||
|
@ -50,9 +50,9 @@ public class NDEFApplet extends Applet {
|
||||||
short c9Off = (short)(bOffset + bArray[bOffset] + 1); // Skip AID
|
short c9Off = (short)(bOffset + bArray[bOffset] + 1); // Skip AID
|
||||||
c9Off += (short)(bArray[c9Off] + 1); // Skip Privileges and parameter length
|
c9Off += (short)(bArray[c9Off] + 1); // Skip Privileges and parameter length
|
||||||
|
|
||||||
short dataLen = Util.makeShort((byte) 0x00, bArray[c9Off++]);
|
short dataLen = Util.makeShort((byte) 0x00, bArray[c9Off]);
|
||||||
if ((dataLen > 2) && ((short)(dataLen - 2) == Util.makeShort(bArray[c9Off], bArray[(short)(c9Off + 1)]))) {
|
if ((dataLen > 2) && ((short)(dataLen - 2) == Util.makeShort(bArray[(short)(c9Off + 1)], bArray[(short)(c9Off + 2)]))) {
|
||||||
Util.arrayCopyNonAtomic(bArray, c9Off, SharedMemory.ndefDataFile, (short) 0, dataLen);
|
Util.arrayCopyNonAtomic(bArray, c9Off, SharedMemory.ndefDataFile, (short) 0, (short)(dataLen + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
register(bArray, (short) (bOffset + 1), bArray[bOffset]);
|
register(bArray, (short) (bOffset + 1), bArray[bOffset]);
|
||||||
|
@ -113,22 +113,20 @@ public class NDEFApplet extends Applet {
|
||||||
byte[] apduBuffer = apdu.getBuffer();
|
byte[] apduBuffer = apdu.getBuffer();
|
||||||
|
|
||||||
byte[] data;
|
byte[] data;
|
||||||
short dataLen;
|
|
||||||
|
|
||||||
switch(selectedFile) {
|
switch(selectedFile) {
|
||||||
case FILEID_NDEF_CAPS:
|
case FILEID_NDEF_CAPS:
|
||||||
data = NDEF_CAPS_FILE;
|
data = NDEF_CAPS_FILE;
|
||||||
dataLen = (short) NDEF_CAPS_FILE.length;
|
|
||||||
break;
|
break;
|
||||||
case FILEID_NDEF_DATA:
|
case FILEID_NDEF_DATA:
|
||||||
data = SharedMemory.ndefDataFile;
|
data = SharedMemory.ndefDataFile;
|
||||||
dataLen = (short) (Util.makeShort(data[0], data[1]) + 2);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
short dataLen = Util.makeShort((byte) 0x00, data[0]);
|
||||||
short offset = Util.getShort(apduBuffer, ISO7816.OFFSET_P1);
|
short offset = Util.getShort(apduBuffer, ISO7816.OFFSET_P1);
|
||||||
|
|
||||||
if (offset < 0 || offset >= dataLen) {
|
if (offset < 0 || offset >= dataLen) {
|
||||||
|
@ -144,6 +142,9 @@ public class NDEFApplet extends Applet {
|
||||||
le = (short)(dataLen - offset);
|
le = (short)(dataLen - offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skip the len byte in data
|
||||||
|
offset++;
|
||||||
|
|
||||||
apdu.setOutgoingLength(le);
|
apdu.setOutgoingLength(le);
|
||||||
apdu.sendBytesLong(data, offset, le);
|
apdu.sendBytesLong(data, offset, le);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,5 +5,8 @@ package im.status.keycard;
|
||||||
*/
|
*/
|
||||||
class SharedMemory {
|
class SharedMemory {
|
||||||
/** The NDEF data file. Read through the NDEFApplet. **/
|
/** The NDEF data file. Read through the NDEFApplet. **/
|
||||||
static final byte[] ndefDataFile = new byte[SecureChannel.SC_MAX_PLAIN_LENGTH];
|
static final byte[] ndefDataFile = new byte[SecureChannel.SC_MAX_PLAIN_LENGTH + 1];
|
||||||
|
|
||||||
|
/** The Cash data file. Read through the CashApplet. **/
|
||||||
|
static final byte[] cashDataFile = new byte[KeycardApplet.MAX_DATA_LENGTH + 1];
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,6 +153,7 @@ public class KeycardTest {
|
||||||
aid = AIDUtil.create(Identifiers.CASH_AID);
|
aid = AIDUtil.create(Identifiers.CASH_AID);
|
||||||
bos.write(Identifiers.CASH_INSTANCE_AID.length);
|
bos.write(Identifiers.CASH_INSTANCE_AID.length);
|
||||||
bos.write(Identifiers.CASH_INSTANCE_AID);
|
bos.write(Identifiers.CASH_INSTANCE_AID);
|
||||||
|
bos.write(new byte[] {0x01, 0x00, 0x02, (byte) 0xC9, 0x00});
|
||||||
|
|
||||||
simulator.installApplet(aid, CashApplet.class, bos.toByteArray(), (short) 0, (byte) bos.size());
|
simulator.installApplet(aid, CashApplet.class, bos.toByteArray(), (short) 0, (byte) bos.size());
|
||||||
bos.reset();
|
bos.reset();
|
||||||
|
@ -497,40 +498,6 @@ public class KeycardTest {
|
||||||
assertNotEquals(null, path);
|
assertNotEquals(null, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("SET NDEF command")
|
|
||||||
@Capabilities("ndef")
|
|
||||||
void setNDEFTest() throws Exception {
|
|
||||||
byte[] ndefData = {
|
|
||||||
(byte) 0x00, (byte) 0x24, (byte) 0xd4, (byte) 0x0f, (byte) 0x12, (byte) 0x61, (byte) 0x6e, (byte) 0x64,
|
|
||||||
(byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d,
|
|
||||||
(byte) 0x3a, (byte) 0x70, (byte) 0x6b, (byte) 0x67, (byte) 0x69, (byte) 0x6d, (byte) 0x2e, (byte) 0x73,
|
|
||||||
(byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x75, (byte) 0x73, (byte) 0x2e, (byte) 0x65, (byte) 0x74,
|
|
||||||
(byte) 0x68, (byte) 0x65, (byte) 0x72, (byte) 0x65, (byte) 0x75, (byte) 0x6d
|
|
||||||
};
|
|
||||||
|
|
||||||
// Security condition violation: SecureChannel not open
|
|
||||||
APDUResponse response = cmdSet.setNDEF(ndefData);
|
|
||||||
assertEquals(0x6985, response.getSw());
|
|
||||||
|
|
||||||
cmdSet.autoOpenSecureChannel();
|
|
||||||
|
|
||||||
// Security condition violation: PIN not verified
|
|
||||||
response = cmdSet.setNDEF(ndefData);
|
|
||||||
assertEquals(0x6985, response.getSw());
|
|
||||||
|
|
||||||
response = cmdSet.verifyPIN("000000");
|
|
||||||
assertEquals(0x9000, response.getSw());
|
|
||||||
|
|
||||||
// Good case.
|
|
||||||
response = cmdSet.setNDEF(ndefData);
|
|
||||||
assertEquals(0x9000, response.getSw());
|
|
||||||
|
|
||||||
// Good case with no length.
|
|
||||||
response = cmdSet.setNDEF(Arrays.copyOfRange(ndefData, 2, ndefData.length));
|
|
||||||
assertEquals(0x9000, response.getSw());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("VERIFY PIN command")
|
@DisplayName("VERIFY PIN command")
|
||||||
@Capabilities("credentialsManagement")
|
@Capabilities("credentialsManagement")
|
||||||
|
@ -1305,7 +1272,7 @@ public class KeycardTest {
|
||||||
|
|
||||||
if (cmdSet.getApplicationInfo().hasSecureChannelCapability()) {
|
if (cmdSet.getApplicationInfo().hasSecureChannelCapability()) {
|
||||||
// Security condition violation: SecureChannel not open
|
// Security condition violation: SecureChannel not open
|
||||||
response = cmdSet.storePublicData(new byte[20]);
|
response = cmdSet.storeData(new byte[20], KeycardCommandSet.STORE_DATA_P1_PUBLIC);
|
||||||
assertEquals(0x6985, response.getSw());
|
assertEquals(0x6985, response.getSw());
|
||||||
|
|
||||||
cmdSet.autoOpenSecureChannel();
|
cmdSet.autoOpenSecureChannel();
|
||||||
|
@ -1313,7 +1280,7 @@ public class KeycardTest {
|
||||||
|
|
||||||
if (cmdSet.getApplicationInfo().hasCredentialsManagementCapability()) {
|
if (cmdSet.getApplicationInfo().hasCredentialsManagementCapability()) {
|
||||||
// Security condition violation: PIN not verified
|
// Security condition violation: PIN not verified
|
||||||
response = cmdSet.storePublicData(new byte[20]);
|
response = cmdSet.storeData(new byte[20], KeycardCommandSet.STORE_DATA_P1_PUBLIC);
|
||||||
assertEquals(0x6985, response.getSw());
|
assertEquals(0x6985, response.getSw());
|
||||||
|
|
||||||
response = cmdSet.verifyPIN("000000");
|
response = cmdSet.verifyPIN("000000");
|
||||||
|
@ -1321,7 +1288,7 @@ public class KeycardTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data too long
|
// Data too long
|
||||||
response = cmdSet.storePublicData(new byte[128]);
|
response = cmdSet.storeData(new byte[128], KeycardCommandSet.STORE_DATA_P1_PUBLIC);
|
||||||
assertEquals(0x6A80, response.getSw());
|
assertEquals(0x6A80, response.getSw());
|
||||||
|
|
||||||
byte[] data = new byte[127];
|
byte[] data = new byte[127];
|
||||||
|
@ -1331,38 +1298,80 @@ public class KeycardTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Correct data
|
// Correct data
|
||||||
response = cmdSet.storePublicData(data);
|
response = cmdSet.storeData(data, KeycardCommandSet.STORE_DATA_P1_PUBLIC);
|
||||||
|
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
|
|
||||||
// Read data back with secure channel
|
// Read data back with secure channel
|
||||||
response = cmdSet.getPublicData();
|
response = cmdSet.getData(KeycardCommandSet.STORE_DATA_P1_PUBLIC);
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
assertArrayEquals(data, response.getData());
|
assertArrayEquals(data, response.getData());
|
||||||
|
|
||||||
// Empty data
|
// Empty data
|
||||||
response = cmdSet.storePublicData(new byte[0]);
|
response = cmdSet.storeData(new byte[0], KeycardCommandSet.STORE_DATA_P1_PUBLIC);
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
|
|
||||||
response = cmdSet.getPublicData();
|
response = cmdSet.getData(KeycardCommandSet.STORE_DATA_P1_PUBLIC);
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
assertEquals(0, response.getData().length);
|
assertEquals(0, response.getData().length);
|
||||||
|
|
||||||
// Shorter data
|
// Shorter data
|
||||||
data = Arrays.copyOf(data, 20);
|
data = Arrays.copyOf(data, 20);
|
||||||
response = cmdSet.storePublicData(data);
|
response = cmdSet.storeData(data, KeycardCommandSet.STORE_DATA_P1_PUBLIC);
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
|
|
||||||
// GET DATA without Secure Channel
|
// GET DATA without Secure Channel
|
||||||
cmdSet.select().checkOK();
|
cmdSet.select().checkOK();
|
||||||
|
|
||||||
response = cmdSet.getPublicData();
|
response = cmdSet.getData(KeycardCommandSet.STORE_DATA_P1_PUBLIC);
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
assertArrayEquals(data, response.getData());
|
assertArrayEquals(data, response.getData());
|
||||||
|
|
||||||
|
if (cmdSet.getApplicationInfo().hasNDEFCapability()) {
|
||||||
|
byte[] ndefData = {
|
||||||
|
(byte) 0x00, (byte) 0x24, (byte) 0xd4, (byte) 0x0f, (byte) 0x12, (byte) 0x61, (byte) 0x6e, (byte) 0x64,
|
||||||
|
(byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d,
|
||||||
|
(byte) 0x3a, (byte) 0x70, (byte) 0x6b, (byte) 0x67, (byte) 0x69, (byte) 0x6d, (byte) 0x2e, (byte) 0x73,
|
||||||
|
(byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x75, (byte) 0x73, (byte) 0x2e, (byte) 0x65, (byte) 0x74,
|
||||||
|
(byte) 0x68, (byte) 0x65, (byte) 0x72, (byte) 0x65, (byte) 0x75, (byte) 0x6d
|
||||||
|
};
|
||||||
|
|
||||||
|
// Security condition violation: SecureChannel not open
|
||||||
|
response = cmdSet.setNDEF(ndefData);
|
||||||
|
assertEquals(0x6985, response.getSw());
|
||||||
|
|
||||||
|
cmdSet.autoOpenSecureChannel();
|
||||||
|
|
||||||
|
// Security condition violation: PIN not verified
|
||||||
|
response = cmdSet.setNDEF(ndefData);
|
||||||
|
assertEquals(0x6985, response.getSw());
|
||||||
|
|
||||||
|
response = cmdSet.verifyPIN("000000");
|
||||||
|
assertEquals(0x9000, response.getSw());
|
||||||
|
|
||||||
|
// Good case.
|
||||||
|
response = cmdSet.setNDEF(ndefData);
|
||||||
|
assertEquals(0x9000, response.getSw());
|
||||||
|
|
||||||
|
// Good case with no length.
|
||||||
|
response = cmdSet.setNDEF(Arrays.copyOfRange(ndefData, 2, ndefData.length));
|
||||||
|
assertEquals(0x9000, response.getSw());
|
||||||
|
}
|
||||||
|
|
||||||
|
data[0] = (byte) 0xAA;
|
||||||
|
|
||||||
|
response = cmdSet.storeData(data, KeycardCommandSet.STORE_DATA_P1_CASH);
|
||||||
|
assertEquals(0x9000, response.getSw());
|
||||||
|
|
||||||
|
CashCommandSet cashCmdSet = new CashCommandSet(sdkChannel);
|
||||||
|
response = cashCmdSet.select();
|
||||||
|
assertEquals(0x9000, response.getSw());
|
||||||
|
CashApplicationInfo info = new CashApplicationInfo(response.getData());
|
||||||
|
assertArrayEquals(data, info.getPubData());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("Test the Cash applet")
|
@DisplayName("Test the Cash applet")
|
||||||
@Tag("manual")
|
|
||||||
void cashTest() throws Exception {
|
void cashTest() throws Exception {
|
||||||
CashCommandSet cashCmdSet = new CashCommandSet(sdkChannel);
|
CashCommandSet cashCmdSet = new CashCommandSet(sdkChannel);
|
||||||
APDUResponse response = cashCmdSet.select();
|
APDUResponse response = cashCmdSet.select();
|
||||||
|
|
Loading…
Reference in New Issue