diff --git a/src/main/java/im/status/keycard/KeycardApplet.java b/src/main/java/im/status/keycard/KeycardApplet.java index 8239995..a287ac4 100644 --- a/src/main/java/im/status/keycard/KeycardApplet.java +++ b/src/main/java/im/status/keycard/KeycardApplet.java @@ -25,6 +25,8 @@ public class KeycardApplet extends Applet { static final byte INS_SIGN = (byte) 0xC0; static final byte INS_SET_PINLESS_PATH = (byte) 0xC1; static final byte INS_EXPORT_KEY = (byte) 0xC2; + static final byte INS_GET_DATA = (byte) 0xCA; + static final byte INS_STORE_DATA = (byte) 0xE2; static final short SW_REFERENCED_DATA_NOT_FOUND = (short) 0x6A88; @@ -35,6 +37,7 @@ public class KeycardApplet extends Applet { static final byte KEY_PATH_MAX_DEPTH = 10; static final byte PAIRING_MAX_CLIENT_COUNT = 5; static final byte UID_LENGTH = 16; + static final byte MAX_DATA_LENGTH = 127; static final short CHAIN_CODE_SIZE = 32; static final short KEY_UID_LENGTH = 32; @@ -141,6 +144,8 @@ public class KeycardApplet extends Applet { private byte[] derivationOutput; + private byte[] data; + /** * Invoked during applet installation. Creates an instance of this class. The installation parameters are passed in * the given buffer. @@ -201,6 +206,8 @@ public class KeycardApplet extends Applet { derivationOutput = JCSystem.makeTransientByteArray((short) (Crypto.KEY_SECRET_SIZE + CHAIN_CODE_SIZE), JCSystem.CLEAR_ON_RESET); + data = new byte[MAX_DATA_LENGTH + 1]; + register(bArray, (short) (bOffset + 1), bArray[bOffset]); } @@ -283,6 +290,12 @@ public class KeycardApplet extends Applet { case INS_EXPORT_KEY: exportKey(apdu); break; + case INS_GET_DATA: + getData(apdu); + break; + case INS_STORE_DATA: + storeData(apdu); + break; default: ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); break; @@ -1434,6 +1447,52 @@ public class KeycardApplet extends Applet { secureChannel.respond(apdu, len, ISO7816.SW_NO_ERROR); } + /** + * Processes the GET DATA command. + * + * @param apdu the JCRE-owned APDU object. + */ + private void getData(APDU apdu) { + byte[] apduBuffer = apdu.getBuffer(); + + if (secureChannel.isOpen()) { + secureChannel.preprocessAPDU(apduBuffer); + } + + short outLen = Util.makeShort((byte) 0x00, data[0]); + Util.arrayCopyNonAtomic(data, (short) 1, apduBuffer, SecureChannel.SC_OUT_OFFSET, outLen); + + if (secureChannel.isOpen()) { + secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR); + } else { + apdu.setOutgoingAndSend(SecureChannel.SC_OUT_OFFSET, outLen); + } + } + + /** + * Processes the STORE DATA command. Requires an open secure channel and the PIN to be verified. + * + * @param apdu the JCRE-owned APDU object. + */ + private void storeData(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]); + + if (dataLen > MAX_DATA_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_DATA); + } + + JCSystem.beginTransaction(); + Util.arrayCopy(apduBuffer, ISO7816.OFFSET_LC, data, (short) 0, (short)(dataLen + 1)); + JCSystem.commitTransaction(); + } + /** * Utility method to verify if all the bytes in the buffer between off (included) and off + len (excluded) are digits. *