summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2021-12-09 09:24:46 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-12-09 09:24:46 +0000
commit8b6af437281c6093ea3b0e22f9ba53da2aafe17f (patch)
tree3ca4599ebd43228a58cf6f5aa854c678d9cdf075
parent3b4775bc4261b2a806e4564c949cdca22e54f47a (diff)
parent411a1800f3a938e8349fc13267c963d2613ca931 (diff)
downloadsecurity-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.cpp145
-rw-r--r--ondevice-signing/include/VerityUtils.h9
-rw-r--r--ondevice-signing/odsign_main.cpp105
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