Parse APK manifest to extract version etc

Add libapkmanifest to encapsulate the parsing, with a small C++
library on top of libandroidfw.

Extract the zip-handling code from libapkverify into a separate
libapkzip, and fix up a bunch of tests tests, to keep the build happy.

We don't do anything with the manifest information except log it; more
to come in another CL.

Bug: 299591171
Test: atest libapkzip.test libapkverify.integration_test
      libapkverify.test
Test: Manual - run VM, inspect logs.
Change-Id: I56d3bb7309d43ecb598a33320705d31948710f83
diff --git a/TEST_MAPPING b/TEST_MAPPING
index f5d2dda..f1bfe09 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -31,6 +31,9 @@
     },
     {
       "name": "libdice_policy.test"
+    },
+    {
+      "name": "libapkzip.test"
     }
   ],
   "avf-postsubmit": [
diff --git a/libs/apkmanifest/Android.bp b/libs/apkmanifest/Android.bp
new file mode 100644
index 0000000..e6fcbef
--- /dev/null
+++ b/libs/apkmanifest/Android.bp
@@ -0,0 +1,46 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_shared {
+    name: "libapkmanifest_native",
+    srcs: ["native/*.cpp"],
+    shared_libs: [
+        "libandroidfw",
+        "libbase",
+        "liblog",
+        "libutils",
+    ],
+}
+
+rust_bindgen {
+    name: "libapkmanifest_bindgen",
+    defaults: ["avf_build_flags_rust"],
+    edition: "2021",
+    wrapper_src: "native/apkmanifest.hpp",
+    crate_name: "apkmanifest_bindgen",
+    source_stem: "bindings",
+    bindgen_flags: [
+        "--default-enum-style rust",
+    ],
+}
+
+rust_library {
+    name: "libapkmanifest",
+    crate_name: "apkmanifest",
+    defaults: ["avf_build_flags_rust"],
+    edition: "2021",
+    srcs: ["src/apkmanifest.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libapkzip",
+        "libapkmanifest_bindgen",
+        "libscopeguard",
+    ],
+    shared_libs: ["libapkmanifest_native"],
+    multilib: {
+        lib32: {
+            enabled: false,
+        },
+    },
+}
diff --git a/libs/apkmanifest/native/apkmanifest.cpp b/libs/apkmanifest/native/apkmanifest.cpp
new file mode 100644
index 0000000..ab0ba72
--- /dev/null
+++ b/libs/apkmanifest/native/apkmanifest.cpp
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apkmanifest.hpp"
+
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <androidfw/AssetsProvider.h>
+#include <androidfw/ResourceTypes.h>
+#include <androidfw/StringPiece.h>
+#include <androidfw/Util.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <utils/Errors.h>
+
+#include <cstdlib>
+#include <limits>
+#include <string>
+#include <string_view>
+
+using android::Asset;
+using android::AssetsProvider;
+using android::OK;
+using android::Res_value;
+using android::ResXMLParser;
+using android::ResXMLTree;
+using android::statusToString;
+using android::StringPiece16;
+using android::base::Error;
+using android::base::Result;
+using android::util::Utf16ToUtf8;
+using std::u16string_view;
+using std::unique_ptr;
+
+struct ApkManifestInfo {
+    std::string package;
+    uint32_t version_code;
+    uint32_t version_code_major;
+};
+
+namespace {
+// See https://developer.android.com/guide/topics/manifest/manifest-element
+constexpr u16string_view MANIFEST_TAG_NAME{u"manifest"};
+constexpr u16string_view ANDROID_NAMESPACE_URL{u"http://schemas.android.com/apk/res/android"};
+constexpr u16string_view PACKAGE_ATTRIBUTE_NAME{u"package"};
+constexpr u16string_view VERSION_CODE_ATTRIBUTE_NAME{u"versionCode"};
+constexpr u16string_view VERSION_CODE_MAJOR_ATTRIBUTE_NAME{u"versionCodeMajor"};
+
+// Read through the XML parse tree up to the <manifest> element.
+Result<void> findManifestElement(ResXMLTree& tree) {
+    for (;;) {
+        ResXMLParser::event_code_t event = tree.next();
+        switch (event) {
+            case ResXMLParser::END_DOCUMENT:
+            case ResXMLParser::END_TAG:
+            case ResXMLParser::TEXT:
+            default:
+                return Error() << "Unexpected XML parsing event: " << event;
+            case ResXMLParser::BAD_DOCUMENT:
+                return Error() << "Failed to parse XML: " << statusToString(tree.getError());
+            case ResXMLParser::START_NAMESPACE:
+            case ResXMLParser::END_NAMESPACE:
+                // Not of interest, keep going.
+                break;
+            case ResXMLParser::START_TAG:
+                // The first tag in an AndroidManifest.xml should be <manifest> (no namespace).
+                // And that's actually the only tag we care about.
+                if (tree.getElementNamespaceID() >= 0) {
+                    return Error() << "Root element has unexpected namespace.";
+                }
+                size_t nameLength = 0;
+                const char16_t* nameChars = tree.getElementName(&nameLength);
+                if (!nameChars) {
+                    return Error() << "Missing tag name";
+                }
+                if (u16string_view(nameChars, nameLength) != MANIFEST_TAG_NAME) {
+                    return Error() << "Expected <manifest> as root element";
+                }
+                return {};
+        }
+    }
+}
+
+// Return an attribute encoded as a string, converted to UTF-8. Note that all
+// attributes are strings in the original XML, but the binary format encodes
+// some as binary numbers etc. This function does not handle converting those
+// encodings back to strings, so should only be used when it is known that a
+// numeric value is not allowed.
+Result<std::string> getStringOnlyAttribute(const ResXMLTree& tree, size_t index) {
+    size_t len;
+    const char16_t* value = tree.getAttributeStringValue(index, &len);
+    if (!value) {
+        return Error() << "Expected attribute to have string value";
+    }
+    return Utf16ToUtf8(StringPiece16(value, len));
+}
+
+// Return the u32 value of an attribute.
+Result<uint32_t> getU32Attribute(const ResXMLTree& tree, size_t index) {
+    auto type = tree.getAttributeDataType(index);
+    switch (type) {
+        case Res_value::TYPE_INT_DEC:
+        case Res_value::TYPE_INT_HEX:
+            // This is how we'd expect the version to be encoded - and we don't
+            // care what base it was originally in.
+            return tree.getAttributeData(index);
+        case Res_value::TYPE_STRING: {
+            // If the original string is encoded, then we need to convert it.
+            auto str = OR_RETURN(getStringOnlyAttribute(tree, index));
+            char* str_end = nullptr;
+            // Note that by specifying base 0 we allow for octal, hex, or
+            // decimal representations here.
+            unsigned long value = std::strtoul(str.c_str(), &str_end, 0);
+            if (str_end != str.c_str() + str.size() ||
+                value > std::numeric_limits<uint32_t>::max()) {
+                return Error() << "Invalid numeric value";
+            }
+            return static_cast<uint32_t>(value);
+        }
+        default:
+            return Error() << "Expected numeric value, got type " << type;
+    }
+}
+
+// Parse the binary manifest and extract the information we care about.
+// Everything we're interested in should be an attribute on the <manifest> tag.
+// We don't care what order they come in, absent attributes will be treated as
+// the default value, and any unknown attributes (including ones not in the
+// expected namespace) will be ignored.
+Result<unique_ptr<ApkManifestInfo>> parseManifest(const void* manifest, size_t size) {
+    ResXMLTree tree;
+    auto status = tree.setTo(manifest, size);
+    if (status != OK) {
+        return Error() << "Failed to create XML Tree: " << statusToString(status);
+    }
+
+    OR_RETURN(findManifestElement(tree));
+
+    unique_ptr<ApkManifestInfo> info{new ApkManifestInfo{}};
+
+    size_t count = tree.getAttributeCount();
+    for (size_t i = 0; i < count; ++i) {
+        size_t len;
+        const char16_t* chars;
+
+        chars = tree.getAttributeNamespace(i, &len);
+        auto namespaceUrl = chars ? u16string_view(chars, len) : u16string_view();
+
+        chars = tree.getAttributeName(i, &len);
+        auto attributeName = chars ? u16string_view(chars, len) : u16string_view();
+
+        if (namespaceUrl.empty()) {
+            if (attributeName == PACKAGE_ATTRIBUTE_NAME) {
+                auto result = getStringOnlyAttribute(tree, i);
+                if (!result.ok()) return Error() << "Package name: " << result.error();
+                info->package = *result;
+            }
+        } else if (namespaceUrl == ANDROID_NAMESPACE_URL) {
+            if (attributeName == VERSION_CODE_ATTRIBUTE_NAME) {
+                auto result = getU32Attribute(tree, i);
+                if (!result.ok()) return Error() << "Version code: " << result.error();
+                info->version_code = *result;
+            } else if (attributeName == VERSION_CODE_MAJOR_ATTRIBUTE_NAME) {
+                auto result = getU32Attribute(tree, i);
+                if (!result.ok()) return Error() << "Version code major: " << result.error();
+                info->version_code_major = *result;
+            }
+        }
+    }
+
+    return info;
+}
+} // namespace
+
+const ApkManifestInfo* extractManifestInfo(const void* manifest, size_t size) {
+    auto result = parseManifest(manifest, size);
+    if (!result.ok()) {
+        LOG(ERROR) << "Failed to parse APK manifest:" << result.error().message();
+        return nullptr;
+    }
+    return result->release();
+}
+
+void freeManifestInfo(const ApkManifestInfo* info) {
+    delete info;
+}
+
+const char* getPackageName(const ApkManifestInfo* info) {
+    return info->package.c_str();
+}
+
+uint64_t getVersionCode(const ApkManifestInfo* info) {
+    return info->version_code | (static_cast<uint64_t>(info->version_code_major) << 32);
+}
diff --git a/libs/apkmanifest/native/apkmanifest.hpp b/libs/apkmanifest/native/apkmanifest.hpp
new file mode 100644
index 0000000..352912e
--- /dev/null
+++ b/libs/apkmanifest/native/apkmanifest.hpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+// Opaque structure holding information extracted from an APK manifest.
+struct ApkManifestInfo;
+
+extern "C" {
+
+// Parse a binary XML encoded APK manifest and extract relevant information.
+// The caller must free the returned pointer using freeManifestInfo.  Returns
+// null if any error occurs. Does not retain any pointer to the manifest
+// provided.
+const ApkManifestInfo* extractManifestInfo(const void* manifest, size_t size);
+
+// Frees an ApkManifestInfo allocated by extractManifestInfo; this invalidates
+// the pointer and it must not be used again.
+void freeManifestInfo(const ApkManifestInfo* info);
+
+// Given a valid ApkManifestInfo pointer, return the package name of the APK, as
+// a nul-terminated UTF-8 string. The pointer remains valid until the
+// ApkManifestInfo is freed.
+const char* getPackageName(const ApkManifestInfo* info);
+
+// Given a valid ApkManifestInfo pointer, return the version code of the APK.
+uint64_t getVersionCode(const ApkManifestInfo* info);
+}
diff --git a/libs/apkmanifest/src/apkmanifest.rs b/libs/apkmanifest/src/apkmanifest.rs
new file mode 100644
index 0000000..6766b21
--- /dev/null
+++ b/libs/apkmanifest/src/apkmanifest.rs
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Handle parsing of APK manifest files.
+//! The manifest file is written as XML text, but is stored in the APK
+//! as Android binary compressed XML. This library is a wrapper around
+//! a thin C++ wrapper around libandroidfw, which contains the same
+//! parsing code as used by package manager and aapt2 (amongst other
+//! things).
+
+use anyhow::{bail, Context, Result};
+use apkmanifest_bindgen::{extractManifestInfo, freeManifestInfo, getPackageName, getVersionCode};
+use std::ffi::CStr;
+use std::fs::File;
+use std::path::Path;
+
+/// Information extracted from the Android manifest inside an APK.
+#[derive(Debug)]
+pub struct ApkManifestInfo {
+    /// The package name of the app.
+    pub package: String,
+    /// The version code of the app.
+    pub version_code: u64,
+}
+
+const ANDROID_MANIFEST: &str = "AndroidManifest.xml";
+
+/// Find the manifest inside the given APK and return information from it.
+pub fn get_manifest_info<P: AsRef<Path>>(apk_path: P) -> Result<ApkManifestInfo> {
+    let apk = File::open(apk_path.as_ref())?;
+    let manifest = apkzip::read_file(apk, ANDROID_MANIFEST)?;
+
+    // Safety: The function only reads the memory range we specify and does not hold
+    // any reference to it.
+    let native_info = unsafe { extractManifestInfo(manifest.as_ptr() as _, manifest.len()) };
+    if native_info.is_null() {
+        bail!("Failed to parse manifest")
+    };
+
+    scopeguard::defer! {
+        // Safety: The value we pass is the result of calling extractManifestInfo as required.
+        // We must call this exactly once, after we have finished using it, which the scopeguard
+        // ensures.
+        unsafe { freeManifestInfo(native_info); }
+    }
+
+    // Safety: It is always safe to call this with a valid native_info, which we have,
+    // and it always returns a valid nul-terminated C string with the same lifetime as native_info.
+    // We immediately make a copy.
+    let package = unsafe { CStr::from_ptr(getPackageName(native_info)) };
+    let package = package.to_str().context("Invalid package name")?.to_string();
+
+    // Safety: It is always safe to call this with a valid native_info, which we have.
+    let version_code = unsafe { getVersionCode(native_info) };
+
+    Ok(ApkManifestInfo { package, version_code })
+}
diff --git a/libs/apkverify/Android.bp b/libs/apkverify/Android.bp
index d3aa7ee..1c18d2d 100644
--- a/libs/apkverify/Android.bp
+++ b/libs/apkverify/Android.bp
@@ -7,10 +7,10 @@
     crate_name: "apkverify",
     defaults: ["avf_build_flags_rust"],
     srcs: ["src/lib.rs"],
-    prefer_rlib: true,
     edition: "2021",
     rustlibs: [
         "libanyhow",
+        "libapkzip",
         "libbyteorder",
         "libbytes",
         "libhex",
@@ -18,7 +18,6 @@
         "libnum_traits",
         "libopenssl",
         "libserde",
-        "libzip",
     ],
     proc_macros: ["libnum_derive"],
 }
