Add signing command for testing.

Extend the AIDL to allow for signing. This will need to be backed out
before shipping, but it's needed for testing right now.

Add a command to sign a set of files to compos_key_cmd & write the
signature files.

Move compos_key_cmd from virt to compos APEX. (We're definitely going
to need this code in CompOS in some form, so might as well make sure
all the dependencies are available.)

Bug: 190166662
Test: Manual: Sign artifacts, check odsign accepts them.
Change-Id: I25361ed0bee52a9ff13924c77d9378efe8bfd314
diff --git a/compos/aidl/Android.bp b/compos/aidl/Android.bp
index 07bec09..4d36d3d 100644
--- a/compos/aidl/Android.bp
+++ b/compos/aidl/Android.bp
@@ -17,7 +17,7 @@
         },
         ndk: {
             apex_available: [
-                "com.android.virt",
+                "com.android.compos",
             ],
         },
     },
diff --git a/compos/aidl/com/android/compos/ICompOsKeyService.aidl b/compos/aidl/com/android/compos/ICompOsKeyService.aidl
index 2ddae58..a2ff917 100644
--- a/compos/aidl/com/android/compos/ICompOsKeyService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsKeyService.aidl
@@ -37,4 +37,15 @@
      * @return whether the inputs are valid and correspond to each other.
      */
     boolean verifySigningKey(in byte[] keyBlob, in byte[] publicKey);
