3

Consider a Java Card Classic applet with multiple instances that need to share some data in a byte[] variable (e.g. persistent mutable, that is EEPROM/Flash), containing information common to all instances.

A static byte[] variable would be a perfect fit, but these happen to be prohibited by validation rules imposed on me (I'm told: as a catch-all way to demonstrate that un-instantiating an instance frees what it allocated on instantiation).

This would be solved if a slave instance could call one of the applet's method in the context of a master instance (perhaps initially identified by AID, or/and/then in some other way). How can this be done? Any example?


Update: the code in this other question is next to doing what I want shares objects, but does not seem to call a method in the context of another instance.

Community
  • 1
  • 1
fgrieu
  • 2,724
  • 1
  • 23
  • 53
  • 1
    Can't you just hide the array behind a `Shareable` object? I think you can simply retrieve the shareable interface by asking it from the system by using the master's AID, then call a `retrieveByteArray()` defined in the `Shareable` interface. – Maarten Bodewes Jan 09 '13 at 21:37
  • 1
    PS if this question is not answered by tomorrow I'll try and make a SSCCE for you, as a token of gratitude for your work at crypto :) – Maarten Bodewes Jan 09 '13 at 21:38
  • Ah: instead of calling a method in the master's context so that it can use all the master's data, you propose that the instance retrieve the master's data stored in a shareable object. I was afraid that byte[] arrays obtained this way would not be accessible (only valid as arguments); but if they are read/writable, that would do the trick. I'd love such a SSCCE. – fgrieu Jan 09 '13 at 22:49
  • They should be I think, unless they are the APDU buffer or a transient `CLEAR_ON_DESELECT`. Don't make them static either, especially writing to a static byte array is certain to lead to disaster. But with JavaCard, sometimes you just have to try (and read the public RE specs if it doesn't work). – Maarten Bodewes Jan 09 '13 at 23:02
  • So your plan is: write a `retrieveByteArray()` defined in the `Shareable` interface that copies the shared data from the master instance's context to the slave instance's context, thru RAM such as the APDU buffer (or perhaps transient byte[]); I was hoping to avoid a copy, by calling the master's context. As an aside: I understand why writing to byte[] other than transient leads to slowness and memory wear, but not how `static` makes that worse. – fgrieu Jan 10 '13 at 07:03
  • No, I do not think you have to copy the array, although creating getters/setters might be a good idea. There are restrictions on static variables in JavaCard, and as the "master" applet had to be instantiated anyway you should not need one either. – Maarten Bodewes Jan 10 '13 at 08:28

1 Answers1

3

A tested example, as promised:

package nl.owlstead.javacard.sharedarray;

import javacard.framework.*;

/**
 * The master and slave AID should only differ in the last byte; the master should end with the 'm' ASCII character.
 * This applet is for demonstration purposes only.
 * 
 * @author owlstead@stackoverflow
 */
public class SharingApplet extends Applet {

    public interface SharedArray extends Shareable {
        public byte[] getSharedArray();
    }

    public static class SharedArrayImpl implements SharedArray {
        private byte[] sharedArray;

        public SharedArrayImpl(final byte[] arrayToShare) {
            this.sharedArray = arrayToShare;
        }

        public byte[] getSharedArray() {
            return sharedArray;
        }
    }

    private static final short MAX_AID_SIZE = 16;
    private static final short START = 0;
    private static final byte SHARABLE_PARAM = 0;
    private static final byte PARAM_SHARED_ARRAY = 0;

    public static void install(byte[] bArray, short bOffset, byte bLength) {
        final byte aidLength = bArray[bOffset++];
        final byte lastAIDByte = bArray[(short) (bOffset + aidLength - 1)];
        final boolean isMaster = lastAIDByte == 'm';
        final SharingApplet applet = new SharingApplet(isMaster);
        applet.register(bArray, bOffset, aidLength);
        applet.setMasterAID();
    }

    // if null, it is not the master
    private final SharedArray sharedArray;
    private AID masterAID;

    public SharingApplet(final boolean isMaster) {
        if (isMaster) {
            final byte[] sa = new byte[] { 'm' };
            sharedArray = new SharedArrayImpl(sa);
        } else {
            sharedArray = null;
        }
    }

    public void process(APDU apdu) {
        if (selectingApplet()) {
            return;
        }

        byte[] buf = apdu.getBuffer();
        switch (buf[ISO7816.OFFSET_INS]) {
        case (byte) 0x00: {
            final SharedArray theSharedArray;
            if (sharedArray == null) {
                theSharedArray = (SharedArray) JCSystem.getAppletShareableInterfaceObject(masterAID, SHARABLE_PARAM);
            } else {
                theSharedArray = sharedArray;
            }
            final byte[] sa = theSharedArray.getSharedArray();
            Util.arrayCopy(sa, START, buf, START, (short) sa.length);
            apdu.setOutgoingAndSend(START, (short) sa.length);
            break;
        }
        case (byte) 0x02: {
            final SharedArray theSharedArray;
            if (sharedArray == null) {
                theSharedArray = (SharedArray) JCSystem.getAppletShareableInterfaceObject(masterAID, SHARABLE_PARAM);
                final byte[] sa = theSharedArray.getSharedArray();
                sa[START] = 's';
            } else {
                theSharedArray = sharedArray;
                final byte[] sa = theSharedArray.getSharedArray();
                sa[START] = 'm';
            }
            break;
        }
        default:
            ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
        }
    }

