Search code examples
smartcardjavacardglobalplatform

JavaCard applet works on simulator but not on card


I’m trying to get a simple applet I’ve written to work on a JavaCard — it seems to work perfectly fine on the simulator, but not on the actual card. It uploads and SELECTs successfully, but returns nonsense SWs for most requests.

Here's the applet code (built using Oracle JavaCard 3.0.5 SDK, JDK 11) — just some simple commands to check what works and what does not


public class UnsignedByte {
    public static short decode(byte bits) { return (short)(bits & 0xFF); }
    public static byte encode(short bits) { return (byte)(bits & 0xFF); }
}

public class MyApplet extends Applet {
    private MyApplet() { register(); }
    public static void install(byte[] p1, short p2, short p3) throws ISOException { new MyApplet(); }
    
    @Override
    public boolean select() { return true; }

    @Override
    public void process(APDU apdu) throws ISOException {
        byte[] buffer = apdu.getBuffer();

        final short CLA = UnsignedByte.decode(buffer[ISO7816.OFFSET_CLA]);
        final short INS = UnsignedByte.decode(buffer[ISO7816.OFFSET_INS]);

        // Ignore SELECT APDU
        if (selectingApplet()) {
            return;
        }

        switch(CLA) {
            case 0x00:
                if (INS == 0x0A) {
                    apdu.setOutgoingAndSend((short) 0, (short) 0);
                } else if (INS == 0x40) {
                    buffer[0] = UnsignedByte.encode((short) 0x40);
                    apdu.setOutgoingAndSend((short) 0, (short) 1);
                } else {
                    ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
                }
                break;

            case 0x0A:
                final short P1 = UnsignedByte.decode(buffer[ISO7816.OFFSET_P1]);
                final short P2 = UnsignedByte.decode(buffer[ISO7816.OFFSET_P2]);

                if (P1 == 0xAA) {
                    buffer[0] = UnsignedByte.encode(P2);
                    apdu.setOutgoingAndSend((short) 0, (short) 1);
                } else {
                    ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
                }
                break;

            case 0xF0:
                if (INS != 0x42) {
                    ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
                }

                buffer[0] = UnsignedByte.encode((short) 0xF0);
                buffer[1] = UnsignedByte.encode((short) 0x42);
                buffer[2] = UnsignedByte.encode((short) 0x00);

                apdu.setOutgoingAndSend((short) 0, (short) 3);
                break;

            default:
                ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
                break;
        }
    }
}

This works as expected on a simulator (JCardSim):

  • 00 0A 00 0090 00;
  • 00 40 00 0040 90 00;
  • 0A 42 AA FFFF 90 00;
  • F0 42 00 00F0 42 00 90 00;

However, if I upload it to the card, these are the responses:

  • 00 0A 00 0090 00 (good);
  • 00 40 00 0069 85;
  • 0A 42 AA FF6E 00;
  • F0 42 00 0069 85;
  • When testing earlier, I've also gotten 68 81 for some of the commands;

The code never throws 68 81 an 69 85, and it would seem pretty unlikely that the switch fails in some strange way to throw 6E 00, so, presumably, something happens before the APDU gets to the applet or another applet somehow intercepts it.

The card is an NXP JCOP4 J3R150, Java Card 3.0.5 Classic, Global Platform 2.0, ATR = 3B 6A 00 FF 00 31 C1 73 C8 40 00 00 90 00; I'm uploading the applet using GlobalPlatformPro; there is one preinstalled applet on the card, AID = D2 76 00 00 85 30 4A 43 4F 90 00 ; the card spec says it supports both T=1 and T=0, but it seems to only connect via T=0 if that matters.

Edit: Have I maybe not initialised the card properly — do I need to do anything with it before uploading an applet? I've also found something about the CLA bits determining the logical channels used — could this be the problem? — I've used three different values to avoid that, though.


Solution

  • The results you got from the card appear quite reasonable, while the simulator seems to take some shortcuts. The class byte is not a good target for arbitrary values, you should leave these bits at zero:

    • b1/b2 (values 1 and 2 respectively) encoding the logical channel
    • b3/b4 indicating the special variant of secure messaging
    • b5 for indicating a command followed by another in the same chain.

    If you have b8 = 0 and b7 = 1 an entirely different structure applies, so I recommend taking a look into ISO 7816-4. Note, that commands with class FF are likely to be intercepted by the reader, so this is another value to avoid.