Add a CLI driver for compos_key_service.

This will eventually go away, but it's useful for verifying that the
service is working. But the cert -> RSAPublicKey code will end up
somewhere else eventually - either in C++ or Rust.

Bug: 191763370
Test: Manual - start service, generate key, verify. Inspect files.
Change-Id: I2181cf5331992a4236500545a9fdfd8640b57c1d
diff --git a/apex/Android.bp b/apex/Android.bp
index bf38860..2194c67 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -47,6 +47,7 @@
     binaries: [
         "fd_server",
         "vm",
+        "compos_key_cmd",
 
         // tools to create composite images
         "mk_cdisk",
diff --git a/compos/compos_key_cmd/Android.bp b/compos/compos_key_cmd/Android.bp
new file mode 100644
index 0000000..e03dfdf
--- /dev/null
+++ b/compos/compos_key_cmd/Android.bp
@@ -0,0 +1,16 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "compos_key_cmd",
+    srcs: ["compos_key_cmd.cpp"],
+    apex_available: ["com.android.virt"],
+
+    shared_libs: [
+        "compos_aidl_interface-ndk_platform",
+        "libbase",
+        "libbinder_ndk",
+        "libcrypto",
+    ],
+}
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
new file mode 100644
index 0000000..d98dac5
--- /dev/null
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -0,0 +1,163 @@
+/*
+ * 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.
+ */
+
+#include <aidl/com/android/compos/ICompOsKeyService.h>
+#include <android-base/file.h>
+#include <android-base/result.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_manager.h>
+#include <openssl/evp.h>
+#include <openssl/mem.h>
+#include <openssl/rsa.h>
+#include <openssl/x509.h>
+
+#include <iostream>
+#include <string>
+
+using android::base::Error;
+using android::base::Result;
+
+using aidl::com::android::compos::CompOsKeyData;
+using aidl::com::android::compos::ICompOsKeyService;
+
+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);
+}
+
+static Result<std::vector<uint8_t>> readBytesFromFile(const std::string& path) {
+    std::string str;
+    if (!android::base::ReadFileToString(path, &str)) {
+        return Error() << "Failed to read " << path;
+    }
+    return std::vector<uint8_t>(str.begin(), str.end());
+}
+
+static Result<std::vector<uint8_t>> extractRsaPublicKey(
+        const std::vector<uint8_t>& der_certificate) {
+    auto data = der_certificate.data();
+    bssl::UniquePtr<X509> x509(d2i_X509(nullptr, &data, der_certificate.size()));
+    if (!x509) {
+        return Error() << "Failed to parse certificate";
+    }
+    if (data != der_certificate.data() + der_certificate.size()) {
+        return Error() << "Certificate has unexpected trailing data";
+    }
+
+    bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(x509.get()));
+    if (EVP_PKEY_base_id(pkey.get()) != EVP_PKEY_RSA) {
+        return Error() << "Subject key is not RSA";
+    }
+    RSA* rsa = EVP_PKEY_get0_RSA(pkey.get());
+    if (!rsa) {
+        return Error() << "Failed to extract RSA key";
+    }
+
+    uint8_t* out = nullptr;
+    int size = i2d_RSAPublicKey(rsa, &out);
+    if (size < 0 || !out) {
+        return Error() << "Failed to convert to RSAPublicKey";
+    }
+
+    bssl::UniquePtr<uint8_t> buffer(out);
+    std::vector<uint8_t> result(out, out + size);
+    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);
+    if (!service) {
+        return Error() << "No service";
+    }
+
+    CompOsKeyData key_data;
+    auto status = service->generateSigningKey(&key_data);
+    if (!status.isOk()) {
+        return Error() << "Failed to generate key: " << status.getDescription();
+    }
+
+    auto public_key = extractRsaPublicKey(key_data.certificate);
+    if (!public_key.ok()) {
+        return Error() << "Failed to extract public key from cert: " << public_key.error();
+    }
+    if (!writeBytesToFile(key_data.keyBlob, blob_file)) {
+        return Error() << "Failed to write keyBlob to " << blob_file;
+    }
+
+    if (!writeBytesToFile(public_key.value(), public_key_file)) {
+        return Error() << "Failed to write public key to " << public_key_file;
+    }
+
+    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);
+    if (!service) {
+        return Error() << "No service";
+    }
+
+    auto blob = readBytesFromFile(blob_file);
+    if (!blob.ok()) {
+        return blob.error();
+    }
+
+    auto public_key = readBytesFromFile(public_key_file);
+    if (!public_key.ok()) {
+        return public_key.error();
+    }
+
+    bool result = false;
+    auto status = service->verifySigningKey(blob.value(), public_key.value(), &result);
+    if (!status.isOk()) {
+        return Error() << "Failed to verify key: " << status.getDescription();
+    }
+
+    return result;
+}
+
+int main(int argc, char** argv) {
+    if (argc == 4 && std::string(argv[1]) == "--generate") {
+        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") {
+        auto result = verify(argv[2], argv[3]);
+        if (result.ok()) {
+            if (result.value()) {
+                std::cerr << "Key files are valid.\n";
+                return 0;
+            } else {
+                std::cerr << "Key files are not valid.\n";
+            }
+        } 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 ";
+    }
+    return 1;
+}