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