| // Copyright 2017 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "arc/setup/priv_code_verifier.h" |
| |
| #include <fcntl.h> |
| #include <openssl/sha.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/threading/platform_thread.h> |
| #include <base/timer/elapsed_timer.h> |
| #include <chromeos/libminijail.h> |
| #include <crypto/secure_hash.h> |
| #include <crypto/sha2.h> |
| |
| #include "arc/setup/arc_setup_util.h" |
| #include "arc/setup/art_container.h" |
| |
| // Ported from Android art/runtime/base/macros.h. |
| #define PACKED(x) __attribute__((__aligned__(x), __packed__)) |
| |
| namespace arc { |
| |
| namespace { |
| |
| // Must be synced with <android_src>/art/runtime/digest.cc. |
| constexpr size_t kAlgorithmSha256DigestFilesize = 68; |
| constexpr char kMultiHashSha256Prefix[] = "1220"; |
| constexpr size_t kBufferSize = 4096; |
| constexpr char kArtFilePrefix[] = "system@framework@"; |
| constexpr char kDigestFileExtension[] = ".digest"; |
| constexpr char kSignatureFilename[] = "digest.sig"; |
| constexpr base::TimeDelta kCryptohomedTimeout = base::TimeDelta::FromSeconds(3); |
| |
| // Converts to digest to string format. |
| std::string ToHex(const uint8_t* digest, size_t len) { |
| std::string hex_str_upper_case = base::HexEncode(digest, len); |
| return base::ToLowerASCII(hex_str_upper_case); |
| } |
| |
| // Calculate the digest of a file. |
| bool CalculateSha256Digest(const base::FilePath& file_path, |
| std::string* digest_str) { |
| std::unique_ptr<crypto::SecureHash> sha256( |
| crypto::SecureHash::Create(crypto::SecureHash::SHA256)); |
| std::vector<uint8_t> digest(crypto::kSHA256Length); |
| |
| base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ); |
| if (!file.IsValid()) |
| return false; |
| |
| std::vector<uint8_t> buffer(kBufferSize); |
| while (true) { |
| int bytes_read = file.ReadAtCurrentPos( |
| reinterpret_cast<char*>(buffer.data()), buffer.size()); |
| if (bytes_read < 0) |
| return false; |
| |
| if (bytes_read == 0) |
| break; |
| sha256->Update(buffer.data(), bytes_read); |
| } |
| sha256->Finish(digest.data(), digest.size()); |
| std::string hex_digest = ToHex(digest.data(), digest.size()); |
| // Use multihash format: https://github.com/multiformats/multihash |
| *digest_str = kMultiHashSha256Prefix + hex_digest; |
| |
| return true; |
| } |
| |
| // Calculate the digest of all digest files. |
| bool GenerateHashList(const base::FilePath& cache_directory, |
| const std::vector<std::string>& art_file_list, |
| uint8_t* out_digest) { |
| SHA256_CTX sha256_ctx; |
| if (!SHA256_Init(&sha256_ctx)) { |
| LOG(ERROR) << "Failed to initialize SHA256_CTX"; |
| return false; |
| } |
| for (const std::string& art_filename : art_file_list) { |
| std::string digest_filename = art_filename + kDigestFileExtension; |
| base::FilePath digest_filepath = cache_directory.Append(digest_filename); |
| std::string digest; |
| if (!base::ReadFileToStringWithMaxSize(digest_filepath, &digest, |
| kAlgorithmSha256DigestFilesize)) { |
| PLOG(ERROR) << "Failed to read digest file " << digest_filename; |
| return false; |
| } |
| |
| // Validates digest of art files before merging its digest. |
| const base::FilePath art_filepath = cache_directory.Append(art_filename); |
| std::string calculated_digest; |
| if (!(CalculateSha256Digest(art_filepath, &calculated_digest))) { |
| PLOG(ERROR) << "Cannot validate the digest for file: " << art_filename; |
| return false; |
| } |
| if (calculated_digest != digest) |
| return false; |
| |
| if (!SHA256_Update(&sha256_ctx, digest.c_str(), |
| kAlgorithmSha256DigestFilesize)) { |
| LOG(ERROR) << "Failed to call SHA256_Update"; |
| return false; |
| } |
| } |
| if (!SHA256_Final(out_digest, &sha256_ctx)) { |
| LOG(ERROR) << "Failed to call SHA256_Final"; |
| return false; |
| } |
| return true; |
| } |
| |
| // Partial image header to read the checksum. Need to be sync'ed with |
| // <Android>/art/runtime/image.h. |
| // TODO(xzhou): Check if P uses the same layout. |
| struct PACKED(4) ImagePartialHeader { |
| uint8_t image_magic[4]; |
| uint8_t image_version[4]; |
| uint32_t image_begin; |
| uint32_t image_size; |
| uint32_t oat_checksum; |
| }; |
| |
| bool ReadImageChecksums(const std::string& image_filename, |
| uint32_t* out_oat_checksum) { |
| base::ScopedFD scoped_fd(open(image_filename.c_str(), O_RDONLY)); |
| if (!scoped_fd.is_valid()) { |
| LOG(ERROR) << "Failed to open image file: " << image_filename; |
| return false; |
| } |
| ImagePartialHeader h; |
| if (!base::ReadFromFD(scoped_fd.get(), reinterpret_cast<char*>(&h), |
| sizeof(ImagePartialHeader))) { |
| PLOG(ERROR) << "Failed to read image header from " << image_filename; |
| return false; |
| } |
| *out_oat_checksum = h.oat_checksum; |
| return true; |
| } |
| |
| bool ChecksumsMatch(const std::string& image_a, const std::string& image_b) { |
| uint32_t oat_checksum_a; |
| uint32_t oat_checksum_b; |
| if (!ReadImageChecksums(image_a, &oat_checksum_a) || |
| !ReadImageChecksums(image_b, &oat_checksum_b)) { |
| return false; |
| } |
| return oat_checksum_a == oat_checksum_b; |
| } |
| |
| // Get a list of file that need to be verified. |
| std::vector<std::string> GetArtFileList(std::string isa) { |
| std::vector<std::string> art_file_list; |
| const base::FilePath isa_dir = base::FilePath(kFrameworkPath).Append(isa); |
| base::FileEnumerator art_files(isa_dir, false /* recursive */, |
| base::FileEnumerator::FILES, "*.art"); |
| for (auto src_file = art_files.Next(); !src_file.empty(); |
| src_file = art_files.Next()) { |
| art_file_list.push_back(kArtFilePrefix + src_file.BaseName().value()); |
| } |
| std::sort(art_file_list.begin(), art_file_list.end()); |
| return art_file_list; |
| } |
| |
| // Checks whether host art files are in sync with system art files. |
| bool IsSynced(const base::FilePath system_isa_image_dir, |
| const base::FilePath cache_isa_image_dir) { |
| base::FileEnumerator art_files(system_isa_image_dir, false /* recursive */, |
| base::FileEnumerator::FILES, "*.art"); |
| for (auto art_file = art_files.Next(); !art_file.empty(); |
| art_file = art_files.Next()) { |
| const base::FilePath cache_art_file = cache_isa_image_dir.Append( |
| kArtFilePrefix + art_file.BaseName().value()); |
| if (!ChecksumsMatch(art_file.value(), cache_art_file.value())) { |
| LOG(ERROR) << "Art file out of sync: " << art_file.value() |
| << " != " << cache_art_file.value(); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| std::unique_ptr<PrivCodeVerifier> CreatePrivCodeVerifier() { |
| return std::make_unique<PrivCodeVerifier>( |
| BootLockboxClient::CreateBootLockboxClient()); |
| } |
| |
| bool PrivCodeVerifier::WaitForCryptohomed() { |
| base::ElapsedTimer timer; |
| while (!boot_lockbox_client_->IsServiceReady()) { |
| if (timer.Elapsed() > kCryptohomedTimeout) { |
| LOG(ERROR) << "Giving up waiting on cryptohomed"; |
| return false; |
| } |
| base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); |
| } |
| LOG(INFO) << "Waiting for cryptohomed took " |
| << timer.Elapsed().InMillisecondsRoundedUp() << "ms"; |
| return true; |
| } |
| |
| PrivCodeVerifier::PrivCodeVerifier( |
| std::unique_ptr<BootLockboxClient> boot_lockbox_client) |
| : boot_lockbox_client_(std::move(boot_lockbox_client)) {} |
| |
| PrivCodeVerifier::~PrivCodeVerifier() = default; |
| |
| bool PrivCodeVerifier::Verify(const base::FilePath& cache_directory, |
| const std::string& isa) { |
| // Make sure the cache is synced with system partition. |
| base::FilePath system_isa_image_dir = |
| base::FilePath(kFrameworkPath).Append(isa); |
| base::FilePath cache_isa_image_dir = cache_directory.Append(isa); |
| if (!IsSynced(system_isa_image_dir, cache_isa_image_dir)) |
| return false; |
| |
| auto files_to_verify = GetArtFileList(isa); |
| |
| uint8_t digest_buffer[SHA256_DIGEST_LENGTH]; |
| if (!GenerateHashList(cache_directory.Append(isa), files_to_verify, |
| digest_buffer)) { |
| LOG(ERROR) << "Failed to merge digest files"; |
| return false; |
| } |
| std::string digest(reinterpret_cast<char*>(digest_buffer), |
| SHA256_DIGEST_LENGTH); |
| std::string signature; |
| const base::FilePath signature_filepath = |
| cache_directory.Append(isa).Append(kSignatureFilename); |
| if (!ReadFileToString(signature_filepath, &signature)) { |
| LOG(ERROR) << "Failed to read digest file " << signature_filepath.value(); |
| return false; |
| } |
| return boot_lockbox_client_->Verify(digest, signature); |
| } |
| |
| bool PrivCodeVerifier::CheckCodeIntegrity( |
| const base::FilePath& dalvik_cache_dir) { |
| if (!IsCodeValid(dalvik_cache_dir)) { |
| LOG(ERROR) << "Check Code Integrity of " << dalvik_cache_dir.value() |
| << " Failed"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool PrivCodeVerifier::IsCodeValid(const base::FilePath& dalvik_cache_dir) { |
| for (const std::string& isa : ArtContainer::GetIsas()) { |
| // TODO(xzhou): merge the digest for all isas and call Sign() just once. |
| if (!Verify(dalvik_cache_dir, isa)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool PrivCodeVerifier::Sign(const base::FilePath& directory) { |
| for (const std::string& isa : ArtContainer::GetIsas()) { |
| const base::FilePath isa_dir = directory.Append(isa); |
| auto files_to_verify = GetArtFileList(isa); |
| uint8_t digest_buffer[SHA256_DIGEST_LENGTH]; |
| if (!GenerateHashList(isa_dir, files_to_verify, digest_buffer)) { |
| LOG(ERROR) << "Failed to merge digest files"; |
| return false; |
| } |
| std::string digest(reinterpret_cast<char*>(digest_buffer), |
| SHA256_DIGEST_LENGTH); |
| std::string signature; |
| boot_lockbox_client_->Sign(digest, &signature); |
| const base::FilePath signature_filepath = |
| isa_dir.Append(kSignatureFilename); |
| if (!WriteToFile(signature_filepath, 0644, signature)) { |
| LOG(ERROR) << "Failed to write signature file"; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool PrivCodeVerifier::IsTpmReady() { |
| return boot_lockbox_client_->IsTpmReady(); |
| } |
| |
| } // namespace arc |