From 4cc3f1576c6e428a9f795e23ff388cc0a7a2fcd4 Mon Sep 17 00:00:00 2001 From: Michele Balistreri Date: Wed, 16 Oct 2019 14:53:36 +0300 Subject: [PATCH] add data in cash applet --- build.gradle | 6 +- buildSrc/build.gradle | 2 +- .../java/im/status/keycard/CashApplet.java | 14 +++ .../java/im/status/keycard/KeycardApplet.java | 104 +++++++++--------- .../java/im/status/keycard/NDEFApplet.java | 17 +-- .../java/im/status/keycard/SharedMemory.java | 5 +- .../java/im/status/keycard/KeycardTest.java | 97 ++++++++-------- 7 files changed, 139 insertions(+), 106 deletions(-) diff --git a/build.gradle b/build.gradle index fbe4f7f..bcbdbe1 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { dependencies { 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' className = 'CashApplet' } - version = '2.3' + version = '3.0' } } @@ -55,7 +55,7 @@ dependencies { testCompile(files("../jcardsim/jcardsim-3.0.5-SNAPSHOT.jar")) testCompile('org.web3j:core:2.3.1') 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.junit.jupiter:junit-jupiter-api:5.1.1") testRuntime("org.junit.jupiter:junit-jupiter-engine:5.1.1") diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 5840932..90e177c 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -4,5 +4,5 @@ repositories { } dependencies { - compile 'com.github.status-im.status-keycard-java:desktop:4a69788' + compile 'com.github.status-im.status-keycard-java:desktop:8cb43e6' } \ No newline at end of file diff --git a/src/main/java/im/status/keycard/CashApplet.java b/src/main/java/im/status/keycard/CashApplet.java index a293671..1067d3a 100644 --- a/src/main/java/im/status/keycard/CashApplet.java +++ b/src/main/java/im/status/keycard/CashApplet.java @@ -5,6 +5,7 @@ import javacard.security.*; public class CashApplet extends Applet { 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 ECPublicKey publicKey; @@ -51,6 +52,14 @@ public class CashApplet extends Applet { signature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); 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]); } @@ -99,6 +108,11 @@ public class CashApplet extends Applet { Util.setShort(apduBuffer, off, KeycardApplet.APPLICATION_VERSION); 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); apdu.setOutgoingAndSend((short) 0, off); } diff --git a/src/main/java/im/status/keycard/KeycardApplet.java b/src/main/java/im/status/keycard/KeycardApplet.java index 5f11d03..89303a2 100644 --- a/src/main/java/im/status/keycard/KeycardApplet.java +++ b/src/main/java/im/status/keycard/KeycardApplet.java @@ -4,14 +4,15 @@ import javacard.framework.*; import javacard.security.*; import javacardx.crypto.Cipher; +import static javacard.framework.ISO7816.OFFSET_P1; + /** * The applet's main class. All incoming commands a processed by this class. */ 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_SET_NDEF = (byte) 0xF3; static final byte INS_INIT = (byte) 0xFE; static final byte INS_VERIFY_PIN = (byte) 0x20; 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_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_KEY_TEMPLATE = (byte) 0xA1; @@ -239,9 +244,6 @@ public class KeycardApplet extends Applet { case INS_GET_STATUS: getStatus(apdu); break; - case INS_SET_NDEF: - setNDEF(apdu); - break; case INS_VERIFY_PIN: verifyPIN(apdu); break; @@ -434,9 +436,9 @@ public class KeycardApplet extends Applet { 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); - } 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); } else { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); @@ -446,36 +448,6 @@ public class KeycardApplet extends Applet { 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 * 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); } - switch(apduBuffer[ISO7816.OFFSET_P1]) { + switch(apduBuffer[OFFSET_P1]) { case CHANGE_PIN_P1_USER_PIN: changeUserPIN(apduBuffer, len); break; @@ -653,7 +625,7 @@ public class KeycardApplet extends Applet { 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_EXT_EC: loadKeyPair(apduBuffer); @@ -814,7 +786,7 @@ public class KeycardApplet extends Applet { 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(); 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) { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); @@ -1105,7 +1077,7 @@ public class KeycardApplet extends Applet { ECPrivateKey signingKey; 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: signingKey = privateKey; outputKey = publicKey; @@ -1154,7 +1126,7 @@ public class KeycardApplet extends Applet { 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); } else { if (len != MessageDigest.LENGTH_SHA_256) { @@ -1257,9 +1229,9 @@ public class KeycardApplet extends Applet { boolean derive = 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: break; case EXPORT_KEY_P1_DERIVE_AND_MAKE_CURRENT: @@ -1339,8 +1311,25 @@ public class KeycardApplet extends Applet { secureChannel.preprocessAPDU(apduBuffer); } - short outLen = Util.makeShort((byte) 0x00, data[0]); - Util.arrayCopyNonAtomic(data, (short) 1, apduBuffer, SecureChannel.SC_OUT_OFFSET, outLen); + 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 outLen = Util.makeShort((byte) 0x00, dst[0]); + Util.arrayCopyNonAtomic(dst, (short) 1, apduBuffer, SecureChannel.SC_OUT_OFFSET, outLen); if (secureChannel.isOpen()) { secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR); @@ -1362,14 +1351,31 @@ public class KeycardApplet extends Applet { 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]); - if (dataLen > MAX_DATA_LENGTH) { + if (dataLen >= (short) dst.length) { ISOException.throwIt(ISO7816.SW_WRONG_DATA); } 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(); } diff --git a/src/main/java/im/status/keycard/NDEFApplet.java b/src/main/java/im/status/keycard/NDEFApplet.java index 80f21b0..498a6c4 100644 --- a/src/main/java/im/status/keycard/NDEFApplet.java +++ b/src/main/java/im/status/keycard/NDEFApplet.java @@ -18,8 +18,8 @@ public class NDEFApplet extends Applet { private static final short NDEF_READ_SIZE = (short) 0xff; private static final byte[] NDEF_CAPS_FILE = { - (byte) 0x00, (byte) 0x0f, (byte) 0x20, (byte) 0x00, (byte) 0xff, (byte) 0x00, (byte) 0x01, (byte) 0x04, - (byte) 0x06, (byte) 0xe1, (byte) 0x04, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0xff + (byte) 0x0f, (byte) 0x00, (byte) 0x0f, (byte) 0x20, (byte) 0x00, (byte) 0xff, (byte) 0x00, (byte) 0x01, + (byte) 0x04, (byte) 0x06, (byte) 0xe1, (byte) 0x04, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0xff }; private short selectedFile; @@ -50,9 +50,9 @@ public class NDEFApplet extends Applet { 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 > 2) && ((short)(dataLen - 2) == Util.makeShort(bArray[c9Off], bArray[(short)(c9Off + 1)]))) { - Util.arrayCopyNonAtomic(bArray, c9Off, SharedMemory.ndefDataFile, (short) 0, dataLen); + short dataLen = Util.makeShort((byte) 0x00, bArray[c9Off]); + if ((dataLen > 2) && ((short)(dataLen - 2) == Util.makeShort(bArray[(short)(c9Off + 1)], bArray[(short)(c9Off + 2)]))) { + Util.arrayCopyNonAtomic(bArray, c9Off, SharedMemory.ndefDataFile, (short) 0, (short)(dataLen + 1)); } register(bArray, (short) (bOffset + 1), bArray[bOffset]); @@ -113,22 +113,20 @@ public class NDEFApplet extends Applet { byte[] apduBuffer = apdu.getBuffer(); byte[] data; - short dataLen; switch(selectedFile) { case FILEID_NDEF_CAPS: data = NDEF_CAPS_FILE; - dataLen = (short) NDEF_CAPS_FILE.length; break; case FILEID_NDEF_DATA: data = SharedMemory.ndefDataFile; - dataLen = (short) (Util.makeShort(data[0], data[1]) + 2); break; default: ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); return; } + short dataLen = Util.makeShort((byte) 0x00, data[0]); short offset = Util.getShort(apduBuffer, ISO7816.OFFSET_P1); if (offset < 0 || offset >= dataLen) { @@ -144,6 +142,9 @@ public class NDEFApplet extends Applet { le = (short)(dataLen - offset); } + // skip the len byte in data + offset++; + apdu.setOutgoingLength(le); apdu.sendBytesLong(data, offset, le); } diff --git a/src/main/java/im/status/keycard/SharedMemory.java b/src/main/java/im/status/keycard/SharedMemory.java index 33fd00b..ea751b3 100644 --- a/src/main/java/im/status/keycard/SharedMemory.java +++ b/src/main/java/im/status/keycard/SharedMemory.java @@ -5,5 +5,8 @@ package im.status.keycard; */ class SharedMemory { /** 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]; } diff --git a/src/test/java/im/status/keycard/KeycardTest.java b/src/test/java/im/status/keycard/KeycardTest.java index 7a74cc8..55b2fad 100644 --- a/src/test/java/im/status/keycard/KeycardTest.java +++ b/src/test/java/im/status/keycard/KeycardTest.java @@ -153,6 +153,7 @@ public class KeycardTest { aid = AIDUtil.create(Identifiers.CASH_AID); bos.write(Identifiers.CASH_INSTANCE_AID.length); 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()); bos.reset(); @@ -497,40 +498,6 @@ public class KeycardTest { 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 @DisplayName("VERIFY PIN command") @Capabilities("credentialsManagement") @@ -1305,7 +1272,7 @@ public class KeycardTest { if (cmdSet.getApplicationInfo().hasSecureChannelCapability()) { // 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()); cmdSet.autoOpenSecureChannel(); @@ -1313,7 +1280,7 @@ public class KeycardTest { if (cmdSet.getApplicationInfo().hasCredentialsManagementCapability()) { // 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()); response = cmdSet.verifyPIN("000000"); @@ -1321,7 +1288,7 @@ public class KeycardTest { } // Data too long - response = cmdSet.storePublicData(new byte[128]); + response = cmdSet.storeData(new byte[128], KeycardCommandSet.STORE_DATA_P1_PUBLIC); assertEquals(0x6A80, response.getSw()); byte[] data = new byte[127]; @@ -1331,38 +1298,80 @@ public class KeycardTest { } // Correct data - response = cmdSet.storePublicData(data); + response = cmdSet.storeData(data, KeycardCommandSet.STORE_DATA_P1_PUBLIC); + assertEquals(0x9000, response.getSw()); // Read data back with secure channel - response = cmdSet.getPublicData(); + response = cmdSet.getData(KeycardCommandSet.STORE_DATA_P1_PUBLIC); assertEquals(0x9000, response.getSw()); assertArrayEquals(data, response.getData()); // Empty data - response = cmdSet.storePublicData(new byte[0]); + response = cmdSet.storeData(new byte[0], KeycardCommandSet.STORE_DATA_P1_PUBLIC); assertEquals(0x9000, response.getSw()); - response = cmdSet.getPublicData(); + response = cmdSet.getData(KeycardCommandSet.STORE_DATA_P1_PUBLIC); assertEquals(0x9000, response.getSw()); assertEquals(0, response.getData().length); // Shorter data data = Arrays.copyOf(data, 20); - response = cmdSet.storePublicData(data); + response = cmdSet.storeData(data, KeycardCommandSet.STORE_DATA_P1_PUBLIC); assertEquals(0x9000, response.getSw()); // GET DATA without Secure Channel cmdSet.select().checkOK(); - response = cmdSet.getPublicData(); + response = cmdSet.getData(KeycardCommandSet.STORE_DATA_P1_PUBLIC); assertEquals(0x9000, response.getSw()); 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 @DisplayName("Test the Cash applet") - @Tag("manual") void cashTest() throws Exception { CashCommandSet cashCmdSet = new CashCommandSet(sdkChannel); APDUResponse response = cashCmdSet.select();