avbtool: verify_image: Verify hash-, hashtree, and chain-descriptors. am: 5dfb4e9999 am: 4c3125a6ea am: 8aef401cdd
am: 9803f2739e

Change-Id: I97aaf8089a22a88e4afb06c86f044d89584442aa
diff --git a/README.md b/README.md
index 9b7fc5b..459308a 100644
--- a/README.md
+++ b/README.md
@@ -381,11 +381,6 @@
 
     /path/to/my_signing_program SHA256_RSA2048 /path/to/publickey.pem
 
-The `verify_image` command verifies that the signature on the vbmeta
-struct is valid and that it was made with the embedded public
-key. This can be used to check that a custom signing helper works as
-intended.
-
 The `append_vbmeta_image` command can be used to append an entire
 vbmeta blob to the end of another image. This is useful for cases when
 not using any vbmeta partitions, for example:
@@ -397,6 +392,60 @@
         --vbmeta_image vbmeta.img
     $ fastboot flash boot boot-with-vbmeta-appended.img
 
+The `verify_image` command can be used to verify the contents of
+several image files at the same time. When invoked on an image the
+following checks are performed:
+
+* If the image has a VBMeta struct the signature is checked against
+  the embedded public key. If the image doesn't look like `vbmeta.img`
+  then a footer is looked for and used if present.
+
+* If the option `--key` is passed then a `.pem` file is expected and
+  it's checked that the embedded public key in said VBMeta struct
+  matches the given key.
+
+* All descriptors in the VBMeta struct are checked in the following
+  way:
+    + For a hash descriptor the image file corresponding to the
+      partition name is loaded and its digest is checked against that
+      in the descriptor.
+    + For a hashtree descriptor the image file corresponding to the
+      partition name is loaded and the hashtree is calculated and its
+      root digest compared to that in the descriptor.
+    + For a chained partition descriptor its contents is compared
+      against content that needs to be passed in via the
+      `--expected_chain_partition` options. The format for this option
+      is similar to that of the `--chain_partition` option. If there
+      is no `--expected_chain_partition` descriptor for the chain
+      partition descriptor the check fails.
+
+Here's an example for a setup where the digests for `boot.img` and
+`system.img` are stored in `vbmeta.img` which is signed with
+`my_key.pem`. It also checks that the chain partition for partition
+`foobar` uses rollback index 8 and that the public key in AVB format
+matches that of the file `foobar_vendor_key.avbpubkey`:
+
+    $ avbtool verify_image \
+         --image /path/to/vbmeta.img \
+         --key my_key.pem \
+         --expect_chained_partition foobar:8:foobar_vendor_key.avbpubkey
+
+    Verifying image /path/to/vbmeta.img using key at my_key.pem
+    vbmeta: Successfully verified SHA256_RSA4096 vbmeta struct in /path_to/vbmeta.img
+    boot: Successfully verified sha256 hash of /path/to/boot.img for image of 10543104 bytes
+    system: Successfully verified sha1 hashtree of /path/to/system.img for image of 1065213952 bytes
+    foobar: Successfully verified chain partition descriptor matches expected data
+
+In this example the `verify_image` command verifies the files
+`vbmeta.img`, `boot.img`, and `system.img` in the directory
+`/path/to`. The directory and file extension of the given image
+(e.g. `/path/to/vbmeta.img`) is used together with the partition name
+in the descriptor to calculate the filenames of the images holding
+hash and hashtree images.
+
+The `verify_image` command can also be used to check that a custom
+signing helper works as intended.
+
 ## Build System Integration
 
 In Android, AVB is enabled by the `BOARD_AVB_ENABLE` variable
diff --git a/avbtool b/avbtool
index 589e555..1a97ea4 100755
--- a/avbtool
+++ b/avbtool
@@ -1061,6 +1061,20 @@
     ret = desc + self.data + padding
     return bytearray(ret)
 
