apps/boot: Clean up applet

The applet had a few todos and a few gaps:
- setWithMetadata was not enforcing requiredLocks.
- extended apdu support for incoming data was added
- a metadata staging interface added to work around
  communication constraints on different devices.
- setting or unsetting a lock with 0 metadata and
  useMetadata, will clear the metadata contents.
- adds inBootloaderRaw to state output for diagnostics.

The libese interface did not change, but the
implementation reflects the new behavior.

The applet version is updated appropriately.

(This change increases the flash footprint by 2k, but
attempts to avoid actually writing to it.)

Test: submit a 4k key and it writes and reads.
      all ese-boot-tool functions pass.
      LOCK_OWNER behavior is enforced even when using metadata.
Bug: 38150381
Change-Id: I0759db7388f8f42a7828b699b7b05b6046ec5a53
diff --git a/apps/boot/boot.c b/apps/boot/boot.c
index 6911d1d..a8ac08b 100644
--- a/apps/boot/boot.c
+++ b/apps/boot/boot.c
@@ -38,6 +38,9 @@
 const uint8_t kCarrierLockTest[] = {0x80, 0x0c, 0x00, 0x00};
 const uint8_t kFactoryReset[] = {0x80, 0x0e, 0x00, 0x00};
 const uint8_t kLockReset[] = {0x80, 0x0e, 0x01, 0x00};
+const uint8_t kLoadMetaClear[] = {0x80, 0x10, 0x00, 0x00};
+const uint8_t kLoadMetaAppend[] = {0x80, 0x10, 0x01, 0x00};
+static const uint16_t kMaxMetadataLoadSize = 1024;
 
 EseAppResult check_apdu_status(uint8_t code[2]) {
   if (code[0] == 0x90 && code[1] == 0x00) {
@@ -206,7 +209,7 @@
 
   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);
+    ALOGE("ese_boot_lock_xget: failed to read lock state (%d)", lock);
     return ESE_APP_RESULT_ERROR_COMM_FAILED;
   }
   if (rx_len == 2) {
@@ -217,7 +220,8 @@
   // 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]);
+    ALOGE("ese_boot_lock_xget: received applet error code %x %x", lockData[0],
+          lockData[1]);
     return ese_make_app_result(lockData[0], lockData[1]);
   }
   return ESE_APP_RESULT_OK;
@@ -250,21 +254,21 @@
 
   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);
+    ALOGE("ese_boot_lock_get: 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.");
+    ALOGE("ese_boot_lock_get: SE OS error.");
     return ret;
   }
   if (rx_len < 5) {
-    ALOGE("Get lock state did not receive enough data.");
+    ALOGE("ese_boot_lock_get: communication error");
     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]);
+    ALOGE("ese_boot_lock_get: Applet error: %x %x", reply[0], reply[1]);
     return ese_make_app_result(reply[0], reply[1]);
   }
   if (lockVal) {
@@ -278,11 +282,96 @@
   return ESE_APP_RESULT_FALSE;
 }
 
