Parse APEX manifest

Modify verify to return the APEX name and version from its manifest
protobuf.

Refactor error handling a bit, improving the messages.

Remove the separate get_payload_vbmeta_image_hash function, merging it
into verify. (It isn't used yet, but it might be.)

Improve the tests. Also remove host_supported - we don't need it (the
reason for adding it no longer applies), and the dependencies now make
it tricky.

Bug: 308759880
Test: atest MicrodroidTests
Test: atest libapexutil_rust.test
Test: composd_cmd test-compile
Change-Id: Idd38001ab28a4a0f27b42f361b18bbab0e4aad84
diff --git a/libs/apexutil/Android.bp b/libs/apexutil/Android.bp
index f9b72c4..d248d5e 100644
--- a/libs/apexutil/Android.bp
+++ b/libs/apexutil/Android.bp
@@ -9,7 +9,9 @@
     srcs: ["src/lib.rs"],
     edition: "2021",
     rustlibs: [
+        "libapex_manifest_rs",
         "liblog_rust",
+        "libprotobuf",
         "libthiserror",
         "libvbmeta_rust",
         "libzip",
diff --git a/libs/apexutil/src/lib.rs b/libs/apexutil/src/lib.rs
index 8a934e2..da3bb45 100644
--- a/libs/apexutil/src/lib.rs
+++ b/libs/apexutil/src/lib.rs
@@ -14,6 +14,8 @@
 
 //! Routines for handling APEX payload
 
+use apex_manifest::apex_manifest::ApexManifest;
+use protobuf::Message;
 use std::fs::File;
 use std::io::{self, Read};
 use thiserror::Error;
@@ -23,28 +25,32 @@
 
 const APEX_PUBKEY_ENTRY: &str = "apex_pubkey";
 const APEX_PAYLOAD_ENTRY: &str = "apex_payload.img";
+const APEX_MANIFEST_ENTRY: &str = "apex_manifest.pb";
 
 /// Errors from parsing an APEX.
 #[derive(Debug, Error)]
 pub enum ApexParseError {
     /// There was an IO error.
-    #[error("IO error")]
+    #[error("IO error: {0}")]
     Io(#[from] io::Error),
     /// The Zip archive was invalid.
-    #[error("Cannot read zip archive")]
+    #[error("Cannot read zip archive: {0}")]
     InvalidZip(&'static str),
-    /// The apex_pubkey file was missing from the APEX.
-    #[error("APEX doesn't contain apex_pubkey")]
-    PubkeyMissing,
-    /// The apex_payload.img file was missing from the APEX.
-    #[error("APEX doesn't contain apex_payload.img")]
-    PayloadMissing,
+    /// An expected file was missing from the APEX.
+    #[error("APEX doesn't contain {0}")]
+    MissingFile(&'static str),
     /// There was no hashtree descriptor in the APEX payload's VBMeta image.
     #[error("Non-hashtree descriptor found in payload's VBMeta image")]
     DescriptorNotHashtree,
     /// There was an error parsing the APEX payload's VBMeta image.
-    #[error("Could not parse payload's VBMeta image")]
+    #[error("Could not parse payload's VBMeta image: {0}")]
     PayloadVbmetaError(#[from] vbmeta::VbMetaImageParseError),
+    /// Data was missing from the VBMeta
+    #[error("Data missing from VBMeta: {0}")]
+    VbmetaMissingData(&'static str),
+    /// An error occurred parsing the APEX manifest as a protobuf
+    #[error("Error parsing manifest protobuf: {0}")]
+    ManifestProtobufError(#[from] protobuf::Error),
 }
 
 /// Errors from verifying an APEX.
@@ -58,29 +64,42 @@
     PayloadVbmetaError(#[from] vbmeta::VbMetaImageVerificationError),
     /// The APEX payload was not verified with the apex_pubkey.
     #[error("APEX pubkey mismatch")]
-    ApexPubkeyMistmatch,
+    ApexPubkeyMismatch,
 }
 
-/// Verification result holds public key and root digest of apex_payload.img
+/// Information extracted from the APEX during verification.
 pub struct ApexVerificationResult {
+    /// The name of the APEX, from its manifest. Unverified, but apexd will reject
+    /// an APEX where the unsigned manifest isn't the same as the signed one.
+    pub name: String,
+    /// The version of the APEX, from its manifest. Unverified, but apexd will reject
+    /// an APEX where the unsigned manifest isn't the same as the signed one.
+    pub version: i64,
     /// The public key that verifies the payload signature.
     pub public_key: Vec<u8>,
     /// The root digest of the payload hashtree.
     pub root_digest: Vec<u8>,
+    /// The hash of the verified VBMeta image data.
+    /// TODO(alanstokes): Delete this if we don't have a use for it.
+    pub image_hash: Vec<u8>,
 }
 
-/// Verify APEX payload by AVB verification and return public key and root digest
+/// Verify APEX payload by AVB verification and return information about the APEX.
 pub fn verify(path: &str) -> Result<ApexVerificationResult, ApexVerificationError> {
     let apex_file = File::open(path).map_err(ApexParseError::Io)?;
-    let (public_key, image_offset, image_size) = get_public_key_and_image_info(&apex_file)?;
+    let ApexZipInfo { public_key, image_offset, image_size, manifest } =
+        get_apex_zip_info(&apex_file)?;
     let vbmeta = VbMetaImage::verify_reader_region(apex_file, image_offset, image_size)?;
     let root_digest = find_root_digest(&vbmeta)?;
-    match vbmeta.public_key() {
-        Some(payload_public_key) if public_key == payload_public_key => {
-            Ok(ApexVerificationResult { public_key, root_digest })
-        }
-        _ => Err(ApexVerificationError::ApexPubkeyMistmatch),
+    let vbmeta_public_key =
+        vbmeta.public_key().ok_or(ApexParseError::VbmetaMissingData("public key"))?;
+    if vbmeta_public_key != public_key {
+        return Err(ApexVerificationError::ApexPubkeyMismatch);
     }
+    let image_hash = vbmeta.hash().ok_or(ApexParseError::VbmetaMissingData("hash"))?.to_vec();
+    let ApexManifestInfo { name, version } = decode_manifest(&manifest)?;
+
+    Ok(ApexVerificationResult { name, version, public_key, root_digest, image_hash })
 }
 
 fn find_root_digest(vbmeta: &VbMetaImage) -> Result<Vec<u8>, ApexParseError> {
@@ -93,46 +112,52 @@
     Err(ApexParseError::DescriptorNotHashtree)
 }
 
-/// Gets the hash of the payload's verified VBMeta image data.
-pub fn get_payload_vbmeta_image_hash(path: &str) -> Result<Vec<u8>, ApexVerificationError> {
-    let apex_file = File::open(path).map_err(ApexParseError::Io)?;
-    let (_, offset, size) = get_public_key_and_image_info(&apex_file)?;
-    let vbmeta = VbMetaImage::verify_reader_region(apex_file, offset, size)?;
-    Ok(vbmeta.hash().ok_or(ApexVerificationError::ApexPubkeyMistmatch)?.to_vec())
+struct ApexZipInfo {
+    public_key: Vec<u8>,
+    image_offset: u64,
+    image_size: u64,
+    manifest: Vec<u8>,
 }
 
-fn get_public_key_and_image_info(apex_file: &File) -> Result<(Vec<u8>, u64, u64), ApexParseError> {
-    let mut z = ZipArchive::new(apex_file).map_err(|err| match err {
-        ZipError::Io(err) => ApexParseError::Io(err),
-        ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
-            ApexParseError::InvalidZip(s)
-        }
-        ZipError::FileNotFound => unreachable!(),
-    })?;
+fn get_apex_zip_info(apex_file: &File) -> Result<ApexZipInfo, ApexParseError> {
+    let mut z = ZipArchive::new(apex_file).map_err(|err| from_zip_error(err, "?"))?;
 
     let mut public_key = Vec::new();
     z.by_name(APEX_PUBKEY_ENTRY)
-        .map_err(|err| match err {
-            ZipError::Io(err) => ApexParseError::Io(err),
-            ZipError::FileNotFound => ApexParseError::PubkeyMissing,
-            ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
-                ApexParseError::InvalidZip(s)
-            }
-        })?
+        .map_err(|err| from_zip_error(err, APEX_PUBKEY_ENTRY))?
         .read_to_end(&mut public_key)?;
 
     let (image_offset, image_size) = z
         .by_name(APEX_PAYLOAD_ENTRY)
         .map(|f| (f.data_start(), f.size()))
-        .map_err(|err| match err {
-            ZipError::Io(err) => ApexParseError::Io(err),
-            ZipError::FileNotFound => ApexParseError::PayloadMissing,
-            ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
-                ApexParseError::InvalidZip(s)
-            }
-        })?;
+        .map_err(|err| from_zip_error(err, APEX_PAYLOAD_ENTRY))?;
 
-    Ok((public_key, image_offset, image_size))
+    let mut manifest = Vec::new();
+    z.by_name(APEX_MANIFEST_ENTRY)
+        .map_err(|err| from_zip_error(err, APEX_MANIFEST_ENTRY))?
+        .read_to_end(&mut manifest)?;
+
+    Ok(ApexZipInfo { public_key, image_offset, image_size, manifest })
+}
+
+struct ApexManifestInfo {
+    name: String,
+    version: i64,
+}
+
+fn decode_manifest(mut manifest: &[u8]) -> Result<ApexManifestInfo, ApexParseError> {
+    let manifest = ApexManifest::parse_from_reader(&mut manifest)?;
+    Ok(ApexManifestInfo { name: manifest.name, version: manifest.version })
+}
+
+fn from_zip_error(err: ZipError, name: &'static str) -> ApexParseError {
+    match err {
+        ZipError::Io(err) => ApexParseError::Io(err),
+        ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
+            ApexParseError::InvalidZip(s)
+        }
+        ZipError::FileNotFound => ApexParseError::MissingFile(name),
+    }
 }
 
 #[cfg(test)]
@@ -143,17 +168,47 @@
     fn apex_verification_returns_valid_result() {
         let res = verify("tests/data/test.apex").unwrap();
         // The expected hex is generated when we ran the method the first time.
+        assert_eq!(res.name, "com.android.apex.cts.shim");
+        assert_eq!(res.version, 1);
+        assert_eq!(
+            hex::encode(res.public_key),
+            "000010007093e081b637735012c7f2\
+            fdacba9b1c01ee2eaa78e7fb69fa810a0e3fff8d70cd69c60eb6458c54c56c6a40e2f68\
+            60c69343c1373eb1785c4e81eca1c8921390da5997115668ef4c5f5cb90d74df3fc29dd\
+            d05d45e298761beba76276669540d5cfe9c79ed1e001637871db4f0d0083d56332fe328\
+            9f1f3aec8d00b06a7db25104d5a05226ab499cd6536434ff8f1d01ca1c653a91d58ee41\
+            a848571abf9ba555610a1dc3555911386f07109c3c9e420a17b8f63c58c74410a94cedd\
+            2e3e8203f4e638b620836742331049c96423c01fbe2609962e35d38d127730692f7e947\
+            80bb21017b4583c9fb59e9f7421a92cff4d4dd6095d5aca2f5f13b9c5320ff0f3fc84bb\
+            b347bbe7fc08b6081d2157bddae97845e2c58da58d9d56732dd90d5b59116db404b859c\
+            b68b4c51790d06337bf939201f5ab356a50242d7e50e29f53f0525ab1693d6b1db1acbd\
+            540dd8eb310accee7b3938b471a1768163c226a44483e0e4453cde393f5495bfe10297d\
+            68f1bfed44b386c5c2ecde221607635ef14aadcba153f1f916d7c1fc92fab1b04f964f8\
+            5660033024084d5b27760e61967c9df5e2a099bdc63e3c3864b15fd3caa85274ab7b02d\
+            8933c2a5e4460adbf95aae0774945e9a5c0abb15f2d533259cb090ea5be513572bd75cc\
+            5eaf23fe4f5dbe4b8fee525059ae0d8c7813704f2b9fc641525075d2ce6e44bd0955c10\
+            f8383f87b0d3a07f524893b78bb67d5428cfa430e863f121c1de0205d3dd64f3a78c5d8\
+            e802dfaa078f07c4626c4a280224816958a1e621d05184214f675a7cd1c55b6c5a2b18e\
+            358f84c4b1068b8d2aace966c47674204ada4b5376b55fd9c145b1224ddb4f578f6279d\
+            d92a381f3a11235be8331ce15754374426a35aa6f17586f1658d48f30c3220fec43b3d5\
+            ca7ed0f8de14225b19ab699fb75c95299b8f81559fe41df31e4d591692d86482c50c3ba\
+            ccfeb002ead775eca116b5674bd8f4f2c5db54bd21596d980f2067e331bc0e30a56c25f\
+            6fd7d5f2a03651198f0c7494add16889dbb49cc79038fd8bc2e7540c3101e5cbbb1f8d6\
+            f0eab86f83eb76ef5d6df29f0c718019c26f8e38d86a54f2b992a17a0c9e00a298e866d\
+            53e2ff78f35de1ccdf166375309397a43b74cf7a34a647a3ee0234dbf4744c6db5f44f7\
+            1a366d87024ec3a5ec4185ac7cc0342460160632f21b791e12b656c71c248cce5fbb45f\
+            3c624852ea9c29264c6b8ad58ac36bf99cf5254d1e69c628bdf1707136475230bbedf1f\
+            ac25849b249795456d5d99214800e44a6d71c460eb495d9926145606d7cbb986044c9f0\
+            11b6d6be5c79f89a6f90ad39676489eac632b105cbf3da29bf7e4e72bf82600bcafc867\
+            f4cb6e0ade8f532d9620b82001c69493ff5679cf0393285aa67b3e4382c8e785e43efe9\
+            7e56fbd24357eec0b19697194f0b91ee46ab82dfeea788"
+        );
         assert_eq!(
             hex::encode(res.root_digest),
             "fe11ab17da0a3a738b54bdc3a13f6139cbdf91ec32f001f8d4bbbf8938e04e39"
         );
-    }
-
-    #[test]
-    fn payload_vbmeta_has_valid_image_hash() {
-        let result = get_payload_vbmeta_image_hash("tests/data/test.apex").unwrap();
         assert_eq!(
-            hex::encode(result),
+            hex::encode(res.image_hash),
             "296e32a76544de9da01713e471403ab4667705ad527bb4f1fac0cf61e7ce122d"
         );
     }
diff --git a/microdroid_manager/src/payload.rs b/microdroid_manager/src/payload.rs
index a553ce4..87f690b 100644
--- a/microdroid_manager/src/payload.rs
+++ b/microdroid_manager/src/payload.rs
@@ -17,7 +17,6 @@
 use crate::instance::ApexData;
 use crate::ioutil::wait_for_file;
 use anyhow::Result;
-use apexutil::verify;
 use log::info;
 use microdroid_metadata::{read_metadata, ApexPayload, Metadata};
 use std::time::Duration;
@@ -40,7 +39,7 @@
         .map(|apex| {
             let name = apex.name.clone();
             let apex_path = format!("/dev/block/by-name/{}", apex.partition_name);
-            let result = verify(&apex_path)?;
+            let result = apexutil::verify(&apex_path)?;
             Ok(ApexData {
                 name,
                 public_key: result.public_key,