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);