+EseAppResult ese_boot_meta_clear(struct EseBootSession *session) {
+  struct EseSgBuffer tx[2];
+  struct EseSgBuffer rx[1];
+  int rx_len;
+  if (!session || !session->ese || !session->active) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+
+  uint8_t chan = kLoadMetaClear[0] | session->channel_id;
+  tx[0].base = &chan;
+  tx[0].len = 1;
+  tx[1].base = (uint8_t *)&kLoadMetaClear[1];
+  tx[1].len = sizeof(kLoadMetaClear) - 1;
+
+  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, 2, rx, 1);
+  if (rx_len < 2 || ese_error(session->ese)) {
+    ALOGE("ese_boot_meta_clear: communication failure");
+    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_meta_clear: SE exception");
+    EseAppResult ret = check_apdu_status(&reply[rx_len - 2]);
+    return ret;
+  }
+  if (reply[0] != 0x0 || reply[1] != 0x0) {
+    ALOGE("ese_boot_meta_clear: received applet error code %.2x %.2x", reply[0],
+          reply[1]);
+    return ese_make_app_result(reply[0], reply[1]);
+  }
+  return ESE_APP_RESULT_OK;
+}
+
+EseAppResult ese_boot_meta_append(struct EseBootSession *session,
+                                  const uint8_t *data, uint16_t dataLen) {
+  struct EseSgBuffer tx[4];
+  struct EseSgBuffer rx[1];
+  int rx_len;
+  if (!session || !session->ese || !session->active) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (dataLen > kMaxMetadataLoadSize) {
+    ALOGE("ese_boot_meta_append: too much data provided");
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+
+  uint8_t chan = kLoadMetaAppend[0] | session->channel_id;
+  tx[0].base = &chan;
+  tx[0].len = 1;
+  tx[1].base = (uint8_t *)&kLoadMetaAppend[1];
+  tx[1].len = sizeof(kLoadMetaAppend) - 1;
+
+  uint8_t apdu_len[] = {0x0, (dataLen >> 8), (dataLen & 0xff)};
+  tx[2].base = &apdu_len[0];
+  tx[2].len = sizeof(apdu_len);
+  tx[3].c_base = data;
+  tx[3].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, 4, rx, 1);
+  if (rx_len < 2 || ese_error(session->ese)) {
+    ALOGE("ese_boot_meta_append: communication failure");
+    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_meta_append: SE exception");
+    EseAppResult ret = check_apdu_status(&reply[rx_len - 2]);
+    return ret;
+  }
+  if (reply[0] != 0x0 || reply[1] != 0x0) {
+    ALOGE("ese_boot_meta_append: received applet error code %.2x %.2x",
+          reply[0], reply[1]);
+    return ese_make_app_result(reply[0], reply[1]);
+  }
+  return ESE_APP_RESULT_OK;
+}
+
 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 tx[3];
   struct EseSgBuffer rx[1];
   int rx_len;
   if (!session || !session->ese || !session->active) {
@@ -292,36 +381,50 @@
     return ESE_APP_RESULT_ERROR_ARGUMENTS;
   }
   if (dataLen < 1 || dataLen > kEseBootOwnerKeyMax + 1) {
-    ALOGE("set_lock_with_meta: too much data: %hu > %d", dataLen,
+    ALOGE("ese_boot_lock_xset: too much data: %hu > %d", dataLen,
           kEseBootOwnerKeyMax + 1);
     return ESE_APP_RESULT_ERROR_ARGUMENTS;
   }
 
+  // Locks with metadata require a multi-step upload to meet the
+  // constraints of the transport.
+  EseAppResult res = ese_boot_meta_clear(session);
+  if (res != ESE_APP_RESULT_OK) {
+    ALOGE("ese_boot_lock_xset: unable to clear scratch metadata");
+    return res;
+  }
+  // The first byte is the lock value itself, so we skip it.
+  const uint8_t *cursor = &lockData[1];
+  uint16_t remaining = dataLen - 1;
+  while (remaining > 0) {
+    uint16_t chunk = (512 < remaining) ? 512 : remaining;
+    res = ese_boot_meta_append(session, cursor, chunk);
+    ALOGI("ese_boot_lock_xset: sending chunk %x", remaining);
+    if (res != ESE_APP_RESULT_OK) {
+      ALOGE("ese_boot_lock_xset: unable to upload metadata");
+      return res;
+    }
+    remaining -= chunk;
+    cursor += chunk;
+  }
+
   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 lockIdLockValueUseMeta[] = {lockId, lockData[0], 0x1, 0x1};
+  tx[2].base = &lockIdLockValueUseMeta[0];
+  tx[2].len = sizeof(lockIdLockValueUseMeta);
 
   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);
+  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);
+    ALOGE("ese_boot_lock_xset: failed to set lock state (%d).", lockId);
     return ESE_APP_RESULT_ERROR_COMM_FAILED;
   }
   if (rx_len == 2) {
@@ -329,13 +432,14 @@
     EseAppResult ret = check_apdu_status(&reply[0]);
     return ret;
   }
-  // Expect the full payload plus the aplet status and the completion code.
+  // Expect the full payload plus the applet status and the completion code.
   if (rx_len != 4) {
-    ALOGE("Get lock state did not receive enough data: %d", rx_len);
+    ALOGE("ese_boot_lock_xset: communication error");
     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]);
+    ALOGE("ese_boot_lock_xset: received applet error code %x %x", reply[0],
+          reply[1]);
     return ese_make_app_result(reply[0], reply[1]);
   }
   return ESE_APP_RESULT_OK;
@@ -360,9 +464,9 @@
   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 lockIdLockValueNoMeta[] = {lockId, lockValue, 0x1, 0x0};
