Merge "Rename vbmeta_mainline to vbmeta_system."
diff --git a/Android.bp b/Android.bp
index 3f43a90..7245161 100644
--- a/Android.bp
+++ b/Android.bp
@@ -74,12 +74,27 @@
     ],
 }
 
-cc_prebuilt_binary {
+python_binary_host {
     name: "avbtool",
-    srcs: ["avbtool"],
+    srcs: [":avbtool_py"],
+    main: "avbtool.py",
     required: ["fec"],
-    device_supported: false,
-    host_supported: true,
+    version: {
+        py2: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+        py3: {
+            enabled: false,
+        },
+    },
+}
+
+genrule {
+  name: "avbtool_py",
+  srcs: ["avbtool",],
+  out: ["avbtool.py"],
+  cmd: "cp $(in) $(out)",
 }
 
 // Build libavb - this is a static library that depends
@@ -94,7 +109,7 @@
     recovery_available: true,
     export_include_dirs: ["."],
     target: {
-        android: {
+        linux: {
             srcs: ["libavb/avb_sysdeps_posix.c"],
         },
         linux_glibc: {
@@ -184,9 +199,11 @@
     ],
     data: [
         "avbtool",
+        "test/avbtool_signing_helper_*.py",
         "test/data/*",
     ],
     test_config: "test/libavb_host_unittest.xml",
+    test_suites: ["general-tests"],
     static_libs: [
         "libavb",
         "libavb_ab_host",
@@ -196,6 +213,7 @@
         "libgtest_host",
     ],
     shared_libs: [
+        "libbase",
         "libchrome",
         "libcrypto",
     ],
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..a3ede88
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+  "postsubmit": [
+    {
+      "name": "libavb_host_unittest",
+      "host": true
+    }
+  ]
+}
diff --git a/avbtool b/avbtool
index b027bbd..610cf19 100755
--- a/avbtool
+++ b/avbtool
@@ -544,13 +544,52 @@
   modulus = decode_long(modulus_blob)
   exponent = 65537
 
-  # For now, just use Crypto.PublicKey.RSA to verify the signature. This
-  # is OK since 'avbtool verify_image' is not expected to run on the
-  # Android builders (see bug #36809096).
-  import Crypto.PublicKey.RSA
-  key = Crypto.PublicKey.RSA.construct((modulus, long(exponent)))
-  if not key.verify(decode_long(padding_and_digest),
-                    (decode_long(sig_blob), None)):
+  # We used to have this:
+  #
+  #  import Crypto.PublicKey.RSA
+  #  key = Crypto.PublicKey.RSA.construct((modulus, long(exponent)))
+  #  if not key.verify(decode_long(padding_and_digest),
+  #                    (decode_long(sig_blob), None)):
+  #    return False
+  #  return True
+  #
+  # but since 'avbtool verify_image' is used on the builders we don't want
+  # to rely on Crypto.PublicKey.RSA. Instead just use openssl(1) to verify.
+  asn1_str = ('asn1=SEQUENCE:pubkeyinfo\n'
+              '\n'
+              '[pubkeyinfo]\n'
+              'algorithm=SEQUENCE:rsa_alg\n'
+              'pubkey=BITWRAP,SEQUENCE:rsapubkey\n'
+              '\n'
+              '[rsa_alg]\n'
+              'algorithm=OID:rsaEncryption\n'
+              'parameter=NULL\n'
+              '\n'
+              '[rsapubkey]\n'
+              'n=INTEGER:%s\n'
+              'e=INTEGER:%s\n' % (hex(modulus).rstrip('L'), hex(exponent).rstrip('L')))
+  asn1_tmpfile = tempfile.NamedTemporaryFile()
+  asn1_tmpfile.write(asn1_str)
+  asn1_tmpfile.flush()
+  der_tmpfile = tempfile.NamedTemporaryFile()
+  p = subprocess.Popen(
+      ['openssl', 'asn1parse', '-genconf', asn1_tmpfile.name, '-out', der_tmpfile.name, '-noout'])
+  retcode = p.wait()
+  if retcode != 0:
+    raise AvbError('Error generating DER file')
+
+  p = subprocess.Popen(
+      ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', der_tmpfile.name, '-keyform', 'DER', '-raw'],
+      stdin=subprocess.PIPE,
+      stdout=subprocess.PIPE,
+      stderr=subprocess.PIPE)
+  (pout, perr) = p.communicate(str(sig_blob))
+  retcode = p.wait()
+  if retcode != 0:
+    raise AvbError('Error verifying data: {}'.format(perr))
+  recovered_data = bytearray(pout)
+  if recovered_data != padding_and_digest:
+    sys.stderr.write('Signature not correct\n')
     return False
   return True
 
@@ -1717,7 +1756,7 @@
     if not value:
       sys.stderr.write('No expected chain partition for partition {}. Use '
                        '--expected_chain_partition to specify expected '
-                       'contents.\n'.
+                       'contents or --follow_chain_partitions.\n'.
                        format(self.partition_name))
       return False
     rollback_index_location, pk_blob = value
@@ -2181,15 +2220,16 @@
     if num_printed == 0:
       o.write('    (none)\n')
 
-  def verify_image(self, image_filename, key_path, expected_chain_partitions):
+  def verify_image(self, image_filename, key_path, expected_chain_partitions, follow_chain_partitions):
     """Implements the 'verify_image' command.
 
     Arguments:
       image_filename: Image file to get information from (file object).
       key_path: None or check that embedded public key matches key at given path.
       expected_chain_partitions: List of chain partitions to check or None.
+      follow_chain_partitions: If True, will follows chain partitions even when not
+                               specified with the --expected_chain_partition option
     """
-
     expected_chain_partitions_map = {}
     if expected_chain_partitions:
       used_locations = {}
@@ -2245,26 +2285,22 @@
              .format(alg_name, image.filename))
 
     for desc in descriptors:
-      if not desc.verify(image_dir, image_ext, expected_chain_partitions_map, image):
-        raise AvbError('Error verifying descriptor.')
-      # Note how AvbDescriptor.verify() method verifies only the descriptor
-      # contents which in the case of chain descriptors means checking only its
-      # contents matches what is in |expected_chain_partitions_map|.
-      #
-      # Specifically AvbHashtreeDescriptor.verify(), doesn't follow chain
-      # descriptors e.g. if it's a chain descriptor for 'system' it will not try
-      # to verify system.img. Why?  Because the whole idea of chain descriptors
-      # is separate organizations. That is, when they are used it's assumed that
-      # all you have is the public key, not an actual image (because if you had
-      # the image you wouldn't need to use a chain partition in the first
-      # place).
-      #
-      # However in certain situations you do have the image so it would be nice
-      # to add something like a --follow_chain_descriptors option for 'avbtool
-      # verify_image' which will look for and follow images specified by chain
-      # descriptors. Maybe it should be on a per-partition basis and specificied
-      # as part of the --expected_chain_partition paramter, maybe if the
-      # partition name ends with a '+' or something. Something to think about.
+      if (isinstance(desc, AvbChainPartitionDescriptor) and follow_chain_partitions and
+          expected_chain_partitions_map.get(desc.partition_name) == None):
+        # In this case we're processing a chain descriptor but don't have a
+        # --expect_chain_partition ... however --follow_chain_partitions was
+        # specified so we shouldn't error out in desc.verify().
+        print ('{}: Chained but ROLLBACK_SLOT (which is {}) and KEY (which has sha1 {}) not specified'
+              .format(desc.partition_name, desc.rollback_index_location,
+                      hashlib.sha1(desc.public_key).hexdigest()))
+      else:
+        if not desc.verify(image_dir, image_ext, expected_chain_partitions_map, image):
+          raise AvbError('Error verifying descriptor.')
+      # Honor --follow_chain_partitions - add '--' to make the output more readable.
+      if isinstance(desc, AvbChainPartitionDescriptor) and follow_chain_partitions:
+        print '--'
+        chained_image_filename = os.path.join(image_dir, desc.partition_name + image_ext)
+        self.verify_image(chained_image_filename, key_path, None, False)
 
 
   def calculate_vbmeta_digest(self, image_filename, hash_algorithm, output):
@@ -4046,6 +4082,10 @@
                             help='Expected chain partition',
                             metavar='PART_NAME:ROLLBACK_SLOT:KEY_PATH',
                             action='append')