+  def verify(self, image_dir, image_ext, expected_chain_partitions_map):
+    """Verifies contents of the descriptor - used in verify_image sub-command.
+
+    Arguments:
+      image_dir: The directory of the file being verified.
+      image_ext: The extension of the file being verified (e.g. '.img').
+      expected_chain_partitions_map: A map from partition name to the
+        tuple (rollback_index_location, key_blob).
+
+    Returns:
+      True if the descriptor verifies, False otherwise.
+    """
+    # Nothing to do.
+    return True
 
 class AvbPropertyDescriptor(AvbDescriptor):
   """A class for property descriptors.
@@ -1131,6 +1145,20 @@
     ret = desc + self.key + '\0' + self.value + '\0' + padding
     return bytearray(ret)
 
+  def verify(self, image_dir, image_ext, expected_chain_partitions_map):
+    """Verifies contents of the descriptor - used in verify_image sub-command.
+
+    Arguments:
+      image_dir: The directory of the file being verified.
+      image_ext: The extension of the file being verified (e.g. '.img').
+      expected_chain_partitions_map: A map from partition name to the
+        tuple (rollback_index_location, key_blob).
+
+    Returns:
+      True if the descriptor verifies, False otherwise.
+    """
+    # Nothing to do.
+    return True
 
 class AvbHashtreeDescriptor(AvbDescriptor):
   """A class for hashtree descriptors.
@@ -1272,6 +1300,52 @@
     ret = desc + encoded_name + self.salt + self.root_digest + padding
     return bytearray(ret)
 
+  def verify(self, image_dir, image_ext, expected_chain_partitions_map):
+    """Verifies contents of the descriptor - used in verify_image sub-command.
+
+    Arguments:
+      image_dir: The directory of the file being verified.
+      image_ext: The extension of the file being verified (e.g. '.img').
+      expected_chain_partitions_map: A map from partition name to the
+        tuple (rollback_index_location, key_blob).
+
+    Returns:
+      True if the descriptor verifies, False otherwise.
+    """
+    image_filename = os.path.join(image_dir, self.partition_name + image_ext)
+    image = ImageHandler(image_filename)
+    # Generate the hashtree and checks that it matches what's in the file.
+    digest_size = len(hashlib.new(name=self.hash_algorithm).digest())
+    digest_padding = round_to_pow2(digest_size) - digest_size
+    (hash_level_offsets, tree_size) = calc_hash_level_offsets(
+      self.image_size, self.data_block_size, digest_size + digest_padding)
+    root_digest, hash_tree = generate_hash_tree(image, self.image_size,
+                                                self.data_block_size,
+                                                self.hash_algorithm, self.salt,
+                                                digest_padding,
+                                                hash_level_offsets,
+                                                tree_size)
+    # The root digest must match...
+    if root_digest != self.root_digest:
+      sys.stderr.write('hashtree of {} does not match descriptor\n'.
+                       format(image_filename))
+      return False
+    # ... also check that the on-disk hashtree matches
+    image.seek(self.tree_offset)
+    hash_tree_ondisk = image.read(self.tree_size)
+    if hash_tree != hash_tree_ondisk:
+      sys.stderr.write('hashtree of {} contains invalid data\n'.
+                       format(image_filename))
+      return False
+    # TODO: we could also verify that the FEC stored in the image is
+    # correct but this a) currently requires the 'fec' binary; and b)
+    # takes a long time; and c) is not strictly needed for
+    # verification purposes as we've already verified the root hash.
+    print ('{}: Successfully verified {} hashtree of {} for image of {} bytes'
+           .format(self.partition_name, self.hash_algorithm, image_filename,
+                   self.image_size))
+    return True
+
 
 class AvbHashDescriptor(AvbDescriptor):
   """A class for hash descriptors.
@@ -1371,6 +1445,34 @@
     ret = desc + encoded_name + self.salt + self.digest + padding
     return bytearray(ret)
 
+  def verify(self, image_dir, image_ext, expected_chain_partitions_map):
+    """Verifies contents of the descriptor - used in verify_image sub-command.
+
+    Arguments:
+      image_dir: The directory of the file being verified.
+      image_ext: The extension of the file being verified (e.g. '.img').
+      expected_chain_partitions_map: A map from partition name to the
+        tuple (rollback_index_location, key_blob).
+
+    Returns:
+      True if the descriptor verifies, False otherwise.
+    """
+    image_filename = os.path.join(image_dir, self.partition_name + image_ext)
+    image = ImageHandler(image_filename)
+    data = image.read(self.image_size)
+    ha = hashlib.new(self.hash_algorithm)
+    ha.update(self.salt)
+    ha.update(data)
+    digest = ha.digest()
+    if digest != self.digest:
+      sys.stderr.write('{} digest of {} does not match digest in descriptor\n'.
+                       format(self.hash_algorithm, image_filename))
+      return False
+    print ('{}: Successfully verified {} hash of {} for image of {} bytes'
+           .format(self.partition_name, self.hash_algorithm, image_filename,
+                   self.image_size))
+    return True
+
 
 class AvbKernelCmdlineDescriptor(AvbDescriptor):
   """A class for kernel command-line descriptors.
