/*
*******************************************************************************
* BTChip Bitcoin Hardware Wallet Java Card implementation
* (c) 2013 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************
*/
package com.btchip.applet.poc;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.Util;
/**
* Applet simulating an NFC Forum Type 4 tag for the second factor validation
* @author BTChip
*
*/
public class BTChipNFCForumApplet extends Applet {
public BTChipNFCForumApplet() {
scratch = JCSystem.makeTransientByteArray((short)1, JCSystem.CLEAR_ON_DESELECT);
FILE_DATA = new byte[500];
// Header initialization
short offset = 0;
offset += (short)2;
FILE_DATA[offset++] = (byte)0xC1; // beginning of well known record, short record bit not set
FILE_DATA[offset++] = (byte)0x01;
FILE_DATA[offset++] = (byte)0x00; // start of 4 bytes length
FILE_DATA[offset++] = (byte)0x00;
offset += (short)2;
Util.arrayCopyNonAtomic(LANG, (short)0, FILE_DATA, offset, (short)LANG.length);
BTChipPocApplet.writeIdleText();
}
public static void writeHeader(short textSize) {
short offset = (short)0;
Util.setShort(FILE_DATA, offset, (short)(textSize + 1 + 5 + 4 + 2 + 1)); // prefix with size of full record
offset += (short)(2 + 4);
Util.setShort(FILE_DATA, offset, (short)(textSize + 1 + 5)); // size of text record payload
}
@Override
public boolean select() { // only grant access on the contactless interface
return (BTChipPocApplet.isContactless());
}
@Override
public void process(APDU apdu) throws ISOException {
if (selectingApplet()) {
return;
}
byte[] buffer = apdu.getBuffer();
if (buffer[ISO7816.OFFSET_CLA] != NFCFORUM_CLA) {
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
switch(buffer[ISO7816.OFFSET_INS]) {
case INS_SELECT: {
apdu.setIncomingAndReceive();
short selectedFile = Util.getShort(buffer, ISO7816.OFFSET_CDATA);
switch(selectedFile) {
case EF_CONTAINER:
scratch[OFFSET_SELECTED_FILE] = SELECTED_FILE_CONTAINER;
break;
case EF_NDEF:
scratch[OFFSET_SELECTED_FILE] = SELECTED_FILE_NDEF;
break;
default:
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
}
}
break;
case INS_READ: {
short offset = Util.makeShort(buffer[ISO7816.OFFSET_P1], buffer[ISO7816.OFFSET_P2]);
if (scratch[OFFSET_SELECTED_FILE] == SELECTED_FILE_NONE) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
byte[] fileData = null;
switch(scratch[OFFSET_SELECTED_FILE]) {
case SELECTED_FILE_CONTAINER:
fileData = CONTAINER_DATA;
break;
case SELECTED_FILE_NDEF:
fileData = FILE_DATA;
break;
}
if (offset >= (short)fileData.length) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
short sizeRead = (short)(buffer[ISO7816.OFFSET_LC] & 0xff);
short blockLength = (((short)(offset + sizeRead) > (short)fileData.length) ? (short)(fileData.length - offset) : sizeRead);
Util.arrayCopyNonAtomic(fileData, offset, buffer, (short)0, blockLength);
apdu.setOutgoingAndSend((short)0, blockLength);
}
break;
}
}
public static void install (byte bArray[], short bOffset, byte bLength) throws ISOException {
new BTChipNFCForumApplet().register(bArray, (short)(bOffset + 1), bArray[bOffset]);
}
public static final byte OFFSET_TEXT = (byte)15;
private static final byte NFCFORUM_CLA = (byte)0x00;
private static final byte INS_SELECT = (byte)0xA4;
private static final byte INS_READ = (byte)0xB0;
private static final short EF_CONTAINER = (short)0xE103;
private static final short EF_NDEF = (short)0xE104;
private static final byte SELECTED_FILE_NONE = (byte)0x00;
private static final byte SELECTED_FILE_CONTAINER = (byte)0x01;
private static final byte SELECTED_FILE_NDEF = (byte)0x02;
private static final byte OFFSET_SELECTED_FILE = (byte)0x00;
private static final byte CONTAINER_DATA[] = {
(byte)0x00, (byte)0x0F, // length
(byte)0x20, // mapping version 2.0
(byte)0x00, (byte)0xFF, // max R-APDU data size
(byte)0x00, (byte)0xFF, // max C-APDU data size
(byte)0x04, (byte)0x06, // NDEF File Control TL
(byte)0xE1, (byte)0x04, // EF_NDEF
(byte)0x01, (byte)0xF4, // Max NDEF size (update with FILE_DATA size)
(byte)0x00, // Read always
(byte)0xFF // Write never
};
private static final byte LANG[] = {
(byte)'T', (byte)0x05, (byte)'e', (byte)'n', (byte)'-', (byte)'U', (byte)'S' // en-US text record
};
public static byte FILE_DATA[];
private static byte scratch[];
}