Merge "Add new "androidboot.vbmeta.public_key_digest" kernel commandline option." into main
diff --git a/avbtool.py b/avbtool.py
index 644f40b..026de9d 100755
--- a/avbtool.py
+++ b/avbtool.py
@@ -3280,6 +3280,20 @@
     """
     output.write(RSAPublicKey(key_path).encode())
 
+  def extract_public_key_digest(self, key_path, output):
+    """Implements the 'extract_public_key_digest' command.
+
+    Arguments:
+      key_path: The path to a RSA private key file.
+      output: The file to write to.
+
+    Raises:
+      AvbError: If the public key could not be extracted.
+    """
+    hasher = hashlib.sha256()
+    hasher.update(RSAPublicKey(key_path).encode())
+    output.write(hasher.hexdigest())
+
   def append_vbmeta_image(self, image_filename, vbmeta_image_filename,
                           partition_size):
     """Implementation of the append_vbmeta_image command.
@@ -4369,6 +4383,17 @@
                             required=True)
     sub_parser.set_defaults(func=self.extract_public_key)
 
+    sub_parser = subparsers.add_parser('extract_public_key_digest',
+                                       help='Extract SHA-256 digest of public key.')
+    sub_parser.add_argument('--key',
+                            help='Path to RSA private key file',
+                            required=True)
+    sub_parser.add_argument('--output',
+                            help='Output file name',
+                            type=argparse.FileType('w', encoding='UTF-8'),
+                            required=True)
+    sub_parser.set_defaults(func=self.extract_public_key_digest)
+
     sub_parser = subparsers.add_parser('make_vbmeta_image',
                                        help='Makes a vbmeta image.')
     sub_parser.add_argument('--output',
@@ -4815,6 +4840,10 @@
     """Implements the 'extract_public_key' sub-command."""
     self.avb.extract_public_key(args.key, args.output)
 
+  def extract_public_key_digest(self, args):
+    """Implements the 'extract_public_key_digest' sub-command."""
+    self.avb.extract_public_key_digest(args.key, args.output)
+
   def make_vbmeta_image(self, args):
     """Implements the 'make_vbmeta_image' sub-command."""
     args = self._fixup_common_args(args)
diff --git a/libavb/avb_cmdline.c b/libavb/avb_cmdline.c
index 6613020..3d4bd54 100644
--- a/libavb/avb_cmdline.c
+++ b/libavb/avb_cmdline.c
@@ -212,6 +212,8 @@
     AvbSlotVerifyFlags flags,
     AvbSlotVerifyData* slot_data,
     AvbVBMetaImageHeader* toplevel_vbmeta,
+    const uint8_t* toplevel_vbmeta_public_key_data,
+    size_t toplevel_vbmeta_public_key_length,
     AvbAlgorithmType algorithm_type,
     AvbHashtreeErrorMode hashtree_error_mode,
     AvbHashtreeErrorMode resolved_hashtree_error_mode) {
@@ -220,16 +222,34 @@
   bool is_device_unlocked;
   AvbIOResult io_ret;
 
-  /* Add androidboot.vbmeta.device option... except if not using a vbmeta
-   * partition since it doesn't make sense in that case.
-   */
+  /* Add options that only make sense if there is a vbmeta partition. */
   if (!(flags & AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION)) {
+    /* Add androidboot.vbmeta.device option. */
     if (!cmdline_append_option(slot_data,
                                "androidboot.vbmeta.device",
                                "PARTUUID=$(ANDROID_VBMETA_PARTUUID)")) {
       ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
       goto out;
     }
+
+    /* Set androidboot.vbmeta.public_key_digest to the SHA-256 hash of the
+     * public key used to verify the vbmeta image. */
+    if (toplevel_vbmeta_public_key_data != NULL &&
+        toplevel_vbmeta_public_key_length > 0) {
+      AvbSHA256Ctx ctx;
+      avb_sha256_init(&ctx);
+      avb_sha256_update(&ctx,
+                        toplevel_vbmeta_public_key_data,
+                        toplevel_vbmeta_public_key_length);
+      uint8_t* vbmeta_public_key_digest = avb_sha256_final(&ctx);
+      if (!cmdline_append_hex(slot_data,
+                              "androidboot.vbmeta.public_key_digest",
+                              vbmeta_public_key_digest,
+                              AVB_SHA256_DIGEST_SIZE)) {
+        ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+        goto out;
+      }
+    }
   }
 
   /* Add androidboot.vbmeta.avb_version option. */
diff --git a/libavb/avb_cmdline.h b/libavb/avb_cmdline.h
index 377783f..f690312 100644
--- a/libavb/avb_cmdline.h
+++ b/libavb/avb_cmdline.h
@@ -65,6 +65,8 @@
     AvbSlotVerifyFlags flags,
     AvbSlotVerifyData* slot_data,
     AvbVBMetaImageHeader* toplevel_vbmeta,
+    const uint8_t* toplevel_vbmeta_public_key_data,
+    size_t toplevel_vbmeta_public_key_length,
     AvbAlgorithmType algorithm_type,
     AvbHashtreeErrorMode hashtree_error_mode,
     AvbHashtreeErrorMode resolved_hashtree_error_mode);
