From f71286e7d1e2d8f33d165efb1386646c4b06008d Mon Sep 17 00:00:00 2001 From: Michele Balistreri Date: Mon, 8 Oct 2018 13:18:55 +0200 Subject: [PATCH] add NDEF applet --- build.gradle | 5 + .../java/im/status/wallet/NDEFApplet.java | 147 ++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 src/main/java/im/status/wallet/NDEFApplet.java diff --git a/build.gradle b/build.gradle index a34d5b8..c78ad6f 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,10 @@ javacard { aid = '0x53:0x74:0x61:0x74:0x75:0x73:0x57:0x61:0x6c:0x6c:0x65:0x74:0x41:0x70:0x70' className = 'WalletApplet' } + applet { + aid = '0x53:0x74:0x61:0x74:0x75:0x73:0x57:0x61:0x6c:0x6c:0x65:0x74:0x4e:0x46:0x43' + className = 'NDEFApplet' + } version = '1.2' } } @@ -64,6 +68,7 @@ task install(type: Exec) { install_for_load -pkgAID 53746174757357616C6C6574 load -file build/javacard/im/status/wallet/javacard/wallet.cap send_apdu -sc 1 -APDU 80E60C005F0C53746174757357616C6C65740F53746174757357616C6C65744170700F53746174757357616C6C657441707001002EC92C313233343536373839303132e929d425d7f73c2a0a24ffefad87b65e9b2ee96603eab34d64088b5aae2a026f00 + install_for_install -AID 53746174757357616C6C65744e4643 -pkgAID 53746174757357616C6C6574 -instAID D2760000850101 card_disconnect release_context """ diff --git a/src/main/java/im/status/wallet/NDEFApplet.java b/src/main/java/im/status/wallet/NDEFApplet.java new file mode 100644 index 0000000..b65efc0 --- /dev/null +++ b/src/main/java/im/status/wallet/NDEFApplet.java @@ -0,0 +1,147 @@ +package im.status.wallet; + +import javacard.framework.*; + +/** + * The applet's main class. All incoming commands a processed by this class. + */ +public class NDEFApplet extends Applet { + private static final byte INS_READ_BINARY = (byte) 0xb0; + + private static final short FILEID_NONE = (short) 0xffff; + private static final short FILEID_NDEF_CAPS = (short) 0xe103; + private static final short FILEID_NDEF_DATA = (short) 0xe104; + + private static final byte SELECT_P1_BY_FILEID = (byte) 0x00; + private static final byte SELECT_P2_FIRST_OR_ONLY = (byte) 0x0c; + + private static final short NDEF_READ_SIZE = (short) 0xff; + + private static final byte[] NDEF_DATA_FILE = { + (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 + }; + + 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 + }; + + private short selectedFile; + + /** + * Invoked during applet installation. Creates an instance of this class. The installation parameters are passed in + * the given buffer. + * + * @param bArray installation parameters buffer + * @param bOffset offset where the installation parameters begin + * @param bLength length of the installation parameters + */ + public static void install(byte[] bArray, short bOffset, byte bLength) { + new NDEFApplet(bArray, bOffset, bLength); + } + + /** + * Application constructor. All memory allocation is done here. The reason for this is two-fold: first the card might + * not have Garbage Collection so dynamic allocation will eventually eat all memory. The second reason is to be sure + * that if the application installs successfully, there is no risk of running out of memory because of other applets + * allocating memory. The constructor also registers the applet with the JCRE so that it becomes selectable. + * + * @param bArray installation parameters buffer + * @param bOffset offset where the installation parameters begin + * @param bLength length of the installation parameters + */ + public NDEFApplet(byte[] bArray, short bOffset, byte bLength) { + register(bArray, (short) (bOffset + 1), bArray[bOffset]); + } + + /** + * This method is called on every incoming APDU. This method is just a dispatcher which invokes the correct method + * depending on the INS of the APDU. + * + * @param apdu the JCRE-owned APDU object. + * @throws ISOException any processing error + */ + public void process(APDU apdu) throws ISOException { + if (selectingApplet()) { + selectedFile = FILEID_NONE; + return; + } + + byte[] apduBuffer = apdu.getBuffer(); + + switch (apduBuffer[ISO7816.OFFSET_INS]) { + case ISO7816.INS_SELECT: + processSelect(apdu); + break; + case INS_READ_BINARY: + processReadBinary(apdu); + break; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + break; + } + } + + private void processSelect(APDU apdu) { + byte[] apduBuffer = apdu.getBuffer(); + apdu.setIncomingAndReceive(); + + if(apduBuffer[ISO7816.OFFSET_P1] != SELECT_P1_BY_FILEID || apduBuffer[ISO7816.OFFSET_P2] != SELECT_P2_FIRST_OR_ONLY) { + ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); + } else if (apduBuffer[ISO7816.OFFSET_LC] != 2) { + ISOException.throwIt(ISO7816.SW_WRONG_DATA); + } + + short fid = Util.getShort(apduBuffer, ISO7816.OFFSET_CDATA); + + switch(fid) { + case FILEID_NDEF_CAPS: + case FILEID_NDEF_DATA: + selectedFile = fid; + break; + default: + ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); + break; + } + } + + private void processReadBinary(APDU apdu) { + byte[] apduBuffer = apdu.getBuffer(); + + byte[] output; + + switch(selectedFile) { + case FILEID_NDEF_CAPS: + output = NDEF_CAPS_FILE; + break; + case FILEID_NDEF_DATA: + output = NDEF_DATA_FILE; + break; + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + return; + } + + short offset = Util.getShort(apduBuffer, ISO7816.OFFSET_P1); + + if (offset < 0 || offset >= (short) output.length) { + ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); + } + + short le = apdu.setOutgoingNoChaining(); + if (le > NDEF_READ_SIZE) { + le = NDEF_READ_SIZE; + } + + if((short)(offset + le) >= (short) output.length) { + le = (short)(((short) output.length) - offset); + } + + apdu.setOutgoingLength(le); + apdu.sendBytesLong(output, offset, le); + } +}