| // Copyright 2021 The Chromium 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 "chrome/browser/net/nss_service_chromeos.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/callback_list.h" |
| #include "base/logging.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/supports_user_data.h" |
| #include "chrome/browser/ash/login/startup_utils.h" |
| #include "chrome/browser/ash/profiles/profile_helper.h" |
| #include "chrome/browser/net/nss_context.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/userdataauth/cryptohome_pkcs11_client.h" |
| #include "chromeos/settings/cros_settings_names.h" |
| #include "chromeos/tpm/tpm_token_info_getter.h" |
| #include "components/user_manager/user.h" |
| #include "components/user_manager/user_manager.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "crypto/nss_util.h" |
| #include "crypto/nss_util_internal.h" |
| #include "net/cert/nss_cert_database_chromeos.h" |
| |
| using content::BrowserThread; |
| |
| namespace { |
| |
| // The following four functions are responsible for initializing NSS for each |
| // profile on ChromeOS Ash, which has a separate NSS database and TPM slot |
| // per-profile. |
| // |
| // Initialization basically follows these steps: |
| // 1) Get some info from user_manager::UserManager about the User for this |
| // profile. |
| // 2) Tell nss_util to initialize the software slot for this profile. |
| // 3) Wait for the TPM module to be loaded by nss_util if it isn't already. |
| // 4) Ask CryptohomePkcs11Client which TPM slot id corresponds to this profile. |
| // 5) Tell nss_util to use that slot id on the TPM module. |
| // |
| // Some of these steps must happen on the UI thread, others must happen on the |
| // IO thread: |
| // UI thread IO Thread |
| // |
| // NssServiceChromeOS::NssServiceChromeOS |
| // | |
| // ProfileHelper::Get()->GetUserByProfile() |
| // \---------------------------------------v |
| // StartNSSInitOnIOThread |
| // | |
| // crypto::InitializeNSSForChromeOSUser |
| // | |
| // crypto::IsTPMTokenReady |
| // | |
| // StartTPMSlotInitializationOnIOThread |
| // v---------------------------------------/ |
| // GetTPMInfoForUserOnUIThread |
| // | |
| // chromeos::TPMTokenInfoGetter::Start |
| // | |
| // DidGetTPMInfoForUserOnUIThread |
| // \---------------------------------------v |
| // crypto::InitializeTPMForChromeOSUser |
| |
| void DidGetTPMInfoForUserOnUIThread( |
| std::unique_ptr<chromeos::TPMTokenInfoGetter> getter, |
| const std::string& username_hash, |
| base::Optional<user_data_auth::TpmTokenInfo> token_info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (token_info.has_value() && token_info->slot() != -1) { |
| DVLOG(1) << "Got TPM slot for " << username_hash << ": " |
| << token_info->slot(); |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&crypto::InitializeTPMForChromeOSUser, |
| username_hash, token_info->slot())); |
| } else { |
| NOTREACHED() << "TPMTokenInfoGetter reported invalid token."; |
| } |
| } |
| |
| void GetTPMInfoForUserOnUIThread(const AccountId& account_id, |
| const std::string& username_hash) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DVLOG(1) << "Getting TPM info from cryptohome for " |
| << " " << account_id.Serialize() << " " << username_hash; |
| std::unique_ptr<chromeos::TPMTokenInfoGetter> scoped_token_info_getter = |
| chromeos::TPMTokenInfoGetter::CreateForUserToken( |
| account_id, chromeos::CryptohomePkcs11Client::Get(), |
| base::ThreadTaskRunnerHandle::Get()); |
| chromeos::TPMTokenInfoGetter* token_info_getter = |
| scoped_token_info_getter.get(); |
| |
| // Bind |token_info_getter| to the callback to ensure it does not go away |
| // before TPM token info is fetched. |
| token_info_getter->Start(base::BindOnce(&DidGetTPMInfoForUserOnUIThread, |
| std::move(scoped_token_info_getter), |
| username_hash)); |
| } |
| |
| void StartTPMSlotInitializationOnIOThread(const AccountId& account_id, |
| const std::string& username_hash) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&GetTPMInfoForUserOnUIThread, account_id, username_hash)); |
| } |
| |
| void StartNSSInitOnIOThread(const AccountId& account_id, |
| const std::string& username_hash, |
| const base::FilePath& path) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DVLOG(1) << "Starting NSS init for " << account_id.Serialize() |
| << " hash:" << username_hash; |
| |
| // Make sure NSS is initialized for the user. |
| crypto::InitializeNSSForChromeOSUser(username_hash, path); |
| |
| // Check if it's OK to initialize TPM for the user before continuing. This |
| // may not be the case if the TPM slot initialization was previously |
| // requested for the same user. |
| if (!crypto::ShouldInitializeTPMForChromeOSUser(username_hash)) |
| return; |
| |
| crypto::WillInitializeTPMForChromeOSUser(username_hash); |
| |
| if (crypto::IsTPMTokenEnabledForNSS()) { |
| if (crypto::IsTPMTokenReady( |
| base::BindOnce(&StartTPMSlotInitializationOnIOThread, account_id, |
| username_hash))) { |
| StartTPMSlotInitializationOnIOThread(account_id, username_hash); |
| } else { |
| DVLOG(1) << "Waiting for tpm ready ..."; |
| } |
| } else { |
| crypto::InitializePrivateSoftwareSlotForChromeOSUser(username_hash); |
| } |
| } |
| |
| // Used to convert a callback that takes a net::NSSCertDatabase to one that |
| // takes a net::NSSCertDatabaseChromeOS. |
| void CallWithNSSCertDatabase( |
| base::OnceCallback<void(net::NSSCertDatabase*)> callback, |
| net::NSSCertDatabaseChromeOS* db) { |
| std::move(callback).Run(db); |
| } |
| |
| } // namespace |
| |
| // Creates and manages a NSSCertDatabaseChromeOS. Created on the UI thread, but |
| // all other calls are made on the IO thread. |
| class NssServiceChromeOS::NSSCertDatabaseChromeOSManager { |
| public: |
| typedef base::OnceCallback<void(net::NSSCertDatabaseChromeOS*)> |
| GetNSSCertDatabaseCallback; |
| |
| NSSCertDatabaseChromeOSManager(std::string username_hash, |
| bool user_is_affiliated) |
| : username_hash_(std::move(username_hash)) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (user_is_affiliated) { |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &NSSCertDatabaseChromeOSManager::EnableNSSSystemKeySlot, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| NSSCertDatabaseChromeOSManager(const NSSCertDatabaseChromeOSManager&) = |
| delete; |
| NSSCertDatabaseChromeOSManager& operator=( |
| const NSSCertDatabaseChromeOSManager&) = delete; |
| |
| ~NSSCertDatabaseChromeOSManager() { DCHECK_CURRENTLY_ON(BrowserThread::IO); } |
| |
| net::NSSCertDatabaseChromeOS* GetNSSCertDatabaseChromeOS( |
| NSSCertDatabaseChromeOSManager::GetNSSCertDatabaseCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| |
| if (nss_cert_database_) |
| return nss_cert_database_.get(); |
| |
| ready_callback_list_.AddUnsafe(std::move(callback)); |
| |
| // If database creation has already started, nothing else to do. |
| if (database_creation_started_) |
| return nullptr; |
| |
| // Otherwise, start creating the database. |
| database_creation_started_ = true; |
| crypto::ScopedPK11Slot private_slot(crypto::GetPrivateSlotForChromeOSUser( |
| username_hash_, |
| base::BindOnce(&NSSCertDatabaseChromeOSManager::DidGetPrivateSlot, |
| weak_ptr_factory_.GetWeakPtr()))); |
| if (private_slot) |
| DidGetPrivateSlot(std::move(private_slot)); |
| |
| return nullptr; |
| } |
| |
| // Just like GetNSSCertDatabaseChromeOS(), but uses net::NSSCertDatabase |
| // instead of net::NSSCertDatabaseChromeOS. |
| net::NSSCertDatabase* GetNSSCertDatabase( |
| base::OnceCallback<void(net::NSSCertDatabase*)> callback) { |
| return GetNSSCertDatabaseChromeOS( |
| base::BindOnce(&CallWithNSSCertDatabase, std::move(callback))); |
| } |
| |
| private: |
| using ReadyCallbackList = |
| base::OnceCallbackList<GetNSSCertDatabaseCallback::RunType>; |
| |
| void EnableNSSSystemKeySlot() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| // This should only be called once. |
| DCHECK(!pending_system_slot_); |
| |
| crypto::ScopedPK11Slot system_slot = crypto::GetSystemNSSKeySlot( |
| base::BindOnce(&NSSCertDatabaseChromeOSManager::SetSystemSlotOfDB, |
| weak_ptr_factory_.GetWeakPtr())); |
| if (system_slot) |
| SetSystemSlotOfDB(std::move(system_slot)); |
| } |
| |
| void SetSystemSlotOfDB(crypto::ScopedPK11Slot system_slot) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| DCHECK(system_slot); |
| |
| pending_system_slot_ = std::move(system_slot); |
| |
| base::RepeatingCallback<void(net::NSSCertDatabaseChromeOS*)> callback = |
| base::BindRepeating(&NSSCertDatabaseChromeOSManager::SetSystemSlot, |
| weak_ptr_factory_.GetWeakPtr()); |
| |
| net::NSSCertDatabaseChromeOS* db = GetNSSCertDatabaseChromeOS(callback); |
| if (db) |
| SetSystemSlot(db); |
| } |
| |
| void SetSystemSlot(net::NSSCertDatabaseChromeOS* db) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| db->SetSystemSlot(std::move(pending_system_slot_)); |
| } |
| |
| void DidGetPrivateSlot(crypto::ScopedPK11Slot private_slot) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| nss_cert_database_ = std::make_unique<net::NSSCertDatabaseChromeOS>( |
| crypto::GetPublicSlotForChromeOSUser(username_hash_), |
| std::move(private_slot)); |
| |
| ready_callback_list_.Notify(nss_cert_database_.get()); |
| } |
| |
| const std::string username_hash_; |
| bool database_creation_started_ = false; |
| |
| crypto::ScopedPK11Slot pending_system_slot_; |
| |
| std::unique_ptr<net::NSSCertDatabaseChromeOS> nss_cert_database_; |
| ReadyCallbackList ready_callback_list_; |
| |
| base::WeakPtrFactory<NSSCertDatabaseChromeOSManager> weak_ptr_factory_{this}; |
| }; |
| |
| NssServiceChromeOS::NssServiceChromeOS(Profile* profile) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| const user_manager::User* user = |
| chromeos::ProfileHelper::Get()->GetUserByProfile(profile); |
| // No need to initialize NSS for users with empty username hash: |
| // Getters for a user's NSS slots always return a null slot if the user's |
| // username hash is empty, even when the NSS is not initialized for the |
| // user. |
| std::string username_hash; |
| bool user_is_affiliated = false; |
| if (user && !user->username_hash().empty()) { |
| username_hash = user->username_hash(); |
| DCHECK(!username_hash.empty()); |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&StartNSSInitOnIOThread, user->GetAccountId(), |
| username_hash, profile->GetPath())); |
| |
| user_is_affiliated = user->IsAffiliated(); |
| } |
| |
| DCHECK(!(username_hash.empty() && user_is_affiliated)); |
| |
| nss_cert_database_manager_ = std::make_unique<NSSCertDatabaseChromeOSManager>( |
| std::move(username_hash), user_is_affiliated); |
| } |
| |
| NssServiceChromeOS::~NssServiceChromeOS() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| content::GetIOThreadTaskRunner({})->DeleteSoon( |
| FROM_HERE, std::move(nss_cert_database_manager_)); |
| } |
| |
| NssCertDatabaseGetter |
| NssServiceChromeOS::CreateNSSCertDatabaseGetterForIOThread() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| return base::BindOnce(&NSSCertDatabaseChromeOSManager::GetNSSCertDatabase, |
| base::Unretained(nss_cert_database_manager_.get())); |
| } |