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