@@ -1447,6 +1549,20 @@
     ret = desc + encoded_str + padding
     return bytearray(ret)
 
+  def verify(self, image_dir, image_ext, expected_chain_partitions_map):
+    """Verifies contents of the descriptor - used in verify_image sub-command.
+
+    Arguments:
+      image_dir: The directory of the file being verified.
+      image_ext: The extension of the file being verified (e.g. '.img').
+      expected_chain_partitions_map: A map from partition name to the
+        tuple (rollback_index_location, key_blob).
+
+    Returns:
+      True if the descriptor verifies, False otherwise.
+    """
+    # Nothing to verify.
+    return True
 
 class AvbChainPartitionDescriptor(AvbDescriptor):
   """A class for chained partition descriptors.
@@ -1534,6 +1650,45 @@
     ret = desc + encoded_name + self.public_key + padding
     return bytearray(ret)
 
+  def verify(self, image_dir, image_ext, expected_chain_partitions_map):
+    """Verifies contents of the descriptor - used in verify_image sub-command.
+
+    Arguments:
+      image_dir: The directory of the file being verified.
+      image_ext: The extension of the file being verified (e.g. '.img').
+      expected_chain_partitions_map: A map from partition name to the
+        tuple (rollback_index_location, key_blob).
+
+    Returns:
+      True if the descriptor verifies, False otherwise.
+    """
+    value = expected_chain_partitions_map.get(self.partition_name)
+    if not value:
+      sys.stderr.write('No expected chain partition for partition {}. Use '
+                       '--expected_chain_partition to specify expected '
+                       'contents.\n'.
+                       format(self.partition_name))
+      return False
+    rollback_index_location, pk_blob = value
+
+    if self.rollback_index_location != rollback_index_location:
+      sys.stderr.write('Expected rollback_index_location {} does not '
+                       'match {} in descriptor for partition {}\n'.
+                       format(rollback_index_location,
+                              self.rollback_index_location,
+                              self.partition_name))
+      return False
+
+    if self.public_key != pk_blob:
+      sys.stderr.write('Expected public key blob does not match public '
+                       'key blob in descriptor for partition {}\n'.
+                       format(self.partition_name))
+      return False
+
+    print ('{}: Successfully verified chain partition descriptor matches '
+           'expected data'.format(self.partition_name))
+
+    return True
 
 DESCRIPTOR_CLASSES = [
     AvbPropertyDescriptor, AvbHashtreeDescriptor, AvbHashDescriptor,
@@ -1948,13 +2103,38 @@
     if num_printed == 0:
       o.write('    (none)\n')
 
-  def verify_image(self, image_filename):
+  def verify_image(self, image_filename, key_path, expected_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.
     """
 
