blob: fc18225f7c56ecb15017aaac547c83227d7a1763 [file] [log] [blame]
// Copyright 2022, 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.
//! Tool for handling AVMD blobs.
use anyhow::{anyhow, bail, Result};
use apexutil::get_payload_vbmeta_image_hash;
use apkverify::get_apk_digest;
use avmd::{ApkDescriptor, Avmd, Descriptor, ResourceIdentifier, VbMetaDescriptor};
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use serde::ser::Serialize;
use std::fs::File;
use vbmeta::VbMetaImage;
fn get_vbmeta_image_hash(file: &str) -> Result<Vec<u8>> {
let img = VbMetaImage::verify_path(file)?;
Ok(img.hash().ok_or_else(|| anyhow!("No hash as VBMeta image isn't signed"))?.to_vec())
}
/// Iterate over a set of argument values, that could be empty or come in
/// (<index>, <namespace>, <name>, <file>) tuple.
struct NamespaceNameFileIterator<'a> {
indices: Option<clap::Indices<'a>>,
values: Option<clap::Values<'a>>,
}
impl<'a> NamespaceNameFileIterator<'a> {
fn new(args: &'a ArgMatches, name: &'a str) -> Self {
NamespaceNameFileIterator { indices: args.indices_of(name), values: args.values_of(name) }
}
}
impl<'a> Iterator for NamespaceNameFileIterator<'a> {
type Item = (usize, &'a str, &'a str, &'a str);
fn next(&mut self) -> Option<Self::Item> {
match (self.indices.as_mut(), self.values.as_mut()) {
(Some(indices), Some(values)) => {
match (indices.nth(2), values.next(), values.next(), values.next()) {
(Some(index), Some(namespace), Some(name), Some(file)) => {
Some((index, namespace, name, file))
}
_ => None,
}
}
_ => None,
}
}
}
fn create(args: &ArgMatches) -> Result<()> {
// Store descriptors in the order they were given in the arguments
// TODO: instead, group them by namespace?
let mut descriptors = std::collections::BTreeMap::new();
for (i, namespace, name, file) in NamespaceNameFileIterator::new(args, "vbmeta") {
descriptors.insert(
i,
Descriptor::VbMeta(VbMetaDescriptor {
resource: ResourceIdentifier::new(namespace, name),
vbmeta_digest: get_vbmeta_image_hash(file)?,
}),
);
}
for (i, namespace, name, file) in NamespaceNameFileIterator::new(args, "apk") {
let file = File::open(file)?;
let (signature_algorithm_id, apk_digest) = get_apk_digest(file, /*verify=*/ true)?;
descriptors.insert(
i,
Descriptor::Apk(ApkDescriptor {
resource: ResourceIdentifier::new(namespace, name),
signature_algorithm_id,
apk_digest: apk_digest.to_vec(),
}),
);
}
for (i, namespace, name, file) in NamespaceNameFileIterator::new(args, "apex-payload") {
descriptors.insert(
i,
Descriptor::VbMeta(VbMetaDescriptor {
resource: ResourceIdentifier::new(namespace, name),
vbmeta_digest: get_payload_vbmeta_image_hash(file)?,
}),
);
}
let avmd = Avmd::new(descriptors.into_values().collect());
let mut bytes = Vec::new();
avmd.serialize(
&mut serde_cbor::Serializer::new(&mut serde_cbor::ser::IoWrite::new(&mut bytes))
.packed_format()
.legacy_enums(),
)?;
std::fs::write(args.value_of("file").unwrap(), &bytes)?;
Ok(())
}
fn dump(args: &ArgMatches) -> Result<()> {
let file = std::fs::read(args.value_of("file").unwrap())?;
let avmd: Avmd = serde_cbor::from_slice(&file)?;
println!("{}", avmd);
Ok(())
}
fn main() -> Result<()> {
let namespace_name_file = ["namespace", "name", "file"];
let app = App::new("avmdtool")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
SubCommand::with_name("create")
.setting(AppSettings::ArgRequiredElseHelp)
.arg(Arg::with_name("file").required(true).takes_value(true))
.arg(
Arg::with_name("vbmeta")
.long("vbmeta")
.takes_value(true)
.value_names(&namespace_name_file)
.multiple(true),
)
.arg(
Arg::with_name("apk")
.long("apk")
.takes_value(true)
.value_names(&namespace_name_file)
.multiple(true),
)
.arg(
Arg::with_name("apex-payload")
.long("apex-payload")
.takes_value(true)
.value_names(&namespace_name_file)
.multiple(true),
),
)
.subcommand(
SubCommand::with_name("dump")
.setting(AppSettings::ArgRequiredElseHelp)
.arg(Arg::with_name("file").required(true).takes_value(true)),
);
let args = app.get_matches();
match args.subcommand() {
Some(("create", sub_args)) => create(sub_args)?,
Some(("dump", sub_args)) => dump(sub_args)?,
_ => bail!("Invalid arguments"),
}
Ok(())
}