/** * Java Card implementation of the OpenPGP card * Copyright (C) 2012-2014 Yubico AB * Copyright (C) 2011 Joeri de Ruiter <joeri@cs.ru.nl> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package openpgpcard; import javacard.framework.*; import javacard.security.*; import javacardx.crypto.*; /** * AID of the applet should be according to the OpenPGP card standard v2.0.1 * E.g. D2760001240102000000000000010000: * D276000124 - RID of FSFE * 01 - OpenPGP application * Version needs to be higher than 0.07, otherwise tags for fingerprints are of by one in gpg * 0200 - Version * 0000 - Manufacturer * 00000001 - Serial number * 0000 - RFU */ public class OpenPGPApplet extends Applet implements ISO7816 { private static final short _0 = 0; private static final boolean FORCE_SM_GET_CHALLENGE = true; private static final byte[] HISTORICAL = { 0x00, 0x73, 0x00, 0x00, (byte) 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // returned by vendor specific command f1 private static final byte[] VERSION = { 0x01, 0x00, 0x12 }; // Openpgp defines 6983 as AUTHENTICATION BLOCKED private static final short SW_AUTHENTICATION_BLOCKED = 0x6983; private static final byte[] EXTENDED_CAP = { (byte) 0xF8, // Support for GET CHALLENGE // Support for Key Import // PW1 Status byte changeable // Support for private use data objects 0x00, // Secure messaging using 3DES 0x00, (byte) 0xFF, // Maximum length of challenges 0x04, (byte) 0xC0, // Maximum length Cardholder Certificate 0x00, (byte) 0xFF, // Maximum length command data 0x00, (byte) 0xFF // Maximum length response data }; private static short RESPONSE_MAX_LENGTH = 255; private static short RESPONSE_SM_MAX_LENGTH = 231; private static short CHALLENGES_MAX_LENGTH = 255; private static short BUFFER_MAX_LENGTH = 1221; private static short LOGINDATA_MAX_LENGTH = 254; private static short URL_MAX_LENGTH = 254; private static short NAME_MAX_LENGTH = 39; private static short LANG_MAX_LENGTH = 8; private static short CERT_MAX_LENGTH = 1216; private static short PRIVATE_DO_MAX_LENGTH = 254; private static short FP_LENGTH = 20; private static byte PW1_MIN_LENGTH = 6; private static byte PW1_MAX_LENGTH = 127; // Default PW1 '123456' private static byte[] PW1_DEFAULT = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 }; private static byte PW1_MODE_NO81 = 0; private static byte PW1_MODE_NO82 = 1; private static final byte RC_MIN_LENGTH = 8; private static final byte RC_MAX_LENGTH = 127; private static final byte PW3_MIN_LENGTH = 8; private static final byte PW3_MAX_LENGTH = 127; // Default PW3 '12345678' private static final byte[] PW3_DEFAULT = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38 }; private static final short SW_REFERENCED_DATA_NOT_FOUND = 0x6A88; private byte[] loginData; private short loginData_length; private byte[] url; private short url_length; private byte[] name; private short name_length; private byte[] lang; private short lang_length; private byte[] cert; private short cert_length; private byte sex; private byte[] private_use_do_1; private short private_use_do_1_length; private byte[] private_use_do_2; private short private_use_do_2_length; private byte[] private_use_do_3; private short private_use_do_3_length; private byte[] private_use_do_4; private short private_use_do_4_length; private OwnerPIN pw1; private byte pw1_length; private byte pw1_status; private boolean[] pw1_modes; private OwnerPIN rc; private byte rc_length; private OwnerPIN pw3; private byte pw3_length; private byte[] ds_counter; private PGPKey sig_key; private PGPKey dec_key; private PGPKey auth_key; private byte[] ca1_fp; private byte[] ca2_fp; private byte[] ca3_fp; private Cipher cipher; private RandomData random; private byte[] buffer; private short out_left = 0; private short out_sent = 0; private short in_received = 0; private boolean chain = false; private byte chain_ins = 0; private short chain_p1p2 = 0; private OpenPGPSecureMessaging sm; private boolean sm_success = false; private boolean terminated = false; public static void install(byte[] bArray, short bOffset, byte bLength) { new OpenPGPApplet().register(bArray, (short) (bOffset + 1), bArray[bOffset]); } private void initialize() { // Initialize PW1 with default password pw1 = new OwnerPIN((byte) 3, PW1_MAX_LENGTH); pw1.update(PW1_DEFAULT, _0, (byte) PW1_DEFAULT.length); pw1_length = (byte) PW1_DEFAULT.length; pw1_status = 0x00; // Initialize RC rc = new OwnerPIN((byte) 3, RC_MAX_LENGTH); rc_length = 0; // Initialize PW3 with default password pw3 = new OwnerPIN((byte) 3, PW3_MAX_LENGTH); pw3.update(PW3_DEFAULT, _0, (byte) PW3_DEFAULT.length); pw3_length = (byte) PW3_DEFAULT.length; // Create empty keys sig_key = new PGPKey(); dec_key = new PGPKey(); auth_key = new PGPKey(); // Initialize Secure Messaging sm.init(); loginData = new byte[LOGINDATA_MAX_LENGTH]; loginData_length = 0; url = new byte[URL_MAX_LENGTH]; url_length = 0; name = new byte[NAME_MAX_LENGTH]; name_length = 0; lang = new byte[LANG_MAX_LENGTH]; lang_length = 0; cert = null; cert_length = 0; sex = 0x39; private_use_do_1 = new byte[PRIVATE_DO_MAX_LENGTH]; private_use_do_1_length = 0; private_use_do_2 = new byte[PRIVATE_DO_MAX_LENGTH]; private_use_do_2_length = 0; private_use_do_3 = new byte[PRIVATE_DO_MAX_LENGTH]; private_use_do_3_length = 0; private_use_do_4 = new byte[PRIVATE_DO_MAX_LENGTH]; private_use_do_4_length = 0; ds_counter = new byte[3]; ca1_fp = new byte[FP_LENGTH]; ca2_fp = new byte[FP_LENGTH]; ca3_fp = new byte[FP_LENGTH]; } public OpenPGPApplet() { // Create temporary array buffer = JCSystem.makeTransientByteArray(BUFFER_MAX_LENGTH, JCSystem.CLEAR_ON_DESELECT); pw1_modes = JCSystem.makeTransientBooleanArray((short) 2, JCSystem.CLEAR_ON_DESELECT); cipher = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false); random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); sm = new OpenPGPSecureMessaging(); initialize(); } public void process(APDU apdu) { if (selectingApplet()) { // Reset PW1 modes pw1_modes[PW1_MODE_NO81] = false; pw1_modes[PW1_MODE_NO82] = false; return; } byte[] buf = apdu.getBuffer(); byte cla= buf[OFFSET_CLA]; byte ins = buf[OFFSET_INS]; byte p1 = buf[OFFSET_P1]; byte p2 = buf[OFFSET_P2]; short p1p2 = Util.makeShort(p1, p2); short lc = (short) (buf[OFFSET_LC] & 0xFF); // Secure messaging //TODO Force SM if contactless is used sm_success = false; if ((byte) (cla & (byte) 0x0C) == (byte) 0x0C) { // Force initialization of SSC before using SM to prevent replays if (FORCE_SM_GET_CHALLENGE && !sm.isSetSSC() && (ins != (byte) 0x84)) ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); lc = sm.unwrapCommandAPDU(); sm_success = true; } short status = SW_NO_ERROR; short le = 0; try { // Support for command chaining commandChaining(apdu); // Reset buffer for GET RESPONSE if (ins != (byte) 0xC0) { out_sent = 0; out_left = 0; } if (terminated == true && ins != 0x44) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } // Other instructions switch (ins) { // GET RESPONSE case (byte) 0xC0: // Will be handled in finally clause break; // VERIFY case (byte) 0x20: verify(apdu, p2); break; // CHANGE REFERENCE DATA case (byte) 0x24: changeReferenceData(apdu, p2); break; // RESET RETRY COUNTER case (byte) 0x2C: // Reset only available for PW1 if (p2 != (byte) 0x81) ISOException.throwIt(SW_INCORRECT_P1P2); resetRetryCounter(apdu, p1); break; // PERFORM SECURITY OPERATION case (byte) 0x2A: // COMPUTE DIGITAL SIGNATURE if (p1p2 == (short) 0x9E9A) { le = computeDigitalSignature(apdu); } // DECIPHER else if (p1p2 == (short) 0x8086) { le = decipher(apdu); } else { ISOException.throwIt(SW_WRONG_P1P2); } break; // INTERNAL AUTHENTICATE case (byte) 0x88: le = internalAuthenticate(apdu); break; // GENERATE ASYMMETRIC KEY PAIR case (byte) 0x47: le = genAsymKey(apdu, p1); break; // GET CHALLENGE case (byte) 0x84: le = getChallenge(apdu, lc); break; // GET DATA case (byte) 0xCA: le = getData(p1p2); break; // PUT DATA case (byte) 0xDA: putData(p1p2); break; // DB - PUT DATA (Odd) case (byte) 0xDB: // Odd PUT DATA only supported for importing keys // 4D - Extended Header list if (p1p2 == (short) 0x3FFF) { importKey(apdu); } else { ISOException.throwIt(SW_RECORD_NOT_FOUND); } break; // E6 - TERMINATE DF case (byte) 0xE6: if (pw1.getTriesRemaining() == 0 && pw3.getTriesRemaining() == 0) { terminated = true; } else { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } break; // 44 - ACTIVATE FILE case (byte) 0x44: if (terminated == true) { initialize(); terminated = false; JCSystem.requestObjectDeletion(); } else { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } break; // GET VERSION (vendor specific) case (byte) 0xF1: le = Util.arrayCopy(VERSION, _0, buffer, _0, (short) VERSION.length); break; // SET RETRIES (vendor specific) case (byte) 0xF2: if (lc != 3) { ISOException.throwIt(ISO7816.SW_WRONG_DATA); } short offs = ISO7816.OFFSET_CDATA; setPinRetries(buf[offs++], buf[offs++], buf[offs++]); break; default: // good practice: If you don't know the INStruction, say so: ISOException.throwIt(SW_INS_NOT_SUPPORTED); } } catch(ISOException e) { status = e.getReason(); } finally { if (status != (short)0x9000) { // Send the exception that was thrown sendException(apdu, status); } else { // GET RESPONSE if (ins == (byte) 0xC0) { sendNext(apdu); } else { sendBuffer(apdu, le); } } } } private void setPinRetries(byte pin_retries, byte reset_retries, byte admin_retries) { if (!pw3.isValidated()) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } if (pin_retries != 0) { pw1 = new OwnerPIN(pin_retries, PW1_MAX_LENGTH); pw1.update(PW1_DEFAULT, _0, (byte) PW1_DEFAULT.length); pw1_length = (byte) PW1_DEFAULT.length; pw1_status = 0x00; } if (reset_retries != 0) { rc = new OwnerPIN(reset_retries, RC_MAX_LENGTH); rc_length = 0; } if (admin_retries != 0) { pw3 = new OwnerPIN(admin_retries, PW3_MAX_LENGTH); pw3.update(PW3_DEFAULT, _0, (byte) PW3_DEFAULT.length); pw3_length = (byte) PW3_DEFAULT.length; } JCSystem.requestObjectDeletion(); } /** * Provide support for command chaining by storing the received data in * buffer * * @param apdu */ private void commandChaining(APDU apdu) { byte[] buf = apdu.getBuffer(); short p1p2 = Util.makeShort(buf[OFFSET_P1], buf[OFFSET_P2]); short len = (short) (buf[OFFSET_LC] & 0xFF); // Reset chaining if it was not yet initiated if (!chain) { resetChaining(); } if ((byte) (buf[OFFSET_CLA] & (byte) 0x10) == (byte) 0x10) { // If chaining was already initiated, INS and P1P2 should match if (chain && (buf[OFFSET_INS] != chain_ins && p1p2 != chain_p1p2)) { resetChaining(); ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); } // Check whether data to be received is larger than size of the // buffer if ((short) (in_received + len) > BUFFER_MAX_LENGTH) { resetChaining(); ISOException.throwIt(SW_WRONG_DATA); } // Store received data in buffer in_received = Util.arrayCopyNonAtomic(buf, OFFSET_CDATA, buffer, in_received, len); chain = true; chain_ins = buf[OFFSET_INS]; chain_p1p2 = p1p2; ISOException.throwIt(SW_NO_ERROR); } if (chain && buf[OFFSET_INS] == chain_ins && p1p2 == chain_p1p2) { chain = false; // Check whether data to be received is larger than size of the // buffer if ((short) (in_received + len) > BUFFER_MAX_LENGTH) { resetChaining(); ISOException.throwIt(SW_WRONG_DATA); } // Add received data to the buffer in_received = Util.arrayCopyNonAtomic(buf, OFFSET_CDATA, buffer, in_received, len); } else if (chain) { // Chained command expected resetChaining(); ISOException.throwIt(SW_UNKNOWN); } else { // No chaining was used, so copy data to buffer in_received = Util.arrayCopyNonAtomic(buf, OFFSET_CDATA, buffer, _0, len); } } private void resetChaining() { chain = false; in_received = 0; } /** * Provide the VERIFY command (INS 20) * * Verify one of the passwords depending on mode: - 81: PW1 for a PSO:CDS * command - 82: PW1 for other commands - 83: PW3 * * @param apdu * @param mode * Password and mode to be verified */ private void verify(APDU apdu, byte mode) { if (mode == (byte) 0x81 || mode == (byte) 0x82) { // Check length of input if (in_received < PW1_MIN_LENGTH || in_received > PW1_MAX_LENGTH) ISOException.throwIt(SW_WRONG_DATA); // Check given PW1 and set requested mode if verified succesfully if (pw1.getTriesRemaining() == 0) { ISOException.throwIt(SW_AUTHENTICATION_BLOCKED); } else if (pw1.check(buffer, _0, (byte) in_received)) { if (mode == (byte) 0x81) { pw1_modes[PW1_MODE_NO81] = true; } else { pw1_modes[PW1_MODE_NO82] = true; } } else { ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); } } else if (mode == (byte) 0x83) { // Check length of input if (in_received < PW3_MIN_LENGTH || in_received > PW3_MAX_LENGTH) ISOException.throwIt(SW_WRONG_DATA); // Check PW3 if (pw3.getTriesRemaining() == 0) { ISOException.throwIt(SW_AUTHENTICATION_BLOCKED); } else if (!pw3.check(buffer, _0, (byte) in_received)) { ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); } } else { ISOException.throwIt(SW_INCORRECT_P1P2); } } /** * Provide the CHANGE REFERENCE DATA command (INS 24) * * Change the password specified using mode: - 81: PW1 - 82: PW3 * * @param apdu * @param mode * Password to be changed */ private void changeReferenceData(APDU apdu, byte mode) { if (mode == (byte) 0x81) { // Check length of the new password short new_length = (short) (in_received - pw1_length); if (new_length < PW1_MIN_LENGTH || new_length > PW1_MAX_LENGTH) ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); if (!pw1.check(buffer, _0, pw1_length)) ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); // Change PW1 JCSystem.beginTransaction(); pw1.update(buffer, pw1_length, (byte) new_length); pw1_length = (byte) new_length; pw1_modes[PW1_MODE_NO81] = false; pw1_modes[PW1_MODE_NO82] = false; JCSystem.commitTransaction(); } else if (mode == (byte) 0x83) { // Check length of the new password short new_length = (short) (in_received - pw3_length); if (new_length < PW3_MIN_LENGTH || new_length > PW3_MAX_LENGTH) ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); if (!pw3.check(buffer, _0, pw3_length)) ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); // Change PW3 JCSystem.beginTransaction(); pw3.update(buffer, pw3_length, (byte) new_length); pw3_length = (byte) new_length; JCSystem.commitTransaction(); } } /** * Provide the RESET RETRY COUNTER command (INS 2C) * * Reset PW1 either using the Resetting Code (mode = 00) or PW3 (mode = 02) * * @param apdu * @param mode * Mode used to reset PW1 */ private void resetRetryCounter(APDU apdu, byte mode) { short new_length = 0; short offs = 0; if (mode == (byte) 0x00) { // Authentication using RC if (rc_length == 0) ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); new_length = (short) (in_received - rc_length); offs = rc_length; if (!rc.check(buffer, _0, rc_length)) ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); } else if (mode == (byte) 0x02) { // Authentication using PW3 if (!pw3.isValidated()) ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); new_length = in_received; } else { ISOException.throwIt(SW_WRONG_P1P2); } if (new_length < PW1_MIN_LENGTH || new_length > PW1_MAX_LENGTH) ISOException.throwIt(SW_WRONG_DATA); // Change PW1 JCSystem.beginTransaction(); pw1.update(buffer, offs, (byte) new_length); pw1_length = (byte) new_length; pw1.resetAndUnblock(); JCSystem.commitTransaction(); } /** * Provide the PSO: COMPUTE DIGITAL SIGNATURE command (INS 2A, P1P2 9E9A) * * Sign the data provided using the key for digital signatures. * * Before using this method PW1 has to be verified with mode No. 81. If the * first status byte of PW1 is 00, access condition PW1 with No. 81 is * reset. * * @param apdu * @return Length of data written in buffer */ private short computeDigitalSignature(APDU apdu) { if (!(pw1.isValidated() && pw1_modes[PW1_MODE_NO81])) ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); if (pw1_status == (byte) 0x00) pw1_modes[PW1_MODE_NO81] = false; if (!sig_key.getPrivate().isInitialized()) ISOException.throwIt(SW_REFERENCED_DATA_NOT_FOUND); cipher.init(sig_key.getPrivate(), Cipher.MODE_ENCRYPT); increaseDSCounter(); short length = cipher.doFinal(buffer, _0, in_received, buffer, in_received); Util.arrayCopyNonAtomic(buffer, in_received, buffer, _0, length); return length; } /** * Provide the PSO: DECIPHER command (INS 2A, P1P2 8086) * * Decrypt the data provided using the key for confidentiality. * * Before using this method PW1 has to be verified with mode No. 82. * * @param apdu * @return Length of data written in buffer */ private short decipher(APDU apdu) { // DECIPHER if (!(pw1.isValidated() && pw1_modes[PW1_MODE_NO82])) ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); if (!dec_key.getPrivate().isInitialized()) ISOException.throwIt(SW_REFERENCED_DATA_NOT_FOUND); cipher.init(dec_key.getPrivate(), Cipher.MODE_DECRYPT); // Start at offset 1 to omit padding indicator byte short length = cipher.doFinal(buffer, (short)1, (short) (in_received - 1), buffer, in_received); Util.arrayCopyNonAtomic(buffer, in_received, buffer, _0, length); return length; } /** * Provide the INTERNAL AUTHENTICATE command (INS 88) * * Sign the data provided using the key for authentication. Before using * this method PW1 has to be verified with mode No. 82. * * @param apdu * @return Length of data written in buffer */ private short internalAuthenticate(APDU apdu) { if (!(pw1.isValidated() && pw1_modes[PW1_MODE_NO82])) ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); if (!auth_key.getPrivate().isInitialized()) ISOException.throwIt(SW_REFERENCED_DATA_NOT_FOUND); cipher.init(auth_key.getPrivate(), Cipher.MODE_ENCRYPT); short length = cipher.doFinal(buffer, _0, in_received, buffer, in_received); Util.arrayCopyNonAtomic(buffer, in_received, buffer, _0, length); return length; } /** * Provide the GENERATE ASYMMETRIC KEY PAIR command (INS 47) * * For mode 80, generate a new key pair, specified in the first element of * buffer, and output the public key. * * For mode 81, output the public key specified in the first element of * buffer. * * Before using this method PW3 has to be verified. * * @param apdu * @param mode * Generate key pair (80) or read public key (81) * @return Length of data written in buffer */ private short genAsymKey(APDU apdu, byte mode) { PGPKey key = getKey(buffer[0]); if (mode == (byte) 0x80) { if (!pw3.isValidated()) ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); key.genKeyPair(); if (buffer[0] == (byte) 0xB6) { Util.arrayFillNonAtomic(ds_counter, _0, (short) 3, (byte) 0); } } // Output requested key return sendPublicKey(key); } /** * Provide the GET CHALLENGE command (INS 84) * * Generate a random number of the length given in len. * * @param apdu * @param len * Length of the requested challenge * @return Length of data written in buffer */ private short getChallenge(APDU apdu, short len) { if (len > CHALLENGES_MAX_LENGTH) ISOException.throwIt(SW_WRONG_DATA); random.generateData(buffer, _0, len); // Set the SSC used in Secure Messaging if the size of the requested // challenge is equal to the size of the SSC if (len == sm.getSSCSize()) { sm.setSSC(buffer, _0); } return len; } /** * Provide the GET DATA command (INS CA) * * Output the data specified with tag. * * @param apdu * @param tag * Tag of the requested data */ private short getData(short tag) { short offset = 0; switch (tag) { // 4F - Application identifier (AID) case (short) 0x004F: return JCSystem.getAID().getBytes(buffer, _0); // 5E - Login data case (short) 0x005E: return Util.arrayCopyNonAtomic(loginData, _0, buffer, _0, loginData_length); // 5F50 - URL case (short) 0x5F50: return Util.arrayCopyNonAtomic(url, _0, buffer, _0, url_length); // 5F52 - Historical bytes case (short) 0x5F52: return Util.arrayCopyNonAtomic(HISTORICAL, _0, buffer, _0, (short)HISTORICAL.length); // 65 - Cardholder Related Data case (short) 0x0065: // 5B - Name buffer[offset++] = 0x5B; buffer[offset++] = (byte) name_length; offset = Util.arrayCopyNonAtomic(name, _0, buffer, offset, name_length); // 5F2D - Language buffer[offset++] = 0x5F; buffer[offset++] = 0x2D; buffer[offset++] = (byte) lang_length; offset = Util.arrayCopyNonAtomic(lang, _0, buffer, offset, lang_length); // 5F35 - Sex buffer[offset++] = 0x5F; buffer[offset++] = 0x35; buffer[offset++] = 0x01; buffer[offset++] = sex; return offset; // 6E - Application Related Data case (short) 0x006E: // 4F - AID buffer[offset++] = 0x4F; byte len = JCSystem.getAID().getBytes(buffer, (short)(offset + 1)); buffer[offset++] = len; offset += len; // 5F52 - Historical bytes buffer[offset++] = 0x5F; buffer[offset++] = 0x52; buffer[offset++] = (byte) HISTORICAL.length; offset = Util.arrayCopyNonAtomic(HISTORICAL, _0, buffer, offset, (short) HISTORICAL.length); // 73 - Discretionary data objects buffer[offset++] = 0x73; buffer[offset++] = (byte)0x81; // This field's length will exceed 127 bytes short ddoLengthOffset = offset; buffer[offset++] = 0x00; // Placeholder for length byte // C0 - Extended capabilities buffer[offset++] = (byte) 0xC0; buffer[offset++] = (byte) EXTENDED_CAP.length; offset = Util.arrayCopyNonAtomic(EXTENDED_CAP, _0, buffer, offset, (short) EXTENDED_CAP.length); // C1 - Algorithm attributes signature buffer[offset++] = (byte) 0xC1; buffer[offset++] = (byte) 0x06; offset = sig_key.getAttributes(buffer, offset); // C2 - Algorithm attributes decryption buffer[offset++] = (byte) 0xC2; buffer[offset++] = (byte) 0x06; offset = dec_key.getAttributes(buffer, offset); // C3 - Algorithm attributes authentication buffer[offset++] = (byte) 0xC3; buffer[offset++] = (byte) 0x06; offset = auth_key.getAttributes(buffer, offset); // C4 - PW1 Status bytes buffer[offset++] = (byte) 0xC4; buffer[offset++] = 0x07; buffer[offset++] = pw1_status; buffer[offset++] = PW1_MAX_LENGTH; buffer[offset++] = RC_MAX_LENGTH; buffer[offset++] = PW3_MAX_LENGTH; buffer[offset++] = pw1.getTriesRemaining(); buffer[offset++] = rc.getTriesRemaining(); buffer[offset++] = pw3.getTriesRemaining(); // C5 - Fingerprints sign, dec and auth keys buffer[offset++] = (byte) 0xC5; buffer[offset++] = (short) 60; offset = sig_key.getFingerprint(buffer, offset); offset = dec_key.getFingerprint(buffer, offset); offset = auth_key.getFingerprint(buffer, offset); // C6 - Fingerprints CA 1, 2 and 3 buffer[offset++] = (byte) 0xC6; buffer[offset++] = (short) 60; offset = Util.arrayCopyNonAtomic(ca1_fp, _0, buffer, offset, FP_LENGTH); offset = Util.arrayCopyNonAtomic(ca2_fp, _0, buffer, offset, FP_LENGTH); offset = Util.arrayCopyNonAtomic(ca3_fp, _0, buffer, offset, FP_LENGTH); // CD - Generation times of public key pair buffer[offset++] = (byte) 0xCD; buffer[offset++] = (short) 12; offset = sig_key.getTime(buffer, offset); offset = dec_key.getTime(buffer, offset); offset = auth_key.getTime(buffer, offset); // Set length of combined discretionary data objects buffer[ddoLengthOffset] = (byte) (offset - ddoLengthOffset - 1); return offset; // 7A - Security support template case (short) 0x007A: // 93 - Digital signature counter buffer[offset++] = (byte) 0x93; buffer[offset++] = 0x03; offset = Util.arrayCopyNonAtomic(ds_counter, _0, buffer, offset, (short) 3); return offset; // 7F21 - Cardholder Certificate case (short) 0x7F21: if (cert_length > 0) { offset = Util.arrayCopyNonAtomic(cert, _0, buffer, offset, cert_length); } return offset; // C4 - PW Status Bytes case (short) 0x00C4: buffer[offset++] = pw1_status; buffer[offset++] = PW1_MAX_LENGTH; buffer[offset++] = RC_MAX_LENGTH; buffer[offset++] = PW3_MAX_LENGTH; buffer[offset++] = pw1.getTriesRemaining(); buffer[offset++] = rc.getTriesRemaining(); buffer[offset++] = pw3.getTriesRemaining(); return offset; // 0101 - Private Use DO 1 case (short) 0x0101: return Util.arrayCopyNonAtomic(private_use_do_1, _0, buffer, _0, private_use_do_1_length); // 0102 - Private Use DO 2 case (short) 0x0102: return Util.arrayCopyNonAtomic(private_use_do_2, _0, buffer, _0, private_use_do_2_length); // 0103 - Private Use DO 3 case (short) 0x0103: // For private use DO 3, PW1 must be verified with mode 82 to read if (!(pw1.isValidated() && pw1_modes[PW1_MODE_NO82])) ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); return Util.arrayCopyNonAtomic(private_use_do_3, _0, buffer, _0, private_use_do_3_length); // 0104 - Private Use DO 4 case (short) 0x0104: // For private use DO 4, PW3 must be verified to read if (!pw3.isValidated()) ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); return Util.arrayCopyNonAtomic(private_use_do_4, _0, buffer, _0, private_use_do_4_length); default: ISOException.throwIt(SW_RECORD_NOT_FOUND); } return offset; } /** * Provide the PUT DATA command (INS DA) * * Write the data specified using tag. * * Before using this method PW3 has to be verified. * * @param apdu * @param tag * Tag of the requested data */ private void putData(short tag) { if(tag == 0x0101 || tag == 0x0103) { // Special case for private use DO's 1 and 3: these can be written if // PW1 is verified with mode 82. All others require PW3 verification. if (!(pw1.isValidated() && pw1_modes[PW1_MODE_NO82])) ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); if (in_received > PRIVATE_DO_MAX_LENGTH) ISOException.throwIt(SW_WRONG_LENGTH); switch (tag) { // 0101 - Private Use DO 1 case 0x0101: JCSystem.beginTransaction(); private_use_do_1_length = in_received; Util.arrayCopy(buffer, _0, private_use_do_1, _0, in_received); JCSystem.commitTransaction(); break; // 0103 - Private Use DO 3 case 0x0103: JCSystem.beginTransaction(); private_use_do_3_length = in_received; Util.arrayCopy(buffer, _0, private_use_do_3, _0, in_received); JCSystem.commitTransaction(); break; } return; } if (!pw3.isValidated()) ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); switch (tag) { // 5B - Name case (short) 0x005B: if (in_received > name.length) ISOException.throwIt(SW_WRONG_DATA); JCSystem.beginTransaction(); name_length = Util.arrayCopy(buffer, _0, name, _0, in_received); JCSystem.commitTransaction(); break; // 5E - Login data case (short) 0x005E: if (in_received > loginData.length) ISOException.throwIt(SW_WRONG_DATA); JCSystem.beginTransaction(); loginData_length = Util.arrayCopy(buffer, _0, loginData, _0, in_received); JCSystem.commitTransaction(); break; // 5F2D - Language preferences case (short) 0x5F2D: if (in_received > lang.length) ISOException.throwIt(SW_WRONG_DATA); JCSystem.beginTransaction(); lang_length = Util.arrayCopy(buffer, _0, lang, _0, in_received); JCSystem.commitTransaction(); break; // 5F35 - Sex case (short) 0x5F35: if (in_received != 1) ISOException.throwIt(SW_WRONG_DATA); // Check for valid values if (buffer[0] != (byte) 0x31 && buffer[0] != (byte) 0x32 && buffer[0] != (byte) 0x39) ISOException.throwIt(SW_WRONG_DATA); sex = buffer[0]; break; // 5F50 - URL case (short) 0x5F50: if (in_received > url.length) ISOException.throwIt(SW_WRONG_DATA); JCSystem.beginTransaction(); url_length = Util.arrayCopy(buffer, _0, url, _0, in_received); JCSystem.commitTransaction(); break; // 7F21 - Cardholder certificate case (short) 0x7F21: if (in_received > CERT_MAX_LENGTH) ISOException.throwIt(SW_WRONG_DATA); if (cert == null) { cert = new byte[CERT_MAX_LENGTH]; } cert_length = Util.arrayCopyNonAtomic(buffer, _0, cert, _0, in_received); break; // C4 - PW Status Bytes case (short) 0x00C4: if (in_received != 1) ISOException.throwIt(SW_WRONG_DATA); // Check for valid values if (buffer[0] != (byte) 0x00 && buffer[0] != (byte) 0x01) ISOException.throwIt(SW_WRONG_DATA); pw1_status = buffer[0]; break; // C7 - Fingerprint signature key case (short) 0x00C7: if (in_received != PGPKey.FP_SIZE) ISOException.throwIt(SW_WRONG_DATA); sig_key.setFingerprint(buffer, _0); break; // C8 - Fingerprint decryption key case (short) 0x00C8: if (in_received != PGPKey.FP_SIZE) ISOException.throwIt(SW_WRONG_DATA); dec_key.setFingerprint(buffer, _0); break; // C9 - Fingerprint authentication key case (short) 0x00C9: if (in_received != PGPKey.FP_SIZE) ISOException.throwIt(SW_WRONG_DATA); auth_key.setFingerprint(buffer, _0); break; // CA - Fingerprint Certification Authority 1 case (short) 0x00CA: if (in_received != FP_LENGTH) ISOException.throwIt(SW_WRONG_DATA); Util.arrayCopy(buffer, _0, ca1_fp, _0, in_received); break; // CB - Fingerprint Certification Authority 2 case (short) 0x00CB: if (in_received != FP_LENGTH) ISOException.throwIt(SW_WRONG_DATA); Util.arrayCopy(buffer, _0, ca2_fp, _0, in_received); break; // CC - Fingerprint Certification Authority 3 case (short) 0x00CC: if (in_received != FP_LENGTH) ISOException.throwIt(SW_WRONG_DATA); Util.arrayCopy(buffer, _0, ca3_fp, _0, in_received); break; // CE - Signature key generation date/time case (short) 0x00CE: if (in_received != 4) ISOException.throwIt(SW_WRONG_DATA); sig_key.setTime(buffer, _0); break; // CF - Decryption key generation date/time case (short) 0x00CF: if (in_received != 4) ISOException.throwIt(SW_WRONG_DATA); dec_key.setTime(buffer, _0); break; // D0 - Authentication key generation date/time case (short) 0x00D0: if (in_received != 4) ISOException.throwIt(SW_WRONG_DATA); auth_key.setTime(buffer, _0); break; // D3 - Resetting Code case (short) 0x00D3: if (in_received == 0) { rc_length = 0; } else if (in_received >= RC_MIN_LENGTH && in_received <= RC_MAX_LENGTH) { JCSystem.beginTransaction(); rc.update(buffer, _0, (byte) in_received); rc_length = (byte) in_received; rc.resetAndUnblock(); JCSystem.commitTransaction(); } else { ISOException.throwIt(SW_WRONG_DATA); } break; // D1 - SM-Key-ENC case (short) 0x00D1: sm.setSessionKeyEncryption(buffer, _0); break; // D2 - SM-Key-MAC case (short) 0x00D2: sm.setSessionKeyMAC(buffer, _0); break; // F4 - SM-Key-Container case (short) 0x00F4: short offset = 0; short key_len = 0; // Set encryption key if (buffer[offset++] == (byte)0xD1) { key_len = (short)(buffer[offset++] & 0x7F); sm.setSessionKeyEncryption(buffer, offset); offset += key_len; } // Set MAC key if (buffer[offset++] == (byte)0xD2) { key_len = (short)(buffer[offset++] & 0x7F); sm.setSessionKeyMAC(buffer, offset); offset += key_len; } break; // 0102 - Private Use DO 2 case 0x0102: if (in_received > PRIVATE_DO_MAX_LENGTH) ISOException.throwIt(SW_WRONG_LENGTH); JCSystem.beginTransaction(); private_use_do_2_length = in_received; Util.arrayCopy(buffer, _0, private_use_do_2, _0, in_received); JCSystem.commitTransaction(); break; // 0104 - Private Use DO 4 case 0x0104: if (in_received > PRIVATE_DO_MAX_LENGTH) ISOException.throwIt(SW_WRONG_LENGTH); JCSystem.beginTransaction(); private_use_do_4_length = in_received; Util.arrayCopy(buffer, _0, private_use_do_4, _0, in_received); JCSystem.commitTransaction(); break; default: ISOException.throwIt(SW_RECORD_NOT_FOUND); break; } } /** * EXPERIMENTAL: Provide functionality for importing keys. * * @param apdu */ private void importKey(APDU apdu) { short offset = 0; if (!pw3.isValidated()) ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); // Check for tag 4D if (buffer[offset++] != 0x4D) ISOException.throwIt(SW_DATA_INVALID); // Length of 4D offset += getLengthBytes(getLength(buffer, offset)); // Get key for Control Reference Template PGPKey key = getKey(buffer[offset++]); // Skip empty length of CRT offset++; // Check for tag 7F48 if (buffer[offset++] != 0x7F || buffer[offset++] != 0x48) ISOException.throwIt(SW_DATA_INVALID); short len_template = getLength(buffer, offset); offset += getLengthBytes(len_template); short offset_data = (short) (offset + len_template); if (buffer[offset++] != (byte) 0x91) ISOException.throwIt(SW_DATA_INVALID); short len_e = getLength(buffer, offset); offset += getLengthBytes(len_e); if (buffer[offset++] != (byte) 0x92) ISOException.throwIt(SW_DATA_INVALID); short len_p = getLength(buffer, offset); offset += getLengthBytes(len_p); if (buffer[offset++] != (byte) 0x93) ISOException.throwIt(SW_DATA_INVALID); short len_q = getLength(buffer, offset); offset += getLengthBytes(len_q); if (buffer[offset++] != (byte) 0x94) ISOException.throwIt(SW_DATA_INVALID); short len_pq = getLength(buffer, offset); offset += getLengthBytes(len_pq); if (buffer[offset++] != (byte) 0x95) ISOException.throwIt(SW_DATA_INVALID); short len_dp1 = getLength(buffer, offset); offset += getLengthBytes(len_dp1); if (buffer[offset++] != (byte) 0x96) ISOException.throwIt(SW_DATA_INVALID); short len_dq1 = getLength(buffer, offset); offset += getLengthBytes(len_dq1); if (buffer[offset++] != (byte) 0x97) ISOException.throwIt(SW_DATA_INVALID); short len_modulus = getLength(buffer, offset); offset += getLengthBytes(len_modulus); if (buffer[offset_data++] != 0x5F || buffer[offset_data++] != 0x48) ISOException.throwIt(SW_DATA_INVALID); offset_data += getLengthBytes(getLength(buffer, offset_data)); key.setExponent(buffer, offset_data, len_e); offset_data += len_e; key.setP(buffer, offset_data, len_p); offset_data += len_p; key.setQ(buffer, offset_data, len_q); offset_data += len_q; key.setPQ(buffer, offset_data, len_pq); offset_data += len_pq; key.setDP1(buffer, offset_data, len_dp1); offset_data += len_dp1; key.setDQ1(buffer, offset_data, len_dq1); offset_data += len_dq1; key.setModulus(buffer, offset_data, len_modulus); offset_data += len_modulus; } /** * Output the public key of the given key pair. * * @param apdu * @param key * Key pair containing public key to be output */ private short sendPublicKey(PGPKey key) { RSAPublicKey pubkey = key.getPublic(); // Build message in buffer short offset = 0; buffer[offset++] = 0x7F; buffer[offset++] = 0x49; buffer[offset++] = (byte) 0x82; short offsetForLength = offset; offset += 2; // 81 - Modulus buffer[offset++] = (byte) 0x81; // Length of modulus is always greater than 128 bytes if (key.getModulusLength() < 256) { buffer[offset++] = (byte) 0x81; buffer[offset++] = (byte) key.getModulusLength(); } else { buffer[offset++] = (byte) 0x82; offset = Util.setShort(buffer, offset, key.getModulusLength()); } pubkey.getModulus(buffer, offset); offset += key.getModulusLength(); // 82 - Exponent buffer[offset++] = (byte) 0x82; buffer[offset++] = (byte) key.getExponentLength(); pubkey.getExponent(buffer, offset); offset += key.getExponentLength(); Util.setShort(buffer, offsetForLength, (short)(offset - offsetForLength - 2)); return offset; } /** * Send len bytes from buffer. If len is greater than RESPONSE_MAX_LENGTH, * remaining data can be retrieved using GET RESPONSE. * * @param apdu * @param len * The byte length of the data to send */ private void sendBuffer(APDU apdu, short len) { out_sent = 0; out_left = len; sendNext(apdu); } /** * Send provided status * * @param apdu * @param status Status to send */ private void sendException(APDU apdu, short status) { out_sent = 0; out_left = 0; sendNext(apdu, status); } /** * Send next block of data in buffer. Used for sending data in <buffer> * * @param apdu */ private void sendNext(APDU apdu) { sendNext(apdu, SW_NO_ERROR); } /** * Send next block of data in buffer. Used for sending data in <buffer> * * @param apdu * @param status Status to send */ private void sendNext(APDU apdu, short status) { byte[] buf = APDU.getCurrentAPDUBuffer(); apdu.setOutgoing(); // Determine maximum size of the messages short max_length; if (sm_success) { max_length = RESPONSE_SM_MAX_LENGTH; } else { max_length = RESPONSE_MAX_LENGTH; } if (max_length > out_left) { max_length = out_left; } Util.arrayCopyNonAtomic(buffer, out_sent, buf, _0, max_length); short len = 0; if (out_left > max_length) { len = max_length; // Compute byte left and sent out_left -= max_length; out_sent += max_length; // Determine new status word if (out_left > max_length) { status = (short) (SW_BYTES_REMAINING_00 | max_length); } else { status = (short) (SW_BYTES_REMAINING_00 | out_left); } } else { len = out_left; // Reset buffer out_sent = 0; out_left = 0; } // If SM is used, wrap response if (sm_success) { len = sm.wrapResponseAPDU(buf, _0, len, status); } // Send data in buffer apdu.setOutgoingLength(len); apdu.sendBytes(_0, len); // Send status word if (status != SW_NO_ERROR) ISOException.throwIt(status); } /** * Get length of TLV element. * * @param data * Byte array * @param offset * Offset within byte array containing first byte * @return Length of value */ private short getLength(byte[] data, short offset) { short len = 0; if ((data[offset] & (byte) 0x80) == (byte) 0x00) { len = data[offset]; } else if ((data[offset] & (byte) 0x7F) == (byte) 0x01) { len = data[(short) (offset + 1)]; len &= 0x00ff; } else if ((data[offset] & (byte) 0x7F) == (byte) 0x02) { len = Util.makeShort(data[(short) (offset + 1)], data[(short) (offset + 2)]); } else { ISOException.throwIt(SW_UNKNOWN); } return len; } /** * Get number of bytes needed to represent length for TLV element. * * @param length * Length of value * @return Number of bytes needed to represent length */ private short getLengthBytes(short length) { if (length <= 127) { return 1; } else if (length <= 255) { return 2; } else { return 3; } } /** * Return the key of the type requested: - B6: Digital signatures - B8: * Confidentiality - A4: Authentication * * @param type * Type of key to be returned * @return Key of requested type */ private PGPKey getKey(byte type) { PGPKey key = sig_key; if (type == (byte) 0xB6) { key = sig_key; } else if (type == (byte) 0xB8) { key = dec_key; } else if (type == (byte) 0xA4) { key = auth_key; } else { ISOException.throwIt(SW_UNKNOWN); } return key; } /** * Increase the digital signature counter by one. In case of overflow * SW_WARNING_STATE_UNCHANGED will be thrown and nothing will * change. */ private void increaseDSCounter() { for (short i = (short) (ds_counter.length - 1); i >= 0; i--) { if ((short) (ds_counter[i] & 0xFF) >= 0xFF) { if (i == 0) { // Overflow ISOException.throwIt(SW_WARNING_STATE_UNCHANGED); } else { ds_counter[i] = 0; } } else { ds_counter[i]++; break; } } } }