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