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/Android.bp b/avmd/Android.bp
new file mode 100644
index 0000000..9f0b28b
--- /dev/null
+++ b/avmd/Android.bp
@@ -0,0 +1,38 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libavmd_defaults",
+    crate_name: "avmd",
+    host_supported: true,
+    srcs: ["src/lib.rs"],
+    prefer_rlib: true,
+    rustlibs: [
+        "libserde",
+        "libapexutil_rust", // TODO(b/239413416): Remove this after adding hex
+    ],
+}
+
+rust_library {
+    name: "libavmd",
+    defaults: ["libavmd_defaults"],
+}
+
+rust_binary {
+    name: "avmdtool",
+    srcs: ["src/main.rs"],
+    required: ["avbtool"],
+    host_supported: true,
+    prefer_rlib: true,
+    rustlibs: [
+        "libanyhow",
+        "libapexutil_rust",
+        "libapkverify",
+        "libavmd",
+        "libclap",
+        "libserde",
+        "libserde_cbor",
+        "libvbmeta_rust",
+    ],
+}
diff --git a/avmd/README.md b/avmd/README.md
new file mode 100644
index 0000000..ae813a0
--- /dev/null
+++ b/avmd/README.md
@@ -0,0 +1,48 @@
+# The AVMD image format
+---
+
+The AVMD image format is used to descibe the verified code that a VM will
+load. This repository contains tools and libraries for working with the AVMD
+image format.
+
+# What is it?
+
+When a VM boots, it loads and verifies a set of images that control execution
+within the VM. Therefore, describing what executes in a VM means describing
+what is loaded. The AVMD image format is designed, for this purpose, to
+describe the closure of images that can be loaded and how they should be
+verified.
+
+# Caveats
+
+The AVMD image format will only allow Android supported signing formats. The
+supported formats are currently limited to [AVB][] and [APK][].
+
+[AVB]: https://android.googlesource.com/platform/external/avb/+/master/README.md
+[APK]: https://source.android.com/security/apksigning#schemes
+
+Verification of the images as they are loaded is the responsibility of the VM.
+The VM is required to only load the images described and to verify them against
+the included parameters. If the VM does not follow this requirement, the
+description of the VM may not be accurate and must not be trusted. Validating
+that the VM behaves as expected requires audit of all boot stages of the VM.
+
+# Using avmdtool
+
+The `.avmd` file can be created as follows
+
+```bash
+avmdtool create /tmp/out.avmd \
+   --vbmeta pvmfw preload u-boot.bin \
+   --vbmeta uboot env_vbmeta disk1/vbmeta.imb \
+   --vbmeta uboot vbmeta micordoid/vbmeta.img \
+   --apk microdroid payload compos.apk \
+   --apk microdroid extra_apk extra_apk.apk \
+   --apex-payload microdroid art_apex art.apex
+```
+
+You can read the `.avmd` file with
+
+```bash
+avmdtool dump /tmp/out.avmd
+```
diff --git a/avmd/src/avmd.rs b/avmd/src/avmd.rs
new file mode 100644
index 0000000..e3bc7a7
--- /dev/null
+++ b/avmd/src/avmd.rs
@@ -0,0 +1,148 @@
+// 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.
+
+use apexutil::to_hex_string;
+use serde::{Deserialize, Serialize};
+use std::fmt;
+
+/// An Avmd struct contains
+/// - A header with version information that allows rollback when needed.
+/// - A list of descriptors that describe different images.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct Avmd {
+    header: Header,
+    descriptors: Vec<Descriptor>,
+}
+
+impl fmt::Display for Avmd {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(f, "Descriptors:")?;
+        for descriptor in &self.descriptors {
+            write!(f, "{}", descriptor)?;
+        }
+        Ok(())
+    }
+}
+
+impl Avmd {
+    /// Creates an instance of Avmd with a given list of descriptors.
+    pub fn new(descriptors: Vec<Descriptor>) -> Avmd {
+        Avmd { header: Header::default(), descriptors }
+    }
+}
+
+static AVMD_MAGIC: u32 = 0x444d5641;
+static AVMD_VERSION_MAJOR: u16 = 1;
+static AVMD_VERSION_MINOR: u16 = 0;
+
+/// Header information for AVMD.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+struct Header {
+    magic: u32,
+    version_major: u16,
+    version_minor: u16,
+}
+
+impl Default for Header {
+    fn default() -> Self {
+        Header {
+            magic: AVMD_MAGIC,
+            version_major: AVMD_VERSION_MAJOR,
+            version_minor: AVMD_VERSION_MINOR,
+        }
+    }
+}
+
+/// AVMD descriptor.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub enum Descriptor {
+    /// Descriptor type for the VBMeta images.
+    VbMeta(VbMetaDescriptor),
+    /// Descriptor type for APK.
+    Apk(ApkDescriptor),
+}
+
+impl fmt::Display for Descriptor {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Descriptor::VbMeta(descriptor) => write!(f, "{}", descriptor),
+            Descriptor::Apk(descriptor) => write!(f, "{}", descriptor),
+        }
+    }
+}
+
+/// VbMeta descriptor.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct VbMetaDescriptor {
+    /// The identifier of this resource.
+    #[serde(flatten)]
+    pub resource: ResourceIdentifier,
+    /// The SHA-512 [VBMeta digest][] calculated from the top-level VBMeta image.
+    ///
+    /// [VBMeta digest]: https://android.googlesource.com/platform/external/avb/+/master/README.md#the-vbmeta-digest
+    pub vbmeta_digest: Vec<u8>,
+}
+
+impl fmt::Display for VbMetaDescriptor {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(f, "  VBMeta descriptor:")?;
+        writeln!(f, "    namespace:             {}", self.resource.namespace)?;
+        writeln!(f, "    name:                  {}", self.resource.name)?;
+        writeln!(f, "    vbmeta digest:         {}", to_hex_string(&self.vbmeta_digest))?;
+        Ok(())
+    }
+}
+
+/// APK descriptor.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct ApkDescriptor {
+    /// The identifier of this resource.
+    #[serde(flatten)]
+    pub resource: ResourceIdentifier,
+    /// The ID of the algoithm used to sign the APK.
+    /// It should be one of the algorithms in the [list][].
+    ///
+    /// [list]: https://source.android.com/security/apksigning/v2#signature-algorithm-ids
+    pub signature_algorithm_id: u32,
+    /// Digest of the APK's v3 signing block. TODO: fix
+    pub apk_digest: Vec<u8>,
+}
+
+impl fmt::Display for ApkDescriptor {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(f, "  APK descriptor:")?;
+        writeln!(f, "    namespace:             {}", self.resource.namespace)?;
+        writeln!(f, "    name:                  {}", self.resource.name)?;
+        writeln!(f, "    Signing algorithm ID:  {:#x}", self.signature_algorithm_id)?;
+        writeln!(f, "    APK digest:            {}", to_hex_string(&self.apk_digest))?;
+        Ok(())
+    }
+}
+
+/// Resource identifier regroups information to identify resources.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct ResourceIdentifier {
+    /// Namespace of the resource.
+    namespace: String,
+    /// Name of the resource.
+    name: String,
+}
+
+impl ResourceIdentifier {
+    /// Creates an instance of ResourceIdentifier with the given
+    /// namespace and name.
+    pub fn new(namespace: &str, name: &str) -> ResourceIdentifier {
+        ResourceIdentifier { namespace: namespace.to_string(), name: name.to_string() }
+    }
+}
diff --git a/avmd/src/lib.rs b/avmd/src/lib.rs
new file mode 100644
index 0000000..9722518
--- /dev/null
+++ b/avmd/src/lib.rs
@@ -0,0 +1,19 @@
+// 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.
+
+//! Library for handling AVMD blobs.
+
+mod avmd;
+
+pub use avmd::{ApkDescriptor, Avmd, Descriptor, ResourceIdentifier, VbMetaDescriptor};
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(())
+}