+  tx[2].base = &lockIdLockValueNoMeta[0];
+  tx[2].len = sizeof(lockIdLockValueNoMeta);
 
   uint8_t reply[4]; // App reply or APDU error.
   rx[0].base = &reply[0];
@@ -375,7 +479,7 @@
   }
   // Expect the full payload plus the applet status and the completion code.
   if (rx_len < 4) {
-    ALOGE("ese_boot_lock_xset: SE exception");
+    ALOGE("ese_boot_lock_set: SE exception");
     EseAppResult ret = check_apdu_status(&reply[rx_len - 2]);
     return ret;
   }
diff --git a/apps/boot/card/src/com/android/verifiedboot/storage/BasicLock.java b/apps/boot/card/src/com/android/verifiedboot/storage/BasicLock.java
index 5cde61b..b3a6aad 100644
--- a/apps/boot/card/src/com/android/verifiedboot/storage/BasicLock.java
+++ b/apps/boot/card/src/com/android/verifiedboot/storage/BasicLock.java
@@ -232,6 +232,25 @@
     }
 
     /**
+     * Ensures any requiredLocks are unlocked.
+     * @return true if allowed or false if not.
+     */
+    public boolean prerequisitesMet() {
+        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 false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
      * {@inheritDoc}
      *
      * Returns 0x0 on success.
@@ -249,6 +268,10 @@
                 return 0x0002;
             }
         }
+        // To relock, the lock must be unlocked, then relocked.
+        if (val != (byte)0 && storage[lockOffset()] != (byte)0) {
+            return 0x0005;
+        }
         if (globalState.production() == true) {
              // Enforce only when in production.
             if (onlyInBootloader == true) {
@@ -264,16 +287,8 @@
                 }
             }
         }
-        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;
-                }
-            }
+        if (prerequisitesMet() == false) {
+          return 0x0a00;
         }
         try {
             storage[storageOffset] = val;
@@ -298,20 +313,46 @@
         }
         // No overruns, please.
         if (lockMetaLength > metadataLength()) {
-          return 0x0002;
+            return 0x0002;
+        }
+        // To relock, the lock must be unlocked, then relocked.
+        // This ensures that a lock like LOCK_OWNER cannot have its key value
+        // changed without first having the permission to unlock and lock again.
+        if (lockValue != (byte)0 && storage[lockOffset()] != (byte)0) {
+            return 0x0005;
         }
         if (metadataLength() == 0) {
-          return set(lockValue);
+            return set(lockValue);
+        }
+        // Before copying, ensure changing the lock state is currently permitted.
+        if (prerequisitesMet() == false) {
+          return 0x0a00;
         }
         try {
-            JCSystem.beginTransaction();
-            storage[lockOffset()] = lockValue;
-            Util.arrayCopy(lockMeta, lockMetaOffset,
-                           storage, metadataOffset(),
-                           lockMetaLength);
-            JCSystem.commitTransaction();
+            // When unlocking, do so before clearing the metadata.
+            if (lockValue == (byte) 0) {
+                JCSystem.beginTransaction();
+                storage[lockOffset()] = lockValue;
+                JCSystem.commitTransaction();
+            }
+            if (lockMetaLength == 0) {
+                // An empty lockMeta will clear the value.
+                Util.arrayFillNonAtomic(storage, metadataOffset(),
+                                        metadataLength(), (byte) 0x00);
+            } else {
+                Util.arrayCopyNonAtomic(lockMeta, lockMetaOffset,
+                                        storage, metadataOffset(),
+                                        lockMetaLength);
+            }
+            // When locking, do so after the copy as interrupting it will
+            // not impact its use in a locked state.
+            if (lockValue != (byte) 0) {
+                JCSystem.beginTransaction();
+                storage[lockOffset()] = lockValue;
+                JCSystem.commitTransaction();
+            }
         } catch (CardRuntimeException e) {
-            return 0x0003;
+            return 0x0004;
         }
         return 0;
     }
diff --git a/apps/boot/card/src/com/android/verifiedboot/storage/GlobalStateImpl.java b/apps/boot/card/src/com/android/verifiedboot/storage/GlobalStateImpl.java
index 5ac00aa..dd3df38 100644
--- a/apps/boot/card/src/com/android/verifiedboot/storage/GlobalStateImpl.java
+++ b/apps/boot/card/src/com/android/verifiedboot/storage/GlobalStateImpl.java
@@ -34,6 +34,10 @@
     // 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;