+
+    /**
+     * Use the supplied encrypted private key to sign some data.
+     *
+     * @param keyBlob The encrypted blob containing the private key, as returned by
+     *                generateSigningKey().
+     * @param data The data to be signed. (Large data sizes may cause failure.)
+     * @return the signature.
+     */
+    // STOPSHIP(b/193241041): We must not expose this from the PVM.
+    byte[] sign(in byte[] keyBlob, in byte[] data);
 }
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
index 1fffa2e..2dded99 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -37,6 +37,7 @@
     platform_apis: true,
 
     binaries: [
+        "compos_key_cmd",
         "compos_key_service",
         "compsvc",
         "compsvc_worker",
diff --git a/compos/compos_key_cmd/Android.bp b/compos/compos_key_cmd/Android.bp
index e03dfdf..460b96f 100644
--- a/compos/compos_key_cmd/Android.bp
+++ b/compos/compos_key_cmd/Android.bp
@@ -5,12 +5,18 @@
 cc_binary {
     name: "compos_key_cmd",
     srcs: ["compos_key_cmd.cpp"],
-    apex_available: ["com.android.virt"],
+    apex_available: ["com.android.compos"],
+
+    static_libs: [
+        "lib_compos_proto",
+    ],
 
     shared_libs: [
         "compos_aidl_interface-ndk_platform",
         "libbase",
         "libbinder_ndk",
         "libcrypto",
+        "libfsverity",
+        "libprotobuf-cpp-lite",
     ],
 }
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index d98dac5..5047a48 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -17,21 +17,33 @@
 #include <aidl/com/android/compos/ICompOsKeyService.h>
 #include <android-base/file.h>
 #include <android-base/result.h>
+#include <android-base/unique_fd.h>
 #include <android/binder_auto_utils.h>
 #include <android/binder_manager.h>
+#include <asm/byteorder.h>
+#include <libfsverity.h>
+#include <linux/fsverity.h>
 #include <openssl/evp.h>
 #include <openssl/mem.h>
-#include <openssl/rsa.h>
+#include <openssl/sha.h>
 #include <openssl/x509.h>
 
+#include <filesystem>
 #include <iostream>
 #include <string>
+#include <string_view>
 
-using android::base::Error;
-using android::base::Result;
+#include "compos_signature.pb.h"
+
+using namespace std::literals;
 
 using aidl::com::android::compos::CompOsKeyData;
 using aidl::com::android::compos::ICompOsKeyService;
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+using android::base::unique_fd;
+using compos::proto::Signature;
 
 static bool writeBytesToFile(const std::vector<uint8_t>& bytes, const std::string& path) {
     std::string str(bytes.begin(), bytes.end());
@@ -131,15 +143,103 @@
     return result;
 }
 
+static Result<void> signFile(ICompOsKeyService* service, const std::vector<uint8_t>& key_blob,
+                             const std::string& file) {
+    unique_fd fd(TEMP_FAILURE_RETRY(open(file.c_str(), O_RDONLY | O_CLOEXEC)));
+    if (!fd.ok()) {
+        return ErrnoError() << "Failed to open";
+    }
+
+    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";
+    }
+
+    struct stat filestat;
+    if (fstat(fd, &filestat) != 0) {
+        return ErrnoError() << "Failed to fstat";
+    }
+
+    struct libfsverity_merkle_tree_params params = {
+            .version = 1,
+            .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
+            .file_size = static_cast<uint64_t>(filestat.st_size),
+            .block_size = 4096,
+    };
+
+    auto read_callback = [](void* file, void* buf, size_t count) {
+        int* fd = static_cast<int*>(file);
+        if (TEMP_FAILURE_RETRY(read(*fd, buf, count)) < 0) return -errno;
+        return 0;
+    };
+
+    struct libfsverity_digest* digest;
+    int ret = libfsverity_compute_digest(&fd, read_callback, &params, &digest);
+    if (ret < 0) {
+        return Error(-ret) << "Failed to compute fs-verity digest";
+    }
+    std::unique_ptr<libfsverity_digest, decltype(&std::free)> digestOwner{digest, std::free};
+
+    std::vector<uint8_t> buffer(sizeof(fsverity_formatted_digest) + 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(digest->digest_algorithm);
+    to_be_signed->digest_size = __cpu_to_le16(digest->digest_size);
+    memcpy(to_be_signed->digest, digest->digest, digest->digest_size);
+
+    std::vector<uint8_t> signature;
+    auto status = service->sign(key_blob, buffer, &signature);
+    if (!status.isOk()) {
+        return Error() << "Failed to sign: " << status.getDescription();
+    }
+
+    Signature compos_signature;
+    compos_signature.set_digest(digest->digest, digest->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(const std::string& blob_file, const std::vector<std::string>& files) {
+    ndk::SpAIBinder binder(AServiceManager_getService("android.system.composkeyservice"));
+    auto service = ICompOsKeyService::fromBinder(binder);
+    if (!service) {
+        return Error() << "No service";
+    }
+
+    auto blob = readBytesFromFile(blob_file);
+    if (!blob.ok()) {
+        return blob.error();
+    }
+
+    for (auto& file : files) {
+        auto result = signFile(service.get(), blob.value(), file);
+        if (!result.ok()) {
+            return Error() << result.error() << ": " << file;
+        }
+    }
+    return {};
+}
+
 int main(int argc, char** argv) {
-    if (argc == 4 && std::string(argv[1]) == "--generate") {
+    if (argc == 4 && argv[1] == "--generate"sv) {
         auto result = generate(argv[2], argv[3]);
         if (result.ok()) {
             return 0;
         } else {
             std::cerr << result.error() << '\n';
         }
-    } else if (argc == 4 && std::string(argv[1]) == "--verify") {
+    } else if (argc == 4 && argv[1] == "--verify"sv) {
         auto result = verify(argv[2], argv[3]);
         if (result.ok()) {
             if (result.value()) {
@@ -151,13 +251,24 @@
         } 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(argv[2], files);
+        if (result.ok()) {
+            std::cerr << "All signatures generated.\n";
+            return 0;
+        } else {
+            std::cerr << result.error() << '\n';
+        }
     } else {
         std::cerr << "Usage: \n"
                   << "  --generate <blob file> <public key file> Generate new key pair and "
                      "write\n"
                   << "    the private key blob and public key to the specified files.\n "
                   << "  --verify <blob file> <public key file> Verify that the content of the\n"
-                  << "    specified private key blob and public key files are valid.\n ";
+                  << "    specified private key blob and public key files are valid.\n "
+                  << "  --sign <blob file> <files to be signed> Generate signatures for one or\n"
+                  << "    more files using the supplied private key blob.\n";
     }
     return 1;
 }
diff --git a/compos/src/compos_key_service.rs b/compos/src/compos_key_service.rs
index 0cbe8de..993ef20 100644
--- a/compos/src/compos_key_service.rs
+++ b/compos/src/compos_key_service.rs
@@ -86,6 +86,11 @@
             true
         })
     }
+
+    fn sign(&self, key_blob: &[u8], data: &[u8]) -> binder::Result<Vec<u8>> {
+        self.do_sign(key_blob, data)
+            .map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))
+    }
 }
 
 /// Constructs a new Binder error `Status` with the given `ExceptionCode` and message.
@@ -126,7 +131,7 @@
         let mut data = [0u8; 32];
         self.random.fill(&mut data).context("No random data")?;
 
-        let signature = self.sign(key_blob, &data)?;
+        let signature = self.do_sign(key_blob, &data)?;
 
         let public_key =
             signature::UnparsedPublicKey::new(&signature::RSA_PKCS1_2048_8192_SHA256, public_key);
@@ -135,7 +140,7 @@
         Ok(())
     }
 
-    fn sign(&self, key_blob: &[u8], data: &[u8]) -> Result<Vec<u8>> {
+    fn do_sign(&self, key_blob: &[u8], data: &[u8]) -> Result<Vec<u8>> {
         let key_descriptor = KeyDescriptor { blob: Some(key_blob.to_vec()), ..KEY_DESCRIPTOR };
         let operation_parameters = [PURPOSE_SIGN, ALGORITHM, PADDING, DIGEST];
         let forced = false;