apps/boot: boot applet, library, and tool.

This change covers a lot of ground (sorry).

It provides an applet and three packages for use on an NXP smartcard
with some additional APIs.  The applet and packages provide secure
storage for bootloader use:
- rollback indices
- boot behavior "locks" (carrier, device, boot, owner)

Rollback index storage is implemented in VersionStorage.java.

Applet state (pre-production/production) and hardware state
(inBootloader) are provided via GlobalStateImpl.java.  This file is an
implementation of the globalstate.OwnerInterface, a shareable interface
which gets its own .cap file (via OwnerInterface.java).  The client
interface is implemented in CallbackInterface.java and is primarily used
to allow the primary applet to indicate a data clearing operation to
other applets.

Storage.java provides the overall Applet interface.

All the locks are implemented in CarrierLock.java and BasicLock.java.
Each lock can only be toggled if the required locks are unlocked.
(addRequiredLock(x)).

The CarrierLock, relies on a (test, in this CL) key for
RSA_SHA256 PKCS#1 padded signature over a device specific hash and a
server provided rollback nonce (monotonically increasing 64-bit value).

The OwnerLock also has specific behavior. It is a lock that impacts the
behavior of the system when BootLock is set.   It indicates to the boot
loader to use a different verifying key than the one provided by the
bootloader itself.  That key (or its hash) is stored as OwnerLock
metadata.

With the applets, is an ant build.xml.  As building applets is not
trivially supported in the Android build system, they can be built by
running 'ant' in the same directory as the build file.  This will fetch
any necessary third party dependencies, then build the cap files.  The
globalstate*.caps must be installed before avb_storage.cap.

This change also includes an interface library built on top of libese
and an example command line tool for using the applet.  The tool and
library setup a logical channel and communicate with the applet and some
initial unittests are in place with a stubbed C++ Ese hardware instance.

The library errors codes and applet status codes need to be refined. As
is, there is a baseline for OS and APPLET errors which include the
embedded status. While those statuses may change, the primary value is
still stable.  In the future, the applet should implement its errors
via an exception class that gets translated to the prefix status codes
(as long as they don't overlap with the APDU status codes for the most
part).

This change also includes documentation for the work in
README.md and a single extension to the pn80t common code to handle
cooldown data collection requests.

(Building the applet requires software from NXP which is not (yet)
 easily available.)

NOTE: CarrierLock is using a _dev_ key.

Test: all functions tested manually with the tool and ese-replay except
      ese-boot-tool verify-key auto # passes
      Tested each nonce edge case manually too -- msb >, ==, < and lsb >, ==, <

Bug: 34460238
Bug: 34467857
Bug: 34684505

Change-Id: I64223548b5c95f24044eb98c9f999468c73bcf4a
diff --git a/Android.bp b/Android.bp
index 6fe8a42..4a27106 100644
--- a/Android.bp
+++ b/Android.bp
@@ -19,6 +19,7 @@
     "libese",
     "libese-teq1",
     "libese-hw",
+    "apps",
     "examples",
     "tools",
     "esed",
