Hot questions for Using Applets in apdu

Question:

Hello i'm a beginner on the use of javacard ! I want to write a file (less than 1024kb) in my smart card so what i do is to transform the file to an array of byte (byte[]), for example i have an array with the length of 638 ! My code to write and read the data works fine (no error) but when i read the data from the smart card and compare it to the default array, they are not same (same length but different content). I read a lot of topic here but i was unable to find one which can solve my problem ! Thank you for help

I use JCDK 2.2.2

My applet that read and write data:

.....
public void process(APDU apdu) {
    byte[] buffer = apdu.getBuffer();
    switch(buffer[ISO7816.OFFSET_INS]) {
        case INS_SET_DATA:
            // byte bufferDataLength = (byte)(apdu.setIncomingAndReceive());

            //We want to start data copy
            if (buffer[ISO7816.OFFSET_P1] == 0x00 && buffer[ISO7816.OFFSET_P2] == 0x00) {
                data = new byte[(short) 638];
            } else {
                // copying the apdu data into byte array Data
                // array copy: (src, offset, target, offset,copy size)
                Util.arrayCopyNonAtomic(buffer, (short) ISO7816.OFFSET_CDATA, data,
                            (short) ((short) buffer[ISO7816.OFFSET_P1] * 100),
                            (short) buffer[ISO7816.OFFSET_P2]
                    );
                }
            break;
        case INS_GET_DATA:
            // array copy: (src, offset, target, offset,copy size)
            short t = (short)(buffer[ISO7816.OFFSET_P2] & 0xFF);
            short o = (short)(buffer[ISO7816.OFFSET_P1] & 0xFF);

            Util.arrayCopyNonAtomic(data, o, buffer, ISO7816.OFFSET_CDATA, t);
            apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, t);
            break;
    }
}
.....

The code i use to send the data to the smartcard (apduio package of JCDK 2.2.2)

.....

switch(choice) {
   case 8:
       byte[] configData = fileToByteArray("D:\\jcard\\config.dat");
       byte[] chunk = new byte[100];

       System.out.println("Config Length: " + configData.length); // Got 638

       int configLength = configData.length;
       double round = Math.floor((double)configLength / 100);
       int lastPart = configLength % 100;

       System.out.println("Round : " + round); // Got 6
       System.out.println("Last Part : " + lastPart); // Got 38

       double partToSend = lastPart == 0 ? round : round + 1;

       //Initialize the array of byte in my applet to have the length of data i want to write in
       apdu.command[Apdu.INS] = MyAppletClient.INS_SET_DATA;
       apdu.command[Apdu.P1] = 0x00;
       apdu.command[Apdu.P2] = 0x00;

       cad.exchangeApdu(apdu);
       handleResponse(apdu);

       boolean allPartSend = true;
       for (int i = 0; i < round; i++) {
           //array copy: (src, offset, target, offset, copy size)
           System.arraycopy(configData, (i * 100), chunk, 0, 100);
           System.out.println("Data Length: " + chunk.length); // Got 100

           apdu.command[Apdu.P1] = (byte) i;
           apdu.command[Apdu.P2] = 100;

           apdu.setDataIn(Data);
           cad.exchangeApdu(apdu);
           if (apdu.getStatus() != 0x9000) {
               System.out.println("["+i+"] An error occurred with status: " + apdu.getStatus());
               allPartSend = false;
               break;
            }
        }

        if(allPartSend) {
            byte[] finalPart = new byte[lastPart];
            System.arraycopy(configData, (int) (round * 100), finalPart, 0, lastPart);

            apdu.command[Apdu.P1] = (byte) round;
            apdu.command[Apdu.P2] = (byte) lastPart;
            apdu.setDataIn(finalPart);
            cad.exchangeApdu(apdu);

            if (apdu.getStatus() != 0x9000) {
                System.out.println("An error occurred with status: " + apdu.getStatus());
                break;
            } else {
                System.out.println("OK");
            }
        } else {
            System.out.println("Fail to send all data");
        }
        break;
    case 9:
        int cfgLength = 638; //Because i know the array has that length
        byte[] res = new byte[cfgLength];
        double rnd = Math.floor((double)cfgLength / 100);
        int last = fpLength % 100, readLength = 0;

        boolean allSend = true;

        for(int i = 0; i < rnd; i++) {
            apdu.command[Apdu.INS] = MyAppletClient.INS_GET_DATA;
            apdu.command[Apdu.P1] = (byte) (i * 100);
            apdu.command[Apdu.P2] = 100;
            cad.exchangeApdu(apdu);

            if (apdu.getStatus() != 0x9000) {
                System.out.println("An error occurred with status: " + apdu.getStatus());
            } else {
                readLength += apdu.dataOut.length;
                // System.out.println("Datalength : " + apdu.dataOut.length); // Got 100
                //array copy: (src, offset, target, offset, copy size)
                System.arraycopy(apdu.dataOut, 0, res, (i*100), apdu.dataOut.length);
            }
         }

         if(allSend) {
             apdu.command[Apdu.INS] = MyAppletClient.INS_GET_DATA;
             apdu.command[Apdu.P1] = (byte) ((int)rnd * 100);
             apdu.command[Apdu.P2] = (byte) last;

             cad.exchangeApdu(apdu);
             if (apdu.getStatus() != 0x9000) {
                 System.out.println("An error occurred with status: " + apdu.getStatus());
                 break;
             } else {
                 readLength += apdu.dataOut.length;
                 //array copy: (src, offset, target, offset, copy size)
                 System.arraycopy(apdu.dataOut, 0, res, (int)(rnd * 100 ), apdu.dataOut.length);          
             }
         } else {
              System.out.println("Fail to get all the part");
         }

         byte[] cfgData = fileToByteArray("D:\\jcard\\config.dat");
         System.out.println("ReadLength : " + readLength); // Got 638
         System.out.println("Array equals : " + Arrays.equals(cfgData, res)); // Got false but expected true

         break;
     }
}

