Merge "Trust CompOs-signed artifacts"
diff --git a/ondevice-signing/Android.bp b/ondevice-signing/Android.bp
index 9085d81..c8ce373 100644
--- a/ondevice-signing/Android.bp
+++ b/ondevice-signing/Android.bp
@@ -11,8 +11,6 @@
// 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.
-// List of clang-tidy checks that are reported as errors.
-// Please keep this list ordered lexicographically.
package {
// See: http://go/android-license-faq
@@ -23,6 +21,8 @@
default_applicable_licenses: ["system_security_license"],
}
+// List of clang-tidy checks that are reported as errors.
+// Please keep this list ordered lexicographically.
tidy_errors = [
"cert-err34-c",
"google-default-arguments",
@@ -95,6 +95,7 @@
static_libs: [
"libc++fs",
"lib_odsign_proto",
+ "lib_compos_proto",
],
shared_libs: [
diff --git a/ondevice-signing/CertUtils.cpp b/ondevice-signing/CertUtils.cpp
index ce2b0fd..acc11e4 100644
--- a/ondevice-signing/CertUtils.cpp
+++ b/ondevice-signing/CertUtils.cpp
@@ -31,8 +31,10 @@
#include "KeyConstants.h"
-const char kRootCommonName[] = "ODS";
+// Common properties for all of our certificates.
constexpr int kCertLifetimeSeconds = 10 * 365 * 24 * 60 * 60;
+const char* const kIssuerCountry = "US";
+const char* const kIssuerOrg = "Android";
using android::base::ErrnoError;
using android::base::Error;
@@ -105,7 +107,7 @@
(const uint8_t*)signature.c_str(), signature.length(), rsaKey->get());
if (!success) {
- return Error() << "Failed to verify signature.";
+ return Error() << "Failed to verify signature";
}
return {};
}
@@ -126,7 +128,7 @@
}
static Result<void> createCertificate(
- const char* commonName, const std::vector<uint8_t>& publicKey,
+ const CertSubject& subject, const std::vector<uint8_t>& publicKey,
const std::function<android::base::Result<std::string>(const std::string&)>& signFunction,
const std::optional<std::string>& issuerCertPath, const std::string& path) {
@@ -141,7 +143,7 @@
X509_set_version(x509.get(), 2);
X509_gmtime_adj(X509_get_notBefore(x509.get()), 0);
X509_gmtime_adj(X509_get_notAfter(x509.get()), kCertLifetimeSeconds);
- ASN1_INTEGER_set(X509_get_serialNumber(x509.get()), selfSigned ? 1 : 2);
+ ASN1_INTEGER_set(X509_get_serialNumber(x509.get()), subject.serialNumber);
bssl::UniquePtr<X509_ALGOR> algor(X509_ALGOR_new());
if (!algor ||
@@ -164,9 +166,9 @@
if (!subjectName) {
return Error() << "Unable to get x509 subject name";
}
- addNameEntry(subjectName, "C", "US");
- addNameEntry(subjectName, "O", "Android");
- addNameEntry(subjectName, "CN", commonName);
+ addNameEntry(subjectName, "C", kIssuerCountry);
+ addNameEntry(subjectName, "O", kIssuerOrg);
+ addNameEntry(subjectName, "CN", subject.commonName);
if (selfSigned) {
if (!X509_set_issuer_name(x509.get(), subjectName)) {
@@ -177,9 +179,9 @@
if (!issuerName) {
return Error() << "Unable to get x509 issuer name";
}
- addNameEntry(issuerName, "C", "US");
- addNameEntry(issuerName, "O", "Android");
- addNameEntry(issuerName, "CN", kRootCommonName);
+ addNameEntry(issuerName, "C", kIssuerCountry);
+ addNameEntry(issuerName, "O", kIssuerOrg);
+ addNameEntry(issuerName, "CN", kRootSubject.commonName);
}
// Beware: context contains a pointer to issuerCert, so we need to keep it alive.
@@ -239,14 +241,14 @@
const std::vector<uint8_t>& publicKey,
const std::function<Result<std::string>(const std::string&)>& signFunction,
const std::string& path) {
- return createCertificate(kRootCommonName, publicKey, signFunction, {}, path);
+ return createCertificate(kRootSubject, publicKey, signFunction, {}, path);
}
android::base::Result<void> createLeafCertificate(
- const char* commonName, const std::vector<uint8_t>& publicKey,
+ const CertSubject& subject, const std::vector<uint8_t>& publicKey,
const std::function<android::base::Result<std::string>(const std::string&)>& signFunction,
const std::string& issuerCertPath, const std::string& path) {
- return createCertificate(commonName, publicKey, signFunction, issuerCertPath, path);
+ return createCertificate(subject, publicKey, signFunction, issuerCertPath, path);
}
Result<std::vector<uint8_t>> extractPublicKey(EVP_PKEY* pkey) {
@@ -340,7 +342,8 @@
return cert_info;
}
-Result<std::vector<uint8_t>> createPkcs7(const std::vector<uint8_t>& signed_digest) {
+Result<std::vector<uint8_t>> createPkcs7(const std::vector<uint8_t>& signed_digest,
+ const CertSubject& signer) {
CBB out, outer_seq, wrapped_seq, seq, digest_algos_set, digest_algo, null;
CBB content_info, issuer_and_serial, signer_infos, signer_info, sign_algo, signature;
uint8_t *pkcs7_data, *name_der;
@@ -353,13 +356,14 @@
return Error() << "Unable to get x509 subject name";
}
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC,
- reinterpret_cast<const unsigned char*>("US"), -1, -1, 0);
+ reinterpret_cast<const unsigned char*>(kIssuerCountry), -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC,
- reinterpret_cast<const unsigned char*>("Android"), -1, -1, 0);
+ reinterpret_cast<const unsigned char*>(kIssuerOrg), -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
- reinterpret_cast<const unsigned char*>("ODS"), -1, -1, 0);
+ reinterpret_cast<const unsigned char*>(signer.commonName), -1, -1,
+ 0);
- BN_set_word(serial, 1);
+ BN_set_word(serial, signer.serialNumber);
name_der_len = i2d_X509_NAME(name, &name_der);
CBB_init(&out, 1024);
diff --git a/ondevice-signing/CertUtils.h b/ondevice-signing/CertUtils.h
index b412d21..1fa5bbc 100644
--- a/ondevice-signing/CertUtils.h
+++ b/ondevice-signing/CertUtils.h
@@ -22,22 +22,37 @@
#include <android-base/result.h>
+// Information extracted from a certificate.
struct CertInfo {
std::string subjectCn;
std::vector<uint8_t> subjectKey;
};
+// Subjects of certificates we issue.
+struct CertSubject {
+ const char* commonName;
+ unsigned serialNumber;
+};
+
+// These are all the certificates we ever sign (the first one being our
+// self-signed cert). We shouldn't really re-use serial numbers for different
+// certificates for the same subject but we do; only one should be in use at a
+// time though.
+inline const CertSubject kRootSubject{"ODS", 1};
+inline const CertSubject kCompOsSubject{"CompOs", 2};
+
android::base::Result<void> createSelfSignedCertificate(
const std::vector<uint8_t>& publicKey,
const std::function<android::base::Result<std::string>(const std::string&)>& signFunction,
const std::string& path);
android::base::Result<void> createLeafCertificate(
- const char* commonName, const std::vector<uint8_t>& publicKey,
+ const CertSubject& subject, const std::vector<uint8_t>& publicKey,
const std::function<android::base::Result<std::string>(const std::string&)>& signFunction,
const std::string& issuerCertPath, const std::string& outPath);
-android::base::Result<std::vector<uint8_t>> createPkcs7(const std::vector<uint8_t>& signedData);
+android::base::Result<std::vector<uint8_t>> createPkcs7(const std::vector<uint8_t>& signedData,
+ const CertSubject& signer);
android::base::Result<std::vector<uint8_t>>
extractPublicKeyFromX509(const std::vector<uint8_t>& x509);
diff --git a/ondevice-signing/KeystoreKey.cpp b/ondevice-signing/KeystoreKey.cpp
index 9780787..03bb6d5 100644
--- a/ondevice-signing/KeystoreKey.cpp
+++ b/ondevice-signing/KeystoreKey.cpp
@@ -122,7 +122,7 @@
return Error() << "Failed to create new key: " << status;
}
- // Exteact the nublir key from the certificate, HMAC it and store the signature
+ // Extract the public key from the certificate, HMAC it and store the signature
auto cert = metadata.certificate;
if (!cert) {
return Error() << "Key did not have a certificate.";
@@ -178,7 +178,7 @@
return false;
}
mPublicKey = *key;
- LOG(ERROR) << "Initialized Keystore key.";
+ LOG(INFO) << "Initialized Keystore key.";
return true;
}
diff --git a/ondevice-signing/VerityUtils.cpp b/ondevice-signing/VerityUtils.cpp
index 243e7df..36f85b5 100644
--- a/ondevice-signing/VerityUtils.cpp
+++ b/ondevice-signing/VerityUtils.cpp
@@ -32,6 +32,7 @@
#include "CertUtils.h"
#include "SigningKey.h"
+#include "compos_signature.pb.h"
#define FS_VERITY_MAX_DIGEST_SIZE 64
@@ -40,7 +41,10 @@
using android::base::Result;
using android::base::unique_fd;
+using compos::proto::Signature;
+
static const char* kFsVerityInitPath = "/system/bin/fsverity_init";
+static const char* kSignatureExtension = ".signature";
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define cpu_to_le16(v) ((__force __le16)(uint16_t)(v))
@@ -50,7 +54,11 @@
#define le16_to_cpu(v) (__builtin_bswap16((__force uint16_t)(v)))
#endif
-static std::string toHex(std::span<uint8_t> data) {
+static bool isSignatureFile(const std::filesystem::path& path) {
+ return path.extension().native() == kSignatureExtension;
+}
+
+static std::string toHex(std::span<const uint8_t> data) {
std::stringstream ss;
for (auto it = data.begin(); it != data.end(); ++it) {
ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned>(*it);
@@ -64,16 +72,11 @@
return 0;
}
-Result<std::vector<uint8_t>> createDigest(const std::string& path) {
+Result<std::vector<uint8_t>> createDigest(int fd) {
struct stat filestat;
- unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
- if (fd < 0) {
- return ErrnoError() << "Failed to open " << path;
- }
-
- int ret = stat(path.c_str(), &filestat);
+ int ret = fstat(fd, &filestat);
if (ret < 0) {
- return ErrnoError() << "Failed to stat " << path;
+ return ErrnoError() << "Failed to fstat";
}
struct libfsverity_merkle_tree_params params = {
.version = 1,
@@ -85,13 +88,21 @@
struct libfsverity_digest* digest;
ret = libfsverity_compute_digest(&fd, &read_callback, ¶ms, &digest);
if (ret < 0) {
- return ErrnoError() << "Failed to compute fs-verity digest for " << path;
+ return ErrnoError() << "Failed to compute fs-verity digest";
}
std::vector<uint8_t> digestVector(&digest->digest[0], &digest->digest[32]);
free(digest);
return digestVector;
}
+Result<std::vector<uint8_t>> createDigest(const std::string& path) {
+ unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
+ if (!fd.ok()) {
+ return ErrnoError() << "Unable to open";
+ }
+ return createDigest(fd.get());
+}
+
namespace {
template <typename T> struct DeleteAsPODArray {
void operator()(T* x) {
@@ -129,10 +140,32 @@
return std::vector<uint8_t>(signed_digest->begin(), signed_digest->end());
}
+Result<void> enableFsVerity(int fd, std::span<uint8_t> pkcs7) {
+ struct fsverity_enable_arg arg = {.version = 1};
+
+ arg.sig_ptr = reinterpret_cast<uint64_t>(pkcs7.data());
+ arg.sig_size = pkcs7.size();
+ arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256;
+ arg.block_size = 4096;
+
+ int ret = ioctl(fd, FS_IOC_ENABLE_VERITY, &arg);
+
+ if (ret != 0) {
+ return ErrnoError() << "Failed to call FS_IOC_ENABLE_VERITY";
+ }
+
+ return {};
+}
+
Result<std::string> enableFsVerity(const std::string& path, const SigningKey& key) {
- auto digest = createDigest(path);
+ unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
+ if (!fd.ok()) {
+ return ErrnoError() << "Failed to open " << path;
+ }
+
+ auto digest = createDigest(fd.get());
if (!digest.ok()) {
- return digest.error();
+ return Error() << digest.error() << ": " << path;
}
auto signed_digest = signDigest(key, digest.value());
@@ -140,20 +173,14 @@
return signed_digest.error();
}
- auto pkcs7_data = createPkcs7(signed_digest.value());
+ auto pkcs7_data = createPkcs7(signed_digest.value(), kRootSubject);
+ if (!pkcs7_data.ok()) {
+ return pkcs7_data.error();
+ }
- struct fsverity_enable_arg arg = {.version = 1};
-
- arg.sig_ptr = (uint64_t)pkcs7_data->data();
- arg.sig_size = pkcs7_data->size();
- arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256;
- arg.block_size = 4096;
-
- unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
- int ret = ioctl(fd, FS_IOC_ENABLE_VERITY, &arg);
-
- if (ret != 0) {
- return ErrnoError() << "Failed to call FS_IOC_ENABLE_VERITY on " << path;
+ auto enabled = enableFsVerity(fd.get(), pkcs7_data.value());
+ if (!enabled.ok()) {
+ return Error() << enabled.error() << ": " << path;
}
// Return the root hash as a hex string
@@ -163,12 +190,10 @@
Result<std::map<std::string, std::string>> addFilesToVerityRecursive(const std::string& path,
const SigningKey& key) {
std::map<std::string, std::string> digests;
+
std::error_code ec;
-
auto it = std::filesystem::recursive_directory_iterator(path, ec);
- auto end = std::filesystem::recursive_directory_iterator();
-
- while (!ec && it != end) {
+ for (auto end = std::filesystem::recursive_directory_iterator(); it != end; it.increment(ec)) {
if (it->is_regular_file()) {
LOG(INFO) << "Adding " << it->path() << " to fs-verity...";
auto result = enableFsVerity(it->path(), key);
@@ -177,38 +202,49 @@
}
digests[it->path()] = *result;
}
- ++it;
}
if (ec) {
- return Error() << "Failed to iterate " << path << ": " << ec;
+ return Error() << "Failed to iterate " << path << ": " << ec.message();
}
return digests;
}
-Result<std::string> isFileInVerity(const std::string& path) {
- unsigned int flags;
+Result<std::string> readVerityDigest(int fd) {
+ auto d = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE);
+ d->digest_size = FS_VERITY_MAX_DIGEST_SIZE;
+ auto ret = ioctl(fd, FS_IOC_MEASURE_VERITY, d.get());
+ if (ret < 0) {
+ return ErrnoError() << "Failed to FS_IOC_MEASURE_VERITY";
+ }
+ return toHex({&d->digest[0], &d->digest[d->digest_size]});
+}
+Result<std::string> isFileInVerity(int fd) {
+ unsigned int flags;
+ int ret = ioctl(fd, FS_IOC_GETFLAGS, &flags);
+ if (ret < 0) {
+ return ErrnoError() << "Failed to FS_IOC_GETFLAGS";
+ }
+ if (!(flags & FS_VERITY_FL)) {
+ return Error() << "File is not in fs-verity";
+ }
+
+ return readVerityDigest(fd);
+}
+
+Result<std::string> isFileInVerity(const std::string& path) {
unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
- if (fd < 0) {
+ if (!fd.ok()) {
return ErrnoError() << "Failed to open " << path;
}
- int ret = ioctl(fd, FS_IOC_GETFLAGS, &flags);
- if (ret < 0) {
- return ErrnoError() << "Failed to FS_IOC_GETFLAGS for " << path;
- }
- if (!(flags & FS_VERITY_FL)) {
- return Error() << "File is not in fs-verity: " << path;
+ auto digest = isFileInVerity(fd);
+ if (!digest.ok()) {
+ return Error() << digest.error() << ": " << path;
}
- auto d = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE);
- d->digest_size = FS_VERITY_MAX_DIGEST_SIZE;
- ret = ioctl(fd, FS_IOC_MEASURE_VERITY, d.get());
- if (ret < 0) {
- return ErrnoError() << "Failed to FS_IOC_MEASURE_VERITY for " << path;
- }
- return toHex({&d->digest[0], &d->digest[d->digest_size]});
+ return digest;
}
Result<std::map<std::string, std::string>> verifyAllFilesInVerity(const std::string& path) {
@@ -242,6 +278,113 @@
return digests;
}
+Result<Signature> readSignature(const std::filesystem::path& signature_path) {
+ unique_fd fd(TEMP_FAILURE_RETRY(open(signature_path.c_str(), O_RDONLY | O_CLOEXEC)));
+ if (fd == -1) {
+ return ErrnoError();
+ }
+ Signature signature;
+ if (!signature.ParseFromFileDescriptor(fd.get())) {
+ return Error() << "Failed to parse";
+ }
+ return signature;
+}
+
+Result<std::map<std::string, std::string>>
+verifyAllFilesUsingCompOs(const std::string& directory_path,
+ const std::vector<uint8_t>& compos_key) {
+ std::map<std::string, std::string> new_digests;
+ std::vector<std::filesystem::path> signature_files;
+
+ std::error_code ec;
+ auto it = std::filesystem::recursive_directory_iterator(directory_path, ec);
+ for (auto end = std::filesystem::recursive_directory_iterator(); it != end; it.increment(ec)) {
+ auto& path = it->path();
+ if (it->is_regular_file()) {
+ if (isSignatureFile(path)) {
+ continue;
+ }
+
+ unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
+ if (!fd.ok()) {
+ return ErrnoError() << "Can't open " << path;
+ }
+
+ auto signature_path = path;
+ signature_path += kSignatureExtension;
+ auto signature = readSignature(signature_path);
+ if (!signature.ok()) {
+ return Error() << "Invalid signature " << signature_path << ": "
+ << signature.error();
+ }
+ signature_files.push_back(signature_path);
+
+ // Note that these values are not yet trusted.
+ auto& raw_digest = signature->digest();
+ auto& raw_signature = signature->signature();
+
+ // Make sure the signature matches the CompOs public key, and not some other
+ // fs-verity trusted key.
+ auto verified = verifySignature(raw_digest, raw_signature, compos_key);
+ if (!verified.ok()) {
+ return Error() << verified.error() << ": " << path;
+ }
+
+ std::span<const uint8_t> digest_bytes(
+ reinterpret_cast<const uint8_t*>(raw_digest.data()), raw_digest.size());
+ std::string compos_digest = toHex(digest_bytes);
+
+ auto verity_digest = isFileInVerity(fd);
+ if (verity_digest.ok()) {
+ // The file is already in fs-verity. We need to make sure it was signed
+ // by CompOs, so we just check that it has the digest we expect.
+ if (verity_digest.value() != compos_digest) {
+ return Error() << "fs-verity digest does not match signature file: " << path;
+ }
+ } else {
+ // Not in fs-verity yet. But we have a valid signature of some
+ // digest. If it's not the correct digest for the file then
+ // enabling fs-verity will fail, so we don't need to check it
+ // explicitly ourselves. Otherwise we should be good.
+ std::vector<uint8_t> signature_bytes(raw_signature.begin(), raw_signature.end());
+ auto pkcs7 = createPkcs7(signature_bytes, kCompOsSubject);
+ if (!pkcs7.ok()) {
+ return Error() << pkcs7.error() << ": " << path;
+ }
+
+ LOG(INFO) << "Adding " << path << " to fs-verity...";
+ auto enabled = enableFsVerity(fd, pkcs7.value());
+ if (!enabled.ok()) {
+ return Error() << enabled.error() << ": " << path;
+ }
+ }
+
+ new_digests[path] = compos_digest;
+ } else if (it->is_directory()) {
+ // These are fine to ignore
+ } else if (it->is_symlink()) {
+ return Error() << "Rejecting artifacts, symlink at " << path;
+ } else {
+ return Error() << "Rejecting artifacts, unexpected file type for " << path;
+ }
+ }
+ if (ec) {
+ return Error() << "Failed to iterate " << directory_path << ": " << ec.message();
+ }
+
+ // Delete the signature files now that they have served their purpose. (ART
+ // has no use for them, and their presence could cause verification to fail
+ // on subsequent boots.)
+ for (auto& signature_path : signature_files) {
+ std::filesystem::remove(signature_path, ec);
+ if (ec) {
+ return Error() << "Failed to delete " << signature_path << ": " << ec.message();
+ }
+ }
+
+ return new_digests;
+}
+
Result<void> addCertToFsVerityKeyring(const std::string& path, const char* keyName) {
const char* const argv[] = {kFsVerityInitPath, "--load-extra-key", keyName};
diff --git a/ondevice-signing/VerityUtils.h b/ondevice-signing/VerityUtils.h
index dca3184..8d8e62c 100644
--- a/ondevice-signing/VerityUtils.h
+++ b/ondevice-signing/VerityUtils.h
@@ -24,5 +24,9 @@
android::base::Result<std::vector<uint8_t>> createDigest(const std::string& path);
android::base::Result<std::map<std::string, std::string>>
verifyAllFilesInVerity(const std::string& path);
+
android::base::Result<std::map<std::string, std::string>>
addFilesToVerityRecursive(const std::string& path, const SigningKey& key);
+
+android::base::Result<std::map<std::string, std::string>>
+verifyAllFilesUsingCompOs(const std::string& path, const std::vector<uint8_t>& compos_key);
diff --git a/ondevice-signing/odsign_main.cpp b/ondevice-signing/odsign_main.cpp
index 5fad7fc..55d8b1c 100644
--- a/ondevice-signing/odsign_main.cpp
+++ b/ondevice-signing/odsign_main.cpp
@@ -59,10 +59,10 @@
static const bool kUseCompOs = false; // STOPSHIP if true
static const char* kVirtApexPath = "/apex/com.android.virt";
-static const char* kCompOsCommonName = "CompOS";
const std::string kCompOsCert = "/data/misc/odsign/compos_key.cert";
const std::string kCompOsPublicKey = "/data/misc/odsign/compos_key.pubkey";
const std::string kCompOsKeyBlob = "/data/misc/odsign/compos_key.blob";
+const std::string kCompOsPendingDir = "/data/misc/apexdata/com.android.art/compos-pending";
static const char* kOdsignVerificationDoneProp = "odsign.verification.done";
static const char* kOdsignKeyDoneProp = "odsign.key.done";
@@ -82,6 +82,48 @@
return std::vector<uint8_t>(str.begin(), str.end());
}
+static int removeDirectory(const std::string& directory) {
+ std::error_code ec;
+ auto num_removed = std::filesystem::remove_all(directory, ec);
+ if (ec) {
+ LOG(ERROR) << "Can't remove " << directory << ": " << ec.message();
+ return 0;
+ } else {
+ if (num_removed > 0) {
+ LOG(INFO) << "Removed " << num_removed << " entries from " << directory;
+ }
+ return num_removed;
+ }
+}
+
+static bool directoryHasContent(const std::string& directory) {
+ std::error_code ec;
+ return std::filesystem::is_directory(directory, ec) &&
+ !std::filesystem::is_empty(directory, ec);
+}
+
+art::odrefresh::ExitCode compileArtifacts(bool force) {
+ const char* const argv[] = {kOdrefreshPath, force ? "--force-compile" : "--compile"};
+ const int exit_code =
+ logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr);
+ return static_cast<art::odrefresh::ExitCode>(exit_code);
+}
+
+art::odrefresh::ExitCode checkArtifacts() {
+ const char* const argv[] = {kOdrefreshPath, "--check"};
+ const int exit_code =
+ logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr);
+ return static_cast<art::odrefresh::ExitCode>(exit_code);
+}
+
+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();
+}
+
bool compOsPresent() {
return access(kVirtApexPath, F_OK) == 0;
}
@@ -124,7 +166,7 @@
const std::string& certPath,
const std::string& expectedCn) {
if (access(certPath.c_str(), F_OK) < 0) {
- return ErrnoError() << "Certificate not found: " << kCompOsCert;
+ return ErrnoError() << "Certificate not found: " << certPath;
}
auto trustedPublicKey = key.getPublicKey();
if (!trustedPublicKey.ok()) {
@@ -146,7 +188,7 @@
return existingCertInfo.value().subjectKey;
}
-Result<void> verifyOrGenerateCompOsKey(const SigningKey& signingKey) {
+Result<std::vector<uint8_t>> verifyOrGenerateCompOsKey(const SigningKey& signingKey) {
auto compOsStatus = FakeCompOs::newInstance();
if (!compOsStatus.ok()) {
return Error() << "Failed to start CompOs: " << compOsStatus.error();
@@ -194,28 +236,13 @@
auto signFunction = [&](const std::string& to_be_signed) {
return signingKey.sign(to_be_signed);
};
- auto certStatus = createLeafCertificate(kCompOsCommonName, publicKey, signFunction,
+ auto certStatus = createLeafCertificate(kCompOsSubject, publicKey, signFunction,
kSigningKeyCert, kCompOsCert);
if (!certStatus.ok()) {
return Error() << "Failed to create CompOs cert: " << certStatus.error();
}
- return {};
-}
-
-art::odrefresh::ExitCode compileArtifacts(bool force) {
- const char* const argv[] = {kOdrefreshPath, force ? "--force-compile" : "--compile"};
- const int exit_code =
- logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr);
- return static_cast<art::odrefresh::ExitCode>(exit_code);
-}
-
-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();
+ return publicKey;
}
Result<std::map<std::string, std::string>> computeDigests(const std::string& path) {
@@ -229,7 +256,8 @@
if (it->is_regular_file()) {
auto digest = createDigest(it->path());
if (!digest.ok()) {
- return Error() << "Failed to compute digest for " << it->path();
+ return Error() << "Failed to compute digest for " << it->path() << ": "
+ << digest.error();
}
digests[it->path()] = toHex(*digest);
}
@@ -341,20 +369,6 @@
return {};
}
-static int removeArtifacts() {
- std::error_code ec;
- auto num_removed = std::filesystem::remove_all(kArtArtifactsDir, ec);
- if (ec) {
- LOG(ERROR) << "Can't remove " << kArtArtifactsDir << ": " << ec.message();
- return 0;
- } else {
- if (num_removed > 0) {
- LOG(INFO) << "Removed " << num_removed << " entries from " << kArtArtifactsDir;
- }
- return num_removed;
- }
-}
-
static Result<void> verifyArtifacts(const SigningKey& key, bool supportsFsVerity) {
auto signInfo = getOdsignInfo(key);
// Tell init we're done with the key; this is a boot time optimization
@@ -386,10 +400,108 @@
return {};
}
+Result<std::vector<uint8_t>> addCompOsCertToFsVerityKeyring(const SigningKey& signingKey) {
+ std::vector<uint8_t> compos_key;
+ auto existing_key =
+ extractPublicKeyFromLeafCert(signingKey, kCompOsCert, kCompOsSubject.commonName);
+ if (existing_key.ok()) {
+ LOG(INFO) << "Found and verified existing CompOs public key certificate: " << kCompOsCert;
+ compos_key = std::move(existing_key.value());
+ } else {
+ LOG(WARNING) << existing_key.error();
+
+ auto new_key = verifyOrGenerateCompOsKey(signingKey);
+ if (!new_key.ok()) {
+ return new_key.error();
+ } else {
+ LOG(INFO) << "Generated new CompOs public key certificate";
+ compos_key = std::move(new_key.value());
+ }
+ }
+
+ auto cert_add_result = addCertToFsVerityKeyring(kCompOsCert, "fsv_compos");
+ if (!cert_add_result.ok()) {
+ // Best efforts only - nothing we can do if deletion fails.
+ unlink(kCompOsCert.c_str());
+ return Error() << "Failed to add CompOs certificate to fs-verity keyring: "
+ << cert_add_result.error();
+ }
+
+ return compos_key;
+}
+
+art::odrefresh::ExitCode checkCompOsPendingArtifacts(const std::vector<uint8_t>& compos_key,
+ const SigningKey& signingKey,
+ bool* digests_verified) {
+ if (!directoryHasContent(kCompOsPendingDir)) {
+ return art::odrefresh::ExitCode::kCompilationRequired;
+ }
+
+ // CompOs has generated some artifacts that may, or may not, match the
+ // current state. But if there are already valid artifacts present the
+ // CompOs ones are redundant.
+ art::odrefresh::ExitCode odrefresh_status = checkArtifacts();
+ if (odrefresh_status != art::odrefresh::ExitCode::kCompilationRequired) {
+ if (odrefresh_status == art::odrefresh::ExitCode::kOkay) {
+ LOG(INFO) << "Current artifacts are OK, deleting pending artifacts";
+ removeDirectory(kCompOsPendingDir);
+ }
+ return odrefresh_status;
+ }
+
+ // No useful current artifacts, lets see if the CompOs ones are ok
+ LOG(INFO) << "Current artifacts are out of date, switching to pending artifacts";
+ removeDirectory(kArtArtifactsDir);
+ std::error_code ec;
+ std::filesystem::rename(kCompOsPendingDir, kArtArtifactsDir, ec);
+ if (ec) {
+ LOG(ERROR) << "Can't rename " << kCompOsPendingDir << " to " << kArtArtifactsDir << ": "
+ << ec.message();
+ removeDirectory(kCompOsPendingDir);
+ return art::odrefresh::ExitCode::kCompilationRequired;
+ }
+ // TODO: Make sure that we check here that the contents of the artifacts
+ // correspond to their filenames (and extensions) - the CompOs signatures
+ // can't guarantee that.
+ odrefresh_status = checkArtifacts();
+ if (odrefresh_status != art::odrefresh::ExitCode::kOkay) {
+ LOG(WARNING) << "Pending artifacts are not OK";
+ return odrefresh_status;
+ }
+
+ // The artifacts appear to be up to date - but we haven't
+ // verified that they are genuine yet.
+ Result<std::map<std::string, std::string>> digests =
+ verifyAllFilesUsingCompOs(kArtArtifactsDir, compos_key);
+
+ if (digests.ok()) {
+ auto persisted = persistDigests(digests.value(), signingKey);
+
+ // Having signed the digests (or failed to), we're done with the signing key.
+ SetProperty(kOdsignKeyDoneProp, "1");
+
+ if (persisted.ok()) {
+ *digests_verified = true;
+ LOG(INFO) << "Pending artifacts successfully verified.";
+ return art::odrefresh::ExitCode::kOkay;
+ } else {
+ LOG(WARNING) << persisted.error();
+ }
+ } else {
+ LOG(WARNING) << "Pending artifact verification failed: " << digests.error();
+ }
+
+ // We can't use the existing artifacts, so we will need to generate new
+ // ones.
+ removeDirectory(kArtArtifactsDir);
+ return art::odrefresh::ExitCode::kCompilationRequired;
+}
+
int main(int /* argc */, char** /* argv */) {
auto errorScopeGuard = []() {
// In case we hit any error, remove the artifacts and tell Zygote not to use anything
- removeArtifacts();
+ removeDirectory(kArtArtifactsDir);
+ removeDirectory(kCompOsPendingDir);
// Tell init we don't need to use our key anymore
SetProperty(kOdsignKeyDoneProp, "1");
// Tell init we're done with verification, and that it was an error
@@ -440,46 +552,37 @@
}
}
- if (supportsCompOs) {
- auto compos_key = extractPublicKeyFromLeafCert(*key, kCompOsCert, kCompOsCommonName);
- if (!compos_key.ok()) {
- LOG(WARNING) << compos_key.error();
+ art::odrefresh::ExitCode odrefresh_status = art::odrefresh::ExitCode::kCompilationRequired;
+ bool digests_verified = false;
- auto status = verifyOrGenerateCompOsKey(*key);
- if (!status.ok()) {
- LOG(ERROR) << status.error();
- } else {
- LOG(INFO) << "Generated new CompOs public key certificate";
- }
+ if (supportsCompOs) {
+ auto compos_key = addCompOsCertToFsVerityKeyring(*key);
+ if (!compos_key.ok()) {
+ LOG(ERROR) << compos_key.error();
} else {
- LOG(INFO) << "Found and verified existing CompOs public key certificate: "
- << kCompOsCert;
- };
- auto cert_add_result = addCertToFsVerityKeyring(kCompOsCert, "fsv_compos");
- if (!cert_add_result.ok()) {
- LOG(ERROR) << "Failed to add CompOs certificate to fs-verity keyring: "
- << cert_add_result.error();
- // Best efforts only - nothing we can do if deletion fails.
- unlink(kCompOsCert.c_str());
- // TODO - what do we do now?
- // return -1;
+ odrefresh_status =
+ checkCompOsPendingArtifacts(compos_key.value(), *key, &digests_verified);
}
}
- art::odrefresh::ExitCode odrefresh_status = compileArtifacts(kForceCompilation);
+ if (odrefresh_status == art::odrefresh::ExitCode::kCompilationRequired) {
+ odrefresh_status = compileArtifacts(kForceCompilation);
+ }
if (odrefresh_status == art::odrefresh::ExitCode::kOkay) {
LOG(INFO) << "odrefresh said artifacts are VALID";
- // A post-condition of validating artifacts is that if the ones on /system
- // are used, kArtArtifactsDir is removed. Conversely, if kArtArtifactsDir
- // exists, those are artifacts that will be used, and we should verify them.
- int err = access(kArtArtifactsDir.c_str(), F_OK);
- // If we receive any error other than ENOENT, be suspicious
- bool artifactsPresent = (err == 0) || (err < 0 && errno != ENOENT);
- if (artifactsPresent) {
- auto verificationResult = verifyArtifacts(*key, supportsFsVerity);
- if (!verificationResult.ok()) {
- LOG(ERROR) << verificationResult.error();
- return -1;
+ if (!digests_verified) {
+ // A post-condition of validating artifacts is that if the ones on /system
+ // are used, kArtArtifactsDir is removed. Conversely, if kArtArtifactsDir
+ // exists, those are artifacts that will be used, and we should verify them.
+ int err = access(kArtArtifactsDir.c_str(), F_OK);
+ // If we receive any error other than ENOENT, be suspicious
+ bool artifactsPresent = (err == 0) || (err < 0 && errno != ENOENT);
+ if (artifactsPresent) {
+ auto verificationResult = verifyArtifacts(*key, supportsFsVerity);
+ if (!verificationResult.ok()) {
+ LOG(ERROR) << verificationResult.error();
+ return -1;
+ }
}
}
} else if (odrefresh_status == art::odrefresh::ExitCode::kCompilationSuccess ||
diff --git a/ondevice-signing/proto/Android.bp b/ondevice-signing/proto/Android.bp
index fd48f31..fc08677 100644
--- a/ondevice-signing/proto/Android.bp
+++ b/ondevice-signing/proto/Android.bp
@@ -27,3 +27,13 @@
},
srcs: ["odsign_info.proto"],
}
+
+cc_library_static {
+ name: "lib_compos_proto",
+ host_supported: true,
+ proto: {
+ export_proto_headers: true,
+ type: "full",
+ },
+ srcs: ["compos_signature.proto"],
+}
diff --git a/ondevice-signing/proto/compos_signature.proto b/ondevice-signing/proto/compos_signature.proto
new file mode 100644
index 0000000..0659ade
--- /dev/null
+++ b/ondevice-signing/proto/compos_signature.proto
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package compos.proto;
+
+// Data provided by CompOS to allow validation of a file it generated.
+message Signature {
+ // The fs-verity digest (root hash of the Merkle tree) of the file
+ // contents.
+ bytes digest = 1;
+
+ // Signature of the digest, signed using CompOS's private key.
+ bytes signature = 2;
+}