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/parallel_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/cros/cryptohome_library.h" 19 #include "chrome/browser/chromeos/login/auth_response_handler.h" 20 #include "chrome/browser/chromeos/login/authentication_notification_details.h" 21 #include "chrome/browser/chromeos/login/login_status_consumer.h" 22 #include "chrome/browser/chromeos/login/ownership_service.h" 23 #include "chrome/browser/chromeos/login/user_manager.h" 24 #include "chrome/browser/profiles/profile.h" 25 #include "chrome/browser/profiles/profile_manager.h" 26 #include "chrome/common/chrome_paths.h" 27 #include "chrome/common/net/gaia/gaia_auth_fetcher.h" 28 #include "chrome/common/net/gaia/gaia_constants.h" 29 #include "content/browser/browser_thread.h" 30 #include "content/common/notification_service.h" 31 #include "net/base/load_flags.h" 32 #include "net/base/net_errors.h" 33 #include "net/url_request/url_request_status.h" 34 #include "third_party/libjingle/source/talk/base/urlencode.h" 35 36 using base::Time; 37 using base::TimeDelta; 38 using file_util::GetFileSize; 39 using file_util::PathExists; 40 using file_util::ReadFile; 41 using file_util::ReadFileToString; 42 43 namespace chromeos { 44 45 // static 46 const char ParallelAuthenticator::kLocalaccountFile[] = "localaccount"; 47 48 // static 49 const int ParallelAuthenticator::kClientLoginTimeoutMs = 10000; 50 // static 51 const int ParallelAuthenticator::kLocalaccountRetryIntervalMs = 20; 52 53 const int kPassHashLen = 32; 54 55 ParallelAuthenticator::ParallelAuthenticator(LoginStatusConsumer* consumer) 56 : Authenticator(consumer), 57 already_reported_success_(false), 58 checked_for_localaccount_(false) { 59 CHECK(chromeos::CrosLibrary::Get()->EnsureLoaded()); 60 // If not already owned, this is a no-op. If it is, this loads the owner's 61 // public key off of disk. 62 OwnershipService::GetSharedInstance()->StartLoadOwnerKeyAttempt(); 63 } 64 65 ParallelAuthenticator::~ParallelAuthenticator() {} 66 67 bool ParallelAuthenticator::AuthenticateToLogin( 68 Profile* profile, 69 const std::string& username, 70 const std::string& password, 71 const std::string& login_token, 72 const std::string& login_captcha) { 73 std::string canonicalized = Authenticator::Canonicalize(username); 74 current_state_.reset( 75 new AuthAttemptState(canonicalized, 76 password, 77 HashPassword(password), 78 login_token, 79 login_captcha, 80 !UserManager::Get()->IsKnownUser(canonicalized))); 81 mounter_ = CryptohomeOp::CreateMountAttempt(current_state_.get(), 82 this, 83 false /* don't create */); 84 current_online_ = new OnlineAttempt(current_state_.get(), this); 85 // Sadly, this MUST be on the UI thread due to sending DBus traffic :-/ 86 BrowserThread::PostTask( 87 BrowserThread::UI, FROM_HERE, 88 NewRunnableMethod(mounter_.get(), &CryptohomeOp::Initiate)); 89 current_online_->Initiate(profile); 90 BrowserThread::PostTask( 91 BrowserThread::FILE, FROM_HERE, 92 NewRunnableMethod(this, 93 &ParallelAuthenticator::LoadLocalaccount, 94 std::string(kLocalaccountFile))); 95 return true; 96 } 97 98 bool ParallelAuthenticator::AuthenticateToUnlock(const std::string& username, 99 const std::string& password) { 100 current_state_.reset( 101 new AuthAttemptState(Authenticator::Canonicalize(username), 102 HashPassword(password))); 103 BrowserThread::PostTask( 104 BrowserThread::FILE, FROM_HERE, 105 NewRunnableMethod(this, 106 &ParallelAuthenticator::LoadLocalaccount, 107 std::string(kLocalaccountFile))); 108 key_checker_ = CryptohomeOp::CreateCheckKeyAttempt(current_state_.get(), 109 this); 110 // Sadly, this MUST be on the UI thread due to sending DBus traffic :-/ 111 BrowserThread::PostTask( 112 BrowserThread::UI, FROM_HERE, 113 NewRunnableMethod(key_checker_.get(), &CryptohomeOp::Initiate)); 114 return true; 115 } 116 117 void ParallelAuthenticator::LoginOffTheRecord() { 118 current_state_.reset(new AuthAttemptState("", "", "", "", "", false)); 119 guest_mounter_ = 120 CryptohomeOp::CreateMountGuestAttempt(current_state_.get(), this); 121 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 122 guest_mounter_->Initiate(); 123 } 124 125 void ParallelAuthenticator::OnLoginSuccess( 126 const GaiaAuthConsumer::ClientLoginResult& credentials, 127 bool request_pending) { 128 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 129 VLOG(1) << "Login success"; 130 // Send notification of success 131 AuthenticationNotificationDetails details(true); 132 NotificationService::current()->Notify( 133 NotificationType::LOGIN_AUTHENTICATION, 134 NotificationService::AllSources(), 135 Details<AuthenticationNotificationDetails>(&details)); 136 { 137 base::AutoLock for_this_block(success_lock_); 138 already_reported_success_ = true; 139 } 140 consumer_->OnLoginSuccess(current_state_->username, 141 current_state_->password, 142 credentials, 143 request_pending); 144 } 145 146 void ParallelAuthenticator::OnOffTheRecordLoginSuccess() { 147 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 148 // Send notification of success 149 AuthenticationNotificationDetails details(true); 150 NotificationService::current()->Notify( 151 NotificationType::LOGIN_AUTHENTICATION, 152 NotificationService::AllSources(), 153 Details<AuthenticationNotificationDetails>(&details)); 154 consumer_->OnOffTheRecordLoginSuccess(); 155 } 156 157 void ParallelAuthenticator::OnPasswordChangeDetected( 158 const GaiaAuthConsumer::ClientLoginResult& credentials) { 159 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 160 consumer_->OnPasswordChangeDetected(credentials); 161 } 162 163 void ParallelAuthenticator::CheckLocalaccount(const LoginFailure& error) { 164 { 165 base::AutoLock for_this_block(localaccount_lock_); 166 VLOG(2) << "Checking localaccount"; 167 if (!checked_for_localaccount_) { 168 BrowserThread::PostDelayedTask( 169 BrowserThread::FILE, FROM_HERE, 170 NewRunnableMethod(this, 171 &ParallelAuthenticator::CheckLocalaccount, 172 error), 173 kLocalaccountRetryIntervalMs); 174 return; 175 } 176 } 177 178 if (!localaccount_.empty() && localaccount_ == current_state_->username) { 179 // Success. Go mount a tmpfs for the profile, if necessary. 180 if (!current_state_->unlock) { 181 guest_mounter_ = 182 CryptohomeOp::CreateMountGuestAttempt(current_state_.get(), this); 183 BrowserThread::PostTask( 184 BrowserThread::UI, FROM_HERE, 185 NewRunnableMethod(guest_mounter_.get(), &CryptohomeOp::Initiate)); 186 } else { 187 BrowserThread::PostTask( 188 BrowserThread::UI, FROM_HERE, 189 NewRunnableMethod(this, &ParallelAuthenticator::OnLoginSuccess, 190 GaiaAuthConsumer::ClientLoginResult(), false)); 191 } 192 } else { 193 // Not the localaccount. Fail, passing along cached error info. 194 BrowserThread::PostTask( 195 BrowserThread::UI, FROM_HERE, 196 NewRunnableMethod(this, &ParallelAuthenticator::OnLoginFailure, error)); 197 } 198 } 199 200 void ParallelAuthenticator::OnLoginFailure(const LoginFailure& error) { 201 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 202 // Send notification of failure 203 AuthenticationNotificationDetails details(false); 204 NotificationService::current()->Notify( 205 NotificationType::LOGIN_AUTHENTICATION, 206 NotificationService::AllSources(), 207 Details<AuthenticationNotificationDetails>(&details)); 208 LOG(WARNING) << "Login failed: " << error.GetErrorString(); 209 consumer_->OnLoginFailure(error); 210 } 211 212 void ParallelAuthenticator::RecoverEncryptedData( 213 const std::string& old_password, 214 const GaiaAuthConsumer::ClientLoginResult& credentials) { 215 std::string old_hash = HashPassword(old_password); 216 key_migrator_ = CryptohomeOp::CreateMigrateAttempt(current_state_.get(), 217 this, 218 true, 219 old_hash); 220 BrowserThread::PostTask( 221 BrowserThread::IO, FROM_HERE, 222 NewRunnableMethod(this, 223 &ParallelAuthenticator::ResyncRecoverHelper, 224 key_migrator_)); 225 } 226 227 void ParallelAuthenticator::ResyncEncryptedData( 228 const GaiaAuthConsumer::ClientLoginResult& credentials) { 229 data_remover_ = 230 CryptohomeOp::CreateRemoveAttempt(current_state_.get(), this); 231 BrowserThread::PostTask( 232 BrowserThread::IO, FROM_HERE, 233 NewRunnableMethod(this, 234 &ParallelAuthenticator::ResyncRecoverHelper, 235 data_remover_)); 236 } 237 238 void ParallelAuthenticator::ResyncRecoverHelper(CryptohomeOp* to_initiate) { 239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 240 current_state_->ResetCryptohomeStatus(); 241 BrowserThread::PostTask( 242 BrowserThread::UI, FROM_HERE, 243 NewRunnableMethod(to_initiate, &CryptohomeOp::Initiate)); 244 } 245 246 void ParallelAuthenticator::RetryAuth(Profile* profile, 247 const std::string& username, 248 const std::string& password, 249 const std::string& login_token, 250 const std::string& login_captcha) { 251 reauth_state_.reset( 252 new AuthAttemptState(Authenticator::Canonicalize(username), 253 password, 254 HashPassword(password), 255 login_token, 256 login_captcha, 257 false /* not a new user */)); 258 current_online_ = new OnlineAttempt(reauth_state_.get(), this); 259 current_online_->Initiate(profile); 260 } 261 262 void ParallelAuthenticator::Resolve() { 263 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 264 bool request_pending = false; 265 bool create = false; 266 ParallelAuthenticator::AuthState state = ResolveState(); 267 VLOG(1) << "Resolved state to: " << state; 268 switch (state) { 269 case CONTINUE: 270 case POSSIBLE_PW_CHANGE: 271 case NO_MOUNT: 272 // These are intermediate states; we need more info from a request that 273 // is still pending. 274 break; 275 case FAILED_MOUNT: 276 // In this case, whether login succeeded or not, we can't log 277 // the user in because their data is horked. So, override with 278 // the appropriate failure. 279 BrowserThread::PostTask( 280 BrowserThread::UI, FROM_HERE, 281 NewRunnableMethod( 282 this, 283 &ParallelAuthenticator::OnLoginFailure, 284 LoginFailure(LoginFailure::COULD_NOT_MOUNT_CRYPTOHOME))); 285 break; 286 case FAILED_REMOVE: 287 // In this case, we tried to remove the user's old cryptohome at her 288 // request, and the remove failed. 289 BrowserThread::PostTask( 290 BrowserThread::UI, FROM_HERE, 291 NewRunnableMethod(this, &ParallelAuthenticator::OnLoginFailure, 292 LoginFailure(LoginFailure::DATA_REMOVAL_FAILED))); 293 break; 294 case FAILED_TMPFS: 295 // In this case, we tried to mount a tmpfs for BWSI or the localaccount 296 // user and failed. 297 BrowserThread::PostTask( 298 BrowserThread::UI, FROM_HERE, 299 NewRunnableMethod(this, &ParallelAuthenticator::OnLoginFailure, 300 LoginFailure(LoginFailure::COULD_NOT_MOUNT_TMPFS))); 301 break; 302 case CREATE_NEW: 303 create = true; 304 case RECOVER_MOUNT: 305 current_state_->ResetCryptohomeStatus(); 306 mounter_ = CryptohomeOp::CreateMountAttempt(current_state_.get(), 307 this, 308 create); 309 BrowserThread::PostTask( 310 BrowserThread::UI, FROM_HERE, 311 NewRunnableMethod(mounter_.get(), &CryptohomeOp::Initiate)); 312 break; 313 case NEED_OLD_PW: 314 BrowserThread::PostTask( 315 BrowserThread::UI, FROM_HERE, 316 NewRunnableMethod(this, 317 &ParallelAuthenticator::OnPasswordChangeDetected, 318 current_state_->credentials())); 319 break; 320 case ONLINE_FAILED: 321 // In this case, we know online login was rejected because the account 322 // is disabled or something similarly fatal. Sending the user through 323 // the same path they get when their password is rejected is cleaner 324 // for now. 325 // TODO(cmasone): optimize this so that we don't send the user through 326 // the 'changed password' path when we know doing so won't succeed. 327 case NEED_NEW_PW: 328 { 329 base::AutoLock for_this_block(success_lock_); 330 if (!already_reported_success_) { 331 // This allows us to present the same behavior for "online: 332 // fail, offline: ok", regardless of the order in which we 333 // receive the results. There will be cases in which we get 334 // the online failure some time after the offline success, 335 // so we just force all cases in this category to present like this: 336 // OnLoginSuccess(..., ..., true) -> OnLoginFailure(). 337 BrowserThread::PostTask( 338 BrowserThread::UI, FROM_HERE, 339 NewRunnableMethod(this, &ParallelAuthenticator::OnLoginSuccess, 340 current_state_->credentials(), true)); 341 } 342 } 343 BrowserThread::PostTask( 344 BrowserThread::UI, FROM_HERE, 345 NewRunnableMethod(this, &ParallelAuthenticator::OnLoginFailure, 346 (reauth_state_.get() ? 347 reauth_state_->online_outcome() : 348 current_state_->online_outcome()))); 349 break; 350 case HAVE_NEW_PW: 351 key_migrator_ = 352 CryptohomeOp::CreateMigrateAttempt(reauth_state_.get(), 353 this, 354 true, 355 current_state_->ascii_hash); 356 BrowserThread::PostTask( 357 BrowserThread::UI, FROM_HERE, 358 NewRunnableMethod(key_migrator_.get(), &CryptohomeOp::Initiate)); 359 break; 360 case OFFLINE_LOGIN: 361 VLOG(2) << "Offline login"; 362 request_pending = !current_state_->online_complete(); 363 // Fall through. 364 case UNLOCK: 365 // Fall through. 366 case ONLINE_LOGIN: 367 VLOG(2) << "Online login"; 368 BrowserThread::PostTask( 369 BrowserThread::UI, FROM_HERE, 370 NewRunnableMethod(this, &ParallelAuthenticator::OnLoginSuccess, 371 current_state_->credentials(), request_pending)); 372 break; 373 case LOCAL_LOGIN: 374 BrowserThread::PostTask( 375 BrowserThread::UI, FROM_HERE, 376 NewRunnableMethod( 377 this, 378 &ParallelAuthenticator::OnOffTheRecordLoginSuccess)); 379 break; 380 case LOGIN_FAILED: 381 current_state_->ResetCryptohomeStatus(); 382 BrowserThread::PostTask( 383 BrowserThread::FILE, FROM_HERE, 384 NewRunnableMethod(this, &ParallelAuthenticator::CheckLocalaccount, 385 current_state_->online_outcome())); 386 break; 387 default: 388 NOTREACHED(); 389 break; 390 } 391 } 392 393 ParallelAuthenticator::AuthState ParallelAuthenticator::ResolveState() { 394 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 395 // If we haven't mounted the user's home dir yet, we can't be done. 396 // We never get past here if a cryptohome op is still pending. 397 // This is an important invariant. 398 if (!current_state_->cryptohome_complete()) 399 return CONTINUE; 400 401 AuthState state = (reauth_state_.get() ? ResolveReauthState() : CONTINUE); 402 if (state != CONTINUE) 403 return state; 404 405 if (current_state_->cryptohome_outcome()) 406 state = ResolveCryptohomeSuccessState(); 407 else 408 state = ResolveCryptohomeFailureState(); 409 410 DCHECK(current_state_->cryptohome_complete()); // Ensure invariant holds. 411 key_migrator_ = NULL; 412 data_remover_ = NULL; 413 guest_mounter_ = NULL; 414 key_checker_ = NULL; 415 416 if (state != POSSIBLE_PW_CHANGE && 417 state != NO_MOUNT && 418 state != OFFLINE_LOGIN) 419 return state; 420 421 if (current_state_->online_complete()) { 422 if (current_state_->online_outcome().reason() == LoginFailure::NONE) { 423 // Online attempt succeeded as well, so combine the results. 424 return ResolveOnlineSuccessState(state); 425 } 426 // Online login attempt was rejected or failed to occur. 427 return ResolveOnlineFailureState(state); 428 } 429 // if online isn't complete yet, just return the offline result. 430 return state; 431 } 432 433 ParallelAuthenticator::AuthState 434 ParallelAuthenticator::ResolveReauthState() { 435 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 436 if (reauth_state_->cryptohome_complete()) { 437 if (!reauth_state_->cryptohome_outcome()) { 438 // If we've tried to migrate and failed, log the error and just wait 439 // til next time the user logs in to migrate their cryptohome key. 440 LOG(ERROR) << "Failed to migrate cryptohome key: " 441 << reauth_state_->cryptohome_code(); 442 } 443 reauth_state_.reset(NULL); 444 return ONLINE_LOGIN; 445 } 446 // Haven't tried the migrate yet, must be processing the online auth attempt. 447 if (!reauth_state_->online_complete()) { 448 NOTREACHED(); // Shouldn't be here at all, if online reauth isn't done! 449 return CONTINUE; 450 } 451 return (reauth_state_->online_outcome().reason() == LoginFailure::NONE) ? 452 HAVE_NEW_PW : NEED_NEW_PW; 453 } 454 455 ParallelAuthenticator::AuthState 456 ParallelAuthenticator::ResolveCryptohomeFailureState() { 457 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 458 if (data_remover_.get()) 459 return FAILED_REMOVE; 460 if (guest_mounter_.get()) 461 return FAILED_TMPFS; 462 if (key_migrator_.get()) 463 return NEED_OLD_PW; 464 if (key_checker_.get()) 465 return LOGIN_FAILED; 466 if (current_state_->cryptohome_code() == 467 chromeos::kCryptohomeMountErrorKeyFailure) { 468 // If we tried a mount but they used the wrong key, we may need to 469 // ask the user for her old password. We'll only know once we've 470 // done the online check. 471 return POSSIBLE_PW_CHANGE; 472 } 473 if (current_state_->cryptohome_code() == 474 chromeos::kCryptohomeMountErrorUserDoesNotExist) { 475 // If we tried a mount but the user did not exist, then we should wait 476 // for online login to succeed and try again with the "create" flag set. 477 return NO_MOUNT; 478 } 479 return FAILED_MOUNT; 480 } 481 482 ParallelAuthenticator::AuthState 483 ParallelAuthenticator::ResolveCryptohomeSuccessState() { 484 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 485 if (data_remover_.get()) 486 return CREATE_NEW; 487 if (guest_mounter_.get()) 488 return LOCAL_LOGIN; 489 if (key_migrator_.get()) 490 return RECOVER_MOUNT; 491 if (key_checker_.get()) 492 return UNLOCK; 493 return OFFLINE_LOGIN; 494 } 495 496 ParallelAuthenticator::AuthState 497 ParallelAuthenticator::ResolveOnlineFailureState( 498 ParallelAuthenticator::AuthState offline_state) { 499 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 500 if (offline_state == OFFLINE_LOGIN) { 501 if (current_state_->online_outcome().error().state() == 502 GoogleServiceAuthError::CONNECTION_FAILED) { 503 // Couldn't do an online check, so just go with the offline result. 504 return OFFLINE_LOGIN; 505 } 506 // Otherwise, online login was rejected! 507 if (current_state_->online_outcome().error().state() == 508 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS) { 509 return NEED_NEW_PW; 510 } 511 return ONLINE_FAILED; 512 } 513 return LOGIN_FAILED; 514 } 515 516 ParallelAuthenticator::AuthState 517 ParallelAuthenticator::ResolveOnlineSuccessState( 518 ParallelAuthenticator::AuthState offline_state) { 519 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 520 switch (offline_state) { 521 case POSSIBLE_PW_CHANGE: 522 return NEED_OLD_PW; 523 case NO_MOUNT: 524 return CREATE_NEW; 525 case OFFLINE_LOGIN: 526 return ONLINE_LOGIN; 527 default: 528 NOTREACHED(); 529 return offline_state; 530 } 531 } 532 533 void ParallelAuthenticator::LoadSystemSalt() { 534 if (!system_salt_.empty()) 535 return; 536 system_salt_ = CrosLibrary::Get()->GetCryptohomeLibrary()->GetSystemSalt(); 537 CHECK(!system_salt_.empty()); 538 CHECK_EQ(system_salt_.size() % 2, 0U); 539 } 540 541 void ParallelAuthenticator::LoadLocalaccount(const std::string& filename) { 542 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 543 { 544 base::AutoLock for_this_block(localaccount_lock_); 545 if (checked_for_localaccount_) 546 return; 547 } 548 FilePath localaccount_file; 549 std::string localaccount; 550 if (PathService::Get(base::DIR_EXE, &localaccount_file)) { 551 localaccount_file = localaccount_file.Append(filename); 552 VLOG(2) << "Looking for localaccount in " << localaccount_file.value(); 553 554 ReadFileToString(localaccount_file, &localaccount); 555 TrimWhitespaceASCII(localaccount, TRIM_TRAILING, &localaccount); 556 VLOG(1) << "Loading localaccount: " << localaccount; 557 } else { 558 VLOG(1) << "Assuming no localaccount"; 559 } 560 SetLocalaccount(localaccount); 561 } 562 563 void ParallelAuthenticator::SetLocalaccount(const std::string& new_name) { 564 localaccount_ = new_name; 565 { // extra braces for clarity about AutoLock scope. 566 base::AutoLock for_this_block(localaccount_lock_); 567 checked_for_localaccount_ = true; 568 } 569 } 570 571 572 std::string ParallelAuthenticator::HashPassword(const std::string& password) { 573 // Get salt, ascii encode, update sha with that, then update with ascii 574 // of password, then end. 575 std::string ascii_salt = SaltAsAscii(); 576 unsigned char passhash_buf[kPassHashLen]; 577 char ascii_buf[kPassHashLen + 1]; 578 579 // Hash salt and password 580 SHA256Context ctx; 581 SHA256_Begin(&ctx); 582 SHA256_Update(&ctx, 583 reinterpret_cast<const unsigned char*>(ascii_salt.data()), 584 static_cast<unsigned int>(ascii_salt.length())); 585 SHA256_Update(&ctx, 586 reinterpret_cast<const unsigned char*>(password.data()), 587 static_cast<unsigned int>(password.length())); 588 SHA256_End(&ctx, 589 passhash_buf, 590 NULL, 591 static_cast<unsigned int>(sizeof(passhash_buf))); 592 593 std::vector<unsigned char> passhash(passhash_buf, 594 passhash_buf + sizeof(passhash_buf)); 595 BinaryToHex(passhash, 596 passhash.size() / 2, // only want top half, at least for now. 597 ascii_buf, 598 sizeof(ascii_buf)); 599 return std::string(ascii_buf, sizeof(ascii_buf) - 1); 600 } 601 602 std::string ParallelAuthenticator::SaltAsAscii() { 603 LoadSystemSalt(); // no-op if it's already loaded. 604 unsigned int salt_len = system_salt_.size(); 605 char ascii_salt[2 * salt_len + 1]; 606 if (ParallelAuthenticator::BinaryToHex(system_salt_, 607 salt_len, 608 ascii_salt, 609 sizeof(ascii_salt))) { 610 return std::string(ascii_salt, sizeof(ascii_salt) - 1); 611 } 612 return std::string(); 613 } 614 615 // static 616 bool ParallelAuthenticator::BinaryToHex( 617 const std::vector<unsigned char>& binary, 618 const unsigned int binary_len, 619 char* hex_string, 620 const unsigned int len) { 621 if (len < 2*binary_len) 622 return false; 623 memset(hex_string, 0, len); 624 for (uint i = 0, j = 0; i < binary_len; i++, j+=2) 625 snprintf(hex_string + j, len - j, "%02x", binary[i]); 626 return true; 627 } 628 629 } // namespace chromeos 630