blob: da3bb457bafdc9922c3ca2809933d24f3206cbaa [file] [log] [blame]
// Copyright 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.
//! 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;
use vbmeta::VbMetaImage;
use zip::result::ZipError;
use zip::ZipArchive;
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: {0}")]
Io(#[from] io::Error),
/// The Zip archive was invalid.
#[error("Cannot read zip archive: {0}")]
InvalidZip(&'static str),
/// 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: {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.
#[derive(Debug, Error)]
pub enum ApexVerificationError {
/// There was an error parsing the APEX.
#[error("Cannot parse APEX file")]
ParseError(#[from] ApexParseError),
/// There was an error validating the APEX payload's VBMeta image.
#[error("Could not parse payload's VBMeta image")]
PayloadVbmetaError(#[from] vbmeta::VbMetaImageVerificationError),
/// The APEX payload was not verified with the apex_pubkey.
#[error("APEX pubkey mismatch")]
ApexPubkeyMismatch,
}
/// 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 information about the APEX.
pub fn verify(path: &str) -> Result<ApexVerificationResult, ApexVerificationError> {
let apex_file = File::open(path).map_err(ApexParseError::Io)?;
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)?;
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> {
// APEXs use the root digest from the first hashtree descriptor to describe the payload.
for descriptor in vbmeta.descriptors()?.iter() {
if let vbmeta::Descriptor::Hashtree(_) = descriptor {
return Ok(descriptor.to_hashtree()?.root_digest().to_vec());
}
}
Err(ApexParseError::DescriptorNotHashtree)
}
struct ApexZipInfo {
public_key: Vec<u8>,
image_offset: u64,
image_size: u64,
manifest: Vec<u8>,
}
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| 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| from_zip_error(err, APEX_PAYLOAD_ENTRY))?;
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)]
mod tests {
use super::*;
#[test]
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"
);
assert_eq!(
hex::encode(res.image_hash),
"296e32a76544de9da01713e471403ab4667705ad527bb4f1fac0cf61e7ce122d"
);
}
}