+    // Scenario values
+    final static byte BOOTLOADER_UNDEFINED = (byte) 0xff;
+    final static byte BOOTLOADER_HIGH = (byte) 0x5a;
+    final static byte BOOTLOADER_LOW = (byte) 0xa5;
 
     private Object[] clientApplets;
     private byte[] clientAppletState;
@@ -224,11 +228,15 @@
     @Override
     public boolean inBootloader() {
         try {
-            if (SystemInfo.getExternalState(SystemInfo.SYSTEMINFO_SCENARIO_0)
-                == (byte) 0x00) {
+            switch (SystemInfo.getExternalState(
+                    SystemInfo.SYSTEMINFO_SCENARIO_0)) {
+            case BOOTLOADER_HIGH:
+                return true;
+            case BOOTLOADER_LOW:
+            case BOOTLOADER_UNDEFINED:
+            default:
                 return false;
             }
-            return true;
         } catch (ISOException e) {
             // If we can't read it, we fail closed unless we're in debug mode.
             return false;
@@ -236,6 +244,17 @@
     }
 
     /**
+     * Returns the external signal that feeds inBootloader().
+     */
+    public byte inBootloaderRaw() {
+        try {
+            return SystemInfo.getExternalState(SystemInfo.SYSTEMINFO_SCENARIO_0);
+        } catch (ISOException e) {
+            return (byte) 0x0;
+        }
+    }
+
+    /**
      * {@inheritDoc}
      *
      * @param val value assigned to the inProduction value.
diff --git a/apps/boot/card/src/com/android/verifiedboot/storage/Storage.java b/apps/boot/card/src/com/android/verifiedboot/storage/Storage.java
index 9c2bd46..a18c88b 100644
--- a/apps/boot/card/src/com/android/verifiedboot/storage/Storage.java
+++ b/apps/boot/card/src/com/android/verifiedboot/storage/Storage.java
@@ -66,6 +66,10 @@
      */
     private byte[] lockStorage;
     private LockInterface[] locks;
+    private byte[] reservedMetadata;
+    private byte[] metadata;
+    private short metadataLength;
+
     // Indices into locks[].
     private final static byte LOCK_CARRIER = (byte) 0x00;
     private final static byte LOCK_DEVICE = (byte) 0x01;
@@ -80,14 +84,19 @@
     private final static byte INS_SET_PRODUCTION = (byte) 0x0a;
     private final static byte INS_CARRIER_LOCK_TEST = (byte) 0x0c;
     private final static byte INS_RESET = (byte) 0x0e;
+    private final static byte INS_LOAD_META = (byte) 0x10;
 
     private final static byte RESET_FACTORY = (byte) 0x0;
     private final static byte RESET_LOCKS = (byte) 0x1;
 
+    private final static byte LOAD_META_CLEAR = (byte) 0x0;
+    private final static byte LOAD_META_APPEND = (byte) 0x1;
+
     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;
+    private final static short INCOMING_BYTES_MAX = (short) 1024;
 
     /**
      * Installs this applet.
@@ -123,6 +132,10 @@
                            versionStorage);
 
         lockStorage = new byte[4096];
+        // Reserve metadata for scratch if there's no transient
+        reservedMetadata = new byte[OWNER_LOCK_METADATA_SIZE];
+        metadata = null;
+
         // Initialize all supported locks here.
         locks = new LockInterface[4];
         // LOCK_CARRIER can be set only when not in production mode
@@ -198,7 +211,8 @@
      * VERSION (byte)
      * Length (short)
      * [global_state.OwnerInterface data]
-     *   inBootloader (byte)
+     *   inBootloaderRaw (byte)
+     *   inBootloader (boolean byte)
      *   production (byte)
      * [lock state]
      *   numLocks (byte)
@@ -220,7 +234,7 @@
         short resp = 0;
         byte i;
         short expectedLength = apdu.setOutgoing();
-        short length = (short)(2 + 1 + 2 + 1 + 1 + 1 + (2 * locks.length) +
+        short length = (short)(2 + 1 + 2 + 1 + 1 + 1 + 1 + (2 * locks.length) +
                                2 + lockStorage.length + 2);
         if (expectedLength < length) {
             // Error with length.
@@ -264,6 +278,14 @@
         }
 
         try {
+            working[0] = globalState.inBootloaderRaw();
+            apdu.sendBytesLong(working, (short) 0, (short) 1);
+            length--;
+        } catch (CardRuntimeException e) {
+            ISOException.throwIt(length);
+        }
+
+        try {
             working[0] = (byte)0;
             if (globalState.inBootloader() == true) {
                 working[0] = (byte)1;
@@ -371,6 +393,50 @@
     }
 
     /**
+     * Fills the incoming buffer from APDU streams.
+     *
+     * @param apdu payload from the client.
+     * @param incoming buffer to fill from apdu buffer.
+     * @param iOffset starting offset into incoming.
+     * @param total total bytes to read from APDU.
+     * @return offset/length into incoming.
+     */
+    private short fillIncomingBuffer(APDU apdu, byte[] incoming, short iOffset, short available) {
+        final byte buffer[] = apdu.getBuffer();
+        final short cdataOffset = apdu.getOffsetCdata();
+        short sum = 0;
+        while (available > 0) {
+            Util.arrayCopyNonAtomic(buffer, cdataOffset,
+                           incoming, (short)(sum + iOffset), available);
+            sum += available;
+            try {
+              available = apdu.receiveBytes(cdataOffset);
+              if (sum > (short)(incoming.length - available)) {
+                available = (short)(incoming.length - sum);
+              }
+            } catch (CardRuntimeException e) {
+              available = 0;
+            }
+        }
+        return (short)(sum + iOffset);
+    }
+
+    public boolean select() {
+        metadataLength = (short) 0;
+        // Try to get the RAM needed.
+        if (metadata == null ||
+            metadata == reservedMetadata) {
+            try {
+                metadata = JCSystem.makeTransientByteArray(
+                        OWNER_LOCK_METADATA_SIZE,
+                        JCSystem.CLEAR_ON_DESELECT);
+            } catch (CardRuntimeException e) {
+                metadata = reservedMetadata;
+            }
+        }
+        return true;
+    }
+    /**
      * Handles incoming APDU requests
      *
      * @param apdu payload from the client.
@@ -391,10 +457,17 @@
             ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
         }
 
-        short bytesRead = apdu.setIncomingAndReceive();
+        short availableBytes = apdu.setIncomingAndReceive();
         short numBytes = apdu.getIncomingLength();
         short cdataOffset = apdu.getOffsetCdata();
 
+        // Maximum possible transmission before triggering weirdness
+        // (really it is 6 * 254 - framing).
+        if (numBytes > INCOMING_BYTES_MAX) {
+          sendResponseCode(apdu, (short)0x0f00);
+          return;
+        }
+
         byte p1 = (byte)(buffer[ISO7816.OFFSET_P1] & (byte)0xff);
         byte p2 = (byte)(buffer[ISO7816.OFFSET_P2] & (byte)0xff);
         short length = 0;
@@ -435,22 +508,24 @@
                 sendResponseCode(apdu, resp);
             }
             return;
-        case INS_SET_LOCK: /* setlock(index, val) { data } */
+        case INS_SET_LOCK: /* setlock(index, val) { useMetadata(byte) } */
             if (p1 >= (byte)locks.length) {
                 sendResponseCode(apdu, (short)0x0100);
+                return;
             }
-
-            if (bytesRead == (short) 0) {
+            // useMetadata argument byte is required.
+            if (numBytes != 1) {
+                sendResponseCode(apdu, (short)0x0200);
+                return;
+            }
+            if (buffer[cdataOffset] == (byte) 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);
-                }
+            } else if (buffer[cdataOffset] == (byte) 1) {
+                resp = locks[p1].setWithMetadata(p2, metadata,
+                                                 (short) 0,
+                                                 metadataLength);
+                // "Consume" the metadata even if an error occurred.
+                metadataLength = (short)0;
             }
             sendResponseCode(apdu, resp);
             return;
@@ -464,28 +539,68 @@
             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;
+            try {
+                short copied = fillIncomingBuffer(apdu, metadata, (short)0,
+                                                  availableBytes);
+                if (numBytes != copied) {
+                    // Declared length did not match read bytes.
+                    sendResponseCode(apdu, (short)0x0101);
+                    return;
+                }
+                resp = ((CarrierLock)locks[LOCK_CARRIER]).testVector(metadata,
+                    (short)0, copied);
+                sendResponseCode(apdu, resp);
+                return;
+            } catch (CardRuntimeException e) {
+                sendResponseCode(apdu, (short)0x0201);
+                return;
             }
-            resp = ((CarrierLock)locks[LOCK_CARRIER]).testVector(buffer, cdataOffset, bytesRead);
-            sendResponseCode(apdu, resp);
-            return;
         /* reset(0x0=factory 0x1=locks) {} */
         case INS_RESET:
             if (p1 != RESET_LOCKS) {
               /* Not implemented */
               resp = 0x0001;
               sendResponseCode(apdu, resp);
+              return;
             }
             if (globalState.production() == true) {
               resp = 0x0100;
               sendResponseCode(apdu, resp);
+              return;
             }
             Util.arrayFillNonAtomic(lockStorage, (short) 0,
                                     (short) lockStorage.length, (byte) 0x00);
             return;
+        /* load_meta(new|append) {} */
+        case INS_LOAD_META:
+            if (p1 == LOAD_META_CLEAR) {
+                metadataLength = (short) 0;
+                sendResponseCode(apdu, (short) 0x0000);
+                return;
+            }
+            if (p1 != LOAD_META_APPEND) {
+                sendResponseCode(apdu, (short) 0x0100);
+                return;
+            }
+            try  {
+                // fillIncomingBuffer will only copy up to the length.
+                short copied = fillIncomingBuffer(apdu, metadata,
+                                                  metadataLength,
+                                                  availableBytes);
+                copied -= metadataLength; // just the new stuff
+                metadataLength += copied;
+                if (numBytes != copied) {
+                    // Could not read all the bytes -- the data is
+                    // copied though.
+                    sendResponseCode(apdu, (short)0x0101);
+                    return;
+                }
+            } catch (CardRuntimeException e) {
+                sendResponseCode(apdu, (short)0x0201);
+                return;
+            }
+            sendResponseCode(apdu, (short) 0x0000);
+            return;
         default:
             ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
         }
