Home | History | Annotate | Download | only in login
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/chromeos/login/google_authenticator.h"
      6 
      7 #include <string>
      8 #include <vector>
      9 
     10 #include "base/file_path.h"
     11 #include "base/file_util.h"
     12 #include "base/logging.h"
     13 #include "base/path_service.h"
     14 #include "base/string_util.h"
     15 #include "base/synchronization/lock.h"
     16 #include "crypto/third_party/nss/blapi.h"
     17 #include "crypto/third_party/nss/sha256.h"
     18 #include "chrome/browser/chromeos/boot_times_loader.h"
     19 #include "chrome/browser/chromeos/cros/cryptohome_library.h"
     20 #include "chrome/browser/chromeos/login/auth_response_handler.h"
     21 #include "chrome/browser/chromeos/login/authentication_notification_details.h"
     22 #include "chrome/browser/chromeos/login/login_status_consumer.h"
     23 #include "chrome/browser/chromeos/login/ownership_service.h"
     24 #include "chrome/browser/chromeos/login/user_manager.h"
     25 #include "chrome/browser/profiles/profile.h"
     26 #include "chrome/browser/profiles/profile_manager.h"
     27 #include "chrome/common/chrome_paths.h"
     28 #include "chrome/common/net/gaia/gaia_auth_fetcher.h"
     29 #include "chrome/common/net/gaia/gaia_constants.h"
     30 #include "content/browser/browser_thread.h"
     31 #include "content/common/notification_service.h"
     32 #include "net/base/load_flags.h"
     33 #include "net/base/net_errors.h"
     34 #include "net/url_request/url_request_status.h"
     35 #include "third_party/libjingle/source/talk/base/urlencode.h"
     36 
     37 using base::Time;
     38 using base::TimeDelta;
     39 using file_util::GetFileSize;
     40 using file_util::PathExists;
     41 using file_util::ReadFile;
     42 using file_util::ReadFileToString;
     43 
     44 namespace chromeos {
     45 
     46 // static
     47 const char GoogleAuthenticator::kLocalaccountFile[] = "localaccount";
     48 
     49 // static
     50 const int GoogleAuthenticator::kClientLoginTimeoutMs = 10000;
     51 // static
     52 const int GoogleAuthenticator::kLocalaccountRetryIntervalMs = 20;
     53 
     54 const int kPassHashLen = 32;
     55 
     56 GoogleAuthenticator::GoogleAuthenticator(LoginStatusConsumer* consumer)
     57     : Authenticator(consumer),
     58       user_manager_(UserManager::Get()),
     59       hosted_policy_(GaiaAuthFetcher::HostedAccountsAllowed),
     60       unlock_(false),
     61       try_again_(true),
     62       checked_for_localaccount_(false) {
     63   CHECK(chromeos::CrosLibrary::Get()->EnsureLoaded());
     64   // If not already owned, this is a no-op.  If it is, this loads the owner's
     65   // public key off of disk.
     66   OwnershipService::GetSharedInstance()->StartLoadOwnerKeyAttempt();
     67 }
     68 
     69 GoogleAuthenticator::~GoogleAuthenticator() {}
     70 
     71 void GoogleAuthenticator::CancelClientLogin() {
     72   if (gaia_authenticator_->HasPendingFetch()) {
     73     VLOG(1) << "Canceling ClientLogin attempt.";
     74     gaia_authenticator_->CancelRequest();
     75 
     76     BrowserThread::PostTask(
     77         BrowserThread::FILE, FROM_HERE,
     78         NewRunnableMethod(this,
     79                           &GoogleAuthenticator::LoadLocalaccount,
     80                           std::string(kLocalaccountFile)));
     81 
     82     CheckOffline(LoginFailure(LoginFailure::LOGIN_TIMED_OUT));
     83   }
     84 }
     85 
     86 void GoogleAuthenticator::TryClientLogin() {
     87   gaia_authenticator_->StartClientLogin(
     88       username_,
     89       password_,
     90       GaiaConstants::kContactsService,
     91       login_token_,
     92       login_captcha_,
     93       hosted_policy_);
     94 
     95   BrowserThread::PostDelayedTask(
     96       BrowserThread::UI,
     97       FROM_HERE,
     98       NewRunnableMethod(this,
     99                         &GoogleAuthenticator::CancelClientLogin),
    100       kClientLoginTimeoutMs);
    101 }
    102 
    103 void GoogleAuthenticator::PrepareClientLoginAttempt(
    104     const std::string& password,
    105     const std::string& token,
    106     const std::string& captcha) {
    107 
    108   // Save so we can retry.
    109   password_.assign(password);
    110   login_token_.assign(token);
    111   login_captcha_.assign(captcha);
    112 }
    113 
    114 void GoogleAuthenticator::ClearClientLoginAttempt() {
    115   // Not clearing the password, because we may need to pass it to the
    116   // sync service if login is successful.
    117   login_token_.clear();
    118   login_captcha_.clear();
    119 }
    120 
    121 bool GoogleAuthenticator::AuthenticateToLogin(
    122     Profile* profile,
    123     const std::string& username,
    124     const std::string& password,
    125     const std::string& login_token,
    126     const std::string& login_captcha) {
    127   unlock_ = false;
    128 
    129   // TODO(cmasone): Figure out how to parallelize fetch, username/password
    130   // processing without impacting testability.
    131   username_.assign(Canonicalize(username));
    132   ascii_hash_.assign(HashPassword(password));
    133 
    134   gaia_authenticator_.reset(
    135       new GaiaAuthFetcher(this,
    136                           GaiaConstants::kChromeOSSource,
    137                           profile->GetRequestContext()));
    138   // Will be used for retries.
    139   PrepareClientLoginAttempt(password, login_token, login_captcha);
    140   TryClientLogin();
    141   return true;
    142 }
    143 
    144 bool GoogleAuthenticator::AuthenticateToUnlock(const std::string& username,
    145                                                const std::string& password) {
    146   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    147   username_.assign(Canonicalize(username));
    148   ascii_hash_.assign(HashPassword(password));
    149   unlock_ = true;
    150   BrowserThread::PostTask(
    151       BrowserThread::FILE, FROM_HERE,
    152       NewRunnableMethod(this,
    153                         &GoogleAuthenticator::LoadLocalaccount,
    154                         std::string(kLocalaccountFile)));
    155   BrowserThread::PostTask(
    156       BrowserThread::UI, FROM_HERE,
    157       NewRunnableMethod(this, &GoogleAuthenticator::CheckOffline,
    158                         LoginFailure(LoginFailure::UNLOCK_FAILED)));
    159   return true;
    160 }
    161 
    162 void GoogleAuthenticator::LoginOffTheRecord() {
    163   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    164   int mount_error = chromeos::kCryptohomeMountErrorNone;
    165   if (CrosLibrary::Get()->GetCryptohomeLibrary()->MountForBwsi(&mount_error)) {
    166     AuthenticationNotificationDetails details(true);
    167     NotificationService::current()->Notify(
    168         NotificationType::LOGIN_AUTHENTICATION,
    169         NotificationService::AllSources(),
    170         Details<AuthenticationNotificationDetails>(&details));
    171     consumer_->OnOffTheRecordLoginSuccess();
    172   } else {
    173     LOG(ERROR) << "Could not mount tmpfs: " << mount_error;
    174     consumer_->OnLoginFailure(
    175         LoginFailure(LoginFailure::COULD_NOT_MOUNT_TMPFS));
    176   }
    177 }
    178 
    179 void GoogleAuthenticator::OnClientLoginSuccess(
    180     const GaiaAuthConsumer::ClientLoginResult& credentials) {
    181 
    182   VLOG(1) << "Online login successful!";
    183   ClearClientLoginAttempt();
    184 
    185   if (hosted_policy_ == GaiaAuthFetcher::HostedAccountsAllowed &&
    186       !user_manager_->IsKnownUser(username_)) {
    187     // First time user, and we don't know if the account is HOSTED or not.
    188     // Since we don't allow HOSTED accounts to log in, we need to try
    189     // again, without allowing HOSTED accounts.
    190     //
    191     // NOTE: we used to do this in the opposite order, so that we'd only
    192     // try the HOSTED pathway if GOOGLE-only failed.  This breaks CAPTCHA
    193     // handling, though.
    194     hosted_policy_ = GaiaAuthFetcher::HostedAccountsNotAllowed;
    195     TryClientLogin();
    196     return;
    197   }
    198   BrowserThread::PostTask(
    199       BrowserThread::UI, FROM_HERE,
    200       NewRunnableMethod(this,
    201                         &GoogleAuthenticator::OnLoginSuccess,
    202                         credentials, false));
    203 }
    204 
    205 void GoogleAuthenticator::OnClientLoginFailure(
    206     const GoogleServiceAuthError& error) {
    207   if (error.state() == GoogleServiceAuthError::REQUEST_CANCELED) {
    208     if (try_again_) {
    209       try_again_ = false;
    210       LOG(ERROR) << "Login attempt canceled!?!?  Trying again.";
    211       TryClientLogin();
    212       return;
    213     }
    214     LOG(ERROR) << "Login attempt canceled again?  Already retried...";
    215   }
    216 
    217   if (error.state() == GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS &&
    218       !user_manager_->IsKnownUser(username_) &&
    219       hosted_policy_ != GaiaAuthFetcher::HostedAccountsAllowed) {
    220     // This was a first-time login, we already tried allowing HOSTED accounts
    221     // and succeeded.  That we've failed with INVALID_GAIA_CREDENTIALS now
    222     // indicates that the account is HOSTED.
    223     LoginFailure failure_details =
    224         LoginFailure::FromNetworkAuthFailure(
    225             GoogleServiceAuthError(
    226                 GoogleServiceAuthError::HOSTED_NOT_ALLOWED));
    227     BrowserThread::PostTask(
    228         BrowserThread::UI, FROM_HERE,
    229         NewRunnableMethod(this,
    230                           &GoogleAuthenticator::OnLoginFailure,
    231                           failure_details));
    232     LOG(WARNING) << "Rejecting valid HOSTED account.";
    233     hosted_policy_ = GaiaAuthFetcher::HostedAccountsNotAllowed;
    234     return;
    235   }
    236 
    237   ClearClientLoginAttempt();
    238 
    239   if (error.state() == GoogleServiceAuthError::TWO_FACTOR) {
    240     LOG(WARNING) << "Two factor authenticated. Sync will not work.";
    241     GaiaAuthConsumer::ClientLoginResult result;
    242     result.two_factor = true;
    243     OnClientLoginSuccess(result);
    244     return;
    245   }
    246 
    247   BrowserThread::PostTask(
    248       BrowserThread::FILE, FROM_HERE,
    249       NewRunnableMethod(this,
    250                         &GoogleAuthenticator::LoadLocalaccount,
    251                         std::string(kLocalaccountFile)));
    252 
    253   LoginFailure failure_details = LoginFailure::FromNetworkAuthFailure(error);
    254 
    255   if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED) {
    256     // The fetch failed for network reasons, try offline login.
    257     BrowserThread::PostTask(
    258         BrowserThread::UI, FROM_HERE,
    259         NewRunnableMethod(this, &GoogleAuthenticator::CheckOffline,
    260                           failure_details));
    261     return;
    262   }
    263 
    264   // The fetch succeeded, but ClientLogin said no, or we exhausted retries.
    265   BrowserThread::PostTask(
    266       BrowserThread::UI, FROM_HERE,
    267       NewRunnableMethod(this,
    268         &GoogleAuthenticator::CheckLocalaccount,
    269         failure_details));
    270 }
    271 
    272 void GoogleAuthenticator::OnLoginSuccess(
    273     const GaiaAuthConsumer::ClientLoginResult& credentials,
    274     bool request_pending) {
    275   // Send notification of success
    276   AuthenticationNotificationDetails details(true);
    277   NotificationService::current()->Notify(
    278       NotificationType::LOGIN_AUTHENTICATION,
    279       NotificationService::AllSources(),
    280       Details<AuthenticationNotificationDetails>(&details));
    281 
    282   int mount_error = chromeos::kCryptohomeMountErrorNone;
    283   BootTimesLoader::Get()->AddLoginTimeMarker("CryptohomeMounting", false);
    284   if (unlock_ ||
    285       (CrosLibrary::Get()->GetCryptohomeLibrary()->Mount(username_.c_str(),
    286                                                          ascii_hash_.c_str(),
    287                                                          &mount_error))) {
    288     BootTimesLoader::Get()->AddLoginTimeMarker("CryptohomeMounted", true);
    289     consumer_->OnLoginSuccess(username_,
    290                               password_,
    291                               credentials,
    292                               request_pending);
    293   } else if (!unlock_ &&
    294              mount_error == chromeos::kCryptohomeMountErrorKeyFailure) {
    295     consumer_->OnPasswordChangeDetected(credentials);
    296   } else {
    297     OnLoginFailure(LoginFailure(LoginFailure::COULD_NOT_MOUNT_CRYPTOHOME));
    298   }
    299 }
    300 
    301 void GoogleAuthenticator::CheckOffline(const LoginFailure& error) {
    302   VLOG(1) << "Attempting offline login";
    303   if (CrosLibrary::Get()->GetCryptohomeLibrary()->CheckKey(
    304           username_.c_str(),
    305           ascii_hash_.c_str())) {
    306     // The fetch didn't succeed, but offline login did.
    307     VLOG(1) << "Offline login successful!";
    308     OnLoginSuccess(GaiaAuthConsumer::ClientLoginResult(), false);
    309   } else {
    310     // We couldn't hit the network, and offline login failed.
    311     GoogleAuthenticator::CheckLocalaccount(error);
    312   }
    313 }
    314 
    315 void GoogleAuthenticator::CheckLocalaccount(const LoginFailure& error) {
    316   {
    317     base::AutoLock for_this_block(localaccount_lock_);
    318     VLOG(1) << "Checking localaccount";
    319     if (!checked_for_localaccount_) {
    320       BrowserThread::PostDelayedTask(
    321           BrowserThread::UI,
    322           FROM_HERE,
    323           NewRunnableMethod(this,
    324                             &GoogleAuthenticator::CheckLocalaccount,
    325                             error),
    326           kLocalaccountRetryIntervalMs);
    327       return;
    328     }
    329   }
    330   int mount_error = chromeos::kCryptohomeMountErrorNone;
    331   if (!localaccount_.empty() && localaccount_ == username_) {
    332     if (CrosLibrary::Get()->GetCryptohomeLibrary()->MountForBwsi(
    333         &mount_error)) {
    334       LOG(WARNING) << "Logging in with localaccount: " << localaccount_;
    335       consumer_->OnLoginSuccess(username_,
    336                                 std::string(),
    337                                 GaiaAuthConsumer::ClientLoginResult(),
    338                                 false);
    339     } else {
    340       LOG(ERROR) << "Could not mount tmpfs for local account: " << mount_error;
    341       OnLoginFailure(
    342           LoginFailure(LoginFailure::COULD_NOT_MOUNT_TMPFS));
    343     }
    344   } else {
    345     OnLoginFailure(error);
    346   }
    347 }
    348 
    349 void GoogleAuthenticator::OnLoginFailure(const LoginFailure& error) {
    350   // Send notification of failure
    351   AuthenticationNotificationDetails details(false);
    352   NotificationService::current()->Notify(
    353       NotificationType::LOGIN_AUTHENTICATION,
    354       NotificationService::AllSources(),
    355       Details<AuthenticationNotificationDetails>(&details));
    356   LOG(WARNING) << "Login failed: " << error.GetErrorString();
    357   consumer_->OnLoginFailure(error);
    358 }
    359 
    360 void GoogleAuthenticator::RecoverEncryptedData(const std::string& old_password,
    361     const GaiaAuthConsumer::ClientLoginResult& credentials) {
    362 
    363   std::string old_hash = HashPassword(old_password);
    364   if (CrosLibrary::Get()->GetCryptohomeLibrary()->MigrateKey(username_,
    365                                                              old_hash,
    366                                                              ascii_hash_)) {
    367     OnLoginSuccess(credentials, false);
    368     return;
    369   }
    370   // User seems to have given us the wrong old password...
    371   consumer_->OnPasswordChangeDetected(credentials);
    372 }
    373 
    374 void GoogleAuthenticator::ResyncEncryptedData(
    375     const GaiaAuthConsumer::ClientLoginResult& credentials) {
    376 
    377   if (CrosLibrary::Get()->GetCryptohomeLibrary()->Remove(username_)) {
    378     OnLoginSuccess(credentials, false);
    379   } else {
    380     OnLoginFailure(LoginFailure(LoginFailure::DATA_REMOVAL_FAILED));
    381   }
    382 }
    383 
    384 void GoogleAuthenticator::RetryAuth(Profile* profile,
    385                                     const std::string& username,
    386                                     const std::string& password,
    387                                     const std::string& login_token,
    388                                     const std::string& login_captcha) {
    389   NOTIMPLEMENTED();
    390 }
    391 
    392 void GoogleAuthenticator::LoadSystemSalt() {
    393   if (!system_salt_.empty())
    394     return;
    395   system_salt_ = CrosLibrary::Get()->GetCryptohomeLibrary()->GetSystemSalt();
    396   CHECK(!system_salt_.empty());
    397   CHECK_EQ(system_salt_.size() % 2, 0U);
    398 }
    399 
    400 void GoogleAuthenticator::LoadLocalaccount(const std::string& filename) {
    401   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    402   {
    403     base::AutoLock for_this_block(localaccount_lock_);
    404     if (checked_for_localaccount_)
    405       return;
    406   }
    407   FilePath localaccount_file;
    408   std::string localaccount;
    409   if (PathService::Get(base::DIR_EXE, &localaccount_file)) {
    410     localaccount_file = localaccount_file.Append(filename);
    411     VLOG(1) << "Looking for localaccount in " << localaccount_file.value();
    412 
    413     ReadFileToString(localaccount_file, &localaccount);
    414     TrimWhitespaceASCII(localaccount, TRIM_TRAILING, &localaccount);
    415     VLOG(1) << "Loading localaccount: " << localaccount;
    416   } else {
    417     VLOG(1) << "Assuming no localaccount";
    418   }
    419   SetLocalaccount(localaccount);
    420 }
    421 
    422 void GoogleAuthenticator::SetLocalaccount(const std::string& new_name) {
    423   localaccount_ = new_name;
    424   {  // extra braces for clarity about AutoLock scope.
    425     base::AutoLock for_this_block(localaccount_lock_);
    426     checked_for_localaccount_ = true;
    427   }
    428 }
    429 
    430 
    431 std::string GoogleAuthenticator::HashPassword(const std::string& password) {
    432   // Get salt, ascii encode, update sha with that, then update with ascii
    433   // of password, then end.
    434   std::string ascii_salt = SaltAsAscii();
    435   unsigned char passhash_buf[kPassHashLen];
    436   char ascii_buf[kPassHashLen + 1];
    437 
    438   // Hash salt and password
    439   SHA256Context ctx;
    440   SHA256_Begin(&ctx);
    441   SHA256_Update(&ctx,
    442                 reinterpret_cast<const unsigned char*>(ascii_salt.data()),
    443                 static_cast<unsigned int>(ascii_salt.length()));
    444   SHA256_Update(&ctx,
    445                 reinterpret_cast<const unsigned char*>(password.data()),
    446                 static_cast<unsigned int>(password.length()));
    447   SHA256_End(&ctx,
    448              passhash_buf,
    449              NULL,
    450              static_cast<unsigned int>(sizeof(passhash_buf)));
    451 
    452   std::vector<unsigned char> passhash(passhash_buf,
    453                                       passhash_buf + sizeof(passhash_buf));
    454   BinaryToHex(passhash,
    455               passhash.size() / 2,  // only want top half, at least for now.
    456               ascii_buf,
    457               sizeof(ascii_buf));
    458   return std::string(ascii_buf, sizeof(ascii_buf) - 1);
    459 }
    460 
    461 std::string GoogleAuthenticator::SaltAsAscii() {
    462   LoadSystemSalt();  // no-op if it's already loaded.
    463   unsigned int salt_len = system_salt_.size();
    464   char ascii_salt[2 * salt_len + 1];
    465   if (GoogleAuthenticator::BinaryToHex(system_salt_,
    466                                        salt_len,
    467                                        ascii_salt,
    468                                        sizeof(ascii_salt))) {
    469     return std::string(ascii_salt, sizeof(ascii_salt) - 1);
    470   } else {
    471     return std::string();
    472   }
    473 }
    474 
    475 // static
    476 bool GoogleAuthenticator::BinaryToHex(const std::vector<unsigned char>& binary,
    477                                       const unsigned int binary_len,
    478                                       char* hex_string,
    479                                       const unsigned int len) {
    480   if (len < 2*binary_len)
    481     return false;
    482   memset(hex_string, 0, len);
    483   for (uint i = 0, j = 0; i < binary_len; i++, j+=2)
    484     snprintf(hex_string + j, len - j, "%02x", binary[i]);
    485   return true;
    486 }
    487 
    488 }  // namespace chromeos
    489