/* * DrivingLicenseApplet - A reference implementation of the ISO18013 standards. * Based on the passport applet code developed by the JMRTD team, see * http://jmrtd.org * * Copyright (C) 2006 SoS group, Radboud University * Copyright (C) 2009 Wojciech Mostowski, 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 org.isodl.applet; import javacard.framework.APDU; import javacard.framework.Applet; import javacard.framework.CardRuntimeException; import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.JCSystem; import javacard.framework.Util; import javacard.security.KeyBuilder; import javacard.security.RandomData; import javacard.security.Signature; import javacardx.crypto.Cipher; /** * License Applet - the implementation of the ISO18013 standard. The AID of the * applet should be A0000002480200. * * @author ceesb (ceeesb@gmail.com) * @author martijno (martijn.oostdijk@gmail.com) * @author Wojciech Mostowski <woj@cs.ru.nl> * */ public class LicenseApplet extends Applet implements ISO7816 { static byte volatileState[]; static byte persistentState; /* values for volatile state */ static final byte CHALLENGED = 1; static final byte MUTUAL_AUTHENTICATED = 2; static final byte FILE_SELECTED = 4; static final byte ACTIVE_AUTHENTICATED = 8; static final byte CHIP_AUTHENTICATED = 0x10; static final byte TERMINAL_AUTHENTICATED = 0x20; /* values for persistent state */ static final byte HAS_MUTUALAUTHENTICATION_KEYS = 1; static final byte HAS_EXPONENT = 2; static final byte LOCKED = 4; static final byte HAS_MODULUS = 8; static final byte HAS_EC_KEY = 0x10; static final byte HAS_CVCERTIFICATE = 0x20; static final byte HAS_SICID = 0x40; static final byte CHAIN_CLA = 0x10; /* for authentication */ static final byte INS_EXTERNAL_AUTHENTICATE = (byte) 0x82; static final byte INS_GET_CHALLENGE = (byte) 0x84; static final byte CLA_PROTECTED_APDU = 0x0c; static final byte CLA_PLAIN_APDU = 0x00; static final byte INS_INTERNAL_AUTHENTICATE = (byte) 0x88; /* for EAP */ static final byte INS_PSO = (byte) 0x2A; static final byte INS_MSE = (byte) 0x22; static final byte P2_VERIFYCERT = (byte) 0xBE; static final byte P1_SETFORCOMPUTATION = (byte) 0x41; static final byte P1_SETFORVERIFICATION = (byte) 0x81; static final byte P2_KAT = (byte) 0xA6; static final byte P2_DST = (byte) 0xB6; static final byte P2_AT = (byte) 0xA4; /* for reading */ static final byte INS_SELECT_FILE = (byte) 0xA4; static final byte INS_READ_BINARY = (byte) 0xB0; /* for writing */ static final byte INS_UPDATE_BINARY = (byte) 0xd6; static final byte INS_CREATE_FILE = (byte) 0xe0; static final byte INS_PUT_DATA = (byte) 0xda; static final short KEY_LENGTH = 16; static final short KEYMATERIAL_LENGTH = 16; static final short RND_LENGTH = 32; static final short MAC_LENGTH = 8; private static final byte PRIVMODULUS_TAG = 0x60; private static final byte PRIVEXPONENT_TAG = 0x61; private static final byte KEYSEED_TAG = 0x62; private static final byte ECPRIVATEKEY_TAG = 0x63; private static final byte CVCERTIFICATE_TAG = 0x64; private static final byte SICID_TAG = 0x65; /* status words */ private static final short SW_OK = (short) 0x9000; private static final short SW_REFERENCE_DATA_NOT_FOUND = (short) 0x6A88; static final short SW_INTERNAL_ERROR = (short) 0x6d66; static final short SW_SM_DO_MISSING = (short) 0x6987; static final short SW_SM_DO_INCORRECT = (short) 0x6988; private static final byte MASK_SFI = (byte)0x80; private byte[] rnd; private short rndLength; private byte[] ssc; private byte[] sicId; private FileSystem fileSystem; private RandomData randomData; static CVCertificate certificate; private short selectedFile; private LicenseCrypto crypto; private byte[] lastINS; private short[] chainingOffset; private byte[] chainingTmp; // This is as long we suspect a card verifiable certifcate could be private static final short CHAINING_BUFFER_LENGTH = 400; KeyStore keyStore; /** * Creates a new driving license applet. */ public LicenseApplet() { fileSystem = new FileSystem(); randomData = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM); certificate = new CVCertificate(); keyStore = new KeyStore(); crypto = new LicenseCrypto(keyStore); rnd = JCSystem.makeTransientByteArray(RND_LENGTH, JCSystem.CLEAR_ON_RESET); ssc = JCSystem .makeTransientByteArray((byte) 8, JCSystem.CLEAR_ON_RESET); volatileState = JCSystem.makeTransientByteArray((byte) 1, JCSystem.CLEAR_ON_RESET); lastINS = JCSystem.makeTransientByteArray((short) 1, JCSystem.CLEAR_ON_DESELECT); chainingOffset = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_DESELECT); chainingTmp = JCSystem.makeTransientByteArray(CHAINING_BUFFER_LENGTH, JCSystem.CLEAR_ON_DESELECT); } /** * Installs an instance of the applet. * * @param buffer * @param offset * @param length * @see javacard.framework.Applet#install(byte[], byte, byte) */ public static void install(byte[] buffer, short offset, byte length) { (new LicenseApplet()).register(); } private static boolean needLe(byte ins) { if(ins == INS_READ_BINARY) { return true; } return false; } /** * Processes incoming APDUs. * * @param apdu * @see javacard.framework.Applet#process(javacard.framework.APDU) */ public void process(APDU apdu) { byte[] buffer = apdu.getBuffer(); byte cla = buffer[OFFSET_CLA]; byte ins = buffer[OFFSET_INS]; short sw1sw2 = SW_OK; if((byte)(cla & ~(CLA_PROTECTED_APDU | (ins == INS_PSO ? CHAIN_CLA : 0x00))) != (byte)0){ ISOException.throwIt(SW_CLA_NOT_SUPPORTED); } boolean protectedApdu = ((byte) (cla & CLA_PROTECTED_APDU) == CLA_PROTECTED_APDU); short responseLength = 0; short le = 0; if (lastINS[0] != ins) { chainingOffset[0] = 0; } lastINS[0] = ins; /* Ignore APDU that selects this applet, but checks that we are in * contactless mode (currently inactive) */ if (selectingApplet()) { /* byte protocol = (byte)(APDU.getProtocol() & APDU.PROTOCOL_MEDIA_MASK); if(isLocked() && protocol != APDU.PROTOCOL_MEDIA_CONTACTLESS_TYPE_A && protocol != APDU.PROTOCOL_MEDIA_CONTACTLESS_TYPE_B) { // We are in contact mode, refuse operation: ISOException.throwIt(SW_REFERENCE_DATA_NOT_FOUND); } */ return; } if( protectedApdu ) { if( hasMutuallyAuthenticated() ) { try { le = crypto.unwrapCommandAPDU(ssc, apdu); } catch (CardRuntimeException e) { sw1sw2 = e.getReason(); protectedApdu = false; setNoChallenged(); volatileState[0] &= ~MUTUAL_AUTHENTICATED; } }else{ ISOException.throwIt(ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED); } }else{ if( hasMutuallyAuthenticated() ) { setNoChallenged(); volatileState[0] &= ~MUTUAL_AUTHENTICATED; ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } } if (sw1sw2 == SW_OK) { try { if(!protectedApdu && needLe(ins)) { le = apdu.setOutgoing(); } responseLength = processAPDU(apdu, cla, ins, protectedApdu, le); } catch (CardRuntimeException e) { sw1sw2 = e.getReason(); } } if (protectedApdu && hasMutuallyAuthenticated()) { responseLength = crypto.wrapResponseAPDU(ssc, apdu, crypto .getApduBufferOffset(responseLength), responseLength, sw1sw2); } if (responseLength > 0) { if (apdu.getCurrentState() != APDU.STATE_OUTGOING) apdu.setOutgoing(); if (apdu.getCurrentState() != APDU.STATE_OUTGOING_LENGTH_KNOWN) apdu.setOutgoingLength(responseLength); apdu.sendBytes((short) 0, responseLength); } if (sw1sw2 != SW_OK) { ISOException.throwIt(sw1sw2); } } private short processAPDU(APDU apdu, byte cla, byte ins, boolean protectedApdu, short le) { short responseLength = 0; switch (ins) { case INS_GET_CHALLENGE: responseLength = processGetChallenge(apdu, protectedApdu, le); break; case INS_EXTERNAL_AUTHENTICATE: responseLength = processMutualAuthenticate(apdu, protectedApdu); break; case INS_INTERNAL_AUTHENTICATE: responseLength = processInternalAuthenticate(apdu, protectedApdu); break; case INS_PSO: if (!protectedApdu) { ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); } responseLength = processPSO(apdu); break; case INS_MSE: responseLength = processMSE(apdu, protectedApdu); break; case INS_SELECT_FILE: processSelectFile(apdu); break; case INS_READ_BINARY: responseLength = processReadBinary(apdu, le, protectedApdu); break; case INS_UPDATE_BINARY: processUpdateBinary(apdu); break; case INS_CREATE_FILE: processCreateFile(apdu); break; case INS_PUT_DATA: processPutData(apdu); break; default: ISOException.throwIt(SW_INS_NOT_SUPPORTED); break; } return responseLength; } private short processPSO(APDU apdu) { byte[] buffer = apdu.getBuffer(); if(!hasEAPCertificate() || !hasSicId()) { ISOException.throwIt(SW_INS_NOT_SUPPORTED); } if (!hasChipAuthenticated() || hasTerminalAuthenticated()) { ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); } if (buffer[OFFSET_P1] != (byte) 0x00 && buffer[OFFSET_P2] != P2_VERIFYCERT) { ISOException.throwIt(SW_INCORRECT_P1P2); } if (!certificate.subjectIdSelected()) { certificate.selectRootHolderReference(); } short lc = (short) (buffer[OFFSET_LC] & 0xFF); if (chainingOffset[0] > (short) (CHAINING_BUFFER_LENGTH - lc)) { ISOException.throwIt(SW_WRONG_LENGTH); } chainingOffset[0] = Util.arrayCopyNonAtomic(buffer, OFFSET_CDATA, chainingTmp, chainingOffset[0], lc); if (((byte) (buffer[OFFSET_CLA] & CHAIN_CLA) == CHAIN_CLA)) { return (short) 0; } short chainingTmpLength = chainingOffset[0]; chainingOffset[0] = (short) 0; certificate.parseCertificate(chainingTmp, (short) 0, chainingTmpLength, false); if (!certificate.verify()) { ISOException.throwIt((short) 0x6300); } return (short) 0; } private short processMSE(APDU apdu, boolean protectedApdu) { byte[] buffer = apdu.getBuffer(); byte p1 = (byte) (buffer[OFFSET_P1] & 0xff); byte p2 = (byte) (buffer[OFFSET_P2] & 0xff); short lc = (short) (buffer[OFFSET_LC] & 0xff); short buffer_p = OFFSET_CDATA; if ((!hasEAPKey() && p1 == P1_SETFORCOMPUTATION) || ((!hasEAPCertificate() || !hasSicId()) && p1 == P1_SETFORVERIFICATION)) { ISOException.throwIt(SW_INS_NOT_SUPPORTED); } if ((hasMutuallyAuthenticated() && !protectedApdu) || hasTerminalAuthenticated()) { ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); } if (p1 == P1_SETFORCOMPUTATION && p2 == P2_KAT) { short lastOffset = (short) (lc + OFFSET_CDATA); if (buffer_p > (short) (lastOffset - 2)) { ISOException.throwIt(SW_WRONG_LENGTH); } if (buffer[buffer_p++] != (byte) 0x91) { ISOException.throwIt(SW_WRONG_DATA); } short pubKeyLen = (short) (buffer[buffer_p++] & 0xFF); short pubKeyOffset = buffer_p; if (pubKeyOffset > (short) (lastOffset - pubKeyLen)) { ISOException.throwIt(SW_WRONG_LENGTH); } buffer_p += pubKeyLen; short keyIdOffset = 0; short keyIdLength = 0; if (buffer_p != lastOffset) { if (buffer_p > (short) (lastOffset - 2)) { ISOException.throwIt(SW_WRONG_LENGTH); } if (buffer[buffer_p++] != (byte) 0x84) { ISOException.throwIt(SW_WRONG_DATA); } keyIdLength = (short) (buffer[buffer_p++] & 0xFF); keyIdOffset = buffer_p; if (keyIdOffset != (short) (lastOffset - keyIdLength)) { ISOException.throwIt(SW_WRONG_LENGTH); } // ignore the key id, we don't use it } if (!crypto.authenticateChip(buffer, pubKeyOffset, pubKeyLen)) { ISOException.throwIt(SW_WRONG_DATA); } if (!protectedApdu) { volatileState[0] |= MUTUAL_AUTHENTICATED; } volatileState[0] |= CHIP_AUTHENTICATED; return 0; } else if (p1 == P1_SETFORVERIFICATION && (p2 == P2_DST || p2 == P2_AT)) { if (!hasChipAuthenticated() || hasTerminalAuthenticated()) { ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); } short lastOffset = (short) (lc + OFFSET_CDATA); if (buffer_p > (short) (lastOffset - 2)) { ISOException.throwIt(SW_WRONG_LENGTH); } if (buffer[buffer_p++] != (byte) 0x93) { ISOException.throwIt(SW_WRONG_DATA); } short subIdLen = (short) (buffer[buffer_p++] & 0xFF); if (buffer_p != (short) (lastOffset - subIdLen)) { ISOException.throwIt(SW_WRONG_LENGTH); } if (!certificate.selectSubjectId(buffer, buffer_p, subIdLen)) { ISOException.throwIt(SW_REFERENCE_DATA_NOT_FOUND); } } else { ISOException.throwIt(SW_INCORRECT_P1P2); } return 0; } private void processPutData(APDU apdu) { if (isLocked()) { ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); } byte[] buffer = apdu.getBuffer(); short buffer_p = (short) (OFFSET_CDATA & 0xff); short lc = (short) (buffer[OFFSET_LC] & 0xff); short p1 = (short) (buffer[OFFSET_P1] & 0xff); short p2 = (short) (buffer[OFFSET_P2] & 0xff); // sanity check if (buffer.length < (short) (buffer_p + lc)) { ISOException.throwIt(SW_INTERNAL_ERROR); } if (p1 == 0xde && p2 == 0xad) { persistentState |= LOCKED; return; } if (apdu.getCurrentState() == APDU.STATE_INITIAL) { apdu.setIncomingAndReceive(); } if (apdu.getCurrentState() < APDU.STATE_FULL_INCOMING) { // need all data in one APDU. ISOException.throwIt(SW_INTERNAL_ERROR); } if (p1 == 0 && p2 == PRIVMODULUS_TAG) { buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag == // PRIVMODULUS_TAG buffer_p = BERTLVScanner.readLength(buffer, buffer_p); // length == // 00 buffer_p = BERTLVScanner.skipValue(); buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag == 04 short modOffset = BERTLVScanner.readLength(buffer, buffer_p); short modLength = BERTLVScanner.valueLength; if (buffer[modOffset] == 0) { modLength--; modOffset++; } keyStore.rsaPrivateKey.setModulus(buffer, modOffset, modLength); persistentState |= HAS_MODULUS; } else if (p1 == 0 && p2 == PRIVEXPONENT_TAG) { buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag == // PRIVEXP_TAG buffer_p = BERTLVScanner.readLength(buffer, buffer_p); // length == // 00 buffer_p = BERTLVScanner.skipValue(); buffer_p = BERTLVScanner.readTag(buffer, buffer_p); // tag == 04 short expOffset = BERTLVScanner.readLength(buffer, buffer_p); short expLength = BERTLVScanner.valueLength; // leading zero if (buffer[expOffset] == 0) { expLength--; expOffset++; } keyStore.rsaPrivateKey.setExponent(buffer, expOffset, expLength); persistentState |= HAS_EXPONENT; } else if (p1 == 0 && p2 == KEYSEED_TAG) { short macKey_p = (short) (buffer_p + lc); short encKey_p = (short) (buffer_p + lc + KEY_LENGTH); crypto.deriveKey(buffer, buffer_p, lc, LicenseCrypto.MAC_MODE, macKey_p); crypto.deriveKey(buffer, buffer_p, lc, LicenseCrypto.ENC_MODE, encKey_p); keyStore.setMutualAuthenticationKeys(buffer, macKey_p, buffer, encKey_p); persistentState |= HAS_MUTUALAUTHENTICATION_KEYS; } else if (p1 == 0 && p2 == ECPRIVATEKEY_TAG) { short finish = (short) (buffer_p + lc); while (buffer_p < finish) { buffer_p = BERTLVScanner.readTag(buffer, buffer_p); buffer_p = BERTLVScanner.readLength(buffer, buffer_p); short len = BERTLVScanner.valueLength; switch (BERTLVScanner.tag) { case (short) 0x81: if(KeyStore.CA_EC_KEYTYPE_PUBLIC == KeyBuilder.TYPE_EC_F2M_PUBLIC) { if (len == (short) 6) { short e1 = Util.getShort(buffer, buffer_p); short e2 = Util .getShort(buffer, (short) (buffer_p + 2)); short e3 = Util .getShort(buffer, (short) (buffer_p + 4)); keyStore.ecPrivateKey.setFieldF2M(e1, e2, e3); keyStore.ecPublicKey.setFieldF2M(e1, e2, e3); } else { keyStore.ecPrivateKey.setFieldF2M(Util.getShort(buffer, buffer_p)); keyStore.ecPublicKey.setFieldF2M(Util.getShort(buffer, buffer_p)); } } else { if(KeyStore.CA_EC_KEYTYPE_PUBLIC == KeyBuilder.TYPE_EC_FP_PUBLIC) { if(len > (short)(KeyStore.CA_EC_KEYLENGTH / 8)) { buffer_p++; len--; } } keyStore.ecPrivateKey.setFieldFP(buffer, buffer_p, len); keyStore.ecPublicKey.setFieldFP(buffer, buffer_p, len); } break; case (short) 0x82: if(KeyStore.CA_EC_KEYTYPE_PUBLIC == KeyBuilder.TYPE_EC_FP_PUBLIC) { if(len > (short)(KeyStore.CA_EC_KEYLENGTH / 8)) { buffer_p++; len--; } } keyStore.ecPrivateKey.setA(buffer, buffer_p, len); keyStore.ecPublicKey.setA(buffer, buffer_p, len); break; case (short) 0x83: if(KeyStore.CA_EC_KEYTYPE_PUBLIC == KeyBuilder.TYPE_EC_FP_PUBLIC) { if(len > (short)(KeyStore.CA_EC_KEYLENGTH / 8)) { buffer_p++; len--; } } keyStore.ecPrivateKey.setB(buffer, buffer_p, len); keyStore.ecPublicKey.setB(buffer, buffer_p, len); break; case (short) 0x84: keyStore.ecPrivateKey.setG(buffer, buffer_p, len); keyStore.ecPublicKey.setG(buffer, buffer_p, len); break; case (short) 0x85: if(KeyStore.CA_EC_KEYTYPE_PUBLIC == KeyBuilder.TYPE_EC_FP_PUBLIC) { if(len > (short)(KeyStore.CA_EC_KEYLENGTH / 8)) { buffer_p++; len--; } } keyStore.ecPrivateKey.setR(buffer, buffer_p, len); keyStore.ecPublicKey.setR(buffer, buffer_p, len); break; case (short) 0x86: if (KeyStore.CA_EC_KEYTYPE_PUBLIC == KeyBuilder.TYPE_EC_F2M_PUBLIC && len == (short) (KeyStore.CA_EC_KEYLENGTH / 8)) { buffer_p--; len++; buffer[buffer_p] = 0x00; } if(KeyStore.CA_EC_KEYTYPE_PUBLIC == KeyBuilder.TYPE_EC_FP_PUBLIC) { if(len > (short)(KeyStore.CA_EC_KEYLENGTH / 8)) { buffer_p++; len--; } } keyStore.ecPrivateKey.setS(buffer, buffer_p, len); break; case (short) 0x87: // This is the k, ignore it // short k = Util.getShort(buffer, buffer_p); break; default: ISOException.throwIt(SW_WRONG_DATA); break; } buffer_p = BERTLVScanner.skipValue(); } if (keyStore.ecPrivateKey.isInitialized()) { persistentState |= HAS_EC_KEY; } else { ISOException.throwIt(SW_WRONG_DATA); } } else if (p1 == 0 && p2 == CVCERTIFICATE_TAG) { if ((byte) (persistentState & HAS_CVCERTIFICATE) == HAS_CVCERTIFICATE) { // We already have the certificate initialized ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); } certificate.parseCertificate(buffer, buffer_p, lc, true); certificate.setRootCertificate(buffer); persistentState |= HAS_CVCERTIFICATE; } else if (p1 == 0 && p2 == SICID_TAG) { if (sicId != null) { // We already have the certificate initialized ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); } sicId = new byte[lc]; Util.arrayCopyNonAtomic(buffer, buffer_p, sicId, (short) 0, lc); persistentState |= HAS_SICID; } else { ISOException.throwIt(SW_INCORRECT_P1P2); } } /** * Processes INTERNAL_AUTHENTICATE apdus. Receives a random and signs it. * * @param apdu * @param protectedApdu * @return */ private short processInternalAuthenticate(APDU apdu, boolean protectedApdu) { if (!hasInternalAuthenticationKeys() || (hasMutualAuthenticationKeys() && (!protectedApdu || !hasMutuallyAuthenticated()))) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); } short buffer_p = (short) (OFFSET_CDATA & 0xff); short hdr_offset = 0; if (protectedApdu) { hdr_offset = crypto.getApduBufferOffset((short) 128); } short hdr_len = 1; short m1_len = 106; // whatever short m1_offset = (short) (hdr_offset + hdr_len); short m2_len = 8; short m2_offset = (short) (m1_offset + m1_len); // we will write the hash over m2 short m1m2hash_offset = (short) (m1_offset + m1_len); short m1m2hash_len = 20; short trailer_offset = (short) (m1m2hash_offset + m1m2hash_len); short trailer_len = 1; byte[] buffer = apdu.getBuffer(); short bytesLeft = (short) (buffer[OFFSET_LC] & 0x00FF); if (bytesLeft != m2_len) ISOException.throwIt(SW_WRONG_LENGTH); // put m2 in place Util.arrayCopyNonAtomic(buffer, buffer_p, buffer, m2_offset, m2_len); // write some random data of m1_len // randomData.generateData(buffer, m1_offset, m1_length); for (short i = m1_offset; i < (short) (m1_offset + m1_len); i++) { buffer[i] = 0; } crypto.shaDigest.doFinal(buffer, m1_offset, (short) (m1_len + m2_len), buffer, m1m2hash_offset); crypto.shaDigest.reset(); // write trailer buffer[trailer_offset] = (byte) 0xbc; // write header buffer[hdr_offset] = (byte) 0x6a; // encrypt the whole buffer with our AA private key short plaintext_len = (short) (hdr_len + m1_len + m1m2hash_len + trailer_len); // sanity check if (plaintext_len != 128) { ISOException.throwIt(SW_INTERNAL_ERROR); } crypto.rsaCiph.init(keyStore.rsaPrivateKey, Cipher.MODE_ENCRYPT); short ciphertext_len = crypto.rsaCiph.doFinal(buffer, hdr_offset, plaintext_len, buffer, hdr_offset); // sanity check if (ciphertext_len != 128) { ISOException.throwIt(SW_INTERNAL_ERROR); } return ciphertext_len; } /** * Processes incoming GET_CHALLENGE APDUs. * * Generates random 8 bytes, sends back result and stores result in rnd. * * @param apdu * is used for sending (8 bytes) only */ private short processGetChallenge(APDU apdu, boolean protectedApdu, short le) { if (protectedApdu) { if(!hasEAPCertificate() || !hasSicId()) { ISOException.throwIt(SW_INS_NOT_SUPPORTED); } if (!hasChipAuthenticated() || !certificate.getPublicKey().isInitialized() || hasTerminalAuthenticated()) { ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); } } else { if(!hasMutualAuthenticationKeys()) { ISOException.throwIt(SW_INS_NOT_SUPPORTED); } if (!hasMutualAuthenticationKeys() || hasMutuallyAuthenticated()) { ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); } } byte[] buffer = apdu.getBuffer(); if (!protectedApdu) { le = apdu.setOutgoing(); } // For the BAP challenge the length should be 8, for the EAP challenge // we guess other lenghts are fine too? if (le > RND_LENGTH || (!protectedApdu && le != 8)) { ISOException.throwIt(SW_WRONG_LENGTH); } randomData.generateData(rnd, (short) 0, le); rndLength = le; short bufferOffset = protectedApdu ? crypto.getApduBufferOffset(le) : (short) 0; Util.arrayCopyNonAtomic(rnd, (short) 0, buffer, bufferOffset, le); volatileState[0] |= CHALLENGED; return le; } /** * Perform mutual authentication with terminal. * * @param apdu * the APDU * @return length of return APDU */ private short processMutualAuthenticate(APDU apdu, boolean protectedApdu) { if (protectedApdu) { if(!hasEAPCertificate() || !hasSicId()) { ISOException.throwIt(SW_INS_NOT_SUPPORTED); } if (!hasChipAuthenticated() || !isChallenged() || !certificate.getPublicKey().isInitialized() || hasTerminalAuthenticated()) { ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); } byte[] buffer = apdu.getBuffer(); short buffer_p = OFFSET_CDATA; short lc = (short) (buffer[OFFSET_LC] & 0xFF); setNoChallenged(); if (!crypto.eapVerifySignature(certificate.getPublicKey(), rnd, rndLength, sicId, buffer, buffer_p, lc)) { certificate.clear(); ISOException.throwIt((short) 0x6300); } Util.arrayCopyNonAtomic(certificate.getAuthorization(), (short) 1, fileSystem.currentAuthorization, (short) 0, (short) 3); certificate.clear(); volatileState[0] |= TERMINAL_AUTHENTICATED; return 0; } else { if(!hasMutualAuthenticationKeys()) { ISOException.throwIt(SW_INS_NOT_SUPPORTED); } if (!isChallenged() || hasMutuallyAuthenticated()) { ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); } byte[] buffer = apdu.getBuffer(); short bytesLeft = (short) (buffer[OFFSET_LC] & 0x00FF); short e_ifd_length = (short) ((short) (rndLength + rndLength) + KEYMATERIAL_LENGTH); // incoming message is e_ifd || m_ifd // where e_ifd == E_KENC(rnd_ifd || rnd_icc || k_ifd) if (bytesLeft != (short) (e_ifd_length + MAC_LENGTH)) ISOException.throwIt(SW_WRONG_LENGTH); short e_ifd_p = OFFSET_CDATA; short m_ifd_p = (short) (e_ifd_p + e_ifd_length); if (apdu.getCurrentState() == APDU.STATE_INITIAL) { apdu.setIncomingAndReceive(); } if (apdu.getCurrentState() < APDU.STATE_FULL_INCOMING) { // need all data in one APDU. ISOException.throwIt(SW_INTERNAL_ERROR); } // buffer[OFFSET_CDATA ... +40] consists of e_ifd || m_ifd // verify checksum m_ifd of cryptogram e_ifd crypto.initMac(Signature.MODE_VERIFY); if (!crypto.verifyMacFinal(buffer, e_ifd_p, e_ifd_length, buffer, m_ifd_p)) ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); // decrypt e_ifd into buffer[0] where buffer = rnd.ifd || rnd.icc || // k.ifd crypto.decryptInit(); short plaintext_len = crypto.decryptFinal(buffer, e_ifd_p, e_ifd_length, buffer, (short) 0); if (plaintext_len != e_ifd_length) // sanity check ISOException.throwIt(SW_INTERNAL_ERROR); short rnd_ifd_p = 0; short rnd_icc_p = rndLength; short k_ifd_p = (short) (rnd_icc_p + rndLength); /* * we use apdu buffer for writing intermediate data in buffer with * following pointers */ short k_icc_p = (short) (k_ifd_p + KEYMATERIAL_LENGTH); short keySeed_p = (short) (k_icc_p + KEYMATERIAL_LENGTH); short keys_p = (short) (keySeed_p + KEYMATERIAL_LENGTH); // verify that rnd.icc equals value generated in getChallenge if (Util.arrayCompare(buffer, rnd_icc_p, rnd, (short) 0, rndLength) != 0) ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); // generate keying material k.icc randomData.generateData(buffer, k_icc_p, KEYMATERIAL_LENGTH); // calculate keySeed for session keys by xorring k_ifd and k_icc LicenseUtil.xor(buffer, k_ifd_p, buffer, k_icc_p, buffer, keySeed_p, KEYMATERIAL_LENGTH); // calculate session keys crypto.deriveKey(buffer, keySeed_p, KEYMATERIAL_LENGTH, LicenseCrypto.MAC_MODE, keys_p); short macKey_p = keys_p; keys_p += KEY_LENGTH; crypto.deriveKey(buffer, keySeed_p, KEYMATERIAL_LENGTH, LicenseCrypto.ENC_MODE, keys_p); short encKey_p = keys_p; keys_p += KEY_LENGTH; keyStore.setSecureMessagingKeys(buffer, macKey_p, buffer, encKey_p); // compute ssc LicenseCrypto.computeSSC(buffer, rnd_icc_p, buffer, rnd_ifd_p, ssc); // create response in buffer where response = rnd.icc || rnd.ifd || // k.icc LicenseUtil.swap(buffer, rnd_icc_p, rnd_ifd_p, rndLength); Util.arrayCopyNonAtomic(buffer, k_icc_p, buffer, (short) (2 * rndLength), KEYMATERIAL_LENGTH); // make buffer encrypted using k_enc crypto.encryptInit(); short ciphertext_len = crypto.encryptFinal(buffer, (short) 0, (short) (2 * rndLength + KEYMATERIAL_LENGTH), buffer, (short) 0); // create m_icc which is a checksum of response crypto.initMac(Signature.MODE_SIGN); crypto.createMacFinal(buffer, (short) 0, ciphertext_len, buffer, ciphertext_len); setNoChallenged(); volatileState[0] |= MUTUAL_AUTHENTICATED; return (short) (ciphertext_len + MAC_LENGTH); } } /** * Processes incoming SELECT_FILE APDUs. * * @param apdu * where the first 2 data bytes encode the file to select. */ private void processSelectFile(APDU apdu) { byte[] buffer = apdu.getBuffer(); byte p1 = buffer[OFFSET_P1]; byte p2 = buffer[OFFSET_P2]; if(p1 != (byte)0x02 || p2 != (byte)0x0C) { ISOException.throwIt(SW_INCORRECT_P1P2); } short lc = (short) (buffer[OFFSET_LC] & 0x00FF); if (lc != 2) ISOException.throwIt(SW_WRONG_LENGTH); if (apdu.getCurrentState() == APDU.STATE_INITIAL) { apdu.setIncomingAndReceive(); } if (apdu.getCurrentState() < APDU.STATE_FULL_INCOMING) { // need all data in one APDU. ISOException.throwIt(SW_INTERNAL_ERROR); } short fid = Util.getShort(buffer, OFFSET_CDATA); if (fileSystem.getFile(fid) != null) { selectedFile = fid; volatileState[0] |= FILE_SELECTED; return; } setNoFileSelected(); ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } /** * Processes incoming READ_BINARY APDUs. Returns data of the currently * selected file. * * @param apdu * where the offset is carried in header bytes p1 and p2. * @param le * expected length by terminal * @return length of the return APDU */ private short processReadBinary(APDU apdu, short le, boolean protectedApdu) { if (hasMutualAuthenticationKeys() && (!protectedApdu || !hasMutuallyAuthenticated())) { ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); } byte[] buffer = apdu.getBuffer(); byte p1 = buffer[OFFSET_P1]; byte p2 = buffer[OFFSET_P2]; short offset = 0; if((byte)(p1 & MASK_SFI) == MASK_SFI) { byte sfi = (byte)(p1 & ~MASK_SFI); if(sfi >= 0x1F) { ISOException.throwIt(SW_INCORRECT_P1P2); } short fid = Util.makeShort((byte)0x00, sfi); if (fileSystem.getFile(fid) != null) { selectedFile = fid; volatileState[0] |= FILE_SELECTED; }else{ setNoFileSelected(); ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } offset = (short)(p2 & 0xFF); }else{ if(!hasFileSelected()) { ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); } offset = Util.makeShort(p1, p2); } byte[] file = fileSystem.getFile(selectedFile); // FIXME is this check redundant? if (file == null) { ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } short len; short fileSize = fileSystem.getFileSize(selectedFile); len = LicenseUtil.min((short) (buffer.length - 37), (short) (fileSize - offset)); // FIXME: 37 magic len = LicenseUtil.min(len, (short) buffer.length); len = LicenseUtil.min(le, len); short bufferOffset = 0; if (protectedApdu) { bufferOffset = crypto.getApduBufferOffset(len); } Util.arrayCopyNonAtomic(file, offset, buffer, bufferOffset, len); return len; } /** * Processes and UPDATE_BINARY apdu. Writes data in the currently selected * file. * * @param apdu * carries the offset where to write date in header bytes p1 and * p2. */ private void processUpdateBinary(APDU apdu) { if (!hasFileSelected() || isLocked()) { ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); } byte[] buffer = apdu.getBuffer(); byte p1 = buffer[OFFSET_P1]; byte p2 = buffer[OFFSET_P2]; short offset = Util.makeShort(p1, p2); short readCount = (short) (buffer[ISO7816.OFFSET_LC] & 0xff); readCount = apdu.setIncomingAndReceive(); while (readCount > 0) { fileSystem.writeData(selectedFile, offset, buffer, OFFSET_CDATA, readCount); offset += readCount; readCount = apdu.receiveBytes(ISO7816.OFFSET_CDATA); } } /** * Processes and CREATE_FILE apdu. * * This functionality is only partly implemented. Only non-directories * (files) can be created, all options for CREATE_FILE are ignored. * * @param apdu * containing 6 bytes: 0x64 || (1 byte) || size (2) || fid (2) */ private void processCreateFile(APDU apdu) { if (isLocked()) { ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); } byte[] buffer = apdu.getBuffer(); short lc = (short) (buffer[OFFSET_LC] & 0xff); boolean eapProtection = (buffer[OFFSET_P1] != (byte)0x00); if (apdu.getCurrentState() == APDU.STATE_INITIAL) { apdu.setIncomingAndReceive(); } if (apdu.getCurrentState() < APDU.STATE_FULL_INCOMING) { // need all data in one APDU. ISOException.throwIt(SW_INTERNAL_ERROR); } if (lc < (short) 6 || (buffer[OFFSET_CDATA + 1] & 0xff) < 4) ISOException.throwIt(SW_WRONG_LENGTH); if (buffer[OFFSET_CDATA] != 0x63) ISOException.throwIt(SW_DATA_INVALID); short size = Util.makeShort(buffer[(short) (OFFSET_CDATA + 2)], buffer[(short) (OFFSET_CDATA + 3)]); short fid = Util.makeShort(buffer[(short) (OFFSET_CDATA + 4)], buffer[(short) (OFFSET_CDATA + 5)]); fileSystem.createFile(fid, size, eapProtection); } static boolean hasActivelyAuthenticated() { return (volatileState[0] & ACTIVE_AUTHENTICATED) == ACTIVE_AUTHENTICATED; } static boolean hasInternalAuthenticationKeys() { return (persistentState & (HAS_EXPONENT | HAS_MODULUS)) == (HAS_EXPONENT | HAS_MODULUS); } static boolean hasMutualAuthenticationKeys() { return (persistentState & HAS_MUTUALAUTHENTICATION_KEYS) == HAS_MUTUALAUTHENTICATION_KEYS; } static boolean hasEAPKey() { return (persistentState & HAS_EC_KEY) == HAS_EC_KEY; } static boolean hasSicId() { return (persistentState & HAS_SICID) == HAS_SICID; } static boolean hasEAPCertificate() { return (persistentState & HAS_CVCERTIFICATE) == HAS_CVCERTIFICATE; } static void setNoFileSelected() { if (hasFileSelected()) { volatileState[0] ^= FILE_SELECTED; } } static void setNoChallenged() { if ((volatileState[0] & CHALLENGED) == CHALLENGED) { volatileState[0] ^= CHALLENGED; } } static boolean hasFileSelected() { return (volatileState[0] & FILE_SELECTED) == FILE_SELECTED; } static boolean isLocked() { return (persistentState & LOCKED) == LOCKED; } static boolean isChallenged() { return (volatileState[0] & CHALLENGED) == CHALLENGED; } static boolean hasMutuallyAuthenticated() { return (volatileState[0] & MUTUAL_AUTHENTICATED) == MUTUAL_AUTHENTICATED; } static boolean hasChipAuthenticated() { return (volatileState[0] & CHIP_AUTHENTICATED) == CHIP_AUTHENTICATED; } static boolean hasTerminalAuthenticated() { return (volatileState[0] & TERMINAL_AUTHENTICATED) == TERMINAL_AUTHENTICATED; } }