@@ -34,6 +33,7 @@
 rust_test {
     name: "libapkverify.test",
     defaults: ["libapkverify.defaults"],
+    prefer_rlib: true,
     test_suites: ["general-tests"],
     data: ["tests/data/*"],
 }
@@ -49,6 +49,8 @@
     rustlibs: [
         "libandroid_logger",
         "libapkverify",
+        "libapkzip",
+        "libbyteorder",
         "liblog_rust",
         "libzip",
     ],
diff --git a/libs/apkverify/src/lib.rs b/libs/apkverify/src/lib.rs
index f7cbb7e..6af8122 100644
--- a/libs/apkverify/src/lib.rs
+++ b/libs/apkverify/src/lib.rs
@@ -24,7 +24,6 @@
 pub mod testing;
 mod v3;
 mod v4;
-mod ziputil;
 
 pub use algorithms::{HashAlgorithm, SignatureAlgorithmID};
 pub use v3::{get_public_key_der, verify};
diff --git a/libs/apkverify/src/sigutil.rs b/libs/apkverify/src/sigutil.rs
index 99132b6..7d03bb2 100644
--- a/libs/apkverify/src/sigutil.rs
+++ b/libs/apkverify/src/sigutil.rs
@@ -17,6 +17,7 @@
 //! Utilities for Signature Verification
 
 use anyhow::{anyhow, ensure, Error, Result};
