Merge "apps/boot: Clean up applet"
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>