/*
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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.ss3t.javacard.gpg;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.OwnerPIN;
import javacard.framework.Util;
import javacard.security.KeyPair;
import javacard.security.RSAPrivateCrtKey;
import javacard.security.RSAPublicKey;
import javacard.security.RandomData;
import javacardx.crypto.Cipher;
/**
* Implements the GPG Card v 2.0.1 specification without using secure channels or Global Platform
* for portability.
* Spec: http://g10code.com/docs/openpgp-card-2.0.pdf
* Support:
* - 2048 bit RSA keys
* - RSA Key import (CRT format)
* - Random number generation
* - Private DOs
*
* Limitations:
* - No extended APDU support.
* - Supports only 2048 bit CRT RSA keys (1024 is too short and most cards don't support 4096 bits)
* - No secure messaging support.
* - No support for the cardholder certificate (DO 7F21).
* - Readability was favored over code size.
* - Will not run on cards with an APDU buffer smaller than 256 + 5 bytes.
*/
public final class Gpg extends Applet {
public static final byte CMD_VERIFY = 0x20;
public static final byte CMD_CHANGE_REFERENCE_DATA = 0x24;
public static final byte CMD_COMPUTE_PSO = 0x2A;
public static final byte CMD_RESET_RETRY_COUNTER = 0x2C;
public static final byte CMD_GENERATE_ASYMETRIC = (byte) 0x47;
public static final byte CMD_GET_CHALLENGE = (byte) 0x84;
public static final byte CMD_INTERNAL_AUTHENTICATE = (byte) 0x88;
public static final byte CMD_GET_RESPONSE = (byte) 0xC0;
public static final byte CMD_GET_DATA = (byte) 0xCA;
public static final byte CMD_PUT_DATA = (byte) 0xDA;
public static final byte CMD_PUT_KEY = (byte) 0xDB;
public static final byte CMD_TERMINATE_DF = (byte) 0xE6;
public static final byte CMD_ACTIVATE_FILE = (byte) 0x44;
private static final short SW_PIN_FAILED_00 = 0x63C0;
private static final short SW_PIN_BLOCKED = 0x6983;
public static final byte MAX_TRIES_PIN1 = 3;
public static final byte MAX_TRIES_RC = 3;
public static final byte MAX_TRIES_PIN3 = 3;
public static final byte MAX_PIN_LENGTH = 32;
private static final byte MIN_PIN1_LENGTH = 6;
private static final byte MIN_PIN3_LENGTH = 8;
private static final byte PIN_INDEX_PW1 = 0;
private static final byte PIN_INDEX_PW3 = 1;
private static final byte PIN_INDEX_RC = 2;
private static final short RSA_KEY_LENGTH_BYTES = 256; // 2048 bits.
private static final short RSA_KEY_HALF_LENGTH_BYTES = 128; // For P, Q...
// The key part order in PUT_KEY
private static final byte KEY_PART_E = 0;
private static final byte KEY_PART_PRIME_P = 1;
private static final byte KEY_PART_PRIME_Q = 2;
private static final byte KEY_PART_PARAM_PQ = 3;
private static final byte KEY_PART_PARAM_DP1 = 4;
private static final byte KEY_PART_PARAM_DQ1 = 5;
private static final byte KEY_PART_N = 6;
// Used for command chaining.
// Byte 0 = last INS, other bytes depend on the command.
private static final byte TEMP_INS = 0;
// For the put key command.
private static final byte TEMP_PUT_KEY_ACCUMULATOR_LENGTH = 1;
private static final byte TEMP_PUT_KEY_KEY_TYPE = 3;
private static final byte TEMP_PUT_KEY_KEY_CHUNK = 4;
private static final byte TEMP_PUT_KEY_EXPECTED_CHUNK_SIZE = 5;
private static final byte TEMP_PUT_KEY_ACCUMULATOR = 7;
// For Get Response.
private static final byte TEMP_GET_RESPONSE_OFFSET = 1;
private static final byte TEMP_GET_RESPONSE_LENGTH = 3;
private static final byte TEMP_GET_RESPONSE_DATA = 5;
// C0: Selection by full or partial DF name.
// 40: Data coding byte, not used by GPG.
// 80: Command chaining
// 00: No life cycle management.
private final static byte[] historicalBytes = {
0, 0x73, (byte) 0xC0, 0x40, (byte) 0x80, 0, (byte) 0x90, 0};
private final static byte[] extendedCapabilities = {
(byte) 0x78, // Get Challenge, Key Import, PW1 status changeable, Private DO
1, // AES
0, (byte) 0xFE, // Max challenge length.
0, 0, // Maximum length of cardholder certificate = 0.
0, (byte) 0xFF, // Maximum length of command data.
1, (byte) 0, // Maximum length of response data.
};
private final static byte[] algorithmAttributes = {
1, // RSA
8, 0, // 2048 bits key
0, 0x20, // 32 bit exponent.
3}; // CRT format with n.
// The spec 4.3.3.7 mandates that the DO are in order and only the ones needed are
// passed so we effectively always have the same header for a given key size.
// Values for e.length = 0
private static final byte[] expectedRSAKeyImportFormat = {
0x4D, (byte) 0x82, 3, (byte) 0x9F,
0, 0, // Key type, masked out before comparing.
0x7f, 0x48, 0x15,
(byte) 0x91, 0, // e
(byte) 0x92, (byte) 0x81, (byte) RSA_KEY_HALF_LENGTH_BYTES, // p
(byte) 0x93, (byte) 0x81, (byte) RSA_KEY_HALF_LENGTH_BYTES, // q
(byte) 0x94, (byte) 0x81, (byte) RSA_KEY_HALF_LENGTH_BYTES, // pq
(byte) 0x95, (byte) 0x81, (byte) RSA_KEY_HALF_LENGTH_BYTES, // dp1
(byte) 0x96, (byte) 0x81, (byte) RSA_KEY_HALF_LENGTH_BYTES, // dq1
(byte) 0x97, (byte) 0x82, (byte) 0x01, (byte) 0x0, // Modulus.
0x5F, 0x48, (byte) 0x82, 0x03, (byte) 0x80
};
// Default PINs according to spec section 4.2
private final byte[] defaultPIN = {0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38};
private final OwnerPIN[] pins;
// To distinguish between PW1/PW2, must be AND'ed with the OwnerPIN status.
private final boolean[] pinSubmitted;
private final byte[] pinLength; // Current PIN length to allow Change Reference Data to work.
private final byte[] privateDO1;
private final byte[] privateDO2;
private final byte[] privateDO3;
private final byte[] privateDO4;
private final byte[] loginData;
private final byte[] url;
private final byte[] name;
private final byte[] language;
private final byte[] sex;
private final byte[] fingerprints;
private final byte[] caFingerprints;
private final byte[] generationDates;
private final byte[] signatureCounter;
private byte pinValidForMultipleSignatures;
private final byte[] commandChainingBuffer;
private final KeyPair signatureKey;
private final KeyPair confidentialityKey;
private final KeyPair authenticationKey;
private final Cipher cipherRSA;
private final RandomData randomData;
private boolean terminated = false;
/**
* Only this class's install method should create the applet object.
*/
protected Gpg(byte[] parameters, short offset, byte length) {
pinLength = new byte[3];
pins = new OwnerPIN[3];
pins[PIN_INDEX_PW1] = new OwnerPIN(MAX_TRIES_PIN1, MAX_PIN_LENGTH);
pins[PIN_INDEX_PW1].update(defaultPIN, (short) 0, MIN_PIN1_LENGTH);
pinLength[PIN_INDEX_PW1] = MIN_PIN1_LENGTH;
pins[PIN_INDEX_PW3] = new OwnerPIN(MAX_TRIES_PIN3, MAX_PIN_LENGTH);
pins[PIN_INDEX_PW3].update(defaultPIN, (short) 0, MIN_PIN3_LENGTH);
pinLength[PIN_INDEX_PW3] = MIN_PIN3_LENGTH;
// The resetting code is disabled by default.
pins[PIN_INDEX_RC] = new OwnerPIN(MAX_TRIES_RC, MAX_PIN_LENGTH);
pinLength[PIN_INDEX_RC] = 0;
pinSubmitted = JCSystem.makeTransientBooleanArray((short) 2, JCSystem.CLEAR_ON_DESELECT);
commandChainingBuffer =
JCSystem.makeTransientByteArray((short) (TEMP_PUT_KEY_ACCUMULATOR + RSA_KEY_LENGTH_BYTES),
JCSystem.CLEAR_ON_DESELECT);
privateDO1 = new byte[255];
privateDO2 = new byte[255];
privateDO3 = new byte[255];
privateDO4 = new byte[255];
loginData = new byte[(short) 255];
url = new byte[(short) 255];
name = new byte[(short) 40];
language = new byte[(short) 9];
sex = new byte[(short) 1];
fingerprints = new byte[(short) 60];
caFingerprints = new byte[(short) 60];
generationDates = new byte[(short) 12];
signatureCounter = new byte[(short) 3];
pinValidForMultipleSignatures = (byte) 0;
signatureKey = new KeyPair(KeyPair.ALG_RSA_CRT, (short) 2048);
confidentialityKey = new KeyPair(KeyPair.ALG_RSA_CRT, (short) 2048);
authenticationKey = new KeyPair(KeyPair.ALG_RSA_CRT, (short) 2048);
cipherRSA = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false);
randomData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
register();
}
/**
* Installs this applet.
*
* @param parameters the array containing installation parameters
* @param offset the starting offset in bArray
* @param length the length in bytes of the parameter data in bArray
*/
public static void install(byte[] parameters, short offset, byte length) {
new Gpg(parameters, offset, length);
}
/**
* Processes an incoming APDU.
*
* @param apdu the incoming APDU
* @throws ISOException with the response bytes per ISO 7816-4
* @see APDU
*/
public void process(APDU apdu) {
byte buffer[] = apdu.getBuffer();
byte ins = buffer[ISO7816.OFFSET_INS];
if (ins == CMD_GET_RESPONSE) {
if (commandChainingBuffer[TEMP_INS] == CMD_GENERATE_ASYMETRIC) {
short lengthLeftToSend = Util.getShort(commandChainingBuffer, TEMP_GET_RESPONSE_LENGTH);
if (lengthLeftToSend == 0) {
return;
}
short responseLength = apdu.setOutgoing();
if (responseLength > lengthLeftToSend) {
responseLength = lengthLeftToSend;
}
lengthLeftToSend -= responseLength;
short offset = Util.getShort(commandChainingBuffer, TEMP_GET_RESPONSE_OFFSET);
apdu.setOutgoingLength(responseLength);
apdu.sendBytesLong(commandChainingBuffer, offset, responseLength);
if (lengthLeftToSend > (short) 0) {
Util.setShort(commandChainingBuffer, TEMP_GET_RESPONSE_OFFSET,
(short) (offset + responseLength));
Util.setShort(commandChainingBuffer, TEMP_GET_RESPONSE_LENGTH, lengthLeftToSend);
ISOException.throwIt(ISO7816.SW_BYTES_REMAINING_00);
} else {
Util.setShort(commandChainingBuffer, TEMP_GET_RESPONSE_OFFSET, (short) 0);
}
}
return;
}
if (commandChainingBuffer[TEMP_INS] != ins) {
// Reset the last chained instruction if we get a different command in the middle.
if (commandChainingBuffer[TEMP_INS] != (byte) 0) {
Util.arrayFillNonAtomic(commandChainingBuffer, (short) 0,
(short) commandChainingBuffer.length, (byte) 0);
}
}
if (selectingApplet()) {
short aidLength = JCSystem.getAID().getBytes(buffer, (short) 4);
buffer[0] = 0x6F;
buffer[1] = (byte) (2 + aidLength);
buffer[2] = (byte) 0x84;
buffer[3] = (byte) (aidLength);
apdu.setOutgoingAndSend((short) 0, (short) (4 + aidLength));
if (terminated) {
ISOException.throwIt((short)0x6285);
}
return;
}
if (terminated && ins != CMD_ACTIVATE_FILE) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
short p1p2 = Util.getShort(buffer, ISO7816.OFFSET_P1);
switch (ins) {
case CMD_VERIFY:
verify(apdu);
break;
case CMD_CHANGE_REFERENCE_DATA:
changeReferenceData(apdu);
break;
case CMD_RESET_RETRY_COUNTER:
resetRetryCounter(apdu);
break;
case CMD_GET_DATA:
getData(apdu);
break;
case CMD_COMPUTE_PSO:
if (p1p2 == (short) 0x9E9A) {
computeSignature(apdu);
} else if (p1p2 == (short) 0x8086) {
decrypt(apdu);
} else {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
break;
case CMD_GENERATE_ASYMETRIC:
if (p1p2 != (short) 0x8000 && p1p2 != (short) 0x8100) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
generateAsymetricKey(apdu);
break;
case CMD_PUT_DATA:
putData(apdu);
break;
case CMD_GET_CHALLENGE:
getChallenge(apdu);
break;
case CMD_PUT_KEY:
if (p1p2 != (short) 0x3FFF) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
putKey(apdu);
break;
case CMD_INTERNAL_AUTHENTICATE:
if (p1p2 != (short) 0) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
internalAuthenticate(apdu);
break;
case CMD_TERMINATE_DF:
terminateDF(apdu);
break;
case CMD_ACTIVATE_FILE:
activateFile(apdu);
break;
default:
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
}
commandChainingBuffer[0] = ins;
}
/**
* VERIFY APDU implementation.
*/
private void verify(APDU apdu) {
byte buffer[] = apdu.getBuffer();
if (buffer[ISO7816.OFFSET_P1] != (byte) 0) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// type = 0x81 or 0x82 -> PIN1, min length = 6
// type = 0x83 -> PIN2, min length = 8
byte pinOffset = PIN_INDEX_PW1;
byte type = buffer[ISO7816.OFFSET_P2];
byte minLength = MIN_PIN1_LENGTH;
if (type == (byte) 0x83) {
pinOffset = PIN_INDEX_PW3;
minLength = MIN_PIN3_LENGTH;
} else if (type != (byte) 0x81 && type != (byte) 0x82) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
short length = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
if (apdu.setIncomingAndReceive() != length ||
length > MAX_PIN_LENGTH ||
length < minLength) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
if (pins[pinOffset].getTriesRemaining() == 0) {
ISOException.throwIt(SW_PIN_BLOCKED);
}
boolean result = pins[pinOffset].check(buffer, ISO7816.OFFSET_CDATA, (byte) length);
if (type != (byte) 0x83) {
pinSubmitted[(byte) (type - 0x81)] = result;
}
if (result) {
ISOException.throwIt(ISO7816.SW_NO_ERROR);
}
ISOException.throwIt((short) (SW_PIN_FAILED_00 + pins[pinOffset].getTriesRemaining()));
}
/**
* Udpate the PIN and its length in a transaction.
* @param pinId which PIN will be updated.
* @param data contains the new PIN.
* @param dataOffset first byte of the new PIN in the data array.
* @param newLength the new PIN length.
*/
private void updatePIN(short pinId, byte[] data, short dataOffset, byte newLength) {
JCSystem.beginTransaction();
pins[pinId].update(data, dataOffset, newLength);
pinLength[pinId] = newLength;
JCSystem.commitTransaction();
}
/**
* CHANGE REFERENCE DATA APDU implementation.
*/
private void changeReferenceData(APDU apdu) {
byte buffer[] = apdu.getBuffer();
if (buffer[ISO7816.OFFSET_P1] != (byte) 0) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
byte pinOffset = PIN_INDEX_PW1;
byte minLength = MIN_PIN1_LENGTH;
byte type = buffer[ISO7816.OFFSET_P2];
if (type == (byte) 0x83) {
pinOffset = PIN_INDEX_PW3;
minLength = MIN_PIN3_LENGTH;
} else if (type != (byte) 0x81) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
byte currentLength = pinLength[pinOffset];
short length = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
if (apdu.setIncomingAndReceive() != length ||
length > (short)(currentLength + MAX_PIN_LENGTH) ||
length < (short)(currentLength + minLength)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
if (pins[pinOffset].getTriesRemaining() == 0) {
ISOException.throwIt(SW_PIN_BLOCKED);
}
if (!pins[pinOffset].check(buffer, ISO7816.OFFSET_CDATA, currentLength)) {
pinSubmitted[0] = false;
ISOException.throwIt((short) (SW_PIN_FAILED_00 + pins[pinOffset].getTriesRemaining()));
}
updatePIN(pinOffset, buffer, (short) (ISO7816.OFFSET_CDATA + currentLength),
(byte) (length - currentLength));
}
/**
* RESET RETRY COUNTER ADPU implementation.
*/
private void resetRetryCounter(APDU apdu) {
byte buffer[] = apdu.getBuffer();
short length = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
if (apdu.setIncomingAndReceive() != length) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
if (buffer[ISO7816.OFFSET_P2] != (byte) 0x81) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
if (buffer[ISO7816.OFFSET_P1] == (byte) 0) {
// We need to check RC then update P1 (if RC is set).
byte rcLength = pinLength[PIN_INDEX_RC];
if (pins[PIN_INDEX_RC].getTriesRemaining() == 0 || rcLength == 0) {
ISOException.throwIt(SW_PIN_BLOCKED);
}
if (length < (short)(rcLength + MIN_PIN1_LENGTH) ||
length > (short)(rcLength + MAX_PIN_LENGTH)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
if (pins[PIN_INDEX_RC].check(buffer, ISO7816.OFFSET_CDATA, rcLength)) {
updatePIN(PIN_INDEX_PW1, buffer, (short) (ISO7816.OFFSET_CDATA + rcLength),
(byte) (length - rcLength));
} else {
ISOException.throwIt((short) (SW_PIN_FAILED_00 + pins[PIN_INDEX_RC].getTriesRemaining()));
}
} else if (buffer[ISO7816.OFFSET_P1] == (byte) 2) {
// Resetting by assuming that PW3 was submitted.
if (!pins[PIN_INDEX_PW3].isValidated()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
if (length < MIN_PIN1_LENGTH || length > MAX_PIN_LENGTH) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
updatePIN(PIN_INDEX_PW1, buffer, ISO7816.OFFSET_CDATA, (byte) length);
} else {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
}
/**
* GET DATA APDU implementation. The spec isn't clear about which DOs are readable individually so
* more tags are readable than strictly necessary.
*/
private void getData(APDU apdu) {
byte[] buffer = apdu.getBuffer();
short tag = Util.getShort(buffer, ISO7816.OFFSET_P1);
short offset = 0;
switch (tag) {
case 0x4F:
// Return the AID
offset = JCSystem.getAID().getBytes(buffer, (short) 0);
break;
case 0x5B:
offset = Util.arrayCopyNonAtomic(name, (short) 1, buffer, (short) 0,
(short) (name[0] & 0xFF));
break;
case 0x5E:
offset = Util.arrayCopyNonAtomic(loginData, (short) 1, buffer, (short) 0,
(short) (loginData[0] & 0xFF));
break;
case 0x5F2D:
offset = Util.arrayCopyNonAtomic(language, (short) 1, buffer, (short) 0,
(short) (language[0] & 0xFF));
break;
case 0x5F35:
buffer[0] = sex[0];
offset = 1;
break;
case 0x5F50:
offset = Util.arrayCopyNonAtomic(url, (short) 1, buffer, (short) 0,
(short) (url[0] & 0xFF));
break;
case 0x5F52:
offset = Util.arrayCopyNonAtomic(historicalBytes, (short) 0, buffer, (short) 0,
(short) historicalBytes.length);
break;
// Cardholder related.
case 0x65:
buffer[0] = 0x5B;
buffer[1] = name[0];
offset = Util.arrayCopyNonAtomic(name, (short) 1, buffer, (short) 2,
(short) (name[0] & 0xFF));
buffer[offset++] = 0x5F;
buffer[offset++] = 0x2D;
buffer[offset++] = language[0];
offset = Util.arrayCopyNonAtomic(language, (short) 1, buffer, offset,
(short) (language[0] & 0xFF));
buffer[offset++] = 0x5F;
buffer[offset++] = 0x35;
buffer[offset++] = 1;
buffer[offset++] = sex[0];
break;
// Application related data.
case 0x6E:
buffer[0] = 0x4F;
buffer[1] = JCSystem.getAID().getBytes(buffer, (short) 2);
offset = (short) (2 + buffer[1]);
offset = addShortTLV((short) 0x5F52, historicalBytes, buffer, offset);
buffer[offset++] = (byte) 0x73;
buffer[offset++] = (byte) 0x81; // We need a two byte length.
short oldpos = offset;
offset = addDiscretionaryDataObjects(buffer, (short) (offset + 1));
buffer[oldpos] = (byte) (offset - oldpos - 1);
break;
case 0x73:
offset = addDiscretionaryDataObjects(buffer, (short) 0);
break;
case 0x7A:
offset = addShortTLV((short) 0x93, signatureCounter, buffer, offset);
break;
case 0x93:
offset = Util.arrayCopyNonAtomic(signatureCounter, (short) 0, buffer, (short) 0,
(short) signatureCounter.length);
break;
case 0xC0:
offset = Util.arrayCopyNonAtomic(extendedCapabilities, (short) 0, buffer, (short) 0,
(short) extendedCapabilities.length);
break;
case 0xC1:
case 0xC2:
case 0xC3:
offset = Util.arrayCopyNonAtomic(algorithmAttributes, (short) 0, buffer, (short) 0,
(short) algorithmAttributes.length);
break;
case 0xC4:
offset = getPWStatusBytes(buffer, (short) 0);
break;
case 0xC5:
offset = Util.arrayCopyNonAtomic(fingerprints, (short) 0, buffer, (short) 0,
(short) fingerprints.length);
break;
case 0xC6:
offset = Util.arrayCopyNonAtomic(caFingerprints, (short) 0, buffer, (short) 0,
(short) caFingerprints.length);
break;
case 0xCD:
offset = Util.arrayCopyNonAtomic(generationDates, (short) 0, buffer, (short) 0,
(short) generationDates.length);
break;
// Private use objects.
case 0x101:
offset = Util.arrayCopyNonAtomic(privateDO1, (short) 1, buffer, (short) 0,
(short) (privateDO1[0] & 0xFF));
break;
case 0x102:
offset = Util.arrayCopyNonAtomic(privateDO2, (short) 1, buffer, (short) 0,
(short) (privateDO2[0] & 0xFF));
break;
case 0x103:
if (!pins[PIN_INDEX_PW1].isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
offset = Util.arrayCopyNonAtomic(privateDO3, (short) 1, buffer, (short) 0,
(short) (privateDO3[0] & 0xFF));
break;
case 0x104:
if (!pins[PIN_INDEX_PW3].isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
offset = Util.arrayCopyNonAtomic(privateDO4, (short) 1, buffer, (short) 0,
(short) (privateDO4[0] & 0xFF));
break;
default:
ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
}
apdu.setOutgoingAndSend((short) 0, offset);
}
/**
* Append the composite 'Discretionary Data Objetcs' DO (Tag = 0x73)
*
* @param out the destination buffer.
* @param offset the offset at which the data should be written.
* @return the next byte that should be written
*/
private short addDiscretionaryDataObjects(byte[] out, short offset) {
offset = addShortTLV((short) 0xC0, extendedCapabilities, out, offset);
for (short i = (short) 0; i < (short) 3; ++i) {
offset = addShortTLV((short) (0xC1 + i), algorithmAttributes, out, offset);
}
out[offset++] = (byte) 0xC4;
out[offset++] = 7;
getPWStatusBytes(out, offset);
offset += 7;
offset = addShortTLV((short) 0xC5, fingerprints, out, offset);
offset = addShortTLV((short) 0xC6, caFingerprints, out, offset);
offset = addShortTLV((short) 0xCD, generationDates, out, offset);
return offset;
}
/**
* Return the PIN statuses as needed by the C4 DO.
*
* @param out the destination buffer.
* @param offset the offset at which the data should be written.
* @return the next byte that should be written
*/
private short getPWStatusBytes(byte[] out, short offset) {
// 0: 00 PW1 valid for one command, 01 PW1 valid for several commands.
out[offset++] = pinValidForMultipleSignatures;
// 1: max length of PW1.
out[offset++] = MAX_PIN_LENGTH;
// 2: max length of Reseting Code.
out[offset++] = MAX_PIN_LENGTH;
// 3: max length of PW3
out[offset++] = MAX_PIN_LENGTH;
// 4, 5, 6: current try counts for PW1, RC, PW3
out[offset++] = pins[PIN_INDEX_PW1].getTriesRemaining();
if (pinLength[PIN_INDEX_RC] > 0) {
out[offset++] = pins[PIN_INDEX_RC].getTriesRemaining();
} else {
out[offset++] = 0;
}
out[offset++] = pins[PIN_INDEX_PW3].getTriesRemaining();
return offset;
}
/**
* Append a fixed length byte buffer as a TLV
*
* @param src the source data, must be <= 127 bytes
* @param out the destination for the tlv
* @param offset the offset into out
* @return the offset to the next byte to be written
*/
private short addShortTLV(short tag, byte[] src, byte[] out, short offset) {
if ((short) (tag & (short) 0xFF00) != (short) 0) {
Util.setShort(out, offset, tag);
offset += 2;
} else {
out[offset++] = (byte) tag;
}
out[offset++] = (byte) src.length;
return Util.arrayCopyNonAtomic(src, (short) 0, out, offset, (short) src.length);
}
/**
* The PUT DATA APDU implementation.
*/
private void putData(APDU apdu) {
byte[] buffer = apdu.getBuffer();
short length = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
short tag = Util.getShort(buffer, ISO7816.OFFSET_P1);
switch (tag) {
// Private use objects.
case 0x101:
storeVariableLength(apdu, privateDO1, PIN_INDEX_PW1);
break;
case 0x102:
storeVariableLength(apdu, privateDO2, PIN_INDEX_PW3);
break;
case 0x103:
storeVariableLength(apdu, privateDO3, PIN_INDEX_PW1);
break;
case 0x104:
storeVariableLength(apdu, privateDO4, PIN_INDEX_PW3);
break;
case 0x5B:
storeVariableLength(apdu, name, PIN_INDEX_PW3);
break;
case 0x5E:
storeVariableLength(apdu, loginData, PIN_INDEX_PW3);
break;
case 0x5F2D:
storeVariableLength(apdu, language, PIN_INDEX_PW3);
break;
case 0x5F35:
storeFixedLength(apdu, sex, (short) 0, (short) 1);
break;
case 0x5F50:
storeVariableLength(apdu, url, PIN_INDEX_PW3);
break;
case 0xC4:
if (!pins[PIN_INDEX_PW3].isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
if (length < (short) 1 || length > (short) 8 || length != apdu.setIncomingAndReceive()) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
pinValidForMultipleSignatures = buffer[ISO7816.OFFSET_CDATA];
break;
case 0xC7:
case 0xC8:
case 0xC9:
storeFixedLength(apdu, fingerprints, (short) (20 * (tag - 0xC7)), (short) 20);
break;
case 0xCA:
case 0xCB:
case 0xCC:
storeFixedLength(apdu, caFingerprints, (short) (20 * (tag - 0xCA)), (short) 20);
break;
case 0xCE:
case 0xCF:
case 0xD0:
storeFixedLength(apdu, generationDates, (short) (4 * (tag - 0xCE)), (short) 4);
break;
case 0xD3:
storeVariableLength(apdu, buffer, PIN_INDEX_PW3);
// Reset code must be zero or 8 - MAX_PIN_LENGTH.
if (length > MAX_PIN_LENGTH || (length != (byte) 0 && length < (byte) 8)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
updatePIN(PIN_INDEX_RC, buffer, (short) 1, buffer[0]);
break;
default:
ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
}
}
/**
* Store the incoming APDU data in a fixed buffer, the first byte will contain the data length.
*
* @param pin_type indicates which PIN should be checked.
*/
void storeVariableLength(APDU apdu, byte[] destination, short pin_type) {
byte[] buffer = apdu.getBuffer();
// When writing DOs, PW1 really means PW1 submitted as PW2.
if (!pins[pin_type].isValidated() ||
((pin_type == PIN_INDEX_PW1) && !pinSubmitted[1])) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
short length = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
if ((short) (length + 1) > destination.length || length > (short) 255 ||
apdu.setIncomingAndReceive() != length) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
JCSystem.beginTransaction();
destination[0] = (byte) length;
Util.arrayCopy(buffer, ISO7816.OFFSET_CDATA, destination, (short) 1, length);
JCSystem.commitTransaction();
}
/**
* Store the fixed length incoming APDU data in a buffer. If the APDU data length is less than the
* maximum length, the data will be padded with zeroes.
*/
void storeFixedLength(APDU apdu, byte[] destination, short offset, short maximum_length) {
byte[] buffer = apdu.getBuffer();
// When writing DOs, PW1 really means PW1 submitted as PW2.
if (!pins[PIN_INDEX_PW3].isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
short length = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
if (length > maximum_length || apdu.setIncomingAndReceive() != length) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
Util.arrayCopy(buffer, ISO7816.OFFSET_CDATA, destination, offset, length);
if (maximum_length > length) {
Util.arrayFillNonAtomic(destination, (short) (offset + length),
(short) (maximum_length - length), (byte) 0);
}
}
// Initialize a key part and return the offset to the next byte that should be used.
private short addKeyPart(byte part, byte[] data, short offset, KeyPair key) {
short size = Util.getShort(commandChainingBuffer, TEMP_PUT_KEY_EXPECTED_CHUNK_SIZE);
short nextSize = RSA_KEY_HALF_LENGTH_BYTES;
switch (part) {
case KEY_PART_E:
((RSAPublicKey) key.getPublic()).setExponent(data, offset, size);
break;
case KEY_PART_PRIME_P:
((RSAPrivateCrtKey) key.getPrivate()).setP(data, offset, size);
break;
case KEY_PART_PRIME_Q:
((RSAPrivateCrtKey) key.getPrivate()).setQ(data, offset, size);
break;
case KEY_PART_PARAM_PQ:
((RSAPrivateCrtKey) key.getPrivate()).setPQ(data, offset, size);
break;
case KEY_PART_PARAM_DP1:
((RSAPrivateCrtKey) key.getPrivate()).setDP1(data, offset, size);
break;
case KEY_PART_PARAM_DQ1:
((RSAPrivateCrtKey) key.getPrivate()).setDQ1(data, offset, size);
nextSize = RSA_KEY_LENGTH_BYTES;
break;
case KEY_PART_N:
((RSAPublicKey) key.getPublic()).setModulus(data, offset, RSA_KEY_LENGTH_BYTES);
if (!key.getPrivate().isInitialized() ||
!key.getPublic().isInitialized()) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
return (short) (offset + RSA_KEY_LENGTH_BYTES);
}
Util.setShort(commandChainingBuffer, TEMP_PUT_KEY_EXPECTED_CHUNK_SIZE, nextSize);
return (short) (offset + size);
}
private KeyPair getKey(byte type) {
switch (type) {
case (byte) 0xB6:
return signatureKey;
case (byte) 0xB8:
return confidentialityKey;
case (byte) 0xA4:
return authenticationKey;
}
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
return signatureKey; // Make the compiler happy.
}
private short addKeyPart(byte[] data, short offset) {
return addKeyPart(commandChainingBuffer[TEMP_PUT_KEY_KEY_CHUNK], data, offset,
getKey(commandChainingBuffer[TEMP_PUT_KEY_KEY_TYPE]));
}
/**
* PUT KEY APDU implementation.
*/
private void putKey(APDU apdu) {
byte[] buffer = apdu.getBuffer();
if (!pins[PIN_INDEX_PW3].isValidated()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
short endPos = (short) (ISO7816.OFFSET_CDATA + apdu.setIncomingAndReceive());
short pos;
boolean firstCommand = (commandChainingBuffer[TEMP_INS] != buffer[ISO7816.OFFSET_INS]);
// Mark the command chain as bad so it stays in this state in case of exception.
commandChainingBuffer[TEMP_INS] = 0;
if (firstCommand) {
// First command, we expect at least all the TLV template and the public exponent.
if (endPos < (short) (ISO7816.OFFSET_CDATA + expectedRSAKeyImportFormat.length)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
Util.arrayFillNonAtomic(commandChainingBuffer, (short) 0,
(short) commandChainingBuffer.length, (byte) 0);
commandChainingBuffer[TEMP_PUT_KEY_KEY_TYPE] = buffer[(short) (ISO7816.OFFSET_CDATA + 4)];
commandChainingBuffer[TEMP_PUT_KEY_EXPECTED_CHUNK_SIZE] = 0;
// Copy the exponent length, all the other sizes are fixed by the RSA key length.
byte eLength = commandChainingBuffer[(short) (TEMP_PUT_KEY_EXPECTED_CHUNK_SIZE + 1)] =
buffer[(short) (ISO7816.OFFSET_CDATA + 10)];
// Adjust the APDU passed as if it had CRT = 0, length of e = 0 so we can compare
// to our canned value.
buffer[(short) (ISO7816.OFFSET_CDATA + 4)] = 0;
buffer[(short) (ISO7816.OFFSET_CDATA + 10)] = 0;
// Adjust Tag 4D length
Util.setShort(buffer, (short) (ISO7816.OFFSET_CDATA + 2),
(short) (Util.getShort(buffer, (short) (ISO7816.OFFSET_CDATA + 2)) - eLength));
// Adjust Tag 5F48 length
Util.setShort(buffer, (short) (ISO7816.OFFSET_CDATA + 33),
(short) (Util.getShort(buffer, (short) (ISO7816.OFFSET_CDATA + 33)) - eLength));
if (Util.arrayCompare(buffer, ISO7816.OFFSET_CDATA, expectedRSAKeyImportFormat, (short) 0,
(short) expectedRSAKeyImportFormat.length) != 0) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
pos = (short) (ISO7816.OFFSET_CDATA + expectedRSAKeyImportFormat.length);
// Clear the existing key.
JCSystem.beginTransaction();
KeyPair key = getKey(commandChainingBuffer[TEMP_PUT_KEY_KEY_TYPE]);
key.getPrivate().clearKey();
key.getPublic().clearKey();
if (commandChainingBuffer[TEMP_PUT_KEY_KEY_TYPE] == (byte) 0xB6) {
// Reset the signature counter.
signatureCounter[0] = (byte) 0;
signatureCounter[1] = (byte) 0;
signatureCounter[2] = (byte) 0;
}
JCSystem.commitTransaction();
commandChainingBuffer[TEMP_PUT_KEY_KEY_CHUNK] = KEY_PART_E;
} else {
// Chained command.
pos = ISO7816.OFFSET_CDATA;
}
short accumulatorLength = Util.getShort(commandChainingBuffer, TEMP_PUT_KEY_ACCUMULATOR_LENGTH);
for (; commandChainingBuffer[TEMP_PUT_KEY_KEY_CHUNK] <= KEY_PART_N; ) {
short left = (short) (endPos - pos);
short sizeNeeded = Util.getShort(commandChainingBuffer, TEMP_PUT_KEY_EXPECTED_CHUNK_SIZE);
if (accumulatorLength != 0) {
// There was a partial chunk left from the previous APDU, add the new data to get a
// complete key part in commandChainingBuffer.
short bytesToAdd;
if ((short) (left + accumulatorLength) > sizeNeeded) {
bytesToAdd = (short) (sizeNeeded - accumulatorLength);
} else {
bytesToAdd = left;
}
Util.arrayCopyNonAtomic(buffer, pos, commandChainingBuffer,
(short) (accumulatorLength + TEMP_PUT_KEY_ACCUMULATOR),
bytesToAdd);
accumulatorLength += bytesToAdd;
if (accumulatorLength == sizeNeeded) {
addKeyPart(commandChainingBuffer, TEMP_PUT_KEY_ACCUMULATOR);
Util.setShort(commandChainingBuffer, TEMP_PUT_KEY_ACCUMULATOR_LENGTH, (short) 0);
accumulatorLength = 0;
} else {
// We need an extra APDU with more data.
Util.setShort(commandChainingBuffer, TEMP_PUT_KEY_ACCUMULATOR_LENGTH, accumulatorLength);
commandChainingBuffer[TEMP_INS] = CMD_PUT_KEY;
return;
}
pos += bytesToAdd;
} else {
if (left < sizeNeeded) {
// Not enough data, store what we have.
Util.arrayCopyNonAtomic(buffer, pos, commandChainingBuffer, TEMP_PUT_KEY_ACCUMULATOR,
left);
Util.setShort(commandChainingBuffer, TEMP_PUT_KEY_ACCUMULATOR_LENGTH, left);
// We need an extra APDU with more data.
commandChainingBuffer[TEMP_INS] = CMD_PUT_KEY;
return;
} else {
pos = addKeyPart(buffer, pos);
}
}
commandChainingBuffer[TEMP_PUT_KEY_KEY_CHUNK]++;
}
// The loop only terminates when the whole key is imported and verified.
}
/**
* GET CHALLENGE APDU implementation.
*/
private void getChallenge(APDU apdu) {
byte[] buffer = apdu.getBuffer();
short length = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
if (length == (short) 0) {
length = (short) 256;
}
randomData.generateData(buffer, (short) 0, length);
apdu.setOutgoingAndSend((short) 0, length);
}
private void computeSignature(APDU apdu) {
byte[] buffer = apdu.getBuffer();
short length = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
// Make sure that DigestInfo is <= 40% of the RSA key length.
if ((short) (length * 4) > (short) (RSA_KEY_LENGTH_BYTES * 10) ||
apdu.setIncomingAndReceive() != length) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
if (!pinSubmitted[PIN_INDEX_PW1] || !pins[PIN_INDEX_PW1].isValidated()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
if (!signatureKey.getPrivate().isInitialized()) {
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
}
if (pinValidForMultipleSignatures == (byte) 0) {
pinSubmitted[PIN_INDEX_PW1] = false;
}
cipherRSA.init(signatureKey.getPrivate(), Cipher.MODE_ENCRYPT);
cipherRSA.doFinal(buffer, ISO7816.OFFSET_CDATA, length, buffer, (short) 0);
JCSystem.beginTransaction();
if (signatureCounter[2] != (byte) 0xFF) {
signatureCounter[2] = (byte) ((signatureCounter[2] & 0xFF) + 1);
} else {
signatureCounter[2] = 0;
if (signatureCounter[1] != (byte) 0xFF) {
signatureCounter[1] = (byte) ((signatureCounter[1] & 0xFF) + 1);
} else if (signatureCounter[0] != (byte) 0xFF) {
signatureCounter[1] = 0;
signatureCounter[0] = (byte) ((signatureCounter[0] & 0xFF) + 1);
} else {
JCSystem.abortTransaction();
ISOException.throwIt(ISO7816.SW_FILE_FULL);
}
}
JCSystem.commitTransaction();
apdu.setOutgoingAndSend((short) 0, RSA_KEY_LENGTH_BYTES);
}
private void decrypt(APDU apdu) {
byte[] buffer = apdu.getBuffer();
// PW1 with 0x82
if (!pins[PIN_INDEX_PW1].isValidated() || !pinSubmitted[1]) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
if (!confidentialityKey.getPrivate().isInitialized()) {
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
}
boolean firstCommand = (commandChainingBuffer[TEMP_INS] != buffer[ISO7816.OFFSET_INS]);
// Mark the command chain as bad so it stays in this state in case of exception.
short len = apdu.setIncomingAndReceive();
if (len < 1) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
if (firstCommand) {
Util.arrayCopyNonAtomic(buffer, (short) (ISO7816.OFFSET_CDATA + 1), commandChainingBuffer,
TEMP_GET_RESPONSE_DATA, (short) (len - 1));
len = (short) (len - 1);
} else {
short existing = Util.getShort(commandChainingBuffer, TEMP_GET_RESPONSE_LENGTH);
if ((short) (len + existing) > RSA_KEY_LENGTH_BYTES) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
Util.arrayCopyNonAtomic(buffer, ISO7816.OFFSET_CDATA, commandChainingBuffer,
(short) (TEMP_GET_RESPONSE_DATA + existing), len);
len += existing;
}
if (len < RSA_KEY_LENGTH_BYTES) {
commandChainingBuffer[TEMP_INS] = CMD_COMPUTE_PSO;
Util.setShort(commandChainingBuffer, TEMP_GET_RESPONSE_LENGTH, len);
return; // For compatibily with GPG
}
// We have enough bytes to decrypt.
cipherRSA.init(confidentialityKey.getPrivate(), Cipher.MODE_DECRYPT);
len = cipherRSA.doFinal(commandChainingBuffer, TEMP_GET_RESPONSE_DATA, RSA_KEY_LENGTH_BYTES,
buffer, (short) 0);
apdu.setOutgoingAndSend((short) 0, len);
}
private void internalAuthenticate(APDU apdu) {
byte[] buffer = apdu.getBuffer();
// PW1 with 0x82
if (!pins[PIN_INDEX_PW1].isValidated() || !pinSubmitted[1]) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
short len = apdu.setIncomingAndReceive();
if (len > (short) 102 || len != (buffer[ISO7816.OFFSET_LC] & 0xFF)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
if (!authenticationKey.getPrivate().isInitialized()) {
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
}
cipherRSA.init(authenticationKey.getPrivate(), Cipher.MODE_ENCRYPT);
cipherRSA.doFinal(buffer, ISO7816.OFFSET_CDATA, len, buffer, (short) 0);
apdu.setOutgoingAndSend((short) 0, RSA_KEY_LENGTH_BYTES);
}
/**
* GENERATE KEY APDU implementation.
*/
private void generateAsymetricKey(APDU apdu) {
byte[] buffer = apdu.getBuffer();
if (apdu.setIncomingAndReceive() != 2) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
KeyPair key = getKey(buffer[ISO7816.OFFSET_CDATA]);
if (buffer[ISO7816.OFFSET_P1] == (byte) 0x81) {
if (!(key.getPublic()).isInitialized()) {
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
}
} else {
if (!pins[PIN_INDEX_PW3].isValidated()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
JCSystem.beginTransaction();
key.genKeyPair();
if (buffer[ISO7816.OFFSET_CDATA] == (byte)0xB6) {
signatureCounter[0] = 0;
signatureCounter[1] = 0;
signatureCounter[2] = 0;
}
JCSystem.commitTransaction();
}
// Send the TLV data and public exponent using the APDU buffer.
buffer[ISO7816.OFFSET_CDATA] = 0x7F;
buffer[(short) (ISO7816.OFFSET_CDATA + 1)] = 0x49;
buffer[(short) (ISO7816.OFFSET_CDATA + 2)] = (byte) 0x82;
buffer[(short) (ISO7816.OFFSET_CDATA + 5)] = (byte) 0x82;
short length = ((RSAPublicKey) key.getPublic()).getExponent(
buffer, (short) (ISO7816.OFFSET_CDATA + 7));
buffer[(short) (ISO7816.OFFSET_CDATA + 6)] = (byte) length;
short pos = (short) (ISO7816.OFFSET_CDATA + 7 + length);
buffer[pos] = (byte) 0x81;
buffer[(short) (pos + 1)] = (byte) 0x82;
Util.setShort(buffer, (short) (pos + 2), RSA_KEY_LENGTH_BYTES);
Util.setShort(buffer, (short) (ISO7816.OFFSET_CDATA + 3),
(short) (pos + RSA_KEY_LENGTH_BYTES - ISO7816.OFFSET_CDATA - 1));
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, (short) (length + 11));
// And the modulus using get response.
Util.setShort(commandChainingBuffer, TEMP_GET_RESPONSE_LENGTH, RSA_KEY_LENGTH_BYTES);
((RSAPublicKey) key.getPublic()).getModulus(commandChainingBuffer, TEMP_GET_RESPONSE_DATA);
// Skip leading zero byte.
if (commandChainingBuffer[TEMP_GET_RESPONSE_DATA] == 0) {
Util.setShort(commandChainingBuffer, TEMP_GET_RESPONSE_OFFSET,
(short) (TEMP_GET_RESPONSE_DATA + 1));
} else {
Util.setShort(commandChainingBuffer, TEMP_GET_RESPONSE_OFFSET, TEMP_GET_RESPONSE_DATA);
}
commandChainingBuffer[TEMP_INS] = buffer[ISO7816.OFFSET_INS];
ISOException.throwIt(ISO7816.SW_BYTES_REMAINING_00);
}
/**
* Terminate DF is only valid if PW1 and PW3 are blocked.
* @param apdu
*/
private void terminateDF(APDU apdu) {
byte[] buffer = apdu.getBuffer();
if (buffer[ISO7816.OFFSET_LC] != 0) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
if (pins[PIN_INDEX_PW1].getTriesRemaining() > 0 ||
pins[PIN_INDEX_PW3].getTriesRemaining() > 0) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
terminated = true;
}
/**
* ACTIVATE FILE does nothing if the card is not in the 'initialization' state (really if it
* hasn't been terminated).
* @param apdu
*/
private void activateFile(APDU apdu) {
if (!terminated) {
return;
}
// Since the card will not do anything until we clear the terminated bool we can erase the
// data without using transactions (and most likely the transaction buffer would not be
// large enough.
signatureKey.getPrivate().clearKey();
signatureKey.getPublic().clearKey();
confidentialityKey.getPrivate().clearKey();
confidentialityKey.getPublic().clearKey();
authenticationKey.getPrivate().clearKey();
authenticationKey.getPublic().clearKey();
pins[PIN_INDEX_PW1].update(defaultPIN, (short) 0, MIN_PIN1_LENGTH);
pinLength[PIN_INDEX_PW1] = MIN_PIN1_LENGTH;
pins[PIN_INDEX_PW3].update(defaultPIN, (short) 0, MIN_PIN3_LENGTH);
pinLength[PIN_INDEX_PW3] = MIN_PIN3_LENGTH;
// The resetting code is disabled by default.
pinLength[PIN_INDEX_RC] = 0;
Util.arrayFillNonAtomic(privateDO1, (short)0, (short)privateDO1.length, (byte)0);
Util.arrayFillNonAtomic(privateDO2, (short)0, (short)privateDO2.length, (byte)0);
Util.arrayFillNonAtomic(privateDO3, (short)0, (short)privateDO3.length, (byte)0);
Util.arrayFillNonAtomic(privateDO4, (short)0, (short)privateDO4.length, (byte)0);
Util.arrayFillNonAtomic(loginData, (short)0, (short)loginData.length, (byte)0);
Util.arrayFillNonAtomic(url, (short)0, (short)url.length, (byte)0);
Util.arrayFillNonAtomic(name, (short)0, (short)name.length, (byte)0);
Util.arrayFillNonAtomic(language, (short)0, (short)language.length, (byte)0);
sex[0] = 0;
Util.arrayFillNonAtomic(fingerprints, (short)0, (short)fingerprints.length, (byte)0);
Util.arrayFillNonAtomic(caFingerprints, (short)0, (short)caFingerprints.length, (byte)0);
Util.arrayFillNonAtomic(generationDates, (short)0, (short)generationDates.length, (byte)0);
Util.arrayFillNonAtomic(signatureCounter, (short)0, (short)signatureCounter.length, (byte)0);
pinValidForMultipleSignatures = (byte) 0;
terminated = false;
}
}