Roll src/third_party/WebKit eac3800:0237a66 (svn 202606:202607)
[chromium-blink-merge.git] / chrome / browser / password_manager / password_manager_util_win.cc
blobd5f7f3e8db3274844e033d02b14d17d243b6d3b7
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.
5 // windows.h must be first otherwise Win8 SDK breaks.
6 #include <windows.h>
7 #include <LM.h>
8 #include <wincred.h>
10 // SECURITY_WIN32 must be defined in order to get
11 // EXTENDED_NAME_FORMAT enumeration.
12 #define SECURITY_WIN32 1
13 #include <security.h>
14 #undef SECURITY_WIN32
16 #include "chrome/browser/password_manager/password_manager_util_win.h"
18 #include "base/bind.h"
19 #include "base/bind_helpers.h"
20 #include "base/memory/scoped_ptr.h"
21 #include "base/metrics/histogram_macros.h"
22 #include "base/prefs/pref_service.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/threading/worker_pool.h"
25 #include "base/time/time.h"
26 #include "base/win/windows_version.h"
27 #include "chrome/browser/browser_process.h"
28 #include "chrome/grit/chromium_strings.h"
29 #include "components/password_manager/core/browser/password_manager.h"
30 #include "components/password_manager/core/common/password_manager_pref_names.h"
31 #include "content/public/browser/browser_thread.h"
32 #include "content/public/browser/render_view_host.h"
33 #include "content/public/browser/render_widget_host_view.h"
34 #include "ui/aura/window.h"
35 #include "ui/aura/window_tree_host.h"
36 #include "ui/base/l10n/l10n_util.h"
38 namespace password_manager_util_win {
39 namespace {
41 enum OsPasswordStatus {
42 PASSWORD_STATUS_UNKNOWN = 0,
43 PASSWORD_STATUS_UNSUPPORTED,
44 PASSWORD_STATUS_BLANK,
45 PASSWORD_STATUS_NONBLANK,
46 PASSWORD_STATUS_WIN_DOMAIN,
47 // NOTE: Add new status types only immediately above this line. Also,
48 // make sure the enum list in tools/histogram/histograms.xml is
49 // updated with any change in here.
50 MAX_PASSWORD_STATUS
53 const unsigned kMaxPasswordRetries = 3;
55 const unsigned kCredUiDefaultFlags =
56 CREDUI_FLAGS_GENERIC_CREDENTIALS |
57 CREDUI_FLAGS_EXCLUDE_CERTIFICATES |
58 CREDUI_FLAGS_KEEP_USERNAME |
59 CREDUI_FLAGS_ALWAYS_SHOW_UI |
60 CREDUI_FLAGS_DO_NOT_PERSIST;
62 struct PasswordCheckPrefs {
63 PasswordCheckPrefs() : pref_last_changed_(0), blank_password_(false) {}
65 void Read(PrefService* local_state);
66 void Write(PrefService* local_state);
68 int64 pref_last_changed_;
69 bool blank_password_;
72 void PasswordCheckPrefs::Read(PrefService* local_state) {
73 blank_password_ =
74 local_state->GetBoolean(password_manager::prefs::kOsPasswordBlank);
75 pref_last_changed_ =
76 local_state->GetInt64(password_manager::prefs::kOsPasswordLastChanged);
79 void PasswordCheckPrefs::Write(PrefService* local_state) {
80 local_state->SetBoolean(password_manager::prefs::kOsPasswordBlank,
81 blank_password_);
82 local_state->SetInt64(password_manager::prefs::kOsPasswordLastChanged,
83 pref_last_changed_);
86 int64 GetPasswordLastChanged(const WCHAR* username) {
87 LPUSER_INFO_1 user_info = NULL;
88 DWORD age = 0;
90 NET_API_STATUS ret = NetUserGetInfo(NULL, username, 1, (LPBYTE*) &user_info);
92 if (ret == NERR_Success) {
93 // Returns seconds since last password change.
94 age = user_info->usri1_password_age;
95 NetApiBufferFree(user_info);
96 } else {
97 return -1;
100 base::Time changed = base::Time::Now() - base::TimeDelta::FromSeconds(age);
102 return changed.ToInternalValue();
105 bool CheckBlankPasswordWithPrefs(const WCHAR* username,
106 PasswordCheckPrefs* prefs) {
107 int64 last_changed = GetPasswordLastChanged(username);
109 // If we cannot determine when the password was last changed
110 // then assume the password is not blank
111 if (last_changed == -1)
112 return false;
114 bool blank_password = prefs->blank_password_;
115 bool need_recheck = true;
116 if (prefs->pref_last_changed_ > 0 &&
117 last_changed <= prefs->pref_last_changed_) {
118 need_recheck = false;
121 if (need_recheck) {
122 HANDLE handle = INVALID_HANDLE_VALUE;
124 // Attempt to login using blank password.
125 DWORD logon_result = LogonUser(username,
126 L".",
127 L"",
128 LOGON32_LOGON_NETWORK,
129 LOGON32_PROVIDER_DEFAULT,
130 &handle);
132 auto last_error = GetLastError();
133 // Win XP and later return ERROR_ACCOUNT_RESTRICTION for blank password.
134 if (logon_result)
135 CloseHandle(handle);
137 // In the case the password is blank, then LogonUser returns a failure,
138 // handle is INVALID_HANDLE_VALUE, and GetLastError() is
139 // ERROR_ACCOUNT_RESTRICTION.
140 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms681385
141 blank_password = (logon_result ||
142 last_error == ERROR_ACCOUNT_RESTRICTION);
145 // Account for clock skew between pulling the password age and
146 // writing to the preferences by adding a small skew factor here.
147 last_changed += base::Time::kMicrosecondsPerSecond;
149 // Update the preferences with new values.
150 prefs->pref_last_changed_ = last_changed;
151 prefs->blank_password_ = blank_password;
152 return blank_password;
155 // Wrapper around CheckBlankPasswordWithPrefs to be called on UI thread.
156 bool CheckBlankPassword(const WCHAR* username) {
157 PrefService* local_state = g_browser_process->local_state();
158 PasswordCheckPrefs prefs;
159 prefs.Read(local_state);
160 bool result = CheckBlankPasswordWithPrefs(username, &prefs);
161 prefs.Write(local_state);
162 return result;
165 void GetOsPasswordStatusInternal(PasswordCheckPrefs* prefs,
166 OsPasswordStatus* status) {
167 DWORD username_length = CREDUI_MAX_USERNAME_LENGTH;
168 WCHAR username[CREDUI_MAX_USERNAME_LENGTH+1] = {};
169 *status = PASSWORD_STATUS_UNKNOWN;
171 if (GetUserNameEx(NameUserPrincipal, username, &username_length)) {
172 // If we are on a domain, it is almost certain that the password is not
173 // blank, but we do not actively check any further than this to avoid any
174 // failed login attempts hitting the domain controller.
175 *status = PASSWORD_STATUS_WIN_DOMAIN;
176 } else {
177 username_length = CREDUI_MAX_USERNAME_LENGTH;
178 // CheckBlankPasswordWithPrefs() isn't safe to call on before Windows 7.
179 // http://crbug.com/345916
180 if (base::win::GetVersion() >= base::win::VERSION_WIN7 &&
181 GetUserName(username, &username_length)) {
182 *status = CheckBlankPasswordWithPrefs(username, prefs) ?
183 PASSWORD_STATUS_BLANK :
184 PASSWORD_STATUS_NONBLANK;
189 void ReplyOsPasswordStatus(scoped_ptr<PasswordCheckPrefs> prefs,
190 scoped_ptr<OsPasswordStatus> status) {
191 PrefService* local_state = g_browser_process->local_state();
192 prefs->Write(local_state);
193 UMA_HISTOGRAM_ENUMERATION("PasswordManager.OsPasswordStatus", *status,
194 MAX_PASSWORD_STATUS);
197 void GetOsPasswordStatus() {
198 // Preferences can be accessed on the UI thread only.
199 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
200 PrefService* local_state = g_browser_process->local_state();
201 scoped_ptr<PasswordCheckPrefs> prefs(new PasswordCheckPrefs);
202 prefs->Read(local_state);
203 scoped_ptr<OsPasswordStatus> status(
204 new OsPasswordStatus(PASSWORD_STATUS_UNKNOWN));
205 bool posted = base::WorkerPool::PostTaskAndReply(
206 FROM_HERE,
207 base::Bind(&GetOsPasswordStatusInternal, prefs.get(), status.get()),
208 base::Bind(&ReplyOsPasswordStatus, base::Passed(&prefs),
209 base::Passed(&status)),
210 true);
211 if (!posted) {
212 UMA_HISTOGRAM_ENUMERATION("PasswordManager.OsPasswordStatus",
213 PASSWORD_STATUS_UNKNOWN, MAX_PASSWORD_STATUS);
217 } // namespace
219 void DelayReportOsPassword() {
220 content::BrowserThread::PostDelayedTask(content::BrowserThread::UI, FROM_HERE,
221 base::Bind(&GetOsPasswordStatus),
222 base::TimeDelta::FromSeconds(40));
225 bool AuthenticateUser(gfx::NativeWindow window) {
226 bool retval = false;
227 CREDUI_INFO cui = {};
228 WCHAR username[CREDUI_MAX_USERNAME_LENGTH+1] = {};
229 WCHAR displayname[CREDUI_MAX_USERNAME_LENGTH+1] = {};
230 WCHAR password[CREDUI_MAX_PASSWORD_LENGTH+1] = {};
231 DWORD username_length = CREDUI_MAX_USERNAME_LENGTH;
232 base::string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
233 base::string16 password_prompt =
234 l10n_util::GetStringUTF16(IDS_PASSWORDS_PAGE_AUTHENTICATION_PROMPT);
235 HANDLE handle = INVALID_HANDLE_VALUE;
236 int tries = 0;
237 bool use_displayname = false;
238 bool use_principalname = false;
239 DWORD logon_result = 0;
241 // Disable password manager reauthentication before Windows 7.
242 // This is because of an interaction between LogonUser() and the sandbox.
243 // http://crbug.com/345916
244 if (base::win::GetVersion() < base::win::VERSION_WIN7)
245 return true;
247 // On a domain, we obtain the User Principal Name
248 // for domain authentication.
249 if (GetUserNameEx(NameUserPrincipal, username, &username_length)) {
250 use_principalname = true;
251 } else {
252 username_length = CREDUI_MAX_USERNAME_LENGTH;
253 // Otherwise, we're a workstation, use the plain local username.
254 if (!GetUserName(username, &username_length)) {
255 DLOG(ERROR) << "Unable to obtain username " << GetLastError();
256 return false;
257 } else {
258 // As we are on a workstation, it's possible the user
259 // has no password, so check here.
260 if (CheckBlankPassword(username))
261 return true;
265 // Try and obtain a friendly display name.
266 username_length = CREDUI_MAX_USERNAME_LENGTH;
267 if (GetUserNameEx(NameDisplay, displayname, &username_length))
268 use_displayname = true;
270 cui.cbSize = sizeof(CREDUI_INFO);
271 cui.hwndParent = NULL;
272 cui.hwndParent = window->GetHost()->GetAcceleratedWidget();
274 cui.pszMessageText = password_prompt.c_str();
275 cui.pszCaptionText = product_name.c_str();
277 cui.hbmBanner = NULL;
278 BOOL save_password = FALSE;
279 DWORD credErr = NO_ERROR;
281 do {
282 tries++;
284 // TODO(wfh) Make sure we support smart cards here.
285 credErr = CredUIPromptForCredentials(
286 &cui,
287 product_name.c_str(),
288 NULL,
290 use_displayname ? displayname : username,
291 CREDUI_MAX_USERNAME_LENGTH+1,
292 password,
293 CREDUI_MAX_PASSWORD_LENGTH+1,
294 &save_password,
295 kCredUiDefaultFlags |
296 (tries > 1 ? CREDUI_FLAGS_INCORRECT_PASSWORD : 0));
298 if (credErr == NO_ERROR) {
299 logon_result = LogonUser(username,
300 use_principalname ? NULL : L".",
301 password,
302 LOGON32_LOGON_NETWORK,
303 LOGON32_PROVIDER_DEFAULT,
304 &handle);
305 if (logon_result) {
306 retval = true;
307 CloseHandle(handle);
308 } else {
309 if (GetLastError() == ERROR_ACCOUNT_RESTRICTION &&
310 wcslen(password) == 0) {
311 // Password is blank, so permit.
312 retval = true;
313 } else {
314 DLOG(WARNING) << "Unable to authenticate " << GetLastError();
317 SecureZeroMemory(password, sizeof(password));
319 } while (credErr == NO_ERROR &&
320 (retval == false && tries < kMaxPasswordRetries));
321 return retval;
324 } // namespace password_manager_util_win