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
36using base::Time;
37using base::TimeDelta;
38using file_util::GetFileSize;
39using file_util::PathExists;
40using file_util::ReadFile;
41using file_util::ReadFileToString;
42
43namespace chromeos {
44
45// static
46const char ParallelAuthenticator::kLocalaccountFile[] = "localaccount";
47
48// static
49const int ParallelAuthenticator::kClientLoginTimeoutMs = 10000;
50// static
51const int ParallelAuthenticator::kLocalaccountRetryIntervalMs = 20;
52
53const int kPassHashLen = 32;
54
55ParallelAuthenticator::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
65ParallelAuthenticator::~ParallelAuthenticator() {}
66
67bool 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
98bool 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
117void 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
125void 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
146void 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
157void ParallelAuthenticator::OnPasswordChangeDetected(
158    const GaiaAuthConsumer::ClientLoginResult& credentials) {
159  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
160  consumer_->OnPasswordChangeDetected(credentials);
161}
162
163void 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
200void 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
212void 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
227void 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
238void 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
246void 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
262void 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
393ParallelAuthenticator::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
433ParallelAuthenticator::AuthState
434ParallelAuthenticator::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
455ParallelAuthenticator::AuthState
456ParallelAuthenticator::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
482ParallelAuthenticator::AuthState
483ParallelAuthenticator::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
496ParallelAuthenticator::AuthState
497ParallelAuthenticator::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
516ParallelAuthenticator::AuthState
517ParallelAuthenticator::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
533void 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
541void 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
563void 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
572std::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
602std::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
616bool 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