/*
* IsoApplet: A Java Card PKI applet aimiing for ISO 7816 compliance.
* Copyright (C) 2014 Philip Wendland (wendlandphilip@gmail.com)
*
* 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, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.pwendland.javacard.pki.isoapplet;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.APDU;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.framework.OwnerPIN;
import javacard.security.KeyBuilder;
import javacard.security.KeyPair;
import javacard.security.Key;
import javacard.security.RSAPublicKey;
import javacard.security.RSAPrivateCrtKey;
import javacard.security.ECKey;
import javacard.security.ECPublicKey;
import javacard.security.ECPrivateKey;
import javacardx.crypto.Cipher;
import javacardx.apdu.ExtendedLength;
import javacard.security.CryptoException;
import javacard.security.Signature;
import javacard.security.RandomData;
/**
* \brief The IsoApplet class.
*
* This applet has a filesystem and accepts relevant ISO 7816 instructions.
* Access control is forced through a PIN and a PUK. The PUK is optional
* (Set PUK_MUST_BE_SET). Security Operations are being processed directly in
* this class. Only private keys are stored as Key-objects. Only security
* operations with private keys can be performed (decrypt with RSA, sign with RSA,
* sign with ECDSA).
*
* \author Philip Wendland
*/
public class IsoApplet extends Applet implements ExtendedLength {
/* API Version */
public static final byte API_VERSION_MAJOR = (byte) 0x00;
public static final byte API_VERSION_MINOR = (byte) 0x06;
/* Card-specific configuration */
public static final boolean DEF_EXT_APDU = false;
public static final boolean DEF_PRIVATE_KEY_IMPORT_ALLOWED = false;
/* ISO constants not in the "ISO7816" interface */
// File system related INS:
public static final byte INS_CREATE_FILE = (byte) 0xE0;
public static final byte INS_UPDATE_BINARY = (byte) 0xD6;
public static final byte INS_READ_BINARY = (byte) 0xB0;
public static final byte INS_DELETE_FILE = (byte) 0xE4;
// Other INS:
public static final byte INS_VERIFY = (byte) 0x20;
public static final byte INS_CHANGE_REFERENCE_DATA = (byte) 0x24;
public static final byte INS_GENERATE_ASYMMETRIC_KEYPAIR = (byte) 0x46;
public static final byte INS_RESET_RETRY_COUNTER = (byte) 0x2C;
public static final byte INS_MANAGE_SECURITY_ENVIRONMENT = (byte) 0x22;
public static final byte INS_PERFORM_SECURITY_OPERATION = (byte) 0x2A;
public static final byte INS_GET_RESPONSE = (byte) 0xC0;
public static final byte INS_PUT_DATA = (byte) 0xDB;
public static final byte INS_GET_CHALLENGE = (byte) 0x84;
// Status words:
public static final short SW_PIN_TRIES_REMAINING = 0x63C0; // See ISO 7816-4 section 7.5.1
public static final short SW_COMMAND_NOT_ALLOWED_GENERAL = 0x6900;
/* PIN, PUK and key realted constants */
// PIN:
private static final byte PIN_MAX_TRIES = 3;
private static final byte PIN_MIN_LENGTH = 4;
private static final byte PIN_MAX_LENGTH = 16;
// PUK:
private static final boolean PUK_MUST_BE_SET = false;
private static final byte PUK_MAX_TRIES = 5;
private static final byte PUK_LENGTH = 16;
// Keys:
private static final short KEY_MAX_COUNT = 16;
private static final byte ALG_GEN_RSA_2048 = (byte) 0xF3;
private static final byte ALG_RSA_PAD_PKCS1 = (byte) 0x11;
private static final byte ALG_GEN_EC = (byte) 0xEC;
private static final byte ALG_ECDSA_SHA1 = (byte) 0x21;
private static final short LENGTH_EC_FP_224 = 224;
private static final short LENGTH_EC_FP_256 = 256;
private static final short LENGTH_EC_FP_320 = 320;
private static final short LENGTH_EC_FP_384 = 384;
private static final short LENGTH_EC_FP_512 = 512;
private static final short LENGTH_EC_FP_521 = 521;
/* Card/Applet lifecycle states */
private static final byte STATE_CREATION = (byte) 0x00; // No restrictions, PUK not set yet.
private static final byte STATE_INITIALISATION = (byte) 0x01; // PUK set, PIN not set yet. PUK may not be changed.
private static final byte STATE_OPERATIONAL_ACTIVATED = (byte) 0x05; // PIN is set, data is secured.
private static final byte STATE_OPERATIONAL_DEACTIVATED = (byte) 0x04; // Applet usage is deactivated. (Unused at the moment.)
private static final byte STATE_TERMINATED = (byte) 0x0C; // Applet usage is terminated. (Unused at the moment.)
private static final byte API_FEATURE_EXT_APDU = (byte) 0x01;
private static final byte API_FEATURE_SECURE_RANDOM = (byte) 0x02;
private static final byte API_FEATURE_ECC = (byte) 0x04;
/* Other constants */
// "ram_buf" is used for:
// * GET RESPONSE (caching for response APDUs):
// - GENERATE ASYMMETRIC KEYPAIR: RSA 2048 bit and ECC >= 256 bit public key information.
// * Command Chaining or extended APDUs (caching of command APDU data):
// - DECIPHER (RSA 2048 bit).
// - GENERATE ASYMMETRIC KEYPAIR: ECC curve parameters if large (> 256 bit) prime fields are used.
// - PUT DATA: RSA and ECC private key import.
private static final short RAM_BUF_SIZE = (short) 660;
// "ram_chaining_cache" is used for:
// - Caching of the amount of bytes remainung.
// - Caching of the current send position.
// - Determining how many operations had previously been performed in the chain (re-use CURRENT_POS)
// - Caching of the current INS (Only one chain at a time, for one specific instruction).
private static final short RAM_CHAINING_CACHE_SIZE = (short) 4;
private static final short RAM_CHAINING_CACHE_OFFSET_BYTES_REMAINING = (short) 0;
private static final short RAM_CHAINING_CACHE_OFFSET_CURRENT_POS = (short) 1;
private static final short RAM_CHAINING_CACHE_OFFSET_CURRENT_INS = (short) 2;
private static final short RAM_CHAINING_CACHE_OFFSET_CURRENT_P1P2 = (short) 3;
/* Member variables: */
private byte state;
private IsoFileSystem fs = null;
private OwnerPIN pin = null;
private OwnerPIN puk = null;
private byte[] currentAlgorithmRef = null;
private short[] currentPrivateKeyRef = null;
private Key[] keys = null;
private byte[] ram_buf = null;
private short[] ram_chaining_cache = null;
private Cipher rsaPkcs1Cipher = null;
private Signature ecdsaSignature = null;
private RandomData randomData = null;
private byte api_features;
/**
* \brief Installs this applet.
*
* \param bArray
* the array containing installation parameters
* \param bOffset
* the starting offset in bArray
* \param bLength
* the length in bytes of the parameter data in bArray
*/
public static void install(byte[] bArray, short bOffset, byte bLength) {
new IsoApplet();
}
/**
* \brief Only this class's install method should create the applet object.
*/
protected IsoApplet() {
api_features = 0;
pin = new OwnerPIN(PIN_MAX_TRIES, PIN_MAX_LENGTH);
puk = new OwnerPIN(PUK_MAX_TRIES, PUK_LENGTH);
fs = new IsoFileSystem();
ram_buf = JCSystem.makeTransientByteArray(RAM_BUF_SIZE, JCSystem.CLEAR_ON_DESELECT);
ram_chaining_cache = JCSystem.makeTransientShortArray(RAM_CHAINING_CACHE_SIZE, JCSystem.CLEAR_ON_DESELECT);
currentAlgorithmRef = JCSystem.makeTransientByteArray((short)1, JCSystem.CLEAR_ON_DESELECT);
currentPrivateKeyRef = JCSystem.makeTransientShortArray((short)1, JCSystem.CLEAR_ON_DESELECT);
keys = new Key[KEY_MAX_COUNT];
rsaPkcs1Cipher = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false);
try {
ecdsaSignature = Signature.getInstance(Signature.ALG_ECDSA_SHA, false);
api_features |= API_FEATURE_ECC;
} catch (CryptoException e) {
if(e.getReason() == CryptoException.NO_SUCH_ALGORITHM) {
/* Few Java Cards do not support ECDSA at all.
* We should not throw an exception in this cases
* as this would prevent installation. */
ecdsaSignature = null;
api_features &= ~API_FEATURE_ECC;
} else {
throw e;
}
}
try {
randomData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
api_features |= API_FEATURE_SECURE_RANDOM;
} catch (CryptoException e) {
if(e.getReason() == CryptoException.NO_SUCH_ALGORITHM) {
randomData = null;
api_features &= ~API_FEATURE_SECURE_RANDOM;
} else {
throw e;
}
}
if(DEF_EXT_APDU) {
api_features |= API_FEATURE_EXT_APDU;
}
state = STATE_CREATION;
register();
}
/**
* \brief This method is called whenever the applet is being deselected.
*/
@Override
public void deselect() {
pin.reset();
puk.reset();
fs.setUserAuthenticated(false);
}
/**
* \brief This method is called whenever the applet is being selected.
*/
@Override
public boolean select() {
if(state == STATE_CREATION
|| state == STATE_INITIALISATION) {
fs.setUserAuthenticated(true);
} else {
fs.setUserAuthenticated(false);
}
// Reset file selection state
fs.selectFile(null);
return true;
}
/**
* \brief Processes an incoming APDU.
*
* \see APDU.
*
* \param apdu The incoming APDU.
*/
@Override
public void process(APDU apdu) {
byte buffer[] = apdu.getBuffer();
byte ins = buffer[ISO7816.OFFSET_INS];
// Return the API version if we are being selected.
// Format:
// - byte 0: Major version
// - byte 1: Minor version
// - byte 2: Feature bitmap (used to distinguish between applet features)
if(selectingApplet()) {
buffer[0] = API_VERSION_MAJOR;
buffer[1] = API_VERSION_MINOR;
buffer[2] = api_features;
apdu.setOutgoingAndSend((short) 0, (short) 3);
return;
}
// No secure messaging at the moment
if(apdu.isSecureMessagingCLA()) {
ISOException.throwIt(ISO7816.SW_SECURE_MESSAGING_NOT_SUPPORTED);
}
// Command chaining checks
if(ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_INS] != 0 || isCommandChainingCLA(apdu)) {
short p1p2 = Util.getShort(buffer, ISO7816.OFFSET_P1);
/*
* Command chaining only for:
* - PERFORM SECURITY OPERATION
* - GENERATE ASYMMETRIC KEYKAIR
* - PUT DATA
* when not using extended APDUs.
*/
if( DEF_EXT_APDU ||
(ins != INS_PERFORM_SECURITY_OPERATION
&& ins != INS_GENERATE_ASYMMETRIC_KEYPAIR
&& ins != INS_PUT_DATA)) {
ISOException.throwIt(ISO7816.SW_COMMAND_CHAINING_NOT_SUPPORTED);
}
if(ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_INS] == 0
&& ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_P1P2] == 0) {
/* A new chain is starting - set the current INS and P1P2. */
if(ins == 0) {
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_INS] = ins;
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_P1P2] = p1p2;
} else if(ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_INS] != ins
|| ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_P1P2] != p1p2) {
/* The current chain is not yet completed,
* but an apdu not part of the chain had been received. */
ISOException.throwIt(SW_COMMAND_NOT_ALLOWED_GENERAL);
} else if(!isCommandChainingCLA(apdu)) {
/* A chain is ending, set the current INS and P1P2 to zero to indicate that. */
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_INS] = 0;
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_P1P2] = 0;
}
}
// If the card expects a GET RESPONSE, no other operation should be requested.
if(ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_BYTES_REMAINING] > 0 && ins != INS_GET_RESPONSE) {
ISOException.throwIt(SW_COMMAND_NOT_ALLOWED_GENERAL);
}
if(apdu.isISOInterindustryCLA()) {
switch (ins) {
case ISO7816.INS_SELECT:
fs.processSelectFile(apdu);
break;
case INS_READ_BINARY:
fs.processReadBinary(apdu);
break;
case INS_VERIFY:
processVerify(apdu);
break;
case INS_MANAGE_SECURITY_ENVIRONMENT:
processManageSecurityEnvironment(apdu);
break;
case INS_PERFORM_SECURITY_OPERATION:
processPerformSecurityOperation(apdu);
break;
case INS_CREATE_FILE:
fs.processCreateFile(apdu);
break;
case INS_UPDATE_BINARY:
fs.processUpdateBinary(apdu);
break;
case INS_CHANGE_REFERENCE_DATA:
processChangeReferenceData(apdu);
break;
case INS_DELETE_FILE:
fs.processDeleteFile(apdu);
break;
case INS_GENERATE_ASYMMETRIC_KEYPAIR:
processGenerateAsymmetricKeypair(apdu);
break;
case INS_RESET_RETRY_COUNTER:
processResetRetryCounter(apdu);
break;
case INS_GET_RESPONSE:
processGetResponse(apdu);
break;
case INS_PUT_DATA:
processPutData(apdu);
break;
case INS_GET_CHALLENGE:
processGetChallenge(apdu);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
} // switch
} else {
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
}
/**
* \brief Parse the apdu's CLA byte to determine if the apdu is the first or second-last part of a chain.
*
* The Java Card API version 2.2.2 has a similar method (APDU.isCommandChainingCLA()), but tests have shown
* that some smartcard platform's implementations are wrong (not according to the JC API specification),
* specifically, but not limited to, JCOP 2.4.1 R3.
*
* \param apdu The apdu.
*
* \return true If the apdu is the [1;last[ part of a command chain,
* false if there is no chain or the apdu is the last part of the chain.
*/
static boolean isCommandChainingCLA(APDU apdu) {
byte[] buf = apdu.getBuffer();
return ((byte)(buf[0] & (byte)0x10) == (byte)0x10);
}
/**
* \brief Process the VERIFY apdu (INS = 20).
*
* This apdu is used to verify a PIN and authenticate the user. A counter is used
* to limit unsuccessful tries (i.e. brute force attacks).
*
* \param apdu The apdu.
*
* \throw ISOException SW_INCORRECT_P1P2, ISO7816.SW_WRONG_LENGTH, SW_PIN_TRIES_REMAINING.
*/
private void processVerify(APDU apdu) throws ISOException {
byte[] buf = apdu.getBuffer();
short offset_cdata;
short lc;
// P1P2 0001 only at the moment. (key-reference 01 = PIN)
if(buf[ISO7816.OFFSET_P1] != 0x00 || buf[ISO7816.OFFSET_P2] != 0x01) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// Bytes received must be Lc.
lc = apdu.setIncomingAndReceive();
if(lc != apdu.getIncomingLength()) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
offset_cdata = apdu.getOffsetCdata();
// Lc might be 0, in this case the caller checks if verification is required.
if((lc > 0 && (lc < PIN_MIN_LENGTH) || lc > PIN_MAX_LENGTH)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Caller asks if verification is needed.
if(lc == 0
&& state != STATE_CREATION
&& state != STATE_INITIALISATION) {
// Verification required, return remaining tries.
ISOException.throwIt((short)(SW_PIN_TRIES_REMAINING | pin.getTriesRemaining()));
} else if(lc == 0
&& (state == STATE_CREATION
|| state == STATE_INITIALISATION)) {
// No verification required.
ISOException.throwIt(ISO7816.SW_NO_ERROR);
}
// Pad the PIN if not done by caller, so no garbage from the APDU will be part of the PIN.
Util.arrayFillNonAtomic(buf, (short)(offset_cdata + lc), (short)(PIN_MAX_LENGTH - lc), (byte) 0x00);
// Check the PIN.
if(!pin.check(buf, offset_cdata, PIN_MAX_LENGTH)) {
fs.setUserAuthenticated(false);
ISOException.throwIt((short)(SW_PIN_TRIES_REMAINING | pin.getTriesRemaining()));
} else {
fs.setUserAuthenticated(true);
}
}
/**
* \brief Process the CHANGE REFERENCE DATA apdu (INS = 24).
*
* If the state is STATE_CREATION, we can set the PUK without verification.
* The state will advance to STATE_INITIALISATION (i.e. the PUK must be set before the PIN).
* In a "later" state the user must authenticate himself to be able to change the PIN.
*
* \param apdu The apdu.
*
* \throws ISOException SW_INCORRECT_P1P2, ISO7816.SW_WRONG_LENGTH, SW_PIN_TRIES_REMAINING.
*/
private void processChangeReferenceData(APDU apdu) throws ISOException {
byte[] buf = apdu.getBuffer();
byte p1 = buf[ISO7816.OFFSET_P1];
byte p2 = buf[ISO7816.OFFSET_P2];
short lc;
short offset_cdata;
// Bytes received must be Lc.
lc = apdu.setIncomingAndReceive();
if(lc != apdu.getIncomingLength()) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
offset_cdata = apdu.getOffsetCdata();
if(state == STATE_CREATION) {
// We _set_ the PUK or the PIN. If we set the PIN in this state, no PUK will be present on the card, ever.
// Key reference must be 02 (PUK) or 01 (PIN). P1 must be 01 because no verification data should be present in this state.
if(p1 != 0x01 || (p2 != 0x02 && p2 != 0x01) ) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
if(p2 == 0x02) {
// We set the PUK and advance to STATE_INITIALISATION.
// Check length.
if(lc != PUK_LENGTH) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Set PUK
puk.update(buf, offset_cdata, (byte)lc);
puk.resetAndUnblock();
state = STATE_INITIALISATION;
} else if(p2 == 0x01) {
// We are supposed to set the PIN right away - no PUK will be set, ever.
// This might me forbidden because of security policies:
if(PUK_MUST_BE_SET) {
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
}
// Check length.
if(lc > PIN_MAX_LENGTH || lc < PIN_MIN_LENGTH) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Pad the PIN upon creation, so no garbage from the APDU will be part of the PIN.
Util.arrayFillNonAtomic(buf, (short)(offset_cdata + lc), (short)(PIN_MAX_LENGTH - lc), (byte) 0x00);
// Set PIN.
pin.update(buf, offset_cdata, PIN_MAX_LENGTH);
pin.resetAndUnblock();
state = STATE_OPERATIONAL_ACTIVATED;
}
} else if(state == STATE_INITIALISATION) {
// We _set_ the PIN (P2=01).
if(buf[ISO7816.OFFSET_P1] != 0x01 || buf[ISO7816.OFFSET_P2] != 0x01) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// Check the PIN length.
if(lc > PIN_MAX_LENGTH || lc < PIN_MIN_LENGTH) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Pad the PIN upon creation, so no garbage from the APDU will be part of the PIN.
Util.arrayFillNonAtomic(buf, (short)(offset_cdata + lc), (short)(PIN_MAX_LENGTH - lc), (byte) 0x00);
// Set PIN.
pin.update(buf, offset_cdata, PIN_MAX_LENGTH);
pin.resetAndUnblock();
state = STATE_OPERATIONAL_ACTIVATED;
} else {
// We _change_ the PIN (P2=01).
// P1 must be 00 as the old PIN must be provided, followed by new PIN without delimitation.
// Both PINs must already padded (otherwise we can not tell when the old PIN ends.)
if(buf[ISO7816.OFFSET_P1] != 0x00 || buf[ISO7816.OFFSET_P2] != 0x01) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// Check PIN lengths: PINs must be padded, i.e. Lc must be 2*PIN_MAX_LENGTH.
if(lc != (short)(2*PIN_MAX_LENGTH)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Check the old PIN.
if(!pin.check(buf, offset_cdata, PIN_MAX_LENGTH)) {
ISOException.throwIt((short)(SW_PIN_TRIES_REMAINING | pin.getTriesRemaining()));
}
// UPDATE PIN
pin.update(buf, (short) (offset_cdata+PIN_MAX_LENGTH), PIN_MAX_LENGTH);
}// end if(state == STATE_CREATION)
}// end processChangeReferenceData()
/**
* \brief Process the RESET RETRY COUNTER apdu (INS = 2C).
*
* This is used to unblock the PIN with the PUK and set a new PIN value.
*
* \param apdu The RESET RETRY COUNTER apdu.
*
* \throw ISOException SW_COMMAND_NOT_ALLOWED, ISO7816.SW_WRONG_LENGTH, SW_INCORRECT_P1P2,
* SW_PIN_TRIES_REMAINING.
*/
public void processResetRetryCounter(APDU apdu) throws ISOException {
byte[] buf = apdu.getBuffer();
byte p1 = buf[ISO7816.OFFSET_P1];
byte p2 = buf[ISO7816.OFFSET_P2];
short lc;
short offset_cdata;
if(state != STATE_OPERATIONAL_ACTIVATED) {
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
}
// Bytes received must be Lc.
lc = apdu.setIncomingAndReceive();
if(lc != apdu.getIncomingLength()) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
offset_cdata = apdu.getOffsetCdata();
// Length of data field.
if(lc < (short)(PUK_LENGTH + PIN_MIN_LENGTH)
|| lc > (short)(PUK_LENGTH + PIN_MAX_LENGTH)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// We expect the PUK followed by a new PIN.
if(p1 != (byte) 0x00 || p2 != (byte) 0x01) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// Check the PUK.
if(!puk.check(buf, offset_cdata, PUK_LENGTH)) {
ISOException.throwIt((short)(SW_PIN_TRIES_REMAINING | puk.getTriesRemaining()));
}
// If we're here, the PUK was correct.
// Pad the new PIN, if not done by caller. We don't want any gargabe from the APDU buffer to be part of the new PIN.
Util.arrayFillNonAtomic(buf, (short)(offset_cdata + lc), (short)(PUK_LENGTH + PIN_MAX_LENGTH - lc), (byte) 0x00);
// Set the PIN.
pin.update(buf, (short)(offset_cdata+PUK_LENGTH), PIN_MAX_LENGTH);
pin.resetAndUnblock();
}
/**
* \brief Initialize an EC key with the curve parameters from buf.
*
* \param buf The buffer containing the EC curve parameters. It must be TLV with the following format:
* 81 - prime
* 82 - coefficient A
* 83 - coefficient B
* 84 - base point G
* 85 - order
* 87 - cofactor
*
* \param bOff The offset at where the first entry is located.
*
* \param bLen The remaining length of buf.
*
* \param key The EC key to initialize.
*
* \throw NotFoundException Parts of the data needed to fully initialize
* the key were missing.
*
* \throw InvalidArgumentsException The ASN.1 sequence was malformatted.
*/
private void initEcParams(byte[] buf, short bOff, short bLen, ECKey key) throws NotFoundException, InvalidArgumentsException {
short pos = bOff;
short len;
/* Search for the prime */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte) 0x81);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
key.setFieldFP(buf, pos, len); // "p"
/* Search for coefficient A */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte) 0x82);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
key.setA(buf, pos, len);
/* Search for coefficient B */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte) 0x83);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
key.setB(buf, pos, len);
/* Search for base point G */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte) 0x84);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
key.setG(buf, pos, len); // G(x,y)
/* Search for order */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte) 0x85);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
key.setR(buf, pos, len); // Order of G - "q"
/* Search for cofactor */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte) 0x87);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
if(len == 2) {
key.setK(Util.getShort(buf, pos));
} else if(len == 1) {
key.setK(buf[pos]);
} else {
throw InvalidArgumentsException.getInstance();
}
}
/**
* \brief Process the GENERATE ASYMMETRIC KEY PAIR apdu (INS = 46).
*
* A MANAGE SECURITY ENVIRONMENT must have succeeded earlier to set parameters for key
* generation.
*
* \param apdu The apdu.
*
* \throw ISOException SW_WRONG_LENGTH, SW_INCORRECT_P1P2, SW_CONDITIONS_NOT_SATISFIED,
* SW_SECURITY_STATUS_NOT_SATISFIED.
*/
public void processGenerateAsymmetricKeypair(APDU apdu) throws ISOException {
byte[] buf = apdu.getBuffer();
byte p1 = buf[ISO7816.OFFSET_P1];
byte p2 = buf[ISO7816.OFFSET_P2];
short privKeyRef = currentPrivateKeyRef[0];
short lc;
KeyPair kp = null;
ECPrivateKey privKey = null;
ECPublicKey pubKey = null;
if( ! pin.isValidated() ) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
switch(currentAlgorithmRef[0]) {
case ALG_GEN_RSA_2048:
if(p1 != (byte) 0x42 || p2 != (byte) 0x00) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
// Command chaining might be used for ECC, but not for RSA.
if(isCommandChainingCLA(apdu)) {
ISOException.throwIt(ISO7816.SW_COMMAND_CHAINING_NOT_SUPPORTED);
}
try {
kp = new KeyPair(KeyPair.ALG_RSA_CRT, KeyBuilder.LENGTH_RSA_2048);
} catch(CryptoException e) {
if(e.getReason() == CryptoException.NO_SUCH_ALGORITHM) {
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
kp.genKeyPair();
if(keys[privKeyRef] != null) {
keys[privKeyRef].clearKey();
}
keys[privKeyRef] = kp.getPrivate();
if(JCSystem.isObjectDeletionSupported()) {
JCSystem.requestObjectDeletion();
}
// Return pubkey. See ISO7816-8 table 3.
sendRSAPublicKey(apdu, ((RSAPublicKey)(kp.getPublic())));
break;
case ALG_GEN_EC:
if((p1 != (byte) 0x00) || p2 != (byte) 0x00) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
lc = doChainingOrExtAPDU(apdu);
/* Search for prime */
short pos = 0;
try {
pos = UtilTLV.findTag(ram_buf, (short) 0, lc, (byte) 0x81);
} catch (NotFoundException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
} catch (InvalidArgumentsException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
pos++;
short len = 0;
try {
len = UtilTLV.decodeLengthField(ram_buf, pos);
} catch (InvalidArgumentsException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
// Try to calculate field length frome prime length.
short field_len = getEcFpFieldLength(len);
// Try to instantiate key objects of that length
try {
privKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, field_len, false);
pubKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, field_len, false);
kp = new KeyPair(pubKey, privKey);
} catch(CryptoException e) {
if(e.getReason() == CryptoException.NO_SUCH_ALGORITHM) {
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
try {
initEcParams(ram_buf, (short) 0, lc, pubKey);
initEcParams(ram_buf, (short) 0, lc, privKey);
} catch (NotFoundException e) {
// Parts of the data needed to initialize the EC keys were missing.
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
} catch (InvalidArgumentsException e) {
// Malformatted ASN.1.
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
try {
kp.genKeyPair();
} catch (CryptoException e) {
if(e.getReason() == CryptoException.ILLEGAL_VALUE) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
}
if(keys[privKeyRef] != null) {
keys[privKeyRef].clearKey();
}
keys[privKeyRef] = privKey;
if(JCSystem.isObjectDeletionSupported()) {
JCSystem.requestObjectDeletion();
}
Util.arrayFillNonAtomic(ram_buf, (short)0, RAM_BUF_SIZE, (byte)0x00);
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] = 0;
// Return pubkey. See ISO7816-8 table 3.
try {
sendECPublicKey(apdu, pubKey);
} catch (InvalidArgumentsException e) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
} catch (NotEnoughSpaceException e) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
break;
default:
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
}
/**
* \brief Encode a 2048 bit RSAPublicKey according to ISO7816-8 table 3 and send it as a response,
* using an extended APDU.
*
* \see ISO7816-8 table 3.
*
* \param apdu The apdu to answer. setOutgoing() must not be called already.
*
* \param key The RSAPublicKey to send.
* Can be null for the secound part if there is no support for extended apdus.
*/
private void sendRSAPublicKey(APDU apdu, RSAPublicKey key) {
short le = apdu.setOutgoing();
short pos = 0;
ram_buf[pos++] = (byte) 0x7F; // Interindustry template for nesting one set of public key data objects.
ram_buf[pos++] = (byte) 0x49; // "
ram_buf[pos++] = (byte) 0x82; // Length field: 3 Bytes.
ram_buf[pos++] = (byte) 0x01; // Length : 265 Bytes.
ram_buf[pos++] = (byte) 0x09; // "
ram_buf[pos++] = (byte) 0x81; // RSA public key modulus tag.
ram_buf[pos++] = (byte) 0x82; // Length field: 3 Bytes.
ram_buf[pos++] = (byte) 0x01; // Length: 256 bytes.
ram_buf[pos++] = (byte) 0x00; // "
pos += key.getModulus(ram_buf, pos);
ram_buf[pos++] = (byte) 0x82; // RSA public key exponent tag.
ram_buf[pos++] = (byte) 0x03; // Length: 3 Bytes.
pos += key.getExponent(ram_buf, pos);
sendLargeData(apdu, (short)0, pos);
}
/**
* \brief Process the GET RESPONSE APDU (INS=C0).
*
* If there is content available in ram_buf that could not be sent in the last operation,
* the host should use this APDU to get the data. The data is cached in ram_buf.
*
* \param apdu The GET RESPONSE apdu.
*
* \throw ISOException SW_CONDITIONS_NOT_SATISFIED, SW_UNKNOWN, SW_CORRECT_LENGTH.
*/
private void processGetResponse(APDU apdu) {
byte[] buf = apdu.getBuffer();
short le = apdu.setOutgoing();
if( ! pin.isValidated() ) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
if(ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_BYTES_REMAINING] <= (short) 0) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
short expectedLe = ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_BYTES_REMAINING] > 256 ?
256 : ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_BYTES_REMAINING];
if(le != expectedLe) {
ISOException.throwIt( (short)(ISO7816.SW_CORRECT_LENGTH_00 | expectedLe) );
}
sendLargeData(apdu, ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS],
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_BYTES_REMAINING]);
}
/**
* \brief Send the data from ram_buf, using either extended APDUs or GET RESPONSE.
*
* \param apdu The APDU object, in STATE_OUTGOING state.
*
* \param pos The position in ram_buf at where the data begins
*
* \param len The length of the data to be sent. If zero, 9000 will be
* returned
*/
private void sendLargeData(APDU apdu, short pos, short len) {
if(len <= 0) {
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_BYTES_REMAINING] = 0;
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] = 0;
ISOException.throwIt(ISO7816.SW_NO_ERROR);
}
if((short)(pos + len) > RAM_BUF_SIZE) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
if(DEF_EXT_APDU) {
apdu.setOutgoingLength(len);
apdu.sendBytesLong(ram_buf, pos, len);
} else {
// We have 256 Bytes send-capacity per APDU.
// Send directly from ram_buf, then prepare for chaining.
short sendLen = len > 256 ? 256 : len;
apdu.setOutgoingLength(sendLen);
apdu.sendBytesLong(ram_buf, pos, sendLen);
short bytesLeft = (short)(len - sendLen);
if(bytesLeft > 0) {
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_BYTES_REMAINING] = bytesLeft;
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] = (short)(pos + sendLen);
short getRespLen = bytesLeft > 256 ? 256 : bytesLeft;
ISOException.throwIt( (short)(ISO7816.SW_BYTES_REMAINING_00 | getRespLen) );
// The next part of the data is now in ram_buf, metadata is in ram_chaining_cache.
// It can be fetched by the host via GET RESPONSE.
} else {
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_BYTES_REMAINING] = 0;
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] = 0;
ISOException.throwIt(ISO7816.SW_NO_ERROR);
}
}
}
/**
* \brief Encode a ECPublicKey according to ISO7816-8 table 3 and send it as a response,
* using an extended APDU.
*
* \see ISO7816-8 table 3.
*
* \param The apdu to answer. setOutgoing() must not be called already.
*
* \throw InvalidArgumentsException Field length of the EC key provided can not be handled.
*
* \throw NotEnoughSpaceException ram_buf is too small to contain the EC key to send.
*/
private void sendECPublicKey(APDU apdu, ECPublicKey key) throws InvalidArgumentsException, NotEnoughSpaceException {
short pos = 0;
final short field_bytes = (key.getSize()%8 == 0) ? (short)(key.getSize()/8) : (short)(key.getSize()/8+1);
short len, r;
// Return pubkey. See ISO7816-8 table 3.
len = (short)(7 // We have: 7 tags,
+ (key.getSize() >= LENGTH_EC_FP_512 ? 9 : 7) // 7 length fields, of which 2 are 2 byte fields when using 521 bit curves,
+ 8 * field_bytes + 4); // 4 * field_len + 2 * 2 field_len + cofactor (2 bytes) + 2 * uncompressed tag
pos += UtilTLV.writeTagAndLen((short)0x7F49, len, ram_buf, pos);
// Prime - "P"
len = field_bytes;
pos += UtilTLV.writeTagAndLen((short)0x81, len, ram_buf, pos);
r = key.getField(ram_buf, pos);
if(r < len) {
// If the parameter has fewer bytes than the field length, we fill
// the MSB's with zeroes.
Util.arrayCopyNonAtomic(ram_buf, pos, ram_buf, (short)(pos+len-r), (short)(len-r));
Util.arrayFillNonAtomic(ram_buf, pos, r, (byte)0x00);
} else if (r > len) {
throw InvalidArgumentsException.getInstance();
}
pos += len;
// First coefficient - "A"
len = field_bytes;
pos += UtilTLV.writeTagAndLen((short)0x82, len, ram_buf, pos);
r = key.getA(ram_buf, pos);
if(r < len) {
Util.arrayCopyNonAtomic(ram_buf, pos, ram_buf, (short)(pos+len-r), (short)(len-r));
Util.arrayFillNonAtomic(ram_buf, pos, r, (byte)0x00);
} else if (r > len) {
throw InvalidArgumentsException.getInstance();
}
pos += len;
// Second coefficient - "B"
len = field_bytes;
pos += UtilTLV.writeTagAndLen((short)0x83, len, ram_buf, pos);
r = key.getB(ram_buf, pos);
if(r < len) {
Util.arrayCopyNonAtomic(ram_buf, pos, ram_buf, (short)(pos+len-r), (short)(len-r));
Util.arrayFillNonAtomic(ram_buf, pos, r, (byte)0x00);
} else if (r > len) {
throw InvalidArgumentsException.getInstance();
}
pos += len;
// Generator - "PB"
len = (short)(1 + 2 * field_bytes);
pos += UtilTLV.writeTagAndLen((short)0x84, len, ram_buf, pos);
r = key.getG(ram_buf, pos);
if(r < len) {
Util.arrayCopyNonAtomic(ram_buf, pos, ram_buf, (short)(pos+len-r), (short)(len-r));
Util.arrayFillNonAtomic(ram_buf, pos, r, (byte)0x00);
} else if (r > len) {
throw InvalidArgumentsException.getInstance();
}
pos += len;
// Order - "Q"
len = field_bytes;
pos += UtilTLV.writeTagAndLen((short)0x85, len, ram_buf, pos);
r = key.getR(ram_buf, pos);
if(r < len) {
Util.arrayCopyNonAtomic(ram_buf, pos, ram_buf, (short)(pos+len-r), (short)(len-r));
Util.arrayFillNonAtomic(ram_buf, pos, r, (byte)0x00);
} else if (r > len) {
throw InvalidArgumentsException.getInstance();
}
pos += len;
// Public key - "PP"
len = (short)(1 + 2 * field_bytes);
pos += UtilTLV.writeTagAndLen((short)0x86, len, ram_buf, pos);
r = key.getW(ram_buf, pos);
if(r < len) {
Util.arrayCopyNonAtomic(ram_buf, pos, ram_buf, (short)(pos+len-r), (short)(len-r));
Util.arrayFillNonAtomic(ram_buf, pos, r, (byte)0x00);
} else if (r > len) {
throw InvalidArgumentsException.getInstance();
}
pos += len;
// Cofactor
len = 2;
pos += UtilTLV.writeTagAndLen((short)0x87, len, ram_buf, pos);
Util.setShort(ram_buf, pos, key.getK());
pos += 2;
// ram_buf now contains the complete public key.
apdu.setOutgoing();
sendLargeData(apdu, (short)0, pos);
}
/**
* \brief Process the MANAGE SECURITY ENVIRONMENT apdu (INS = 22).
*
* \attention Only SET is supported. RESTORE will reset the security environment.
* The security environment will be cleared upon deselection of the applet.
* STOREing and ERASEing of security environments is not supported.
*
* \param apdu The apdu.
*
* \throw ISOException SW_SECURITY_STATUS_NOT_SATISFIED, SW_WRONG_LENGTH, SW_DATA_INVALID,
* SW_INCORRECT_P1P2, SW_FUNC_NOT_SUPPORTED, SW_COMMAND_NOT_ALLOWED.
*/
public void processManageSecurityEnvironment(APDU apdu) throws ISOException {
byte[] buf = apdu.getBuffer();
byte p1 = buf[ISO7816.OFFSET_P1];
byte p2 = buf[ISO7816.OFFSET_P2];
short lc;
short pos = 0;
short offset_cdata;
byte algRef = 0;
short privKeyRef = -1;
// Check PIN
if( ! pin.isValidated() ) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
// Bytes received must be Lc.
lc = apdu.setIncomingAndReceive();
if(lc != apdu.getIncomingLength()) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
offset_cdata = apdu.getOffsetCdata();
// TLV structure consistency check.
if( ! UtilTLV.isTLVconsistent(buf, offset_cdata, lc)) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
/* Extract data: */
switch(p1) {
case (byte) 0x41:
// SET Computation, decipherment, internal authentication and key agreement.
// Algorithm reference.
try {
pos = UtilTLV.findTag(buf, offset_cdata, (byte) lc, (byte) 0x80);
} catch (NotFoundException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
} catch (InvalidArgumentsException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
if(buf[++pos] != (byte) 0x01) { // Length must be 1.
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
// Set the current algorithm reference.
algRef = buf[++pos];
// Private key reference (Index in keys[]-array).
try {
pos = UtilTLV.findTag(buf, offset_cdata, (byte) lc, (byte) 0x84);
} catch (NotFoundException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
} catch (InvalidArgumentsException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
if(buf[++pos] != (byte) 0x01 // Length: must be 1 - only one key reference (byte) provided.
|| buf[++pos] >= KEY_MAX_COUNT) { // Value: KEY_MAX_COUNT may not be exceeded. Valid key references are from 0..KEY_MAX_COUNT.
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
privKeyRef = buf[pos];
break;
case (byte) 0xF3:
// RESTORE // Set sec env constants to default values.
algRef = 0;
privKeyRef = -1;
break;
case (byte) 0x81: // SET Verification, encipherment, external authentication and key agreement.
case (byte) 0xF4: // ERASE
case (byte) 0xF2: // STORE
default:
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
/* Perform checks (Note: Nothing is updated yet) */
switch(p2) {
case (byte) 0x00:
/* *****************
* Key generation. *
*******************/
if(algRef != ALG_GEN_EC
&& algRef != ALG_GEN_RSA_2048) {
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
// Check: We need a private key reference.
if(privKeyRef < 0) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
if(algRef == ALG_GEN_EC && ecdsaSignature == null) {
// There are cards that do not support ECDSA at all.
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
break;
case (byte) 0xB6:
/* ***********
* Signature *
*************/
// Check: We need a private key reference.
if(privKeyRef == -1) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
// Supported signature algorithms: RSA with PKCS1 padding, ECDSA with raw input.
if(algRef == ALG_RSA_PAD_PKCS1) {
// Key reference must point to a RSA private key.
if(keys[privKeyRef].getType() != KeyBuilder.TYPE_RSA_CRT_PRIVATE) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
} else if(algRef == ALG_ECDSA_SHA1) {
// Key reference must point to a EC private key.
if(keys[privKeyRef].getType() != KeyBuilder.TYPE_EC_FP_PRIVATE) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
if(ecdsaSignature == null) {
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
} else {
// No known or supported signature algorithm.
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
break;
case (byte) 0xB8:
/* ************
* Decryption *
**************/
// For decryption, only RSA with PKCS1 padding is supported.
if(algRef == ALG_RSA_PAD_PKCS1) {
// Check: We need a private key reference.
if(privKeyRef == -1) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
// Key reference must point to a RSA private key.
if(keys[privKeyRef].getType() != KeyBuilder.TYPE_RSA_CRT_PRIVATE) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
} else {
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
break;
default:
/* Unsupported or unknown P2. */
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
// Finally, update the security environment.
JCSystem.beginTransaction();
currentAlgorithmRef[0] = algRef;
currentPrivateKeyRef[0] = privKeyRef;
JCSystem.commitTransaction();
}
/**
* \brief Process the PERFORM SECURITY OPERATION apdu (INS=2A).
*
* This operation is used for cryptographic operations
* (Computation of digital signatures, decrypting.).
*
* \param apdu The PERFORM SECURITY OPERATION apdu.
*
* \throw ISOException SW_SECURITY_STATUS_NOT_SATISFIED, SW_INCORRECT_P1P2 and
* the ones from computeDigitalSignature() and decipher().
*/
private void processPerformSecurityOperation(APDU apdu) throws ISOException {
byte[] buf = apdu.getBuffer();
byte p1 = buf[ISO7816.OFFSET_P1];
byte p2 = buf[ISO7816.OFFSET_P2];
if( ! pin.isValidated() ) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
if(p1 == (byte) 0x9E && p2 == (byte) 0x9A) {
computeDigitalSignature(apdu);
} else if(p1 == (byte) 0x80 && p2 == (byte) 0x86) {
decipher(apdu);
} else {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
}
/**
* \brief Decipher the data from the apdu using the private key referenced by
* an earlier MANAGE SECURITY ENVIRONMENT apdu.
*
* \param apdu The PERFORM SECURITY OPERATION apdu with P1=80 and P2=86.
*
* \throw ISOException SW_CONDITIONS_NOT_SATISFIED, SW_WRONG_LENGTH and
* SW_WRONG_DATA
*/
private void decipher(APDU apdu) {
short offset_cdata;
short lc;
short decLen = -1;
lc = doChainingOrExtAPDU(apdu);
offset_cdata = 0;
// Padding indicator should be "No further indication".
if(ram_buf[offset_cdata] != (byte) 0x00) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
switch(currentAlgorithmRef[0]) {
case ALG_RSA_PAD_PKCS1:
// Get the key - it must be an RSA private key,
// checks have been done in MANAGE SECURITY ENVIRONMENT.
RSAPrivateCrtKey theKey = (RSAPrivateCrtKey) keys[currentPrivateKeyRef[0]];
// Check the length of the cipher.
// Note: The first byte of the data field is the padding indicator
// and therefor not part of the ciphertext.
if((short)(lc-1) != (short)(theKey.getSize() / 8)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
rsaPkcs1Cipher.init(theKey, Cipher.MODE_DECRYPT);
try {
decLen = rsaPkcs1Cipher.doFinal(ram_buf, (short)(offset_cdata+1), (short)(lc-1),
apdu.getBuffer(), (short) 0);
} catch(CryptoException e) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
// We have to send at most 256 bytes. A short APDU can handle that - only one send operation neccessary.
apdu.setOutgoingAndSend((short)0, decLen);
break;
default:
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
}
/**
* \brief Compute a digital signature of the data from the apdu
* using the private key referenced by an earlier
* MANAGE SECURITY ENVIRONMENT apdu.
*
* \attention The apdu should contain a hash, not raw data for RSA keys.
* PKCS1 padding will be applied if neccessary.
*
* \param apdu The PERFORM SECURITY OPERATION apdu with P1=9E and P2=9A.
*
* \throw ISOException SW_CONDITIONS_NOT_SATISFIED, SW_WRONG_LENGTH
* and SW_UNKNOWN.
*/
private void computeDigitalSignature(APDU apdu) throws ISOException {
byte[] buf = apdu.getBuffer();
short offset_cdata;
short lc;
short sigLen = 0;
switch(currentAlgorithmRef[0]) {
case ALG_RSA_PAD_PKCS1:
// Receive.
// Bytes received must be Lc.
lc = apdu.setIncomingAndReceive();
if(lc != apdu.getIncomingLength()) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
offset_cdata = apdu.getOffsetCdata();
// RSA signature operation.
RSAPrivateCrtKey rsaKey = (RSAPrivateCrtKey) keys[currentPrivateKeyRef[0]];
if(lc > (short) 247) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
rsaPkcs1Cipher.init(rsaKey, Cipher.MODE_ENCRYPT);
sigLen = rsaPkcs1Cipher.doFinal(buf, offset_cdata, lc, ram_buf, (short)0);
if(sigLen != 256) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
// A single short APDU can handle 256 bytes - only one send operation neccessary.
short le = apdu.setOutgoing();
if(le < sigLen) {
ISOException.throwIt(ISO7816.SW_CORRECT_LENGTH_00);
}
apdu.setOutgoingLength(sigLen);
apdu.sendBytesLong(ram_buf, (short) 0, sigLen);
break;
case ALG_ECDSA_SHA1:
// Get the key - it must be a EC private key,
// checks have been done in MANAGE SECURITY ENVIRONMENT.
ECPrivateKey ecKey = (ECPrivateKey) keys[currentPrivateKeyRef[0]];
// Initialisation should be done when:
// - No command chaining is performed at all.
// - Command chaining is performed and this is the first apdu in the chain.
if(ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] == (short) 0) {
ecdsaSignature.init(ecKey, Signature.MODE_SIGN);
if(isCommandChainingCLA(apdu)) {
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] = (short) 1;
}
}
short recvLen = apdu.setIncomingAndReceive();
offset_cdata = apdu.getOffsetCdata();
// Receive data. For extended APDUs, the data is received piecewise
// and aggregated in the hash. When using short APDUs, command
// chaining is performed.
while (recvLen > 0) {
ecdsaSignature.update(buf, offset_cdata, recvLen);
recvLen = apdu.receiveBytes(offset_cdata);
}
if(!isCommandChainingCLA(apdu)) {
sigLen = ecdsaSignature.sign(buf, (short)0, (short)0, buf, (short) 0);
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] = (short) 0;
apdu.setOutgoingAndSend((short) 0, sigLen);
} else {
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS]++;
}
break;
default:
// Wrong/unknown algorithm.
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
}
/**
* \brief Process the PUT DATA apdu (INS=DB).
*
* PUT DATA is currently used for private key import.
*
* \throw ISOException SW_SECURITY_STATUS_NOT_SATISFIED, SW_INCORRECT_P1P2
*/
private void processPutData(APDU apdu) throws ISOException {
byte[] buf = apdu.getBuffer();
byte p1 = buf[ISO7816.OFFSET_P1];
byte p2 = buf[ISO7816.OFFSET_P2];
if( ! pin.isValidated() ) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
if(p1 == (byte) 0x3F && p2 == (byte) 0xFF) {
if( ! DEF_PRIVATE_KEY_IMPORT_ALLOWED) {
ISOException.throwIt(SW_COMMAND_NOT_ALLOWED_GENERAL);
}
importPrivateKey(apdu);
} else {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
}
/**
* \brief Upload and import a usable private key.
*
* A preceeding MANAGE SECURITY ENVIRONMENT is necessary (like with key-generation).
* The format of the data (of the apdu) must be BER-TLV,
* Tag 7F48 ("T-L pair to indicate a private key data object") for RSA or tag 0xC1
* for EC keys, containing the point Q.
*
* For RSA, the data to be submitted is quite large. It is required that command chaining is
* used for the submission of the private key. One chunk of the chain (one apdu) must contain
* exactly one tag (0x92 - 0x96). The first apdu of the chain must contain the outer tag (7F48).
*
* \throw ISOException SW_SECURITY_STATUS_NOT_SATISFIED, SW_DATA_INVALID, SW_WRONG_LENGTH.
*/
private void importPrivateKey(APDU apdu) throws ISOException {
short recvLen;
short offset = 0;
short len = 0;
if( ! pin.isValidated() ) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
switch(currentAlgorithmRef[0]) {
case ALG_GEN_RSA_2048:
// RSA key import.
// This ensures that all the data is located in ram_buf, beginning at zero.
recvLen = doChainingOrExtAPDU(apdu);
// Parse the outer tag.
if(ram_buf[offset] != (byte)0x7F || ram_buf[(short)(offset+1)] != (byte)0x48) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
offset += 2;
try {
len = UtilTLV.decodeLengthField(ram_buf, offset);
offset += UtilTLV.getLengthFieldLength(len);
} catch (InvalidArgumentsException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
if(len != (short)(recvLen - offset)) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
if( ! UtilTLV.isTLVconsistent(ram_buf, offset, len) ) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
// Import the key from the value field of the outer tag.
try {
importRSAkey(ram_buf, offset, len);
} catch (InvalidArgumentsException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
} catch (NotFoundException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
break;
case ALG_GEN_EC:
// EC key import.
// This ensures that all the data is located in ram_buf, beginning at zero.
recvLen = doChainingOrExtAPDU(apdu);
// Parse the outer tag.
if( ram_buf[offset++] != (byte) 0xE0 ) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
try {
len = UtilTLV.decodeLengthField(ram_buf, offset);
offset += UtilTLV.getLengthFieldLength(len);
} catch (InvalidArgumentsException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
if(len != (short)(recvLen - offset)) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
if( ! UtilTLV.isTLVconsistent(ram_buf, offset, len) ) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
// Import the key from the value field of the outer tag.
try {
importECkey(ram_buf, offset, len);
} catch (InvalidArgumentsException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
} catch (NotFoundException e) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
break;
default:
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
}
/**
* \brief Receive the data sent by chaining or extended apdus and store it in ram_buf.
*
* This is a convienience method if large data has to be accumulated using command chaining
* or extended apdus. The apdu must be in the INITIAL state, i.e. setIncomingAndReceive()
* might not have been called already.
*
* \param apdu The apdu object in the initial state.
*
* \throw ISOException SW_WRONG_LENGTH
*/
private short doChainingOrExtAPDU(APDU apdu) throws ISOException {
byte[] buf = apdu.getBuffer();
short recvLen = apdu.setIncomingAndReceive();
short offset_cdata = apdu.getOffsetCdata();
// Receive data (short or extended).
while (recvLen > 0) {
if((short)(ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] + recvLen) > RAM_BUF_SIZE) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
Util.arrayCopyNonAtomic(buf, offset_cdata, ram_buf, ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS], recvLen);
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] += recvLen;
recvLen = apdu.receiveBytes(offset_cdata);
}
if(isCommandChainingCLA(apdu)) {
// We are still in the middle of a chain, otherwise there would not have been a chaining CLA.
// Make sure the caller does not forget to return as the data should only be interpreted
// when the chain is completed (when using this method).
ISOException.throwIt(ISO7816.SW_NO_ERROR);
return (short)0;
} else {
// Chain has ended or no chaining.
// We did receive the data, everything is fine.
// Reset the current position in ram_buf.
recvLen = (short) (recvLen + ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS]);
ram_chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] = 0;
return recvLen;
}
}
/**
* \brief Update fields of the current private RSA key.
*
* A MANAGE SECURITY ENVIRONMENT must have preceeded, setting the current
* algorithm reference to ALG_GEN_RSA_2048.
* This method creates a new instance of the current private key,
* depending on the current algorithn reference.
*
* \param buf The buffer containing the information to update the private key
* field with. The format must be TLV-encoded with the tags:
* - 0x92: p
* - 0x93: q
* - 0x94: 1/q mod p
* - 0x95: d mod (p-1)
* - 0x96: d mod (q-1)
* Note: This buffer will be filled with 0x00 after the operation
* had been performed.
*
* \param bOff The offset at which the data in buf starts.
*
* \param bLen The length of the data in buf.
*
* \throw ISOException SW_CONDITION_NOT_SATISFIED The current algorithm reference does not match.
* SW_FUNC_NOT_SUPPORTED Algorithm is unsupported by the card.
* SW_UNKNOWN Unknown error.
*
* \throw NotFoundException The buffer does not contain all the information needed to import a private key.
*
* \throw InvalidArgumentsException The buffer is malformatted.
*/
private void importRSAkey(byte[] buf, short bOff, short bLen) throws ISOException, NotFoundException, InvalidArgumentsException {
short pos = 0;
short len;
RSAPrivateCrtKey rsaPrKey = null;
if(currentAlgorithmRef[0] != ALG_GEN_RSA_2048) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
try {
rsaPrKey = (RSAPrivateCrtKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_CRT_PRIVATE, KeyBuilder.LENGTH_RSA_2048, false);
} catch(CryptoException e) {
if(e.getReason() == CryptoException.NO_SUCH_ALGORITHM) {
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
ISOException.throwIt(ISO7816.SW_UNKNOWN);
return;
}
if( ! UtilTLV.isTLVconsistent(buf, bOff, bLen)) {
throw InvalidArgumentsException.getInstance();
}
/* Set P */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte)0x92);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
rsaPrKey.setP(buf, pos, len);
/* Set Q */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte)0x93);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
rsaPrKey.setQ(buf, pos, len);
/* Set PQ (1/q mod p) */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte)0x94);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
rsaPrKey.setPQ(buf, pos, len);
/* Set DP1 (d mod (p-1)) */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte)0x95);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
rsaPrKey.setDP1(buf, pos, len);
/* Set DQ1 (d mod (q-1)) */
pos = UtilTLV.findTag(buf, bOff, bLen, (byte)0x96);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
rsaPrKey.setDQ1(buf, pos, len);
if(rsaPrKey.isInitialized()) {
// If the key is usable, it MUST NOT remain in buf.
JCSystem.beginTransaction();
Util.arrayFillNonAtomic(buf, bOff, bLen, (byte)0x00);
if(keys[currentPrivateKeyRef[0]] != null) {
keys[currentPrivateKeyRef[0]].clearKey();
}
keys[currentPrivateKeyRef[0]] = rsaPrKey;
if(JCSystem.isObjectDeletionSupported()) {
JCSystem.requestObjectDeletion();
}
JCSystem.commitTransaction();
} else {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
}
/**
* \brief Get the field length of an EC FP key using the amount of bytes
* of a parameter (e.g. the prime).
*
* \return The bit length of the field.
*
* \throw ISOException SC_FUNC_NOT_SUPPORTED.
*/
private short getEcFpFieldLength(short bytes) {
switch(bytes) {
case 24:
return KeyBuilder.LENGTH_EC_FP_192;
case 28:
return LENGTH_EC_FP_224;
case 32:
return LENGTH_EC_FP_256;
case 40:
return LENGTH_EC_FP_320;
case 48:
return LENGTH_EC_FP_384;
case 64:
return LENGTH_EC_FP_512;
case 66:
return LENGTH_EC_FP_521;
default:
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
return 0;
}
}
/**
* \brief Instatiate and initialize the current private (EC) key.
*
* A MANAGE SECURITY ENVIRONMENT must have preceeded, setting the current
* algorithm reference to ALG_GEN_EC.
* This method creates a new instance of the current private key.
*
* \param buf The buffer containing the private key. It must be a sequence of
* the following TLV-encoded entries:
* 81 - prime
* 82 - coefficient A
* 83 - coefficient B
* 84 - base point G
* 85 - order
* 87 - cofactor
* 88 - private D
* Note: This buffer will be filled with 0x00 after the operation had been performed.
*
* \param bOff The offset at which the data in buf starts.
*
* \param bLen The length of the data in buf.
*
* \throw ISOException SW_CONDITION_NOT_SATISFIED The current algorithm reference does not match.
* SW_FUNC_NOT_SUPPORTED Algorithm is unsupported by the card.
* SW_UNKNOWN Unknown error.
*
* \throw NotFoundException The buffer does not contain all the information needed to import a private key.
*
* \throw InvalidArgumentsException The buffer is malformatted.
*/
private void importECkey(byte[] buf, short bOff, short bLen) throws InvalidArgumentsException, NotFoundException, ISOException {
short pos = 0;
short len;
short field_len;
ECPrivateKey ecPrKey = null;
if(currentAlgorithmRef[0] != ALG_GEN_EC) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
// Search for prime
pos = UtilTLV.findTag(buf, bOff, bLen, (byte) 0x81);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
// Try to calculate field length frome prime length.
field_len = getEcFpFieldLength(len);
// Try to instantiate key objects of that length
try {
ecPrKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, field_len, false);
} catch(CryptoException e) {
if(e.getReason() == CryptoException.NO_SUCH_ALGORITHM) {
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
ISOException.throwIt(ISO7816.SW_UNKNOWN);
return;
}
initEcParams(buf, bOff, bLen, ecPrKey);
// Set the private component "private D"
pos = UtilTLV.findTag(buf, bOff, bLen, (byte)0x88);
pos++;
len = UtilTLV.decodeLengthField(buf, pos);
pos += UtilTLV.getLengthFieldLength(len);
ecPrKey.setS(buf, pos, len);
if(ecPrKey.isInitialized()) {
// If the key is usable, it MUST NOT remain in buf.
JCSystem.beginTransaction();
Util.arrayFillNonAtomic(buf, bOff, bLen, (byte)0x00);
if(keys[currentPrivateKeyRef[0]] != null) {
keys[currentPrivateKeyRef[0]].clearKey();
}
keys[currentPrivateKeyRef[0]] = ecPrKey;
if(JCSystem.isObjectDeletionSupported()) {
JCSystem.requestObjectDeletion();
}
JCSystem.commitTransaction();
} else {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
}
/**
* \brief Process the GET CHALLENGE instruction (INS=0x84).
*
* The host may request a random number of length "Le". This random number
* is currently _not_ used for any cryptographic function (e.g. secure
* messaging) by the applet.
*
* \param apdu The GET CHALLENGE apdu with P1P2=0000.
*
* \throw ISOException SW_INCORRECT_P1P2, SW_WRONG_LENGTH, SW_FUNC_NOT_SUPPORTED.
*/
private void processGetChallenge(APDU apdu) {
byte[] buf = apdu.getBuffer();
byte p1 = buf[ISO7816.OFFSET_P1];
byte p2 = buf[ISO7816.OFFSET_P2];
if(randomData == null) {
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
if(p1 != 0x00 || p1 != 0x00) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
short le = apdu.setOutgoing();
if(le <= 0 || le > 256) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
randomData.generateData(buf, (short)0, le);
apdu.setOutgoingLength(le);
apdu.sendBytes((short)0, le);
}
} // class IsoApplet