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())
+}