Add avmdtool
This CL is a starting point for the avmdtool. We will improve
incrementally from here later.
Bug: 234564414
Test: manual
Change-Id: I588de80fe37826d55af347b64872e98ac1e81624
diff --git a/avmd/src/main.rs b/avmd/src/main.rs
new file mode 100644
index 0000000..b156a66
--- /dev/null
+++ b/avmd/src/main.rs
@@ -0,0 +1,157 @@
+// 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::pick_v4_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) = pick_v4_apk_digest(file)?;
+ 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() {
+ ("create", Some(sub_args)) => create(sub_args)?,
+ ("dump", Some(sub_args)) => dump(sub_args)?,
+ _ => bail!("Invalid arguments"),
+ }
+ Ok(())
+}