/* * * * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * 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 version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.satsa.pkiapplet; import javacard.framework.*; import javacard.security.*; import javacardx.crypto.Cipher; /** * Card side application for PKI implementation. Supports subset of * WIM functionality. */ public class PKIApplet extends Applet { /** Constant that is used to avoid '(short) x' notation. */ static final byte x0 = 0; /** Constant that is used to avoid '(short) x' notation. */ static final byte x1 = 1; /** Constant that is used to avoid '(short) x' notation. */ static final byte x2 = 2; /** Constant that is used to avoid '(short) x' notation. */ static final byte x3 = 3; /** Constant that is used to avoid '(short) x' notation. */ static final byte x4 = 4; /** Constant that is used to avoid '(short) x' notation. */ static final byte x5 = 5; /** Constant that is used to avoid '(short) x' notation. */ static final byte x6 = 6; /** Constant that is used to avoid '(short) x' notation. */ static final byte x8 = 8; /** INS byte for command APDU. */ static final byte INS_VERIFY = (byte) 0x20; /** INS byte for command APDU. */ static final byte INS_SELECT = (byte) 0xa4; /** INS byte for command APDU. */ static final byte INS_READ = (byte) 0xb0; /** INS byte for command APDU. */ static final byte INS_UPDATE = (byte) 0xd6; /** INS byte for command APDU. */ static final byte INS_MSE = (byte) 0x22; /** INS byte for command APDU. */ static final byte INS_PSO = (byte) 0x2a; /** INS byte for command APDU. */ static final byte INS_NEW = (byte) 0xBC; /** DigestInfo structure size for RSA signature. */ static final short digestLength = 35; /** Temporaru buffer for RSA signature. */ static byte[] signBuffer = new byte[digestLength]; /** If false, applet always report that PINs are validated. */ static boolean verifyPINs = true; /** If false, key generation is disabled. */ static boolean supportKeyGeneration = true; /** PIN identifiers. */ byte[] PIN_REFs; /** PIN objects. */ OwnerPIN[] PINs; /** Private keys. */ PrivateKey[] keys; /** Root DF for entire file structure. */ DFile top; /** * Root DF for WIM application. All relative paths start from * here. */ DFile base; /** Currently selected file. */ File current; /** Flag that indicates that SE is restored. */ boolean isSERestored; /** Flag that indicates that private key path was set properly. */ boolean isKeyFileSet; /** Private key number for signature generation. */ short keyNum; /** Cipher object. */ Cipher cipher; /** MessageDigest object for key hash calculation. */ MessageDigest digest; /** Number of unused keys. */ static short unusedKeys = 0; /** Constructor. */ PKIApplet() { if (Data.PINs == null) { CardRuntimeException.throwIt(x0); } cipher = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false); if (supportKeyGeneration) { digest = MessageDigest.getInstance(MessageDigest.ALG_SHA, false); } register(); } /** * To create an instance of the Applet subclass, the JCRE will call * this static method first. * @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 PKIApplet(); } /** * Called by the JCRE to inform this applet that it has been * selected. When invoked first time initialises the file system. * @return true */ public boolean select() { if (Data.PINs != null) { init(); } current = base; isSERestored = false; return true; } /** * Called by the JCRE to inform this applet that it has been * deselected. When invoked PIN-G should be reset. */ public void deselect() { if (PINs != null) { PINs[0].reset(); } } /** * Initialises the WIM data structures. */ void init() { Parser.init(Data.PINs); short cnt = Parser.getByte(); PIN_REFs = new byte[(short) (cnt + Data.freePINSlots)]; PINs = new OwnerPIN[(short) (cnt + Data.freePINSlots)]; for (short i = 0; i < cnt; i++) { PIN_REFs[i] = Parser.getByte(); byte len = Parser.getByte(); PINs[i] = new OwnerPIN(x3, x8); PINs[i].update(Data.PINs, Parser.offset, len); Parser.skip(len); } Parser.init(Data.PrivateKeys); cnt = Parser.getByte(); keys = new PrivateKey[(short) (cnt + Data.freeKeySlots)]; short keyPos = 0; // calculate start of first key in a file short privKeyStart = (short) (Data.PrKDFOffset + Data.newPrivKeyOffset - Data.privKeyRecordSize * cnt); short pubKeyStart = (short) (Data.PuKDFOffset + Data.newPubKeyOffset - Data.pubKeyRecordSize * cnt); for (short i = 0; i < cnt; i++) { PrivateKey key = new PrivateKey(this); if (key.value != null) { keys[keyPos++] = key; } else { // This key is not supported by card byte[] files = Data.Files; short tail = (short)(cnt + Data.freeKeySlots - keyPos - 1); if (tail > 0) { // Shift up private keys short privOffset = (short)(privKeyStart + Data.privKeyRecordSize * keyPos); Util.arrayCopyNonAtomic(files, (short)(privOffset + Data.privKeyRecordSize), files, privOffset, (short)(Data.privKeyRecordSize * tail)); privOffset = (short)((privKeyStart + Data.privKeyRecordSize * (keyPos + tail))); // Shift up public keys short pubOffset = (short)(pubKeyStart + Data.pubKeyRecordSize * keyPos); Util.arrayCopyNonAtomic(files, (short)(pubOffset + Data.pubKeyRecordSize), files, pubOffset, (short)(Data.pubKeyRecordSize * tail)); pubOffset = (short)((pubKeyStart + Data.pubKeyRecordSize * (keyPos + tail))); } } } Data.newPrivKeyOffset = (short)(privKeyStart - Data.PrKDFOffset + keyPos * Data.privKeyRecordSize); Data.newPubKeyOffset = (short)(pubKeyStart - Data.PuKDFOffset + keyPos * Data.pubKeyRecordSize); unusedKeys = (short)(keys.length - Data.freeKeySlots - keyPos); Parser.init(Data.Files); top = (DFile) readFile(null); if (base == null) { ISOException.throwIt((short) 0x9001); } Data.PINs = null; Data.PrivateKeys = null; // IMPL_NOTE: debug check - remove if (Parser.offset != Data.Files.length) { ISOException.throwIt((short) 0x9001); } } /** * Creates new file object. * @param parent parent DF for this file * @return the new file object */ File readFile(DFile parent) { short id = Parser.getShort(); short type = Parser.getByte(); short length = Parser.getShort(); if ((type & File.DIR) == 0) { EFile f; if ((type & File.EMPTY) == 0) { f = new EFile(parent, id, type, Parser.offset, length, Data.Files); Parser.skip(length); } else { type &= ~File.EMPTY; byte[] data = new byte[length]; short dlen = Parser.getShort(); Util.arrayCopyNonAtomic(Data.Files, Parser.offset, data, (short) 0, dlen); f = new EFile(parent, id, type, (short) 0, length, data); Parser.skip(dlen); } return f; } DFile f = new DFile(parent, id, type); File[] files = new File[length]; for (short i = 0; i < length; i++) { files[i] = readFile(f); } f.files = files; if (type == File.WIM) { base = f; } return f; } /** * Main entry point. * @param apdu command APDU */ public void process(APDU apdu) { byte[] data = apdu.getBuffer(); byte CLA = (byte) (data[ISO7816.OFFSET_CLA] & 0xF0); byte INS = data[ISO7816.OFFSET_INS]; if (CLA == 0 && INS == (byte)(0xA4) && data[ISO7816.OFFSET_P1] == 4) { return; } if (CLA != (byte) 0x80) { ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); } switch (INS) { case INS_SELECT: selectFile(apdu); return; case INS_READ: read(apdu); return; case INS_UPDATE: update(apdu); return; case INS_VERIFY: verify(apdu); return; case INS_MSE: manageSE(apdu); return; case INS_PSO: sign(apdu); return; case INS_NEW: if (supportKeyGeneration) { newKey(apdu); return; } break; } ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); } /** * Handles SELECT FILE APDU. * @param apdu command APDU */ void selectFile(APDU apdu) { byte[] data = apdu.getBuffer(); if (Util.getShort(data, x2) != 0) { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } checkDataSize(x2, apdu); File f = select(Util.getShort(data, x5)); if (f == null) { ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } current = f; if (current.isDF()) { Util.setShort(data, x0, (short) 0x6f00); apdu.setOutgoingAndSend(x0, x2); } else { Util.setShort(data, x0, (short) 0x6f04); Util.setShort(data, x2, (short) 0x8002); Util.setShort(data, x4, ((EFile) current).length); apdu.setOutgoingAndSend(x0, x6); } } /** * Selects the file specified by file identifier. * @param id file identifier * @return selected file */ File select(short id) { DFile f; if (current.isDF()) { f = (DFile) current; } else { f = current.parent; } File x = f.getFile(id); if (x != null) { return x; } f = f.parent; if (f == null) { return null; } return f.getFile(id); } /** * Handles READ BINARY APDU. * @param apdu command APDU */ void read(APDU apdu) { if (current.isDF()) { ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); } EFile f = (EFile) current; byte[] data = apdu.getBuffer(); short offset = Util.getShort(data, x2); if (offset < 0 || offset > f.length) { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } short len = (short) (data[x4] & 0xff); if ((short)(offset + len) > f.length) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } apdu.setOutgoing(); apdu.setOutgoingLength(len); apdu.sendBytesLong(f.data, (short) (f.offset + offset), len); } /** * Handles UPDATE BINARY apdu. * @param apdu command APDU */ void update(APDU apdu) { if (current.isDF()) { ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); } EFile f = (EFile) current; if (!(f.type == File.UPDATE && isValidated(PINs[0]))) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); } byte[] data = apdu.getBuffer(); short offset = Util.getShort(data, x2); if (offset < 0) { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } short len = (short) (data[x4] & 0xff); if ((short)(offset + len) > f.length) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } short l = apdu.setIncomingAndReceive(); short off = 5; while (l > 0) { Util.arrayCopyNonAtomic(data, off, f.data, (short) (f.offset + offset), l); offset += l; l = apdu.receiveBytes(x0); off = 0; } } /** * Handles PIN related APDUs. * @param apdu command APDU */ void verify(APDU apdu) { if (current.type != File.PIN) { ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } byte[] data = apdu.getBuffer(); short pin_num = -1; for (short i = 0; i < (short)(PIN_REFs.length - Data.freePINSlots); i++) { if (PIN_REFs[i] == data[x3]) { pin_num = i; break; } } if (pin_num == -1 || data[x2] != 0) { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } OwnerPIN pin = PINs[pin_num]; if (data[x4] == 0) { if (isValidated(pin)) { return; // SW = 0x9000 } if (pin.getTriesRemaining() == 0) { ISOException.throwIt(ISO7816.SW_FILE_INVALID); } ISOException.throwIt((short) (0x6300 | pin.getTriesRemaining())); } short len = apdu.setIncomingAndReceive(); if (len > 8) { // too long, set to 0 to update PIN len = 0; } pin.check(data, x5, (byte) len); if (isValidated(pin)) { return; // SW = 0x9000 } ISOException.throwIt((short) 0x6300); } /** * Hanldes MANAGE SECURITY ENVIRONMENT APDUs. * @param apdu command APDU */ void manageSE(APDU apdu) { byte[] data = apdu.getBuffer(); if (data[x2] == (byte) 0xf3) { if (data[x3] != Data.WIM_GENERIC_RSA_ID) { ISOException.throwIt((short) 0x6600); } isSERestored = true; isKeyFileSet = false; keyNum = -1; return; } if (! isSERestored || Util.getShort(data, x2) != (short) 0x41b6) { ISOException.throwIt((short) 0x6600); } File keyFile = current; short len = apdu.setIncomingAndReceive(); short index = 5; len += index; try { while (index < len) { byte tag = data[index++]; byte l = data[index++]; if (l <= 0 || l > 32) { ISOException.throwIt((short) 0x6600); } if (tag == (byte) 0x84) { // private key reference if (l != 1) { ISOException.throwIt((short) 0x6600); } for (short i = 0; i < (short)(keys.length - Data.freeKeySlots - unusedKeys); i++) { if (data[index] == keys[i].id) { keyNum = i; break; } } } else if (tag == (byte) 0x81) { // private key DF path keyFile = getFile(data, index, l); } else { ISOException.throwIt((short) 0x6A80); // invalid tag } // path (id, relative or complete) index += l; } } catch (ArrayIndexOutOfBoundsException e) { keyNum = -1; } if (keyNum == -1 || keyFile == null || keyFile.type != File.PrivateKeyFile) { ISOException.throwIt((short) 0x6600); } EFile f = (EFile) keyFile; isKeyFileSet = (f.data[f.offset] == keys[keyNum].id); if (! isKeyFileSet) { ISOException.throwIt((short) 0x6600); } } /** * Allocates new key and, if necessary, PIN. * @param apdu the command APDU. APDU data contains key type * (byte, 0 - authenticatiuon, 1 - non-repudiation), key lenght in * bits (2 byte value). For non-repudiation key after that goes * initial PIN value (8 bytes) and label (32 bytes). If p1 = 1 * the command just verifies that a new key can be generated. */ void newKey(APDU apdu) { apdu.setIncomingAndReceive(); byte[] data = apdu.getBuffer(); boolean nonRepudiation = (data[x5] == 1); if (Data.freeKeySlots == 0 || (nonRepudiation && Data.freePINSlots == 0)) { ISOException.throwIt((short) 0x9001); } short keyLen = Util.getShort(data, x6); if (keyLen > (short)((data.length - 1) * 8)) { ISOException.throwIt((short) 0x9001); } if (data[x2] == 1) { Util.setShort(data, x0, (short) 0x1234); Util.setShort(data, x2, (short) 0x4321); apdu.setOutgoingAndSend(x0, x4); return; } /* * IMPL_NOTE: For testing purposes return existing key instead of new one for (short i = 0; i < (short)(keys.length - Data.freeKeySlots - unusedKeys); i++) { if (keys[i].keyLen == keyLen && keys[i].nonRepudiation == nonRepudiation) { data[0] = (byte)keys[i].id; apdu.setOutgoingAndSend(x0, x1); return; } } */ try { short pinIndex = 0; if (nonRepudiation) { // new PIN must be allocated pinIndex = (short) (PINs.length - Data.freePINSlots); OwnerPIN pin = new OwnerPIN((byte) 3, (byte)8); pin.update(data, x8, (byte) 8); pin.check(data, x8, (byte) 8); PINs[pinIndex] = pin; PIN_REFs[pinIndex] = Data.newPINRef; Util.arrayCopy(data, (short) 16, Data.Files, (short) (Data.AODFOffset + Data.newPINOffset + Data.PINLabelOffset), (short) 32); } // check if keyLen & RSA algorithm are supported by card KeyPair p = new KeyPair(KeyPair.ALG_RSA, keyLen); p.genKeyPair(); PrivateKey key = new PrivateKey(this, Data.newKeyID, PINs[pinIndex], nonRepudiation, keyLen, (RSAPrivateKey) p.getPrivate()); RSAPublicKey pk = (RSAPublicKey) p.getPublic(); EFile f = (EFile) base.getFile(Data.newFileID); f.data = encodePublicKey(pk, data); f.offset = 0; f.length = (short) (f.data.length); byte[] hash = getKeyHash(pk, data); byte[] files = Data.Files; short offset = (short) (Data.PuKDFOffset + Data.newPubKeyOffset); Util.arrayCopy(hash, x0, files, (short) (offset + Data.pubHashOffset), (short) 20); Util.setShort(files, (short) (offset + Data.pubKeyLengthOffset), keyLen); offset = (short) (Data.PrKDFOffset + Data.newPrivKeyOffset); files[(short) (offset + Data.privPINIDOffset)] = nonRepudiation ? Data.newPINID : Data.PIN_G_ID; Util.arrayCopy(hash, x0, files, (short) (offset + Data.privHashOffset), (short) 20); Util.setShort(files, (short) (offset + Data.privKeyLengthOffset), keyLen); offset += Data.privUsageOffset; if (nonRepudiation) { files[offset++] = 6; files[offset++] = 0; files[offset++] = 0x40; } else { files[offset++] = 7; files[offset++] = 0x20; files[offset++] = 0; } data[0] = Data.newKeyID; apdu.setOutgoingAndSend(x0, x1); JCSystem.beginTransaction(); if (nonRepudiation) { Data.freePINSlots--; Data.newPINID++; Data.newPINRef++; files[(short) (Data.AODFOffset + Data.newPINOffset)] = 0x30; Data.newPINOffset += Data.PINRecordSize; } keys[(short) (keys.length - Data.freeKeySlots - unusedKeys)] = key; Data.freeKeySlots--; Data.newFileID += 2; Data.newKeyID++; files[(short) (Data.PrKDFOffset + Data.newPrivKeyOffset)] = 0x30; files[(short) (Data.PuKDFOffset + Data.newPubKeyOffset)] = 0x30; Data.newPrivKeyOffset += Data.privKeyRecordSize; Data.newPubKeyOffset += Data.pubKeyRecordSize; JCSystem.commitTransaction(); } catch (ISOException ie) { throw ie; } catch (Exception e) { ISOException.throwIt((short) 0x9001); } } /** * Generates DER encoded RSA public key. * @param pk the key * @param data temporary data buffer * @return DER encoded RSA public key */ static byte[] encodePublicKey(RSAPublicKey pk, byte[] data) { short modulusLength = pk.getModulus(data, (short) 0); boolean padModulus = ((data[0] & 0x80) != 0); if (padModulus) { modulusLength++; } short size = getDERSize(modulusLength); short exponentLength = pk.getExponent(data, (short) 0); boolean padExponent = ((data[0] & 0x80) != 0); if (padExponent) { exponentLength++; } size += getDERSize(exponentLength); byte[] der = new byte[getDERSize(size)]; // generate public key record short offset = 0; der[offset++] = 0x30; offset = putLength(der, offset, size); der[offset++] = 2; offset = putLength(der, offset, modulusLength); if (padModulus) { offset++; } offset += pk.getModulus(der, offset); der[offset++] = 2; offset = putLength(der, offset, exponentLength); if (padExponent) { offset++; } pk.getExponent(der, offset); return der; } /** * Calculates identifier for public RSA key. * @param pk the key * @param data temporary data buffer * @return the identifier */ byte[] getKeyHash(RSAPublicKey pk, byte[] data) { short offset = 1; short len = pk.getModulus(data, offset); if ((data[1] & 0x80) != 0) { offset--; len++; data[offset] = 0; } byte[] hash = new byte[(short) 20]; digest.doFinal(data, offset, len, hash, x0); return hash; } /** * Returns the size of DER object for given value size. * @param i the value size * @return the size of DER object */ static short getDERSize(short i) { if (i < 128) { return (short) (i + 2); } return (short) (i + 3); } /** * Places encoded length of DER object into the buffer. * @param data the buffer * @param offset offset in the buffer where the length must be * placed * @param length the length to be placed * @return the new offset */ static short putLength(byte[] data, short offset, short length) { if (length >= 128) { data[offset++] = (byte) 0x81; } data[offset++] = (byte) length; return offset; } /** * Returns file object specified by path in the buffer. * @param data the buffer * @param index path offset * @param l path length * @return file object or null if not found */ File getFile(byte[] data, short index, short l) { // path must contain even number of bytes if (l < 2 || l % 2 != 0) { return null; } l = (short) (l / 2); short id = Util.getShort(data, index); File x = null; if (l == 1) { x = base; } else { if (id == top.id) { x = top; } else { if (id == (short)0x3fff) { x = base; } else { return null; } } index += 2; l--; } while (l != 0) { if (! x.isDF()) { return null; } File f = ((DFile) x).getFile(Util.getShort(data, index)); if (f == null || f.parent != x) { return null; } x = f; index += 2; l--; } return x; } /** * Handles PSO-CDS APDU command. * @param apdu command APDU */ void sign(APDU apdu) { byte[] data = apdu.getBuffer(); if (Util.getShort(data, x2) != (short) 0x9e9a) { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } if (! isSERestored || keyNum == -1 || ! isKeyFileSet) { ISOException.throwIt((short) 0x6600); } checkDataSize(digestLength, apdu); if (! keys[keyNum].checkAccess()) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); } cipher.init(keys[keyNum].value, Cipher.MODE_ENCRYPT); Util.arrayCopyNonAtomic(data, x5, signBuffer, x0, digestLength); short len = cipher.doFinal(signBuffer, x0, digestLength, data, x0); short expected = (short) (keys[keyNum].value.getSize() >> 3); if (len != expected) { Util.arrayFillNonAtomic(data, x0, (short) (expected - len), x0); cipher.doFinal(signBuffer, x0, digestLength, data, (short) (expected - len)); } apdu.setOutgoingAndSend(x0, expected); } /** * Verifies that APDU contains correct number of data bytes. * @param expectedSize expected data size * @param apdu APDU object */ private void checkDataSize(short expectedSize, APDU apdu) { if (expectedSize != apdu.setIncomingAndReceive()) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } } /** * Verifies that PIN is validated. * @param pin the PIN * @return true if the PIN is validated or PIN verification is disabled. */ public boolean isValidated(OwnerPIN pin) { return verifyPINs ? pin.isValidated() : true; } } /** * This class represents private key. */ class PrivateKey { /** Owner applet. */ PKIApplet applet; /** Key identifier. */ short id; /** PIN object that protects this key. */ OwnerPIN PIN; /** True if its non-repudiation key. */ boolean nonRepudiation; /** Length of the key. */ short keyLen; /** The key object. */ RSAPrivateKey value; /** * Constructs new key object using hardcoded data. * @param app The applet object */ PrivateKey(PKIApplet app) { applet = app; id = Parser.getByte(); PIN = applet.PINs[Parser.getByte()]; nonRepudiation = (Parser.getByte() == 0); keyLen = Parser.getShort(); boolean notSupported = false; try { value = (RSAPrivateKey) KeyBuilder.buildKey( KeyBuilder.TYPE_RSA_PRIVATE, keyLen, false); } catch (CryptoException e) { notSupported = true; } if (value == null) { notSupported = true; } if (notSupported) { value = null; short len = Parser.getShort(); Parser.skip(len); len = Parser.getShort(); Parser.skip(len); } else { short len = Parser.getShort(); value.setModulus(Data.PrivateKeys, Parser.offset, len); Parser.skip(len); len = Parser.getShort(); value.setExponent(Data.PrivateKeys, Parser.offset, len); Parser.skip(len); } } /** * Constructs new key object. * @param app the applet object * @param id key identifier * @param PIN PIN object that protects this key * @param nonRepudiation is it non-repudiation key? * @param keyLen length of key * @param value the key object */ PrivateKey(PKIApplet app, short id, OwnerPIN PIN, boolean nonRepudiation, short keyLen, RSAPrivateKey value) { this.applet = app; this.id = id; this.PIN = PIN; this.nonRepudiation = nonRepudiation; this.keyLen = keyLen; this.value = value; } /** * Verifies that user has access to this key. * @return true if PIN is validated */ boolean checkAccess() { boolean result = applet.isValidated(PIN); if (nonRepudiation) { PIN.reset(); } return result; } }