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> {