+    sub_parser.add_argument('--follow_chain_partitions',
+                            help=('Follows chain partitions even when not '
+                                  'specified with the --expected_chain_partition option'),
+                            action='store_true')
     sub_parser.set_defaults(func=self.verify_image)
 
     sub_parser = subparsers.add_parser(
@@ -4327,7 +4367,8 @@
   def verify_image(self, args):
     """Implements the 'verify_image' sub-command."""
     self.avb.verify_image(args.image.name, args.key,
-                          args.expected_chain_partition)
+                          args.expected_chain_partition,
+                          args.follow_chain_partitions)
 
   def calculate_vbmeta_digest(self, args):
     """Implements the 'calculate_vbmeta_digest' sub-command."""
diff --git a/libavb/avb_slot_verify.c b/libavb/avb_slot_verify.c
index 244a652..75b26d6 100644
--- a/libavb/avb_slot_verify.c
+++ b/libavb/avb_slot_verify.c
@@ -1284,6 +1284,35 @@
   return io_ret;
 }
 
+static bool has_system_partition(AvbOps* ops, const char* ab_suffix) {
+  char part_name[AVB_PART_NAME_MAX_SIZE];
+  char* system_part_name = "system";
+  char guid_buf[37];
+  AvbIOResult io_ret;
+
+  if (!avb_str_concat(part_name,
+                      sizeof part_name,
+                      system_part_name,
+                      avb_strlen(system_part_name),
+                      ab_suffix,
+                      avb_strlen(ab_suffix))) {
+    avb_error("System partition name and suffix does not fit.\n");
+    return false;
+  }
+
+  io_ret = ops->get_unique_guid_for_partition(
+      ops, part_name, guid_buf, sizeof guid_buf);
+  if (io_ret == AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION) {
+    avb_debug("No system partition.\n");
+    return false;
+  } else if (io_ret != AVB_IO_RESULT_OK) {
+    avb_error("Error getting unique GUID for system partition.\n");
+    return false;
+  }
+
+  return true;
+}
+
 AvbSlotVerifyResult avb_slot_verify(AvbOps* ops,
                                     const char* const* requested_partitions,
                                     const char* ab_suffix,
@@ -1407,8 +1436,16 @@
        * that the system partition is mounted.
        */
       avb_assert(slot_data->cmdline == NULL);
-      slot_data->cmdline =
-          avb_strdup("root=PARTUUID=$(ANDROID_SYSTEM_PARTUUID)");
+      // Devices with dynamic partitions won't have system partition.
+      // Instead, it has a large super partition to accommodate *.img files.
+      // See b/119551429 for details.
+      if (has_system_partition(ops, ab_suffix)) {
+        slot_data->cmdline =
+            avb_strdup("root=PARTUUID=$(ANDROID_SYSTEM_PARTUUID)");
+      } else {
+        // The |cmdline| field should be a NUL-terminated string.
+        slot_data->cmdline = avb_strdup("");
+      }
       if (slot_data->cmdline == NULL) {
         ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
         goto fail;
@@ -1446,7 +1483,7 @@
     }
 
     /* Substitute $(ANDROID_SYSTEM_PARTUUID) and friends. */
-    if (slot_data->cmdline != NULL) {
+    if (slot_data->cmdline != NULL && avb_strlen(slot_data->cmdline) != 0) {
       char* new_cmdline;
       new_cmdline = avb_sub_cmdline(ops,
                                     slot_data->cmdline,
diff --git a/libavb_user/avb_ops_user.cpp b/libavb_user/avb_ops_user.cpp
index 3e9956f..d7815f0 100644
--- a/libavb_user/avb_ops_user.cpp
+++ b/libavb_user/avb_ops_user.cpp
@@ -39,25 +39,21 @@
 
 #include <libavb_ab/libavb_ab.h>
 
+using android::fs_mgr::Fstab;
+using android::fs_mgr::GetEntryForMountPoint;
+using android::fs_mgr::ReadDefaultFstab;
+using android::fs_mgr::ReadFstabFromFile;
+
 /* Open the appropriate fstab file and fallback to /fstab.device if
  * that's what's being used.
  */
-static struct fstab* open_fstab(void) {
-  struct fstab* fstab = fs_mgr_read_fstab_default();
-
-  if (fstab != NULL) {
-    return fstab;
-  }
-
-  fstab = fs_mgr_read_fstab("/fstab.device");
-  return fstab;
+static bool open_fstab(Fstab* fstab) {
+  return ReadDefaultFstab(fstab) || ReadFstabFromFile("/fstab.device", fstab);
 }
 
 static int open_partition(const char* name, int flags) {
   char* path;
   int fd;
-  struct fstab* fstab;
-  struct fstab_rec* record;
 
   /* Per https://android-review.googlesource.com/c/platform/system/core/+/674989
    * Android now supports /dev/block/by-name/<partition_name> ... try that
@@ -93,31 +89,28 @@
    * misc and then finding an entry in /dev matching the sysfs entry.
    */
 
-  fstab = open_fstab();
-  if (fstab == NULL) {
+  Fstab fstab;
+  if (!open_fstab(&fstab)) {
     return -1;
   }
-  record = fs_mgr_get_entry_for_mount_point(fstab, "/misc");
-  if (record == NULL) {
-    fs_mgr_free_fstab(fstab);
+  auto record = GetEntryForMountPoint(&fstab, "/misc");
+  if (record == nullptr) {
     return -1;
   }
   if (strcmp(name, "misc") == 0) {
-    path = strdup(record->blk_device);
+    path = strdup(record->blk_device.c_str());
   } else {
     size_t trimmed_len, name_len;
-    const char* end_slash = strrchr(record->blk_device, '/');
+    const char* end_slash = strrchr(record->blk_device.c_str(), '/');
     if (end_slash == NULL) {
-      fs_mgr_free_fstab(fstab);
       return -1;
     }
-    trimmed_len = end_slash - record->blk_device + 1;
+    trimmed_len = end_slash - record->blk_device.c_str() + 1;
     name_len = strlen(name);
     path = static_cast<char*>(calloc(trimmed_len + name_len + 1, 1));
-    strncpy(path, record->blk_device, trimmed_len);
+    strncpy(path, record->blk_device.c_str(), trimmed_len);
     strncpy(path + trimmed_len, name, name_len);
   }
-  fs_mgr_free_fstab(fstab);
 
   fd = open(path, flags);
   free(path);
diff --git a/test/avb_slot_verify_unittest.cc b/test/avb_slot_verify_unittest.cc
index f63e831..e03c37e 100644
--- a/test/avb_slot_verify_unittest.cc
+++ b/test/avb_slot_verify_unittest.cc
@@ -48,7 +48,9 @@
 
   void CmdlineWithHashtreeVerification(bool hashtree_verification_on);
   void CmdlineWithChainedHashtreeVerification(bool hashtree_verification_on);
-  void VerificationDisabled(bool use_avbctl, bool preload);
+  void VerificationDisabled(bool use_avbctl,
+                            bool preload,
+                            bool has_system_partition);
 };
 
 TEST_F(AvbSlotVerifyTest, Basic) {
@@ -2006,7 +2008,8 @@
 }
 
 void AvbSlotVerifyTest::VerificationDisabled(bool use_avbctl,
-                                             bool preload_boot) {
+                                             bool preload_boot,
+                                             bool has_system_partition) {
   const size_t boot_part_size = 32 * 1024 * 1024;
   const size_t dtbo_part_size = 4 * 1024 * 1024;
   const size_t rootfs_size = 1028 * 1024;
@@ -2089,6 +2092,10 @@
   ops_.set_expected_public_key(
       PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
 
+  if (!has_system_partition) {
+    ops_.set_hidden_partitions({"system", "system_a", "system_b"});
+  }
+
   // Manually set the flag the same way 'avbctl disable-verification'
   // would do it.
   if (use_avbctl) {
@@ -2131,8 +2138,13 @@
                             AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
                             &slot_data));
   EXPECT_NE(nullptr, slot_data);
-  EXPECT_EQ("root=PARTUUID=1234-fake-guid-for:system_a",
-            std::string(slot_data->cmdline));
+  if (has_system_partition) {
+    EXPECT_EQ("root=PARTUUID=1234-fake-guid-for:system_a",
+              std::string(slot_data->cmdline));
+  } else {
+    EXPECT_EQ("", std::string(slot_data->cmdline));
+  }
+
   // Also make sure that it actually loads the boot and dtbo partitions.
   EXPECT_EQ(size_t(2), slot_data->num_loaded_partitions);
   EXPECT_EQ("boot",
@@ -2156,19 +2168,39 @@
 }
 
 TEST_F(AvbSlotVerifyTest, VerificationDisabledUnmodified) {
-  VerificationDisabled(false, false);  // use_avbctl
+  VerificationDisabled(false,  // use_avbctl
+                       false,  // preload_boot
+                       true);  // has_system_partition
 }
 
 TEST_F(AvbSlotVerifyTest, VerificationDisabledModified) {
-  VerificationDisabled(true, false);  // use_avbctl
+  VerificationDisabled(true,   // use_avbctl
+                       false,  // preload_boot
+                       true);  // has_system_partition
 }
 
 TEST_F(AvbSlotVerifyTest, VerificationDisabledUnmodifiedPreloadBoot) {
-  VerificationDisabled(false, true);  // use_avbctl
+  VerificationDisabled(false,  // use_avbctl
+                       true,   // preload_boot
+                       true);  // has_system_partition
 }
 
 TEST_F(AvbSlotVerifyTest, VerificationDisabledModifiedPreloadBoot) {
-  VerificationDisabled(true, true);  // use_avbctl
+  VerificationDisabled(true,   // use_avbctl
+                       true,   // preload_boot
+                       true);  // has_system_partition
+}
+
+TEST_F(AvbSlotVerifyTest, VerificationDisabledUnmodifiedNoSystemPartition) {
+  VerificationDisabled(false,   // use_avbctl
+                       false,   // preload_boot
+                       false);  // has_system_partition
+}
+
+TEST_F(AvbSlotVerifyTest, VerificationDisabledModifiedNoSystemPartition) {
+  VerificationDisabled(true,    // use_avbctl
+                       false,   // preload_boot
+                       false);  // has_system_partition
 }
 
 // In the event that there's no vbmeta partition, we treat the vbmeta
@@ -2827,9 +2859,9 @@
   const char* kPersistentValueName = "avb.persistent_digest.factory";
   // The digest for the hash descriptor which matches the factory contents.
   const uint8_t kDigest[AVB_SHA256_DIGEST_SIZE] = {
-      0x18, 0x4c, 0xb3, 0x62, 0x43, 0xad, 0xb8, 0xb8, 0x7d, 0x2d, 0x8c,
-      0x48, 0x02, 0xde, 0x32, 0x12, 0x5f, 0xe2, 0x94, 0xec, 0x46, 0x75,
-      0x3d, 0x73, 0x21, 0x44, 0xee, 0x65, 0xdf, 0x68, 0xa2, 0x3d};
+      0x2e, 0x7c, 0xab, 0x63, 0x14, 0xe9, 0x61, 0x4b, 0x6f, 0x2d, 0xa1,
+      0x26, 0x30, 0x66, 0x1c, 0x30, 0x38, 0xe5, 0x59, 0x20, 0x25, 0xf6,
+      0x53, 0x4b, 0xa5, 0x82, 0x3c, 0x3b, 0x34, 0x0a, 0x1c, 0xb6};
 };
 
 TEST_F(AvbSlotVerifyTestWithPersistentDigest, Basic) {
@@ -2845,7 +2877,7 @@
       "androidboot.vbmeta.hash_alg=sha256 "
       "androidboot.vbmeta.size=1280 "
       "androidboot.vbmeta.digest="
-      "604268b04d4a971d2d727c79a70b2ea7f6a0e42ccbdead1983acbf015061ce6b "
+      "f7a4ce48092379fe0e913ffda10d859cd5fc19fa721c9e81f05f8bfea14b9873 "
       "androidboot.vbmeta.invalidate_on_error=yes "
       "androidboot.veritymode=enforcing",
       last_cmdline_);
@@ -3050,7 +3082,7 @@
       "androidboot.vbmeta.hash_alg=sha256 "
       "androidboot.vbmeta.size=1408 "
       "androidboot.vbmeta.digest="
-      "0bf73ed205a043d410277444a49cc5643c1046f53b3b942cdc2c16fea06acd7b "
+      "eeaa2fb8deb48b9645f817bb6a6ce05ba3ef92d0d2d9c950c2383853cd4a3064 "
       "androidboot.vbmeta.invalidate_on_error=yes "
       "androidboot.veritymode=enforcing",
       last_cmdline_);
@@ -3077,7 +3109,7 @@
       "androidboot.vbmeta.hash_alg=sha256 "
       "androidboot.vbmeta.size=1408 "
       "androidboot.vbmeta.digest="
-      "7d64315450f035f4ff93560403c46de5a7e2a0ddfc84b95bd69f7ed5654aa687 "
+      "d3f35ef7a0812d8328be7850003b2c5607b673d0aede641656c9c04fa7992d40 "
       "androidboot.vbmeta.invalidate_on_error=yes "
       "androidboot.veritymode=enforcing",
       last_cmdline_);
@@ -3108,7 +3140,7 @@
       "androidboot.vbmeta.hash_alg=sha256 "
       "androidboot.vbmeta.size=1408 "
       "androidboot.vbmeta.digest="
-      "097d002a75d1e89557b662b6db3f1ebffb8419a02e79792a97b2c4fd1c8bedc4 "
+      "d6ea8d50dce5ca6d38ea6e780bb5b5d7ee588b53a92020ad3d1c99018f3e5f52 "
       "androidboot.vbmeta.invalidate_on_error=yes "
       "androidboot.veritymode=enforcing",
       last_cmdline_);
diff --git a/test/avb_unittest_util.cc b/test/avb_unittest_util.cc
index 59bac28..4c23a8f 100644
--- a/test/avb_unittest_util.cc
+++ b/test/avb_unittest_util.cc
@@ -24,6 +24,8 @@
 
 #include "avb_unittest_util.h"
 
+#include <android-base/file.h>
+
 std::string mem_to_hexstring(const uint8_t* data, size_t len) {
   std::string ret;
   char digits[17] = "0123456789abcdef";
@@ -42,3 +44,123 @@
   size_t last = str.find_last_not_of(" \t\n");
   return str.substr(first, (last - first + 1));
 }
+
+namespace avb {
+
+void BaseAvbToolTest::SetUp() {
+  /* Change current directory to test executable directory so that relative path
+   * references to test dependencies don't rely on being manually run from
+   * correct directory */
+  base::SetCurrentDirectory(
+      base::FilePath(android::base::GetExecutableDirectory()));
+
+  /* Create temporary directory to stash images in. */
+  base::FilePath ret;
+  char* buf = strdup("/tmp/libavb-tests.XXXXXX");
+  ASSERT_TRUE(mkdtemp(buf) != nullptr);
+  testdir_ = base::FilePath(buf);
+  free(buf);
+
+  /* Reset memory leak tracing */
+  avb::testing_memory_reset();
+}
+
+void BaseAvbToolTest::TearDown() {
+  /* Nuke temporary directory. */
+  ASSERT_EQ(0U, testdir_.value().find("/tmp/libavb-tests"));
+  ASSERT_TRUE(base::DeleteFile(testdir_, true /* recursive */));
+  /* Ensure all memory has been freed. */
+  EXPECT_TRUE(avb::testing_memory_all_freed());
+}
+
+std::string BaseAvbToolTest::CalcVBMetaDigest(const std::string& vbmeta_image,
+                                              const std::string& digest_alg) {
+  base::FilePath vbmeta_path = testdir_.Append(vbmeta_image);
+  base::FilePath vbmeta_digest_path = testdir_.Append("vbmeta_digest");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool calculate_vbmeta_digest --image %s --hash_algorithm %s"
+      " --output %s",
+      vbmeta_path.value().c_str(),
+      digest_alg.c_str(),
+      vbmeta_digest_path.value().c_str());
+  std::string vbmeta_digest_data;
+  EXPECT_TRUE(base::ReadFileToString(vbmeta_digest_path, &vbmeta_digest_data));
+  return string_trim(vbmeta_digest_data);
+}
+
+void BaseAvbToolTest::GenerateVBMetaImage(
+    const std::string& image_name,
+    const std::string& algorithm,
+    uint64_t rollback_index,
+    const base::FilePath& key_path,
+    const std::string& additional_options) {
+  std::string signing_options;
+  if (algorithm == "") {
+    signing_options = " --algorithm NONE ";
+  } else {
+    signing_options = std::string(" --algorithm ") + algorithm + " --key " +
+                      key_path.value() + " ";
+  }
+  vbmeta_image_path_ = testdir_.Append(image_name);
+  EXPECT_COMMAND(0,
+                 "./avbtool make_vbmeta_image"
+                 " --rollback_index %" PRIu64
+                 " %s %s "
+                 " --output %s",
+                 rollback_index,
+                 additional_options.c_str(),
+                 signing_options.c_str(),
+                 vbmeta_image_path_.value().c_str());
+  int64_t file_size;
+  ASSERT_TRUE(base::GetFileSize(vbmeta_image_path_, &file_size));
+  vbmeta_image_.resize(file_size);
+  ASSERT_TRUE(base::ReadFile(vbmeta_image_path_,
+                             reinterpret_cast<char*>(vbmeta_image_.data()),
+                             vbmeta_image_.size()));
+}
+
+/* Generate a file with name |file_name| of size |image_size| with
+ * known content (0x00 0x01 0x02 .. 0xff 0x00 0x01 ..).
+ */
+base::FilePath BaseAvbToolTest::GenerateImage(const std::string file_name,
+                                              size_t image_size,
+                                              uint8_t start_byte) {
+  std::vector<uint8_t> image;
+  image.resize(image_size);
+  for (size_t n = 0; n < image_size; n++) {
+    image[n] = uint8_t(n + start_byte);
+  }
+  base::FilePath image_path = testdir_.Append(file_name);
+  EXPECT_EQ(image_size,
+            static_cast<const size_t>(
+                base::WriteFile(image_path,
+                                reinterpret_cast<const char*>(image.data()),
+                                image.size())));
+  return image_path;
+}
+
+std::string BaseAvbToolTest::InfoImage(const base::FilePath& image_path) {
+  base::FilePath tmp_path = testdir_.Append("info_output.txt");
+  EXPECT_COMMAND(0,
+                 "./avbtool info_image --image %s --output %s",
+                 image_path.value().c_str(),
+                 tmp_path.value().c_str());
+  std::string info_data;
+  EXPECT_TRUE(base::ReadFileToString(tmp_path, &info_data));
+  return info_data;
+}
+
+std::string BaseAvbToolTest::PublicKeyAVB(const base::FilePath& key_path) {
+  base::FilePath tmp_path = testdir_.Append("public_key.bin");
+  EXPECT_COMMAND(0,
+                 "./avbtool extract_public_key --key %s"
+                 " --output %s",
+                 key_path.value().c_str(),
+                 tmp_path.value().c_str());
+  std::string key_data;
+  EXPECT_TRUE(base::ReadFileToString(tmp_path, &key_data));
+  return key_data;
+}
+
+}  // namespace avb
diff --git a/test/avb_unittest_util.h b/test/avb_unittest_util.h
index 014aa58..e14e6a7 100644
--- a/test/avb_unittest_util.h
+++ b/test/avb_unittest_util.h
@@ -30,7 +30,6 @@
 #include <gtest/gtest.h>
 
 #include <base/files/file_util.h>
-#include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
 
 // Encodes |len| bytes of |data| as a lower-case hex-string.
@@ -68,21 +67,7 @@
 
   /* Calculates the vbmeta digest using 'avbtool calc_vbmeta_digest' command. */
   std::string CalcVBMetaDigest(const std::string& vbmeta_image,
-                               const std::string& digest_alg) {
-    base::FilePath vbmeta_path = testdir_.Append(vbmeta_image);
-    base::FilePath vbmeta_digest_path = testdir_.Append("vbmeta_digest");
-    EXPECT_COMMAND(
-        0,
-        "./avbtool calculate_vbmeta_digest --image %s --hash_algorithm %s"
-        " --output %s",
-        vbmeta_path.value().c_str(),
-        digest_alg.c_str(),
-        vbmeta_digest_path.value().c_str());
-    std::string vbmeta_digest_data;
-    EXPECT_TRUE(
-        base::ReadFileToString(vbmeta_digest_path, &vbmeta_digest_data));
-    return string_trim(vbmeta_digest_data);
-  }
+                               const std::string& digest_alg);
 
   /* Generates a vbmeta image, using avbtoool, with file name
    * |image_name|. The generated vbmeta image will written to disk,
@@ -93,95 +78,23 @@
                            const std::string& algorithm,
                            uint64_t rollback_index,
                            const base::FilePath& key_path,
-                           const std::string& additional_options = "") {
-    std::string signing_options;
-    if (algorithm == "") {
-      signing_options = " --algorithm NONE ";
-    } else {
-      signing_options = std::string(" --algorithm ") + algorithm + " --key " +
-                        key_path.value() + " ";
-    }
-    vbmeta_image_path_ = testdir_.Append(image_name);
-    EXPECT_COMMAND(0,
-                   "./avbtool make_vbmeta_image"
-                   " --rollback_index %" PRIu64
-                   " %s %s "
-                   " --output %s",
-                   rollback_index,
-                   additional_options.c_str(),
-                   signing_options.c_str(),
-                   vbmeta_image_path_.value().c_str());
-    int64_t file_size;
-    ASSERT_TRUE(base::GetFileSize(vbmeta_image_path_, &file_size));
-    vbmeta_image_.resize(file_size);
-    ASSERT_TRUE(base::ReadFile(vbmeta_image_path_,
-                               reinterpret_cast<char*>(vbmeta_image_.data()),
-                               vbmeta_image_.size()));
-  }
+                           const std::string& additional_options = "");
 
   /* Generate a file with name |file_name| of size |image_size| with
    * known content (0x00 0x01 0x02 .. 0xff 0x00 0x01 ..).
    */
   base::FilePath GenerateImage(const std::string file_name,
                                size_t image_size,
-                               uint8_t start_byte = 0) {
-    std::vector<uint8_t> image;
-    image.resize(image_size);
-    for (size_t n = 0; n < image_size; n++) {
-      image[n] = uint8_t(n + start_byte);
-    }
-    base::FilePath image_path = testdir_.Append(file_name);
-    EXPECT_EQ(image_size,
-              static_cast<const size_t>(
-                  base::WriteFile(image_path,
-                                  reinterpret_cast<const char*>(image.data()),
-                                  image.size())));
-    return image_path;
-  }
+                               uint8_t start_byte = 0);
 
   /* Returns the output of 'avbtool info_image' for a given image. */
