On-device signing binary.
This is a first version of the on-device signing binary, that will be
used to sign ART compilation artifacts on an ART mainline module update.
The following basic functionality is implemented:
1) Creating a signing key (though not an early-boot key yet, because
those are broken on cuttlefish)
2) Creating an X.509 certificate from that key
3) Adding said certificate to the fs-verity keychain
4) Verify existing artifacts are in fs-verity (and delete them if
they're not)
5) Call odrefresh --check to have ART verify the artifacts are fresh and
correct
6) Call odrefresh --compile if they're not
7) Add generated output files to fs-verity
Important things left to do (not an exhaustive list):
1) Verify the key characteristics (eg, early-boot key)
2) Add a "manifest" file that records the signed files and their root
hashes; this will prevent us from accepting any random fs-verity file
3) Add a property or other signal to tell Zygote "we think these
artifacts are safe to use"
4) Migrate to keystore2 (once available)
Bug: 165630556
Test: Run odsign as root
Change-Id: I8aa09914d76f20f30c2093961271202fe7add711
diff --git a/ondevice-signing/odsign_main.cpp b/ondevice-signing/odsign_main.cpp
new file mode 100644
index 0000000..efe7d35
--- /dev/null
+++ b/ondevice-signing/odsign_main.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2020 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 <fcntl.h>
+#include <filesystem>
+#include <iomanip>
+#include <iostream>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/scopeguard.h>
+#include <logwrap/logwrap.h>
+
+#include "CertUtils.h"
+#include "KeymasterSigningKey.h"
+#include "VerityUtils.h"
+
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+
+const std::string kSigningKeyBlob = "/data/misc/odsign/key.blob";
+const std::string kSigningKeyCert = "/data/misc/odsign/key.cert";
+
+const std::string kArtArtifactsDir = "/data/misc/apexdata/com.android.art/system";
+
+static const char* kOdrefreshPath = "/apex/com.android.art/bin/odrefresh";
+
+static const char* kFsVerityInitPath = "/system/bin/fsverity_init";
+
+static const bool kForceCompilation = false;
+
+Result<void> addCertToFsVerityKeyring(const std::string& path) {
+ const char* const argv[] = {kFsVerityInitPath, "--load-extra-key", "fsv_ods"};
+
+ int fd = open(path.c_str(), O_RDONLY);
+ pid_t pid = fork();
+ if (pid == 0) {
+ dup2(fd, STDIN_FILENO);
+ close(fd);
+ int argc = arraysize(argv);
+ char* argv_child[argc + 1];
+ memcpy(argv_child, argv, argc * sizeof(char*));
+ argv_child[argc] = nullptr;
+ execvp(argv_child[0], const_cast<char**>(argv_child));
+ PLOG(ERROR) << "exec in ForkExecvp";
+ _exit(EXIT_FAILURE);
+ } else {
+ close(fd);
+ }
+ if (pid == -1) {
+ return ErrnoError() << "Failed to fork.";
+ }
+ int status;
+ if (waitpid(pid, &status, 0) == -1) {
+ return ErrnoError() << "waitpid() failed.";
+ }
+ if (!WIFEXITED(status)) {
+ return Error() << kFsVerityInitPath << ": abnormal process exit";
+ }
+ if (WEXITSTATUS(status)) {
+ if (status != 0) {
+ return Error() << kFsVerityInitPath << " exited with " << status;
+ }
+ }
+
+ return {};
+}
+
+Result<KeymasterSigningKey> loadAndVerifyExistingKey() {
+ if (access(kSigningKeyBlob.c_str(), F_OK) < 0) {
+ return ErrnoError() << "Key blob not found: " << kSigningKeyBlob;
+ }
+ return KeymasterSigningKey::loadFromBlobAndVerify(kSigningKeyBlob);
+}
+
+Result<void> verifyAndLoadExistingCert(const KeymasterSigningKey& key) {
+ if (access(kSigningKeyCert.c_str(), F_OK) < 0) {
+ return ErrnoError() << "Key certificate not found: " << kSigningKeyCert;
+ }
+ auto trustedPublicKey = key.getPublicKey();
+ if (!trustedPublicKey.ok()) {
+ return Error() << "Failed to retrieve signing public key.";
+ }
+
+ auto publicKeyFromExistingCert = extractPublicKeyFromX509(kSigningKeyCert);
+ if (!publicKeyFromExistingCert.ok()) {
+ return publicKeyFromExistingCert.error();
+ }
+ if (publicKeyFromExistingCert.value() != trustedPublicKey.value()) {
+ return Error() << "Public key of existing certificate at " << kSigningKeyCert
+ << " does not match signing public key.";
+ }
+
+ auto cert_add_result = addCertToFsVerityKeyring(kSigningKeyCert);
+ if (!cert_add_result.ok()) {
+ return cert_add_result.error();
+ }
+
+ // At this point, we know the cert matches
+ return {};
+}
+
+Result<KeymasterSigningKey> createAndPersistKey(const std::string& path) {
+ auto key = KeymasterSigningKey::createNewKey();
+
+ if (!key.ok()) {
+ return key.error();
+ }
+
+ auto result = key->saveKeyblob(path);
+ if (!result.ok()) {
+ return result.error();
+ }
+
+ return key;
+}
+
+bool compileArtifacts(bool force) {
+ const char* const argv[] = {kOdrefreshPath, force ? "--force-compile" : "--compile"};
+
+ return logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr) ==
+ 0;
+}
+
+bool validateArtifacts() {
+ const char* const argv[] = {kOdrefreshPath, "--check"};
+
+ return logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr) ==
+ 0;
+}
+
+int main(int /* argc */, char** /* argv */) {
+ auto removeArtifacts = []() {
+ std::error_code ec;
+ auto num_removed = std::filesystem::remove_all(kArtArtifactsDir, ec);
+ if (ec) {
+ // TODO can't remove artifacts, signal Zygote shouldn't use them
+ LOG(ERROR) << "Can't remove " << kArtArtifactsDir << ": " << ec.message();
+ } else {
+ LOG(INFO) << "Removed " << num_removed << " entries from " << kArtArtifactsDir;
+ }
+ };
+ // Make sure we delete the artifacts in all early (error) exit paths
+ auto scope_guard = android::base::make_scope_guard(removeArtifacts);
+
+ auto key = loadAndVerifyExistingKey();
+ if (!key.ok()) {
+ LOG(WARNING) << key.error().message();
+
+ key = createAndPersistKey(kSigningKeyBlob);
+ if (!key.ok()) {
+ LOG(ERROR) << "Failed to create or persist new key: " << key.error().message();
+ return -1;
+ }
+ } else {
+ LOG(INFO) << "Found and verified existing key: " << kSigningKeyBlob;
+ }
+
+ auto existing_cert = verifyAndLoadExistingCert(key.value());
+ if (!existing_cert.ok()) {
+ LOG(WARNING) << existing_cert.error().message();
+
+ // Try to create a new cert
+ auto new_cert = key->createX509Cert(kSigningKeyCert);
+ if (!new_cert.ok()) {
+ LOG(ERROR) << "Failed to create X509 certificate: " << new_cert.error().message();
+ // TODO apparently the key become invalid - delete the blob / cert
+ return -1;
+ }
+ auto cert_add_result = addCertToFsVerityKeyring(kSigningKeyCert);
+ if (!cert_add_result.ok()) {
+ LOG(ERROR) << "Failed to add certificate to fs-verity keyring: "
+ << cert_add_result.error().message();
+ return -1;
+ }
+ } else {
+ LOG(INFO) << "Found and verified existing public key certificate: " << kSigningKeyCert;
+ }
+
+ auto verityStatus = verifyAllFilesInVerity(kArtArtifactsDir);
+ if (!verityStatus.ok()) {
+ LOG(WARNING) << verityStatus.error().message() << ", removing " << kArtArtifactsDir;
+ removeArtifacts();
+ }
+
+ bool artifactsValid = validateArtifacts();
+
+ if (!artifactsValid || kForceCompilation) {
+ removeArtifacts();
+
+ LOG(INFO) << "Starting compilation... ";
+ bool ret = compileArtifacts(kForceCompilation);
+ LOG(INFO) << "Compilation done, returned " << ret;
+
+ verityStatus = addFilesToVerityRecursive(kArtArtifactsDir, key.value());
+
+ if (!verityStatus.ok()) {
+ LOG(ERROR) << "Failed to add " << verityStatus.error().message();
+ return -1;
+ }
+ }
+
+ // TODO we want to make sure Zygote only picks up the artifacts if we deemed
+ // everything was ok here. We could use a sysprop, or some other mechanism?
+ LOG(INFO) << "On-device signing done.";
+
+ scope_guard.Disable();
+ return 0;
+}