    private void setMasterAID() {
        final byte[] workspace = new byte[MAX_AID_SIZE];
        final AID slaveOrMasterAID = JCSystem.getAID();
        final byte aidLength = slaveOrMasterAID.getBytes(workspace, START);
        workspace[(short) (aidLength - 1)] = 'm';
        this.masterAID = new AID(workspace, START, aidLength);
    }

    public Shareable getShareableInterfaceObject(AID clientAID, byte parameter) {
        if (sharedArray == null || parameter != PARAM_SHARED_ARRAY) {
            return null;
        }
        return sharedArray;
    }
}
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Many, MANY thanks. I get how it solves the problem of obtaining the master's `sharedArray` in the slave's context. Unless I err, it also does what I originally asked: theSharedArray.getSharedArray(), when called in the slave's context, in effect executes in the master's context with access to its variables, right? Also: why the `if (selectingApplet()) { return; }`? Note: I'll mark this as accepted answer after trying the code. – fgrieu Jan 10 '13 at 23:09
  • 1
    Yep, it switches to that context. The `if (selectingApplet()) { return; }` code is standard Java Card practice. During a `SELECT by AID` call first `select()` method of the Applet is executed (which can be used to start up a session). But then the APDU is also passed to the `process()` method. This can be useful to return data (e.g. when the `P2` value is `00`). However, normally you should at least return with a `9000` status word, as the Applet *has* been selected by the platform, regardless what you do in the `process()` method. This is accomplished by simply returning without an exception. – Maarten Bodewes Jan 11 '13 at 00:28
  • Sorry, not yet. I need help from a colleague to install the CAP in a physical card. – fgrieu Jan 15 '13 at 07:38
  • Tested it in a real card: it works! Many thanks again. Created 3 instances with AID `ffca11ab1e31` followed by `6d`, `6e`, `6f`; then tested as follows: APDU `00a4040005ffca11ab1e31` selects master, then `8000000000` shows the current state (either `6D` or `73`); `00a4040005ffca11ab1e31` selects master, then `8002000000` sets the state to `6D`; `00a4040005ffca11ab1e31` selects master, then `00a4040205ffca11ab1e31` selects next which is a slave, then `8002000000` sets the state to `73`. I fail to find how to select a slave directly by its AID, but that's not a problem with the applet, I guess. – fgrieu Jan 15 '13 at 17:41
  • Did you read the only comment I left in the code, on top? The master AID is supposed to end with ASCII 'm' the slaves with any other character, e.g. 's' or '1' or '2' (in ASCII encoded binary of course). The length of the AID's should not differ - but that's just a property of this sample Applet, you could also choose to use the user parameters given in the `INSTALL for INSTALL` APDU, passed to the static `install()` method. – Maarten Bodewes Jan 15 '13 at 20:41
  • Yes I read your useful comment. The Master's AID is `ffca11ab1e316d` (7 bytes ending in `m`), the slave's AIDs are `ffca11ab1e316e` and `ffca11ab1e316f` (tried to say that in my earlier comment). IIRC these values showed in the tool my colleague used to load the CAP. But somehow the only way I manage to select a slave is selecting the master with "select first" `00a40400..` and the first 6 bytes of its AID, then "select next" `00a40402..`. Your applet then behaves exactly as expected, it's only the card's "select" which does not work as I expect it to; likely PLBKAC, I'll try again tomorrow. – fgrieu Jan 15 '13 at 22:01
  • Problem solved (I used a wrong `Lc` thus the last byte of the AID was taken as `Le` and ignored by select). Now `00a4040007 ffca11ab1e316d` selects master, `00a4040007 ffca11ab1e316e` or `00a4040007 ffca11ab1e316f` selects one of the two slaves, `8002000000` sets the state to either `6D` (master selected) or `73` (slave selected), `8000000000` reads the state (common to master and slave). Everything 100% nominal. Problem solved, in principle. I still hesitate: bring the master's variables (including transients) to the slave, or pass APDU and slave's variables (less of them) to the master? – fgrieu Jan 16 '13 at 08:23
  • I normally that's solve these kind of issues by looking to what would be logical in the use case. In general try to make the entity that holds or owns the data responsible. But as I do not know the use case or threat model... – Maarten Bodewes Jan 16 '13 at 09:48
  • Here the constraints involve coding standards including ["Applet object references must not be stored in static fields"](https://www.google.com/search?q=Applet+object+references+must+not+be+stored+in+static+fields), portability, data needing to be shared between applications selectable by different AID, security (including shared usage of functionality tied to a confidential key), memory footprint (code, transient and persistent), performance, and existing code. I'm in the analysis phase of how to re-factor that code to meet the coding standards. You helped significantly! – fgrieu Jan 16 '13 at 10:56
  • Hopefully ui did not directly help a competitor, but good luck anyway, it sounds like a handful :) – Maarten Bodewes Jan 16 '13 at 18:03