| /* Copyright 2024 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| * |
| * This tool will attempt to mount or create the encrypted stateful partition, |
| * and the various bind mountable subdirectories. |
| * |
| */ |
| |
| #include <fcntl.h> |
| #include <sys/time.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <brillo/blkdev_utils/lvm.h> |
| #include <brillo/files/file_util.h> |
| #include <brillo/flag_helper.h> |
| #include <brillo/secure_blob.h> |
| #include <brillo/syslog_logging.h> |
| #include <libcrossystem/crossystem.h> |
| #include <libstorage/platform/platform.h> |
| #include <libstorage/storage_container/filesystem_key.h> |
| #include <libstorage/storage_container/storage_container_factory.h> |
| |
| #include "init/metrics/metrics.h" |
| #include "init/tpm_encryption/encryption_key.h" |
| #include "init/tpm_encryption/tpm.h" |
| #include "init/tpm_encryption/tpm_setup.h" |
| |
| using encryption::paths::cryptohome::kTpmOwned; |
| |
| namespace encryption { |
| |
| namespace { |
| constexpr char kBioCryptoInitPath[] = "/usr/bin/bio_crypto_init"; |
| constexpr char kBioTpmSeedSalt[] = "biod"; |
| constexpr char kBioTpmSeedTmpDir[] = "/run/bio_crypto_init"; |
| constexpr char kBioTpmSeedFile[] = "seed"; |
| constexpr char kFeaturedTpmSeedSalt[] = "featured"; |
| constexpr char kFeaturedTpmSeedTmpDir[] = "/run/featured_seed"; |
| constexpr char kFeaturedTpmSeedFile[] = "tpm_seed"; |
| constexpr char kOldTpmOwnershipStateFile[] = ".tpm_owned"; |
| constexpr char kNvramExport[] = "tmp/lockbox.nvram"; |
| |
| bool SendSecretToTmpFile(const encryption::EncryptionKey& key, |
| const std::string& salt, |
| const base::FilePath& tmp_dir, |
| const std::string& filename, |
| uid_t user_id, |
| gid_t group_id, |
| libstorage::Platform* platform) { |
| brillo::SecureBlob tpm_seed = key.GetDerivedSystemKey(salt); |
| if (tpm_seed.empty()) { |
| LOG(ERROR) << "TPM Seed provided for " << filename << " is empty"; |
| return false; |
| } |
| |
| if (!platform->SafeCreateDirAndSetOwnershipAndPermissions( |
| tmp_dir, |
| /* mode=700 */ S_IRUSR | S_IWUSR | S_IXUSR, user_id, group_id)) { |
| PLOG(ERROR) << "Failed to CreateDir or SetOwnershipAndPermissions of " |
| << tmp_dir; |
| return false; |
| } |
| |
| auto file = tmp_dir.Append(filename); |
| if (!platform->WriteStringToFileAtomic(file, tpm_seed.to_string(), |
| /* mode=600 */ S_IRUSR | S_IWUSR)) { |
| PLOG(ERROR) << "Failed to write TPM seed to tmpfs file " << filename; |
| return false; |
| } |
| |
| if (!platform->SetOwnership(file, user_id, group_id, true)) { |
| PLOG(ERROR) << "Failed to change ownership/perms of tmpfs file " |
| << filename; |
| // Remove the file as it contains the tpm seed with incorrect owner. |
| PLOG_IF(ERROR, !platform->DeleteFile(file)) << "Unable to remove file!"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Send a secret derived from the system key to the biometric managers, if |
| // available, via a tmpfs file which will be read by bio_crypto_init. The tmpfs |
| // directory will be created if it doesn't exist. |
| bool SendSecretToBiodTmpFile(const encryption::EncryptionKey& key, |
| libstorage::Platform* platform) { |
| // If there isn't a bio-sensor, don't bother. |
| if (!platform->FileExists(base::FilePath(kBioCryptoInitPath))) { |
| LOG(INFO) |
| << "There is no bio_crypto_init binary, so skip sending TPM seed."; |
| return true; |
| } |
| |
| return SendSecretToTmpFile( |
| key, std::string(kBioTpmSeedSalt), base::FilePath(kBioTpmSeedTmpDir), |
| kBioTpmSeedFile, libstorage::kBiodUid, libstorage::kBiodGid, platform); |
| } |
| |
| // Send a secret derived from the system key to featured, if available, via a |
| // tmpfs file which will be read by featured. The tmpfs directory will be |
| // created if it doesn't exist. |
| bool SendSecretToFeaturedTmpFile(const encryption::EncryptionKey& key, |
| libstorage::Platform* platform) { |
| return SendSecretToTmpFile(key, std::string(kFeaturedTpmSeedSalt), |
| base::FilePath(kFeaturedTpmSeedTmpDir), |
| kFeaturedTpmSeedFile, libstorage::kRootUid, |
| libstorage::kRootGid, platform); |
| } |
| |
| } // namespace |
| |
| TpmSystemKey::TpmSystemKey(libstorage::Platform* platform, |
| hwsec_foundation::TlclWrapper* tlcl, |
| init_metrics::InitMetrics* metrics, |
| base::FilePath rootdir, |
| base::FilePath stateful_mount) |
| : platform_(platform), |
| tlcl_(tlcl), |
| metrics_(metrics), |
| rootdir_(rootdir), |
| stateful_mount_(stateful_mount), |
| tpm_(tlcl_), |
| loader_(encryption::SystemKeyLoader::Create( |
| platform_, &tpm_, rootdir_, stateful_mount_)) {} |
| |
| bool TpmSystemKey::Set(base::FilePath key_material_file) { |
| if (!tpm_.is_tpm2()) { |
| LOG(WARNING) << "Custom system key is not supported in TPM 1.2."; |
| return false; |
| } |
| |
| brillo::SecureBlob key_material; |
| if (!platform_->ReadFileToSecureBlob(key_material_file, &key_material)) { |
| LOG(ERROR) << "Failed to read custom system key material from file " |
| << key_material_file.value(); |
| return false; |
| } |
| |
| bool rc = loader_->Initialize(key_material, nullptr); |
| if (!rc) { |
| LOG(ERROR) << "Failed to initialize system key NV space contents."; |
| return false; |
| } |
| |
| rc = loader_->Persist(); |
| if (!rc) { |
| LOG(ERROR) << "Failed to persist custom system key material in NVRAM."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| std::optional<encryption::EncryptionKey> TpmSystemKey::Load( |
| bool safe_mount, base::FilePath backup) { |
| bool rc; |
| |
| if (!MigrateTpmOwnerShipStateFile()) { |
| LOG(ERROR) << "Failed to migrate tpm owership state file to" << kTpmOwned; |
| } |
| |
| encryption::EncryptionKey key(platform_, loader_.get(), rootdir_, |
| stateful_mount_); |
| if (ShallUseTpmForSystemKey() && safe_mount) { |
| if (!tpm_.available()) { |
| // The TPM should be available before we load the system_key. |
| LOG(ERROR) << "TPM not available."; |
| // We shouldn't continue to load the system_key. |
| return std::nullopt; |
| } |
| rc = key.LoadChromeOSSystemKey(backup); |
| } else { |
| rc = key.SetInsecureFallbackSystemKey(); |
| } |
| metrics_->ReportSystemKeyStatus(key.system_key_status()); |
| if (!rc) { |
| return std::nullopt; |
| } |
| |
| rc = key.LoadEncryptionKey(); |
| metrics_->ReportEncryptionKeyStatus(key.encryption_key_status()); |
| if (!rc) { |
| return std::nullopt; |
| } |
| |
| /* Log errors during sending seed to biod, but don't stop execution. */ |
| if (HasChromeFw()) { |
| LOG_IF(ERROR, !SendSecretToBiodTmpFile(key, platform_)) |
| << "Failed to send TPM secret to biod."; |
| } else { |
| LOG(ERROR) << "biod won't get a TPM seed without chromefw."; |
| } |
| |
| /* Log errors during sending seed to featured, but don't stop |
| * execution. */ |
| if (ShallUseTpmForSystemKey()) { |
| LOG_IF(ERROR, !SendSecretToFeaturedTmpFile(key, platform_)) |
| << "Failed to send TPM secret to featured."; |
| } else { |
| LOG(ERROR) << "Failed to load TPM system key, featured won't " |
| "get a TPM seed."; |
| } |
| |
| return key; |
| } |
| |
| void TpmSystemKey::ReportInfo() { |
| printf("TPM: %s\n", tpm_.available() ? "yes" : "no"); |
| if (tpm_.available()) { |
| bool owned = false; |
| printf("TPM Owned: %s\n", |
| tpm_.IsOwned(&owned) ? (owned ? "yes" : "no") : "fail"); |
| } |
| printf("ChromeOS: %s\n", HasChromeFw() ? "yes" : "no"); |
| printf("TPM2: %s\n", tpm_.is_tpm2() ? "yes" : "no"); |
| if (ShallUseTpmForSystemKey()) { |
| brillo::SecureBlob system_key; |
| bool rc = loader_->Load(&system_key); |
| if (!rc) { |
| printf("NVRAM: missing.\n"); |
| } else { |
| printf("NVRAM: available.\n"); |
| } |
| } else { |
| printf("NVRAM: not present\n"); |
| } |
| } |
| |
| bool TpmSystemKey::Export() { |
| /* Only check the lockbox when we are using TPM for system key. */ |
| if (ShallUseTpmForSystemKey()) { |
| bool lockbox_valid = false; |
| if (loader_->CheckLockbox(&lockbox_valid)) { |
| encryption::NvramSpace* lockbox_space = tpm_.GetLockboxSpace(); |
| if (lockbox_valid && lockbox_space->is_valid()) { |
| LOG(INFO) << "Lockbox is valid, exporting."; |
| return platform_->WriteSecureBlobToFile(rootdir_.Append(kNvramExport), |
| lockbox_space->contents()); |
| } |
| } else { |
| LOG(ERROR) << "Lockbox validity check error."; |
| } |
| } |
| LOG(INFO) << "Done."; |
| return true; |
| } |
| |
| bool TpmSystemKey::HasChromeFw() { |
| if (!has_chromefw_) { |
| auto fw = platform_->GetCrosssystem()->VbGetSystemPropertyString( |
| crossystem::Crossystem::kMainFirmwareType); |
| if (!fw) |
| has_chromefw_ = false; |
| else |
| has_chromefw_ = (fw != crossystem::Crossystem::kMainfwTypeNonchrome); |
| } |
| return *has_chromefw_; |
| } |
| |
| bool TpmSystemKey::ShallUseTpmForSystemKey() { |
| if (!USE_TPM_INSECURE_FALLBACK) { |
| return true; |
| } |
| |
| if (HasChromeFw()) { |
| return true; |
| } |
| |
| /* Don't use tpm for system key if we are using runtime TPM selection. */ |
| if (USE_TPM_DYNAMIC) { |
| return false; |
| } |
| |
| /* Assume we have tpm for system_key when we are using vtpm tpm2 simulator. */ |
| return USE_TPM2_SIMULATOR && USE_VTPM_PROXY; |
| } |
| |
| bool TpmSystemKey::MigrateTpmOwnerShipStateFile() { |
| base::FilePath tpm_owned = |
| stateful_mount_.Append("unencrypted/tpm_manager/tpm_owned"); |
| base::FilePath old_tpm_state = |
| stateful_mount_.Append(kOldTpmOwnershipStateFile); |
| |
| if (!platform_->CreateDirectory(tpm_owned.DirName())) { |
| LOG(ERROR) << "Failed to create dir for TPM pwnership state file."; |
| return false; |
| } |
| |
| if (platform_->FileExists(old_tpm_state)) { |
| LOG(INFO) << old_tpm_state.value() << " exists. " << "Moving it to " |
| << tpm_owned.value(); |
| return platform_->Rename(old_tpm_state, tpm_owned); |
| } |
| return true; |
| } |
| |
| } // namespace encryption |