+use apkzip::{set_central_directory_offset, zip_sections};
 use byteorder::{LittleEndian, ReadBytesExt};
 use bytes::{Buf, BufMut, Bytes, BytesMut};
 use openssl::hash::{DigestBytes, Hasher, MessageDigest};
@@ -24,7 +25,6 @@
 use std::io::{self, Cursor, ErrorKind, Read, Seek, SeekFrom, Take};
 
 use crate::algorithms::SignatureAlgorithmID;
-use crate::ziputil::{set_central_directory_offset, zip_sections};
 
 const APK_SIG_BLOCK_MIN_SIZE: u32 = 32;
 const APK_SIG_BLOCK_MAGIC: u128 = 0x3234206b636f6c4220676953204b5041;
diff --git a/libs/apkverify/src/v3.rs b/libs/apkverify/src/v3.rs
index 6082422..8a8ad73 100644
--- a/libs/apkverify/src/v3.rs
+++ b/libs/apkverify/src/v3.rs
@@ -29,7 +29,7 @@
 
 use crate::algorithms::SignatureAlgorithmID;
 use crate::bytes_ext::{BytesExt, LengthPrefixed, ReadFromBytes};
-use crate::sigutil::*;
+use crate::sigutil::ApkSections;
 
 pub const APK_SIGNATURE_SCHEME_V3_BLOCK_ID: u32 = 0xf05368c0;
 
