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/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/src/ziputil.rs b/libs/apkverify/src/ziputil.rs
deleted file mode 100644
index 715ea30..0000000
--- a/libs/apkverify/src/ziputil.rs
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-//! Utilities for zip handling of APK files.
-
-use anyhow::{ensure, Result};
-use bytes::{Buf, BufMut};
-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;
-/// End of Central Directory signature
-const EOCD_SIGNATURE: u32 = 0x06054b50;
-const ZIP64_MARK: u32 = 0xffffffff;
-
-#[derive(Debug, PartialEq, Eq)]
-pub struct ZipSections {
-    pub central_directory_offset: u32,
-    pub central_directory_size: u32,
-    pub eocd_offset: u32,
-    pub eocd_size: u32,
-}
-
-/// Discover the layout of a zip file.
-pub fn zip_sections<R: Read + Seek>(mut reader: R) -> Result<ZipSections> {
-    // open a zip to parse EOCD
-    let archive = ZipArchive::new(reader)?;
-    let eocd_size = archive.comment().len() + EOCD_SIZE_WITHOUT_COMMENT;
-    ensure!(archive.offset() == 0, "Invalid ZIP: offset should be 0, but {}.", archive.offset());
-    // retrieve reader back
-    reader = archive.into_inner();
-    // the current position should point EOCD offset
-    let eocd_offset = reader.stream_position()? as u32;
-    let mut eocd = vec![0u8; eocd_size];
-    reader.read_exact(&mut eocd)?;
-    ensure!(
-        (&eocd[0..]).get_u32_le() == EOCD_SIGNATURE,
-        "Invalid ZIP: ZipArchive::new() should point EOCD after reading."
-    );
-    let (central_directory_size, central_directory_offset) = get_central_directory(&eocd)?;
-    ensure!(
-        central_directory_offset != ZIP64_MARK && central_directory_size != ZIP64_MARK,
-        "Unsupported ZIP: ZIP64 is not supported."
-    );
-    ensure!(
-        central_directory_offset + central_directory_size == eocd_offset,
-        "Invalid ZIP: EOCD should follow CD with no extra data or overlap."
-    );
-
-    Ok(ZipSections {
-        central_directory_offset,
-        central_directory_size,
-        eocd_offset,
-        eocd_size: eocd_size as u32,
-    })
-}
-
-fn get_central_directory(buf: &[u8]) -> Result<(u32, u32)> {
-    ensure!(buf.len() >= EOCD_SIZE_WITHOUT_COMMENT, "Invalid EOCD size: {}", buf.len());
-    let mut buf = &buf[EOCD_CENTRAL_DIRECTORY_SIZE_FIELD_OFFSET..];
-    let size = buf.get_u32_le();
-    let offset = buf.get_u32_le();
-    Ok((size, offset))
-}
-
-/// Update EOCD's central_directory_offset field.
-pub fn set_central_directory_offset(buf: &mut [u8], value: u32) -> Result<()> {
-    ensure!(buf.len() >= EOCD_SIZE_WITHOUT_COMMENT, "Invalid EOCD size: {}", buf.len());
-    (&mut buf[EOCD_CENTRAL_DIRECTORY_OFFSET_FIELD_OFFSET..]).put_u32_le(value);
-    Ok(())
-}
-
-#[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};
-
-    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.finish().unwrap()
-    }
-
-    #[test]
-    fn test_zip_sections() {
-        let mut cursor = create_test_zip();
-        let sections = zip_sections(&mut cursor).unwrap();
-        assert_eq!(
-            sections.eocd_offset,
-            (cursor.get_ref().len() - EOCD_SIZE_WITHOUT_COMMENT) as u32
-        );
-    }
-
-    #[test]
-    fn test_reject_if_extra_data_between_cd_and_eocd() {
-        // prepare normal zip
-        let buf = create_test_zip().into_inner();
-
-        // insert garbage between CD and EOCD.
-        // by the way, to mock zip-rs, use CD as garbage. This is implementation detail of zip-rs,
-        // which reads CD at (eocd_offset - cd_size) instead of at cd_offset from EOCD.
-        let (pre_eocd, eocd) = buf.split_at(buf.len() - EOCD_SIZE_WITHOUT_COMMENT);
-        let (_, cd_offset) = get_central_directory(eocd).unwrap();
-        let cd = &pre_eocd[cd_offset as usize..];
-
-        // ZipArchive::new() succeeds, but we should reject
-        let res = zip_sections(Cursor::new([pre_eocd, cd, eocd].concat()));
-        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/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);