Parse APEX manifest
Modify verify to return the APEX name and version from its manifest
protobuf.
Refactor error handling a bit, improving the messages.
Remove the separate get_payload_vbmeta_image_hash function, merging it
into verify. (It isn't used yet, but it might be.)
Improve the tests. Also remove host_supported - we don't need it (the
reason for adding it no longer applies), and the dependencies now make
it tricky.
Bug: 308759880
Test: atest MicrodroidTests
Test: atest libapexutil_rust.test
Test: composd_cmd test-compile
Change-Id: Idd38001ab28a4a0f27b42f361b18bbab0e4aad84
diff --git a/libs/apexutil/Android.bp b/libs/apexutil/Android.bp
index f9b72c4..d248d5e 100644
--- a/libs/apexutil/Android.bp
+++ b/libs/apexutil/Android.bp
@@ -9,7 +9,9 @@
srcs: ["src/lib.rs"],
edition: "2021",
rustlibs: [
+ "libapex_manifest_rs",
"liblog_rust",
+ "libprotobuf",
"libthiserror",
"libvbmeta_rust",
"libzip",
diff --git a/libs/apexutil/src/lib.rs b/libs/apexutil/src/lib.rs
index 8a934e2..da3bb45 100644
--- a/libs/apexutil/src/lib.rs
+++ b/libs/apexutil/src/lib.rs
@@ -14,6 +14,8 @@
//! Routines for handling APEX payload
+use apex_manifest::apex_manifest::ApexManifest;
+use protobuf::Message;
use std::fs::File;
use std::io::{self, Read};
use thiserror::Error;
@@ -23,28 +25,32 @@
const APEX_PUBKEY_ENTRY: &str = "apex_pubkey";
const APEX_PAYLOAD_ENTRY: &str = "apex_payload.img";
+const APEX_MANIFEST_ENTRY: &str = "apex_manifest.pb";
/// Errors from parsing an APEX.
#[derive(Debug, Error)]
pub enum ApexParseError {
/// There was an IO error.
- #[error("IO error")]
+ #[error("IO error: {0}")]
Io(#[from] io::Error),
/// The Zip archive was invalid.
- #[error("Cannot read zip archive")]
+ #[error("Cannot read zip archive: {0}")]
InvalidZip(&'static str),
- /// The apex_pubkey file was missing from the APEX.
- #[error("APEX doesn't contain apex_pubkey")]
- PubkeyMissing,
- /// The apex_payload.img file was missing from the APEX.
- #[error("APEX doesn't contain apex_payload.img")]
- PayloadMissing,
+ /// An expected file was missing from the APEX.
+ #[error("APEX doesn't contain {0}")]
+ MissingFile(&'static str),
/// There was no hashtree descriptor in the APEX payload's VBMeta image.
#[error("Non-hashtree descriptor found in payload's VBMeta image")]
DescriptorNotHashtree,
/// There was an error parsing the APEX payload's VBMeta image.
- #[error("Could not parse payload's VBMeta image")]
+ #[error("Could not parse payload's VBMeta image: {0}")]
PayloadVbmetaError(#[from] vbmeta::VbMetaImageParseError),
+ /// Data was missing from the VBMeta
+ #[error("Data missing from VBMeta: {0}")]
+ VbmetaMissingData(&'static str),
+ /// An error occurred parsing the APEX manifest as a protobuf
+ #[error("Error parsing manifest protobuf: {0}")]
+ ManifestProtobufError(#[from] protobuf::Error),
}
/// Errors from verifying an APEX.
@@ -58,29 +64,42 @@
PayloadVbmetaError(#[from] vbmeta::VbMetaImageVerificationError),
/// The APEX payload was not verified with the apex_pubkey.
#[error("APEX pubkey mismatch")]
- ApexPubkeyMistmatch,
+ ApexPubkeyMismatch,
}
-/// Verification result holds public key and root digest of apex_payload.img
+/// Information extracted from the APEX during verification.
pub struct ApexVerificationResult {
+ /// The name of the APEX, from its manifest. Unverified, but apexd will reject
+ /// an APEX where the unsigned manifest isn't the same as the signed one.
+ pub name: String,
+ /// The version of the APEX, from its manifest. Unverified, but apexd will reject
+ /// an APEX where the unsigned manifest isn't the same as the signed one.
+ pub version: i64,
/// The public key that verifies the payload signature.
pub public_key: Vec<u8>,
/// The root digest of the payload hashtree.
pub root_digest: Vec<u8>,
+ /// The hash of the verified VBMeta image data.
+ /// TODO(alanstokes): Delete this if we don't have a use for it.
+ pub image_hash: Vec<u8>,
}
-/// Verify APEX payload by AVB verification and return public key and root digest
+/// Verify APEX payload by AVB verification and return information about the APEX.
pub fn verify(path: &str) -> Result<ApexVerificationResult, ApexVerificationError> {
let apex_file = File::open(path).map_err(ApexParseError::Io)?;
- let (public_key, image_offset, image_size) = get_public_key_and_image_info(&apex_file)?;
+ let ApexZipInfo { public_key, image_offset, image_size, manifest } =
+ get_apex_zip_info(&apex_file)?;
let vbmeta = VbMetaImage::verify_reader_region(apex_file, image_offset, image_size)?;
let root_digest = find_root_digest(&vbmeta)?;
- match vbmeta.public_key() {
- Some(payload_public_key) if public_key == payload_public_key => {
- Ok(ApexVerificationResult { public_key, root_digest })
- }
- _ => Err(ApexVerificationError::ApexPubkeyMistmatch),
+ let vbmeta_public_key =
+ vbmeta.public_key().ok_or(ApexParseError::VbmetaMissingData("public key"))?;
+ if vbmeta_public_key != public_key {
+ return Err(ApexVerificationError::ApexPubkeyMismatch);
}
+ let image_hash = vbmeta.hash().ok_or(ApexParseError::VbmetaMissingData("hash"))?.to_vec();
+ let ApexManifestInfo { name, version } = decode_manifest(&manifest)?;
+
+ Ok(ApexVerificationResult { name, version, public_key, root_digest, image_hash })
}
fn find_root_digest(vbmeta: &VbMetaImage) -> Result<Vec<u8>, ApexParseError> {
@@ -93,46 +112,52 @@
Err(ApexParseError::DescriptorNotHashtree)
}
-/// Gets the hash of the payload's verified VBMeta image data.
-pub fn get_payload_vbmeta_image_hash(path: &str) -> Result<Vec<u8>, ApexVerificationError> {
- let apex_file = File::open(path).map_err(ApexParseError::Io)?;
- let (_, offset, size) = get_public_key_and_image_info(&apex_file)?;
- let vbmeta = VbMetaImage::verify_reader_region(apex_file, offset, size)?;
- Ok(vbmeta.hash().ok_or(ApexVerificationError::ApexPubkeyMistmatch)?.to_vec())
+struct ApexZipInfo {
+ public_key: Vec<u8>,
+ image_offset: u64,
+ image_size: u64,
+ manifest: Vec<u8>,
}
-fn get_public_key_and_image_info(apex_file: &File) -> Result<(Vec<u8>, u64, u64), ApexParseError> {
- let mut z = ZipArchive::new(apex_file).map_err(|err| match err {
- ZipError::Io(err) => ApexParseError::Io(err),
- ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
- ApexParseError::InvalidZip(s)
- }
- ZipError::FileNotFound => unreachable!(),
- })?;
+fn get_apex_zip_info(apex_file: &File) -> Result<ApexZipInfo, ApexParseError> {
+ let mut z = ZipArchive::new(apex_file).map_err(|err| from_zip_error(err, "?"))?;
let mut public_key = Vec::new();
z.by_name(APEX_PUBKEY_ENTRY)
- .map_err(|err| match err {
- ZipError::Io(err) => ApexParseError::Io(err),
- ZipError::FileNotFound => ApexParseError::PubkeyMissing,
- ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
- ApexParseError::InvalidZip(s)
- }
- })?
+ .map_err(|err| from_zip_error(err, APEX_PUBKEY_ENTRY))?
.read_to_end(&mut public_key)?;
let (image_offset, image_size) = z
.by_name(APEX_PAYLOAD_ENTRY)
.map(|f| (f.data_start(), f.size()))
- .map_err(|err| match err {
- ZipError::Io(err) => ApexParseError::Io(err),
- ZipError::FileNotFound => ApexParseError::PayloadMissing,
- ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
- ApexParseError::InvalidZip(s)
- }
- })?;
+ .map_err(|err| from_zip_error(err, APEX_PAYLOAD_ENTRY))?;
- Ok((public_key, image_offset, image_size))
+ let mut manifest = Vec::new();
+ z.by_name(APEX_MANIFEST_ENTRY)
+ .map_err(|err| from_zip_error(err, APEX_MANIFEST_ENTRY))?
+ .read_to_end(&mut manifest)?;
+
+ Ok(ApexZipInfo { public_key, image_offset, image_size, manifest })
+}
+
+struct ApexManifestInfo {
+ name: String,
+ version: i64,
+}
+
+fn decode_manifest(mut manifest: &[u8]) -> Result<ApexManifestInfo, ApexParseError> {
+ let manifest = ApexManifest::parse_from_reader(&mut manifest)?;
+ Ok(ApexManifestInfo { name: manifest.name, version: manifest.version })
+}
+
+fn from_zip_error(err: ZipError, name: &'static str) -> ApexParseError {
+ match err {
+ ZipError::Io(err) => ApexParseError::Io(err),
+ ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
+ ApexParseError::InvalidZip(s)
+ }
+ ZipError::FileNotFound => ApexParseError::MissingFile(name),
+ }
}
#[cfg(test)]
@@ -143,17 +168,47 @@
fn apex_verification_returns_valid_result() {
let res = verify("tests/data/test.apex").unwrap();
// The expected hex is generated when we ran the method the first time.
+ assert_eq!(res.name, "com.android.apex.cts.shim");
+ assert_eq!(res.version, 1);
+ assert_eq!(
+ hex::encode(res.public_key),
+ "000010007093e081b637735012c7f2\
+ fdacba9b1c01ee2eaa78e7fb69fa810a0e3fff8d70cd69c60eb6458c54c56c6a40e2f68\
+ 60c69343c1373eb1785c4e81eca1c8921390da5997115668ef4c5f5cb90d74df3fc29dd\
+ d05d45e298761beba76276669540d5cfe9c79ed1e001637871db4f0d0083d56332fe328\
+ 9f1f3aec8d00b06a7db25104d5a05226ab499cd6536434ff8f1d01ca1c653a91d58ee41\
+ a848571abf9ba555610a1dc3555911386f07109c3c9e420a17b8f63c58c74410a94cedd\
+ 2e3e8203f4e638b620836742331049c96423c01fbe2609962e35d38d127730692f7e947\
+ 80bb21017b4583c9fb59e9f7421a92cff4d4dd6095d5aca2f5f13b9c5320ff0f3fc84bb\
+ b347bbe7fc08b6081d2157bddae97845e2c58da58d9d56732dd90d5b59116db404b859c\
+ b68b4c51790d06337bf939201f5ab356a50242d7e50e29f53f0525ab1693d6b1db1acbd\
+ 540dd8eb310accee7b3938b471a1768163c226a44483e0e4453cde393f5495bfe10297d\
+ 68f1bfed44b386c5c2ecde221607635ef14aadcba153f1f916d7c1fc92fab1b04f964f8\
+ 5660033024084d5b27760e61967c9df5e2a099bdc63e3c3864b15fd3caa85274ab7b02d\
+ 8933c2a5e4460adbf95aae0774945e9a5c0abb15f2d533259cb090ea5be513572bd75cc\
+ 5eaf23fe4f5dbe4b8fee525059ae0d8c7813704f2b9fc641525075d2ce6e44bd0955c10\
+ f8383f87b0d3a07f524893b78bb67d5428cfa430e863f121c1de0205d3dd64f3a78c5d8\
+ e802dfaa078f07c4626c4a280224816958a1e621d05184214f675a7cd1c55b6c5a2b18e\
+ 358f84c4b1068b8d2aace966c47674204ada4b5376b55fd9c145b1224ddb4f578f6279d\
+ d92a381f3a11235be8331ce15754374426a35aa6f17586f1658d48f30c3220fec43b3d5\
+ ca7ed0f8de14225b19ab699fb75c95299b8f81559fe41df31e4d591692d86482c50c3ba\
+ ccfeb002ead775eca116b5674bd8f4f2c5db54bd21596d980f2067e331bc0e30a56c25f\
+ 6fd7d5f2a03651198f0c7494add16889dbb49cc79038fd8bc2e7540c3101e5cbbb1f8d6\
+ f0eab86f83eb76ef5d6df29f0c718019c26f8e38d86a54f2b992a17a0c9e00a298e866d\
+ 53e2ff78f35de1ccdf166375309397a43b74cf7a34a647a3ee0234dbf4744c6db5f44f7\
+ 1a366d87024ec3a5ec4185ac7cc0342460160632f21b791e12b656c71c248cce5fbb45f\
+ 3c624852ea9c29264c6b8ad58ac36bf99cf5254d1e69c628bdf1707136475230bbedf1f\
+ ac25849b249795456d5d99214800e44a6d71c460eb495d9926145606d7cbb986044c9f0\
+ 11b6d6be5c79f89a6f90ad39676489eac632b105cbf3da29bf7e4e72bf82600bcafc867\
+ f4cb6e0ade8f532d9620b82001c69493ff5679cf0393285aa67b3e4382c8e785e43efe9\
+ 7e56fbd24357eec0b19697194f0b91ee46ab82dfeea788"
+ );
assert_eq!(
hex::encode(res.root_digest),
"fe11ab17da0a3a738b54bdc3a13f6139cbdf91ec32f001f8d4bbbf8938e04e39"
);
- }
-
- #[test]
- fn payload_vbmeta_has_valid_image_hash() {
- let result = get_payload_vbmeta_image_hash("tests/data/test.apex").unwrap();
assert_eq!(
- hex::encode(result),
+ hex::encode(res.image_hash),
"296e32a76544de9da01713e471403ab4667705ad527bb4f1fac0cf61e7ce122d"
);
}