Support CompOS Key Service in a VM

Add the ability to start the service in a VM, and to then communicate
with it from the host via RPC Binder. Update command-line syntax.

Also revert my recent client naming change, since one tool that can
handle both host & VM seems better than two different ones.

Bug: 193603140
Test: Manual: start service in VM, connect to it, generate & verify keys.
Change-Id: I3cdb25395537e29bbfaa957eeac0c16ba4de93de
diff --git a/compos/Android.bp b/compos/Android.bp
index ba270b8..9428210 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -67,8 +67,8 @@
 }
 
 rust_binary {
-    name: "compos_key_host",
-    srcs: ["src/compos_key_host_main.rs"],
+    name: "compos_key_main",
+    srcs: ["src/compos_key_main.rs"],
     edition: "2018",
     rustlibs: [
         "compos_aidl_interface-rust",
@@ -76,10 +76,16 @@
         "android.hardware.security.keymint-V1-rust",
         "libandroid_logger",
         "libanyhow",
+        "libbinder_rs",
+        "libbinder_rpc_unstable_bindgen",
+        "libclap",
         "liblog_rust",
         "libring",
         "libscopeguard",
     ],
     prefer_rlib: true,
+    shared_libs: [
+        "libbinder_rpc_unstable",
+    ],
     apex_available: ["com.android.compos"],
 }
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
index c4ab321..7638c41 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -38,7 +38,7 @@
 
     binaries: [
         "compos_key_cmd",
-        "compos_key_host",
+        "compos_key_main",
         "compsvc",
         "compsvc_worker",
         "pvm_exec",
diff --git a/compos/apk/assets/key_service_vm_config.json b/compos/apk/assets/key_service_vm_config.json
new file mode 100644
index 0000000..3b6b88c
--- /dev/null
+++ b/compos/apk/assets/key_service_vm_config.json
@@ -0,0 +1,18 @@
+{
+    "version": 1,
+    "os": {
+        "name": "microdroid"
+    },
+    "task": {
+        "type": "executable",
+        "command": "/apex/com.android.compos/bin/compos_key_main",
+        "args": [
+            "--rpc-binder"
+        ]
+    },
+    "apexes": [
+        {
+            "name": "com.android.compos"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/compos/compos_key_cmd/Android.bp b/compos/compos_key_cmd/Android.bp
index 460b96f..00d1035 100644
--- a/compos/compos_key_cmd/Android.bp
+++ b/compos/compos_key_cmd/Android.bp
@@ -14,6 +14,7 @@
     shared_libs: [
         "compos_aidl_interface-ndk_platform",
         "libbase",
+        "libbinder_rpc_unstable",
         "libbinder_ndk",
         "libcrypto",
         "libfsverity",
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index 3fe843a..bee9de1 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -35,6 +35,11 @@
 
 #include "compos_signature.pb.h"
 
+// From frameworks/native/libs/binder/rust/src/binder_rpc_unstable.hpp
+extern "C" {
+AIBinder* RpcClient(unsigned int cid, unsigned int port);
+}
+
 using namespace std::literals;
 
 using aidl::com::android::compos::CompOsKeyData;
@@ -45,6 +50,8 @@
 using android::base::unique_fd;
 using compos::proto::Signature;
 
+const unsigned int kRpcPort = 3142;
+
 static bool writeBytesToFile(const std::vector<uint8_t>& bytes, const std::string& path) {
     std::string str(bytes.begin(), bytes.end());
     return android::base::WriteStringToFile(str, path);
@@ -58,6 +65,12 @@
     return std::vector<uint8_t>(str.begin(), str.end());
 }
 
+static std::shared_ptr<ICompOsKeyService> getService(int cid) {
+    ndk::SpAIBinder binder(cid == 0 ? AServiceManager_getService("android.system.composkeyservice")
+                                    : RpcClient(cid, kRpcPort));
+    return ICompOsKeyService::fromBinder(binder);
+}
+
 static Result<std::vector<uint8_t>> extractRsaPublicKey(
         const std::vector<uint8_t>& der_certificate) {
     auto data = der_certificate.data();
@@ -89,9 +102,9 @@
     return result;
 }
 
-static Result<void> generate(const std::string& blob_file, const std::string& public_key_file) {
-    ndk::SpAIBinder binder(AServiceManager_getService("android.system.composkeyservice"));
-    auto service = ICompOsKeyService::fromBinder(binder);
+static Result<void> generate(int cid, const std::string& blob_file,
+                             const std::string& public_key_file) {
+    auto service = getService(cid);
     if (!service) {
         return Error() << "No service";
     }
@@ -117,9 +130,9 @@
     return {};
 }
 
-static Result<bool> verify(const std::string& blob_file, const std::string& public_key_file) {
-    ndk::SpAIBinder binder(AServiceManager_getService("android.system.composkeyservice"));
-    auto service = ICompOsKeyService::fromBinder(binder);
+static Result<bool> verify(int cid, const std::string& blob_file,
+                           const std::string& public_key_file) {
+    auto service = getService(cid);
     if (!service) {
         return Error() << "No service";
     }
@@ -210,9 +223,9 @@
     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);
+static Result<void> sign(int cid, const std::string& blob_file,
+                         const std::vector<std::string>& files) {
+    auto service = getService(cid);
     if (!service) {
         return Error() << "No service";
     }
@@ -235,15 +248,26 @@
     // Restrict access to our outputs to the current user.
     umask(077);
 
-    if (argc == 4 && argv[1] == "--generate"sv) {
-        auto result = generate(argv[2], argv[3]);
+    int cid = 0;
+    if (argc >= 3 && argv[1] == "--cid"sv) {
+        cid = atoi(argv[2]);
+        if (cid == 0) {
+            std::cerr << "Invalid cid\n";
+            return 1;
+        }
+        argc -= 2;
+        argv += 2;
+    }
+
+    if (argc == 4 && argv[1] == "generate"sv) {
+        auto result = generate(cid, argv[2], argv[3]);
         if (result.ok()) {
             return 0;
         } else {
             std::cerr << result.error() << '\n';
         }
-    } else if (argc == 4 && argv[1] == "--verify"sv) {
-        auto result = verify(argv[2], argv[3]);
+    } else if (argc == 4 && argv[1] == "verify"sv) {
+        auto result = verify(cid, argv[2], argv[3]);
         if (result.ok()) {
             if (result.value()) {
                 std::cerr << "Key files are valid.\n";
@@ -254,9 +278,9 @@
         } else {
             std::cerr << result.error() << '\n';
         }
-    } else if (argc >= 4 && argv[1] == "--sign"sv) {
+    } else if (argc >= 4 && argv[1] == "sign"sv) {
         const std::vector<std::string> files{&argv[3], &argv[argc]};
-        auto result = sign(argv[2], files);
+        auto result = sign(cid, argv[2], files);
         if (result.ok()) {
             std::cerr << "All signatures generated.\n";
             return 0;
@@ -264,14 +288,15 @@
             std::cerr << result.error() << '\n';
         }
     } else {
-        std::cerr << "Usage: \n"
-                  << "  --generate <blob file> <public key file> Generate new key pair and "
+        std::cerr << "Usage: compos_key_cmd [--cid <cid>] generate|verify|sign\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"
+                  << "  verify <blob file> <public key file> Verify that the content of the\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";
+                  << "  sign <blob file> <files to be signed> Generate signatures for one or\n"
+                  << "    more files using the supplied private key blob.\n"
+                  << "Specify --cid to connect to a VM rather than the host\n";
     }
     return 1;
 }
diff --git a/compos/src/compos_key_host_main.rs b/compos/src/compos_key_host_main.rs
deleted file mode 100644
index 28b069a..0000000
--- a/compos/src/compos_key_host_main.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 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.
-
-//! Run the CompOS key management service in the host, using normal Binder.
-
-mod compos_key_service;
-
-use crate::compos_key_service::CompOsKeyService;
-use anyhow::{Context, Result};
-use compos_aidl_interface::aidl::com::android::compos::ICompOsKeyService::BnCompOsKeyService;
-use compos_aidl_interface::binder::{add_service, BinderFeatures, ProcessState};
-use log::{info, Level};
-
-const LOG_TAG: &str = "CompOsKeyService";
-const OUR_SERVICE_NAME: &str = "android.system.composkeyservice";
-
-fn main() -> Result<()> {
-    android_logger::init_once(
-        android_logger::Config::default().with_tag(LOG_TAG).with_min_level(Level::Trace),
-    );
-
-    // We need to start the thread pool for Binder to work properly.
-    ProcessState::start_thread_pool();
-
-    let service = CompOsKeyService::new()?;
-    let service = BnCompOsKeyService::new_binder(service, BinderFeatures::default());
-
-    add_service(OUR_SERVICE_NAME, service.as_binder()).context("Adding service failed")?;
-    info!("It's alive!");
-
-    ProcessState::join_thread_pool();
-
-    Ok(())
-}
diff --git a/compos/src/compos_key_main.rs b/compos/src/compos_key_main.rs
new file mode 100644
index 0000000..9135c18
--- /dev/null
+++ b/compos/src/compos_key_main.rs
@@ -0,0 +1,72 @@
+// Copyright 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.
+
+//! Run the CompOS key management service, either in the host using normal Binder or in the
+//! VM using RPC Binder.
+
+mod compos_key_service;
+
+use crate::compos_key_service::{CompOsKeyService, KeystoreNamespace};
+use anyhow::{bail, Context, Result};
+use binder::unstable_api::AsNative;
+use compos_aidl_interface::aidl::com::android::compos::ICompOsKeyService::BnCompOsKeyService;
+use compos_aidl_interface::binder::{add_service, BinderFeatures, ProcessState};
+use log::{info, Level};
+
+const LOG_TAG: &str = "CompOsKeyService";
+const OUR_SERVICE_NAME: &str = "android.system.composkeyservice";
+const OUR_VSOCK_PORT: u32 = 3142;
+
+fn main() -> Result<()> {
+    android_logger::init_once(
+        android_logger::Config::default().with_tag(LOG_TAG).with_min_level(Level::Info),
+    );
+
+    let matches = clap::App::new("compos_key_main")
+        .arg(clap::Arg::with_name("rpc_binder").long("rpc-binder"))
+        .get_matches();
+
+    let rpc_binder = matches.is_present("rpc_binder");
+
+    let key_namespace =
+        if rpc_binder { KeystoreNamespace::VmPayload } else { KeystoreNamespace::Odsign };
+    let service = CompOsKeyService::new(key_namespace)?;
+    let mut service =
+        BnCompOsKeyService::new_binder(service, BinderFeatures::default()).as_binder();
+
+    if rpc_binder {
+        info!("Starting RPC service");
+        // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+        // Plus the binder objects are threadsafe.
+        let retval = unsafe {
+            binder_rpc_unstable_bindgen::RunRpcServer(
+                service.as_native_mut() as *mut binder_rpc_unstable_bindgen::AIBinder,
+                OUR_VSOCK_PORT,
+            )
+        };
+        if retval {
+            info!("RPC server has shut down gracefully");
+        } else {
+            bail!("Premature termination of RPC server");
+        }
+    } else {
+        info!("Starting binder service");
+        add_service(OUR_SERVICE_NAME, service).context("Adding service failed")?;
+        info!("It's alive!");
+
+        ProcessState::join_thread_pool();
+    }
+
+    Ok(())
+}
diff --git a/compos/src/compos_key_service.rs b/compos/src/compos_key_service.rs
index 654eedd..66451b3 100644
--- a/compos/src/compos_key_service.rs
+++ b/compos/src/compos_key_service.rs
@@ -38,8 +38,17 @@
 use scopeguard::ScopeGuard;
 use std::ffi::CString;
 
+/// Keystore2 namespace IDs, used for access control to keys.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum KeystoreNamespace {
+    /// In the host we re-use the ID assigned to odsign. See system/sepolicy/private/keystore2_key_contexts.
+    // TODO(alanstokes): Remove this.
+    Odsign = 101,
+    /// In a VM we can use the generic ID allocated for payloads. See microdroid's keystore2_key_contexts.
+    VmPayload = 140,
+}
+
 const KEYSTORE_SERVICE_NAME: &str = "android.system.keystore2.IKeystoreService/default";
-const COMPOS_NAMESPACE: i64 = 101;
 const PURPOSE_SIGN: KeyParameter =
     KeyParameter { tag: Tag::PURPOSE, value: KeyParameterValue::KeyPurpose(KeyPurpose::SIGN) };
 const ALGORITHM: KeyParameter =
@@ -57,10 +66,11 @@
 const NO_AUTH_REQUIRED: KeyParameter =
     KeyParameter { tag: Tag::NO_AUTH_REQUIRED, value: KeyParameterValue::BoolValue(true) };
 
-const KEY_DESCRIPTOR: KeyDescriptor =
-    KeyDescriptor { domain: Domain::BLOB, nspace: COMPOS_NAMESPACE, alias: None, blob: None };
+const BLOB_KEY_DESCRIPTOR: KeyDescriptor =
+    KeyDescriptor { domain: Domain::BLOB, nspace: 0, alias: None, blob: None };
 
 pub struct CompOsKeyService {
+    namespace: KeystoreNamespace,
     random: SystemRandom,
     security_level: Strong<dyn IKeystoreSecurityLevel>,
 }
@@ -94,11 +104,12 @@
 }
 
 impl CompOsKeyService {
-    pub fn new() -> Result<Self> {
+    pub fn new(namespace: KeystoreNamespace) -> Result<Self> {
         let keystore_service = wait_for_interface::<dyn IKeystoreService>(KEYSTORE_SERVICE_NAME)
             .context("No Keystore service")?;
 
         Ok(Self {
+            namespace,
             random: SystemRandom::new(),
             security_level: keystore_service
                 .getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT)
@@ -107,6 +118,7 @@
     }
 
     fn do_generate(&self) -> Result<CompOsKeyData> {
+        let key_descriptor = KeyDescriptor { nspace: self.namespace as i64, ..BLOB_KEY_DESCRIPTOR };
         let key_parameters =
             [PURPOSE_SIGN, ALGORITHM, PADDING, DIGEST, KEY_SIZE, EXPONENT, NO_AUTH_REQUIRED];
         let attestation_key = None;
@@ -115,7 +127,7 @@
 
         let key_metadata = self
             .security_level
-            .generateKey(&KEY_DESCRIPTOR, attestation_key, &key_parameters, flags, &entropy)
+            .generateKey(&key_descriptor, attestation_key, &key_parameters, flags, &entropy)
             .context("Generating key failed")?;
 
         if let (Some(certificate), Some(blob)) = (key_metadata.certificate, key_metadata.key.blob) {
@@ -139,7 +151,11 @@
     }
 
     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 key_descriptor = KeyDescriptor {
+            nspace: self.namespace as i64,
+            blob: Some(key_blob.to_vec()),
+            ..BLOB_KEY_DESCRIPTOR
+        };
         let operation_parameters = [PURPOSE_SIGN, ALGORITHM, PADDING, DIGEST];
         let forced = false;