Merge "Implement directory listing"
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/compos_key_cmd/Android.bp b/compos/compos_key_cmd/Android.bp
index 75ea090..9d5a490 100644
--- a/compos/compos_key_cmd/Android.bp
+++ b/compos/compos_key_cmd/Android.bp
@@ -8,7 +8,6 @@
     apex_available: ["com.android.compos"],
 
     static_libs: [
-        "lib_compos_proto",
         "lib_odsign_proto",
     ],
 
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index 159e9e9..f8b3d16 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -45,7 +45,6 @@
 #include <string_view>
 #include <thread>
 
-#include "compos_signature.pb.h"
 #include "odsign_info.pb.h"
 
 using namespace std::literals;
@@ -65,7 +64,6 @@
 using android::base::Result;
 using android::base::unique_fd;
 using android::base::WriteFully;
-using compos::proto::Signature;
 using ndk::ScopedAStatus;
 using ndk::ScopedFileDescriptor;
 using ndk::SharedRefBase;
@@ -441,77 +439,6 @@
     return std::vector(&digest->digest[0], &digest->digest[digest->digest_size]);
 }
 
-static Result<void> signFile(ICompOsService* service, const std::string& file) {
-    std::filesystem::path signature_path{file};
-    signature_path += ".signature";
-    unique_fd out_fd(TEMP_FAILURE_RETRY(open(signature_path.c_str(),
-                                             O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC,
-                                             S_IRUSR | S_IWUSR | S_IRGRP)));
-    if (!out_fd.ok()) {
-        return ErrnoError() << "Unable to create signature file";
-    }
-
-    auto digest = computeDigest(file);
-    if (!digest.ok()) {
-        return digest.error();
-    }
-
-    std::vector<uint8_t> buffer(sizeof(fsverity_formatted_digest) + digest->size());
-    auto to_be_signed = new (buffer.data()) fsverity_formatted_digest;
-    memcpy(to_be_signed->magic, "FSVerity", sizeof(to_be_signed->magic));
-    to_be_signed->digest_algorithm = __cpu_to_le16(FS_VERITY_HASH_ALG_SHA256);
-    to_be_signed->digest_size = __cpu_to_le16(digest->size());
-    memcpy(to_be_signed->digest, digest->data(), digest->size());
-
-    std::vector<uint8_t> signature;
-    auto status = service->sign(buffer, &signature);
-    if (!status.isOk()) {
-        return Error() << "Failed to sign: " << status.getDescription();
-    }
-
-    Signature compos_signature;
-    compos_signature.set_digest(digest->data(), digest->size());
-    compos_signature.set_signature(signature.data(), signature.size());
-    if (!compos_signature.SerializeToFileDescriptor(out_fd.get())) {
-        return Error() << "Failed to write signature";
-    }
-    if (close(out_fd.release()) != 0) {
-        return ErrnoError() << "Failed to close signature file";
-    }
-
-    return {};
-}
-
-static Result<void> sign(TargetVm& vm, const std::string& blob_file,
-                         const std::vector<std::string>& files) {
-    auto cid = vm.resolveCid();
-    if (!cid.ok()) {
-        return cid.error();
-    }
-    auto service = getService(*cid);
-    if (!service) {
-        return Error() << "No service";
-    }
-
-    auto blob = readBytesFromFile(blob_file);
-    if (!blob.ok()) {
-        return blob.error();
-    }
-
-    auto status = service->initializeSigningKey(blob.value());
-    if (!status.isOk()) {
-        return Error() << "Failed to initialize signing key: " << status.getDescription();
-    }
-
-    for (auto& file : files) {
-        auto result = signFile(service.get(), file);
-        if (!result.ok()) {
-            return Error() << result.error() << ": " << file;
-        }
-    }
-    return {};
-}
-
 static std::string toHex(const std::vector<uint8_t>& digest) {
     std::stringstream ss;
     for (auto it = digest.begin(); it != digest.end(); ++it) {
@@ -701,15 +628,6 @@
         } else {
             std::cerr << result.error() << '\n';
         }
-    } else if (argc >= 4 && argv[1] == "sign"sv) {
-        const std::vector<std::string> files{&argv[3], &argv[argc]};
-        auto result = sign(vm, argv[2], files);
-        if (result.ok()) {
-            std::cerr << "All signatures generated.\n";
-            return 0;
-        } else {
-            std::cerr << result.error() << '\n';
-        }
     } else if (argc >= 5 && argv[1] == "sign-info"sv) {
         const std::string blob_file = argv[2];
         const std::string info_file = argv[3];
@@ -744,9 +662,6 @@
                   << "  verify <blob file> <public key file> Verify that the content of the\n"
                   << "    specified private key blob and public key files are valid.\n "
                   << "  init-key <blob file> Initialize the service key.\n"
-                  << "  sign <blob file> <files to be signed> Generate signatures for one or\n"
-                  << "    more files using the supplied private key blob. Signature is stored \n"
-                  << "    in <filename>.signature\n"
                   << "  sign-info <blob file> <info file> <files to be signed> Generate\n"
                   << "    an info file listing the paths and root digests of each of the files to\n"
                   << "    be signed, along with a signature of that file.\n"
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>>;
-}
diff --git a/compos/tests/java/android/compos/test/ComposKeyTestCase.java b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
index 140f74b..eacf3fb 100644
--- a/compos/tests/java/android/compos/test/ComposKeyTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
@@ -141,12 +141,13 @@
         android.run(
                 COMPOS_KEY_CMD_BIN,
                 "--cid " + mCid,
-                "sign",
+                "sign-info",
                 TEST_ROOT + "test_key3.blob",
+                TEST_ROOT + "test.info",
                 "/data/local/tmp/something.txt");
 
         // Check existence of the output signature - should succeed
-        android.run("test -f /data/local/tmp/something.txt.signature");
+        android.run("test -f " + TEST_ROOT + "test.info.signature");
     }
 
     private void startVm() throws Exception {