普通文本  |  489行  |  17.33 KB

// 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 <string>
#include <vector>

#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<AuthenticationNotificationDetails>(&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<AuthenticationNotificationDetails>(&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<AuthenticationNotificationDetails>(&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<const unsigned char*>(ascii_salt.data()),
                static_cast<unsigned int>(ascii_salt.length()));
  SHA256_Update(&ctx,
                reinterpret_cast<const unsigned char*>(password.data()),
                static_cast<unsigned int>(password.length()));
  SHA256_End(&ctx,
             passhash_buf,
             NULL,
             static_cast<unsigned int>(sizeof(passhash_buf)));

  std::vector<unsigned char> 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<unsigned char>& 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