apkverify: add ziputil

apkverify doesn't need all zip features. ziputil provides zip_sections()
to discover the layout of a zip file.

Bug: 190343842
Test: cargo test
Change-Id: Ib6354828cebe819fd3f48265089815ccf29f18ea
diff --git a/apkverify/Cargo.toml b/apkverify/Cargo.toml
index 589056b..965dd9a 100644
--- a/apkverify/Cargo.toml
+++ b/apkverify/Cargo.toml
@@ -8,4 +8,4 @@
 anyhow = { path = "../../../../external/rust/crates/anyhow" }
 bytes = { path = "../../../../external/rust/crates/bytes" }
 byteorder = { path = "../../../../external/rust/crates/byteorder" }
-zip = { version = "0.5", path = "../../../../external/rust/crates/zip" }
+zip = { version = "0.5", path = "../../../../external/rust/crates/zip" }
\ No newline at end of file
diff --git a/apkverify/src/lib.rs b/apkverify/src/lib.rs
index 9ea0e14..9930099 100644
--- a/apkverify/src/lib.rs
+++ b/apkverify/src/lib.rs
@@ -19,6 +19,7 @@
 mod bytes_ext;
 mod sigutil;
 mod v3;
+mod ziputil;
 
 use anyhow::Result;
 use std::path::Path;
diff --git a/apkverify/src/sigutil.rs b/apkverify/src/sigutil.rs
index de3d0dd..564831f 100644
--- a/apkverify/src/sigutil.rs
+++ b/apkverify/src/sigutil.rs
@@ -19,9 +19,9 @@
 use anyhow::{anyhow, bail, Result};
 use byteorder::{LittleEndian, ReadBytesExt};
 use bytes::{Buf, Bytes};
-use std::io;
-use std::io::Read;
-use zip::spec::CentralDirectoryEnd as EndOfCentralDirectory;
+use std::io::{Read, Seek, SeekFrom};
+
+use crate::ziputil::zip_sections;
 
 const APK_SIG_BLOCK_MIN_SIZE: u32 = 32;
 const APK_SIG_BLOCK_MAGIC: u128 = 0x3234206b636f6c4220676953204b5041;
@@ -46,34 +46,23 @@
 const CONTENT_DIGEST_SHA256: u32 = 4;
 
 pub struct SignatureInfo {
-    pub signing_block_offset: u32,
     pub signature_block: Bytes,
-    pub eocd_offset: u32,
-    pub eocd: EndOfCentralDirectory,
 }
 
 /// Returns the APK Signature Scheme block contained in the provided file for the given ID
 /// and the additional information relevant for verifying the block against the file.
