www.pudn.com > openemv-code-3-trunk.zip > SimpleEMVApplet.java, change:2011-11-30,size:10139b


/* 
 * Copyright (C) 2011  Digital Security group, Radboud University
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

package openemv;

import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.OwnerPIN;
import javacard.framework.Util;
import javacard.security.RandomData;
 

/* A very basic EMV applet supporting only SDA and plaintext offline PIN.
 * This applet does not offer personalisation support - everything is hard-coded.
 * 
 * The code is optimised for readability, and not for performance or memory use.
 * 
 * This class does the central processing of APDUs. Handling of all crypto-related
 * stuff is outsourced to EMVCrypro, handling of the static card data to EMVStaticData,
 * and handling of the EMV protocol and session state to EMVProtocolState.
 *
 * @author joeri (joeri@cs.ru.nl)
 * @author erikpoll (erikpoll@cs.ru.nl)
 *
 */
public class SimpleEMVApplet extends Applet implements EMVConstants {

	final OwnerPIN pin;
	final RandomData randomData;
	final EMVCrypto theCrypto;
	final EMVProtocolState protocolState;
	final EMVStaticData staticData;
	
	/* Transient byte array for constructing APDU responses. 
	 * We could have used the APDU buffer for this, but then we have to be careful not to 
	 * overwrite any info in the instruction APDU that we still need.
	 */
	private final byte[] response;
	

	private SimpleEMVApplet() {
		response = JCSystem.makeTransientByteArray((short)256, JCSystem.CLEAR_ON_DESELECT);

		pin = new OwnerPIN((byte) 3, (byte) 2);
		pin.update(new byte[] { (byte) 0x12, (byte) 0x34 }, (short) 0, (byte) 2);
		randomData = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM);
		