diff --git a/apps/Android.bp b/apps/Android.bp
new file mode 100644
index 0000000..8bfd0b6
--- /dev/null
+++ b/apps/Android.bp
@@ -0,0 +1,27 @@
+//
+// 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_defaults {
+    name: "libese-app-defaults",
+    defaults: ["libese-defaults"],
+
+    // Ensure that only explicitly exported symbols are visible.
+    local_include_dirs: ["include"],
+    export_include_dirs: ["include"],
+    cflags: ["-fvisibility=internal"],
+}
+
+subdirs = ["boot"]
diff --git a/apps/boot/Android.bp b/apps/boot/Android.bp
new file mode 100644
index 0000000..8c9cc25
--- /dev/null
+++ b/apps/boot/Android.bp
@@ -0,0 +1,58 @@
+//
+// 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-boot",
+    proprietary: true,
+    defaults: ["libese-app-defaults"],
+    srcs: ["boot.c"],
+    host_supported: true,
+    shared_libs: ["liblog", "libese", "libese-sysdeps"],
+}
+
+cc_library {
+    name: "libese-app-boot-fortest",
+    proprietary: true,
+    defaults: ["libese-app-defaults"],
+    srcs: ["boot.c"],
+    host_supported: true,
+    cflags: ["-fvisibility=default"],
+    shared_libs: ["liblog", "libese", "libese-sysdeps"],
+}
+
+
+cc_binary {
+    name: "ese-boot-tool",
+    proprietary: true,
+    srcs: ["ese_boot_tool.cpp"],
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libese",
+        "libese-sysdeps",
+        "libese-app-boot",
+        "libese-teq1",
+        "libese-hw-nxp-pn80t-nq-nci",
+    ],
+    host_supported: false,
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+}
+
+subdirs = ["tests"]
diff --git a/apps/boot/README.md b/apps/boot/README.md
new file mode 100644
index 0000000..144c0f2
--- /dev/null
+++ b/apps/boot/README.md
@@ -0,0 +1,273 @@
+# Verified Boot Storage Applet for AVB 2.0
+
+  - Status: Draft as of April 6, 2017
+
+## Introduction
+
+The application and support libraries in this directory provide
+a mechanism for a device's bootloader, using [AVB](https://android.googlesource.com/platform/external/avb/),
+to store sensitive information.  For a bootloader, sensitive information
+includes whether the device is unlocked or locked, whether it is unlockable,
+and what the minimum version of the OS/kernel is allowed to be booted.  It
+may also store other sensitive flags.
+
+The verified boot storage applet provides a mechanism to store this
+data in a way that enforceѕ the expected policies even if the higher level
+operating system is compromised or operates in an unexpected fashion.
+
+
+## Design Overview
+
+The Verified Boot Storage Applet, VBSA, provides three purpose-built
+interfaces:
+
+  - Lock storage and policy enforcement
+  - Rollback index storage
+  - Applet state
+
+Each will be detailed below.
+
+### Locks
+
+There are four supported lock types:
+
+  - LOCK\_CARRIER
+  - LOCK\_DEVICE
+  - LOCK\_BOOT
+  - LOCK\_OWNER
+
+Each lock has a single byte of "lock" storage.  If that byte is 0x0, then it is
+unlocked, or cleared.  If it is non-zero, then the lock is locked.  Any
+non-zero value is valid and may be used by the bootloader if any additional
+internal flagging is necessary.
+
+In addition, a lock may have associated metadata which must be supplied during
+lock or unlock, or both.
+
+See ese\_boot\_lock\_\* in include/ese/app/boot.h for the specific interfaces.
+
+
+#### LOCK\_CARRIER
+
+The Carrier Lock implements a lock which can only be set when the device is not
+in production and can only be unlocked if provided a cryptographic signature.
+
+This lock is available for use to implement "sim locking" or "phone locking"
+such that the carrier can determine if the device is allowed to boot an
+unsigned or unknown system image.
+
+To provision this lock, device-specific data must be provided in an exact
+format.  An example of this can be found in
+'ese-boot-tool.cpp':collect\_device\_data().  This data is then provided to
+the applet using ese\_boot\_lock\_xset().
+
+##### Metadata format for locking/provisioning
+
+The following system attributes must be collected in the given order:
+
+  - ro.product.brand
+  - ro.product.device
+  - ro.build.product
+  - ro.serialno
+  - [Modem ID: MEID or IMEI]
+  - ro.product.manufacturer
+  - ro.product.model
+
+The data is serialized as follows:
+
+    \[length||string\]\[...\]
+
+The length is a uint8\_t and the value is appended as a stream of
+uint8\_t values.
+
+This data is then prefixed with the desired non-zero lock value before
+being submitted to the applet.  If successful, the applet will have
+stored a SHA256 hash of the device data
+
+Note, LOCK\_CARRIER can only be locked (non-zero lock value) when the
+applet is in 'production' mode.
+
+##### Clearing/unlocking
+
+If LOCK\_CARRIER is set to a non-zero value and the applet is in
+production mode, then clearing the lock value requires authorization.
+
+Authorization comes in the form of a RSA\_SHA256-PKCS#1 signature over
+the provisioned device data SHA256 hash and a supplied montonically
+increasing "nonce".
+
+The nonce value must be higher than the last seen nonce value and the
+signature must validate using public key internally stored in the
+applet (CarrierLock.java:PK\_MOD).
+
+To perform a clear, ese\_boot\_lock\_xset() must be called with lock
+data that begins with 0x0, to clear the lock, and then contains data of
+the following format:
+
+    unlockToken = VERSION || NONCE || SIGNATURE
+
+    SIGNATURE = RSA_Sign(SHA256(deviceData))
+
+  - The version is a little endian uint64\_t (8 bytes).
+  - The nonce is a little endian uint64\_t (8 bytes).
+  - The signature is a RSA 2048-bit with SHA-256 PKCS#1 v1.5 (256 bytes).
+
+On unlock, the device data hash is cleared.
+
+##### Testing
+
+It is possible to test the key with a valid signature but a fake
+internal nonce and fake internal device data using
+ese\_boot\_carrier\_lock\_test().  When using this interface, it
+expects the same unlock token as in the prior but prefixes with the
+fake data:
+
+    testVector = LAST_NONCE || DEVICE_DATA || unlockToken
+
+  - The last nonce is the value the nonce is compared against (8 bytes).
+  - Device data is a replacement for the internally stored SHA-256
+    hash of the deviec data. (32 bytes).
+
+#### LOCK\_DEVICE
+
+The device lock is one of the setting used by the bootloader to
+determine if the boot lock can be changed.  It may only be set by the
+operating system and is meant to protect the device from being reflashed
+by someone that cannot unlock or access the OS.  This may also be used
+by an enterprise administrator to control lock policy for managed
+devices.
+
+As LOCK\_DEVICE has not metadata, it can be set and retrieved using
+ese\_boot\_lock\_set() and ese\_boot\_lock\_get().
+
+#### LOCK\_BOOT
+
+The boot lock is used by the bootloader to control whether it should
+only boot verified system software or not.  When the lock value
+is cleared (0x0), the bootloader will boot anything.  When the lock
+value is non-zero, it should only boot software that is signed by a key
+stored in the bootloader except if LOCK\_OWNER is set.  Discussion of
+LOCK\_OWNER will follow.
+
+LOCK\_BOOT can only be toggled when in the bootloader/fastboot and if
+both LOCK\_CARRIER and LOCK\_DEVICE are cleared/unlocked.
+
+As with LOCK\_DEVICE, LOCK\_BOOT has no metadata so it does not need the
+extended accessors.
+
+#### LOCK\_OWNER
+
+The owner lock is used by the bootloader to support an alternative
+OS signing key provided by the device owner.  LOCK\_OWNER can only be
+toggled if LOCK\_BOOT is cleared.  LOCK\_OWNER does not require
+any metadata to unlock, but to lock, it requires a blob of up to 2048
+bytes be provided.  This is just secure storage for use by the
+bootloader.  LOCK\_OWNER may be toggled in the bootloader or the
+operating system.  This allows an unlocked device (LOCK\_BOOT=0x0) to
+install an owner key using fastboot or using software on the operating
+system itself.
+
+Before LOCK\_OWNER's key should be honored by the bootloader, LOCK\_BOOT
+should be set (in the bootloader) to enforce use of the key and to keep
+malicious software in the operating system from changing it.
+
+(Note, that the owner key should not be treated as equivalent to the
+bootloader's internally stored key in that the device user should have a
+means of knowing if an owner key is in use, but the requirement for the
+device to be unlocked implies both physical access the LOCK\_DEVICE
+being cleared.)
+
+
+### Rollback storage
+
+Verifying an operating system kernel and image is useful both for system
+reliability and for ensuring that the software has not been modified by
+a malicious party.  However, if the system software is updated,
+malicious software may attempt to "roll" a device back to an older
+version in order to take advantage of mistakes in the older, verified
+code.
+
+Rollback indices, or versions, may be stored securely by the bootloader
+to prevent these problems.  The Verified Boot Storage applet provides
+eight 64-bit slots for storing a value.  They may be read at any time,
+but they may only be written to when the device is in the bootloader (or
+fastboot).
+
+Rollback storage is written to using
+ese\_boot\_rollback\_index\_write() and read using
+ese\_boot\_rollback\_index\_read().
+
+### Applet state
+
+The applet supports two operational states:
+
+  - production=true
+  - production=false
+
+On initial installation, production is false.  When the applet is not
+in production mode, it does not enforce a number of security boundaries,
+such as requiring bootloader or hlos mode for lock toggling or
+CarrierLock verification.  This allows the factory tools to run in any
+mode and properly configure a default lock state.
+
+To transition to "production", a call to ese\_boot\_set\_production(true)
+is necessary.
+
+To check the state and collect debugging information, the call
+ese\_boot\_get\_state() will return the current bootloader value,
+the production state, any errors codes from lock initialization, and the
+contents of lock storage.
+
+#### Example applet provisioning
+
+After the applet is installed, a flow as follows would prepare the
+applet for use:
+
+  - ese\_boot\_session\_init();
+  - ese\_boot\_session\_open();
+  - ese\_boot\_session\_lock\_xset(LOCK\_OWNER, {0, ...});
+  - ese\_boot\_session\_lock\_set(LOCK\_BOOT, 0x1);
+  - ese\_boot\_session\_lock\_set(LOCK\_DEVICE, 0x1);
+  - [collect device data]
+  - ese\_boot\_session\_lock\_set(LOCK\_CARRIER, {1, deviceData});
+  - ese\_boot\_session\_set\_production(true);
+  - ese\_boot\_session\_close();
+
+### Additional details
+
+#### Bootloader mode
+
+Bootloader mode detection depends on hardware support to signal the
+applet that the application processor has been reset.  Additionally,
+there is a mechanism for the bootloader to indicate that is loading the
+main OS where it flips the value.  This signal provides the assurances
+around when storage is mutable or not during the time a device is
+powered on.
+
+#### Error results
+
+EseAppResult is an enum that all ese\_boot\_\* functions return.  The
+enum only covers the lower 16-bits.  The upper 16-bits are reserved for
+passing applet and secure element OS status for debugging and analysis.
+When the lower 16-bits are ESE\_APP\_RESULT\_ERROR\_APPLET, then the
+upper bytes will be the applet code. That code can then be
+cross-referenced in the applet by function and code.  If the lower
+bytes are ESE\_APP\_RESULT\_ERROR\_OS, then the status code are the
+ISO7816 code from an uncaught exception or OS-level error.
+
+##### Cooldown
+
+ESE\_APP\_RESULT\_ERROR\_COOLDOWN indicates that the secure element has
+indicated that its attack counter is high. In order to decrement it, the secure
+element needs to remain powered on for a certain number of minutes.  For
+chips that support it, like the one this applet is being tested on, the cooldown
+time can be requested with a special payload to ese\_transceive():
+
+    FFFF00E1
+
+In response, a 6 byte response will contain a uint32\_t and a successfuly
+status code (90 00).  The integer indicates how many minutes the chip needs to
+stay powered and unused to cooldown.  If this happens before the locks or
+rollback storage can be read, the bootloader will need to determine a
+safe delay or recovery path until boot can proceed securely.
+
diff --git a/apps/boot/boot.c b/apps/boot/boot.c
new file mode 100644
index 0000000..fe381af
--- /dev/null
+++ b/apps/boot/boot.c
@@ -0,0 +1,675 @@
+/*
+ * 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/boot.h"
+#include "boot_private.h"
+
+const uint8_t kBootStateVersion = 0x1;
+const uint16_t kBootStorageLength = 4096;
+/* 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, 0x10, 0xA0, 0x00, 0x00,
+                                 0x04, 0x76, 0x50, 0x49, 0x58, 0x4C, 0x42, 0x4F,
+                                 0x4F, 0x54, 0x00, 0x01, 0x01, 0x00};
+const uint32_t kSelectAppletLength = (uint32_t)sizeof(kSelectApplet);
+// Supported commands.
+const uint8_t kGetState[] = {0x80, 0x00, 0x00, 0x00, 0x00};
+const uint8_t kLoadCmd[] = {0x80, 0x02};
+const uint8_t kStoreCmd[] = {0x80, 0x04};
+const uint8_t kGetLockState[] = {0x80, 0x06, 0x00, 0x00, 0x00};
+const uint8_t kSetLockState[] = {0x80, 0x08, 0x00, 0x00, 0x00};
+const uint8_t kSetProduction[] = {0x80, 0x0a};
+const uint8_t kCarrierLockTest[] = {0x80, 0x0c, 0x00, 0x00};
+
+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_boot_session_init(struct EseBootSession *session) {
+  session->ese = NULL;
+  session->active = false;
+  session->channel_id = 0;
+}
+
+ESE_API EseAppResult ese_boot_session_open(struct EseInterface *ese,
+                                           struct EseBootSession *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 Boot 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_boot_session_close(struct EseBootSession *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_boot_lock_xget(struct EseBootSession *session,
+                                        EseBootLockId lock, uint8_t *lockData,
+                                        uint16_t maxSize, uint16_t *length) {
+  struct EseSgBuffer tx[4];
+  struct EseSgBuffer rx[3];
+  int rx_len;
+  if (!session || !session->ese || !session->active) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (lock > kEseBootLockIdMax) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (maxSize < 1 || maxSize > 4096) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  uint8_t chan = kGetLockState[0] | session->channel_id;
+  tx[0].base = &chan;
+  tx[0].len = 1;
+  tx[1].base = (uint8_t *)&kGetLockState[1];
+  tx[1].len = 1;
+
+  uint8_t p1p2[] = {lock, 0x01};
+  tx[2].base = &p1p2[0];
+  tx[2].len = sizeof(p1p2);
+
+  // Accomodate the applet 2 byte status code.
+  uint8_t max_reply[] = {0x0, ((maxSize + 2) >> 8), ((maxSize + 2) & 0xff)};
+  tx[3].base = &max_reply[0];
+  tx[3].len = sizeof(max_reply);
+
+  uint8_t reply[2]; // App reply or APDU error.
+  rx[0].base = &reply[0];
+  rx[0].len = sizeof(reply);
+  // Applet data
+  rx[1].base = lockData;
+  rx[1].len = maxSize;
+  // Only used if the full maxSize is used.
+  uint8_t apdu_status[2];
+  rx[2].base = &apdu_status[0];
+  rx[2].len = sizeof(apdu_status);
+
+  rx_len = ese_transceive_sg(session->ese, tx, 4, rx, 3);
+  if (rx_len < 2 || ese_error(session->ese)) {
+    ALOGE("Failed to read lock state (%d).", lock);
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (rx_len == 2) {
+    ALOGE("ese_boot_lock_xget: SE exception");
+    EseAppResult ret = check_apdu_status(&reply[0]);
+    return ret;
+  }
+  // Expect the full payload plus the aplet status and the completion code.
+  *length = (uint16_t)(rx_len - 4);
+  if (rx_len == 4) {
+    ALOGE("Received applet error code %x %x", lockData[0], lockData[1]);
+    return ese_make_app_result(lockData[0], lockData[1]);
+  }
+  return ESE_APP_RESULT_OK;
+}
+
+ESE_API EseAppResult ese_boot_lock_get(struct EseBootSession *session,
+                                       EseBootLockId lock, uint8_t *lockVal) {
+  struct EseSgBuffer tx[3];
+  struct EseSgBuffer rx[1];
+  int rx_len;
+  if (!session || !session->ese || !session->active) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (lock > kEseBootLockIdMax) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  uint8_t chan = kGetLockState[0] | session->channel_id;
+  tx[0].base = &chan;
+  tx[0].len = 1;
+  tx[1].base = (uint8_t *)&kGetLockState[1];
+  tx[1].len = 1;
+
+  uint8_t p1p2[] = {lock, 0x0};
+  tx[2].base = &p1p2[0];
+  tx[2].len = sizeof(p1p2);
+
+  uint8_t reply[6];
+  rx[0].base = &reply[0];
+  rx[0].len = sizeof(reply);
+
+  rx_len = ese_transceive_sg(session->ese, tx, 3, rx, 1);
+  if (rx_len < 2 || ese_error(session->ese)) {
+    ALOGE("Failed to read lock state (%d).", lock);
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  EseAppResult ret = check_apdu_status(&reply[rx_len - 2]);
+  if (ret != ESE_APP_RESULT_OK) {
+    ALOGE("Get lock state returned a SE OS error.");
+    return ret;
+  }
+  if (rx_len < 5) {
+    ALOGE("Get lock state did not receive enough data.");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  // TODO: unify in the applet, then map them here.
+  if (reply[0] != 0x0 && reply[1] != 0x0) {
+    ALOGE("INS_GET_LOCK: Applet error: %x %x", reply[0], reply[1]);
+    return ese_make_app_result(reply[0], reply[1]);
+  }
+  if (lockVal) {
+    *lockVal = reply[2];
+    return ESE_APP_RESULT_OK;
+  }
+
+  if (reply[2] != 0) {
+    return ESE_APP_RESULT_TRUE;
+  }
+  return ESE_APP_RESULT_FALSE;
+}
+
+ESE_API EseAppResult ese_boot_lock_xset(struct EseBootSession *session,
+                                        EseBootLockId lockId,
+                                        const uint8_t *lockData,
+                                        uint16_t dataLen) {
+  struct EseSgBuffer tx[5];
+  struct EseSgBuffer rx[1];
+  int rx_len;
+  if (!session || !session->ese || !session->active) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (lockId > kEseBootLockIdMax) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (dataLen < 1 || dataLen > kEseBootOwnerKeyMax + 1) {
+    ALOGE("set_lock_with_meta: too much data: %hu > %d", dataLen,
+          kEseBootOwnerKeyMax + 1);
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+
+  uint8_t chan = kSetLockState[0] | session->channel_id;
+  tx[0].base = &chan;
+  tx[0].len = 1;
+  tx[1].base = (uint8_t *)&kSetLockState[1];
+  tx[1].len = 1;
+
+  uint8_t p1p2[] = {lockId, lockData[0]};
+  tx[2].base = &p1p2[0];
+  tx[2].len = sizeof(p1p2);
+  dataLen--;
+
+  uint8_t apdu_len[] = {0x0, (dataLen >> 8), (dataLen & 0xff)};
+  tx[3].base = &apdu_len[0];
+  tx[3].len = sizeof(apdu_len);
+
+  tx[4].c_base = &lockData[1];
+  tx[4].len = dataLen;
+
+  uint8_t reply[4]; // App reply or APDU error.
+  rx[0].base = &reply[0];
+  rx[0].len = sizeof(reply);
+
+  rx_len = ese_transceive_sg(session->ese, tx, 5, rx, 1);
+  if (rx_len < 2 || ese_error(session->ese)) {
+    ALOGE("Failed to set lock state (%d).", lockId);
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (rx_len == 2) {
+    ALOGE("ese_boot_lock_xset: SE exception");
+    EseAppResult ret = check_apdu_status(&reply[0]);
+    return ret;
+  }
+  // Expect the full payload plus the aplet status and the completion code.
+  if (rx_len != 4) {
+    ALOGE("Get lock state did not receive enough data: %d", rx_len);
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (reply[0] != 0x0 || reply[1] != 0x0) {
+    ALOGE("Received applet error code %x %x", reply[0], reply[1]);
+    return ese_make_app_result(reply[0], reply[1]);
+  }
+  return ESE_APP_RESULT_OK;
+}
+
+ESE_API EseAppResult ese_boot_lock_set(struct EseBootSession *session,
+                                       EseBootLockId lockId,
+                                       uint8_t lockValue) {
+  struct EseSgBuffer tx[3];
+  struct EseSgBuffer rx[1];
+  int rx_len;
+  if (!session || !session->ese || !session->active) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (lockId > kEseBootLockIdMax) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+
+  uint8_t chan = kSetLockState[0] | session->channel_id;
+  tx[0].base = &chan;
+  tx[0].len = 1;
+  tx[1].base = (uint8_t *)&kSetLockState[1];
+  tx[1].len = 1;
+
+  uint8_t p1p2[] = {lockId, lockValue};
+  tx[2].base = &p1p2[0];
+  tx[2].len = sizeof(p1p2);
+
+  uint8_t reply[4]; // App reply or APDU error.
+  rx[0].base = &reply[0];
+  rx[0].len = sizeof(reply);
+
+  rx_len = ese_transceive_sg(session->ese, tx, 3, rx, 1);
+  if (rx_len < 2 || ese_error(session->ese)) {
+    ALOGE("Failed to set lock state (%d).", lockId);
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  // Expect the full payload plus the applet status and the completion code.
+  if (rx_len < 4) {
+    ALOGE("ese_boot_lock_xset: SE exception");
+    EseAppResult ret = check_apdu_status(&reply[rx_len - 2]);
+    return ret;
+  }
+  if (reply[0] != 0x0 || reply[1] != 0x0) {
+    ALOGE("Received applet error code %x %x", reply[0], reply[1]);
+    return ese_make_app_result(reply[0], reply[1]);
+  }
+  return ESE_APP_RESULT_OK;
+}
+
+ESE_API EseAppResult ese_boot_rollback_index_write(
+    struct EseBootSession *session, uint8_t slot, uint64_t value) {
+  struct EseSgBuffer tx[5];
+  struct EseSgBuffer rx[1];
+  uint8_t chan;
+  if (!session || !session->ese || !session->active) {
+    ALOGE("ese_boot_rollback_index_write: invalid session");
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (slot >= kEseBootRollbackSlotCount) {
+    ALOGE("ese_boot_rollback_index_write: slot invalid");
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+
+  // APDU CLA
+  chan = kStoreCmd[0] | session->channel_id;
+  tx[0].base = &chan;
+  tx[0].len = 1;
+  // APDU INS
+  tx[1].base = (uint8_t *)&kStoreCmd[1];
+  tx[1].len = 1;
+  // APDU P1 - P2
+  const uint8_t p1p2[] = {slot, 0x0};
+  tx[2].c_base = &p1p2[0];
+  tx[2].len = sizeof(p1p2);
+  // APDU Lc
+  uint8_t len = (uint8_t)sizeof(value);
+  tx[3].base = &len;
+  tx[3].len = sizeof(len);
+  // APDU data
+  tx[4].base = (uint8_t *)&value;
+  tx[4].len = sizeof(value);
+
+  uint8_t rx_buf[4];
+  rx[0].base = &rx_buf[0];
+  rx[0].len = sizeof(rx_buf);
+
+  int rx_len = ese_transceive_sg(session->ese, tx, 5, rx, 1);
+  if (rx_len < 0 || ese_error(session->ese)) {
+    ALOGE("ese_boot_rollback_index_write: comm error");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (rx_len < 2) {
+    ALOGE("ese_boot_rollback_index_write: too few bytes recieved.");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (rx_len < 4) {
+    ALOGE("ese_boot_rollback_index_write: APDU Error");
+    return check_apdu_status(&rx_buf[rx_len - 2]);
+  }
+
+  if (rx_buf[0] != 0 || rx_buf[1] != 0) {
+    ALOGE("ese_boot_rollback_index_write: applet error code %x %x", rx_buf[0],
+          rx_buf[1]);
+    return ese_make_app_result(rx_buf[0], rx_buf[1]);
+  }
+  return ESE_APP_RESULT_OK;
+}
+
+ESE_API EseAppResult ese_boot_rollback_index_read(
+    struct EseBootSession *session, uint8_t slot, uint64_t *value) {
+  struct EseSgBuffer tx[4];
+  struct EseSgBuffer rx[1];
+  uint8_t chan;
+  if (!session || !session->ese || !session->active) {
+    ALOGE("ese_boot_rollback_index_write: invalid session");
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (!value) {
+    ALOGE("ese_boot_rollback_index_write: NULL value supplied");
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (slot >= kEseBootRollbackSlotCount) {
+    ALOGE("ese_boot_rollback_index_write: slot invalid");
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+
+  // APDU CLA
+  chan = kLoadCmd[0] | session->channel_id;
+  tx[0].base = &chan;
+  tx[0].len = 1;
+  // APDU INS
+  tx[1].base = (uint8_t *)&kLoadCmd[1];
+  tx[1].len = 1;
+  // APDU P1 - P2
+  const uint8_t p1p2[] = {slot, 0x0};
+  tx[2].c_base = &p1p2[0];
+  tx[2].len = sizeof(p1p2);
+  // APDU Lc
+  uint8_t len = 0;
+  tx[3].base = &len;
+  tx[3].len = sizeof(len);
+
+  uint8_t rx_buf[4 + sizeof(*value)];
+  rx[0].base = &rx_buf[0];
+  rx[0].len = sizeof(rx_buf);
+
+  int rx_len = ese_transceive_sg(session->ese, tx, 4, rx, 1);
+  if (rx_len < 0 || ese_error(session->ese)) {
+    ALOGE("ese_boot_rollback_index_read: comm error");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (rx_len < 2) {
+    ALOGE("ese_boot_rollback_index_read: too few bytes recieved.");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  // TODO(wad) We should check the APDU status anyway.
+  if (rx_len < 4) {
+    ALOGE("ese_boot_rollback_index_read: APDU Error");
+    return check_apdu_status(&rx_buf[rx_len - 2]);
+  }
+  if (rx_buf[0] != 0 || rx_buf[1] != 0) {
+    ALOGE("ese_boot_rollback_index_read: applet error code %x %x", rx_buf[0],
+          rx_buf[1]);
+    return ese_make_app_result(rx_buf[0], rx_buf[1]);
+  }
+  if (rx_len != (int)sizeof(rx_buf)) {
+    ALOGE("ese_boot_rollback_index_read: unexpected partial reply (%d)",
+          rx_len);
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  *value = *((uint64_t *)&rx_buf[2]);
+  return ESE_APP_RESULT_OK;
+}
+
+ESE_API EseAppResult ese_boot_carrier_lock_test(struct EseBootSession *session,
+                                                const uint8_t *testdata,
+                                                uint16_t len) {
+  struct EseSgBuffer tx[5];
+  struct EseSgBuffer rx[1];
+  int rx_len;
+  if (!session || !session->ese || !session->active) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (len > 2048) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+
+  uint8_t chan = kCarrierLockTest[0] | session->channel_id;
+  tx[0].base = &chan;
+  tx[0].len = 1;
+  tx[1].base = (uint8_t *)&kCarrierLockTest[1];
+  tx[1].len = 1;
+
+  uint8_t p1p2[] = {0, 0};
+  tx[2].base = &p1p2[0];
+  tx[2].len = sizeof(p1p2);
+
+  uint8_t apdu_len[] = {0x0, (len >> 8), (len & 0xff)};
+  tx[3].base = &apdu_len[0];
+  tx[3].len = sizeof(apdu_len);
+
+  tx[4].c_base = testdata;
+  tx[4].len = len;
+
+  uint8_t reply[4]; // App reply or APDU error.
+  rx[0].base = &reply[0];
+  rx[0].len = sizeof(reply);
+
+  rx_len = ese_transceive_sg(session->ese, tx, 5, rx, 1);
+  if (rx_len < 2 || ese_error(session->ese)) {
+    ALOGE("ese_boot_carrier_lock_test: failed to test carrier vector");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (rx_len < 4) {
+    ALOGE("ese_boot_carrier_lock_test: SE exception");
+    EseAppResult ret = check_apdu_status(&reply[rx_len - 2]);
+    return ret;
+  }
+  if (reply[0] != 0x0 || reply[1] != 0x0) {
+    ALOGE("ese_boot_carrier_lock_test: applet error %x %x", reply[0], reply[1]);
+    return ese_make_app_result(reply[0], reply[1]);
+  }
+  return ESE_APP_RESULT_OK;
+}
+
+ESE_API EseAppResult ese_boot_set_production(struct EseBootSession *session,
+                                             bool production_mode) {
+  struct EseSgBuffer tx[3];
+  struct EseSgBuffer rx[1];
+  int rx_len;
+  uint8_t prodVal = production_mode ? 0x1 : 0x00;
+  if (!session || !session->ese || !session->active) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+
+  uint8_t chan = kSetProduction[0] | session->channel_id;
+  tx[0].base = &chan;
+  tx[0].len = 1;
+  tx[1].base = (uint8_t *)&kSetProduction[1];
+  tx[1].len = 1;
+
+  uint8_t p1p2[] = {prodVal, 0x0};
+  tx[2].base = &p1p2[0];
+  tx[2].len = sizeof(p1p2);
+
+  uint8_t reply[4]; // App reply or APDU error.
+  rx[0].base = &reply[0];
+  rx[0].len = sizeof(reply);
+
+  rx_len = ese_transceive_sg(session->ese, tx, 3, rx, 1);
+  if (rx_len < 2 || ese_error(session->ese)) {
+    ALOGE("ese_boot_set_production: comms failure.");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (rx_len == 2) {
+    ALOGE("ese_boot_set_production: SE exception");
+    EseAppResult ret = check_apdu_status(&reply[0]);
+    return ret;
+  }
+  // Expect the full payload plus the aplet status and the completion code.
+  if (rx_len != 4) {
+    ALOGE("ese_boot_set_production: not enough data (%d)", rx_len);
+    return ese_make_app_result(reply[0], reply[1]);
+  }
+  if (reply[0] != 0x0 || reply[1] != 0x0) {
+    ALOGE("ese_boot_set_production: applet error code %x %x", reply[0],
+          reply[1]);
+    return ese_make_app_result(reply[0], reply[1]);
+  }
+  return ESE_APP_RESULT_OK;
+}
+
+ESE_API EseAppResult ese_boot_get_state(struct EseBootSession *session,
+                                        uint8_t *state, uint16_t maxSize) {
+  struct EseSgBuffer tx[4];
+  struct EseSgBuffer rx[3];
+  int rx_len;
+  if (!session || !session->ese || !session->active) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  uint8_t chan = kGetState[0] | session->channel_id;
+  tx[0].base = &chan;
+  tx[0].len = 1;
+  tx[1].base = (uint8_t *)&kGetState[1];
+  tx[1].len = 1;
+
+  uint8_t p1p2[] = {0x0, 0x0};
+  tx[2].base = &p1p2[0];
+  tx[2].len = sizeof(p1p2);
+
+  // Accomodate the applet 2 byte status code.
+  uint8_t max_reply[] = {0x0, ((maxSize + 2) >> 8), ((maxSize + 2) & 0xff)};
+  tx[3].base = &max_reply[0];
+  tx[3].len = sizeof(max_reply);
+
+  uint8_t reply[2]; // App reply or APDU error.
+  rx[0].base = &reply[0];
+  rx[0].len = sizeof(reply);
+  // Applet data
+  rx[1].base = state;
+  rx[1].len = maxSize;
+  // Just in case the maxSize is used. That is unlikely.
+  // TODO(wad) clean this up.
+  uint8_t apdu_status[2];
+  rx[2].base = &apdu_status[0];
+  rx[2].len = sizeof(apdu_status);
+
+  rx_len = ese_transceive_sg(session->ese, tx, 4, rx, 3);
+  if (rx_len < 2 || ese_error(session->ese)) {
+    ALOGE("ese_boot_get_state: comm failure");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (rx_len == 2) {
+    ALOGE("ese_boot_get_state: SE exception");
+    EseAppResult ret = check_apdu_status(&reply[0]);
+    return ret;
+  }
+  // Expect the full payload plus the aplet status and the completion code.
+  if (rx_len < 3 + 4) {
+    ALOGE("ese_boot_get_state: did not receive enough data: %d", rx_len);
+    if (rx_len == 4) {
+      ALOGE("Received applet error code %x %x", reply[0], reply[1]);
+    }
+    return ese_make_app_result(reply[0], reply[1]);
+  }
+  // Well known version (for now).
+  if (state[0] == kBootStateVersion) {
+    uint16_t expected = (state[1] << 8) | (state[2]);
+    // Reduce for version (1), status (2).
+    if ((rx_len - 3) != expected) {
+      ALOGE("ese_boot_get_state: may be truncated: %d != %d", rx_len - 5,
+            expected);
+    }
+    return ESE_APP_RESULT_OK;
+  }
+  ALOGE("ese_boot_get_state: missing version tag");
+  return ESE_APP_RESULT_ERROR_OS;
+}
diff --git a/apps/boot/boot_private.h b/apps/boot/boot_private.h
new file mode 100644
index 0000000..fe426d4
--- /dev/null
+++ b/apps/boot/boot_private.h
@@ -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.
+ */
+
+#ifndef ESE_APP_BOOT_PRIVATE_H_
+#define ESE_APP_BOOT_PRIVATE_H_ 1
+
+typedef enum {
+  LOCK_STATE_PROVISION =  (0x1 << 0),
+  LOCK_STATE_CARRIER = (0x1 << 1),
+  LOCK_STATE_DEVICE = (0x1 << 2),
+  LOCK_STATE_BOOT = (0x1 << 3),
+} LockState;
+
+extern const uint8_t kManageChannelOpen[];
+extern const uint32_t kManageChannelOpenLength;
+extern const uint8_t kManageChannelClose[];
+extern const uint8_t kSelectApplet[];
+extern const uint32_t kSelectAppletLength;
+extern const uint8_t kStoreCmd[];
+extern const uint8_t kLoadCmd[];
+extern const uint8_t kGetLockState[];
+extern const uint8_t kSetLockState[];
+extern const uint8_t kGetState[];
+extern const uint8_t kSetProduction[];
+extern const uint8_t kCarrierLockTest[];
+
+#endif  /* ESE_APP_BOOT_PRIVATE_H_ */
diff --git a/apps/boot/card/build.xml b/apps/boot/card/build.xml
new file mode 100644
index 0000000..8fd5a78
--- /dev/null
+++ b/apps/boot/card/build.xml
@@ -0,0 +1,77 @@
+<?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="avb_storage" 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="shared_interfaces">
+<javacard jckit="jc303_kit">
+  <!-- Build the JCOPX jar file. AID and cap file are not used. exps either. -->
+  <cap aid="A000000FFFFFFFFFFFFFFFFFFFFFFF" package="com.nxp.id.jcopx.security"  version="0.1"
+       output="jcopx-security.cap" sources="jcopx/src/com/nxp/id/jcopx/security" export="export-jcopx/security">
+       <!-- Only use the exports for dummy cap creation -->
+       <import exps="export-jcopx/security"/>
+  </cap>
+  <cap aid="A000000FFFFFFFFFFFFFFFFFFFFFFF" package="com.nxp.id.jcopx.util"  version="0.1"
+       output="jcopx-util.cap" sources="jcopx/src/com/nxp/id/jcopx/util" export="export-jcopx/util">
+       <!-- Only use the exports for dummy cap creation -->
+       <import exps="export-jcopx/util"/>
+       <import exps="export-jcopx/security" jar="export-jcopx/security.jar"/>
+  </cap>
+  <!-- CallbackInterface -->
+  <cap aid="A000000476424F4F54525354300000" package="com.android.verifiedboot.globalstate.callback"  version="0.1"
+       output="globalstate-callback.cap" sources="src/com/android/verifiedboot/globalstate/callback" export="export/callback">
+       <import exps="export/callback"/>
+  </cap>
+  <!-- OwnerInterface -->
+  <cap aid="A000000476424F4F54524547300000" package="com.android.verifiedboot.globalstate.owner"  version="0.1"
+       output="globalstate-owner.cap" sources="src/com/android/verifiedboot/globalstate/owner" export="export/owner">
+       <import exps="export/owner"/>
+  </cap>
+</javacard>
+</target>
+
+<target name="avb_storage" depends="shared_interfaces">
+<javacard jckit="jc303_kit">
+  <!-- CallbackInterface -->
+  <cap aid="A0000004765049584C424F4F54000100" package="com.android.verifiedboot.storage" version="0.1"
+       output="avb_storage.cap" sources="src/com/android/verifiedboot" export="export">
+    <!-- Use supplied exp and build interface jar. -->
+    <import exps="jcopx" jar="export-jcopx/util/util.jar"/>
+    <!-- Requires ls_library to be on the device already. -->
+    <import exps="loaderservice" jar="loaderservice/ls_library_v02.00.jar"/>
+    <!-- Grab the other interfaces from export/ -->
+    <import exps="export/owner" jar="export/owner/owner.jar" />
+    <import exps="export/callback" jar="export/callback/callback.jar" />
+    <applet class="com.android.verifiedboot.storage.Storage" aid="A0000004765049584C424F4F54000101"/>
+    <import exps="export/avb_storage"/>
+  </cap>
+</javacard>
+</target>
+</project>
diff --git a/apps/boot/card/src/com/android/verifiedboot/globalstate/callback/CallbackInterface.java b/apps/boot/card/src/com/android/verifiedboot/globalstate/callback/CallbackInterface.java
new file mode 100644
index 0000000..e5b1f94
--- /dev/null
+++ b/apps/boot/card/src/com/android/verifiedboot/globalstate/callback/CallbackInterface.java
@@ -0,0 +1,12 @@
+package com.android.verifiedboot.globalstate.callback;
+
+import javacard.framework.Shareable;
+
+public interface CallbackInterface extends Shareable {
+    /**
+     * Called when a triggerDataClear() occurs on the GlobalState applet.
+     * When the applet is complete, it should call reportDataCleared()
+     * on the GlobalStateInterface.
+     */
+    void clearData();
+};
diff --git a/apps/boot/card/src/com/android/verifiedboot/globalstate/owner/OwnerInterface.java b/apps/boot/card/src/com/android/verifiedboot/globalstate/owner/OwnerInterface.java
new file mode 100644
index 0000000..f564893
--- /dev/null
+++ b/apps/boot/card/src/com/android/verifiedboot/globalstate/owner/OwnerInterface.java
@@ -0,0 +1,65 @@
+package com.android.verifiedboot.globalstate.owner;
+
+import javacard.framework.Shareable;
+
+public interface OwnerInterface extends Shareable {
+    /**
+     * Stores the calling applet's AID which must implement the NotifyInterface.
+     * When a global data clear is requested, dataChanged() will be called.
+     *
+     * @param unregister whether to register or unregister for data change.
+     *
+     * Returns true if registered and false if not.
+     */
+    boolean notifyOnDataClear(boolean unregister);
+
+    /**
+     * Stores that the calling applet completed onDataClear() call.
+     * If this is called, the applet will received a onDataClear() will
+     * be renotified on each isDataCleared() call from any other applet.
+     */
+    void reportDataCleared();
+
+    /**
+     * Returns true if the caller has pending data to clear.
+     */
+    boolean dataClearNeeded();
+
+    /**
+     * Returns true if there is no pending clients needing to clear.
+     *
+     * This is often called by the boot support client to determine
+     * if a new notify call is needed.
+     */
+    boolean globalDataClearComplete();
+
+    /**
+     * Notifies all applets that a dataClear is underway.
+     * (The calling applet will also be notified, if registered.)
+     *
+     * @param resume indicates renotifying only pending applets.
+     */
+    void triggerDataClear(boolean resume);
+
+
+    /**
+     * Returns true if the external signal indicates the bootloader
+     * is still in control of the application proceesor.
+     */
+    boolean inBootloader();
+
+    /**
+     * Sets the {@link #production} value.
+     *
+     * @param val 
+     * @return true if the value could be assigned.
+     */
+    boolean setProduction(boolean val);
+
+    /**
+     * Returns if the Interface has been transitioned to production mode.
+     *
+     * @return true if in production mode and false if in provisioning.
+     */
+    boolean production();
+};
diff --git a/apps/boot/card/src/com/android/verifiedboot/storage/BackupInterface.java b/apps/boot/card/src/com/android/verifiedboot/storage/BackupInterface.java
new file mode 100644
index 0000000..9f14951
--- /dev/null
+++ b/apps/boot/card/src/com/android/verifiedboot/storage/BackupInterface.java
@@ -0,0 +1,45 @@
+//
+// 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.verifiedboot.storage;
+
+public interface BackupInterface {
+    /**
+     * Returns true on a successful reimport of data.
+     *
+     * @param inBytes array to read from
+     * @param inBytesOffset offset to begin copying from.
+     * @param inBytesLength length to copy from |inBytes|.
+     */
+    boolean restore(byte[] inBytes, short inBytesOffset, short inBytesLength);
+
+    /**
+     * Copies all internal state to the given array and returns the number of
+     * bytes written.  The maximum return size is 0xfffe.
+     *
+     * @param outBytes array to copy internal state to starting at |outBytesOffset|.
+     * @param outBytesOffset
+     * @return length written
+     */
+    short backup(byte[] outBytes, short outBytesOffset);
+
+   /**
+    * Returns the size needed for backup().
+    *
+    * @return size of a backup as a short.
+    */
+   short backupSize();
+}
diff --git a/apps/boot/card/src/com/android/verifiedboot/storage/BasicLock.java b/apps/boot/card/src/com/android/verifiedboot/storage/BasicLock.java
new file mode 100644
index 0000000..5cde61b
--- /dev/null
+++ b/apps/boot/card/src/com/android/verifiedboot/storage/BasicLock.java
@@ -0,0 +1,318 @@
+//
+// 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.verifiedboot.storage;
+
+import javacard.framework.CardRuntimeException;
+import javacard.framework.JCSystem;
+import javacard.framework.Util;
+
+import javacard.security.KeyBuilder;
+import javacard.security.MessageDigest;
+import javacard.security.RSAPublicKey;
+import javacard.security.Signature;
+
+import com.android.verifiedboot.storage.LockInterface;
+import com.android.verifiedboot.globalstate.owner.OwnerInterface;
+
+class BasicLock implements LockInterface {
+    // Layout: LockValue (byte)
+    private byte[] storage;
+    private short storageOffset;
+    private OwnerInterface globalState;
+    private boolean onlyInBootloader;
+    private boolean onlyInHLOS;
+    private boolean needMetadata;
+    private short metadataSize;
+    private LockInterface[] requiredLocks;
+
+    /**
+     * Initializes the instance.
+     *
+     * @param maxMetadataSize length of the metadata
+     * @param requiredLocks specify the number of locks that unlocking
+     *                      will depend on.
+     *
+     */
+    public BasicLock(short maxMetadataSize, short requiredLockNum) {
+        onlyInBootloader = false;
+        onlyInHLOS = false;
+        needMetadata = false;
+        metadataSize = maxMetadataSize;
+        requiredLocks = new LockInterface[requiredLockNum];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public short backupSize() {
+        return getStorageNeeded();
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public short backup(byte[] outBytes, short outBytesOffset) {
+        Util.arrayCopy(storage, storageOffset,
+                       outBytes, outBytesOffset,
+                       backupSize());
+        return backupSize();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean restore(byte[] inBytes, short inBytesOffset,
+                           short inBytesLength) {
+        if (inBytesLength > backupSize() || inBytesLength == (short)0) {
+            return false;
+        }
+        Util.arrayCopy(inBytes, inBytesOffset,
+                       storage, storageOffset,
+                       inBytesLength);
+        return true;
+    }
+
+    /**
+     * Indicates that it is required that the {@link #globalState} is
+     * in the bootloader when the lock is changed.
+     *
+     * @param inBootloader  true if changes can only happen in the bootloader.
+     */
+    public void requireBootloader(boolean inBootloader) {
+        onlyInBootloader = inBootloader;
+    }
+
+    /**
+     * Indicates that it is required that the {@link #globalState} is
+     * NOT in the bootloader when the lock is changed.
+     *
+     * @param inHLOS  true if changes can only happen in the bootloader.
+     */
+    public void requireHLOS(boolean inHLOS) {
+        onlyInHLOS = inHLOS;
+    }
+
+
+    /**
+     * Indicates that metadata must be supplied when locking.
+     *
+     * @param atLock  true if metadata must be supplied.
+     */
+    public void requireMetadata(boolean atLock) {
+        needMetadata = atLock;
+    }
+
+    /**
+     * Adds a lock that must be unlocked to enable
+     * this lock to toggle.
+     *
+     * @param lock lock to depend on
+     * @return true on success or false on no space.
+     */
+    public boolean addRequiredLock(LockInterface lock) {
+        for (short i = 0; i < (short) requiredLocks.length; ++i) {
+            if (requiredLocks[i] == null) {
+                requiredLocks[i] = lock;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Return the error states useful for diagnostics.
+     */
+    @Override
+    public short initialized() {
+        if (storage == null) {
+            return 1;
+        }
+        if (globalState == null) {
+            return 2;
+        }
+        return 0;
+    }
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public short getStorageNeeded() {
+        return (short)(1 + metadataLength());
+    }
+
+    /**
+     * Sets the backing store to use for state.
+     *
+     * @param extStorage  external array to use for storage
+     * @param extStorageOffset where to begin storing data
+     *
+     * This should be called before use.
+     */
+    @Override
+    public void initialize(OwnerInterface globalStateOwner, byte[] extStorage,
+                           short extStorageOffset) {
+        globalState = globalStateOwner;
+        // Zero it first (in case we are interrupted).
+        Util.arrayFillNonAtomic(extStorage, extStorageOffset,
+                                getStorageNeeded(), (byte) 0x00);
+        storage = extStorage;
+        storageOffset = extStorageOffset;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public short get(byte[] lockOut, short lockOffset) {
+        if (storage == null) {
+            return 0x0001;
+        }
+        try {
+            Util.arrayCopy(storage, storageOffset,
+                           lockOut, lockOffset, (short) 1);
+        } catch (CardRuntimeException e) {
+            return 0x0002;
+        }
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Returns 0xffff if {@link #initialize()} has not yet been called.
+     */
+    @Override
+    public short lockOffset() {
+        if (storage == null) {
+            return (short) 0xffff;
+        }
+        return storageOffset;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     *
+     */
+    @Override
+    public short metadataOffset() {
+        if (storage == null) {
+            return (short) 0xffff;
+        }
+        return (short)(lockOffset() + 1);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return length of metadata.
+     */
+    public short metadataLength() {
+        return metadataSize;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Returns 0x0 on success.
+     */
+    @Override
+    public short set(byte val) {
+        if (storage == null) {
+            return 0x0001;
+        }
+        // Do not require meta on unlock.
+        if (val != 0) {
+            // While an invalid combo, we can just make the require flag
+            // pointless if metadataLength == 0.
+            if (needMetadata == true && metadataLength() > 0) {
+                return 0x0002;
+            }
+        }
+        if (globalState.production() == true) {
+             // Enforce only when in production.
+            if (onlyInBootloader == true) {
+                // If onlyInBootloader is false, we allow toggling regardless.
+                if (globalState.inBootloader() == false) {
+                    return 0x0003;
+                }
+            }
+            if (onlyInHLOS == true) {
+                 // If onlyInHLOS is false, we allow toggling regardless.
+                if (globalState.inBootloader() == true) {
+                    return 0x0003;
+                }
+            }
+        }
+        if (requiredLocks.length != 0) {
+            byte[] temp = new byte[1];
+            short resp = 0;
+            for (short l = 0; l < requiredLocks.length; ++l) {
+                resp = requiredLocks[l].get(temp, (short) 0);
+                // On error or not cleared, fail.
+                if (resp != 0 || temp[0] != (byte) 0x0) {
+                    return 0x0a00;
+                }
+            }
+        }
+        try {
+            storage[storageOffset] = val;
+        } catch (CardRuntimeException e) {
+            return 0x0004;
+        }
+        return 0;
+    }
+
+   /**
+     * {@inheritDoc}
+     *
+     * If configured with {@link #requiredMetadata}, will populate the
+     * metadata. Otherwise, it will just call {@link #set}.
+     *
+     */
+    @Override
+    public short setWithMetadata(byte lockValue, byte[] lockMeta,
+                                 short lockMetaOffset, short lockMetaLength) {
+        if (storage == null) {
+            return 0x0001;
+        }
+        // No overruns, please.
+        if (lockMetaLength > metadataLength()) {
+          return 0x0002;
+        }
+        if (metadataLength() == 0) {
+          return set(lockValue);
+        }
+        try {
+            JCSystem.beginTransaction();
+            storage[lockOffset()] = lockValue;
+            Util.arrayCopy(lockMeta, lockMetaOffset,
+                           storage, metadataOffset(),
+                           lockMetaLength);
+            JCSystem.commitTransaction();
+        } catch (CardRuntimeException e) {
+            return 0x0003;
+        }
+        return 0;
+    }
+}
diff --git a/apps/boot/card/src/com/android/verifiedboot/storage/CarrierLock.java b/apps/boot/card/src/com/android/verifiedboot/storage/CarrierLock.java
new file mode 100644
index 0000000..13e8187
--- /dev/null
+++ b/apps/boot/card/src/com/android/verifiedboot/storage/CarrierLock.java
@@ -0,0 +1,459 @@
+//
+// 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.verifiedboot.storage;
+
+import javacard.framework.CardRuntimeException;
+import javacard.framework.JCSystem;
+import javacard.framework.Util;
+
+import javacard.security.KeyBuilder;
+import javacard.security.MessageDigest;
+import javacard.security.RSAPublicKey;
+import javacard.security.Signature;
+
+import com.android.verifiedboot.storage.LockInterface;
+import com.android.verifiedboot.globalstate.owner.OwnerInterface;
+
+class CarrierLock implements LockInterface, BackupInterface {
+    private final static byte VERSION = (byte) 1;
+    private final static byte VERSION_SIZE = (byte) 8;
+    private final static byte NONCE_SIZE = (byte) 8;
+    private final static byte DEVICE_DATA_SIZE = (byte) (256 / 8);
+
+    private final static byte[] PK_EXP = { (byte) 0x01, (byte) 0x00, (byte) 0x01 };  /* 65537 */
+    // XXX Development key. Must be updated.
+    private final static byte[] PK_MOD = {
+        (byte) 0xAE, (byte) 0x14, (byte) 0xA4, (byte) 0x91, (byte) 0xA6,
+        (byte) 0xC8, (byte) 0x2E, (byte) 0x4D, (byte) 0x6B, (byte) 0xB3,
+        (byte) 0x4E, (byte) 0x23, (byte) 0x96, (byte) 0x57, (byte) 0x7C,
+        (byte) 0x2C, (byte) 0x7E, (byte) 0x69, (byte) 0xE6, (byte) 0xBF,
+        (byte) 0x5A, (byte) 0x9C, (byte) 0xD7, (byte) 0xA8, (byte) 0x38,
+        (byte) 0x0C, (byte) 0x9A, (byte) 0x54, (byte) 0x43, (byte) 0x4C,
+        (byte) 0x3C, (byte) 0xDA, (byte) 0xC5, (byte) 0xB1, (byte) 0x58,
+        (byte) 0x56, (byte) 0x9B, (byte) 0x5A, (byte) 0x05, (byte) 0xBA,
+        (byte) 0x2C, (byte) 0xAB, (byte) 0xC6, (byte) 0x50, (byte) 0x34,
+        (byte) 0x3C, (byte) 0x3B, (byte) 0x8E, (byte) 0xD8, (byte) 0x55,
+        (byte) 0xEB, (byte) 0xFA, (byte) 0x4F, (byte) 0x72, (byte) 0x81,
+        (byte) 0xA3, (byte) 0x8F, (byte) 0xDD, (byte) 0x8E, (byte) 0x0E,
+        (byte) 0xF2, (byte) 0xF6, (byte) 0xEF, (byte) 0x18, (byte) 0x95,
+        (byte) 0xCF, (byte) 0x71, (byte) 0x7D, (byte) 0x33, (byte) 0xA1,
+        (byte) 0xAE, (byte) 0xBE, (byte) 0x8C, (byte) 0xA5, (byte) 0x50,
+        (byte) 0x4C, (byte) 0xF2, (byte) 0xDC, (byte) 0x7B, (byte) 0x6C,
+        (byte) 0xAE, (byte) 0x14, (byte) 0x95, (byte) 0xB7, (byte) 0xE7,
+        (byte) 0xCA, (byte) 0xEB, (byte) 0xB0, (byte) 0x24, (byte) 0x5B,
+        (byte) 0xC9, (byte) 0x24, (byte) 0x2B, (byte) 0xC6, (byte) 0x96,
+        (byte) 0x99, (byte) 0xE9, (byte) 0x8B, (byte) 0x10, (byte) 0xCA,
+        (byte) 0x34, (byte) 0x2D, (byte) 0x84, (byte) 0x57, (byte) 0x09,
+        (byte) 0x4C, (byte) 0x32, (byte) 0x35, (byte) 0x68, (byte) 0x37,
+        (byte) 0x53, (byte) 0x0E, (byte) 0xF6, (byte) 0x93, (byte) 0x6C,
+        (byte) 0x86, (byte) 0x84, (byte) 0xC1, (byte) 0x44, (byte) 0x70,
+        (byte) 0x4A, (byte) 0x12, (byte) 0xAA, (byte) 0xC2, (byte) 0x9F,
+        (byte) 0x68, (byte) 0x5C, (byte) 0x42, (byte) 0xC8, (byte) 0xEB,
+        (byte) 0xD3, (byte) 0xAF, (byte) 0xD6, (byte) 0x34, (byte) 0x7F,
+        (byte) 0x9D, (byte) 0xC9, (byte) 0xE8, (byte) 0x81, (byte) 0x4A,
+        (byte) 0x5C, (byte) 0xDA, (byte) 0x36, (byte) 0x33, (byte) 0xFD,
+        (byte) 0x5C, (byte) 0x67, (byte) 0xBB, (byte) 0x91, (byte) 0x1C,
+        (byte) 0xF5, (byte) 0x21, (byte) 0xC0, (byte) 0x4E, (byte) 0x64,
+        (byte) 0x87, (byte) 0x89, (byte) 0xB6, (byte) 0x8B, (byte) 0xFD,
+        (byte) 0xDA, (byte) 0x30, (byte) 0x74, (byte) 0x1E, (byte) 0x00,
+        (byte) 0x57, (byte) 0xE1, (byte) 0x5C, (byte) 0xC4, (byte) 0xF2,
+        (byte) 0xEE, (byte) 0xF7, (byte) 0x05, (byte) 0x1C, (byte) 0xCE,
+        (byte) 0xF1, (byte) 0xCA, (byte) 0x88, (byte) 0xA0, (byte) 0x28,
+        (byte) 0x53, (byte) 0x2C, (byte) 0x84, (byte) 0xCD, (byte) 0xA3,
+        (byte) 0x6C, (byte) 0x1D, (byte) 0x15, (byte) 0x00, (byte) 0x5A,
+        (byte) 0x5D, (byte) 0x80, (byte) 0x40, (byte) 0x59, (byte) 0xE5,
+        (byte) 0xEA, (byte) 0xD1, (byte) 0x2A, (byte) 0xD6, (byte) 0x5A,
+        (byte) 0xE0, (byte) 0xE6, (byte) 0x9C, (byte) 0xEB, (byte) 0x23,
+        (byte) 0x4D, (byte) 0xD0, (byte) 0xB1, (byte) 0x27, (byte) 0xEE,
+        (byte) 0x41, (byte) 0x0D, (byte) 0xAA, (byte) 0x25, (byte) 0xBD,
+        (byte) 0xA0, (byte) 0xD0, (byte) 0x20, (byte) 0x00, (byte) 0x16,
+        (byte) 0x1F, (byte) 0x54, (byte) 0xC6, (byte) 0x4A, (byte) 0xDD,
+        (byte) 0x2A, (byte) 0x7E, (byte) 0x32, (byte) 0x43, (byte) 0x7F,
+        (byte) 0xD8, (byte) 0x74, (byte) 0x0F, (byte) 0x94, (byte) 0x88,
+        (byte) 0x3F, (byte) 0x26, (byte) 0x27, (byte) 0x54, (byte) 0x5D,
+        (byte) 0x01, (byte) 0x83, (byte) 0xAE, (byte) 0x47, (byte) 0x37,
+        (byte) 0x03, (byte) 0x6C, (byte) 0x80, (byte) 0xFD, (byte) 0x6E,
+        (byte) 0x08, (byte) 0xEB, (byte) 0xB4, (byte) 0x55, (byte) 0x81,
+        (byte) 0x13,
+    };
+
+    // Layout:
+    // LockValue (byte) || lastNonce (byte[8]) || deviceDataHash (byte[32])
+    private byte[] storage;
+    private short storageOffset;
+    RSAPublicKey verifyingKey;
+    Signature verifier;
+    MessageDigest md_sha256;  /* For creating the lock data hash from the input. */
+    OwnerInterface globalState;
+
+    /**
+     * Initializes the instance objects.
+     */
+    public CarrierLock() {
+        try {
+            verifyingKey = (RSAPublicKey)KeyBuilder.buildKey(
+                KeyBuilder.TYPE_RSA_PUBLIC, KeyBuilder.LENGTH_RSA_2048, false);
+            verifyingKey.setExponent(PK_EXP, (short)0, (short)PK_EXP.length);
+            verifyingKey.setModulus(PK_MOD, (short)0, (short)PK_MOD.length);
+        } catch (CardRuntimeException e) {
+          verifyingKey = null;
+        }
+
+        try {
+            verifier = Signature.getInstance(Signature.ALG_RSA_SHA_256_PKCS1, false);
+            verifier.init(verifyingKey, Signature.MODE_VERIFY);
+        } catch (CardRuntimeException e) {
+          verifier = null;
+        }
+        md_sha256 = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Return the error states useful for diagnostics.
+     */
+    @Override
+    public short initialized() {
+      if (storage == null) {
+          return 1;
+      }
+      if (verifyingKey == null) {
+          return 2;
+      }
+      if (verifier == null) {
+          return 3;
+      }
+      return 0;
+    }
+    /**
+     * {@inheritDoc}
+     *
+     */
+    @Override
+    public short getStorageNeeded() {
+        return NONCE_SIZE + DEVICE_DATA_SIZE + 1;
+    }
+
+    /**
+     * Sets the backing store to use for state.
+     *
+     * @param globalStateOwner interface for querying global state
+     * @param extStorage  external array to use for storage
+     * @param extStorageOffset where to begin storing data
+     *
+     * This should be called before use.
+     */
+    @Override
+    public void initialize(OwnerInterface globalStateOwner, byte[] extStorage,
+                           short extStorageOffset) {
+        globalState = globalStateOwner;
+        // Zero it first (in case we are interrupted).
+        Util.arrayFillNonAtomic(extStorage, extStorageOffset,
+                                getStorageNeeded(), (byte) 0x00);
+        storage = extStorage;
+        storageOffset = extStorageOffset;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public short get(byte[] lockOut, short outOffset) {
+        if (storage == null) {
+            return 0x0001;
+        }
+        try {
+            Util.arrayCopy(storage, lockOffset(),
+                           lockOut, outOffset, (short) 1);
+        } catch (CardRuntimeException e) {
+          return 0x0002;
+        }
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Returns 0xffff if {@link #initialize()} has not yet been called.
+     */
+    @Override
+    public short lockOffset() {
+        if (storage == null) {
+            return (short) 0xffff;
+        }
+        return storageOffset;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     *
+     * Returns 0xffff if {@link #initialize()} has not yet been called.
+     */
+    @Override
+    public short metadataOffset() {
+        if (storage == null) {
+            return (short) 0xffff;
+        }
+        return (short)(storageOffset + NONCE_SIZE + 1);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Returns length of metadata.
+     *
+     * @return length of metadata.
+     */
+    public short metadataLength() {
+        return (short) DEVICE_DATA_SIZE;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Always returns false. Locking CarrierLock requires
+     * device data and unlocking (val=0x0) requires a signed
+     * assertion.
+     */
+    @Override
+    public short set(byte val) {
+        return (short)0xffff;  // Not implemented.
+    }
+
+    /**
+     * Performs the verification of the incoming unlock token.
+     *
+     * This will check the version code, the nonce value, and the signature.
+     *
+     *
+     * @param deviceData
+     * @param deviceDataOffset
+     * @param lastNonce
+     * @param lastNonceOffset
+     * @param unlockToken
+     * @param unlockTokenOffset
+     * @param unlockTokenLength
+     * @return 0x0 on verified and an error code if not.
+     */
+    private short verifyUnlock(byte[] deviceData, short deviceDataOffset,
+                               byte[] lastNonce, short lastNonceOffset,
+                               byte[] unlockToken, short unlockTokenOffset,
+                               short unlockTokenLength) {
+        if (unlockTokenLength < (short)(VERSION_SIZE + NONCE_SIZE + PK_MOD.length)) {
+            return 0x0002;
+        }
+        // Only supported version is the uint64le_t 1
+        if (unlockToken[unlockTokenOffset] != VERSION) {
+            return 0x0003;
+        }
+
+        byte[] message = JCSystem.makeTransientByteArray(
+            (short)(NONCE_SIZE + DEVICE_DATA_SIZE), JCSystem.CLEAR_ON_DESELECT);
+        // Collect the incoming nonce.
+        Util.arrayCopy(unlockToken, (short)(unlockTokenOffset + VERSION_SIZE),
+                       message, (short) 0x0, (short) NONCE_SIZE);
+        // Append the internallty stored device data.
+        Util.arrayCopy(deviceData, deviceDataOffset,
+            message, (short)NONCE_SIZE, (short) DEVICE_DATA_SIZE);
+
+        // Verify it against the incoming signature.
+        if (verifier.verify(
+                message, (short) 0, (short) message.length, unlockToken,
+                (short)(unlockTokenOffset + VERSION_SIZE + NONCE_SIZE),
+                (short)(unlockTokenLength - (VERSION_SIZE + NONCE_SIZE))) ==
+                false) {
+            return 0x0004;
+        }
+        if (littleEndianUnsignedGreaterThan(NONCE_SIZE,
+                unlockToken, (short)(unlockTokenOffset + VERSION_SIZE),
+                lastNonce, lastNonceOffset) == true) {
+            return 0;
+        }
+        return 0x0005;
+    }
+
+    /**
+     * Compares two little endian byte streams and returns
+     * true if lhs is greater than rhs.
+     *
+     * @param len number of bytes to compare
+     * @param lhs left hand size buffer
+     * @param lhsBase starting offset
+     * @param rhs right hand size buffer
+     * @param rhsBase starting offset
+     */
+    private boolean littleEndianUnsignedGreaterThan(
+            byte len,
+            byte[] lhs, short lhsBase,
+            byte[] rhs, short rhsBase) {
+        // Start with the most significant byte.
+        short i = len;
+        do {
+            i -= 1;
+            if (lhs[(short)(lhsBase +i)] > rhs[(short)(rhsBase + i)]) {
+                return true;
+            }
+            if (lhs[(short)(lhsBase +i)] < rhs[(short)(rhsBase + i)]) {
+                return false;
+            }
+            // Only proceed if the current bytes are equal.
+        } while (i > 0);
+        return false;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     * Returns true if the lock is changed with associated metadata.
+     *
+     * If |lockValue| is non-zero, then |lockMeta| should contain a series of
+     * ASCII (or hex) values separated by short lengths.  These will be SHA256
+     * hashed together to create a "device data hash". Its use is covered next.
+     *
+     * If |lockValue| is zero, then |lockMeta| should contain the following
+     * (all little endian): 8-bit version tag (0x01) || byte[8] "64-bit nonce"
+     * || byte[] Signature(nonce||deviceDataHash) The signature is using the
+     * embedded key {@link #pkModulus} and the format is ALG_RSA_SHA_256_PKCS1.
+     * If the signature verifies using the internally stored deviceDataHash and
+     * the provided nonce, then one last check is applied.  If the nonce,
+     * treated as a little endian uint64_t, is greater than the stored nonce then
+     * it will be rejected.  Note, once unlocked, the device data hash is deleted
+     * and the CarrierLock cannot be reapplied unless the device is taken out
+     * of production mode (bootloader RMA path).
+     *
+     * If {@link #globalState} indicates that the device is not yet in production
+     * mode, then the lock values can be toggled arbitrarily.
+     * The lock values may also be changed in the bootloader or in the HLOS as
+     * the transitions are either one-way (lock) or authenticated.  It is required
+     * that the lock state is assigned prior to transitioning to production as that
+     * ensures that an unlocked device cannot be re-locked maliciously from the HLOS.
+     */
+    @Override
+    public short setWithMetadata(byte lockValue, byte[] lockMeta,
+                                 short lockMetaOffset, short lockMetaLength) {
+        if (storage == null) {
+            // TODO: move to constants.
+            return 0x0001;
+        }
+        // Ensure we don't update the nonce if we didn't go through verify.
+        short resp = (short) 0xffff;
+        if (lockValue == LOCK_UNLOCKED) {  // SHUT IT DOWN.
+            // If we're already unlocked, allow another call to make sure all the
+            // data is cleared.
+            if (storage[storageOffset] != LOCK_UNLOCKED &&
+                globalState.production() == true) {
+                // RSA PKCS#1 signature should be the same length as the modulus but we'll allow it to
+                // be larger because ???. XXX TODO
+                resp = verifyUnlock(storage, (short)(storageOffset + 1 + NONCE_SIZE),
+                                    storage, (short)(storageOffset + 1),
+                                    lockMeta, lockMetaOffset, lockMetaLength);
+                if (resp != (short) 0) {
+                  return resp;
+                }
+            }
+            JCSystem.beginTransaction();
+            storage[storageOffset] = lockValue;
+            // Update the monotonically increasing "nonce" value.
+            // Note that the nonce is only ever updated if a signed value
+            // was seen or if we're not production() to assure it doesn't get
+            // rolled forward.
+            if (resp == 0) {
+                Util.arrayCopy(lockMeta, (short)1,
+                               storage, (short)(1 + storageOffset),
+                               (short)NONCE_SIZE);
+            }
+            // Delete the device-unique data.
+            Util.arrayFillNonAtomic(storage, (short)(NONCE_SIZE + 1 + storageOffset),
+                (short)DEVICE_DATA_SIZE, (byte)0x00);
+            JCSystem.commitTransaction();
+        } else {  // Locking. Expect a lockMeta of the device data.
+            if (globalState.production() == true) {
+                // Locking can only be done prior to production.
+                return 0x0006;
+            }
+            md_sha256.reset();
+            JCSystem.beginTransaction();
+            // Hash all the input data and store the result as the device data
+            // digest.
+            md_sha256.doFinal(lockMeta, lockMetaOffset, lockMetaLength,
+                              storage, metadataOffset());
+            // Note that we never clear or overwrite the nonce.
+            storage[storageOffset] = lockValue;
+            JCSystem.commitTransaction();
+        }
+        return 0x0000;
+    }
+
+    /**
+     * Given all the data, tests if the key actually works.
+     *
+     * buffer should contain:
+     *   fakeLastNonce | fakeDeviceData | version (8) | testNonce | signature
+     *
+     * @param buffer Array with the test data.
+     * @param offset offset into the buffer.
+     * @param length total length from offset.
+     * @return 0x0 on verify and an error code otherwise.
+     */
+    public short testVector(byte[] buffer, short offset, short length) {
+        return verifyUnlock(buffer, (short)(offset + NONCE_SIZE), // device data
+                            buffer, offset,  // fake last nonce.
+                            // unlock data
+                            buffer, (short)(offset + NONCE_SIZE + DEVICE_DATA_SIZE),
+                            (short)(length - (NONCE_SIZE + DEVICE_DATA_SIZE)));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public short backupSize() {
+        return getStorageNeeded();
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public short backup(byte[] outBytes, short outBytesOffset) {
+        Util.arrayCopy(storage, storageOffset,
+                       outBytes, outBytesOffset,
+                       backupSize());
+        return backupSize();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean restore(byte[] inBytes, short inBytesOffset,
+                           short inBytesLength) {
+        if (inBytesLength > backupSize() || inBytesLength == (short)0) {
+            return false;
+        }
+        Util.arrayCopy(inBytes, inBytesOffset,
+                       storage, storageOffset,
+                       inBytesLength);
+        return true;
+    }
+
+
+}
diff --git a/apps/boot/card/src/com/android/verifiedboot/storage/DefaultOsBackupImpl.java b/apps/boot/card/src/com/android/verifiedboot/storage/DefaultOsBackupImpl.java
new file mode 100644
index 0000000..226ff70
--- /dev/null
+++ b/apps/boot/card/src/com/android/verifiedboot/storage/DefaultOsBackupImpl.java
@@ -0,0 +1,68 @@
+//
+// 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.verifiedboot.storage;
+
+import javacard.framework.AID;
+import javacard.framework.CardRuntimeException;
+import javacard.framework.Shareable;
+import javacard.framework.Util;
+
+import com.android.verifiedboot.storage.BackupInterface;
+
+public class DefaultOsBackupImpl implements OsBackupInterface {
+    private BackupInterface[] objects;
+
+    public DefaultOsBackupImpl() {
+        objects = new BackupInterface[OsBackupInterface.TAG_MAX + 1];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void track(byte tag, Object obj) {
+        objects[tag] = (BackupInterface)obj;
+    }
+
+    /**
+     * Return the tracked objects to subclasses.
+     */
+    protected BackupInterface[] tracked() {
+        return objects;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Not implemented.
+     */
+    @Override
+    public boolean restore(byte[] inBytes, short inBytesOffset) {
+        return false;
+    }
+
+    /**
+      * Returns |this|.
+      *
+      * @param AID Unused.
+      * @param arg Unused.
+      * @return this interface.
+      */
+    public Shareable getShareableInterfaceObject(AID clientAid, byte arg) {
+        return this;
+    }
+}
diff --git a/apps/boot/card/src/com/android/verifiedboot/storage/GlobalStateImpl.java b/apps/boot/card/src/com/android/verifiedboot/storage/GlobalStateImpl.java
new file mode 100644
index 0000000..5ac00aa
--- /dev/null
+++ b/apps/boot/card/src/com/android/verifiedboot/storage/GlobalStateImpl.java
@@ -0,0 +1,266 @@
+//
+// 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.
+//
+// Shared interface for accessing hardware state.
+//
+
+package com.android.verifiedboot.storage;
+
+import javacard.framework.AID;
+import javacard.framework.Applet;
+import javacard.framework.ISO7816;
+import javacard.framework.ISOException;
+import javacard.framework.JCSystem;
+import javacard.framework.Shareable;
+
+import com.nxp.id.jcopx.util.SystemInfo;
+
+import com.android.verifiedboot.globalstate.owner.OwnerInterface;
+import com.android.verifiedboot.globalstate.callback.CallbackInterface;
+
+class GlobalStateImpl implements OwnerInterface {
+    // Used to track if a client needs to be renotified in case of a
+    // power down, etc.
+    final static byte CLIENT_STATE_CLEAR_PENDING = (byte)0x1;
+
+    private Object[] clientApplets;
+    private byte[] clientAppletState;
+    boolean inProduction;
+
+
+    public GlobalStateImpl() {
+        // Support up to 10 clients.
+        clientApplets = new Object[10];
+        // Used to store mask for each client.
+        clientAppletState = new byte[clientApplets.length];
+        // Used to signal the end of factory provisioning.
+        inProduction = false;
+    }
+
+    /**
+     * Returns the index of the given AID in clientApplets
+     * if existent. Returns -1 if not found.
+     *
+     * @param aid The client applet AID to find.
+     */
+    private short findClientApplet(AID aid) {
+        for (short i = 0; i < clientApplets.length; ++i) {
+            if (clientApplets[i] == null) {
+                continue;
+            }
+            if (((AID)clientApplets[i]).equals(aid)) {
+                return i;
+            }
+        }
+        return (short) -1;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * It is expected that an applet can call notifyOnDataClear() on every
+     * select.
+     *
+     * @param unregister If true, will remove the caller's AID from the registered store.
+     *                   If false, will add the caller's AID to the register store.
+     */
+    @Override
+    public boolean notifyOnDataClear(boolean unregister) {
+        final AID aid = JCSystem.getPreviousContextAID();
+        short firstFreeSlot = -1;
+        for (short i = 0; i < clientApplets.length; ++i) {
+            if (clientApplets[i] == null) {
+                if (firstFreeSlot == -1) {
+                    firstFreeSlot = i;
+                }
+                continue;
+            }
+            if (((AID)clientApplets[i]).equals(aid)) {
+                if (unregister == true) {
+                    clientApplets[i] = null;
+                    // Clean up memory if we can.
+                    if (JCSystem.isObjectDeletionSupported()) {
+                        JCSystem.requestObjectDeletion();
+                    }
+                    return true;
+                } else {
+                    // Already registered.
+                    return true;
+                }
+            }
+        }
+        // Not registered anyway.
+        if (unregister == true) {
+            return true;
+        }
+        // No spaces left.
+        if (firstFreeSlot == -1) {
+            return false;
+        }
+        clientApplets[firstFreeSlot] = aid;
+        return true;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     *
+     * Processes an acknowledgement request from the given AID.
+     */
+    @Override
+    public void reportDataCleared() {
+        final AID aid = JCSystem.getPreviousContextAID();
+        short id = findClientApplet(aid);
+        if (id >= clientAppletState.length) {
+            // This would be surprising.
+            return;
+        }
+        if (id == (short) -1) {
+            // Not found.
+            return;
+        }
+        if ((clientAppletState[id] & CLIENT_STATE_CLEAR_PENDING)
+                == CLIENT_STATE_CLEAR_PENDING) {
+            clientAppletState[id] ^= CLIENT_STATE_CLEAR_PENDING;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Returns true if data still needs to be cleared.
+     */
+    @Override
+    public boolean dataClearNeeded() {
+        final AID aid = JCSystem.getPreviousContextAID();
+        short id = findClientApplet(aid);
+        if (id >= clientAppletState.length) {
+            // This would be surprising.
+            return false;
+        }
+       if (id == (short) -1) {
+           return false;
+       }
+        return ((clientAppletState[id] & CLIENT_STATE_CLEAR_PENDING)
+                == CLIENT_STATE_CLEAR_PENDING);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Walks all clientApplets, and if they exist, checks if the have not
+     * cleared the pending action bit. If any applets need to clear data,
+     * false will be returned.
+     */
+    @Override
+    public boolean globalDataClearComplete() {
+        for (short i = 0; i < clientApplets.length; ++i) {
+            if (clientApplets[i] == null) {
+                continue;
+            }
+            if ((clientAppletState[i] & CLIENT_STATE_CLEAR_PENDING)
+                == CLIENT_STATE_CLEAR_PENDING) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Notifies all clients that a data clear is needed.
+     *
+     * If a power down or other interrupting event occurs, it is expected
+     * that the clients will always call dataClearNeeded() prior to doing work
+     * in process().
+     *
+     * However, the secure boot applet will check globalDataClearComplete()
+     * on its first process() invocation after power on and call this method
+     * with resume=true if a data clear was intended but incomplete.
+     */
+    @Override
+    public void triggerDataClear(boolean resume) {
+        // Two passes for a full
+        // - First just sets the byte atomically.
+        // - Second attempts notification.
+        if (resume == false) {
+            for (short i = 0; i < clientApplets.length; ++i) {
+                if (clientApplets[i] == null) {
+                    continue;
+                }
+                clientAppletState[i] |= CLIENT_STATE_CLEAR_PENDING;
+            }
+        }
+        for (short i = 0; i < clientApplets.length; ++i) {
+            if (clientApplets[i] == null) {
+                continue;
+            }
+            if ((clientAppletState[i] & CLIENT_STATE_CLEAR_PENDING)
+                == CLIENT_STATE_CLEAR_PENDING) {
+                ((CallbackInterface) JCSystem.getAppletShareableInterfaceObject(
+                        (AID)clientApplets[i], (byte)0)).clearData();
+            }
+        }
+    }
+
+    /**
+     *  {@inheritDoc}
+     *
+     *  Returns true if the AP reset GPIO latch has not been cleared.
+     */
+    @Override
+    public boolean inBootloader() {
+        try {
+            if (SystemInfo.getExternalState(SystemInfo.SYSTEMINFO_SCENARIO_0)
+                == (byte) 0x00) {
+                return false;
+            }
+            return true;
+        } catch (ISOException e) {
+            // If we can't read it, we fail closed unless we're in debug mode.
+            return false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param val value assigned to the inProduction value.
+     * @return true if changed and false otherwise.
+     */
+    @Override
+    public boolean setProduction(boolean val) {
+        // Move to production is one way except for RMA - which
+        // is handled in bootloader mode.
+        if (val == false) {
+            if (inBootloader() == false) {
+                return false;
+            }
+        }
+        inProduction = val;
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * accessor for inProduction.
+     */
+    @Override
+    public boolean production() {
+        return inProduction;
+    }
+}
diff --git a/apps/boot/card/src/com/android/verifiedboot/storage/JcopBackupImpl.java b/apps/boot/card/src/com/android/verifiedboot/storage/JcopBackupImpl.java
new file mode 100644
index 0000000..41614eb
--- /dev/null
+++ b/apps/boot/card/src/com/android/verifiedboot/storage/JcopBackupImpl.java
@@ -0,0 +1,191 @@
+//
+// 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.verifiedboot.storage;
+
+import javacard.framework.AID;
+import javacard.framework.CardRuntimeException;
+import javacard.framework.JCSystem;
+import javacard.framework.Shareable;
+import javacard.framework.Util;
+
+import com.nxp.ls.library.LSBackup;
+import com.nxp.ls.library.LSPullModeRestore;
+import com.nxp.ls.library.LSPushModeBackup;
+
+import com.android.verifiedboot.storage.DefaultOsBackupImpl;
+import com.android.verifiedboot.storage.OsBackupInterface;
+
+public class JcopBackupImpl extends DefaultOsBackupImpl implements LSBackup {
+    final public static short MAGIC = (short)0xdeed;
+
+    // From NXP, the AID of the LoaderService.
+    private static final byte[] LS_AID = {
+        // NXP RID
+        (byte)0xA0, (byte)0x00, (byte)0x00, (byte)0x03, (byte)0x96,
+        // NXP JCOP Applets
+        (byte)0x54, (byte)0x43, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01,
+        // Family
+        (byte)0x00, (byte)0x0B,
+        // Version
+        (byte)0x00, // LS Application Instance
+        // Type
+        (byte)0x01, // LS Application
+    };
+
+
+    /**
+     * Returns true on a successful reimport of data.
+     *
+     * @param iarray to read from
+     * @param offset to begin copying from.
+     */
+    @Override
+    public boolean restore(byte[] in, short offset) {
+        LSPullModeRestore restore = getLSPullModeRestore();
+        offset = (short) 0;
+        if (restore == null) {
+            return false;
+        }
+        if (restore.start() == (short) 0) {
+            return false;
+        }
+        short length = (short) 5;
+        if (restore.pullData(in, offset, length) == false) {
+            return false;
+        }
+        byte tag = in[0];
+        if (tag != TAG_MAGIC) {
+            return false;
+        }
+        if (Util.getShort(in, (short)1) != (short)2) {
+            return false;
+        }
+        if (Util.getShort(in, (short)3) != MAGIC) {
+            return false;
+        }
+        // Magic is good. Let's process all the tags. They should be
+        // serialized in order and if not, we abort.
+        BackupInterface[] objects = tracked();
+        short i = (short) 0;
+        for ( ; i < objects.length; ++i) {
+            // Get tag and length at the same time.
+            length = 3;
+            if (restore.pullData(in, (short)0, length) == false) {
+                return false;
+            }
+            tag = in[0];
+            length = Util.getShort(in, (short)1);
+            if (restore.pullData(in, (short)0, length) == false) {
+                return false;
+            }
+            if (tag == i && objects[i] != null) {
+                objects[i].restore(in, (short)0, length);
+            } // else we skip it.
+        }
+
+        if (restore.end() == false) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Retrieve the restore interface from the LoaderService.
+     *
+     * @return LSPullModeRestore interface or null
+     */
+    private static LSPullModeRestore getLSPullModeRestore() {
+        try {
+          return (LSPullModeRestore)JCSystem.getAppletShareableInterfaceObject(
+            JCSystem.lookupAID(LS_AID, (short)(0), (byte)(LS_AID.length)),
+            LSPullModeRestore.LS_PULL_MODE_RESTORE_PARAMETER);
+        } catch (CardRuntimeException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Called via LSBackup for saving off data prior to an update.
+     * Only lockStorage has to be stored, but it means that install() would
+     * need to handle any changes in lock sizes.
+     *
+     * TODO(wad) In each LockInterface tag store with last metadataSize.
+     *
+     * @param backup interface to feed data to
+     * @param buffer working buffer that is shared with LSPushModeBackup
+     * @return true on success or false if there is a failure.
+     */
+    public boolean backup(LSPushModeBackup backup, byte[] buffer) {
+        BackupInterface[] objects = tracked();
+        short length = (short) 5;  // magic(TAG, SIZE, MAGIC)
+        short i;
+        for (i = (short)0; i < (short)objects.length; ++i) {
+            length += 3;  // tag, length
+            if (objects[i] != null) {
+                length += objects[i].backupSize();
+            }
+        }
+        // Interface requires mod 16.
+        if (length % 16 != 0) {
+          length += (16 - (length % 16));
+        }
+        if (backup.start(length) == false) {
+            return false;
+        }
+        // Set magic
+        short offset = (short) 0;
+        buffer[offset++] = TAG_MAGIC;
+        Util.setShort(buffer, offset, (short)2);
+        offset += 2;
+        Util.setShort(buffer, offset, MAGIC);
+        offset += 2;
+
+        for (i = (short)0; i < (short)objects.length; ++i) {
+            buffer[offset++] = (byte) i;  // TAG == index.
+            if (objects[i] != null) {
+                Util.setShort(buffer, offset, objects[i].backupSize());
+            } else {
+                Util.setShort(buffer, offset, (short)0);
+            }
+            offset += 2;
+            if (objects[i] != null) {
+                offset += objects[i].backup(buffer, offset);
+            }
+        }
+        // TODO(wad) Worth checking if offset != length.
+        if (backup.pushData(buffer, (short)0, length) == false) {
+            return false;
+        }
+        return backup.end();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Checks for the calling LoaderService applet first.
+     */
+    @Override
+    public Shareable getShareableInterfaceObject(AID clientAid, byte arg) {
+        AID lsAid = JCSystem.lookupAID(LS_AID, (short)(0), (byte)(LS_AID.length));
+        if (clientAid.equals(lsAid)) {
+            if (arg == LSBackup.LS_BACKUP_PARAMETER) {
+                return this;
+            }
+        }
+        return null;
+    }
+}
diff --git a/apps/boot/card/src/com/android/verifiedboot/storage/LockInterface.java b/apps/boot/card/src/com/android/verifiedboot/storage/LockInterface.java
new file mode 100644
index 0000000..26eaea0
--- /dev/null
+++ b/apps/boot/card/src/com/android/verifiedboot/storage/LockInterface.java
@@ -0,0 +1,108 @@
+//
+// 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.verifiedboot.storage;
+
+import com.android.verifiedboot.globalstate.owner.OwnerInterface;
+import com.android.verifiedboot.storage.BackupInterface;
+
+// The base interface for all locks.  As the іnterface clobbers a number
+// of common methods it might make more sense as a base class, but this
+// seems fine enough for now.
+public interface LockInterface extends BackupInterface {
+    // 0 means unlocked and non-zero means locks.
+    public final static byte LOCK_UNLOCKED = (byte) 0;
+
+    /**
+     * @return 0, as short, if initialized, or a non-zero error
+     *         based on the error state.
+     */
+    short initialized();
+
+    /**
+     * Return the bytes needed by this lock.
+     *
+     * Must be callable prior to initialize.
+     *
+     * @return size, as short, of storage required for {@link #setStorage}.
+     */
+    short getStorageNeeded();
+
+    /**
+     * Sets the backing store to use for state and global state dependency.
+     *
+     * @param globalStateOwner OwnerInterface implementation for policy checking.
+     * @param extStorage  external array to use for storage
+     * @param extStorageOffset where to begin storing data
+     *
+     * This should be called before use.
+     */
+    void initialize(OwnerInterface globalStateOwner, byte[] extStorage, short extStorageOffset);
+
+    /**
+     * Emits the lock byte into the array.
+     *
+     * @param lockOut lock value as a byte. 0x0 is unlocked, !0x0 is locked.
+     * @param lockOffset offset to write the lock byte.
+     * @return 0x0 on success or an error code otherwise.
+     */
+    short get(byte[] lockOut, short lockOffset);
+
+    /**
+     * Returns the offset into the external storage for the lock byte.
+     *
+     * @return The offset into the external storage for metadata or 0xffff
+     *         on error.
+     *
+     */
+    short lockOffset();
+
+    /**
+     * Returns the offset into the external storage for the metadata.
+     *
+     * @return The offset into the external storage for metadata or 0xffff
+     *         on error.
+     *
+     */
+    short metadataOffset();
+
+    /**
+     * Returns length of metadata.
+     *
+     * @return length of metadata or 0xffff on error.
+     */
+    short metadataLength();
+
+
+    /**
+     * Returns true if the lock state can be set.
+     *
+     * @param val New lock byte. Non-zero values are considered "locked".
+     * @return 0x0 if the lock state was set to |val| and an error code otherwise.
+     */
+    short set(byte val);
+
+    /**
+     * Returns true if the lock is changed with associated metadata.
+     *
+     * @param lockValue New lock byte value
+     * @param lockMeta array to copy metadata from
+     * @param lockMetaOffset offset to start copying from
+     * @param lockMetaLength bytes to copy
+     * @return 0x0 is successful and an error code if not.
+     */
+    short setWithMetadata(byte lockValue, byte[] lockMeta, short lockMetaOffset, short lockMetaLength);
+}
diff --git a/apps/boot/card/src/com/android/verifiedboot/storage/OsBackupInterface.java b/apps/boot/card/src/com/android/verifiedboot/storage/OsBackupInterface.java
new file mode 100644
index 0000000..9841328
--- /dev/null
+++ b/apps/boot/card/src/com/android/verifiedboot/storage/OsBackupInterface.java
@@ -0,0 +1,60 @@
+//
+// 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.verifiedboot.storage;
+
+import javacard.framework.AID;
+import javacard.framework.Shareable;
+
+import com.android.verifiedboot.storage.BackupInterface;
+
+public interface OsBackupInterface extends Shareable {
+    final public static byte TAG_VERSION_STORAGE = 0x0;
+    final public static byte TAG_LOCK_CARRIER = 0x1;
+    final public static byte TAG_LOCK_DEVICE = 0x2;
+    final public static byte TAG_LOCK_BOOT = 0x3;
+    final public static byte TAG_LOCK_OWNER = 0x4;
+    final public static byte TAG_MAX = TAG_LOCK_OWNER;
+
+    final public static byte TAG_MAGIC = (byte) 0xf0;
+
+
+    /**
+     * Αdds the given BackupInterface object for tracking
+     * on backup or restore. Only one object is allowed
+     * per tag.
+     *
+     * @param tag The tag mapping the specific object to the tagḣ
+     * @param bObj Object to track.
+     */
+    void track(byte tag, Object bObj);
+
+    /**
+     * Returns true on a successful reimport of data.
+     *
+     * @param inBytes array to read from
+     * @param inBytesOffset offset to begin copying from.
+     */
+    boolean restore(byte[] inBytes, short inBytesOffset);
+
+    /**
+     * Mimic the applet call.
+     *
+     * @param aid caller's AID
+     * @param arg requesting argument
+     */
+    Shareable getShareableInterfaceObject(AID aid, byte arg);
+}
diff --git a/apps/boot/card/src/com/android/verifiedboot/storage/Storage.java b/apps/boot/card/src/com/android/verifiedboot/storage/Storage.java
new file mode 100644
index 0000000..d01680b
--- /dev/null
+++ b/apps/boot/card/src/com/android/verifiedboot/storage/Storage.java
@@ -0,0 +1,485 @@
+//
+// 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.
+//
+// Test on device with:
+//    echo -e '00A4040010A0000004765049584C424F4F54000101 9000\n8000000000 9000\n' | ese-replay nq-nci
+// Install (after loading the two Shareable caps) with:
+// ${JAVA_HOME}/bin/java -jar gp.jar   -d --install avb_storage.cap
+//
+
+package com.android.verifiedboot.storage;
+
+import javacard.framework.AID;
+import javacard.framework.APDU;
+import javacard.framework.Applet;
+import javacard.framework.CardRuntimeException;
+import javacard.framework.ISO7816;
+import javacard.framework.ISOException;
+import javacard.framework.JCSystem;
+import javacard.framework.Shareable;
+import javacard.framework.Util;
+
+import javacard.security.KeyBuilder;
+import javacard.security.MessageDigest;
+import javacard.security.RSAPublicKey;
+import javacard.security.Signature;
+
+// Enables processing longer messages atomically.
+import javacardx.apdu.ExtendedLength;
+
+import com.android.verifiedboot.globalstate.callback.CallbackInterface;
+import com.android.verifiedboot.globalstate.owner.OwnerInterface;
+
+import com.android.verifiedboot.storage.GlobalStateImpl;
+import com.android.verifiedboot.storage.BasicLock;
+import com.android.verifiedboot.storage.CarrierLock;
+import com.android.verifiedboot.storage.LockInterface;
+import com.android.verifiedboot.storage.OsBackupInterface;
+import com.android.verifiedboot.storage.VersionStorage;
+
+import com.android.verifiedboot.storage.JcopBackupImpl;
+
+public class Storage extends Applet implements ExtendedLength, Shareable {
+    final static byte VERSION = (byte) 0x01;
+    final static byte APPLET_CLA = (byte)0x80;
+
+    /* Note, globalState never needs to be backed up as any clients should re-register on every
+     * call -- not just on install().
+     */
+    private GlobalStateImpl globalState;
+    private VersionStorage versionStorage;
+    private OsBackupInterface osBackupImpl;
+    /* lockStorage can be backed up directly, as long as the locks themselves have been
+     * properly intiialized.
+     */
+    private byte[] lockStorage;
+    private LockInterface[] locks;
+    // Indices into locks[].
+    private final static byte LOCK_CARRIER = (byte) 0x00;
+    private final static byte LOCK_DEVICE = (byte) 0x01;
+    private final static byte LOCK_BOOT = (byte) 0x02;
+    private final static byte LOCK_OWNER = (byte) 0x03;
+
+    private final static byte INS_GET_STATE = (byte) 0x00;
+    private final static byte INS_LOAD = (byte) 0x02;
+    private final static byte INS_STORE = (byte) 0x04;
+    private final static byte INS_GET_LOCK = (byte) 0x06;
+    private final static byte INS_SET_LOCK = (byte) 0x08;
+    private final static byte INS_SET_PRODUCTION = (byte) 0x0a;
+    private final static byte INS_CARRIER_LOCK_TEST = (byte) 0x0c;
+
+    private final static short NO_METADATA = (short) 0;
+    private final static short NO_REQ_LOCKS = (short) 0;
+    // Plenty of space for an owner key and any serialization.
+    private final static short OWNER_LOCK_METADATA_SIZE = (short) 2048;
+
+    /**
+     * 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[] bArray, short bOffset, byte bLength) {
+        // GP-compliant-ish JavaCard applet registration
+        short aidOffset = bOffset;
+        short aidLength = bArray[aidOffset++];
+        short privsOffset = (short)(aidOffset + aidLength);
+        short privLength = bArray[privsOffset++];
+        // Grab the install parameters.
+        short paramsOffset = (short)(privsOffset + privLength);
+        short paramLength = bArray[paramsOffset++];
+
+        Storage applet = new Storage();
+        applet.register(bArray, aidOffset, (byte)aidLength);
+        if (paramLength == 0) {
+            // TODO(wad) Should we fail the install on failure?
+            applet.restore(bArray);
+        }
+    }
+
+    private Storage() {
+        globalState = new GlobalStateImpl();
+        osBackupImpl = new JcopBackupImpl();
+
+        versionStorage = new VersionStorage(globalState);
+        osBackupImpl.track(OsBackupInterface.TAG_VERSION_STORAGE,
+                           versionStorage);
+
+        lockStorage = new byte[4096];
+        // Initialize all supported locks here.
+        locks = new LockInterface[4];
+        // LOCK_CARRIER can be set only when not in production mode
+        // but can be unlocked any time authenticated data is provided.
+        locks[LOCK_CARRIER] = new CarrierLock();
+        osBackupImpl.track(OsBackupInterface.TAG_LOCK_CARRIER,
+                           locks[LOCK_CARRIER]);
+
+        // LOCK_DEVICE can be toggled any time from anywhere.  It
+        // expresses a HLOS management policy.
+        locks[LOCK_DEVICE] = new BasicLock(NO_METADATA, NO_REQ_LOCKS);
+        BasicLock lockRef = (BasicLock)locks[LOCK_DEVICE];
+        lockRef.requireHLOS(true);
+        osBackupImpl.track(OsBackupInterface.TAG_LOCK_DEVICE,
+                           lockRef);
+
+         // LOCK_BOOT can only be toggled if both the carrier and device
+         // locks are clear and then, only in the bootloader/fastboot.
+        locks[LOCK_BOOT] = new BasicLock(NO_METADATA, (short) 2);
+        lockRef = (BasicLock)locks[LOCK_BOOT];
+        lockRef.addRequiredLock(locks[LOCK_CARRIER]);
+        lockRef.addRequiredLock(locks[LOCK_DEVICE]);
+        lockRef.requireBootloader(true);
+        osBackupImpl.track(OsBackupInterface.TAG_LOCK_BOOT,
+                           lockRef);
+
+        // LOCK_OWNER can be toggled at any time if the BOOT_LOCK is unlocked.
+        // It modifies the behavior of LOCK_BOOT by providing an alternative
+        // "owner" boot key.
+        locks[LOCK_OWNER] = new BasicLock(OWNER_LOCK_METADATA_SIZE, (short) 1);
+        lockRef = (BasicLock)locks[LOCK_OWNER];
+        lockRef.addRequiredLock(locks[LOCK_BOOT]);
+        lockRef.requireMetadata(true);
+        osBackupImpl.track(OsBackupInterface.TAG_LOCK_OWNER,
+                           lockRef);
+
+        short offset = (short) 0;
+        byte lockNum = (byte) 0;
+        for ( ; lockNum < (byte) locks.length; ++lockNum) {
+            if (locks[lockNum].getStorageNeeded() >
+                (short)(lockStorage.length - offset)) {
+                ISOException.throwIt((short) 0xfeed);
+            }
+            locks[lockNum].initialize(globalState, lockStorage, offset);
+            offset += locks[lockNum].getStorageNeeded();
+        }
+
+    }
+
+    /**
+      * Returns the globalState or OsBackupInterface object.
+      *
+      * @param clientAid AID of the caller.
+      * @param arg Object request indicator
+      */
+    @Override
+    public Shareable getShareableInterfaceObject(AID clientAid, byte arg) {
+        if (clientAid != null) {  // TODO: check AID.
+            switch (arg) {
+                case (byte) 0:
+                  return globalState;
+                default:
+                  return osBackupImpl.getShareableInterfaceObject(clientAid, arg);
+            }
+        }
+        return globalState;
+    }
+
+    /**
+     * Emits the state of this Applet to the wire.
+     *
+     * Format:
+     * VERSION (byte)
+     * Length (short)
+     * [global_state.OwnerInterface data]
+     *   inBootloader (byte)
+     *   production (byte)
+     * [lock state]
+     *   numLocks (byte)
+     *   locks[0].initialized (short)
+     *   ...
+     *   locks[numLocks - 1].initialized (short)
+     * [lockStorage]
+     *   lockStorageLength (short)
+     *   lockStorage (lockStorageLength=4096)
+     *
+     * TODO(wad) It'd be nice to TLV these values...
+     *
+     * @return 0x00 if the response has been sent.
+     */
+    private short sendStorageState(APDU apdu) {
+        final byte buffer[] = apdu.getBuffer();
+        byte[] working = new byte[2];
+        short value = 0;
+        short resp = 0;
+        byte i;
+        short expectedLength = apdu.setOutgoing();
+        short length = (short)(2 + 1 + 2 + 1 + 1 + 1 + (2 * locks.length) +
+                               2 + lockStorage.length + 2);
+        if (expectedLength < length) {
+            // Error with length.
+            buffer[0] = (byte) 0x01;
+            buffer[1] = (byte) 0x00;
+            buffer[2] = (byte)(length >> 8);
+            buffer[3] = (byte)(length & 0xff);
+            apdu.setOutgoingLength((short) 4);
+            apdu.sendBytes((short) 0, (short) 4);
+            return 0x0;
+        }
+        try {
+            apdu.setOutgoingLength(length);
+        } catch (CardRuntimeException e) {
+            return 0x0101;
+        }
+
+        // Send the usual prefix status indicating we made it this far.
+        try {
+            Util.setShort(working, (short) 0, (short) 0x0);
+            apdu.sendBytesLong(working, (short) 0, (short) 2);
+            length -= 2;
+        } catch (CardRuntimeException e) {
+            return 0x0001;
+        }
+
+        try {
+            working[0] = VERSION;
+            apdu.sendBytesLong(working, (short) 0, (short) 1);
+            length--;
+        } catch (CardRuntimeException e) {
+            return 0x0001;
+        }
+
+        try {
+            Util.setShort(working, (short) 0, length);
+            apdu.sendBytesLong(working, (short) 0, (short) 2);
+            length -= 2;
+        } catch (CardRuntimeException e) {
+            ISOException.throwIt(length);
+        }
+
+        try {
+            working[0] = (byte)0;
+            if (globalState.inBootloader() == true) {
+                working[0] = (byte)1;
+            }
+            apdu.sendBytesLong(working, (short) 0, (short) 1);
+            length--;
+        } catch (CardRuntimeException e) {
+            ISOException.throwIt(length);
+        }
+
+        try {
+            working[0] = (byte)0;
+            if (globalState.production() == true) {
+                working[0] = (byte)1;
+            }
+            apdu.sendBytesLong(working, (short) 0, (short) 1);
+            length--;
+        } catch (CardRuntimeException e) {
+            ISOException.throwIt(length);
+        }
+        try {
+            working[0] = (byte)locks.length;
+            apdu.sendBytesLong(working, (short) 0, (short) 1);
+            length--;
+        } catch (CardRuntimeException e) {
+            ISOException.throwIt(length);
+        }
+
+        try {
+            for (i = 0; i < (byte)locks.length; ++i) {
+                Util.setShort(working, (short) 0, locks[i].initialized());
+                apdu.sendBytesLong(working, (short) 0, (short) 2);
+                length -= 2;
+            }
+        } catch (CardRuntimeException e) {
+            ISOException.throwIt(length);
+        }
+
+        try {
+            Util.setShort(working, (short) 0, (short)lockStorage.length);
+            apdu.sendBytesLong(working, (short) 0, (short) 2);
+            length -= 2;
+        } catch (CardRuntimeException e) {
+            ISOException.throwIt(length);
+        }
+
+        try {
+          apdu.sendBytesLong(lockStorage, (short) 0, (short) lockStorage.length);
+          length -= (short) lockStorage.length;
+        } catch (CardRuntimeException e) {
+            ISOException.throwIt(length);
+        }
+        if (length != 0) {
+            ISOException.throwIt(length);
+        }
+        return 0;
+    }
+
+    private void sendResponseCode(APDU apdu, short resp) {
+        final byte buffer[] = apdu.getBuffer();
+        buffer[0] = (byte)(resp >> 8);
+        buffer[1] = (byte)(resp & 0xff);
+        apdu.setOutgoingAndSend((short)0, (short)2);
+    }
+
+    /**
+     * Returns 0x0 if no data needs to be sent.
+     */
+    private short sendLockData(APDU apdu, byte p1, byte p2) {
+        final byte buffer[] = apdu.getBuffer();
+        short resp = 0;
+        if (p1 >= (byte)locks.length) {
+            return 0x0001;
+        }
+        if (locks[p1].initialized() != 0) {
+          return locks[p1].initialized();
+        }
+
+        if (p2 == (byte) 0x00) {
+            resp = locks[p1].get(buffer, (short) 2);
+            Util.setShort(buffer, (short) 0, resp);
+            apdu.setOutgoingAndSend((short) 0, (short) 3);
+            return 0;
+        }
+        short length = (short)(3 + locks[p1].metadataLength());
+        try {
+            byte[] resp_val = new byte[1];
+            apdu.setOutgoing();
+            apdu.setOutgoingLength(length);
+
+            // Send a successful response code.
+            resp_val[0] = (byte) 0x00;
+            apdu.sendBytesLong(resp_val, (short) 0, (short) 1);
+            apdu.sendBytesLong(resp_val, (short) 0, (short) 1);
+            // Then the lock byte.
+            apdu.sendBytesLong(lockStorage, locks[p1].lockOffset(), (short) 1);
+            // Then any exported metadata.  Note that the metadataOffset may
+            // exclude some data which is considered private to the lock.
+            apdu.sendBytesLong(lockStorage, locks[p1].metadataOffset(),
+                               locks[p1].metadataLength());
+            return 0;
+        } catch (CardRuntimeException e) {
+            return 0x0002;
+        }
+    }
+
+    /**
+     * Handles incoming APDU requests
+     *
+     * @param apdu payload from the client.
+     */
+    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()) {
+            switch (ins) {
+            case ISO7816.INS_SELECT:
+                // Do nothing, successfully
+                return;
+            default:
+                ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
+            }
+            ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
+        }
+
+        short bytesRead = apdu.setIncomingAndReceive();
+        short numBytes = apdu.getIncomingLength();
+        short cdataOffset = apdu.getOffsetCdata();
+
+        byte p1 = (byte)(buffer[ISO7816.OFFSET_P1] & (byte)0xff);
+        byte p2 = (byte)(buffer[ISO7816.OFFSET_P2] & (byte)0xff);
+        short length = 0;
+        short expectedLength = 0;
+        short resp = 0;
+        boolean enable = false;
+        if (p1 != 0) {
+          enable = true;
+        }
+
+        switch (buffer[ISO7816.OFFSET_INS]) {
+        case INS_GET_STATE:  /* getStorageState(0x0, 0x0) */
+            resp = sendStorageState(apdu);
+            if (resp != 0) {
+                sendResponseCode(apdu, resp);
+            }
+            return;
+        case INS_LOAD: /* getSlot(id) */
+            resp = versionStorage.getSlot(p1, buffer, (short) 2);
+            buffer[0] = (byte)(resp >> 8);
+            buffer[1] = (byte)(resp & 0xff);
+            length = 2;
+            if (resp == 0) {
+                length += (short) VersionStorage.SLOT_BYTES;
+            }
+            // Always send the two bytes of status as they are more
+            // useful than the APDU error.
+            apdu.setOutgoingAndSend((short)0, length);
+            return;
+        case INS_STORE: /* setSlot(id) {uint64_t} */
+            resp = versionStorage.setSlot(p1, buffer, cdataOffset);
+            Util.setShort(buffer, (short) 0, resp);
+            apdu.setOutgoingAndSend((short) 0, (short) 2);
+            return;
+        case INS_GET_LOCK: /* getLock(lockId, sendMetadata) */
+            resp = sendLockData(apdu, p1, p2);
+            if (resp != 0) {
+                sendResponseCode(apdu, resp);
+            }
+            return;
+        case INS_SET_LOCK: /* setlock(index, val) { data } */
+            if (p1 >= (byte)locks.length) {
+                sendResponseCode(apdu, (short)0x0100);
+            }
+
+            if (bytesRead == (short) 0) {
+                resp = locks[p1].set(p2);
+             } else {
+                // Note, there may be more bytes to read than fit in the first pass.
+                // If so, we'll need to stage it in a transient buffer to pass in.
+                resp = (short) 0x0101;
+                if (bytesRead == numBytes) {
+                    resp = locks[p1].setWithMetadata(p2, buffer,
+                                                     cdataOffset,
+                                                     bytesRead);
+                }
+            }
+            sendResponseCode(apdu, resp);
+            return;
+        case INS_SET_PRODUCTION: /* setProduction(p1) */
+            if (globalState.setProduction(enable) == true) {
+                resp = 0x0000;
+            } else {
+                resp = 0x0001;
+            }
+            sendResponseCode(apdu, resp);
+            return;
+        /* carrierLockTest() { testVector } */
+        case INS_CARRIER_LOCK_TEST:
+            // Note, there may be more bytes to read than fit in the first pass.
+            // If so, we'll need to stage it in a transient buffer to pass in.
+            if (numBytes != bytesRead) {
+                resp = 0x0100;
+            }
+            resp = ((CarrierLock)locks[0]).testVector(buffer, cdataOffset, bytesRead);
+            sendResponseCode(apdu, resp);
+            return;
+        default:
+            ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
+        }
+    }
+
+    /**
+     * Restores data across upgrades
+     *
+     * @param buffer bytes
+     * @return true on success and false on failure.
+     */
+    private boolean restore(byte[] buffer) {
+        return osBackupImpl.restore(buffer, (short) 0);
+    }
+}
diff --git a/apps/boot/card/src/com/android/verifiedboot/storage/VersionStorage.java b/apps/boot/card/src/com/android/verifiedboot/storage/VersionStorage.java
new file mode 100644
index 0000000..ba7c3c8
--- /dev/null
+++ b/apps/boot/card/src/com/android/verifiedboot/storage/VersionStorage.java
@@ -0,0 +1,135 @@
+//
+// 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.verifiedboot.storage;
+
+import javacard.framework.CardRuntimeException;
+import javacard.framework.Util;
+
+import com.android.verifiedboot.storage.BackupInterface;
+import com.android.verifiedboot.globalstate.owner.OwnerInterface;
+
+class VersionStorage implements BackupInterface {
+    final public static byte NUM_SLOTS = (byte) 8;
+    final public static byte SLOT_BYTES = (byte) 8;
+    final private static byte EXPORT_VERSION = (byte)0x01;
+    private OwnerInterface globalState;
+    private byte[] storage;
+
+    public VersionStorage(OwnerInterface globalStateRef) {
+      storage = new byte[NUM_SLOTS * SLOT_BYTES];
+      globalState = globalStateRef;
+      Util.arrayFillNonAtomic(storage, (short) 0, (short) storage.length, (byte) 0x00);
+    }
+
+    /**
+     * Copies content from the given slot in |out| and returns true.
+     *
+     * @param slot slot number to retrieve
+     * @param out array to copy the slot data to.
+     * @param oOffset offset into |out| to start the copy at.
+     * @return 0x0 on success and an error otherwise.
+     */
+    public short getSlot(byte slot, byte[] out, short oOffset) {
+        if (slot > NUM_SLOTS - 1) {
+            return 0x0001;
+        }
+        try {
+            Util.arrayCopy(storage, (short)(SLOT_BYTES * slot),
+                         out, oOffset, SLOT_BYTES);
+        } catch (CardRuntimeException e) {
+            return 0x0002;
+        }
+        return 0x0;
+    }
+
+    /**
+     * Copies content for the given slot from |in| and returns true.
+     *
+     * @param slot slot number to retrieve
+     * @param in array to copy the slot data from.
+     * @param iOffset into |in| to start the copy at.
+     * @return 0x0 on success or an error code.
+     */
+    public short setSlot(byte slot, byte[] in, short iOffset) {
+        if (slot > NUM_SLOTS - 1) {
+            return 0x0001;
+        }
+        // Slots can be set only if we're in the bootloader
+        // or we're not yet in production.
+        if (globalState.production() == true &&
+            globalState.inBootloader() == false) {
+            return 0x0003;
+        }
+        try {
+            Util.arrayCopy(in, iOffset,
+                     storage, (short)(SLOT_BYTES * slot), SLOT_BYTES);
+        } catch (CardRuntimeException e) {
+            return 0x0002;
+        }
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Checks the size and version prefix prior to copying.
+     */
+    @Override
+    public boolean restore(byte[] inBytes, short inBytesOffset, short inBytesLength) {
+        if (inBytesLength == (short) 0 ||
+            inBytesLength > (short)(storage.length + 1)) {
+            return false;
+        }
+        if (inBytes[0] != EXPORT_VERSION) {
+            return false;
+        }
+        try {
+            Util.arrayCopy(inBytes, inBytesOffset, storage, (short) 0, inBytesLength);
+        } catch (CardRuntimeException e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Copies storage to outBytes with a leading version byte which can be
+     * checked on restore.
+     */
+    @Override
+    public short backup(byte[] outBytes, short outBytesOffset) {
+        try {
+            // Tag the export version that way if any internal storage format changes
+            // occur, they can be handled.
+            outBytes[(short)(outBytesOffset + 1)] = EXPORT_VERSION;
+            Util.arrayCopy(storage, (short) 0, outBytes, (short)(outBytesOffset + 1),
+                           (short)storage.length);
+            return (short)(storage.length + 1);
+        } catch (CardRuntimeException e) {
+            return 0x0;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public short backupSize() {
+      return (short)(storage.length + 1);
+    }
+}
diff --git a/apps/boot/ese_boot_tool.cpp b/apps/boot/ese_boot_tool.cpp
new file mode 100644
index 0000000..e20d125
--- /dev/null
+++ b/apps/boot/ese_boot_tool.cpp
@@ -0,0 +1,428 @@
+/*
+ * 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.
+ *
+ * Commandline tool for interfacing with the Boot Storage app.
+ */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <cutils/properties.h>
+#include <ese/ese.h>
+ESE_INCLUDE_HW(ESE_HW_NXP_PN80T_NQ_NCI);
+
+#include "include/ese/app/boot.h"
+
+void usage(const char *prog) {
+  fprintf(stderr,
+    "Usage:\n"
+    "%s <cmd> <args>\n"
+    "    state    get\n"
+    "    production set {true,false}\n"
+    "    rollback set <іndex> <value>\n"
+    "             get <іndex>\n"
+    "    lock get {carrier,device,boot,owner}\n"
+    "         set carrier 0 <unlockToken>\n"
+    "                     <nonzero byte> {IMEI,MEID}\n"
+    "             device <byte>\n"
+    "             boot <byte>\n"
+    "             owner 0\n"
+    "             owner <non-zero byte> <keyValue>\n"
+    "    verify-key test <blob>\n"
+    "    verify-key auto\n"
+    "\n"
+    "Note, any non-zero byte value is considered 'locked'.\n"
+    "\n\n", prog);
+}
+
+#define handle_error(ese, result) ;
+#if 0  // TODO
+void handle_error(struct EseInterface *ese, EseAppResult result) {
+  uint32_t minutes =  ese_cooldown_get(ese, ESE_COOLDOWN_ATTACK);
+  set_timer_cb(minutes * 60, power_down_ese);
+  ...
+}
+#endif
+
+static void print_hexdump(const uint8_t* data, int start, int stop) {
+  for (int i = start; i < stop; ++i) {
+    if (i % 20 == start - 1) {
+      printf("\n");
+    }
+    printf("%.2x ", data[i]);
+  }
+  printf("\n");
+}
+
+static uint16_t hexify(const std::string& input, std::vector<uint8_t> *output) {
+  for (auto it = input.cbegin(); it != input.cend(); ++it) {
+    std::string hex;
+    hex.push_back(*it++);
+    hex.push_back(*it);
+    output->push_back(static_cast<uint8_t>(std::stoi(hex, nullptr, 16)));
+  }
+  return static_cast<uint16_t>(output->size() & 0xffff);
+}
+
+
+bool get_property_helper(const char *key, char *value) {
+  if (property_get(key, value, NULL) == 0) {
+    fprintf(stderr, "Property '%s' is empty!\n", key);
+    return false;
+  }
+  return true;
+}
+
+// Serializes the data to a string which is hashed by the applet.
+bool collect_device_data(const std::string &modem_id, std::string *device_data) {
+  static const char *kDeviceKeys[] = {
+    "ro.product.brand",
+    "ro.product.device",
+    "ro.build.product",
+    "ro.serialno",
+    "",
+    "ro.product.manufacturer",
+    "ro.product.model",
+    NULL,
+  };
+  uint8_t len = 0;
+  const char **key = &kDeviceKeys[0];
+  do {
+    if (strlen(*key) == 0) {
+      len = static_cast<uint8_t>(modem_id.length());
+      device_data->push_back(len);
+      device_data->append(modem_id);
+    } else {
+      char value[PROPERTY_VALUE_MAX];
+      if (!get_property_helper(*key, &value[0])) {
+        return false;
+      }
+      len = static_cast<uint8_t>(strlen(value));
+      device_data->push_back(len);
+      device_data->append(value);
+    }
+    if (*++key == NULL) {
+      break;
+    }
+  } while (*key != NULL);
+  return true;
+}
+
+int handle_production(struct EseBootSession *session, std::vector<std::string> &args) {
+  EseAppResult res;
+  if (args[1] != "set") {
+    fprintf(stderr, "production: unknown command '%s'\n", args[2].c_str());
+    return -1;
+  }
+  if (args.size() < 3) {
+    fprintf(stderr, "production: not enough arguments\n");
+    return -1;
+  }
+  bool prod = false;
+  if (args[2] == "true") {
+    prod = true;
+  } else if (args[2] == "false") {
+    prod = false;
+  } else {
+    fprintf(stderr, "production: must be 'true' or 'false'\n");
+    return -1;
+  }
+  res = ese_boot_set_production(session, prod);
+  if (res == ESE_APP_RESULT_OK) {
+    printf("production mode changed\n");
+    return 0;
+  }
+  fprintf(stderr, "production: failed to change (%.8x)\n", res);
+  return 1;
+}
+
+int handle_state(struct EseBootSession *session, std::vector<std::string> &args) {
+  EseAppResult res;
+  if (args[1] != "get") {
+    fprintf(stderr, "state: unknown command '%s'\n", args[2].c_str());
+    return -1;
+  }
+  // Read in the hex unlockToken and hope for the best.
+  std::vector<uint8_t> data;
+  data.resize(8192);
+  uint16_t len = static_cast<uint16_t>(data.size());
+  res = ese_boot_get_state(session, data.data(), len);
+  if (res != ESE_APP_RESULT_OK) {
+    fprintf(stderr, "state: failed (%.8x)\n", res);
+    return 1;
+  }
+  // TODO: ese_boot_get_state should guarantee length is safe...
+  len = (data[1] << 8) | (data[2]) + 3;
+  printf("Boot Storage State:\n    ");
+  print_hexdump(data.data(), 3, len);
+  return 0;
+}
+
+
+static const uint8_t auto_data[] = {
+  // lastNonce
+  0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x40,
+  // deviceData
+  0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+  0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+  0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+  0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+  // Version
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  // Nonce
+  0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+  // Signature
+  0x79, 0x71, 0xd9, 0x5a, 0x2c, 0x64, 0x16, 0xdc, 0x9c, 0xff, 0xa5, 0xfe,
+  0x6a, 0xd3, 0x80, 0x74, 0xa7, 0xc6, 0x1d, 0x59, 0xce, 0x90, 0x4f, 0xa1,
+  0xe7, 0x24, 0x0d, 0x9f, 0x18, 0x2f, 0x21, 0xd1, 0x2b, 0xec, 0xf3, 0x5e,
+  0x9e, 0xa7, 0x27, 0x11, 0xfa, 0x42, 0xa3, 0x3c, 0xbc, 0x27, 0xa6, 0xff,
+  0x2a, 0x2c, 0x01, 0xae, 0x1f, 0x29, 0xf8, 0x06, 0x73, 0x70, 0x21, 0x8b,
+  0xcb, 0x0b, 0xa1, 0xd4, 0xbc, 0xc4, 0xbc, 0x7a, 0x51, 0x87, 0xf4, 0x64,
+  0xdc, 0x18, 0x0f, 0x44, 0xd2, 0x95, 0x78, 0xe8, 0x51, 0xc5, 0xac, 0x6a,
+  0x55, 0x2b, 0x72, 0x64, 0x31, 0x56, 0x7a, 0x4f, 0x46, 0x15, 0xec, 0x1d,
+  0xe4, 0xc5, 0x9b, 0x2a, 0xcf, 0x81, 0x39, 0xc9, 0x1b, 0x60, 0x89, 0x56,
+  0x1f, 0x91, 0x62, 0xd1, 0xa0, 0x10, 0xba, 0x80, 0x5a, 0x30, 0x52, 0x6e,
+  0x46, 0x45, 0xff, 0x0f, 0xf5, 0x4a, 0xf3, 0x0d, 0x64, 0x6e, 0x58, 0xa7,
+  0xbe, 0x9e, 0xba, 0xa9, 0x78, 0xee, 0x10, 0xc7, 0xea, 0xc4, 0xc0, 0xdb,
+  0x40, 0xe5, 0xe6, 0xae, 0xf1, 0x5f, 0x88, 0xd7, 0x60, 0x73, 0xbd, 0x97,
+  0xfc, 0x01, 0x4a, 0xff, 0x29, 0x6f, 0x86, 0x17, 0x33, 0x53, 0xfd, 0xe0,
+  0xb2, 0x4f, 0xe7, 0xf8, 0x28, 0xf3, 0x23, 0x88, 0xd6, 0x60, 0x52, 0xa4,
+  0x77, 0x43, 0x6c, 0x9c, 0x1e, 0x35, 0x58, 0x7f, 0x1c, 0x04, 0x06, 0xec,
+  0x3c, 0x2f, 0x18, 0xa6, 0xee, 0x71, 0xc4, 0x26, 0xf6, 0x53, 0x05, 0x8d,
+  0x24, 0xad, 0x11, 0xf3, 0x2f, 0x11, 0xe6, 0x75, 0x65, 0xfd, 0x14, 0xcf,
+  0x66, 0x5b, 0x27, 0x91, 0x3d, 0xfe, 0x59, 0xc2, 0x82, 0x3f, 0xa1, 0x36,
+  0xe0, 0x3d, 0xb9, 0x86, 0xe3, 0xa5, 0x7a, 0xb6, 0xde, 0x72, 0xab, 0x31,
+  0x4f, 0x06, 0xb0, 0xd8, 0x4e, 0xfa, 0x1b, 0xd0, 0x3a, 0x93, 0xae, 0x11,
+  0xaa, 0x64, 0x2b, 0xd3,
+};
+
+int handle_verify_key(struct EseBootSession *session, std::vector<std::string> &args) {
+  EseAppResult res;
+  if (args[1] != "test" && args[1] != "auto") {
+    fprintf(stderr, "verify-key: unknown command '%s'\n", args[2].c_str());
+    return -1;
+  }
+  // Read in the hex unlockToken and hope for the best.
+  std::vector<uint8_t> data;
+  uint16_t len;
+  if (args[1] == "test") {
+    len = hexify(args[2], &data);
+    const uint16_t kExpectedLength = (sizeof(uint64_t) * 2 + sizeof(uint64_t) + 32 + 256);
+    if (len != kExpectedLength) {
+      fprintf(stderr, "verify-key: expected blob of length %hu not %hu\n", kExpectedLength, len);
+      fprintf(stderr, "verify-key: format is as follows (in hex):\n");
+      fprintf(stderr, "[lastNonce:8][deviceData:32][version:8][unlockNonce:8][RSA-SHA256PKCS#1 Signature:256]\n");
+      return 2;
+    }
+  } else {
+    len = sizeof(auto_data);
+    data.assign(&auto_data[0], auto_data + sizeof(auto_data));
+  }
+  printf("verify-key: sending the following test data:\n");
+  print_hexdump(data.data(), 0, data.size());
+  res = ese_boot_carrier_lock_test(session, data.data(), len);
+  if (res == ESE_APP_RESULT_OK) {
+    printf("verified\n");
+    return 0;
+  }
+  printf("failed to verify (%.8x)\n", res);
+  return 1;
+}
+
+int handle_lock_state(struct EseBootSession *session, std::vector<std::string> &args) {
+  EseAppResult res;
+  EseBootLockId lockId;
+  uint16_t lockMetaLen = 0;
+  uint8_t lockMeta[1024];
+  if (args[2] == "carrier") {
+    lockId = kEseBootLockIdCarrier;
+  } else if (args[2] == "device") {
+    lockId = kEseBootLockIdDevice;
+  } else if (args[2] == "boot") {
+    lockId = kEseBootLockIdBoot;
+  } else if (args[2] == "owner") {
+    lockId = kEseBootLockIdOwner;
+  } else {
+    fprintf(stderr, "lock: unknown lock '%s'\n", args[2].c_str());
+    return 1;
+  }
+
+  if (args[1] == "get") {
+    uint8_t lockVal = 0;
+    if (lockId == kEseBootLockIdCarrier ||
+        lockId == kEseBootLockIdOwner) {
+      res = ese_boot_lock_xget(session, lockId, lockMeta,
+                               sizeof(lockMeta), &lockMetaLen);
+    } else {
+      res = ese_boot_lock_get(session, lockId, &lockVal);
+    }
+    if (res == ESE_APP_RESULT_OK) {
+      if (lockMetaLen > 0) {
+        lockVal = lockMeta[0];
+      }
+      printf("%.2x\n", lockVal);
+      if (lockMetaLen > 0) {
+        print_hexdump(&lockMeta[1], 0, lockMetaLen - 1);
+      }
+      return 0;
+     }
+    fprintf(stderr, "lock: failed to get '%s' (%.8x)\n", args[2].c_str(), res);
+    handle_error(session->ese, res);
+    return 2;
+  } else if (args[1] == "set") {
+    if (args.size() < 4) {
+      fprintf(stderr, "lock set: not enough arguments supplied\n");
+      return 2;
+    }
+    uint8_t lockVal = static_cast<uint8_t>(std::stoi(args[3], nullptr, 0));
+    if (lockId == kEseBootLockIdCarrier) {
+      res = ESE_APP_RESULT_ERROR_UNCONFIGURED;
+      if (lockVal != 0) {
+        std::string device_data;
+        device_data.push_back(lockVal);
+        if (!collect_device_data(args[4], &device_data)) {
+          fprintf(stderr, "carrier set 1: failed to aggregate device data\n");
+          return 3;
+        }
+        printf("Setting carrier lock with '");
+        for (std::string::iterator it = device_data.begin();
+             it != device_data.end(); ++it) {
+          printf("%c", isprint(*it) ? *it : '_');
+        }
+        printf("'\n");
+        const uint8_t *data = reinterpret_cast<const uint8_t *>(device_data.data());
+        res = ese_boot_lock_xset(session, lockId, data, device_data.length());
+      } else {
+        // Read in the hex unlockToken and hope for the best.
+        std::vector<uint8_t> data;
+        data.push_back(lockVal);
+        uint16_t len = hexify(args[4], &data);
+        if (len == 1) {
+          fprintf(stderr, "lock: carrier unlock requires a token\n");
+          return 5;
+        }
+        printf("Passing an unlockToken of length %d to the eSE\n", len - 1);
+        res = ese_boot_lock_xset(session, lockId, data.data(), len);
+      }
+    } else if (lockId == kEseBootLockIdOwner && lockVal != 0) {
+      std::vector<uint8_t> data;
+      data.push_back(lockVal);
+      uint16_t len = hexify(args[4], &data);
+      res = ese_boot_lock_xset(session, lockId, data.data(), len);
+    } else {
+      res = ese_boot_lock_set(session, lockId, lockVal);
+    }
+    if (res != ESE_APP_RESULT_OK) {
+      fprintf(stderr, "lock: failed to set %s state (%.8x)\n",
+              args[2].c_str(), res);
+      handle_error(session->ese, res);
+      return 4;
+    }
+    return 0;
+  }
+  fprintf(stderr, "lock: invalid command\n");
+  return -1;
+}
+
+int handle_rollback(struct EseBootSession *session, std::vector<std::string> &args) {
+  int index = std::stoi(args[2], nullptr, 0);
+  uint8_t slot = static_cast<uint8_t>(index & 0xff);
+  if (slot > 7) {
+    fprintf(stderr, "rollback: slot must be one of [0-7]\n");
+    return 2;
+  }
+
+  uint64_t value = 0;
+  if (args.size() > 3) {
+    unsigned long long conv = std::stoull(args[3], nullptr, 0);
+    value = static_cast<uint64_t>(conv);
+  }
+
+  EseAppResult res;
+  if (args[1] == "get") {
+    res = ese_boot_rollback_index_read(session, slot, &value);
+    if (res != ESE_APP_RESULT_OK) {
+      fprintf(stderr, "rollback: failed to read slot %2x (%.8x)\n",
+              slot, res);
+      handle_error(session->ese, res);
+      return 3;
+    }
+    printf("%" PRIu64 "\n", value);
+    return 0;
+  } else if (args[1] == "set") {
+    res = ese_boot_rollback_index_write(session, slot, value);
+    if (res != ESE_APP_RESULT_OK) {
+      fprintf(stderr, "rollback: failed to write slot %2x (%.8x)\n",
+              slot, res);
+      handle_error(session->ese, res);
+      return 4;
+    }
+    return 0;
+  }
+  fprintf(stderr, "rollback: unknown command '%s'\n", args[1].c_str());
+  return -1;
+}
+
+int handle_args(struct EseBootSession *session, const char *prog, std::vector<std::string> &args) {
+  if (args[0] == "rollback") {
+    return handle_rollback(session, args);
+  } else if (args[0] == "lock") {
+    return handle_lock_state(session, args);
+  } else if (args[0] == "verify-key") {
+    return handle_verify_key(session, args);
+  } else if (args[0] == "production") {
+    return handle_production(session, args);
+  } else if (args[0] == "state") {
+    return handle_state(session, args);
+  } else {
+    usage(prog);
+    return 1;
+  }
+  return 0;
+}
+
+int main(int argc, char **argv) {
+  if (argc < 3) {
+    usage(argv[0]);
+    return 1;
+  }
+  // TODO(wad): move main to a class so we can just dep inject the hw.
+  struct EseInterface ese = ESE_INITIALIZER(ESE_HW_NXP_PN80T_NQ_NCI);
+  ese_open(&ese, nullptr);
+  EseBootSession session;
+  ese_boot_session_init(&session);
+  EseAppResult res = ese_boot_session_open(&ese, &session);
+  if (res != ESE_APP_RESULT_OK) {
+    fprintf(stderr, "failed to initiate session (%.8x)\n", res);
+    handle_error(ese, res);
+    return 1;
+  }
+  std::vector<std::string> args;
+  args.assign(argv + 1, argv + argc);
+  int ret = handle_args(&session, argv[0], args);
+
+  res = ese_boot_session_close(&session);
+  if (res != ESE_APP_RESULT_OK) {
+    handle_error(&ese, res);
+  }
+  ese_close(&ese);
+  return ret;
+}
diff --git a/apps/boot/include/ese/app/boot.h b/apps/boot/include/ese/app/boot.h
new file mode 100644
index 0000000..72892bc
--- /dev/null
+++ b/apps/boot/include/ese/app/boot.h
@@ -0,0 +1,197 @@
+/*
+ * 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.
+ *
+ * See the README.md in the parent (../../..) directory.
+ */
+
+#ifndef ESE_APP_BOOT_H_
+#define ESE_APP_BOOT_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
+
+/**
+ * EseBootSession carries the necessary start for interfacing
+ * with the methods below.
+ *
+ * Its usage follows a lifecycle like:
+ *
+ *   EseAppResult res;
+ *   EseBootSession session;
+ *   ese_boot_session_init(&session);
+ *   res = ese_boot_session_open(ese, &session);
+ *   if (res != ESE_APP_RESULT_OK) {
+ *     ... handle error (especially cooldown) ...
+ *   }
+ *   ... ese_boot_* ...
+ *   ese_boot_session_close(&session);
+ *
+ */
+struct EseBootSession {
+  struct EseInterface *ese;
+  bool active;
+  uint8_t channel_id;
+};
+
+/**
+ * The Storage applet supports up to 8 64-bit storage slots for storing
+ * rollback protection indices.
+ */
+const uint8_t kEseBootRollbackSlotCount = 8;
+/**
+ * When using the LOCK_OWNER, a key, or other relevant value, must be supplied.
+ * It may be at most OWNER_LOCK_METADATA_SIZE as defined in
+ * card/src/com/android/verifiedboot/storage/Storage.java.
+ */
+const uint16_t kEseBootOwnerKeyMax = 2048;
+
+
+/* Keep in sync with card/src/com/android/verifiedboot/storage/Storage.java */
+/**
+ * This enum reflects the types of Locks that are supported by
+ * the ese_boot_lock_* calls.
+ */
+typedef enum {
+  kEseBootLockIdCarrier = 0,
+  kEseBootLockIdDevice,
+  kEseBootLockIdBoot,
+  kEseBootLockIdOwner,
+  kEseBootLockIdMax = kEseBootLockIdOwner,
+} EseBootLockId;
+
+
+/**
+ * Initializes a pre-allocated |session| for use.
+ */
+void ese_boot_session_init(struct EseBootSession *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_boot_session_open(struct EseInterface *ese, struct EseBootSession *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_boot_session_close(struct EseBootSession *session);
+
+/**
+ * Retrieves the uint8_t value stored for the lock specified by |lockId|.
+ * On success, the value is stored in |lockVal|.  If the byte is 0x0, then
+ * the lock is cleared (or unlocked).  If it is any non-zero value, then it
+ * is locked.  Any specific byte value may have additional meaning to the
+ * caller.
+ *
+ * @returns ESE_APP_RESULT_OK if |lockVal| contains a valid byte.
+ */
+EseAppResult ese_boot_lock_get(struct EseBootSession *session, EseBootLockId lockId, uint8_t *lockVal);
+/**
+ * Retrieves extended lock data for the lock specified by |lockId|.
+ *
+ * |maxSize| specifies how many bytes may be written to |lockData|. |dataLen|
+ * will be updated to hold the length of the data received from the applet on
+ * success.
+ *
+ * The first byte of |lockData| will be the lock's value.  The remaining bytes
+ * are the associated metadata.  See the README.md for more details
+ * on each lock's behavior.
+ *
+ * @returns ESE_APP_RESULT_OK on success.
+ */
+EseAppResult ese_boot_lock_xget(
+    struct EseBootSession *session, EseBootLockId lockId, uint8_t *lockData,
+    uint16_t maxSize, uint16_t *dataLen);
+
+/**
+ * Sets the lock specified by |lockId| to |lockVal|.
+ *
+ * @returns ESE_APP_RESULT_OK on success.
+ */
+
+EseAppResult ese_boot_lock_set(struct EseBootSession *session, EseBootLockId lockId, uint8_t lockVal);
+/**
+ * Sets the lock and its metadata specified by |lockId| and |lockData|,
+ * respectively.  |dataLen| indicates the length of |lockData|.
+ *
+ * The first byte of |lockData| will be treated as the new value for the lock.
+ *
+ * @returns ESE_APP_RESULT_OK on success.
+ */
+EseAppResult ese_boot_lock_xset(struct EseBootSession *session, EseBootLockId lockId, const uint8_t *lockData, uint16_t dataLen);
+
+/**
+ * Performs a test of the carrier unlock code by allowing the caller to specify
+ * a fake internal nonce value, fake internal device data, as well as an actual
+ * unlock token (made up of a nonce and signature).
+ *
+ * @returns ESE_APP_RESULT_OK on success.  On failure, it is worthwhile to
+ *          check the upper two bytes in the result code if the lower two bytes
+ *          are ESE_APP_RESULT_ERROR_APPLET as it will provide an error
+ *          specific to the code path.  These applet codes are not (yet)
+ *          considered API and should be relied on for debugging.
+ */
+EseAppResult ese_boot_carrier_lock_test(struct EseBootSession *session, const uint8_t *testdata, uint16_t len);
+
+/**
+ * Transitions the applet from "factory" mode to "production" mode.
+ * This can only be done if the bootloader gpio has not been cleared.
+ *
+ * When not in production mode, the applet will ignore the bootloader gpio
+ * and allow for all the locks to be provisioned.  Once |mode| is set
+ * to true, LOCK_CARRIER can not be "lock"ed once cleared and any locks
+ * that depend on being in the bootloader (gpio not cleared) will respect
+ * that value.
+ */
+EseAppResult ese_boot_set_production(struct EseBootSession *session, bool production_mode);
+
+/**
+ * Debugging helper that emits the internal value of production, bootloader gpio,
+ * and lock initialization and storage.  It is not insecure in the field, but
+ * it is not expected to be needed during normal operation.
+ */
+EseAppResult ese_boot_get_state(struct EseBootSession *session, uint8_t *state, uint16_t maxSize);
+
+/**
+ * Stores |value| in the specified |slot| in the applet.
+ *
+ * @returns ESE_APP_RESULT_OK on success
+ */
+EseAppResult ese_boot_rollback_index_write(struct EseBootSession *session, uint8_t slot, uint64_t value);
+
+/**
+ * Reads a uint64_t from |slot| into |value|.
+ *
+ * @retuns ESE_APP_RESULT_OK on success.
+ */
+EseAppResult ese_boot_rollback_index_read(struct EseBootSession *session, uint8_t slot, uint64_t *value);
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* ESE_APP_BOOT_H_ */
diff --git a/apps/boot/tests/Android.bp b/apps/boot/tests/Android.bp
new file mode 100644
index 0000000..f092c6c
--- /dev/null
+++ b/apps/boot/tests/Android.bp
@@ -0,0 +1,27 @@
+//
+// 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_test {
+    name: "ese_app_boot_tests",
+    srcs: ["ese_app_boot_tests.cpp", "ese_operations_wrapper.cpp"],
+    host_supported: true,
+    cflags: ["-Wno-unused-parameter"],
+    shared_libs: [
+        "libese-app-boot-fortest",
+        "libese",
+        "liblog",
+    ],
+}
diff --git a/apps/boot/tests/ese_app_boot_tests.cpp b/apps/boot/tests/ese_app_boot_tests.cpp
new file mode 100644
index 0000000..dcac797
--- /dev/null
+++ b/apps/boot/tests/ese_app_boot_tests.cpp
@@ -0,0 +1,162 @@
+/*
+ * 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 <vector>
+#include <gtest/gtest.h>
+
+#include <ese/ese.h>
+#include <ese/app/boot.h>
+#include "../boot_private.h"
+
+#include "ese_operations_interface.h"
+#include "ese_operations_wrapper.h"
+
+using ::testing::Test;
+
+class FakeTransceive : public EseOperationsInterface {
+ public:
+  FakeTransceive() { }
+  virtual ~FakeTransceive() { }
+
+  virtual int EseOpen(struct EseInterface *ese, void *data) {
+    return 0;
+  }
+  virtual uint32_t EseHwReceive(struct EseInterface *ese, uint8_t *data,
+                                uint32_t len, int complete) { return -1; }
+  virtual uint32_t EseHwTransmit(struct EseInterface *ese, const uint8_t *data,
+                                 uint32_t len, int complete) { return -1; }
+  virtual int EseReset(struct EseInterface *ese) { return -1; }
+  virtual int EsePoll(struct EseInterface *ese, uint8_t poll_for, float timeout, int complete) {
+    return -1;
+  }
+  virtual void EseClose(struct EseInterface *ese) { }
+
+  virtual uint32_t EseTransceive(struct EseInterface *ese, const struct EseSgBuffer *tx_sg, uint32_t tx_nsg,
+                                 struct EseSgBuffer *rx_sg, uint32_t rx_nsg) {
+    // Get this calls expected data.
+    EXPECT_NE(0UL, invocations.size());
+    if (!invocations.size())
+      return 0;
+    const struct Invocation &invocation = invocations.at(0);
+
+    uint32_t tx_total = ese_sg_length(tx_sg, tx_nsg);
+    EXPECT_EQ(invocation.expected_tx.size(), tx_total);
+    std::vector<uint8_t> incoming(tx_total);
+    ese_sg_to_buf(tx_sg, tx_nsg, 0, tx_total, incoming.data());
+    EXPECT_EQ(0, memcmp(incoming.data(), invocation.expected_tx.data(), tx_total));
+
+    // Supply the golden return data and pop off the invocation.
+    ese_sg_from_buf(rx_sg, rx_nsg, 0, invocation.rx.size(), invocation.rx.data());
+    uint32_t rx_total = invocation.rx.size();
+    invocations.erase(invocations.begin());
+    return rx_total;
+  }
+
+  struct Invocation {
+    std::vector<uint8_t> rx;
+    std::vector<uint8_t> expected_tx;
+  };
+
+  std::vector<Invocation> invocations;
+};
+
+class BootAppTest : public virtual Test {
+ public:
+  BootAppTest() { }
+  virtual ~BootAppTest() { }
+
+  void SetUp() {
+    // Configure ese with our internal ops.
+    EseOperationsWrapper::InitializeEse(&ese_, &trans_);
+  }
+
+  void TearDown() {
+    trans_.invocations.resize(0);
+  }
+
+ protected:
+  FakeTransceive trans_;
+  EseInterface ese_;
+};
+
+TEST_F(BootAppTest, EseBootSessionOpenSuccess) {
+  EXPECT_EQ(0, ese_open(&ese_, NULL));
+  struct EseBootSession session;
+  ese_boot_session_init(&session);
+
+  trans_.invocations.resize(2);
+
+  trans_.invocations[0].expected_tx.resize(kManageChannelOpenLength);
+  memcpy(trans_.invocations[0].expected_tx.data(), kManageChannelOpen,
+    kManageChannelOpenLength);
+  trans_.invocations[0].rx.resize(3);
+  trans_.invocations[0].rx[0] = 0x01;  // Channel
+  trans_.invocations[0].rx[1] = 0x90;  // Return code
+  trans_.invocations[0].rx[2] = 0x00;
+
+  trans_.invocations[1].expected_tx.resize(kSelectAppletLength);
+  memcpy(trans_.invocations[1].expected_tx.data(), kSelectApplet,
+    kSelectAppletLength);
+  trans_.invocations[1].expected_tx[0] |= 0x01;  // Channel
+  trans_.invocations[1].rx.resize(2);
+  trans_.invocations[1].rx[0] = 0x90;
+  trans_.invocations[1].rx[1] = 0x00;
+  EXPECT_EQ(ESE_APP_RESULT_OK, ese_boot_session_open(&ese_, &session));
+};
+
+TEST_F(BootAppTest, EseBootSessionOpenCooldown) {
+  EXPECT_EQ(0, ese_open(&ese_, NULL));
+  struct EseBootSession session;
+  ese_boot_session_init(&session);
+
+  trans_.invocations.resize(1);
+
+  trans_.invocations[0].expected_tx.resize(kManageChannelOpenLength);
+  memcpy(trans_.invocations[0].expected_tx.data(), kManageChannelOpen,
+    kManageChannelOpenLength);
+  trans_.invocations[0].rx.resize(2);
+  trans_.invocations[0].rx[0] = 0x66;  // Return code
+  trans_.invocations[0].rx[1] = 0xA5;
+  // This return code should allow a subsequent call of
+  // ese_boot_cooldown_values();
+  EXPECT_EQ(ESE_APP_RESULT_ERROR_COOLDOWN, ese_boot_session_open(&ese_, &session));
+};
+
+TEST_F(BootAppTest, EseBootSessionOpenSelectFailure) {
+  EXPECT_EQ(0, ese_open(&ese_, NULL));
+  struct EseBootSession session;
+  ese_boot_session_init(&session);
+
+  trans_.invocations.resize(2);
+
+  trans_.invocations[0].expected_tx.resize(kManageChannelOpenLength);
+  memcpy(trans_.invocations[0].expected_tx.data(), kManageChannelOpen,
+    kManageChannelOpenLength);
+  trans_.invocations[0].rx.resize(3);
+  trans_.invocations[0].rx[0] = 0x01;  // Channel
+  trans_.invocations[0].rx[1] = 0x90;  // Return code
+  trans_.invocations[0].rx[2] = 0x00;
+
+  trans_.invocations[1].expected_tx.resize(kSelectAppletLength);
+  memcpy(trans_.invocations[1].expected_tx.data(), kSelectApplet,
+    kSelectAppletLength);
+  trans_.invocations[1].expected_tx[0] |= 0x01;  // Channel
+  trans_.invocations[1].rx.resize(2);
+  trans_.invocations[1].rx[0] = 0x90;
+  trans_.invocations[1].rx[1] = 0x01;
+  EXPECT_EQ(ESE_APP_RESULT_ERROR_OS, ese_boot_session_open(&ese_, &session));
+};
diff --git a/apps/boot/tests/ese_operations_interface.h b/apps/boot/tests/ese_operations_interface.h
new file mode 100644
index 0000000..54a9c56
--- /dev/null
+++ b/apps/boot/tests/ese_operations_interface.h
@@ -0,0 +1,38 @@
+/*
+ * 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_OPERATIONS_INTERFACE_H_
+#define ESE_OPERATIONS_INTERFACE_H_ 1
+
+#include <ese/ese.h>
+
+class EseOperationsInterface {
+ public:
+  EseOperationsInterface() { }
+  virtual ~EseOperationsInterface() { };
+
+  virtual int EseOpen(struct EseInterface *ese, void *data) = 0;
+  virtual uint32_t EseHwReceive(struct EseInterface *ese, uint8_t *data, uint32_t len, int complete) = 0;
+  virtual uint32_t EseHwTransmit(struct EseInterface *ese, const uint8_t *data, uint32_t len, int complete) = 0;
+  virtual int EseReset(struct EseInterface *ese) = 0;
+  virtual uint32_t EseTransceive(struct EseInterface *ese, const struct EseSgBuffer *tx_sg, uint32_t tx_nsg,
+                                 struct EseSgBuffer *rx_sg, uint32_t rx_nsg) = 0;
+  virtual int EsePoll(struct EseInterface *ese, uint8_t poll_for, float timeout, int complete) = 0;
+  virtual void EseClose(struct EseInterface *ese) = 0;
+};
+
+#endif  // ESE_OPERATIONS_INTERFACE_H_
diff --git a/apps/boot/tests/ese_operations_wrapper.cpp b/apps/boot/tests/ese_operations_wrapper.cpp
new file mode 100644
index 0000000..cbf1bd5
--- /dev/null
+++ b/apps/boot/tests/ese_operations_wrapper.cpp
@@ -0,0 +1,77 @@
+/*
+ * 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 <ese/ese.h>
+
+#include "ese_operations_interface.h"
+#include "ese_operations_wrapper.h"
+
+void EseOperationsWrapper::InitializeEse(
+    struct EseInterface *ese, EseOperationsInterface *ops_interface) {
+  EseOperationsWrapperData data;
+  data.ops_interface = ops_interface;
+  ese_init(ese, data.wrapper);
+}
+
+static int EseOpen(struct EseInterface *ese, void *data) {
+  return EseOperationsWrapperData::ops_interface->EseOpen(ese, data);
+}
+
+static uint32_t EseHwReceive(struct EseInterface *ese, uint8_t *data, uint32_t len, int complete) {
+  return EseOperationsWrapperData::ops_interface->EseHwReceive(ese, data, len, complete);
+}
+
+static uint32_t EseHwTransmit(struct EseInterface *ese, const uint8_t *data, uint32_t len, int complete) {
+  return EseOperationsWrapperData::ops_interface->EseHwTransmit(ese, data, len, complete);
+}
+
+static int EseReset(struct EseInterface *ese) {
+  return EseOperationsWrapperData::ops_interface->EseReset(ese);
+}
+
+static uint32_t EseTransceive(struct EseInterface *ese, const struct EseSgBuffer *tx_sg, uint32_t tx_nsg,
+                               struct EseSgBuffer *rx_sg, uint32_t rx_nsg) {
+  return EseOperationsWrapperData::ops_interface->EseTransceive(ese, tx_sg, tx_nsg, rx_sg, rx_nsg);
+}
+
+static int EsePoll(struct EseInterface *ese, uint8_t poll_for, float timeout, int complete) {
+  return EseOperationsWrapperData::ops_interface->EsePoll(ese, poll_for, timeout, complete);
+}
+
+static void EseClose(struct EseInterface *ese) {
+  return EseOperationsWrapperData::ops_interface->EseClose(ese);
+}
+
+EseOperationsInterface *EseOperationsWrapperData::ops_interface =
+  reinterpret_cast<EseOperationsInterface *>(NULL);
+
+static const char *kErrors[] = {};
+const struct EseOperations EseOperationsWrapperData::ops = {
+  .name = "EseOperationsWrapper HW",
+  .open = &EseOpen,
+  .hw_receive = &EseHwReceive,
+  .hw_transmit = &EseHwTransmit,
+  .hw_reset = &EseReset,
+  .poll = &EsePoll,
+  .transceive = &EseTransceive,
+  .close = &EseClose,
+  .opts = NULL,
+  .errors = kErrors,
+  .errors_count = 0,
+};
+const struct EseOperations *EseOperationsWrapperData::wrapper_ops =
+  &EseOperationsWrapperData::ops;
diff --git a/apps/boot/tests/ese_operations_wrapper.h b/apps/boot/tests/ese_operations_wrapper.h
new file mode 100644
index 0000000..4d1d57f
--- /dev/null
+++ b/apps/boot/tests/ese_operations_wrapper.h
@@ -0,0 +1,42 @@
+/*
+ * 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_OPERATIONS_WRAPPER_H_
+#define ESE_OPERATIONS_WRAPPER_H_ 1
+
+#include <ese/ese.h>
+#include "ese_operations_interface.h"
+
+// Wraps a supplied interface object with static calls.
+// This acts as a singleton. If mulitple instances are needed, the
+// multion pattern would be more appropriate.
+class EseOperationsWrapperData {
+ public:
+  static EseOperationsInterface *ops_interface;
+  static const struct EseOperations ops;
+  static const struct EseOperations *wrapper_ops;
+};
+
+class EseOperationsWrapper {
+ public:
+  // Naming convention to be compatible with ese_init();
+  EseOperationsWrapper() = default;
+  virtual ~EseOperationsWrapper() = default;
+  static void InitializeEse(struct EseInterface *ese, EseOperationsInterface *ops_interface);
+};
+
+#endif  // ESE_OPERATIONS_WRAPPER_H_
diff --git a/apps/include/ese/app/result.h b/apps/include/ese/app/result.h
new file mode 100644
index 0000000..0c0ae1a
--- /dev/null
+++ b/apps/include/ese/app/result.h
@@ -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.
+ */
+
+#ifndef ESE_APP_RESULT_H_
+#define ESE_APP_RESULT_H_ 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// EseAppResult is structured with the low order bits being
+// the enum below and the high order bits being a short
+// from the applet when the code is ERROR_OS or ERROR_APPLET.
+typedef enum {
+  ESE_APP_RESULT_FALSE,
+  ESE_APP_RESULT_OK = ESE_APP_RESULT_FALSE,
+  ESE_APP_RESULT_TRUE = 1,
+  ESE_APP_RESULT_ERROR_ARGUMENTS,
+  ESE_APP_RESULT_ERROR_COMM_FAILED,
+  ESE_APP_RESULT_ERROR_OS,
+  ESE_APP_RESULT_ERROR_APPLET,
+  ESE_APP_RESULT_ERROR_UNCONFIGURED,
+  ESE_APP_RESULT_ERROR_COOLDOWN,
+} EseAppResult;
+
+#define EseAppResultValue(_res) (res & 0xffff)
+#define EseAppResultAppValue(_res) (res >> 16)
+#define ese_make_os_result(_app_hi, _app_lo) \
+  ((((_app_hi) << 8 | (_app_lo)) << 16) | (ESE_APP_RESULT_ERROR_OS))
+#define ese_make_app_result(_app_hi, _app_lo) \
+  ((((_app_hi) << 8 | (_app_lo)) << 16) | (ESE_APP_RESULT_ERROR_APPLET))
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* ESE_APP_RESULT_H_ */
diff --git a/libese-hw/nxp/pn80t/common.c b/libese-hw/nxp/pn80t/common.c
index eb33182..12aa223 100644
--- a/libese-hw/nxp/pn80t/common.c
+++ b/libese-hw/nxp/pn80t/common.c
@@ -147,25 +147,24 @@
   return -1;
 }
 
-uint32_t nxp_pn80t_transceive(struct EseInterface *ese,
-                              const struct EseSgBuffer *tx_buf, uint32_t tx_len,
-                              struct EseSgBuffer *rx_buf, uint32_t rx_len) {
-  return teq1_transceive(ese, &kTeq1Options, tx_buf, tx_len, rx_buf, rx_len);
-}
-
 /* Returns the minutes needed to decrement the attack counter. */
-uint32_t nxp_pn80t_send_cooldown(struct EseInterface *ese) {
+uint32_t nxp_pn80t_send_cooldown(struct EseInterface *ese, bool end) {
   const struct Pn80tPlatform *platform = ese->ops->opts;
   const static uint8_t kEndofApduSession[] = {0x5a, 0xc5, 0x00, 0xc5};
+  const static uint8_t kResetSession[] = {0x5a, 0xc4, 0x00, 0xc4};
   uint8_t rx_buf[32];
   uint32_t bytes_read = 0;
   uint32_t *secure_timer = NULL;
   uint32_t *attack_counter_decrement = NULL;
   uint32_t *restricted_mode_penalty = NULL;
+  const uint8_t *message = kResetSession;
+  if (end) {
+    message = kEndofApduSession;
+  }
   ese->ops->hw_transmit(ese, kEndofApduSession, sizeof(kEndofApduSession), 1);
   nxp_pn80t_poll(ese, kTeq1Options.host_address, 5.0f, 0);
   bytes_read = ese->ops->hw_receive(ese, rx_buf, sizeof(rx_buf), 1);
-  ALOGI("End of APDU Session:");
+  ALOGI("Cooldown data:");
   if (bytes_read >= 0x8 && rx_buf[0] == 0xe5 && rx_buf[1] == 0x12) {
     uint8_t *tag = &rx_buf[2];
     while (tag < (rx_buf + bytes_read)) {
@@ -208,13 +207,66 @@
   return 0;
 }
 
+uint32_t nxp_pn80t_handle_interface_call(struct EseInterface *ese,
+                                         const struct EseSgBuffer *tx_buf,
+                                         uint32_t tx_len,
+                                         struct EseSgBuffer *rx_buf,
+                                         uint32_t rx_len) {
+  /* Catch proprietary, host-targeted calls FF FF 00 XX */
+  static const uint32_t kCommandLength = 4;
+  static const uint8_t kResetCommand = 0x01;
+  static const uint8_t kGpioToggleCommand = 0xe0;
+  static const uint8_t kCooldownCommand = 0xe1;
+  uint8_t buf[kCommandLength + 1];
+  uint8_t ok[2] = {0x90, 0x00};
+  /* Over-copy by one to make sure the command length matches. */
+  if (ese_sg_to_buf(tx_buf, tx_len, 0, sizeof(buf), buf) != kCommandLength) {
+    return 0;
+  }
+  if (buf[0] != 0xff || buf[1] != 0xff || buf[2] != 0x00) {
+    return 0;
+  }
+  switch (buf[3]) {
+  case kResetCommand:
+    ALOGI("interface command received: reset");
+    if (nxp_pn80t_reset(ese) < 0) {
+      /* Warning, state unchanged error. */
+      ok[0] = 0x62;
+    }
+    return ese_sg_from_buf(rx_buf, rx_len, 0, sizeof(ok), ok);
+  case kGpioToggleCommand:
+    ALOGI("interface command received: gpio toggle");
+    /* TODO - need kernel support first. */
+    return 0;
+  case kCooldownCommand:
+    ALOGI("interface command received: cooldown");
+    uint8_t reply[6] = {0, 0, 0, 0, 0x90, 0x00};
+    uint32_t cooldownMin = nxp_pn80t_send_cooldown(ese, false);
+    *(uint32_t *)(&reply[0]) = cooldownMin;
+    return ese_sg_from_buf(rx_buf, rx_len, 0, sizeof(reply), reply);
+  }
+  return 0;
+}
+
+uint32_t nxp_pn80t_transceive(struct EseInterface *ese,
+                              const struct EseSgBuffer *tx_buf, uint32_t tx_len,
+                              struct EseSgBuffer *rx_buf, uint32_t rx_len) {
+
+  uint32_t recvd =
+      nxp_pn80t_handle_interface_call(ese, tx_buf, tx_len, rx_buf, rx_len);
+  if (recvd > 0) {
+    return recvd;
+  }
+  return teq1_transceive(ese, &kTeq1Options, tx_buf, tx_len, rx_buf, rx_len);
+}
+
 void nxp_pn80t_close(struct EseInterface *ese) {
   struct NxpState *ns;
   const struct Pn80tPlatform *platform = ese->ops->opts;
   uint32_t wait_min = 0;
   ALOGV("%s: called", __func__);
   ns = NXP_PN80T_STATE(ese);
-  wait_min = nxp_pn80t_send_cooldown(ese);
+  wait_min = nxp_pn80t_send_cooldown(ese, true);
   /* TODO(wad): Move to the non-common code.
    * E.g., platform->wait(ns->handle, 60000000 * wait_min);
    * would not be practical in many cases.
diff --git a/libese/include/ese/ese_hw_api.h b/libese/include/ese/ese_hw_api.h
index 3f1abb8..92fd671 100644
--- a/libese/include/ese/ese_hw_api.h
+++ b/libese/include/ese/ese_hw_api.h
@@ -127,43 +127,51 @@
 #define __ESE_INITIALIZER(TYPE) \
 { \
   .ops = TYPE## _ops, \
+  .error = { \
+    .is_err = false, \
+    .code = 0, \
+    .message = NULL, \
+  }, \
   .pad =  { 0 }, \
 }
 
 #define __ese_init(_ptr, TYPE) {\
-  _ptr->ops = TYPE## _ops; \
-  _ptr->pad[0] = 0; \
+  (_ptr)->ops = TYPE## _ops; \
+  (_ptr)->pad[0] = 0; \
+  (_ptr)->error.is_err = false; \
+  (_ptr)->error.code = 0; \
+  (_ptr)->error.message = (const char *)NULL; \
 }
 
 struct EseOperations {
-  const char *const name;
+  const char *name;
   /* Used to prepare any implementation specific internal data and
    * state needed for robust communication.
    */
-  ese_open_op_t *const open;
+  ese_open_op_t *open;
   /* Used to receive raw data from the ese. */
-  ese_hw_receive_op_t *const hw_receive;
+  ese_hw_receive_op_t *hw_receive;
   /* Used to transmit raw data to the ese. */
-  ese_hw_transmit_op_t *const hw_transmit;
+  ese_hw_transmit_op_t *hw_transmit;
   /* Used to perform a power reset on the device. */
-  ese_hw_reset_op_t *const hw_reset;
+  ese_hw_reset_op_t *hw_reset;
   /* Wire-specific protocol polling for readiness. */
-  ese_poll_op_t *const poll;
+  ese_poll_op_t *poll;
   /* Wire-specific protocol for transmitting and receiving
    * application data to the eSE. By default, this may point to
    * a generic implementation, like teq1_transceive, which uses
    * the hw_* ops above.
    */
-  ese_transceive_op_t *const transceive;
+  ese_transceive_op_t *transceive;
   /* Cleans up any required state: file descriptors or heap allocations. */
-  ese_close_op_t *const close;
+  ese_close_op_t *close;
 
   /* Operational options */
-  const void *const opts;
+  const void *opts;
 
   /* Operation error messages. */
   const char **errors;
-  const uint32_t errors_count;
+  uint32_t errors_count;
 };
 
 /* Maximum private stack storage on the interface instance. */