PKIAppletpublic class PKIApplet extends Applet Card side application for PKI implementation. Supports subset of
WIM functionality. |
Fields Summary |
---|
static final byte | x0Constant that is used to avoid '(short) x' notation. | static final byte | x1Constant that is used to avoid '(short) x' notation. | static final byte | x2Constant that is used to avoid '(short) x' notation. | static final byte | x3Constant that is used to avoid '(short) x' notation. | static final byte | x4Constant that is used to avoid '(short) x' notation. | static final byte | x5Constant that is used to avoid '(short) x' notation. | static final byte | x6Constant that is used to avoid '(short) x' notation. | static final byte | x8Constant that is used to avoid '(short) x' notation. | static final byte | INS_VERIFYINS byte for command APDU. | static final byte | INS_SELECTINS byte for command APDU. | static final byte | INS_READINS byte for command APDU. | static final byte | INS_UPDATEINS byte for command APDU. | static final byte | INS_MSEINS byte for command APDU. | static final byte | INS_PSOINS byte for command APDU. | static final byte | INS_NEWINS byte for command APDU. | static final short | digestLengthDigestInfo structure size for RSA signature. | static byte[] | signBufferTemporaru buffer for RSA signature. | static boolean | verifyPINsIf false, applet always report that PINs are validated. | static boolean | supportKeyGenerationIf false, key generation is disabled. | byte[] | PIN_REFsPIN identifiers. | OwnerPIN[] | PINsPIN objects. | PrivateKey[] | keysPrivate keys. | DFile | topRoot DF for entire file structure. | DFile | baseRoot DF for WIM application. All relative paths start from
here. | File | currentCurrently selected file. | boolean | isSERestoredFlag that indicates that SE is restored. | boolean | isKeyFileSetFlag that indicates that private key path was set properly. | short | keyNumPrivate key number for signature generation. | javacardx.crypto.Cipher | cipherCipher object. | MessageDigest | digestMessageDigest object for key hash calculation. | static short | unusedKeysNumber of unused keys. |
Constructors Summary |
---|
PKIApplet()Constructor.
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();
|
Methods Summary |
---|
private void | checkDataSize(short expectedSize, APDU apdu)Verifies that APDU contains correct number of data bytes.
if (expectedSize != apdu.setIncomingAndReceive()) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
| public void | deselect()Called by the JCRE to inform this applet that it has been
deselected. When invoked PIN-G should be reset.
if (PINs != null) {
PINs[0].reset();
}
| static byte[] | encodePublicKey(RSAPublicKey pk, byte[] data)Generates DER encoded RSA public key.
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;
| static short | getDERSize(short i)Returns the size of DER object for given value size.
if (i < 128) {
return (short) (i + 2);
}
return (short) (i + 3);
| File | getFile(byte[] data, short index, short l)Returns file object specified by path in the buffer.
// 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;
| byte[] | getKeyHash(RSAPublicKey pk, byte[] data)Calculates identifier for public RSA key.
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;
| void | init()Initialises the WIM data structures.
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);
}
| public static void | install(byte[] bArray, short bOffset, byte bLength)To create an instance of the Applet subclass, the JCRE will call
this static method first.
new PKIApplet();
| public boolean | isValidated(OwnerPIN pin)Verifies that PIN is validated.
return verifyPINs ? pin.isValidated() : true;
| void | manageSE(APDU apdu)Hanldes MANAGE SECURITY ENVIRONMENT APDUs.
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);
}
| void | newKey(APDU apdu)Allocates new key and, if necessary, PIN.
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);
}
// check if keyLen & RSA algorithm are supported by card
if (!Pairs.tryKeyPair(keyLen)) {
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);
}
KeyPair p = Pairs.getKeyPair(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);
}
| public void | process(APDU apdu)Main entry point.
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);
| static short | putLength(byte[] data, short offset, short length)Places encoded length of DER object into the buffer.
if (length >= 128) {
data[offset++] = (byte) 0x81;
}
data[offset++] = (byte) length;
return offset;
| void | read(APDU apdu)Handles READ BINARY 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);
| File | readFile(DFile parent)Creates new file object.
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;
| public boolean | select()Called by the JCRE to inform this applet that it has been
selected. When invoked first time initialises the file system.
if (Data.PINs != null) {
init();
}
current = base;
isSERestored = false;
return true;
| File | select(short id)Selects the file specified by file identifier.
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);
| void | selectFile(APDU apdu)Handles SELECT FILE 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);
}
| void | sign(APDU apdu)Handles PSO-CDS APDU command.
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);
| void | update(APDU apdu)Handles UPDATE BINARY 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;
}
| void | verify(APDU apdu)Handles PIN related APDUs.
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);
|
|