1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.weaver;
18
19import javacard.framework.AID;
20import javacard.framework.APDU;
21import javacard.framework.Applet;
22import javacard.framework.ISO7816;
23import javacard.framework.ISOException;
24import javacard.framework.JCSystem;
25import javacard.framework.Shareable;
26import javacard.framework.Util;
27
28public class Weaver extends Applet {
29    // Keep constants in sync with esed
30    // Uses the full AID which needs to be kept in sync on updates.
31    public static final byte[] CORE_APPLET_AID
32            = new byte[] {(byte) 0xA0, 0x00, 0x00, 0x04, 0x76, 0x57, 0x56,
33                                 0x52, 0x43, 0x4F, 0x52, 0x45, 0x30,
34                                 0x01, 0x01, 0x01};
35
36    public static final byte CORE_APPLET_SLOTS_INTERFACE = 0;
37
38    private Slots mSlots;
39
40    protected Weaver() {
41        register();
42    }
43
44    /**
45     * Installs this applet.
46     *
47     * @param params the installation parameters
48     * @param offset the starting offset of the parameters
49     * @param length the length of the parameters
50     */
51    public static void install(byte[] params, short offset, byte length) {
52        new Weaver();
53    }
54
55    /**
56     * Get a handle on the slots after the applet is registered but before and APDUs are received.
57     */
58    @Override
59    public boolean select() {
60      mSlots = null;
61      return true;
62    }
63
64    /**
65     * Processes an incoming APDU.
66     *
67     * @param apdu the incoming APDU
68     * @exception ISOException with the response bytes per ISO 7816-4
69     */
70    @Override
71    public void process(APDU apdu) {
72        // TODO(drewry,ascull) Move this back into select.
73        if (mSlots == null) {
74            AID coreAid = JCSystem.lookupAID(CORE_APPLET_AID, (short) 0, (byte) CORE_APPLET_AID.length);
75            if (coreAid == null) {
76                ISOException.throwIt((short)0x0010);
77            }
78
79            mSlots = (Slots) JCSystem.getAppletShareableInterfaceObject(
80                    coreAid, CORE_APPLET_SLOTS_INTERFACE);
81            if (mSlots == null) {
82                ISOException.throwIt((short)0x0012);
83            }
84        }
85
86
87        final byte buffer[] = apdu.getBuffer();
88        final byte cla = buffer[ISO7816.OFFSET_CLA];
89        final byte ins = buffer[ISO7816.OFFSET_INS];
90
91        // Handle standard commands
92        if (apdu.isISOInterindustryCLA()) {
93            switch (ins) {
94                case ISO7816.INS_SELECT:
95                    // Do nothing, successfully
96                    return;
97                default:
98                    ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
99            }
100        }
101
102        // Handle custom applet commands
103        switch (ins) {
104            case Consts.INS_GET_NUM_SLOTS:
105                getNumSlots(apdu);
106                return;
107
108            case Consts.INS_WRITE:
109                write(apdu);
110                return;
111
112            case Consts.INS_READ:
113                read(apdu);
114                return;
115
116            case Consts.INS_ERASE_VALUE:
117                eraseValue(apdu);
118                return;
119
120            case Consts.INS_ERASE_ALL:
121                eraseAll(apdu);
122                return;
123
124            default:
125                ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
126        }
127    }
128
129    /**
130     * Get the number of slots.
131     *
132     * p1: 0
133     * p2: 0
134     * data: _
135     */
136    private void getNumSlots(APDU apdu) {
137        p1p2Unused(apdu);
138        //dataUnused(apdu);
139        // TODO(ascull): how to handle the cases of APDU properly?
140        prepareToSend(apdu, (short) 4);
141        apdu.setOutgoingLength((short) 4);
142
143        final byte buffer[] = apdu.getBuffer();
144        Util.setShort(buffer, (short) 0, (short) 0);
145        Util.setShort(buffer, (short) 2, mSlots.getNumSlots());
146
147        apdu.sendBytes((short) 0, (byte) 4);
148    }
149
150    public static final short WRITE_DATA_BYTES
151            = Consts.SLOT_ID_BYTES + Consts.SLOT_KEY_BYTES + Consts.SLOT_VALUE_BYTES;
152    private static final byte WRITE_DATA_SLOT_ID_OFFSET = ISO7816.OFFSET_CDATA;
153    private static final byte WRITE_DATA_KEY_OFFSET
154            = WRITE_DATA_SLOT_ID_OFFSET + Consts.SLOT_ID_BYTES;
155    private static final byte WRITE_DATA_VALUE_OFFSET
156            = WRITE_DATA_KEY_OFFSET + Consts.SLOT_KEY_BYTES;
157
158    /**
159     * Write to a slot.
160     *
161     * p1: 0
162     * p2: 0
163     * data: [slot ID] [key data] [value data]
164     */
165    private void write(APDU apdu) {
166        p1p2Unused(apdu);
167        receiveData(apdu, WRITE_DATA_BYTES);
168
169        final byte buffer[] = apdu.getBuffer();
170        final short slotId = getSlotId(buffer, WRITE_DATA_SLOT_ID_OFFSET);
171        mSlots.write(slotId, buffer, WRITE_DATA_KEY_OFFSET, buffer, WRITE_DATA_VALUE_OFFSET);
172    }
173
174    public static final short READ_DATA_BYTES
175            = Consts.SLOT_ID_BYTES + Consts.SLOT_KEY_BYTES;
176    private static final byte READ_DATA_SLOT_ID_OFFSET = ISO7816.OFFSET_CDATA;
177    private static final byte READ_DATA_KEY_OFFSET
178            = WRITE_DATA_SLOT_ID_OFFSET + Consts.SLOT_ID_BYTES;
179
180    /**
181     * Read a slot.
182     *
183     * p1: 0
184     * p2: 0
185     * data: [slot ID] [key data]
186     */
187    private void read(APDU apdu) {
188        final byte successSize = 1 + Consts.SLOT_VALUE_BYTES;
189        final byte failSize = 1 + 4;
190
191        p1p2Unused(apdu);
192        receiveData(apdu, READ_DATA_BYTES);
193        prepareToSend(apdu, successSize);
194
195        final byte buffer[] = apdu.getBuffer();
196        final short slotId = getSlotId(buffer, READ_DATA_SLOT_ID_OFFSET);
197
198        final byte err = mSlots.read(slotId, buffer, READ_DATA_KEY_OFFSET, buffer, (short) 1);
199        buffer[(short) 0] = err;
200        if (err == Consts.READ_SUCCESS) {
201            apdu.setOutgoingLength(successSize);
202            apdu.sendBytes((short) 0, successSize);
203        } else {
204            apdu.setOutgoingLength(failSize);
205            apdu.sendBytes((short) 0, failSize);
206        }
207    }
208
209    public static final short ERASE_VALUE_BYTES = Consts.SLOT_ID_BYTES;
210    private static final byte ERASE_VALUE_SLOT_ID_OFFSET = ISO7816.OFFSET_CDATA;
211
212    /**
213     * Erase the value of a slot.
214     *
215     * p1: 0
216     * p2: 0
217     * data: [slot ID]
218     */
219    private void eraseValue(APDU apdu) {
220        p1p2Unused(apdu);
221        receiveData(apdu, ERASE_VALUE_BYTES);
222
223        final byte buffer[] = apdu.getBuffer();
224        final short slotId = getSlotId(buffer, READ_DATA_SLOT_ID_OFFSET);
225        mSlots.eraseValue(slotId);
226    }
227
228    /**
229     * Erase all slots.
230     *
231     * p1: 0
232     * p2: 0
233     * data: _
234     */
235    private void eraseAll(APDU apdu) {
236        p1p2Unused(apdu);
237        dataUnused(apdu);
238        mSlots.eraseAll();
239    }
240
241    /**
242     * Check that the parameters are 0.
243     *
244     * They are not being used but should be under control.
245     */
246    private void p1p2Unused(APDU apdu) {
247        final byte buffer[] = apdu.getBuffer();
248        if (buffer[ISO7816.OFFSET_P1] != 0 || buffer[ISO7816.OFFSET_P2] != 0) {
249            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
250        }
251    }
252
253    /**
254     * Check that no data was provided.
255     */
256    private void dataUnused(APDU apdu) {
257        receiveData(apdu, (short) 0);
258    }
259
260    /**
261     * Calls setIncomingAndReceive() on the APDU and checks the length is as expected.
262     */
263    private void receiveData(APDU apdu, short expectedLength) {
264        final short bytesRead = apdu.setIncomingAndReceive();
265        if (apdu.getIncomingLength() != expectedLength || bytesRead != expectedLength) {
266            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
267        }
268    }
269
270    /**
271     * The slot ID in the API is 32-bits but the applet works with 16-bit IDs.
272     */
273    private short getSlotId(byte[] bArray, short bOff) {
274        if (bArray[bOff] != 0 || bArray[(short) (bOff + 1)] != 0) {
275            ISOException.throwIt(Consts.SW_INVALID_SLOT_ID);
276        }
277        return Util.getShort(bArray,(short) (bOff + 2));
278    }
279
280    /**
281     * Calls setOutgoing() on the APDU, checks the length is as expected and calls
282     * setOutgoingLength() with that length.
283     *
284     * Still need to call setOutgoingLength() after this method.
285     */
286    private void prepareToSend(APDU apdu, short expectedMaxLength) {
287        final short outDataLen = apdu.setOutgoing();
288        if (outDataLen != expectedMaxLength) {
289            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
290        }
291    }
292}
293