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