		protocolState = new EMVProtocolState(this);
		staticData = new EMVStaticData();
		theCrypto = new EMVCrypto(this);
	} 

	/**
	 * Installs an instance of the applet.
	 * 
	 * @see javacard.framework.Applet#install(byte[], byte, byte)
	 */
	public static void install(byte[] buffer, short offset, byte length) {
		(new SimpleEMVApplet()).register();
	}

	/**
	 * Processes incoming APDUs.
	 * 
	 * @see javacard.framework.Applet#process(javacard.framework.APDU)
	 */
	public void process(APDU apdu) {
		byte[] apduBuffer = apdu.getBuffer();
		byte cla = apduBuffer[OFFSET_CLA];
		byte ins = apduBuffer[OFFSET_INS];

		if (selectingApplet()) {
			// Reset all the flags recording the protocol state.
			// This should already have happened by the clearing of the
			// transient array used for them.
			protocolState.startNewSession();
			
			apdu.setOutgoing();
			apdu.setOutgoingLength(staticData.getFCILength());
			apdu.sendBytesLong(staticData.getFCI(), (short)0, staticData.getFCILength());
			return;
		}

		switch (ins) {

		case INS_EXTERNAL_AUTHENTICATE: // 0x82
			break;

		case INS_GET_CHALLENGE: // 0x84
			getChallenge(apdu, apduBuffer);
			break;

		case INS_INTERNAL_AUTHENTICATE:
			break;

		case INS_READ_RECORD: // 0xB2
			readRecord(apdu, apduBuffer);
			break;

		case INS_GET_PROCESSING_OPTIONS: // 0xA8
			getProcessingOptions(apdu, apduBuffer);
			break;

		case INS_GET_DATA: // 0xCA
			getData(apdu, apduBuffer);
			break;

		case INS_VERIFY: // 0x20
			verifyPIN(apdu, apduBuffer);
			break;

		case INS_GENERATE_AC: // 0xAE
			// get remaining data
			short len = (short) (apduBuffer[OFFSET_LC] & 0xFF);
			if (len != apdu.setIncomingAndReceive()) {
				ISOException.throwIt(SW_WRONG_LENGTH);
			}
			// check for request of CDA signature
			if ((apduBuffer[OFFSET_P1] & 0x10) == 0x10) {
				// CDA signature requested, which we don't support (yet)
				ISOException.throwIt(SW_WRONG_P1P2);
			}
			if (protocolState.getFirstACGenerated() == NONE) {
				generateFirstAC(apdu, apduBuffer);
			} else if (protocolState.getSecondACGenerated() == NONE) {
				generateSecondAC(apdu, apduBuffer);
			} else
				// trying to generate a third AC
				ISOException.throwIt(SW_INS_NOT_SUPPORTED);
			break;

		// below the (unsupported) post-issuance commands
		case INS_APPLICATION_BLOCK:
		case INS_APPLICATION_UNBLOCK:
		case INS_CARD_BLOCK:
		case INS_PIN_CHANGE_UNBLOCK:
		default:
			ISOException.throwIt(SW_INS_NOT_SUPPORTED);
			break;
		}
	}
 
	/*
	 * The VERIFY command checks the pin. This implementation only supports
	 * transaction_data PIN.
	 */	
	private void verifyPIN(APDU apdu, byte[] apduBuffer) {
		if (apduBuffer[OFFSET_P2] != (byte) (0x80)) {
			ISOException.throwIt(SW_WRONG_P1P2); // we only support transaction_data PIN
		}
		if (pin.getTriesRemaining() == 0) {
			ISOException.throwIt((short) 0x6983); // PIN blocked
			return;
		}

		/* EP: For the code below to be correct, digits in the PIN object need
		 * to be coded in the same way as in the APDU, ie. using 4 bit words.
		 */

		if (pin.check(apduBuffer, (short) (OFFSET_CDATA + 1), (byte) 2)) {
			protocolState.setCVMPerformed(PLAINTEXT_PIN);
			apdu.setOutgoingAndSend((short) 0, (short) 0); // return 9000
		} else {
			ISOException.throwIt((short) ((short) (0x63C0) + (short) pin
					.getTriesRemaining()));
		}
	}

	/*
	 * The GET CHALLENGE command generates an 8 byte unpredictable number.
	 */
	private void getChallenge(APDU apdu, byte[] apduBuffer) {
		randomData.generateData(apduBuffer, (short) 0, (short) 8);
		apdu.setOutgoingAndSend((short) 0, (short) 8);
	}

	/*
	 * The GET DATA command is used to retrieve a primitive data object not
	 * encapsulated in a record within the current application.
	 * 
	 * The usage of GET DATA in this implementation is limited to the ATC,
	 * the PIN Try Counter, and the last online ATC.
	 */
	private void getData(APDU apdu, byte[] apduBuffer) {
		/*
		 * buffer[OFFSET_P1..OFFSET_P2] should contains of the following tags
		 *  9F36 - ATC 
		 *  9F17 - PIN Try Counter 
		 *  9F13 - Last online ATC 
		 *  9F4F - Log Format
		 */
		if (apduBuffer[OFFSET_P1] == (byte) 0x9F) {
			apduBuffer[0] = (byte) 0x9F;
			apduBuffer[1] = apduBuffer[OFFSET_P2];
			switch (apduBuffer[OFFSET_P2]) {
			// The apduBuffer[OFFSET_P1,OFFSET_P2] already contains the right Tag,
			// so we can write the Length and Value to the next bytes in the apduBuffer
			// and then send this.
			case 0x36: // ATC
				apduBuffer[OFFSET_P2 + 1] = (byte) 0x02; // length 2 bytes
				Util.setShort(apduBuffer, (short) (OFFSET_P2 + 2), protocolState.getATC()); // value
				// send the 5 byte long TLV for ATC
				apdu.setOutgoingAndSend(OFFSET_P1, (short) 5); 
				break;

			case 0x17: // PIN Try Counter
				apduBuffer[OFFSET_P2 + 1] = (byte) 0x01; // length 1 byte
				apduBuffer[OFFSET_P2 + 2] = pin.getTriesRemaining(); // value
				// send the 4 byte TLV for PIN Try counter
				apdu.setOutgoingAndSend(OFFSET_P1, (short) 4); 
				break;

			case 0x13: // Last online ATC
				apduBuffer[OFFSET_P2 + 1] = (byte) 0x02; // length 2 bytes
				Util.setShort(apduBuffer, (short) (OFFSET_P2 + 2), protocolState.getLastOnlineATC()); // value
				// send the 5 byte long TLV for last online ATC
				apdu.setOutgoingAndSend(OFFSET_P1, (short) 5);  
				break;
			case 0x4F: // Log Format - not supported yet
			default:
				ISOException.throwIt(SW_WRONG_P1P2);
				break;
			}
		}
	}

	private void readRecord(APDU apdu, byte[] apduBuffer) {
		staticData.readRecord(apduBuffer, response);
		
		apdu.setOutgoing();
		apdu.setOutgoingLength((short)(response[1]+2));
		apdu.sendBytesLong(response, (short)0, (short)(response[1]+2));
	}

	private void getProcessingOptions(APDU apdu, byte[] apduBuffer) {
		// TODO Check APDU? PDOL is not checked at the moment
		
		// Return data using Format 1 
		response[0] = (byte) 0x80; // Tag
		response[1] = (byte) 0x06; // Length
		
		// 2 byte Application Interchange Profile 
		Util.setShort(response, (short)2, staticData.getAIP()); 
		
		// 4 byte Application File Locator
		Util.arrayCopyNonAtomic(staticData.getAFL(), (short)0, response, (short)4, (short)4);
		
		apdu.setOutgoing();
		apdu.setOutgoingLength((short)8);
		apdu.sendBytesLong(response, (short)0, (short)8);		
	}

	public void generateFirstAC(APDU apdu, byte[] apduBuffer) {
		// First 2 bits of P1 specify the type
		// These bits also have to be returned, as the Cryptogram Information Data (CID);
		// See Book 3, Annex C6.5.5.4 
		byte cid = (byte) (apduBuffer[OFFSET_P1] & 0xC0);
		if (cid == RFU_CODE || cid == AAC_CODE) {
			// not a request for TC or ARQC
			ISOException.throwIt(SW_WRONG_P1P2);
		}
		
		theCrypto.generateFirstACReponse(cid, apduBuffer, staticData.getCDOL1DataLength(), null, (short)0, response, (short)0);
		protocolState.setFirstACGenerated(cid);
		
		apdu.setOutgoing();
		apdu.setOutgoingLength((short)(response[1]+2));
		apdu.sendBytesLong(response, (short)0, (short)(response[1]+2));		
	}
 
	public void generateSecondAC(APDU apdu, byte[] apduBuffer) {
		// First 2 bits of P1 specify the type
		// These bits also have to be returned, as the Cryptogram Information Data (CID);
		// See Book 3, Sect 6.5.5.4 of the Common Core Definitions.
		byte cid = (byte) (apduBuffer[OFFSET_P1] & 0xC0);
		if (cid == RFU_CODE || cid == ARQC_CODE) {
			// not a request for TC or AAC
			ISOException.throwIt(SW_WRONG_P1P2);
		}	

		theCrypto.generateSecondACReponse(cid, apduBuffer, staticData.getCDOL2DataLength(), null, (short)0, response, (short)0);
		protocolState.setSecondACGenerated(cid);
		
		apdu.setOutgoing();
		apdu.setOutgoingLength((short)(response[1]+2));
		apdu.sendBytesLong(response, (short)0, (short)(response[1]+2));		
	}

}