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, &params, &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;
+}