-  std::string InfoImage(const base::FilePath& image_path) {
-    base::FilePath tmp_path = testdir_.Append("info_output.txt");
-    EXPECT_COMMAND(0,
-                   "./avbtool info_image --image %s --output %s",
-                   image_path.value().c_str(),
-                   tmp_path.value().c_str());
-    std::string info_data;
-    EXPECT_TRUE(base::ReadFileToString(tmp_path, &info_data));
-    return info_data;
-  }
+  std::string InfoImage(const base::FilePath& image_path);
 
   /* Returns public key in AVB format for a .pem key */
-  std::string PublicKeyAVB(const base::FilePath& key_path) {
-    base::FilePath tmp_path = testdir_.Append("public_key.bin");
-    EXPECT_COMMAND(0,
-                   "./avbtool extract_public_key --key %s"
-                   " --output %s",
-                   key_path.value().c_str(),
-                   tmp_path.value().c_str());
-    std::string key_data;
-    EXPECT_TRUE(base::ReadFileToString(tmp_path, &key_data));
-    return key_data;
-  }
+  std::string PublicKeyAVB(const base::FilePath& key_path);
 
-  virtual void SetUp() override {
-    /* Create temporary directory to stash images in. */
-    base::FilePath ret;
-    char* buf = strdup("/tmp/libavb-tests.XXXXXX");
-    ASSERT_TRUE(mkdtemp(buf) != nullptr);
-    testdir_ = base::FilePath(buf);
-    free(buf);
-    /* Reset memory leak tracing */
-    avb::testing_memory_reset();
-  }
-
-  virtual void TearDown() override {
-    /* Nuke temporary directory. */
-    ASSERT_EQ(0U, testdir_.value().find("/tmp/libavb-tests"));
-    ASSERT_TRUE(base::DeleteFile(testdir_, true /* recursive */));
-    /* Ensure all memory has been freed. */
-    EXPECT_TRUE(avb::testing_memory_all_freed());
-  }
+  void SetUp() override;
+  void TearDown() override;
 
   /* Temporary directory created in SetUp(). */
   base::FilePath testdir_;