diff --git a/libavb/avb_slot_verify.c b/libavb/avb_slot_verify.c
index 3ff59c2..a8b4255 100644
--- a/libavb/avb_slot_verify.c
+++ b/libavb/avb_slot_verify.c
@@ -565,6 +565,8 @@
     size_t expected_public_key_length,
     AvbSlotVerifyData* slot_data,
     AvbAlgorithmType* out_algorithm_type,
+    uint8_t** out_toplevel_vbmeta_public_key_data,
+    size_t* out_toplevel_vbmeta_public_key_length,
     AvbCmdlineSubstList* out_additional_cmdline_subst,
     bool use_ab_suffix) {
   char full_partition_name[AVB_PART_NAME_MAX_SIZE];
@@ -753,6 +755,8 @@
                                    0 /* expected_public_key_length */,
                                    slot_data,
                                    out_algorithm_type,
+                                   out_toplevel_vbmeta_public_key_data,
+                                   out_toplevel_vbmeta_public_key_length,
                                    out_additional_cmdline_subst,
                                    use_ab_suffix);
       goto out;
@@ -772,6 +776,22 @@
   switch (vbmeta_ret) {
     case AVB_VBMETA_VERIFY_RESULT_OK:
       avb_assert(pk_data != NULL && pk_len > 0);
+      if (is_main_vbmeta) {
+        if (out_toplevel_vbmeta_public_key_data != NULL) {
+          *out_toplevel_vbmeta_public_key_data = avb_malloc(pk_len);
+          if (*out_toplevel_vbmeta_public_key_data == NULL) {
+            ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+            goto out;
+          }
+          // Copy the public key data into the output parameter since pk_data
+          // is a pointer to data in vbmeta_buf, whose memory gets deallocated
+          // at the end of this function.
+          avb_memcpy(*out_toplevel_vbmeta_public_key_data, pk_data, pk_len);
+        }
+        if (out_toplevel_vbmeta_public_key_length != NULL) {
+          *out_toplevel_vbmeta_public_key_length = pk_len;
+        }
+      }
       break;
 
     case AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED:
@@ -1052,6 +1072,8 @@
                                    chain_desc.public_key_len,
                                    slot_data,
                                    NULL, /* out_algorithm_type */
+                                   out_toplevel_vbmeta_public_key_data,
+                                   out_toplevel_vbmeta_public_key_length,
                                    NULL, /* out_additional_cmdline_subst */
                                    use_ab_suffix);
         if (sub_ret != AVB_SLOT_VERIFY_RESULT_OK) {
@@ -1393,6 +1415,8 @@
   AvbAlgorithmType algorithm_type = AVB_ALGORITHM_TYPE_NONE;
   bool using_boot_for_vbmeta = false;
   AvbVBMetaImageHeader toplevel_vbmeta;
+  uint8_t* toplevel_vbmeta_public_key_data = NULL;
+  size_t toplevel_vbmeta_public_key_length = 0;
   bool allow_verification_error =
       (flags & AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR);
   AvbCmdlineSubstList* additional_cmdline_subst = NULL;
@@ -1483,21 +1507,24 @@
 
     /* No vbmeta partition, go through each of the requested partitions... */
     for (size_t n = 0; requested_partitions[n] != NULL; n++) {
-      ret = load_and_verify_vbmeta(ops,
-                                   requested_partitions,
-                                   ab_suffix,
-                                   flags,
-                                   allow_verification_error,
-                                   0 /* toplevel_vbmeta_flags */,
-                                   0 /* rollback_index_location */,
-                                   requested_partitions[n],
-                                   avb_strlen(requested_partitions[n]),
-                                   NULL /* expected_public_key */,
-                                   0 /* expected_public_key_length */,
-                                   slot_data,
-                                   &algorithm_type,
-                                   additional_cmdline_subst,
-                                   true /*use_ab_suffix*/);
+      ret = load_and_verify_vbmeta(
+          ops,
+          requested_partitions,
+          ab_suffix,
+          flags,
+          allow_verification_error,
+          0 /* toplevel_vbmeta_flags */,
+          0 /* rollback_index_location */,
+          requested_partitions[n],
+          avb_strlen(requested_partitions[n]),
+          NULL /* expected_public_key */,
+          0 /* expected_public_key_length */,
+          slot_data,
+          &algorithm_type,
+          NULL /* out_toplevel_vbmeta_public_key_data */,
+          NULL /* out_toplevel_vbmeta_public_key_length */,
+          additional_cmdline_subst,
+          true /*use_ab_suffix*/);
       if (!allow_verification_error && ret != AVB_SLOT_VERIFY_RESULT_OK) {
         goto fail;
       }
@@ -1518,6 +1545,8 @@
                                  0 /* expected_public_key_length */,
                                  slot_data,
                                  &algorithm_type,
+                                 &toplevel_vbmeta_public_key_data,
+                                 &toplevel_vbmeta_public_key_length,
                                  additional_cmdline_subst,
                                  true /*use_ab_suffix*/);
     if (!allow_verification_error && ret != AVB_SLOT_VERIFY_RESULT_OK) {
@@ -1598,6 +1627,8 @@
                                  flags,
                                  slot_data,
                                  &toplevel_vbmeta,
+                                 toplevel_vbmeta_public_key_data,
+                                 toplevel_vbmeta_public_key_length,
                                  algorithm_type,
                                  hashtree_error_mode,
                                  resolved_hashtree_error_mode);
@@ -1631,6 +1662,10 @@
     avb_slot_verify_data_free(slot_data);
   }
 
