add data in cash applet

This commit is contained in:
Michele Balistreri 2019-10-16 14:53:36 +03:00
parent 1b716790f0
commit 4cc3f1576c
No known key found for this signature in database
GPG Key ID: E9567DA33A4F791A
7 changed files with 139 additions and 106 deletions

View File

@ -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")

View File

@ -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'
} }

View File

@ -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);
} }

View File

@ -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();
} }

View File

@ -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);
} }

View File

@ -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];
} }

View File

@ -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();