@@ -161,7 +161,8 @@
         // 1. Choose the strongest supported signature algorithm ID from signatures.
         let strongest = self.strongest_signature()?;
 
-        // 2. Verify the corresponding signature from signatures against signed data using public key.
+        // 2. Verify the corresponding signature from signatures against signed data using public
+        // key.
         let verified_signed_data = self.verify_signature(strongest)?;
 
         // 3. Verify the min and max SDK versions in the signed data match those specified for the
@@ -196,8 +197,8 @@
             hex::encode(digest.digest.as_ref()),
         );
 
-        // 7. Verify that public key of the first certificate of certificates is identical
-        //    to public key.
+        // 7. Verify that public key of the first certificate of certificates is identical to public
+        //    key.
         let cert = verified_signed_data.certificates.first().context("No certificates listed")?;
         let cert = X509::from_der(cert.as_ref())?;
         ensure!(
diff --git a/libs/apkverify/tests/apkverify_test.rs b/libs/apkverify/tests/apkverify_test.rs
index 52e1da4..680c81e 100644
--- a/libs/apkverify/tests/apkverify_test.rs
+++ b/libs/apkverify/tests/apkverify_test.rs
@@ -17,7 +17,10 @@
 use apkverify::{
     get_apk_digest, get_public_key_der, testing::assert_contains, verify, SignatureAlgorithmID,
 };
+use apkzip::zip_sections;
+use byteorder::{LittleEndian, ReadBytesExt};
 use log::info;
+use std::io::{Seek, SeekFrom};
 use std::{fs, matches, path::Path};
 
 const KEY_NAMES_DSA: &[&str] = &["1024", "2048", "3072"];
@@ -37,6 +40,28 @@
 }
 
 #[test]
