Add code to bulk-sign artifacts

This isn't wired up yet - we need to be able to iterate over AuthFs
directories, but that's coming soon.

Import the OdsignInfo proto, which contains the map of filenames and
digests, and provide a way to build it from a set of artifact files
and write it along with its signature.

Also remove the old Signer trait (which is unused), and a couple of
small refactorings.

Bug: 161471326
Test: Builds
Change-Id: I37fccb1f2ca4fa1ea3006185d9f805d668252e2a
diff --git a/compos/Android.bp b/compos/Android.bp
index b9fcfff..783ba22 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -50,6 +50,8 @@
         "liblog_rust",
         "libminijail_rust",
         "libnix",
+        "libodsign_proto_rust",
+        "libprotobuf",
         "libring",
         "libscopeguard",
     ],
diff --git a/compos/src/artifact_signer.rs b/compos/src/artifact_signer.rs
new file mode 100644
index 0000000..54b8d7a
--- /dev/null
+++ b/compos/src/artifact_signer.rs
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! Support for generating and signing an info file listing names and digests of generated
+//! artifacts.
+
+#![allow(dead_code)] // Will be used soon
+
+use crate::compos_key_service::CompOsKeyService;
+use crate::fsverity;
+use anyhow::{anyhow, Context, Result};
+use odsign_proto::odsign_info::OdsignInfo;
+use protobuf::Message;
+use std::fs::File;
+use std::io::Write;
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
+
+const TARGET_DIRECTORY: &str = "/data/misc/apexdata/com.android.art/dalvik-cache";
+const SIGNATURE_EXTENSION: &str = ".signature";
+
+/// Accumulates and then signs information about generated artifacts.
+pub struct ArtifactSigner<'a> {
+    key_blob: Vec<u8>,
+    key_service: &'a CompOsKeyService,
+    base_directory: PathBuf,
+    file_digests: Vec<(String, String)>, // (File name, digest in hex)
+}
+
+impl<'a> ArtifactSigner<'a> {
+    /// base_directory specifies the directory under which the artifacts are currently located;
+    /// they will eventually be moved under TARGET_DIRECTORY once they are verified and activated.
+    pub fn new(
+        key_blob: Vec<u8>,
+        key_service: &'a CompOsKeyService,
+        base_directory: PathBuf,
+    ) -> Self {
+        ArtifactSigner { key_blob, key_service, base_directory, file_digests: Vec::new() }
+    }
+
+    pub fn add_artifact(&mut self, path: PathBuf) -> Result<()> {
+        // The path we store is where the file will be when it is verified, not where it is now.
+        let suffix = path
+            .strip_prefix(&self.base_directory)
+            .context("Artifacts must be under base directory")?;
+        let target_path = Path::new(TARGET_DIRECTORY).join(suffix);
+        let target_path = target_path.to_str().ok_or_else(|| anyhow!("Invalid path"))?;
+
+        let file = File::open(&path)?;
+        let digest = fsverity::measure(file.as_raw_fd())?;
+        let digest = to_hex_string(&digest);
+
+        self.file_digests.push((target_path.to_owned(), digest));
+        Ok(())
+    }
+
+    /// Consume this ArtifactSigner and write details of all its artifacts to the given path,
+    /// with accompanying sigature file.
+    pub fn write_info_and_signature(self, info_path: &Path) -> Result<()> {
+        let mut info = OdsignInfo::new();
+        info.mut_file_hashes().extend(self.file_digests.into_iter());
+        let bytes = info.write_to_bytes()?;
+
+        let signature = self.key_service.sign(&self.key_blob, &bytes)?;
+
+        let mut file = File::create(info_path)?;
+        file.write_all(&bytes)?;
+
+        let mut signature_name = info_path.file_name().unwrap().to_owned();
+        signature_name.push(SIGNATURE_EXTENSION);
+        let signature_path = info_path.with_file_name(&signature_name);
+        let mut signature_file = File::create(&signature_path)?;
+        signature_file.write_all(&signature)?;
+
+        Ok(())
+    }
+}
+
+fn to_hex_string(buf: &[u8]) -> String {
+    buf.iter().map(|b| format!("{:02x}", b)).collect()
+}
diff --git a/compos/src/compos_key_service.rs b/compos/src/compos_key_service.rs
index 4a1566d..f6caac9 100644
--- a/compos/src/compos_key_service.rs
+++ b/compos/src/compos_key_service.rs
@@ -77,7 +77,7 @@
         })
     }
 
-    pub fn do_generate(&self) -> Result<CompOsKeyData> {
+    pub fn generate(&self) -> Result<CompOsKeyData> {
         let key_descriptor = BLOB_KEY_DESCRIPTOR;
         let key_parameters =
             [PURPOSE_SIGN, ALGORITHM, PADDING, DIGEST, KEY_SIZE, EXPONENT, NO_AUTH_REQUIRED];
@@ -97,11 +97,11 @@
         }
     }
 