-pub fn find_signature<F: Read + io::Seek>(f: &mut F, block_id: u32) -> Result<SignatureInfo> {
-    let (eocd, eocd_offset) = EndOfCentralDirectory::find_and_parse(f)?;
-    if eocd.disk_number != eocd.disk_with_central_directory {
-        bail!("Support for multi-disk files is not implemented");
-    }
-    // TODO(jooyung): reject zip64 file
-    let (signing_block, signing_block_offset) =
-        find_signing_block(f, eocd.central_directory_offset)?;
+pub fn find_signature<F: Read + Seek>(f: F, block_id: u32) -> Result<SignatureInfo> {
+    let (mut f, sections) = zip_sections(f)?;
+
+    let (signing_block, _signing_block_offset) =
+        find_signing_block(&mut f, sections.central_directory_offset)?;
 
     // TODO(jooyung): propagate NotFound error so that verification can fallback to V2
     let signature_scheme_block = find_signature_scheme_block(signing_block, block_id)?;
-    Ok(SignatureInfo {
-        signing_block_offset,
-        signature_block: signature_scheme_block,
-        eocd_offset: eocd_offset as u32,
-        eocd,
-    })
+    Ok(SignatureInfo { signature_block: signature_scheme_block })
 }
 
-fn find_signing_block<T: Read + io::Seek>(
+fn find_signing_block<T: Read + Seek>(
     reader: &mut T,
     central_directory_offset: u32,
 ) -> Result<(Bytes, u32)> {
@@ -89,7 +78,7 @@
             central_directory_offset
         );
     }
-    reader.seek(io::SeekFrom::Start((central_directory_offset - 24) as u64))?;
+    reader.seek(SeekFrom::Start((central_directory_offset - 24) as u64))?;
     let size_in_footer = reader.read_u64::<LittleEndian>()? as u32;
     if reader.read_u128::<LittleEndian>()? != APK_SIG_BLOCK_MAGIC {
         bail!("No APK Signing Block before ZIP Central Directory")
@@ -98,7 +87,7 @@
     let signing_block_offset = central_directory_offset
         .checked_sub(total_size)
         .ok_or_else(|| anyhow!("APK Signing Block size out of range: {}", size_in_footer))?;
-    reader.seek(io::SeekFrom::Start(signing_block_offset as u64))?;
+    reader.seek(SeekFrom::Start(signing_block_offset as u64))?;
     let size_in_header = reader.read_u64::<LittleEndian>()? as u32;
     if size_in_header != size_in_footer {
         bail!(
@@ -107,7 +96,7 @@
             size_in_footer
         );
     }
-    reader.seek(io::SeekFrom::Start(signing_block_offset as u64))?;
+    reader.seek(SeekFrom::Start(signing_block_offset as u64))?;
     let mut buf = vec![0u8; total_size as usize];
     reader.read_exact(&mut buf)?;
     Ok((Bytes::from(buf), signing_block_offset))
diff --git a/apkverify/src/v3.rs b/apkverify/src/v3.rs
index 75551db..0a292df 100644
--- a/apkverify/src/v3.rs
+++ b/apkverify/src/v3.rs
@@ -79,8 +79,8 @@
 /// Verifies APK Signature Scheme v3 signatures of the provided APK and returns the certificates
 /// associated with each signer.
 pub fn verify<P: AsRef<Path>>(path: P) -> Result<()> {
-    let mut f = File::open(path.as_ref())?;
-    let signature = find_signature(&mut f, APK_SIGNATURE_SCHEME_V3_BLOCK_ID)?;
+    let f = File::open(path.as_ref())?;
+    let signature = find_signature(f, APK_SIGNATURE_SCHEME_V3_BLOCK_ID)?;
     verify_signature(&signature.signature_block)?;
     Ok(())
 }
diff --git a/apkverify/src/ziputil.rs b/apkverify/src/ziputil.rs
new file mode 100644
index 0000000..28ecf87
--- /dev/null
+++ b/apkverify/src/ziputil.rs
@@ -0,0 +1,71 @@
+/*
+ * 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
+
+use anyhow::{bail, Result};
+use bytes::Buf;
+use std::io::{Read, Seek, SeekFrom};
+use zip::ZipArchive;
+
+const EOCD_MIN_SIZE: usize = 22;
+const EOCD_CENTRAL_DIRECTORY_OFFSET_FIELD_OFFSET: usize = 16;
+const EOCD_MAGIC: u32 = 0x06054b50;
+
+#[derive(Debug, PartialEq)]
+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<(R, ZipSections)> {
+    // open a zip to parse EOCD
+    let archive = ZipArchive::new(reader)?;
+    let eocd_size = archive.comment().len() + EOCD_MIN_SIZE;
+    if archive.offset() != 0 {
+        bail!("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.seek(SeekFrom::Current(0))?;
+    let mut eocd = vec![0u8; eocd_size as usize];
+    reader.read_exact(&mut eocd)?;
+    if (&eocd[0..]).get_u32_le() != EOCD_MAGIC {
+        bail!("Invalid ZIP: ZipArchive::new() should point EOCD after reading.");
+    }
+    let central_directory_offset = get_central_directory_offset(&eocd)?;
+    let central_directory_size = eocd_offset as u32 - central_directory_offset;
+    Ok((
+        reader,
+        ZipSections {
+            central_directory_offset,
+            central_directory_size,
+            eocd_offset: eocd_offset as u32,
+            eocd_size: eocd_size as u32,
+        },
+    ))
+}
+
+fn get_central_directory_offset(buf: &[u8]) -> Result<u32> {
+    if buf.len() < EOCD_MIN_SIZE {
+        bail!("Invalid EOCD size: {}", buf.len());
+    }
+    Ok((&buf[EOCD_CENTRAL_DIRECTORY_OFFSET_FIELD_OFFSET..]).get_u32_le())
+}