+fn test_zip_sections_with_apk() {
+    let mut reader = fs::File::open("tests/data/v3-only-with-stamp.apk").unwrap();
+    let sections = zip_sections(&mut reader).unwrap();
+
+    // Checks Central directory.
+    assert_eq!(
+        sections.central_directory_offset + sections.central_directory_size,
+        sections.eocd_offset
+    );
+
+    // Checks EOCD.
+    const EOCD_SIGNATURE: u32 = 0x06054b50;
+
+    reader.seek(SeekFrom::Start(sections.eocd_offset as u64)).unwrap();
+    assert_eq!(reader.read_u32::<LittleEndian>().unwrap(), EOCD_SIGNATURE);
+    assert_eq!(
+        reader.metadata().unwrap().len(),
+        (sections.eocd_offset + sections.eocd_size) as u64
+    );
+}
+
+#[test]
 fn test_verify_truncated_cd() {
     setup();
     use zip::result::ZipError;
@@ -284,7 +309,7 @@
     let apk = fs::File::open(&apk_path).expect("Unabled to open apk file");
 
     let (verified_algorithm_id, verified_digest) =
-        get_apk_digest(&apk, SDK_INT, /*verify=*/ true)
+        get_apk_digest(&apk, SDK_INT, /* verify= */ true)
             .expect("Error when extracting apk digest with verification.");
 
     assert_eq!(expected_algorithm_id, verified_algorithm_id);
@@ -292,7 +317,7 @@
     assert_bytes_eq_to_data_in_file(&verified_digest, expected_digest_path);
 
     let (unverified_algorithm_id, unverified_digest) =
-        get_apk_digest(&apk, SDK_INT, /*verify=*/ false)
+        get_apk_digest(&apk, SDK_INT, /* verify= */ false)
             .expect("Error when extracting apk digest without verification.");
     assert_eq!(expected_algorithm_id, unverified_algorithm_id);
     assert_eq!(verified_digest, unverified_digest);
diff --git a/libs/apkzip/Android.bp b/libs/apkzip/Android.bp
new file mode 100644
index 0000000..dc35b5e
--- /dev/null
+++ b/libs/apkzip/Android.bp
@@ -0,0 +1,35 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libapkzip.defaults",
+    crate_name: "apkzip",
+    defaults: ["avf_build_flags_rust"],
+    edition: "2021",
+    srcs: ["src/ziputil.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libbytes",
+        "liblog_rust",
+        "libanyhow",
+        "libzip",
+    ],
+}
+
+rust_library {
+    name: "libapkzip",
+    defaults: ["libapkzip.defaults"],
+    host_supported: true,
+    apex_available: ["com.android.virt"],
+}
+
+rust_test {
+    name: "libapkzip.test",
+    defaults: ["libapkzip.defaults"],
+    prefer_rlib: true,
+    test_suites: ["general-tests"],
+    rustlibs: [
+        "libbyteorder",
+    ],
+}
diff --git a/libs/apkverify/src/ziputil.rs b/libs/apkzip/src/ziputil.rs
similarity index 80%
rename from libs/apkverify/src/ziputil.rs
rename to libs/apkzip/src/ziputil.rs
index 715ea30..708bbcc 100644
--- a/libs/apkverify/src/ziputil.rs
+++ b/libs/apkzip/src/ziputil.rs
@@ -21,9 +21,6 @@
 use std::io::{Read, Seek};
 use zip::ZipArchive;
 