.....

Answer:

It looks like you are mixing ISO case 1 and 3 commands. An ISO case 1 means that no CDATA or RDATA is present, and ISO case 3 means just CDATA, no RDATA. Generally it is best to let a command be only one of the possible 4 cases (case 2 is just RDATA and case 4 both CDATA and RDATA. For instance, you can create a CREATE RECORD command or something similar.

You should call setIncomingAndReceive right before copying the data to the array. If you do not do this then only part of the data may be present in the buffer. Note though that setIncomingAndReceive is a convenience method, and you may be able to find better methods for handling large amounts of data.

Note that ISO 7816-4 already defines a file based card system; copying part of that existing standard may be more useful than creating your own from scratch. Also note that you should prefer APDU#getOffsetCdata rather than the constant. That method is compatible with extended length APDU's, and you may want to upscale to those in the future.

Question:

I am writing Java Applet. I need to upload pre-generated RSAPrivateCrtKey to applet with APDU Command.

In my computer I am generating key. Next I serialize this key in privateKeyBuffer:

keyPair = new KeyPair(KeyPair.ALG_RSA_CRT, KeyBuilder.LENGTH_RSA_512);
keyPair.genKeyPair();
rsaPrivateKey = (RSAPrivateCrtKey) keyPair.getPrivate();

byte[] privateKeyBuffer = new byte[165];

short lengthDP1 = rsaPrivateKey.getDP1(privateKeyBuffer, 0);
offset += lengthDP1;

short lengthDQ1 = rsaPrivateKey.getDQ1(privateKeyBuffer, offset);
offset += lengthDQ1;

short lengthP = rsaPrivateKey.getP(privateKeyBuffer, offset);
offset += lengthP;

short lengthQ = rsaPrivateKey.getQ(privateKeyBuffer, offset);
offset += lengthQ;

short lengthPQ = rsaPrivateKey.getPQ(privateKeyBuffer, offset);

After generating a buffer I should convert each array element to hex and then send with APDU and last in applet restore my private key, but in privateKeyBuffer we have negative numbers :

37,65,-96,-110,38,6,-2,73,-37,28,120,-90... (etc)

How I should convert them to hex and keep APDU correct (because as I know only positive numbers allowed) or may be there another way to push key to JavaCard?

Any ideas?


Answer:

You can simply copy your byte array to the APDU buffer and send it. It will work.

final byte[] apduBuffer = apdu.getBuffer();
Util.arrayCopyNonAtomic(privateKeyBuffer, (short) 0, apduBuffer, (short) 0, (short) privateKeyBuffer.length);
apdu.setOutgoingAndSend((short) 0, (short) privateKeyBuffer.length);

You do not have to think about hex values and positive/negative numbers at all! When you send a byte = 8 bits, you do not deal with its number representation or meaning at all.


An extra note:

byte[] privateKeyBuffer = new byte[165]; is generally a bad idea. You do not need this buffer at all. You can copy key values directly into the APDU buffer, save some persistent memory and make your applet faster.


Another note based on dear @Abraham's comment:

The only problem with positive/negative numbers you could face in Java Card is the fact that Java Card byte is signed, unfortunately. That means its values are always in range [-128,127], although people often forget about it:

final byte b = getSomeByteValue(); //from APDU buffer, for example
if (b == 0x80) { //always false!
     //this never happens!
}

If you want to understand your byte as unsigned (range [0, 255]), you have to cast it to short this way:

final byte b = getSomeByteValue(); //from APDU buffer, for example
final short unsignedValue = (short) (b & 0xFF);
if (unsignedValue == 0x80) { //correct!
     //can happen
}

Question:

I am trying to load an applet into a smart card, before I wanted just to make a little test with the following code :

import javax.smartcardio.*;
import java.util.*;

public class TestSmartCardIO {

    public static String toString(byte[] bytes) {
        StringBuffer sbTmp = new StringBuffer();
        for(byte b : bytes){
            sbTmp.append(String.format("%X", b));
        }
        return sbTmp.toString();
    }

    public static void main(String[] args) {
        try {
            TerminalFactory factory = TerminalFactory.getDefault();
            List terminals = factory.terminals().list();
            System.out.println("Terminals count: " + terminals.size());
            System.out.println("Terminals: " + terminals);

            // Get the first terminal in the list
            CardTerminal terminal = (CardTerminal) terminals.get(0);

            // Establish a connection with the card using
            // "T=0", "T=1", "T=CL" or "*"
            Card card = terminal.connect("*");
            System.out.println("Card: " + card);

            // Get ATR
            byte[] baATR = card.getATR().getBytes();
            System.out.println("ATR: " + TestSmartCardIO.toString(baATR) );

            CardChannel channel = card.getBasicChannel();

            /*SELECT Command
             See GlobalPlatform Card Specification (e.g. 2.2, section 11.9)
             CLA: 00
             INS: A4
             P1: 04 i.e. b3 is set to 1, means select by name
             P2: 00 i.e. first or only occurence
             Lc: 08 i.e. length of AID see below
             Data: A0 00 00 00 03 00 00 00
             AID of the card manager,
             in the future should change to A0 00 00 01 51 00 00*/

             byte[] baCommandAPDU = {(byte) 0x00, (byte) 0xA4, (byte) 0x04, (byte) 0x00, (byte) 0x08, (byte) 0xA0, (byte) 0x00, 
                                    (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x00};
            System.out.println("APDU >>>: " + TestSmartCardIO.toString(baCommandAPDU));

            ResponseAPDU r = channel.transmit(new CommandAPDU(baCommandAPDU));
            System.out.println("APDU <<<: " + TestSmartCardIO.toString(r.getBytes()));

            // Disconnect
            // true: reset the card after disconnecting card.

            card.disconnect(true);
        } catch(Exception ex)  {
            ex.printStackTrace();
        }
    }
} 

So I just wanted to test if the card is recognized and if I can send APDUs properly. I am trying to select the AID Card Manager by the APDU but I get :

Terminals count: 1
Terminals: [PC/SC terminal OT MicroSD smartcard Reader 1]
Card: PC/SC card in OT MicroSD smartcard Reader 1, protocol T=1, state OK
ATR: 3BDB96081B1FE451F83031C0641A181019005D
APDU >>>: 0A4408A00003000
APDU <<<: 6A82

And SW1 = 6A and SW2 = 82 means that the card does not find the AID Card Manager... Is it normal ? I don't really understand, I wonder if it is linked with the fact that the card uses the protocol T = 1 ? Thank you very much for your help


Answer:

Previously Global Platform borrowed the RID (the first 5 bytes of the AID) from VISA. This is because of historical reasons. Global Platform is now a separate entity but Open Platform - as it was once called - was started by (at least) VISA. There are many RID's registered.

However from what I understood, VISA did not want to use Global Platform to use their RID anymore. So a new RID was requested. Instead of the rather low A000000003 RID, Global Platform now used their own: A000000151 RID. Another difference is that the last bytes (which can be anything, up to 15 - 5 = 10 bytes, specified by the organization) now consists of two bytes instead of three. Some OS versions actually get that wrong and still use three 00 bytes.

So you previously had A000000003 000000 for Open Platform and earlier GP implementations and for later cards or Global Platform specifications you have A000000151 0000 to select the card manager. The handling of SELECT is not completely clear from ISO/IEC 7816-4. Generally though if you provide a smaller AID (of at least 5 bytes) within SELECT by NAME then a matching application will be selected.

Question:

This is the first time I work on a smart card and I am not able to understand perfectly how to use it...

My situation : I have an ACOS5-64 smart card which supports cryptographic functions like RSA signature, SHA256 etc... and I would like to add a Schnorr signature on the card, as an applet for example. The thing is : all I have is the smart card's reference manual which explains how to use card's functionnality by APDU commands (create files, computes hashes or signatures...) but nothing about programing applets for the smart card. I know that Java Card allows this kind of thing but is it possible to add applets to a smart card that does not support Java Card technology ? (That's the case of mine)

Thank you very much for your consideration


Answer:

No - the smart card must have an operating system that supports injecting and running an applet (GlobalPlatform, for instance). If the card does not have an operating system that supports the lifecycle of the Java Card applet, it will not work. Even some cards that do have such an operating system can restrict the ability to load new applets.