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
37using base::Time;
38using base::TimeDelta;
39using file_util::GetFileSize;
40using file_util::PathExists;
41using file_util::ReadFile;
42using file_util::ReadFileToString;
43
44namespace chromeos {
45
46// static
47const char GoogleAuthenticator::kLocalaccountFile[] = "localaccount";
48
49// static
50const int GoogleAuthenticator::kClientLoginTimeoutMs = 10000;
51// static
52const int GoogleAuthenticator::kLocalaccountRetryIntervalMs = 20;
53
54const int kPassHashLen = 32;
55
56GoogleAuthenticator::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
69GoogleAuthenticator::~GoogleAuthenticator() {}
70
71void 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
86void 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
103void 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
114void 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
121bool 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
144bool 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
162void 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
179void 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
205void 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
272void 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
301void 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
315void 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
349void 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
360void 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
374void 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
384void 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
392void 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
400void 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
422void 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
431std::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
461std::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
476bool 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