+    expected_chain_partitions_map = {}
+    if expected_chain_partitions:
+      used_locations = {}
+      for cp in expected_chain_partitions:
+        cp_tokens = cp.split(':')
+        if len(cp_tokens) != 3:
+          raise AvbError('Malformed chained partition "{}".'.format(cp))
+        partition_name = cp_tokens[0]
+        rollback_index_location = int(cp_tokens[1])
+        file_path = cp_tokens[2]
+        pk_blob = open(file_path).read()
+        expected_chain_partitions_map[partition_name] = (rollback_index_location, pk_blob)
+
+    image_dir = os.path.dirname(image_filename)
+    image_ext = os.path.splitext(image_filename)[1]
+
+    key_blob = None
+    if key_path:
+      print 'Verifying image {} using key at {}'.format(image_filename, key_path)
+      key_blob = encode_rsa_key(key_path)
+    else:
+      print 'Verifying image {} using embedded public key'.format(image_filename)
+
     image = ImageHandler(image_filename)
     (footer, header, descriptors, image_size) = self._parse_image(image)
     offset = 0
@@ -1964,8 +2144,32 @@
             header.auxiliary_data_block_size)
     image.seek(offset)
     vbmeta_blob = image.read(size)
+    h = AvbVBMetaHeader(vbmeta_blob[0:AvbVBMetaHeader.SIZE])
+    alg_name, _ = lookup_algorithm_by_type(header.algorithm_type)
     if not verify_vbmeta_signature(header, vbmeta_blob):
