• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GoogleAuthenticator(LoginStatusConsumer * consumer)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 
~GoogleAuthenticator()69 GoogleAuthenticator::~GoogleAuthenticator() {}
70 
CancelClientLogin()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 
TryClientLogin()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 
PrepareClientLoginAttempt(const std::string & password,const std::string & token,const std::string & captcha)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 
ClearClientLoginAttempt()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 
AuthenticateToLogin(Profile * profile,const std::string & username,const std::string & password,const std::string & login_token,const std::string & login_captcha)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 
AuthenticateToUnlock(const std::string & username,const std::string & password)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 
LoginOffTheRecord()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 
OnClientLoginSuccess(const GaiaAuthConsumer::ClientLoginResult & credentials)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 
OnClientLoginFailure(const GoogleServiceAuthError & error)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 
OnLoginSuccess(const GaiaAuthConsumer::ClientLoginResult & credentials,bool request_pending)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 
CheckOffline(const LoginFailure & error)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 
CheckLocalaccount(const LoginFailure & error)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 
OnLoginFailure(const LoginFailure & error)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 
RecoverEncryptedData(const std::string & old_password,const GaiaAuthConsumer::ClientLoginResult & credentials)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 
ResyncEncryptedData(const GaiaAuthConsumer::ClientLoginResult & credentials)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 
RetryAuth(Profile * profile,const std::string & username,const std::string & password,const std::string & login_token,const std::string & login_captcha)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 
LoadSystemSalt()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 
LoadLocalaccount(const std::string & filename)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 
SetLocalaccount(const std::string & new_name)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 
HashPassword(const std::string & password)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 
SaltAsAscii()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
BinaryToHex(const std::vector<unsigned char> & binary,const unsigned int binary_len,char * hex_string,const unsigned int len)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