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