diff --git a/test/avbtool_unittest.cc b/test/avbtool_unittest.cc
index c436a2a..1dfdf86 100644
--- a/test/avbtool_unittest.cc
+++ b/test/avbtool_unittest.cc
@@ -2503,6 +2503,86 @@
                  pk8192_path.value().c_str());
 }
 
+TEST_F(AvbToolTest, VerifyImageChainPartitionWithFollow) {
+  base::FilePath pk4096_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+      " --output %s",
+      pk4096_path.value().c_str());
+
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      base::StringPrintf("--chain_partition system:1:%s ",
+                                         pk4096_path.value().c_str()));
+
+  const size_t system_partition_size = 10 * 1024 * 1024;
+  const size_t system_image_size = 8 * 1024 * 1024;
+  base::FilePath system_path = GenerateImage("system.img", system_image_size);
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hashtree_footer --salt d00df00d --image %s "
+                 "--partition_size %zd --partition_name system "
+                 "--algorithm SHA256_RSA4096 "
+                 "--key test/data/testkey_rsa4096.pem "
+                 "--internal_release_string \"\" ",
+                 system_path.value().c_str(),
+                 system_partition_size);
+
+  // Even without --expected_chain_partition this shouldn't fail because we use
+  // --follow_chain_partitions and system.img exists... to avoid unstable paths
+  // (e.g. /tmp/libavb.12345) in the output we need to run this from the test
+  // directory itself. It's a little ugly but it works.
+  char cwdbuf[PATH_MAX];
+  ASSERT_NE(nullptr, getcwd(cwdbuf, sizeof cwdbuf));
+  EXPECT_COMMAND(0,
+                 "cd %s && (%s/avbtool verify_image "
+                 "--image vbmeta.img --follow_chain_partitions > out.txt)",
+                 testdir_.value().c_str(),
+                 cwdbuf);
+  base::FilePath out_path = testdir_.Append("out.txt");
+  std::string out;
+  ASSERT_TRUE(base::ReadFileToString(out_path, &out));
+  EXPECT_EQ(
+      "Verifying image vbmeta.img using embedded public key\n"
+      "vbmeta: Successfully verified SHA256_RSA2048 vbmeta struct in "
+      "vbmeta.img\n"
+      "system: Chained but ROLLBACK_SLOT (which is 1) and KEY (which has sha1 "
+      "2597c218aae470a130f61162feaae70afd97f011) not specified\n"
+      "--\n"
+      "Verifying image system.img using embedded public key\n"
+      "vbmeta: Successfully verified footer and SHA256_RSA4096 vbmeta struct "
+      "in system.img\n"
+      "system: Successfully verified sha1 hashtree of system.img for image of "
+      "8388608 bytes\n",
+      out);
+
+  // Make sure we also follow partitions *even* when specifying
+  // --expect_chain_partition. The output is slightly different from above.
+  EXPECT_COMMAND(0,
+                 "cd %s && (%s/avbtool verify_image "
+                 "--image vbmeta.img --expected_chain_partition system:1:%s "
+                 "--follow_chain_partitions > out.txt)",
+                 testdir_.value().c_str(),
+                 cwdbuf,
+                 pk4096_path.value().c_str());
+  ASSERT_TRUE(base::ReadFileToString(out_path, &out));
+  EXPECT_EQ(
+      "Verifying image vbmeta.img using embedded public key\n"
+      "vbmeta: Successfully verified SHA256_RSA2048 vbmeta struct in "
+      "vbmeta.img\n"
+      "system: Successfully verified chain partition descriptor matches "
+      "expected data\n"
+      "--\n"
+      "Verifying image system.img using embedded public key\n"
+      "vbmeta: Successfully verified footer and SHA256_RSA4096 vbmeta struct "
+      "in system.img\n"
+      "system: Successfully verified sha1 hashtree of system.img for image of "
+      "8388608 bytes\n",
+      out);
+}
+
 TEST_F(AvbToolTest, VerifyImageChainPartitionOtherVBMeta) {
   base::FilePath pk4096_path = testdir_.Append("testkey_rsa4096.avbpubkey");
   EXPECT_COMMAND(
diff --git a/tools/at_auth_unlock.py b/tools/at_auth_unlock.py
index f49b4a6..80b5bb6 100755
--- a/tools/at_auth_unlock.py
+++ b/tools/at_auth_unlock.py
@@ -362,7 +362,7 @@
         creds.append(UnlockCredentials.from_credential_archive(path))
         if verbose:
           print('Found valid unlock credential bundle: ' + path)
-      except (IOError, ValueError, zipfile.BadZipFile) as e:
+      except (IOError, ValueError, zipfile.BadZipfile) as e:
         if verbose:
           print(
               "Ignoring file which isn't a valid unlock credential zip bundle: "