-#[cfg(test)]
-use std::io::SeekFrom;
-
 const EOCD_SIZE_WITHOUT_COMMENT: usize = 22;
 const EOCD_CENTRAL_DIRECTORY_SIZE_FIELD_OFFSET: usize = 12;
 const EOCD_CENTRAL_DIRECTORY_OFFSET_FIELD_OFFSET: usize = 16;
@@ -31,11 +28,16 @@
 const EOCD_SIGNATURE: u32 = 0x06054b50;
 const ZIP64_MARK: u32 = 0xffffffff;
 
+/// Information about the layout of a zip file.
 #[derive(Debug, PartialEq, Eq)]
 pub struct ZipSections {
+    /// Offset within the file of the central directory.
     pub central_directory_offset: u32,
+    /// Size of the central directory.
     pub central_directory_size: u32,
+    /// Offset within the file of end of central directory marker.
     pub eocd_offset: u32,
+    /// Size of the end of central directory marker.
     pub eocd_size: u32,
 }
 
@@ -88,22 +90,35 @@
     Ok(())
 }
 
+/// Read an entire file from a .zip file into memory and return it.
+pub fn read_file<R: Read + Seek>(reader: R, file_name: &str) -> Result<Vec<u8>> {
+    let mut archive = ZipArchive::new(reader)?;
+    let mut file = archive.by_name(file_name)?;
+    let mut bytes = Vec::with_capacity(file.size() as usize);
+    file.read_to_end(&mut bytes)?;
+    Ok(bytes)
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::testing::assert_contains;
-    use byteorder::{LittleEndian, ReadBytesExt};
-    use std::fs::File;
     use std::io::{Cursor, Write};
     use zip::{write::FileOptions, ZipWriter};
 
+    const FILE_CONTENT: &[u8] = b"testcontent";
+    const FILE_NAME: &str = "testfile";
+
     fn create_test_zip() -> Cursor<Vec<u8>> {
         let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
-        writer.start_file("testfile", FileOptions::default()).unwrap();
-        writer.write_all(b"testcontent").unwrap();
+        writer.start_file(FILE_NAME, FileOptions::default()).unwrap();
+        writer.write_all(FILE_CONTENT).unwrap();
         writer.finish().unwrap()
     }
 
+    fn assert_contains(haystack: &str, needle: &str) {
+        assert!(haystack.contains(needle), "{} is not found in {}", needle, haystack);
+    }
+
     #[test]
     fn test_zip_sections() {
         let mut cursor = create_test_zip();
@@ -115,6 +130,12 @@
     }
 
     #[test]
+    fn test_read_file() {
+        let file = read_file(create_test_zip(), FILE_NAME).unwrap();
+        assert_eq!(file.as_slice(), FILE_CONTENT);
+    }
+
+    #[test]
     fn test_reject_if_extra_data_between_cd_and_eocd() {
         // prepare normal zip
         let buf = create_test_zip().into_inner();
@@ -131,24 +152,4 @@
         assert!(res.is_err());
         assert_contains(&res.err().unwrap().to_string(), "Invalid ZIP: offset should be 0");
     }
-
-    #[test]
-    fn test_zip_sections_with_apk() {
-        let mut reader = File::open("tests/data/v3-only-with-stamp.apk").unwrap();
-        let sections = zip_sections(&mut reader).unwrap();
-
-        // Checks Central directory.
-        assert_eq!(
-            sections.central_directory_offset + sections.central_directory_size,
-            sections.eocd_offset
-        );
-
-        // Checks EOCD.
-        reader.seek(SeekFrom::Start(sections.eocd_offset as u64)).unwrap();
-        assert_eq!(reader.read_u32::<LittleEndian>().unwrap(), EOCD_SIGNATURE);
-        assert_eq!(
-            reader.metadata().unwrap().len(),
-            (sections.eocd_offset + sections.eocd_size) as u64
-        );
-    }
 }
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index c91519c..db65193 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -16,6 +16,7 @@
         "android.system.virtualization.payload-rust",
         "libandroid_logger",
         "libanyhow",