+  if (toplevel_vbmeta_public_key_data != NULL) {
+    avb_free(toplevel_vbmeta_public_key_data);
+  }
+
   avb_free_cmdline_subst_list(additional_cmdline_subst);
   additional_cmdline_subst = NULL;
 
@@ -1644,6 +1679,9 @@
   if (slot_data != NULL) {
     avb_slot_verify_data_free(slot_data);
   }
+  if (toplevel_vbmeta_public_key_data != NULL) {
+    avb_free(toplevel_vbmeta_public_key_data);
+  }
   if (additional_cmdline_subst != NULL) {
     avb_free_cmdline_subst_list(additional_cmdline_subst);
   }
diff --git a/libavb/avb_slot_verify.h b/libavb/avb_slot_verify.h
index 8702c21..b24a11e 100644
--- a/libavb/avb_slot_verify.h
+++ b/libavb/avb_slot_verify.h
@@ -253,6 +253,11 @@
  *   androidboot.vbmeta.{hash_alg, size, digest}: Will be set to
  *   the digest of all images in |vbmeta_images|.
  *
+ *   androidboot.vbmeta.public_key_digest: Will be set to the SHA-256
+ *   digest of the public key used to verify the vbmeta partition (or
+ *   boot partition if there is no vbmeta partition). If the flag
+ *   AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION is used, this is not set.
+ *
  *   androidboot.vbmeta.device: This is set to the value
  *   PARTUUID=$(ANDROID_VBMETA_PARTUUID) before substitution so it
  *   will end up pointing to the vbmeta partition for the verified