-    pub fn do_verify(&self, key_blob: &[u8], public_key: &[u8]) -> Result<()> {
+    pub fn verify(&self, key_blob: &[u8], public_key: &[u8]) -> Result<()> {
         let mut data = [0u8; 32];
         self.random.fill(&mut data).context("No random data")?;
 
-        let signature = self.do_sign(key_blob, &data)?;
+        let signature = self.sign(key_blob, &data)?;
 
         let public_key =
             signature::UnparsedPublicKey::new(&signature::RSA_PKCS1_2048_8192_SHA256, public_key);
@@ -110,7 +110,7 @@
         Ok(())
     }
 
-    pub fn do_sign(&self, key_blob: &[u8], data: &[u8]) -> Result<Vec<u8>> {
+    pub fn sign(&self, key_blob: &[u8], data: &[u8]) -> Result<Vec<u8>> {
         let key_descriptor = KeyDescriptor { blob: Some(key_blob.to_vec()), ..BLOB_KEY_DESCRIPTOR };
         let operation_parameters = [PURPOSE_SIGN, ALGORITHM, PADDING, DIGEST];
         let forced = false;
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 0a15876..19f2f47 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -24,7 +24,7 @@
 use std::default::Default;
 use std::env;
 use std::path::PathBuf;
-use std::sync::{Arc, RwLock};
+use std::sync::RwLock;
 
 use crate::compilation::{compile_cmd, odrefresh, CompilerOutput};
 use crate::compos_key_service::CompOsKeyService;
@@ -50,7 +50,7 @@
         dex2oat_path: PathBuf::from(DEX2OAT_PATH),
         odrefresh_path: PathBuf::from(ODREFRESH_PATH),
         key_service: CompOsKeyService::new()?,
-        key_blob: Arc::new(RwLock::new(Vec::new())),
+        key_blob: RwLock::new(Vec::new()),
     };
     Ok(BnCompOsService::new_binder(service, BinderFeatures::default()))
 }
@@ -59,7 +59,7 @@
     dex2oat_path: PathBuf,
     odrefresh_path: PathBuf,
     key_service: CompOsKeyService,
-    key_blob: Arc<RwLock<Vec<u8>>>,
+    key_blob: RwLock<Vec<u8>>,
 }
 
 impl CompOsService {
@@ -69,7 +69,7 @@
         fsverity_digest: &fsverity::Sha256Digest,
     ) -> Vec<u8> {
         let formatted_digest = fsverity::to_formatted_digest(fsverity_digest);
-        self.key_service.do_sign(key_blob, &formatted_digest[..]).unwrap_or_else(|e| {
+        self.key_service.sign(key_blob, &formatted_digest[..]).unwrap_or_else(|e| {
             warn!("Failed to sign the fsverity digest, returning empty signature.  Error: {}", e);
             Vec::new()
         })
@@ -189,12 +189,12 @@
 
     fn generateSigningKey(&self) -> BinderResult<CompOsKeyData> {
         self.key_service
-            .do_generate()
+            .generate()
             .map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))
     }
 
     fn verifySigningKey(&self, key_blob: &[u8], public_key: &[u8]) -> BinderResult<bool> {
-        Ok(if let Err(e) = self.key_service.do_verify(key_blob, public_key) {
+        Ok(if let Err(e) = self.key_service.verify(key_blob, public_key) {
             warn!("Signing key verification failed: {}", e.to_string());
             false
         } else {
@@ -208,7 +208,7 @@
             Err(new_binder_exception(ExceptionCode::ILLEGAL_STATE, "Key is not initialized"))
         } else {
             self.key_service
-                .do_sign(key, data)
+                .sign(key, data)
                 .map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))
         }
     }
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index fc00039..9347905 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -16,11 +16,11 @@
 
 //! A tool to start a standalone compsvc server that serves over RPC binder.
 
+mod artifact_signer;
 mod compilation;
 mod compos_key_service;
 mod compsvc;
 mod fsverity;
-mod signer;
 
 use android_system_virtualmachineservice::{
     aidl::android::system::virtualmachineservice::IVirtualMachineService::{
diff --git a/compos/src/signer.rs b/compos/src/signer.rs
deleted file mode 100644
index 9ff1477..0000000
--- a/compos/src/signer.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-use anyhow::Result;
-
-/// Provides the ability to cryptographically sign messages.
-pub trait Signer: Send + Sync {
-    /// Sign the supplied data. The result is a raw signature over the input data.
-    fn sign(&self, data: &[u8]) -> Result<Vec<u8>>;
-}