FileDocCategorySizeDatePackage
PKIApplet.javaAPI DocphoneME MR2 API (J2ME)34292Wed May 02 18:00:40 BST 2007com.sun.satsa.pkiapplet

PKIApplet

public class PKIApplet extends Applet
Card side application for PKI implementation. Supports subset of WIM functionality.

Fields Summary
static final byte
x0
Constant that is used to avoid '(short) x' notation.
static final byte
x1
Constant that is used to avoid '(short) x' notation.
static final byte
x2
Constant that is used to avoid '(short) x' notation.
static final byte
x3
Constant that is used to avoid '(short) x' notation.
static final byte
x4
Constant that is used to avoid '(short) x' notation.
static final byte
x5
Constant that is used to avoid '(short) x' notation.
static final byte
x6
Constant that is used to avoid '(short) x' notation.
static final byte
x8
Constant that is used to avoid '(short) x' notation.
static final byte
INS_VERIFY
INS byte for command APDU.
static final byte
INS_SELECT
INS byte for command APDU.
static final byte
INS_READ
INS byte for command APDU.
static final byte
INS_UPDATE
INS byte for command APDU.
static final byte
INS_MSE
INS byte for command APDU.
static final byte
INS_PSO
INS byte for command APDU.
static final byte
INS_NEW
INS byte for command APDU.
static final short
digestLength
DigestInfo structure size for RSA signature.
static byte[]
signBuffer
Temporaru buffer for RSA signature.
static boolean
verifyPINs
If false, applet always report that PINs are validated.
static boolean
supportKeyGeneration
If false, key generation is disabled.
byte[]
PIN_REFs
PIN identifiers.
OwnerPIN[]
PINs
PIN objects.
PrivateKey[]
keys
Private keys.
DFile
top
Root DF for entire file structure.
DFile
base
Root DF for WIM application. All relative paths start from here.
File
current
Currently selected file.
boolean
isSERestored
Flag that indicates that SE is restored.
boolean
isKeyFileSet
Flag that indicates that private key path was set properly.
short
keyNum
Private key number for signature generation.
javacardx.crypto.Cipher
cipher
Cipher object.
MessageDigest
digest
MessageDigest object for key hash calculation.
static short
unusedKeys
Number 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 voidcheckDataSize(short expectedSize, APDU apdu)
Verifies that APDU contains correct number of data bytes.

param
expectedSize expected data size
param
apdu APDU object

        if (expectedSize != apdu.setIncomingAndReceive()) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }
    
public voiddeselect()
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.

param
pk the key
param
data temporary data buffer
return
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 shortgetDERSize(short i)
Returns the size of DER object for given value size.

param
i the value size
return
the size of DER object


        if (i < 128) {
            return (short) (i + 2);
        }
        return (short) (i + 3);
    
FilegetFile(byte[] data, short index, short l)
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


        // 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.

param
pk the key
param
data temporary data buffer
return
the identifier


        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;
    
voidinit()
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 voidinstall(byte[] bArray, short bOffset, byte bLength)
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

        new PKIApplet();
    
public booleanisValidated(OwnerPIN pin)
Verifies that PIN is validated.

param
pin the PIN
return
true if the PIN is validated or PIN verification is disabled.

        return verifyPINs ? pin.isValidated() : true;
    
voidmanageSE(APDU apdu)
Hanldes MANAGE SECURITY ENVIRONMENT APDUs.

param
apdu command 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);
        }
    
voidnewKey(APDU apdu)
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.


        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 voidprocess(APDU apdu)
Main entry point.

param
apdu command 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);
    
static shortputLength(byte[] data, short offset, short length)
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

        if (length >= 128) {
            data[offset++] = (byte) 0x81;
        }
        data[offset++] = (byte) length;
        return offset;
    
voidread(APDU apdu)
Handles READ BINARY APDU.

param
apdu command 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);
    
FilereadFile(DFile parent)
Creates new file object.

param
parent parent DF for this file
return
the 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 booleanselect()
Called by the JCRE to inform this applet that it has been selected. When invoked first time initialises the file system.

return
true


        if (Data.PINs != null) {
            init();
        }
        current = base;
        isSERestored = false;
        return true;
    
Fileselect(short id)
Selects the file specified by file identifier.

param
id file identifier
return
selected file


        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);
    
voidselectFile(APDU apdu)
Handles SELECT FILE APDU.

param
apdu command 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);
        }
    
voidsign(APDU apdu)
Handles PSO-CDS APDU command.

param
apdu command 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);
    
voidupdate(APDU apdu)
Handles UPDATE BINARY apdu.

param
apdu command 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;
        }
    
voidverify(APDU apdu)
Handles PIN related APDUs.

param
apdu command 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);