-      raise AvbError('Signature check failed.')
+      raise AvbError('Signature check failed for {} vbmeta struct {}'
+                     .format(alg_name, image_filename))
+
+    if key_blob:
+      # The embedded public key is in the auxiliary block at an offset.
+      key_offset = AvbVBMetaHeader.SIZE
+      key_offset += h.authentication_data_block_size
+      key_offset += h.public_key_offset
+      key_blob_in_vbmeta = vbmeta_blob[key_offset:key_offset + h.public_key_size]
+      if key_blob != key_blob_in_vbmeta:
+        raise AvbError('Embedded public key does not match given key.')
+
+    if footer:
+      print ('vbmeta: Successfully verified footer and {} vbmeta struct in {}'
+             .format(alg_name, image_filename))
+    else:
+      print ('vbmeta: Successfully verified {} vbmeta struct in {}'
+             .format(alg_name, image_filename))
+
+    for desc in descriptors:
+      if not desc.verify(image_dir, image_ext, expected_chain_partitions_map):
+        raise AvbError('Error verifying descriptor.')
+
 
   def _parse_image(self, image):
     """Gets information about an image.
@@ -3407,6 +3611,14 @@
                             help='Image to verify',
                             type=argparse.FileType('rb'),
                             required=True)
+    sub_parser.add_argument('--key',
+                            help='Check embedded public key matches KEY',
+                            metavar='KEY',
+                            required=False)
+    sub_parser.add_argument('--expected_chain_partition',
+                            help='Expected chain partition',
+                            metavar='PART_NAME:ROLLBACK_SLOT:KEY_PATH',
+                            action='append')
     sub_parser.set_defaults(func=self.verify_image)
 
     sub_parser = subparsers.add_parser('set_ab_metadata',
@@ -3591,7 +3803,8 @@
 
   def verify_image(self, args):
     """Implements the 'verify_image' sub-command."""
-    self.avb.verify_image(args.image.name)
+    self.avb.verify_image(args.image.name, args.key,
+                          args.expected_chain_partition)
 
   def make_atx_certificate(self, args):
     """Implements the 'make_atx_certificate' sub-command."""
diff --git a/test/avbtool_unittest.cc b/test/avbtool_unittest.cc
index 9d53f6c..f1c1831 100644
--- a/test/avbtool_unittest.cc
+++ b/test/avbtool_unittest.cc
@@ -37,6 +37,7 @@
 #include <libavb/libavb.h>
 
 #include "avb_unittest_util.h"
+#include "fake_avb_ops.h"
 
 namespace avb {
 
@@ -44,9 +45,20 @@
  public:
   AvbToolTest() {}
 
+  virtual void SetUp() override {
+    BaseAvbToolTest::SetUp();
+    ops_.set_partition_dir(testdir_);
+    ops_.set_stored_rollback_indexes({{0, 0}, {1, 0}, {2, 0}, {3, 0}});
+    ops_.set_stored_is_device_unlocked(false);
+  }
+
   void AddHashFooterTest(bool sparse_image);
   void AddHashtreeFooterTest(bool sparse_image);
   void AddHashtreeFooterFECTest(bool sparse_image);
+
+  void GenerateImageWithHashAndHashtreeSetup();
+
+  FakeAvbOps ops_;
 };
 
 // This test ensure that the version is increased in both
@@ -1599,6 +1611,53 @@
                  vbmeta_image_path_.value().c_str());
 }
 
+TEST_F(AvbToolTest, VerifyImageCorruptedVBMeta) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  // Corrupt four bytes of data in the end of the image. Since the aux
+  // data is at the end and this data is signed, this will change the
+  // value of the computed hash.
+  uint8_t corrupt_data[4] = {0xff, 0xff, 0xff, 0xff};
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            ops_.avb_ops()->write_to_partition(ops_.avb_ops(),
+                                               "vbmeta",
+                                               -4,  // offset from end
+                                               sizeof corrupt_data,
+                                               corrupt_data));
+
+  EXPECT_COMMAND(1,
+                 "./avbtool verify_image "
+                 "--image %s ",
+                 vbmeta_image_path_.value().c_str());
+}
+
+TEST_F(AvbToolTest, VerifyImageOtherKeyMatching) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  EXPECT_COMMAND(0,
+                 "./avbtool verify_image "
+                 "--image %s --key test/data/testkey_rsa2048.pem",
+                 vbmeta_image_path_.value().c_str());
+}
+
+TEST_F(AvbToolTest, VerifyImageOtherKeyNotMatching) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  EXPECT_COMMAND(1,
+                 "./avbtool verify_image "
+                 "--image %s --key test/data/testkey_rsa4096.pem",
+                 vbmeta_image_path_.value().c_str());
+}
+
 TEST_F(AvbToolTest, VerifyImageBrokenSignature) {
   base::FilePath vbmeta_path = testdir_.Append("vbmeta.bin");
   base::FilePath signing_helper_test_path =
@@ -1620,6 +1679,176 @@
                  vbmeta_path.value().c_str());
 }
 
+// Helper to generate boot.img, unsparse system.img, and vbmeta.img.
+void AvbToolTest::GenerateImageWithHashAndHashtreeSetup() {
+  const size_t boot_partition_size = 16 * 1024 * 1024;
+  const size_t boot_image_size = 5 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --rollback_index 0"
+                 " --partition_name boot"
+                 " --partition_size %zd"
+                 " --salt deadbeef"
+                 " --internal_release_string \"\"",
+                 boot_path.value().c_str(),
+                 boot_partition_size);
+
+  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 "
+                 "--internal_release_string \"\" ",
+                 system_path.value().c_str(),
+                 system_partition_size);
+
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      base::StringPrintf("--include_descriptors_from_image %s "
+                                         "--include_descriptors_from_image %s",
+                                         boot_path.value().c_str(),
+                                         system_path.value().c_str()));
+}
+
+TEST_F(AvbToolTest, VerifyImageWithHashAndHashtree) {
+  GenerateImageWithHashAndHashtreeSetup();
+
+  // Do two checks - one for system.img not sparse, and one where it
+  // is sparse.
+  for (int n = 0; n < 2; n++) {
+    EXPECT_COMMAND(0,
+                   "./avbtool verify_image "
+                   "--image %s ",
+                   vbmeta_image_path_.value().c_str());
+    if (n == 0) {
+      EXPECT_COMMAND(0,
+                     "img2simg %s %s.sparse",
+                     testdir_.Append("system.img").value().c_str(),
+                     testdir_.Append("system.img").value().c_str());
+      EXPECT_COMMAND(0,
+                     "mv %s.sparse %s",
+                     testdir_.Append("system.img").value().c_str(),
+                     testdir_.Append("system.img").value().c_str());
+    }
+  }
+}
+
+TEST_F(AvbToolTest, VerifyImageWithHashAndHashtreeCorruptHash) {
+  GenerateImageWithHashAndHashtreeSetup();
+
+  // Corrupt four bytes of data in the middle of boot.img.
+  uint8_t corrupt_data[4] = {0xff, 0xff, 0xff, 0xff};
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            ops_.avb_ops()->write_to_partition(ops_.avb_ops(),
+                                               "boot",
+                                               105 * 1024,  // offset from start
+                                               sizeof corrupt_data,
+                                               corrupt_data));
+
+  EXPECT_COMMAND(1,
+                 "./avbtool verify_image "
+                 "--image %s ",
+                 vbmeta_image_path_.value().c_str());
+}
+
+TEST_F(AvbToolTest, VerifyImageWithHashAndHashtreeCorruptHashtree) {
+  GenerateImageWithHashAndHashtreeSetup();
+
+  // Corrupt four bytes of data in the middle of system.img.
+  uint8_t corrupt_data[4] = {0xff, 0xff, 0xff, 0xff};
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            ops_.avb_ops()->write_to_partition(ops_.avb_ops(),
+                                               "system",
+                                               123 * 1024,  // offset from start
+                                               sizeof corrupt_data,
+                                               corrupt_data));
+
+  // Do two checks - one for system.img not sparse, and one where it
+  // is sparse.
+  for (int n = 0; n < 2; n++) {
+    EXPECT_COMMAND(1,
+                   "./avbtool verify_image "
+                   "--image %s ",
+                   vbmeta_image_path_.value().c_str());
+    if (n == 0) {
+      EXPECT_COMMAND(0,
+                     "img2simg %s %s.sparse",
+                     testdir_.Append("system.img").value().c_str(),
+                     testdir_.Append("system.img").value().c_str());
+      EXPECT_COMMAND(0,
+                     "mv %s.sparse %s",
+                     testdir_.Append("system.img").value().c_str(),
+                     testdir_.Append("system.img").value().c_str());
+    }
+  }
+}
+
+TEST_F(AvbToolTest, VerifyImageChainPartition) {
+  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());
+
+  base::FilePath pk8192_path = testdir_.Append("testkey_rsa8192.avbpubkey");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa8192.pem"
+      " --output %s",
+      pk8192_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()));
+
+  // Should not fail (name, rollback_index, contents all correct).
+  EXPECT_COMMAND(0,
+                 "./avbtool verify_image "
+                 "--image %s "
+                 "--expected_chain_partition system:1:%s",
+                 vbmeta_image_path_.value().c_str(),
+                 pk4096_path.value().c_str());
+
+  // Should fail because we didn't use --expected_chain_partition.
+  EXPECT_COMMAND(1,
+                 "./avbtool verify_image "
+                 "--image %s ",
+                 vbmeta_image_path_.value().c_str());
+
+  // Should fail because partition name is wrong.
+  EXPECT_COMMAND(1,
+                 "./avbtool verify_image "
+                 "--image %s "
+                 "--expected_chain_partition xyz:1:%s",
+                 vbmeta_image_path_.value().c_str(),
+                 pk4096_path.value().c_str());
+
+  // Should fail because rollback index location is wrong.
+  EXPECT_COMMAND(1,
+                 "./avbtool verify_image "
+                 "--image %s "
+                 "--expected_chain_partition system:2:%s",
+                 vbmeta_image_path_.value().c_str(),
+                 pk4096_path.value().c_str());
+
+  // Should fail because public key blob is wrong.
+  EXPECT_COMMAND(1,
+                 "./avbtool verify_image "
+                 "--image %s "
+                 "--expected_chain_partition system:1:%s",
+                 vbmeta_image_path_.value().c_str(),
+                 pk8192_path.value().c_str());
+}
+
 TEST_F(AvbToolTest, MakeAtxPikCertificate) {
   base::FilePath subject_path = testdir_.Append("tmp_subject");
   ASSERT_TRUE(base::WriteFile(subject_path, "fake PIK subject", 16));