diff --git a/test/avb_slot_verify_unittest.cc b/test/avb_slot_verify_unittest.cc
index 76af86a..25ab1eb 100644
--- a/test/avb_slot_verify_unittest.cc
+++ b/test/avb_slot_verify_unittest.cc
@@ -53,6 +53,14 @@
                             bool has_system_partition);
 };
 
+// This digest appears in all tests that check the kernel commandline options
+// and use the key in "test/data/testkey_rsa2048.pem", so we check that the
+// digest is correct in a standalone test to avoid repetition.
+TEST_F(AvbSlotVerifyTest, Rsa2048TestKey) {
+  EXPECT_EQ("22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c",
+            PublicKeyAVBDigest("test/data/testkey_rsa2048.pem"));
+}
+
 TEST_F(AvbSlotVerifyTest, Basic) {
   GenerateVBMetaImage("vbmeta_a.img",
                       "SHA256_RSA2048",
@@ -74,6 +82,8 @@
   EXPECT_NE(nullptr, slot_data);
   EXPECT_EQ(
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1152 "
@@ -114,6 +124,8 @@
   EXPECT_NE(nullptr, slot_data);
   EXPECT_EQ(
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha512 androidboot.vbmeta.size=1152 "
@@ -161,6 +173,8 @@
   EXPECT_NE(nullptr, slot_data);
   EXPECT_EQ(
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=unlocked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1152 "
@@ -724,6 +738,8 @@
       "cmdline in vbmeta 1234-fake-guid-for:boot_a cmdline in hash footer "
       "1234-fake-guid-for:system_a "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1472 "
@@ -1105,6 +1121,8 @@
   EXPECT_EQ(
       "cmdline2 in hash footer cmdline2 in vbmeta "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=4416 "
@@ -1576,6 +1594,8 @@
   EXPECT_EQ(
       "cmdline2 in hash footer cmdline2 in vbmeta "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=4416 "
@@ -1922,6 +1942,8 @@
   EXPECT_EQ(
       "cmdline2 in hash footer cmdline2 in vbmeta "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=4416 "
@@ -2214,7 +2236,8 @@
   EXPECT_EQ(size_t(2), slot_data->num_loaded_partitions);
   EXPECT_EQ("foo", std::string(slot_data->loaded_partitions[0].partition_name));
   EXPECT_EQ("bar", std::string(slot_data->loaded_partitions[1].partition_name));
-  // Note the absence of 'androidboot.vbmeta.device'
+  // Note the absence of 'androidboot.vbmeta.device' and
+  // 'androidboot.vbmeta.public_key_digest'.
   EXPECT_EQ(
       "this is=5 from foo=42 and=43 from bar "
       "androidboot.vbmeta.avb_version=1.3 "
@@ -2280,6 +2303,8 @@
   EXPECT_NE(nullptr, slot_data);
   EXPECT_EQ(
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=2688 "
@@ -2389,6 +2414,8 @@
         "restart_on_corruption ignore_zero_blocks\" root=/dev/dm-0 "
         "should_be_in_both=1 "
         "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+        "androidboot.vbmeta.public_key_digest="
+        "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
         "androidboot.vbmeta.avb_version=1.3 "
         "androidboot.vbmeta.device_state=locked "
         "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1536 "
@@ -2403,6 +2430,8 @@
     EXPECT_EQ(
         "root=PARTUUID=1234-fake-guid-for:system_a should_be_in_both=1 "
         "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+        "androidboot.vbmeta.public_key_digest="
+        "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
         "androidboot.vbmeta.avb_version=1.3 "
         "androidboot.vbmeta.device_state=locked "
         "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1536 "
@@ -2570,6 +2599,8 @@
         "restart_on_corruption ignore_zero_blocks\" root=/dev/dm-0 "
         "should_be_in_both=1 "
         "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+        "androidboot.vbmeta.public_key_digest="
+        "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
         "androidboot.vbmeta.avb_version=1.3 "
         "androidboot.vbmeta.device_state=locked "
         "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=3456 "
@@ -2584,6 +2615,8 @@
     EXPECT_EQ(
         "root=PARTUUID=1234-fake-guid-for:system_a should_be_in_both=1 "
         "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+        "androidboot.vbmeta.public_key_digest="
+        "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
         "androidboot.vbmeta.avb_version=1.3 "
         "androidboot.vbmeta.device_state=locked "
         "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=3456 "
@@ -2975,6 +3008,8 @@
       "4096 4096 4096 4096 sha1 c9ffc3bfae5000269a55a56621547fd1fcf819df "
       "d00df00d 2 restart_on_corruption ignore_zero_blocks\" root=/dev/dm-0 "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:boot "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=5312 "
@@ -3144,6 +3179,8 @@
       "c9ffc3bfae5000269a55a56621547fd1fcf819df d00df00d 2 "
       "restart_on_corruption ignore_zero_blocks\" root=/dev/dm-0 "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 "
@@ -3174,6 +3211,8 @@
       "c9ffc3bfae5000269a55a56621547fd1fcf819df d00df00d 2 "
       "restart_on_corruption ignore_zero_blocks\" root=/dev/dm-0 "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 "
@@ -3203,6 +3242,8 @@
       "c9ffc3bfae5000269a55a56621547fd1fcf819df d00df00d 2 "
       "ignore_zero_blocks ignore_zero_blocks\" root=/dev/dm-0 "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 "
@@ -3244,6 +3285,8 @@
       "c9ffc3bfae5000269a55a56621547fd1fcf819df d00df00d 2 "
       "ignore_corruption ignore_zero_blocks\" root=/dev/dm-0 "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 "
@@ -3284,6 +3327,8 @@
     EXPECT_EQ(
         "root=PARTUUID=1234-fake-guid-for:system "
         "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
+        "androidboot.vbmeta.public_key_digest="
+        "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
         "androidboot.vbmeta.avb_version=1.3 "
         "androidboot.vbmeta.device_state=locked "
         "androidboot.vbmeta.hash_alg=sha256 "
@@ -3474,6 +3519,8 @@
   Verify(true /* expect_success */);
   EXPECT_EQ(
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 "
@@ -3679,6 +3726,8 @@
       // Note: Here appear the bytes used in write_persistent_value above.
       "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 "
@@ -3706,6 +3755,8 @@
       // Note: Here appear the bytes used in write_persistent_value above.
       "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 "
@@ -3737,6 +3788,8 @@
       "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
       "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 "
@@ -4043,6 +4096,8 @@
   EXPECT_NE(nullptr, slot_data);
   EXPECT_EQ(
       "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.vbmeta.public_key_digest="
+      "22de3994532196f61c039e90260d78a93a4c57362c7e789be928036e80b77c8c "
       "androidboot.vbmeta.avb_version=1.3 "
       "androidboot.vbmeta.device_state=locked "
       "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1152 "
diff --git a/test/avb_unittest_util.cc b/test/avb_unittest_util.cc
index fa8de93..3e5f659 100644
--- a/test/avb_unittest_util.cc
+++ b/test/avb_unittest_util.cc
@@ -167,4 +167,16 @@
   return key_data;
 }
 
+std::string BaseAvbToolTest::PublicKeyAVBDigest(const std::string& key_path) {
+  std::filesystem::path tmp_path = testdir_ / "public_key_digest";
+  EXPECT_COMMAND(0,
+                 "./avbtool.py extract_public_key_digest --key %s"
+                 " --output %s",
+                 key_path.c_str(),
+                 tmp_path.c_str());
+  std::string digest_data;
+  EXPECT_TRUE(android::base::ReadFileToString(tmp_path.string(), &digest_data));
+  return digest_data;
+}
+
 }  // namespace avb
diff --git a/test/avb_unittest_util.h b/test/avb_unittest_util.h
index 781877a..3726acd 100644
--- a/test/avb_unittest_util.h
+++ b/test/avb_unittest_util.h
@@ -93,6 +93,11 @@
   /* Returns public key in AVB format for a .pem key */
   std::string PublicKeyAVB(const std::string& key_path);
 
+  /* Returns a hex string containing the SHA-256 digest of the public key in AVB
+   * format for a .pem key.
+   */
+  std::string PublicKeyAVBDigest(const std::string& key_path);
+
   void SetUp() override;
   void TearDown() override;