diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2021-12-09 09:24:46 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-12-09 09:24:46 +0000 |
commit | 8b6af437281c6093ea3b0e22f9ba53da2aafe17f (patch) | |
tree | 3ca4599ebd43228a58cf6f5aa854c678d9cdf075 | |
parent | 3b4775bc4261b2a806e4564c949cdca22e54f47a (diff) | |
parent | 411a1800f3a938e8349fc13267c963d2613ca931 (diff) | |
download | security-8b6af437281c6093ea3b0e22f9ba53da2aafe17f.tar.gz |
Merge "Verify pending artifacts using compos.info" am: 411a1800f3
Original change: https://android-review.googlesource.com/c/platform/system/security/+/1913486
Change-Id: Ic3a2a6015fec7245e5249948b3d0f379a52ee1de
-rw-r--r-- | ondevice-signing/VerityUtils.cpp | 145 | ||||
-rw-r--r-- | ondevice-signing/include/VerityUtils.h | 9 | ||||
-rw-r--r-- | ondevice-signing/odsign_main.cpp | 105 |
3 files changed, 140 insertions, 119 deletions
diff --git a/ondevice-signing/VerityUtils.cpp b/ondevice-signing/VerityUtils.cpp index e5a872af..54490bdf 100644 --- a/ondevice-signing/VerityUtils.cpp +++ b/ondevice-signing/VerityUtils.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <charconv> #include <filesystem> #include <map> #include <span> @@ -33,7 +34,6 @@ #include "CertUtils.h" #include "SigningKey.h" -#include "compos_signature.pb.h" #define FS_VERITY_MAX_DIGEST_SIZE 64 @@ -42,14 +42,7 @@ using android::base::Error; 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"; - -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; @@ -59,6 +52,23 @@ static std::string toHex(std::span<const uint8_t> data) { return ss.str(); } +static std::vector<uint8_t> fromHex(std::string_view hex) { + if (hex.size() % 2 != 0) { + return {}; + } + std::vector<uint8_t> result; + result.reserve(hex.size() / 2); + for (size_t i = 0; i < hex.size(); i += 2) { + uint8_t byte; + auto conversion_result = std::from_chars(&hex[i], &hex[i + 2], byte, 16); + if (conversion_result.ptr != &hex[i + 2] || conversion_result.ec != std::errc()) { + return {}; + } + result.push_back(byte); + } + return result; +} + static int read_callback(void* file, void* buf, size_t count) { int* fd = (int*)file; if (TEMP_FAILURE_RETRY(read(*fd, buf, count)) < 0) return errno ? -errno : -EIO; @@ -285,99 +295,62 @@ Result<std::map<std::string, std::string>> verifyAllFilesInVerity(const std::str 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; - +Result<void> verifyAllFilesUsingCompOs(const std::string& directory_path, + std::map<std::string, std::string> digests, + const SigningKey& signing_key) { std::error_code ec; auto it = std::filesystem::recursive_directory_iterator(directory_path, ec); + size_t verified_count = 0; 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; + auto entry = digests.find(path); + if (entry == digests.end()) { + return Error() << "Unexpected file found: " << path; } + auto& compos_digest = entry->second; 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(); - - // Re-construct the fsverity_formatted_digest that was signed, so we - // can verify the signature. - std::vector<uint8_t> buffer(sizeof(fsverity_formatted_digest) + raw_digest.size()); - auto signed_data = new (buffer.data()) fsverity_formatted_digest; - memcpy(signed_data->magic, "FSVerity", sizeof signed_data->magic); - signed_data->digest_algorithm = __cpu_to_le16(FS_VERITY_HASH_ALG_SHA256); - signed_data->digest_size = __cpu_to_le16(raw_digest.size()); - memcpy(signed_data->digest, raw_digest.data(), raw_digest.size()); - - // Make sure the signature matches the CompOs public key, and not some other - // fs-verity trusted key. - std::string to_verify(reinterpret_cast<char*>(buffer.data()), buffer.size()); - - auto verified = verifyRsaPublicKeySignature(to_verify, 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; + // by CompOS, so we just check that it has the digest we expect. + if (verity_digest.value() == compos_digest) { + ++verified_count; + } else { + return Error() << "fs-verity digest does not match CompOS digest: " << 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; + // Not in fs-verity yet. We know the digest CompOS provided; 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. + LOG(INFO) << "Adding " << path << " to fs-verity..."; + + auto digest_bytes = fromHex(compos_digest); + if (digest_bytes.empty()) { + return Error() << "Invalid digest " << compos_digest; + } + auto signed_digest = signDigest(signing_key, digest_bytes); + if (!signed_digest.ok()) { + return signed_digest.error(); } - LOG(INFO) << "Adding " << path << " to fs-verity..."; - auto enabled = enableFsVerity(fd, pkcs7.value()); + auto pkcs7_data = createPkcs7(signed_digest.value(), kRootSubject); + if (!pkcs7_data.ok()) { + return pkcs7_data.error(); + } + + auto enabled = enableFsVerity(fd, pkcs7_data.value()); if (!enabled.ok()) { - return Error() << enabled.error() << ": " << path; + return Error() << enabled.error(); } + ++verified_count; } - - new_digests[path] = compos_digest; } else if (it->is_directory()) { // These are fine to ignore } else if (it->is_symlink()) { @@ -389,18 +362,12 @@ verifyAllFilesUsingCompOs(const std::string& directory_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(); - } + // Make sure all the files we expected have been seen + if (verified_count != digests.size()) { + return Error() << "Verified " << verified_count << "files, but expected " << digests.size(); } - return new_digests; + return {}; } Result<void> addCertToFsVerityKeyring(const std::string& path, const char* keyName) { diff --git a/ondevice-signing/include/VerityUtils.h b/ondevice-signing/include/VerityUtils.h index 76b22293..77156280 100644 --- a/ondevice-signing/include/VerityUtils.h +++ b/ondevice-signing/include/VerityUtils.h @@ -18,6 +18,10 @@ #include <android-base/result.h> +#include <map> +#include <string> +#include <vector> + #include "SigningKey.h" android::base::Result<void> addCertToFsVerityKeyring(const std::string& path, const char* keyName); @@ -30,5 +34,6 @@ 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); +android::base::Result<void> verifyAllFilesUsingCompOs(const std::string& directory_path, + std::map<std::string, std::string> digests, + const SigningKey& signing_key); diff --git a/ondevice-signing/odsign_main.cpp b/ondevice-signing/odsign_main.cpp index 6ea20ead..ec4a9975 100644 --- a/ondevice-signing/odsign_main.cpp +++ b/ondevice-signing/odsign_main.cpp @@ -65,8 +65,14 @@ const std::string kCompOsCurrentPublicKey = "/data/misc/apexdata/com.android.compos/current/key.pubkey"; const std::string kCompOsPendingPublicKey = "/data/misc/apexdata/com.android.compos/pending/key.pubkey"; - const std::string kCompOsPendingArtifactsDir = "/data/misc/apexdata/com.android.art/compos-pending"; +const std::string kCompOsInfo = kArtArtifactsDir + "/compos.info"; +const std::string kCompOsInfoSignature = kCompOsInfo + ".signature"; + +constexpr const char* kCompOsPendingInfoPath = + "/data/misc/apexdata/com.android.art/compos-pending/compos.info"; +constexpr const char* kCompOsPendingInfoSignaturePath = + "/data/misc/apexdata/com.android.art/compos-pending/compos.info.signature"; constexpr const char* kOdsignVerificationDoneProp = "odsign.verification.done"; constexpr const char* kOdsignKeyDoneProp = "odsign.key.done"; @@ -241,7 +247,7 @@ Result<std::vector<uint8_t>> verifyCompOsKey(const SigningKey& signingKey) { auto existing_key = extractRsaPublicKeyFromLeafCert(signingKey, kCompOsCert, kCompOsSubject.commonName); if (existing_key.ok()) { - LOG(INFO) << "Found and verified existing CompOs public key certificate: " + LOG(INFO) << "Found and verified existing CompOS public key certificate: " << kCompOsCert; return existing_key.value(); } @@ -254,7 +260,7 @@ Result<std::vector<uint8_t>> verifyCompOsKey(const SigningKey& signingKey) { } if (!verified) { - return Error() << "No valid CompOs key present."; + return Error() << "No valid CompOS key present."; } // If the pending key was verified it will have been promoted to current, so @@ -262,7 +268,7 @@ Result<std::vector<uint8_t>> verifyCompOsKey(const SigningKey& signingKey) { auto publicKey = readBytesFromFile(kCompOsCurrentPublicKey); if (publicKey.empty()) { // This shouldn`t really happen. - return Error() << "Failed to read CompOs key."; + return Error() << "Failed to read CompOS key."; } // One way or another we now have a valid public key. Persist a certificate so @@ -274,10 +280,10 @@ Result<std::vector<uint8_t>> verifyCompOsKey(const SigningKey& signingKey) { auto certStatus = createLeafCertificate(kCompOsSubject, publicKey, signFunction, kSigningKeyCert, kCompOsCert); if (!certStatus.ok()) { - return Error() << "Failed to create CompOs cert: " << certStatus.error(); + return Error() << "Failed to create CompOS cert: " << certStatus.error(); } - LOG(INFO) << "Verified key, wrote new CompOs cert"; + LOG(INFO) << "Verified key, wrote new CompOS cert"; return publicKey; } @@ -447,23 +453,58 @@ Result<std::vector<uint8_t>> addCompOsCertToFsVerityKeyring(const SigningKey& si 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: " + return Error() << "Failed to add CompOS certificate to fs-verity keyring: " << cert_add_result.error(); } return publicKey; } +Result<OdsignInfo> getComposInfo(const std::vector<uint8_t>& compos_key) { + std::string compos_signature; + if (!android::base::ReadFileToString(kCompOsInfoSignature, &compos_signature)) { + return ErrnoError() << "Failed to read " << kCompOsInfoSignature; + } + + std::string compos_info_str; + if (!android::base::ReadFileToString(kCompOsInfo, &compos_info_str)) { + return ErrnoError() << "Failed to read " << kCompOsInfo; + } + + // Delete the files - if they're valid we don't need them any more, and + // they'd confuse artifact verification; if they're not we never need to + // look at them again. + if (unlink(kCompOsInfo.c_str()) != 0 || unlink(kCompOsInfoSignature.c_str()) != 0) { + return ErrnoError() << "Unable to delete CompOS info/signature file"; + } + + // Verify the signature + auto verified = verifyRsaPublicKeySignature(compos_info_str, compos_signature, compos_key); + if (!verified.ok()) { + return Error() << kCompOsInfoSignature << " does not match."; + } else { + LOG(INFO) << kCompOsInfoSignature << " matches."; + } + + OdsignInfo compos_info; + if (!compos_info.ParseFromString(compos_info_str)) { + return Error() << "Failed to parse " << kCompOsInfo; + } + + LOG(INFO) << "Loaded " << kCompOsInfo; + return compos_info; +} + art::odrefresh::ExitCode checkCompOsPendingArtifacts(const std::vector<uint8_t>& compos_key, - const SigningKey& signingKey, + const SigningKey& signing_key, bool* digests_verified) { if (!directoryHasContent(kCompOsPendingArtifactsDir)) { return art::odrefresh::ExitCode::kCompilationRequired; } - // CompOs has generated some artifacts that may, or may not, match the + // 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. + // CompOS ones are redundant. art::odrefresh::ExitCode odrefresh_status = checkArtifacts(); if (odrefresh_status != art::odrefresh::ExitCode::kCompilationRequired) { if (odrefresh_status == art::odrefresh::ExitCode::kOkay) { @@ -473,7 +514,14 @@ art::odrefresh::ExitCode checkCompOsPendingArtifacts(const std::vector<uint8_t>& return odrefresh_status; } - // No useful current artifacts, lets see if the CompOs ones are ok + // No useful current artifacts, lets see if the CompOS ones are ok + if (access(kCompOsPendingInfoPath, R_OK) != 0 || + access(kCompOsPendingInfoSignaturePath, R_OK) != 0) { + LOG(INFO) << "Missing CompOS info/signature, deleting pending artifacts"; + removeDirectory(kCompOsPendingArtifactsDir); + return art::odrefresh::ExitCode::kCompilationRequired; + } + LOG(INFO) << "Current artifacts are out of date, switching to pending artifacts"; removeDirectory(kArtArtifactsDir); if (!rename(kCompOsPendingArtifactsDir, kArtArtifactsDir)) { @@ -481,9 +529,6 @@ art::odrefresh::ExitCode checkCompOsPendingArtifacts(const std::vector<uint8_t>& 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"; @@ -492,24 +537,28 @@ art::odrefresh::ExitCode checkCompOsPendingArtifacts(const std::vector<uint8_t>& // 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"); + auto compos_info = getComposInfo(compos_key); + if (!compos_info.ok()) { + LOG(WARNING) << compos_info.error(); + } else { + std::map<std::string, std::string> compos_digests(compos_info->file_hashes().begin(), + compos_info->file_hashes().end()); - if (persisted.ok()) { - *digests_verified = true; - LOG(INFO) << "Pending artifacts successfully verified."; - return art::odrefresh::ExitCode::kOkay; + auto status = verifyAllFilesUsingCompOs(kArtArtifactsDir, compos_digests, signing_key); + if (!status.ok()) { + LOG(WARNING) << status.error(); } else { - LOG(WARNING) << persisted.error(); + auto persisted = persistDigests(compos_digests, signing_key); + + if (!persisted.ok()) { + LOG(WARNING) << persisted.error(); + } else { + LOG(INFO) << "Pending artifacts successfully verified."; + *digests_verified = true; + return art::odrefresh::ExitCode::kOkay; + } } - } else { - LOG(WARNING) << "Pending artifact verification failed: " << digests.error(); } // We can't use the existing artifacts, so we will need to generate new |