Add guest OS capability: SecretkeeperProtection

Not all guest OS are capable of interacting with Secretkeeper. Add a
capability "secretkeeper_protection" (which can be extracted from vbmeta
property "com.android.virt.cap"). Add this property to Microdroid
kernel.

pvmfw will have check if the guest OS has this capability & ensures the
rollback_index > 0 if the guest OS has it. Note that this will be
factored in while pvmfw check if updated guest OS should be accepted.

Bug: 291213374
Test: avbtool.py --info microdroid_kernel & check if property is
present.
Test: #payload_with_multiple_capabilities

Change-Id: I99c159d3d65005ec02729b47620ac05ab8d1ec5e
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index bac93a4..b494cfa 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -418,6 +418,7 @@
     ],
     properties: [
         "rollback_index",
+        "props",
     ],
 }
 
@@ -447,6 +448,12 @@
     soong_config_variables: {
         release_avf_enable_llpvm_changes: {
             rollback_index: 1,
+            props: [
+                {
+                    name: "com.android.virt.cap",
+                    value: "secretkeeper_protection",
+                },
+            ],
         },
     },
 }
@@ -487,6 +494,12 @@
     soong_config_variables: {
         release_avf_enable_llpvm_changes: {
             rollback_index: 1,
+            props: [
+                {
+                    name: "com.android.virt.cap",
+                    value: "secretkeeper_protection",
+                },
+            ],
         },
     },
 }
diff --git a/pvmfw/avb/Android.bp b/pvmfw/avb/Android.bp
index f7362d8..6df1c4d 100644
--- a/pvmfw/avb/Android.bp
+++ b/pvmfw/avb/Android.bp
@@ -43,6 +43,7 @@
         ":test_image_with_multiple_props",
         ":test_image_with_duplicated_capability",
         ":test_image_with_rollback_index_5",
+        ":test_image_with_multiple_capabilities",
         ":unsigned_test_image",
     ],
     prefer_rlib: true,
@@ -204,3 +205,17 @@
     salt: "1211",
     rollback_index: 5,
 }
+
+avb_add_hash_footer {
+    name: "test_image_with_multiple_capabilities",
+    src: ":unsigned_test_image",
+    partition_name: "boot",
+    private_key: ":pvmfw_sign_key",
+    salt: "2134",
+    props: [
+        {
+            name: "com.android.virt.cap",
+            value: "remote_attest|secretkeeper_protection",
+        },
+    ],
+}
diff --git a/pvmfw/avb/src/verify.rs b/pvmfw/avb/src/verify.rs
index 1a16f9d..492d387 100644
--- a/pvmfw/avb/src/verify.rs
+++ b/pvmfw/avb/src/verify.rs
@@ -43,6 +43,13 @@
     pub rollback_index: u64,
 }
 
+impl VerifiedBootData<'_> {
+    /// Returns whether the kernel have the given capability
+    pub fn has_capability(&self, cap: Capability) -> bool {
+        self.capabilities.contains(&cap)
+    }
+}
+
 /// This enum corresponds to the `DebugLevel` in `VirtualMachineConfig`.
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum DebugLevel {
@@ -53,15 +60,18 @@
 }
 
 /// VM Capability.
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub enum Capability {
     /// Remote attestation.
     RemoteAttest,
+    /// Secretkeeper protected secrets.
+    SecretkeeperProtection,
 }
 
 impl Capability {
     const KEY: &[u8] = b"com.android.virt.cap";
     const REMOTE_ATTEST: &[u8] = b"remote_attest";
+    const SECRETKEEPER_PROTECTION: &[u8] = b"secretkeeper_protection";
     const SEPARATOR: u8 = b'|';
 
     fn get_capabilities(property_value: &[u8]) -> Result<Vec<Self>, PvmfwVerifyError> {
@@ -70,6 +80,7 @@
         for v in property_value.split(|b| *b == Self::SEPARATOR) {
             let cap = match v {
                 Self::REMOTE_ATTEST => Self::RemoteAttest,
+                Self::SECRETKEEPER_PROTECTION => Self::SecretkeeperProtection,
                 _ => return Err(PvmfwVerifyError::UnknownVbmetaProperty),
             };
             if res.contains(&cap) {
diff --git a/pvmfw/avb/tests/api_test.rs b/pvmfw/avb/tests/api_test.rs
index 46f5228..6344433 100644
--- a/pvmfw/avb/tests/api_test.rs
+++ b/pvmfw/avb/tests/api_test.rs
@@ -32,6 +32,7 @@
 const TEST_IMG_WITH_NON_INITRD_HASHDESC_PATH: &str = "test_image_with_non_initrd_hashdesc.img";
 const TEST_IMG_WITH_INITRD_AND_NON_INITRD_DESC_PATH: &str =
     "test_image_with_initrd_and_non_initrd_desc.img";
+const TEST_IMG_WITH_MULTIPLE_CAPABILITIES: &str = "test_image_with_multiple_capabilities.img";
 const UNSIGNED_TEST_IMG_PATH: &str = "unsigned_test.img";
 
 const RANDOM_FOOTER_POS: usize = 30;
@@ -409,3 +410,18 @@
     assert_eq!(expected_boot_data, verified_boot_data);
     Ok(())
 }
+
+#[test]
+fn payload_with_multiple_capabilities() -> Result<()> {
+    let public_key = load_trusted_public_key()?;
+    let verified_boot_data = verify_payload(
+        &fs::read(TEST_IMG_WITH_MULTIPLE_CAPABILITIES)?,
+        /* initrd= */ None,
+        &public_key,
+    )
+    .map_err(|e| anyhow!("Verification failed. Error: {}", e))?;
+
+    assert!(verified_boot_data.has_capability(Capability::RemoteAttest));
+    assert!(verified_boot_data.has_capability(Capability::SecretkeeperProtection));
+    Ok(())
+}
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index d39d51c..b8cbf1b 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -112,10 +112,22 @@
         info!("Please disregard any previous libavb ERROR about initrd_normal.");
     }
 
-    if verified_boot_data.capabilities.contains(&Capability::RemoteAttest) {
+    if verified_boot_data.has_capability(Capability::RemoteAttest) {
         info!("Service VM capable of remote attestation detected");
     }
 
+    if verified_boot_data.has_capability(Capability::SecretkeeperProtection) {
+        info!("Guest OS is capable of Secretkeeper protection");
+        // For Secretkeeper based Antirollback protection, rollback_index of the image > 0
+        if verified_boot_data.rollback_index == 0 {
+            error!(
+                "Expected positive rollback_index, found {:?}",
+                verified_boot_data.rollback_index
+            );
+            return Err(RebootReason::InvalidPayload);
+        };
+    }
+
     let next_bcc = heap::aligned_boxed_slice(NEXT_BCC_SIZE, GUEST_PAGE_SIZE).ok_or_else(|| {
         error!("Failed to allocate the next-stage BCC");
         RebootReason::InternalError