Merge "Add bulk signing to compos_key_cmd" am: e275568b0d
Original change: https://android-review.googlesource.com/c/platform/packages/modules/Virtualization/+/1913505
Change-Id: I7de9b04d3781c7ea675f599106fb8efa024204d4
diff --git a/compos/compos_key_cmd/Android.bp b/compos/compos_key_cmd/Android.bp
index 36c1b5c..75ea090 100644
--- a/compos/compos_key_cmd/Android.bp
+++ b/compos/compos_key_cmd/Android.bp
@@ -9,6 +9,7 @@
static_libs: [
"lib_compos_proto",
+ "lib_odsign_proto",
],
shared_libs: [
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index 27c7275..159e9e9 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -39,12 +39,14 @@
#include <condition_variable>
#include <filesystem>
#include <iostream>
+#include <map>
#include <mutex>
#include <string>
#include <string_view>
#include <thread>
#include "compos_signature.pb.h"
+#include "odsign_info.pb.h"
using namespace std::literals;
@@ -62,10 +64,12 @@
using android::base::Fdopen;
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;
+using odsign::proto::OdsignInfo;
constexpr unsigned int kRpcPort = 6432;
@@ -403,21 +407,12 @@
return result;
}
-static Result<void> signFile(ICompOsService* service, const std::string& file) {
+static Result<std::vector<uint8_t>> computeDigest(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";
@@ -443,12 +438,30 @@
}
std::unique_ptr<libfsverity_digest, decltype(&std::free)> digestOwner{digest, std::free};
- std::vector<uint8_t> buffer(sizeof(fsverity_formatted_digest) + digest->digest_size);
+ 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(digest->digest_algorithm);
- to_be_signed->digest_size = __cpu_to_le16(digest->digest_size);
- memcpy(to_be_signed->digest, digest->digest, digest->digest_size);
+ 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);
@@ -457,7 +470,7 @@
}
Signature compos_signature;
- compos_signature.set_digest(digest->digest, digest->digest_size);
+ 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";
@@ -499,6 +512,87 @@
return {};
}
+static std::string toHex(const std::vector<uint8_t>& digest) {
+ std::stringstream ss;
+ for (auto it = digest.begin(); it != digest.end(); ++it) {
+ ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned>(*it);
+ }
+ return ss.str();
+}
+
+static Result<void> signInfo(TargetVm& vm, const std::string& blob_file,
+ const std::string& info_file, const std::vector<std::string>& files) {
+ unique_fd info_fd(
+ TEMP_FAILURE_RETRY(open(info_file.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC,
+ S_IRUSR | S_IWUSR | S_IRGRP)));
+ if (!info_fd.ok()) {
+ return ErrnoError() << "Unable to create " << info_file;
+ }
+
+ std::string signature_file = info_file + ".signature";
+ unique_fd signature_fd(TEMP_FAILURE_RETRY(open(signature_file.c_str(),
+ O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC,
+ S_IRUSR | S_IWUSR | S_IRGRP)));
+ if (!signature_fd.ok()) {
+ return ErrnoError() << "Unable to create " << signature_file;
+ }
+
+ 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 initialized = service->initializeSigningKey(blob.value());
+ if (!initialized.isOk()) {
+ return Error() << "Failed to initialize signing key: " << initialized.getDescription();
+ }
+
+ std::map<std::string, std::string> file_digests;
+
+ for (auto& file : files) {
+ auto digest = computeDigest(file);
+ if (!digest.ok()) {
+ return digest.error();
+ }
+ file_digests.emplace(file, toHex(*digest));
+ }
+
+ OdsignInfo info;
+ info.mutable_file_hashes()->insert(file_digests.begin(), file_digests.end());
+
+ std::vector<uint8_t> serialized(info.ByteSizeLong());
+ if (!info.SerializeToArray(serialized.data(), serialized.size())) {
+ return Error() << "Failed to serialize protobuf";
+ }
+
+ if (!WriteFully(info_fd, serialized.data(), serialized.size()) ||
+ close(info_fd.release()) != 0) {
+ return Error() << "Failed to write info file";
+ }
+
+ std::vector<uint8_t> signature;
+ auto status = service->sign(serialized, &signature);
+ if (!status.isOk()) {
+ return Error() << "Failed to sign: " << status.getDescription();
+ }
+
+ if (!WriteFully(signature_fd, signature.data(), signature.size()) ||
+ close(signature_fd.release()) != 0) {
+ return Error() << "Failed to write signature";
+ }
+
+ return {};
+}
+
static Result<void> initializeKey(TargetVm& vm, const std::string& blob_file) {
auto cid = vm.resolveCid();
if (!cid.ok()) {
@@ -616,6 +710,17 @@
} 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];
+ const std::vector<std::string> files{&argv[4], &argv[argc]};
+ auto result = signInfo(vm, blob_file, info_file, files);
+ if (result.ok()) {
+ std::cerr << "Info file generated and signed.\n";
+ return 0;
+ } else {
+ std::cerr << result.error() << '\n';
+ }
} else if (argc == 3 && argv[1] == "init-key"sv) {
auto result = initializeKey(vm, argv[2]);
if (result.ok()) {
@@ -631,16 +736,20 @@
std::cerr << result.error() << '\n';
}
} else {
- std::cerr << "Usage: compos_key_cmd [OPTIONS] generate|verify|sign|make-instance|init-key\n"
+ std::cerr << "Usage: compos_key_cmd [OPTIONS] COMMAND\n"
+ << "Where COMMAND can be:\n"
+ << " make-instance <image file> Create an empty instance image file for a VM.\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 "
<< " 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 in\n"
- << " <filename>.signature\n"
- << " make-instance <image file> Create an empty instance image file for a VM.\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"
<< "\n"
<< "OPTIONS: --log <log file> --debug --staged\n"
<< " (--cid <cid> | --start <image file>)\n"