diff --git a/apps/boot/ese_boot_tool.cpp b/apps/boot/ese_boot_tool.cpp
index cffd8ee..98d2ce3 100644
--- a/apps/boot/ese_boot_tool.cpp
+++ b/apps/boot/ese_boot_tool.cpp
@@ -269,7 +269,7 @@
   EseAppResult res;
   EseBootLockId lockId;
   uint16_t lockMetaLen = 0;
-  uint8_t lockMeta[1024];
+  uint8_t lockMeta[kEseBootOwnerKeyMax + 1];
   if (args[1] == "reset") {
     // No work.
   } else if (args[2] == "carrier") {
@@ -450,6 +450,7 @@
   if (res != ESE_APP_RESULT_OK) {
     fprintf(stderr, "failed to initiate session (%.8x)\n", res);
     handle_error(ese, res);
+    ese_close(&ese);
     return 1;
   }
   std::vector<std::string> args;
diff --git a/apps/boot/include/ese/app/boot.h b/apps/boot/include/ese/app/boot.h
index c0cbff8..09659a3 100644
--- a/apps/boot/include/ese/app/boot.h
+++ b/apps/boot/include/ese/app/boot.h
@@ -64,7 +64,6 @@
  */
 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
diff --git a/apps/build.xml b/apps/build.xml
index 6eb0a75..77626cc 100644
--- a/apps/build.xml
+++ b/apps/build.xml
@@ -144,7 +144,7 @@
       <!-- Base version (Version + .1) for displacing a preinstalled package on early cards. -->
       <cap aid="A0000004765049584C424F4F540000"
            package="com.android.verifiedboot.storage"
-           version="2.1"
+           version="3.1"
            output="${out}/avb_storage_clobber.cap"
            sources="boot/card/src/com/android/verifiedboot"
            export="${build}/export/avb_storage">
@@ -164,9 +164,9 @@
         <import exps="${build}/export/avb_storage"/>
       </cap>
       <!-- 14th byte is the version. Increment on each release. -->
-      <cap aid="A0000004765049584C424F4F540200"
+      <cap aid="A0000004765049584C424F4F540300"
            package="com.android.verifiedboot.storage"
-           version="2.0"
+           version="3.0"
            output="${out}/avb_storage.cap"
            sources="boot/card/src/com/android/verifiedboot"
            export="${build}/export/avb_storage">
@@ -182,7 +182,7 @@
              16th byte is 01 for the applet.
           -->
         <applet class="com.android.verifiedboot.storage.Storage"
-                aid="A0000004765049584C424F4F54020101"/>
+                aid="A0000004765049584C424F4F54030101"/>
         <import exps="${build}/export/avb_storage"/>
       </cap>
     </javacard>