// Copyright (c) 2011 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/chromeos/login/google_authenticator.h" #include #include #include "base/file_path.h" #include "base/file_util.h" #include "base/logging.h" #include "base/path_service.h" #include "base/string_util.h" #include "base/synchronization/lock.h" #include "crypto/third_party/nss/blapi.h" #include "crypto/third_party/nss/sha256.h" #include "chrome/browser/chromeos/boot_times_loader.h" #include "chrome/browser/chromeos/cros/cryptohome_library.h" #include "chrome/browser/chromeos/login/auth_response_handler.h" #include "chrome/browser/chromeos/login/authentication_notification_details.h" #include "chrome/browser/chromeos/login/login_status_consumer.h" #include "chrome/browser/chromeos/login/ownership_service.h" #include "chrome/browser/chromeos/login/user_manager.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/net/gaia/gaia_auth_fetcher.h" #include "chrome/common/net/gaia/gaia_constants.h" #include "content/browser/browser_thread.h" #include "content/common/notification_service.h" #include "net/base/load_flags.h" #include "net/base/net_errors.h" #include "net/url_request/url_request_status.h" #include "third_party/libjingle/source/talk/base/urlencode.h" using base::Time; using base::TimeDelta; using file_util::GetFileSize; using file_util::PathExists; using file_util::ReadFile; using file_util::ReadFileToString; namespace chromeos { // static const char GoogleAuthenticator::kLocalaccountFile[] = "localaccount"; // static const int GoogleAuthenticator::kClientLoginTimeoutMs = 10000; // static const int GoogleAuthenticator::kLocalaccountRetryIntervalMs = 20; const int kPassHashLen = 32; GoogleAuthenticator::GoogleAuthenticator(LoginStatusConsumer* consumer) : Authenticator(consumer), user_manager_(UserManager::Get()), hosted_policy_(GaiaAuthFetcher::HostedAccountsAllowed), unlock_(false), try_again_(true), checked_for_localaccount_(false) { CHECK(chromeos::CrosLibrary::Get()->EnsureLoaded()); // If not already owned, this is a no-op. If it is, this loads the owner's // public key off of disk. OwnershipService::GetSharedInstance()->StartLoadOwnerKeyAttempt(); } GoogleAuthenticator::~GoogleAuthenticator() {} void GoogleAuthenticator::CancelClientLogin() { if (gaia_authenticator_->HasPendingFetch()) { VLOG(1) << "Canceling ClientLogin attempt."; gaia_authenticator_->CancelRequest(); BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, NewRunnableMethod(this, &GoogleAuthenticator::LoadLocalaccount, std::string(kLocalaccountFile))); CheckOffline(LoginFailure(LoginFailure::LOGIN_TIMED_OUT)); } } void GoogleAuthenticator::TryClientLogin() { gaia_authenticator_->StartClientLogin( username_, password_, GaiaConstants::kContactsService, login_token_, login_captcha_, hosted_policy_); BrowserThread::PostDelayedTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod(this, &GoogleAuthenticator::CancelClientLogin), kClientLoginTimeoutMs); } void GoogleAuthenticator::PrepareClientLoginAttempt( const std::string& password, const std::string& token, const std::string& captcha) { // Save so we can retry. password_.assign(password); login_token_.assign(token); login_captcha_.assign(captcha); } void GoogleAuthenticator::ClearClientLoginAttempt() { // Not clearing the password, because we may need to pass it to the // sync service if login is successful. login_token_.clear(); login_captcha_.clear(); } bool GoogleAuthenticator::AuthenticateToLogin( Profile* profile, const std::string& username, const std::string& password, const std::string& login_token, const std::string& login_captcha) { unlock_ = false; // TODO(cmasone): Figure out how to parallelize fetch, username/password // processing without impacting testability. username_.assign(Canonicalize(username)); ascii_hash_.assign(HashPassword(password)); gaia_authenticator_.reset( new GaiaAuthFetcher(this, GaiaConstants::kChromeOSSource, profile->GetRequestContext())); // Will be used for retries. PrepareClientLoginAttempt(password, login_token, login_captcha); TryClientLogin(); return true; } bool GoogleAuthenticator::AuthenticateToUnlock(const std::string& username, const std::string& password) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); username_.assign(Canonicalize(username)); ascii_hash_.assign(HashPassword(password)); unlock_ = true; BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, NewRunnableMethod(this, &GoogleAuthenticator::LoadLocalaccount, std::string(kLocalaccountFile))); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod(this, &GoogleAuthenticator::CheckOffline, LoginFailure(LoginFailure::UNLOCK_FAILED))); return true; } void GoogleAuthenticator::LoginOffTheRecord() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); int mount_error = chromeos::kCryptohomeMountErrorNone; if (CrosLibrary::Get()->GetCryptohomeLibrary()->MountForBwsi(&mount_error)) { AuthenticationNotificationDetails details(true); NotificationService::current()->Notify( NotificationType::LOGIN_AUTHENTICATION, NotificationService::AllSources(), Details(&details)); consumer_->OnOffTheRecordLoginSuccess(); } else { LOG(ERROR) << "Could not mount tmpfs: " << mount_error; consumer_->OnLoginFailure( LoginFailure(LoginFailure::COULD_NOT_MOUNT_TMPFS)); } } void GoogleAuthenticator::OnClientLoginSuccess( const GaiaAuthConsumer::ClientLoginResult& credentials) { VLOG(1) << "Online login successful!"; ClearClientLoginAttempt(); if (hosted_policy_ == GaiaAuthFetcher::HostedAccountsAllowed && !user_manager_->IsKnownUser(username_)) { // First time user, and we don't know if the account is HOSTED or not. // Since we don't allow HOSTED accounts to log in, we need to try // again, without allowing HOSTED accounts. // // NOTE: we used to do this in the opposite order, so that we'd only // try the HOSTED pathway if GOOGLE-only failed. This breaks CAPTCHA // handling, though. hosted_policy_ = GaiaAuthFetcher::HostedAccountsNotAllowed; TryClientLogin(); return; } BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod(this, &GoogleAuthenticator::OnLoginSuccess, credentials, false)); } void GoogleAuthenticator::OnClientLoginFailure( const GoogleServiceAuthError& error) { if (error.state() == GoogleServiceAuthError::REQUEST_CANCELED) { if (try_again_) { try_again_ = false; LOG(ERROR) << "Login attempt canceled!?!? Trying again."; TryClientLogin(); return; } LOG(ERROR) << "Login attempt canceled again? Already retried..."; } if (error.state() == GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS && !user_manager_->IsKnownUser(username_) && hosted_policy_ != GaiaAuthFetcher::HostedAccountsAllowed) { // This was a first-time login, we already tried allowing HOSTED accounts // and succeeded. That we've failed with INVALID_GAIA_CREDENTIALS now // indicates that the account is HOSTED. LoginFailure failure_details = LoginFailure::FromNetworkAuthFailure( GoogleServiceAuthError( GoogleServiceAuthError::HOSTED_NOT_ALLOWED)); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod(this, &GoogleAuthenticator::OnLoginFailure, failure_details)); LOG(WARNING) << "Rejecting valid HOSTED account."; hosted_policy_ = GaiaAuthFetcher::HostedAccountsNotAllowed; return; } ClearClientLoginAttempt(); if (error.state() == GoogleServiceAuthError::TWO_FACTOR) { LOG(WARNING) << "Two factor authenticated. Sync will not work."; GaiaAuthConsumer::ClientLoginResult result; result.two_factor = true; OnClientLoginSuccess(result); return; } BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, NewRunnableMethod(this, &GoogleAuthenticator::LoadLocalaccount, std::string(kLocalaccountFile))); LoginFailure failure_details = LoginFailure::FromNetworkAuthFailure(error); if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED) { // The fetch failed for network reasons, try offline login. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod(this, &GoogleAuthenticator::CheckOffline, failure_details)); return; } // The fetch succeeded, but ClientLogin said no, or we exhausted retries. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod(this, &GoogleAuthenticator::CheckLocalaccount, failure_details)); } void GoogleAuthenticator::OnLoginSuccess( const GaiaAuthConsumer::ClientLoginResult& credentials, bool request_pending) { // Send notification of success AuthenticationNotificationDetails details(true); NotificationService::current()->Notify( NotificationType::LOGIN_AUTHENTICATION, NotificationService::AllSources(), Details(&details)); int mount_error = chromeos::kCryptohomeMountErrorNone; BootTimesLoader::Get()->AddLoginTimeMarker("CryptohomeMounting", false); if (unlock_ || (CrosLibrary::Get()->GetCryptohomeLibrary()->Mount(username_.c_str(), ascii_hash_.c_str(), &mount_error))) { BootTimesLoader::Get()->AddLoginTimeMarker("CryptohomeMounted", true); consumer_->OnLoginSuccess(username_, password_, credentials, request_pending); } else if (!unlock_ && mount_error == chromeos::kCryptohomeMountErrorKeyFailure) { consumer_->OnPasswordChangeDetected(credentials); } else { OnLoginFailure(LoginFailure(LoginFailure::COULD_NOT_MOUNT_CRYPTOHOME)); } } void GoogleAuthenticator::CheckOffline(const LoginFailure& error) { VLOG(1) << "Attempting offline login"; if (CrosLibrary::Get()->GetCryptohomeLibrary()->CheckKey( username_.c_str(), ascii_hash_.c_str())) { // The fetch didn't succeed, but offline login did. VLOG(1) << "Offline login successful!"; OnLoginSuccess(GaiaAuthConsumer::ClientLoginResult(), false); } else { // We couldn't hit the network, and offline login failed. GoogleAuthenticator::CheckLocalaccount(error); } } void GoogleAuthenticator::CheckLocalaccount(const LoginFailure& error) { { base::AutoLock for_this_block(localaccount_lock_); VLOG(1) << "Checking localaccount"; if (!checked_for_localaccount_) { BrowserThread::PostDelayedTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod(this, &GoogleAuthenticator::CheckLocalaccount, error), kLocalaccountRetryIntervalMs); return; } } int mount_error = chromeos::kCryptohomeMountErrorNone; if (!localaccount_.empty() && localaccount_ == username_) { if (CrosLibrary::Get()->GetCryptohomeLibrary()->MountForBwsi( &mount_error)) { LOG(WARNING) << "Logging in with localaccount: " << localaccount_; consumer_->OnLoginSuccess(username_, std::string(), GaiaAuthConsumer::ClientLoginResult(), false); } else { LOG(ERROR) << "Could not mount tmpfs for local account: " << mount_error; OnLoginFailure( LoginFailure(LoginFailure::COULD_NOT_MOUNT_TMPFS)); } } else { OnLoginFailure(error); } } void GoogleAuthenticator::OnLoginFailure(const LoginFailure& error) { // Send notification of failure AuthenticationNotificationDetails details(false); NotificationService::current()->Notify( NotificationType::LOGIN_AUTHENTICATION, NotificationService::AllSources(), Details(&details)); LOG(WARNING) << "Login failed: " << error.GetErrorString(); consumer_->OnLoginFailure(error); } void GoogleAuthenticator::RecoverEncryptedData(const std::string& old_password, const GaiaAuthConsumer::ClientLoginResult& credentials) { std::string old_hash = HashPassword(old_password); if (CrosLibrary::Get()->GetCryptohomeLibrary()->MigrateKey(username_, old_hash, ascii_hash_)) { OnLoginSuccess(credentials, false); return; } // User seems to have given us the wrong old password... consumer_->OnPasswordChangeDetected(credentials); } void GoogleAuthenticator::ResyncEncryptedData( const GaiaAuthConsumer::ClientLoginResult& credentials) { if (CrosLibrary::Get()->GetCryptohomeLibrary()->Remove(username_)) { OnLoginSuccess(credentials, false); } else { OnLoginFailure(LoginFailure(LoginFailure::DATA_REMOVAL_FAILED)); } } void GoogleAuthenticator::RetryAuth(Profile* profile, const std::string& username, const std::string& password, const std::string& login_token, const std::string& login_captcha) { NOTIMPLEMENTED(); } void GoogleAuthenticator::LoadSystemSalt() { if (!system_salt_.empty()) return; system_salt_ = CrosLibrary::Get()->GetCryptohomeLibrary()->GetSystemSalt(); CHECK(!system_salt_.empty()); CHECK_EQ(system_salt_.size() % 2, 0U); } void GoogleAuthenticator::LoadLocalaccount(const std::string& filename) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); { base::AutoLock for_this_block(localaccount_lock_); if (checked_for_localaccount_) return; } FilePath localaccount_file; std::string localaccount; if (PathService::Get(base::DIR_EXE, &localaccount_file)) { localaccount_file = localaccount_file.Append(filename); VLOG(1) << "Looking for localaccount in " << localaccount_file.value(); ReadFileToString(localaccount_file, &localaccount); TrimWhitespaceASCII(localaccount, TRIM_TRAILING, &localaccount); VLOG(1) << "Loading localaccount: " << localaccount; } else { VLOG(1) << "Assuming no localaccount"; } SetLocalaccount(localaccount); } void GoogleAuthenticator::SetLocalaccount(const std::string& new_name) { localaccount_ = new_name; { // extra braces for clarity about AutoLock scope. base::AutoLock for_this_block(localaccount_lock_); checked_for_localaccount_ = true; } } std::string GoogleAuthenticator::HashPassword(const std::string& password) { // Get salt, ascii encode, update sha with that, then update with ascii // of password, then end. std::string ascii_salt = SaltAsAscii(); unsigned char passhash_buf[kPassHashLen]; char ascii_buf[kPassHashLen + 1]; // Hash salt and password SHA256Context ctx; SHA256_Begin(&ctx); SHA256_Update(&ctx, reinterpret_cast(ascii_salt.data()), static_cast(ascii_salt.length())); SHA256_Update(&ctx, reinterpret_cast(password.data()), static_cast(password.length())); SHA256_End(&ctx, passhash_buf, NULL, static_cast(sizeof(passhash_buf))); std::vector passhash(passhash_buf, passhash_buf + sizeof(passhash_buf)); BinaryToHex(passhash, passhash.size() / 2, // only want top half, at least for now. ascii_buf, sizeof(ascii_buf)); return std::string(ascii_buf, sizeof(ascii_buf) - 1); } std::string GoogleAuthenticator::SaltAsAscii() { LoadSystemSalt(); // no-op if it's already loaded. unsigned int salt_len = system_salt_.size(); char ascii_salt[2 * salt_len + 1]; if (GoogleAuthenticator::BinaryToHex(system_salt_, salt_len, ascii_salt, sizeof(ascii_salt))) { return std::string(ascii_salt, sizeof(ascii_salt) - 1); } else { return std::string(); } } // static bool GoogleAuthenticator::BinaryToHex(const std::vector& binary, const unsigned int binary_len, char* hex_string, const unsigned int len) { if (len < 2*binary_len) return false; memset(hex_string, 0, len); for (uint i = 0, j = 0; i < binary_len; i++, j+=2) snprintf(hex_string + j, len - j, "%02x", binary[i]); return true; } } // namespace chromeos