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/apex/Android.bp b/apex/Android.bp
index 4c2c00c..c875131 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -48,7 +48,6 @@
binaries: [
"fd_server",
"vm",
- "compos_key_cmd",
],
java_libs: [
"android.system.virtualmachine",
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, ¶ms, &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;