Merge "Weaver applet." am: 06c9c8316c am: 14f5bfe064
am: 3701a512d2
Change-Id: I8af1fb9670acda2aad5f0b704cbcd3bff9a5eb79
diff --git a/apps/Android.bp b/apps/Android.bp
index f8c1283..9e87adb 100644
--- a/apps/Android.bp
+++ b/apps/Android.bp
@@ -25,4 +25,4 @@
cflags: ["-fvisibility=internal"],
}
-subdirs = ["boot"]
+subdirs = ["boot", "weaver"]
diff --git a/apps/weaver/Android.bp b/apps/weaver/Android.bp
new file mode 100644
index 0000000..65bb0e7
--- /dev/null
+++ b/apps/weaver/Android.bp
@@ -0,0 +1,22 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_library {
+ name: "libese-app-weaver",
+ defaults: ["libese-app-defaults"],
+ srcs: ["weaver.c"],
+ shared_libs: ["liblog", "libese", "libese-sysdeps"],
+}
diff --git a/apps/weaver/card/build.xml b/apps/weaver/card/build.xml
new file mode 100644
index 0000000..fdb4ee5
--- /dev/null
+++ b/apps/weaver/card/build.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- Ant XML for out of band building the applet. -->
+<project basedir="." default="weaver" name="applet build">
+
+<!-- Grab the sdk. -->
+<get src="https://github.com/martinpaljak/oracle_javacard_sdks/archive/master.zip"
+ dest="javacard_sdks.zip" skipexisting="true"/>
+<unzip src="javacard_sdks.zip" dest="." stripAbsolutePathSpec="true">
+ <patternset>
+ <include name="**/jc303_kit/**"/>
+ </patternset>
+ <cutdirsmapper dirs="1" />
+</unzip>
+
+<!-- Grab the awesome ant helper. -->
+<get src="https://github.com/martinpaljak/ant-javacard/releases/download/v1.7/ant-javacard.jar" dest="." skipexisting="true"/>
+<taskdef name="javacard" classname="pro.javacard.ant.JavaCard" classpath="ant-javacard.jar"/>
+
+<target name="weaver">
+<javacard jckit="jc303_kit">
+ <!-- Comm applet -->
+ <cap aid="A00000006203010C01" package="com.android.weaver" version="0.1"
+ output="weaver_comm.cap" sources="src/com/android/weaver" export="export/comm">
+ <applet class="com.android.weaver.Weaver" aid="A00000006203010C0101"/>
+ <import exps="export/comm" />
+ </cap>
+ <!-- Core applet -->
+ <cap aid="A00000006203010C02" package="com.android.weaver.core" version="0.1"
+ output="weaver_core.cap" sources="src/com/android/weaver/core" export="export/core">
+ <!-- TODO: reintroduce jcopx dependency for DSTimer -->
+ <!-- Grab the other interfaces from export/ -->
+ <import exps="export/comm" jar="export/comm/weaver.jar" />
+ <applet class="com.android.weaver.core.WeaverCore" aid="A00000006203010C0201"/>
+ </cap>
+</javacard>
+</target>
+</project>
diff --git a/apps/weaver/card/src/com/android/weaver/Consts.java b/apps/weaver/card/src/com/android/weaver/Consts.java
new file mode 100644
index 0000000..b1beab1
--- /dev/null
+++ b/apps/weaver/card/src/com/android/weaver/Consts.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.weaver;
+
+// Keep in sync with native code
+public class Consts {
+ public static final byte SLOT_ID_BYTES = 4; // 32-bit
+ public static final byte SLOT_KEY_BYTES = 16; // 128-bit
+ public static final byte SLOT_VALUE_BYTES = 16; // 128-bit
+
+ // Read errors (first byte of response)
+ public static final byte READ_SUCCESS = 0x00;
+ public static final byte READ_WRONG_KEY = 0x7f;
+ public static final byte READ_BACK_OFF = 0x76;
+
+ // Errors
+ public static final short SW_INVALID_SLOT_ID = 0x6a86;
+
+ public static final byte CLA = (byte) 0x80;
+
+ // Instructions
+ public static final byte INS_GET_NUM_SLOTS = 0x02;
+ public static final byte INS_WRITE = 0x4;
+ public static final byte INS_READ = 0x6;
+ public static final byte INS_ERASE_ALL = 0x8;
+}
diff --git a/apps/weaver/card/src/com/android/weaver/Slots.java b/apps/weaver/card/src/com/android/weaver/Slots.java
new file mode 100644
index 0000000..6f316ad
--- /dev/null
+++ b/apps/weaver/card/src/com/android/weaver/Slots.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.weaver;
+
+public interface Slots extends javacard.framework.Shareable {
+ /** @return The number of slots available. */
+ short getNumSlots();
+
+ /**
+ * Write the key and value to the identified slot.
+ *
+ * @param slotId ID of the slot to write to.
+ * @param key Buffer containing the key.
+ * @param keyOffset Offset of the key in the buffer.
+ * @param value Buffer containing the value.
+ * @param valueOffset Offset of the value in the buffer.
+ */
+ void write(short slotId, byte[] key, short keyOffset, byte[] value, short valueOffset);
+
+ /**
+ * Read the value from the identified slot.
+ *
+ * This is only successful if the key matches that stored in the slot.
+ *
+ * @param slotId ID of the slot to write to.
+ * @param key Buffer containing the key.
+ * @param keyOffset Offset of the key in the buffer.
+ * @param value Buffer to receive the value.
+ * @param valueOffset Offset into the buffer to write the value.
+ * @return Status byte indicating the success or otherwise of the read.
+ */
+ byte read(short slotId, byte[] key, short keyOffset, byte[] value, short valueOffset);
+
+ /** Erases the contents of all slots. */
+ void eraseAll();
+}
diff --git a/apps/weaver/card/src/com/android/weaver/Weaver.java b/apps/weaver/card/src/com/android/weaver/Weaver.java
new file mode 100644
index 0000000..af41465
--- /dev/null
+++ b/apps/weaver/card/src/com/android/weaver/Weaver.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.weaver;
+
+import javacard.framework.AID;
+import javacard.framework.APDU;
+import javacard.framework.Applet;
+import javacard.framework.ISO7816;
+import javacard.framework.ISOException;
+import javacard.framework.JCSystem;
+import javacard.framework.Shareable;
+import javacard.framework.Util;
+
+public class Weaver extends Applet {
+ // Keep constants in sync with esed
+ public static final byte[] CORE_APPLET_AID
+ = new byte[] {(byte)0xa0, 0x00, 0x00, 0x00, 0x62, 0x03, 0x01, 0x0c, 0x02, 0x01};
+ public static final byte CORE_APPLET_SLOTS_INTERFACE = 0;
+
+ private Slots mSlots;
+
+ protected Weaver() {
+ register();
+ }
+
+ /**
+ * Installs this applet.
+ *
+ * @param params the installation parameters
+ * @param offset the starting offset of the parameters
+ * @param length the length of the parameters
+ */
+ public static void install(byte[] params, short offset, byte length) {
+ new Weaver();
+ }
+
+ /**
+ * Get a handle on the slots after the applet is registered but before and APDUs are received.
+ */
+ @Override
+ public boolean select() {
+ AID coreAid = JCSystem.lookupAID(CORE_APPLET_AID, (short) 0, (byte) CORE_APPLET_AID.length);
+ if (coreAid == null) {
+ return false;
+ }
+
+ mSlots = (Slots) JCSystem.getAppletShareableInterfaceObject(
+ coreAid, CORE_APPLET_SLOTS_INTERFACE);
+ if (mSlots == null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Processes an incoming APDU.
+ *
+ * @param apdu the incoming APDU
+ * @exception ISOException with the response bytes per ISO 7816-4
+ */
+ @Override
+ public void process(APDU apdu) {
+ final byte buffer[] = apdu.getBuffer();
+ final byte cla = buffer[ISO7816.OFFSET_CLA];
+ final byte ins = buffer[ISO7816.OFFSET_INS];
+
+ // Handle standard commands
+ if (apdu.isISOInterindustryCLA()) {
+ if (cla == ISO7816.CLA_ISO7816) {
+ switch (ins) {
+ case ISO7816.INS_SELECT:
+ // Do nothing, successfully
+ return;
+ default:
+ ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
+ }
+ } else {
+ ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
+ }
+ }
+
+ // Handle custom applet commands
+ if (cla != Consts.CLA) {
+ ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
+ }
+
+ switch (ins) {
+ case Consts.INS_GET_NUM_SLOTS:
+ getNumSlots(apdu);
+ return;
+
+ case Consts.INS_WRITE:
+ write(apdu);
+ return;
+
+ case Consts.INS_READ:
+ read(apdu);
+ return;
+
+ case Consts.INS_ERASE_ALL:
+ eraseAll(apdu);
+ return;
+
+ default:
+ ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
+ }
+ }
+
+ /**
+ * Get the number of slots.
+ *
+ * p1: 0
+ * p2: 0
+ * data: _
+ */
+ private void getNumSlots(APDU apdu) {
+ p1p2Unused(apdu);
+ //dataUnused(apdu);
+ // TODO(ascull): how to handle the cases of APDU properly?
+ prepareToSend(apdu, (short) 4);
+ apdu.setOutgoingLength((short) 4);
+
+ final byte buffer[] = apdu.getBuffer();
+ Util.setShort(buffer, (short) 0, (short) 0);
+ Util.setShort(buffer, (short) 2, mSlots.getNumSlots());
+
+ apdu.sendBytes((short) 0, (byte) 4);
+ }
+
+ public static final short WRITE_DATA_BYTES
+ = Consts.SLOT_ID_BYTES + Consts.SLOT_KEY_BYTES + Consts.SLOT_VALUE_BYTES;
+ private static final byte WRITE_DATA_SLOT_ID_OFFSET = ISO7816.OFFSET_CDATA;
+ private static final byte WRITE_DATA_KEY_OFFSET
+ = WRITE_DATA_SLOT_ID_OFFSET + Consts.SLOT_ID_BYTES;
+ private static final byte WRITE_DATA_VALUE_OFFSET
+ = WRITE_DATA_KEY_OFFSET + Consts.SLOT_KEY_BYTES;
+
+ /**
+ * Write to a slot.
+ *
+ * p1: 0
+ * p2: 0
+ * data: [slot ID] [key data] [value data]
+ */
+ private void write(APDU apdu) {
+ p1p2Unused(apdu);
+ receiveData(apdu, WRITE_DATA_BYTES);
+
+ final byte buffer[] = apdu.getBuffer();
+ final short slotId = getSlotId(buffer, WRITE_DATA_SLOT_ID_OFFSET);
+ mSlots.write(slotId, buffer, WRITE_DATA_KEY_OFFSET, buffer, WRITE_DATA_VALUE_OFFSET);
+ }
+
+ public static final short READ_DATA_BYTES
+ = Consts.SLOT_ID_BYTES + Consts.SLOT_KEY_BYTES;
+ private static final byte READ_DATA_SLOT_ID_OFFSET = ISO7816.OFFSET_CDATA;
+ private static final byte READ_DATA_KEY_OFFSET
+ = WRITE_DATA_SLOT_ID_OFFSET + Consts.SLOT_ID_BYTES;
+
+ /**
+ * Read a slot.
+ *
+ * p1: 0
+ * p2: 0
+ * data: [slot ID] [key data]
+ */
+ private void read(APDU apdu) {
+ final byte successSize = 1 + Consts.SLOT_VALUE_BYTES;
+ final byte failSize = 1 + 4;
+
+ p1p2Unused(apdu);
+ receiveData(apdu, READ_DATA_BYTES);
+ prepareToSend(apdu, successSize);
+
+ final byte buffer[] = apdu.getBuffer();
+ final short slotId = getSlotId(buffer, READ_DATA_SLOT_ID_OFFSET);
+
+ final byte err = mSlots.read(slotId, buffer, READ_DATA_KEY_OFFSET, buffer, (short) 1);
+ buffer[(short) 0] = err;
+ if (err == Consts.READ_SUCCESS) {
+ apdu.setOutgoingLength(successSize);
+ apdu.sendBytes((short) 0, successSize);
+ } else {
+ apdu.setOutgoingLength(failSize);
+ apdu.sendBytes((short) 0, failSize);
+ }
+ }
+
+ /**
+ * Erase all slots.
+ *
+ * p1: 0
+ * p2: 0
+ * data: _
+ */
+ private void eraseAll(APDU apdu) {
+ p1p2Unused(apdu);
+ dataUnused(apdu);
+ mSlots.eraseAll();
+ }
+
+ /**
+ * Check that the parameters are 0.
+ *
+ * They are not being used but should be under control.
+ */
+ private void p1p2Unused(APDU apdu) {
+ final byte buffer[] = apdu.getBuffer();
+ if (buffer[ISO7816.OFFSET_P1] != 0 || buffer[ISO7816.OFFSET_P2] != 0) {
+ ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
+ }
+ }
+
+ /**
+ * Check that no data was provided.
+ */
+ private void dataUnused(APDU apdu) {
+ receiveData(apdu, (short) 0);
+ }
+
+ /**
+ * Calls setIncomingAndReceive() on the APDU and checks the length is as expected.
+ */
+ private void receiveData(APDU apdu, short expectedLength) {
+ final short bytesRead = apdu.setIncomingAndReceive();
+ if (apdu.getIncomingLength() != expectedLength || bytesRead != expectedLength) {
+ ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
+ }
+ }
+
+ /**
+ * The slot ID in the API is 32-bits but the applet works with 16-bit IDs.
+ */
+ private short getSlotId(byte[] bArray, short bOff) {
+ if (bArray[bOff] != 0 || bArray[(short) (bOff + 1)] != 0) {
+ ISOException.throwIt(Consts.SW_INVALID_SLOT_ID);
+ }
+ return Util.getShort(bArray,(short) (bOff + 2));
+ }
+
+ /**
+ * Calls setOutgoing() on the APDU, checks the length is as expected and calls
+ * setOutgoingLength() with that length.
+ *
+ * Still need to call setOutgoingLength() after this method.
+ */
+ private void prepareToSend(APDU apdu, short expectedMaxLength) {
+ final short outDataLen = apdu.setOutgoing();
+ if (outDataLen != expectedMaxLength) {
+ ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
+ }
+ }
+}
diff --git a/apps/weaver/card/src/com/android/weaver/core/CoreSlots.java b/apps/weaver/card/src/com/android/weaver/core/CoreSlots.java
new file mode 100644
index 0000000..35842d3
--- /dev/null
+++ b/apps/weaver/card/src/com/android/weaver/core/CoreSlots.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.weaver.core;
+
+import javacard.framework.ISO7816;
+import javacard.framework.ISOException;
+import javacard.framework.JCSystem;
+import javacard.framework.Util;
+
+import com.android.weaver.Consts;
+import com.android.weaver.Slots;
+
+//import com.nxp.id.jcopx.util.DSTimer;
+
+class CoreSlots implements Slots {
+ static final byte NUM_SLOTS = 64;
+
+ private Slot[] mSlots;
+
+ CoreSlots() {
+ // Allocate all memory up front
+ mSlots = new Slot[NUM_SLOTS];
+ for (short i = 0; i < NUM_SLOTS; ++i) {
+ mSlots[i] = new Slot();
+ }
+
+ final short intBytes = 4;
+ Slot.sRemainingBackoff = JCSystem.makeTransientByteArray(intBytes, JCSystem.CLEAR_ON_RESET);
+ }
+
+ @Override
+ public short getNumSlots() {
+ return NUM_SLOTS;
+ }
+
+ @Override
+ public void write(short rawSlotId, byte[] key, short keyOffset,
+ byte[] value, short valueOffset) {
+ final short slotId = validateSlotId(rawSlotId);
+ mSlots[slotId].write(key, keyOffset, value, valueOffset);
+ }
+
+ @Override
+ public byte read(short rawSlotId, byte[] key, short keyOffset,
+ byte[] outValue, short outOffset) {
+ final short slotId = validateSlotId(rawSlotId);
+ return mSlots[slotId].read(key, keyOffset, outValue, outOffset);
+ }
+
+ @Override
+ public void eraseAll() {
+ for (short i = 0; i < NUM_SLOTS; ++i) {
+ mSlots[i].erase();
+ }
+ }
+
+ /**
+ * Check the slot ID is within range and convert it to a short.
+ */
+ private short validateSlotId(short slotId) {
+ // slotId is unsigned so if the signed version is negative then it is far too big
+ if (slotId < 0 || slotId >= NUM_SLOTS) {
+ ISOException.throwIt(Consts.SW_INVALID_SLOT_ID);
+ }
+ return slotId;
+ }
+
+ private static class Slot {
+ private static byte[] sRemainingBackoff;
+
+ private byte[] mKey = new byte[Consts.SLOT_KEY_BYTES];
+ private byte[] mValue = new byte[Consts.SLOT_VALUE_BYTES];
+ private short mFailureCount;
+ //private DSTimer mBackoffTimer;
+
+ /**
+ * Transactionally reset the slot with a new key and value.
+ *
+ * @param keyBuffer the buffer containing the key data
+ * @param keyOffset the offset of the key in its buffer
+ * @param valueBuffer the buffer containing the value data
+ * @param valueOffset the offset of the value in its buffer
+ */
+ public void write(
+ byte[] keyBuffer, short keyOffset, byte[] valueBuffer, short valueOffset) {
+ JCSystem.beginTransaction();
+ Util.arrayCopy(keyBuffer, keyOffset, mKey, (short) 0, Consts.SLOT_KEY_BYTES);
+ Util.arrayCopy(valueBuffer, valueOffset, mValue, (short) 0, Consts.SLOT_VALUE_BYTES);
+ mFailureCount = 0;
+ //mBackoffTimer = DSTimer.getInstance();
+ JCSystem.commitTransaction();
+ }
+
+ /**
+ * Transactionally clear the slot.
+ */
+ public void erase() {
+ JCSystem.beginTransaction();
+ arrayFill(mKey, (short) 0, Consts.SLOT_KEY_BYTES, (byte) 0);
+ arrayFill(mValue, (short) 0, Consts.SLOT_VALUE_BYTES, (byte) 0);
+ mFailureCount = 0;
+ //mBackoffTimer.stopTimer();
+ JCSystem.commitTransaction();
+ }
+
+ /**
+ * Copy the slot's value to the buffer if the provided key matches the slot's key.
+ *
+ * @param keyBuffer the buffer containing the key
+ * @param keyOffset the offset of the key in its buffer
+ * @param outBuffer the buffer to copy the value or backoff time into
+ * @param outOffset the offset into the output buffer
+ * @return status code
+ */
+ public byte read(byte[] keyBuffer, short keyOffset, byte[] outBuffer, short outOffset) {
+ // Check timeout has expired or hasn't been started
+ //mBackoffTimer.getRemainingTime(sRemainingBackoff, (short) 0);
+ if (sRemainingBackoff[0] != 0 || sRemainingBackoff[1] != 0 ||
+ sRemainingBackoff[2] != 0 || sRemainingBackoff[3] != 0) {
+ Util.arrayCopyNonAtomic(
+ sRemainingBackoff, (short) 0, outBuffer, outOffset, (byte) 4);
+ return Consts.READ_BACK_OFF;
+ }
+
+ // Assume this to be a failed attempt until proven otherwise. This means losing power
+ // midway cannot be abused for extra attempts.
+ JCSystem.beginTransaction();
+ if (mFailureCount != 0x7fff) {
+ mFailureCount += 1;
+ }
+ throttle(sRemainingBackoff, (short) 0, mFailureCount);
+ //mBackoffTimer.startTimer(
+ // sRemainingBackoff, (short) 0, DSTimer.DST_POWEROFFMODE_FALLBACK);
+ JCSystem.commitTransaction();
+
+ // Check the key matches and copy out the value if it does
+ if (Util.arrayCompare(
+ keyBuffer, keyOffset, mKey, (short) 0, Consts.SLOT_KEY_BYTES) != 0) {
+ Util.arrayCopyNonAtomic(
+ sRemainingBackoff, (short) 0, outBuffer, outOffset, (byte) 4);
+ return Consts.READ_WRONG_KEY;
+ }
+
+ // This attempt was successful so reset the failures
+ JCSystem.beginTransaction();
+ mFailureCount = 0;
+ //mBackoffTimer.stopTimer();
+ JCSystem.commitTransaction();
+
+ Util.arrayCopyNonAtomic(
+ mValue, (short) 0, outBuffer, outOffset, Consts.SLOT_VALUE_BYTES);
+ return Consts.READ_SUCCESS;
+ }
+
+ /**
+ * 3.0.3 does not offset Util.arrayFill
+ */
+ private static void arrayFill(byte[] bArray, short bOff, short bLen, byte bValue) {
+ for (short i = 0; i < bLen; ++i) {
+ bArray[(short) (bOff + i)] = bValue;
+ }
+ }
+
+ /**
+ * Calculates the timeout in milliseconds as a function of the failure
+ * counter 'x' as follows:
+ *
+ * [0, 5) -> 0
+ * 5 -> 30
+ * [6, 10) -> 0
+ * [11, 30) -> 30
+ * [30, 140) -> 30 * (2^((x - 30)/10))
+ * [140, inf) -> 1 day
+ *
+ * The 32-bit millisecond timeout is written to the array.
+ */
+ private static void throttle(byte[] bArray, short bOff, short failureCount) {
+ short highWord = 0;
+ short lowWord = 0;
+
+ final short thirtySecondsInMilliseconds = 0x7530; // = 1000 * 30
+ if (failureCount == 0) {
+ // 0s
+ } else if (failureCount > 0 && failureCount <= 10) {
+ if (failureCount % 5 == 0) {
+ // 30s
+ lowWord = thirtySecondsInMilliseconds;
+ } else {
+ // 0s
+ }
+ } else if (failureCount < 30) {
+ // 30s
+ lowWord = thirtySecondsInMilliseconds;
+ } else if (failureCount < 140) {
+ // 30 * (2^((x - 30)/10))
+ final short shift = (short) ((short) (failureCount - 30) / 10);
+ highWord = (short) (thirtySecondsInMilliseconds >> (16 - shift));
+ lowWord = (short) (thirtySecondsInMilliseconds << shift);
+ } else {
+ // 1 day in ms = 1000 * 60 * 60 * 24 = 0x526 5C00
+ highWord = 0x0526;
+ lowWord = 0x5c00;
+ }
+
+ // Write the value to the buffer
+ Util.setShort(bArray, bOff, highWord);
+ Util.setShort(bArray, (short) (bOff + 2), lowWord);
+ }
+ }
+}
diff --git a/apps/weaver/card/src/com/android/weaver/core/WeaverCore.java b/apps/weaver/card/src/com/android/weaver/core/WeaverCore.java
new file mode 100644
index 0000000..35a73c5
--- /dev/null
+++ b/apps/weaver/card/src/com/android/weaver/core/WeaverCore.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.weaver.core;
+
+import javacard.framework.AID;
+import javacard.framework.APDU;
+import javacard.framework.Applet;
+import javacard.framework.Shareable;
+
+class WeaverCore extends Applet {
+ public static final byte[] COMMAPP_APPLET_AID
+ = new byte[] {(byte) 0xa0, 0x00, 0x00, 0x00, 0x62, 0x03, 0x01, 0x0c, 0x01, 0x01};
+
+ private CoreSlots mSlots;
+
+ protected WeaverCore() {
+ // Allocate all memory up front
+ mSlots = new CoreSlots();
+ register();
+ }
+
+ /**
+ * Installs this applet.
+ *
+ * @param params the installation parameters
+ * @param offset the starting offset of the parameters
+ * @param length the length of the parameters
+ */
+ public static void install(byte[] params, short offset, byte length) {
+ new WeaverCore();
+ }
+
+ /**
+ * This applet can only be accessed from other applets.
+ */
+ @Override
+ public boolean select() {
+ return false;
+ }
+
+ /**
+ * Returns and instance of the {@link Slots} interface.
+ *
+ * @param AID The requesting applet's AID must be that of the CoreApp.
+ * @param arg Must be {@link #SLOTS_INTERFACE} else returns {@code null}.
+ */
+ @Override
+ public Shareable getShareableInterfaceObject(AID clientAid, byte arg) {
+ if (!clientAid.equals(COMMAPP_APPLET_AID, (short) 0, (byte) COMMAPP_APPLET_AID.length)) {
+ return null;
+ }
+ return (arg == 0) ? mSlots : null;
+ }
+
+ /**
+ * Should never be called.
+ */
+ @Override
+ public void process(APDU apdu) {
+ }
+}
diff --git a/apps/weaver/include/ese/app/weaver.h b/apps/weaver/include/ese/app/weaver.h
new file mode 100644
index 0000000..67bea4b
--- /dev/null
+++ b/apps/weaver/include/ese/app/weaver.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ESE_APP_WEAVER_H_
+#define ESE_APP_WEAVER_H_ 1
+
+#include "../../../../../libese/include/ese/ese.h"
+#include "../../../../../libese/include/ese/log.h"
+#include "../../../../../libese-sysdeps/include/ese/sysdeps.h"
+
+#include "../../../../include/ese/app/result.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * EseWeaverSession carries the necessary start for interfacing
+ * with the methods below.
+ *
+ * Its usage follows a lifecycle like:
+ *
+ * EseAppResult res;
+ * EseWeaverSession session;
+ * ese_weaver_session_init(&session);
+ * res = ese_weaver_session_open(ese, &session);
+ * if (res != ESE_APP_RESULT_OK) {
+ * ... handle error (especially cooldown) ...
+ * }
+ * ... ese_weaver_* ...
+ * ese_weaver_session_close(&session);
+ *
+ */
+struct EseWeaverSession {
+ struct EseInterface *ese;
+ bool active;
+ uint8_t channel_id;
+};
+
+/** The keys are 16 bytes */
+const uint8_t kEseWeaverKeySize = 16;
+
+/** The values are 16 bytes */
+const uint8_t kEseWeaverValueSize = 16;
+
+
+const int ESE_WEAVER_READ_WRONG_KEY = ese_make_app_result(0x6a, 0x85);
+const int ESE_WEAVER_READ_TIMEOUT = ese_make_app_result(0x6a, 0x87);
+
+/**
+ * Initializes a pre-allocated |session| for use.
+ */
+void ese_weaver_session_init(struct EseWeaverSession *session);
+
+/**
+ * Configures a communication session with the Storage applet using a logical
+ * channel on an already open |ese| object.
+ *
+ * @returns ESE_APP_RESULT_OK on success.
+ */
+EseAppResult ese_weaver_session_open(struct EseInterface *ese, struct EseWeaverSession *session);
+
+/**
+ * Shuts down the logical channel with the Storage applet and invalidates
+ * the |session| internal state.
+ *
+ * @returns ESE_APP_RESULT_OK on success.
+ */
+EseAppResult ese_weaver_session_close(struct EseWeaverSession *session);
+
+/**
+ * Retreives the number of slots available.
+ * @returns ESE_APP_RESULT_OK if |numSlots| contains a valid value.
+ */
+EseAppResult ese_weaver_get_num_slots(struct EseWeaverSession *session, uint32_t *numSlots);
+
+/**
+ * Writes a new key-value pair into the slot.
+ *
+ * |key| and |value| must be buffers of sizes |kEseWeaverKeySize| and
+ * |kEseWeaverValueSize| respectively.
+ *
+ * @returns ESE_APP_RESULT_OK on success.
+ */
+EseAppResult ese_weaver_write(struct EseWeaverSession *session, uint32_t slotId,
+ const uint8_t *key, const uint8_t *value);
+
+
+/**
+ * Reads the value in the slot provided the correct key was passed.
+ *
+ * |key| and |value| must be buffers of sizes |kEseWeaverKeySize| and
+ * |kEseWeaverValueSize| respectively.
+ *
+ * @returns ESE_APP_RESULT_OK if |value| was filled with the value.
+ * ESE_WEAVER_READ_WRONG_KEY if |key| was wrong and |timeout| contains
+ * a valid timeout.
+ * ESE_WEAVER_READ_TIMEOUT if Weaver is in backoff mode and |timeout|
+ * contains a valid timeout.
+ */
+EseAppResult ese_weaver_read(struct EseWeaverSession *session, uint32_t slotId,
+ const uint8_t *key, uint8_t *value, uint32_t *timeout);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ESE_APP_WEAVER_H_ */
diff --git a/apps/weaver/weaver.c b/apps/weaver/weaver.c
new file mode 100644
index 0000000..942adb0
--- /dev/null
+++ b/apps/weaver/weaver.c
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "include/ese/app/weaver.h"
+
+/* Non-static, but visibility=hidden so they can be used in test. */
+const uint8_t kManageChannelOpen[] = {0x00, 0x70, 0x00, 0x00, 0x01};
+const uint32_t kManageChannelOpenLength = (uint32_t)sizeof(kManageChannelOpen);
+const uint8_t kManageChannelClose[] = {0x00, 0x70, 0x80, 0x00, 0x00};
+
+const uint8_t kSelectApplet[] = {0x00, 0xA4, 0x04, 0x00, 0x0A, 0xA0,
+ 0x00, 0x00, 0x00, 0x62, 0x03, 0x01,
+ 0x0C, 0x01, 0x01, 0x00};
+const uint32_t kSelectAppletLength = (uint32_t)sizeof(kSelectApplet);
+// Supported commands.
+const uint8_t kGetNumSlots[] = {0x80, 0x02, 0x00, 0x00, 0x04};
+const uint8_t kWrite[] = {0x80, 0x04, 0x00, 0x00,
+ 4 + kEseWeaverKeySize +
+ kEseWeaverValueSize}; // slotid + key + value
+const uint8_t kRead[] = {0x80, 0x06, 0x00, 0x00,
+ 4 + kEseWeaverKeySize}; // slotid + key
+const uint8_t kEraseAll[] = {0x80, 0x08, 0x00, 0x00};
+
+// Build 32-bit int from big endian bytes
+static uint32_t get_uint32(uint8_t buf[4]) {
+ uint32_t x = buf[3];
+ x |= buf[2] << 8;
+ x |= buf[1] << 16;
+ x |= buf[0] << 24;
+ return x;
+}
+
+static void put_uint32(uint8_t buf[4], uint32_t val) {
+ buf[0] = 0xff & (val >> 24);
+ buf[1] = 0xff & (val >> 16);
+ buf[2] = 0xff & (val >> 8);
+ buf[3] = 0xff & val;
+}
+
+EseAppResult check_apdu_status(uint8_t code[2]) {
+ if (code[0] == 0x90 && code[1] == 0x00) {
+ return ESE_APP_RESULT_OK;
+ }
+ if (code[0] == 0x66 && code[1] == 0xA5) {
+ return ESE_APP_RESULT_ERROR_COOLDOWN;
+ }
+ if (code[0] == 0x6A && code[1] == 0x83) {
+ return ESE_APP_RESULT_ERROR_UNCONFIGURED;
+ }
+ /* TODO(wad) Bubble up the error code if needed. */
+ ALOGE("unhandled response %.2x %.2x", code[0], code[1]);
+ return ese_make_os_result(code[0], code[1]);
+}
+
+ESE_API void ese_weaver_session_init(struct EseWeaverSession *session) {
+ session->ese = NULL;
+ session->active = false;
+ session->channel_id = 0;
+}
+
+ESE_API EseAppResult ese_weaver_session_open(struct EseInterface *ese,
+ struct EseWeaverSession *session) {
+ struct EseSgBuffer tx[2];
+ struct EseSgBuffer rx;
+ uint8_t rx_buf[32];
+ int rx_len;
+ if (!ese || !session) {
+ ALOGE("Invalid |ese| or |session|");
+ return ESE_APP_RESULT_ERROR_ARGUMENTS;
+ }
+ if (session->active == true) {
+ ALOGE("|session| is already active");
+ return ESE_APP_RESULT_ERROR_ARGUMENTS;
+ }
+ /* Instantiate a logical channel */
+ rx_len = ese_transceive(ese, kManageChannelOpen, sizeof(kManageChannelOpen),
+ rx_buf, sizeof(rx_buf));
+ if (ese_error(ese)) {
+ ALOGE("transceive error: code:%d message:'%s'", ese_error_code(ese),
+ ese_error_message(ese));
+ return ESE_APP_RESULT_ERROR_COMM_FAILED;
+ }
+ if (rx_len < 0) {
+ ALOGE("transceive error: rx_len: %d", rx_len);
+ return ESE_APP_RESULT_ERROR_COMM_FAILED;
+ }
+ if (rx_len < 2) {
+ ALOGE("transceive error: reply too short");
+ return ESE_APP_RESULT_ERROR_COMM_FAILED;
+ }
+ EseAppResult ret;
+ ret = check_apdu_status(&rx_buf[rx_len - 2]);
+ if (ret != ESE_APP_RESULT_OK) {
+ ALOGE("MANAGE CHANNEL OPEN failed with error code: %x %x",
+ rx_buf[rx_len - 2], rx_buf[rx_len - 1]);
+ return ret;
+ }
+ if (rx_len < 3) {
+ ALOGE("transceive error: successful reply unexpectedly short");
+ return ESE_APP_RESULT_ERROR_COMM_FAILED;
+ }
+ session->ese = ese;
+ session->channel_id = rx_buf[rx_len - 3];
+
+ /* Select Weaver Applet. */
+ uint8_t chan = kSelectApplet[0] | session->channel_id;
+ tx[0].base = &chan;
+ tx[0].len = 1;
+ tx[1].base = (uint8_t *)&kSelectApplet[1];
+ tx[1].len = sizeof(kSelectApplet) - 1;
+ rx.base = &rx_buf[0];
+ rx.len = sizeof(rx_buf);
+ rx_len = ese_transceive_sg(ese, tx, 2, &rx, 1);
+ if (rx_len < 0 || ese_error(ese)) {
+ ALOGE("transceive error: caller should check ese_error()");
+ return ESE_APP_RESULT_ERROR_COMM_FAILED;
+ }
+ if (rx_len < 2) {
+ ALOGE("transceive error: reply too short");
+ return ESE_APP_RESULT_ERROR_COMM_FAILED;
+ }
+ ret = check_apdu_status(&rx_buf[rx_len - 2]);
+ if (ret != ESE_APP_RESULT_OK) {
+ ALOGE("SELECT failed with error code: %x %x", rx_buf[rx_len - 2],
+ rx_buf[rx_len - 1]);
+ return ret;
+ }
+ session->active = true;
+ return ESE_APP_RESULT_OK;
+}
+
+ESE_API EseAppResult
+ese_weaver_session_close(struct EseWeaverSession *session) {
+ uint8_t rx_buf[32];
+ int rx_len;
+ if (!session || !session->ese) {
+ return ESE_APP_RESULT_ERROR_ARGUMENTS;
+ }
+ if (!session->active || session->channel_id == 0) {
+ return ESE_APP_RESULT_ERROR_ARGUMENTS;
+ }
+ /* Release the channel */
+ uint8_t close_channel[sizeof(kManageChannelClose)];
+ ese_memcpy(close_channel, kManageChannelClose, sizeof(kManageChannelClose));
+ close_channel[0] |= session->channel_id;
+ close_channel[3] |= session->channel_id;
+ rx_len = ese_transceive(session->ese, close_channel, sizeof(close_channel),
+ rx_buf, sizeof(rx_buf));
+ if (rx_len < 0 || ese_error(session->ese)) {
+ return ESE_APP_RESULT_ERROR_COMM_FAILED;
+ }
+ if (rx_len < 2) {
+ return ESE_APP_RESULT_ERROR_COMM_FAILED;
+ }
+ EseAppResult ret;
+ ret = check_apdu_status(&rx_buf[rx_len - 2]);
+ if (ret != ESE_APP_RESULT_OK) {
+ return ret;
+ }
+ session->channel_id = 0;
+ session->active = false;
+ return ESE_APP_RESULT_OK;
+}
+
+ESE_API EseAppResult ese_weaver_get_num_slots(struct EseWeaverSession *session,
+ uint32_t *numSlots) {
+ if (!session || !session->ese || !session->active) {
+ return ESE_APP_RESULT_ERROR_ARGUMENTS;
+ }
+ if (!session->active || session->channel_id == 0) {
+ return ESE_APP_RESULT_ERROR_ARGUMENTS;
+ }
+ if (!numSlots) {
+ return ESE_APP_RESULT_ERROR_ARGUMENTS;
+ }
+
+ // Prepare command
+ uint8_t get_num_slots[sizeof(kGetNumSlots)];
+ ese_memcpy(get_num_slots, kGetNumSlots, sizeof(kGetNumSlots));
+ get_num_slots[0] |= session->channel_id;
+
+ // Send command
+ uint8_t rx_buf[6];
+ const int rx_len =
+ ese_transceive(session->ese, get_num_slots, sizeof(get_num_slots), rx_buf,
+ sizeof(rx_buf));
+
+ // Check for errors
+ if (rx_len < 2 || ese_error(session->ese)) {
+ ALOGE("Failed to get num slots");
+ return ESE_APP_RESULT_ERROR_COMM_FAILED;
+ }
+ if (rx_len == 2) {
+ ALOGE("ese_weaver_get_num_slots: SE exception");
+ EseAppResult ret = check_apdu_status(rx_buf);
+ return ret;
+ }
+ if (rx_len != 6) {
+ ALOGE("Unexpected response from Weaver applet");
+ return ESE_APP_RESULT_ERROR_COMM_FAILED;
+ }
+
+ *numSlots = get_uint32(rx_buf);
+ return ESE_APP_RESULT_OK;
+}
+
+ESE_API EseAppResult ese_weaver_write(struct EseWeaverSession *session,
+ uint32_t slotId, const uint8_t *key,
+ const uint8_t *value) {
+ if (!session || !session->ese || !session->active) {
+ return ESE_APP_RESULT_ERROR_ARGUMENTS;
+ }
+ if (!session->active || session->channel_id == 0) {
+ return ESE_APP_RESULT_ERROR_ARGUMENTS;
+ }
+ if (!key || !value) {
+ return ESE_APP_RESULT_ERROR_ARGUMENTS;
+ }
+
+ // Prepare data to send
+ struct EseSgBuffer tx[5];
+ uint8_t chan = kWrite[0] | session->channel_id;
+ tx[0].base = &chan;
+ tx[0].len = 1;
+ tx[1].base = (uint8_t *)&kWrite[1];
+ tx[1].len = sizeof(kWrite) - 1;
+
+ // Slot ID in big endian
+ uint8_t slot_id[4];
+ put_uint32(slot_id, slotId);
+ tx[2].base = slot_id;
+ tx[2].len = sizeof(slot_id);
+
+ // Key and value
+ tx[3].c_base = key;
+ tx[3].len = kEseWeaverKeySize;
+ tx[4].c_base = value;
+ tx[4].len = kEseWeaverValueSize;
+
+ // Prepare buffer for result
+ struct EseSgBuffer rx;
+ uint8_t rx_buf[2];
+ rx.base = rx_buf;
+ rx.len = sizeof(rx_buf);
+
+ // Send the command
+ const int rx_len = ese_transceive_sg(session->ese, tx, 5, &rx, 1);
+
+ // Check for errors
+ if (rx_len < 2 || ese_error(session->ese)) {
+ ALOGE("Failed to write to slot");
+ return ESE_APP_RESULT_ERROR_COMM_FAILED;
+ }
+ if (rx_len > 2) {
+ ALOGE("Unexpected response from Weaver applet");
+ return ESE_APP_RESULT_ERROR_COMM_FAILED;
+ }
+ return check_apdu_status(rx_buf);
+}
+
+ESE_API EseAppResult ese_weaver_read(struct EseWeaverSession *session,
+ uint32_t slotId, const uint8_t *key,
+ uint8_t *value, uint32_t *timeout) {
+ if (!session || !session->ese || !session->active) {
+ return ESE_APP_RESULT_ERROR_ARGUMENTS;
+ }
+ if (!session->active || session->channel_id == 0) {
+ return ESE_APP_RESULT_ERROR_ARGUMENTS;
+ }
+ if (!key || !value || !timeout) {
+ return ESE_APP_RESULT_ERROR_ARGUMENTS;
+ }
+
+ // Prepare data to send
+ struct EseSgBuffer tx[5];
+ uint8_t chan = kRead[0] | session->channel_id;
+ tx[0].base = &chan;
+ tx[0].len = 1;
+ tx[1].base = (uint8_t *)&kRead[1];
+ tx[1].len = sizeof(kRead) - 1;
+
+ // Slot ID in big endian
+ uint8_t slot_id[4];
+ put_uint32(slot_id, slotId);
+ tx[2].base = slot_id;
+ tx[2].len = sizeof(slot_id);
+
+ // Key of 16 bytes
+ tx[3].c_base = key;
+ tx[3].len = kEseWeaverKeySize;
+
+ // Value response is 16 bytes
+ const uint8_t maxResponse = 1 + kEseWeaverValueSize;
+ tx[4].c_base = &maxResponse;
+ tx[4].len = 1;
+
+ // Prepare buffer for result
+ struct EseSgBuffer rx[2];
+ uint8_t appletStatus;
+ rx[0].base = &appletStatus;
+ rx[0].len = 1;
+ rx[1].base = value;
+ rx[1].len = kEseWeaverValueSize;
+ uint8_t rx_buf[2];
+ rx[2].base = rx_buf;
+ rx[2].len = sizeof(rx_buf);
+
+ // Send the command
+ const int rx_len = ese_transceive_sg(session->ese, tx, 5, rx, 2);
+
+ // Check for errors
+ if (rx_len < 2 || ese_error(session->ese)) {
+ ALOGE("Failed to write to slot");
+ return ESE_APP_RESULT_ERROR_COMM_FAILED;
+ }
+ if (rx_len == 2) {
+ rx_buf[0] = appletStatus;
+ rx_buf[1] = value[0];
+ ALOGE("ese_weaver_read: SE exception");
+ EseAppResult ret = check_apdu_status(rx_buf);
+ return ret;
+ }
+ if (rx_len < 7) {
+ ALOGE("Unexpected response from Weaver applet");
+ return ESE_APP_RESULT_ERROR_COMM_FAILED;
+ }
+ const uint8_t READ_SUCCESS = 0x00;
+ const uint8_t READ_WRONG_KEY = 0x7f;
+ const uint8_t READ_BACK_OFF = 0x76;
+ // wrong key
+ if (appletStatus == READ_WRONG_KEY) {
+ ALOGI("ese_weaver_read wrong key provided");
+ *timeout = get_uint32(value);
+ return ESE_WEAVER_READ_WRONG_KEY;
+ }
+ // backoff
+ if (appletStatus == READ_BACK_OFF) {
+ ALOGI("ese_weaver_read wrong key provided");
+ *timeout = get_uint32(value);
+ return ESE_WEAVER_READ_TIMEOUT;
+ }
+ if (rx_len != 19) {
+ ALOGE("Unexpected response from Weaver applet");
+ return ESE_APP_RESULT_ERROR_COMM_FAILED;
+ }
+ return ESE_APP_RESULT_OK;
+}
+
+// TODO: erase all, not currently used