+        "libapkmanifest",
         "libavflog",
         "libapexutil_rust",
         "libapkverify",
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index dd0ddbb..491d4de 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -34,12 +34,13 @@
 };
 use anyhow::{anyhow, bail, ensure, Context, Error, Result};
 use apkverify::{get_public_key_der, verify, V4Signature};
+use apkmanifest::get_manifest_info;
 use binder::Strong;
 use diced_open_dice::OwnedDiceArtifacts;
 use glob::glob;
 use itertools::sorted;
 use libc::VMADDR_CID_HOST;
-use log::{error, info};
+use log::{error, info, warn};
 use keystore2_crypto::ZVec;
 use microdroid_metadata::{write_metadata, Metadata, PayloadMetadata};
 use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
@@ -424,7 +425,7 @@
     zipfuse.mount(
         MountForExec::Allowed,
         "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
-        Path::new("/dev/block/mapper/microdroid-apk"),
+        Path::new(DM_MOUNTED_APK_PATH),
         Path::new(VM_APK_CONTENTS_PATH),
         "microdroid_manager.apk.mounted".to_owned(),
     )?;
@@ -776,14 +777,25 @@
 
 fn get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>> {
     let current_sdk = get_current_sdk()?;
-    if !root_hash_trustful {
+
+    let public_key_der = if !root_hash_trustful {
         verify(apk, current_sdk).context(MicrodroidError::PayloadVerificationFailed(format!(
             "failed to verify {}",
             apk
-        )))
+        )))?
     } else {
-        get_public_key_der(apk, current_sdk)
-    }
+        get_public_key_der(apk, current_sdk)?
+    };
+
+    match get_manifest_info(apk) {
+        Ok(manifest_info) => {
+            // TODO (b/299591171): Do something with this info
+            info!("Manifest info is {manifest_info:?}")
+        }
+        Err(e) => warn!("Failed to read manifest info from APK: {e:?}"),
+    };
+
+    Ok(public_key_der)
 }
 
 fn get_current_sdk() -> Result<u32> {