local_auth.cc revision 1e9bf3e0803691d0a228da41fc608347b6db4340
1// Copyright 2013 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/signin/local_auth.h"
6
7#include "base/base64.h"
8#include "base/logging.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/metrics/histogram.h"
11#include "base/prefs/pref_service.h"
12#include "base/strings/string_util.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/common/pref_names.h"
15#include "components/user_prefs/pref_registry_syncable.h"
16#include "components/webdata/encryptor/encryptor.h"
17#include "crypto/random.h"
18#include "crypto/secure_util.h"
19#include "crypto/symmetric_key.h"
20
21namespace {
22
23// WARNING: Changing these values will make it impossible to do off-line
24// authentication until the next successful on-line authentication.  To change
25// these safely, change the "encoding" version below and make verification
26// handle multiple values.
27const char kHash1Encoding = '1';
28const unsigned kHash1Bits = 256;
29const unsigned kHash1Bytes = kHash1Bits / 8;
30const unsigned kHash1IterationCount = 100000;
31
32std::string CreateSecurePasswordHash(const std::string& salt,
33                                     const std::string& password,
34                                     char encoding) {
35  DCHECK_EQ(kHash1Bytes, salt.length());
36  DCHECK_EQ(kHash1Encoding, encoding);  // Currently support only one method.
37
38  base::Time start_time = base::Time::Now();
39
40  // Library call to create secure password hash as SymmetricKey (uses PBKDF2).
41  scoped_ptr<crypto::SymmetricKey> password_key(
42      crypto::SymmetricKey::DeriveKeyFromPassword(
43          crypto::SymmetricKey::AES,
44          password, salt,
45          kHash1IterationCount, kHash1Bits));
46  std::string password_hash;
47  const bool success = password_key->GetRawKey(&password_hash);
48  DCHECK(success);
49  DCHECK_EQ(kHash1Bytes, password_hash.length());
50
51  UMA_HISTOGRAM_TIMES("PasswordHash.CreateTime",
52                      base::Time::Now() - start_time);
53
54  return password_hash;
55}
56
57std::string EncodePasswordHashRecord(const std::string& record,
58                                     char encoding) {
59  DCHECK_EQ(kHash1Encoding, encoding);  // Currently support only one method.
60
61  // Encrypt the hash using the OS account-password protection (if available).
62  std::string encoded;
63  const bool success = Encryptor::EncryptString(record, &encoded);
64  DCHECK(success);
65
66  // Convert binary record to text for preference database.
67  std::string encoded64;
68  base::Base64Encode(encoded, &encoded64);
69
70  // Stuff the "encoding" value into the first byte.
71  encoded64.insert(0, &encoding, sizeof(encoding));
72
73  return encoded64;
74}
75
76bool DecodePasswordHashRecord(const std::string& encoded,
77                              std::string* decoded,
78                              char* encoding) {
79  // Extract the "encoding" value from the first byte and validate.
80  if (encoded.length() < 1)
81    return false;
82  *encoding = encoded[0];
83  if (*encoding != kHash1Encoding)
84    return false;
85
86  // Stored record is base64; convert to binary.
87  std::string unbase64;
88  if (!base::Base64Decode(encoded.substr(1), &unbase64))
89    return false;
90
91  // Decrypt the record using the OS account-password protection (if available).
92  return Encryptor::DecryptString(unbase64, decoded);
93}
94
95}  // namespace
96
97namespace chrome {
98
99void RegisterLocalAuthPrefs(user_prefs::PrefRegistrySyncable* registry) {
100  registry->RegisterStringPref(
101      prefs::kGoogleServicesPasswordHash,
102      std::string(),
103      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
104}
105
106void SetLocalAuthCredentials(Profile* profile,
107                             const std::string& username,
108                             const std::string& password) {
109  DCHECK(profile);
110  DCHECK(username.length());
111  DCHECK(password.length());
112
113  // Salt should be random data, as long as the hash length, and different with
114  // every save.
115  std::string salt_str;
116  crypto::RandBytes(WriteInto(&salt_str, kHash1Bytes + 1), kHash1Bytes);
117  DCHECK_EQ(kHash1Bytes, salt_str.length());
118
119  // Perform secure hash of password for storage.
120  std::string password_hash = CreateSecurePasswordHash(
121      salt_str, password, kHash1Encoding);
122  DCHECK_EQ(kHash1Bytes, password_hash.length());
123
124  // Group all fields into a single record for storage;
125  std::string record;
126  record.append(salt_str);
127  record.append(password_hash);
128  record.append(username);
129
130  // Encode it and store it.
131  std::string encoded = EncodePasswordHashRecord(record, kHash1Encoding);
132  profile->GetPrefs()->SetString(prefs::kGoogleServicesPasswordHash,
133                                 encoded);
134}
135
136bool ValidateLocalAuthCredentials(Profile* profile,
137                                  const std::string& username,
138                                  const std::string& password) {
139  DCHECK(profile);
140  DCHECK(username.length());
141  DCHECK(password.length());
142
143  const size_t name_length = username.length();
144  std::string record;
145  char encoding;
146
147  if (!profile->GetPrefs()->HasPrefPath(prefs::kGoogleServicesPasswordHash))
148    return false;
149  std::string encodedhash = profile->GetPrefs()->GetString(
150      prefs::kGoogleServicesPasswordHash);
151  if (!DecodePasswordHashRecord(encodedhash, &record, &encoding))
152    return false;
153
154  std::string password_hash;
155  const char* password_saved;
156  const char* password_check;
157  size_t password_length;
158
159  if (encoding == '1') {
160    // Validate correct length and username; extract salt and password hash.
161    if (record.length() != 2 * kHash1Bytes + name_length)
162      return false;
163    if (record.compare(2 * kHash1Bytes, name_length, username) != 0)
164      return false;
165    std::string salt_str(record.data(), kHash1Bytes);
166    password_saved = record.data() + kHash1Bytes;
167    password_hash = CreateSecurePasswordHash(salt_str, password, encoding);
168    password_check = password_hash.data();
169    password_length = kHash1Bytes;
170  } else {
171    // unknown encoding
172    return false;
173  }
174
175  return crypto::SecureMemEqual(password_saved, password_check,
176                                password_length);
177}
178
179}  // namespace chrome
180