Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / password_manager / password_manager_util_win.cc
blob9b52cace0da100f1d2593b176ca24c7219446def
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 "components/password_manager/core/browser/password_manager_util.h"
18 #include "base/bind.h"
19 #include "base/prefs/pref_service.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/threading/worker_pool.h"
22 #include "base/time/time.h"
23 #include "base/win/windows_version.h"
24 #include "chrome/browser/browser_process.h"
25 #include "chrome/grit/chromium_strings.h"
26 #include "components/password_manager/core/browser/password_manager.h"
27 #include "components/password_manager/core/common/password_manager_pref_names.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "content/public/browser/render_view_host.h"
30 #include "content/public/browser/render_widget_host_view.h"
31 #include "ui/aura/window.h"
32 #include "ui/aura/window_tree_host.h"
33 #include "ui/base/l10n/l10n_util.h"
35 namespace password_manager_util {
36 namespace {
38 const unsigned kMaxPasswordRetries = 3;
40 const unsigned kCredUiDefaultFlags =
41 CREDUI_FLAGS_GENERIC_CREDENTIALS |
42 CREDUI_FLAGS_EXCLUDE_CERTIFICATES |
43 CREDUI_FLAGS_KEEP_USERNAME |
44 CREDUI_FLAGS_ALWAYS_SHOW_UI |
45 CREDUI_FLAGS_DO_NOT_PERSIST;
47 struct PasswordCheckPrefs {
48 PasswordCheckPrefs() : pref_last_changed_(0), blank_password_(false) {}
50 void Read(PrefService* local_state);
51 void Write(PrefService* local_state);
53 int64 pref_last_changed_;
54 bool blank_password_;
57 void PasswordCheckPrefs::Read(PrefService* local_state) {
58 blank_password_ =
59 local_state->GetBoolean(password_manager::prefs::kOsPasswordBlank);
60 pref_last_changed_ =
61 local_state->GetInt64(password_manager::prefs::kOsPasswordLastChanged);
64 void PasswordCheckPrefs::Write(PrefService* local_state) {
65 local_state->SetBoolean(password_manager::prefs::kOsPasswordBlank,
66 blank_password_);
67 local_state->SetInt64(password_manager::prefs::kOsPasswordLastChanged,
68 pref_last_changed_);
71 int64 GetPasswordLastChanged(const WCHAR* username) {
72 LPUSER_INFO_1 user_info = NULL;
73 DWORD age = 0;
75 NET_API_STATUS ret = NetUserGetInfo(NULL, username, 1, (LPBYTE*) &user_info);
77 if (ret == NERR_Success) {
78 // Returns seconds since last password change.
79 age = user_info->usri1_password_age;
80 NetApiBufferFree(user_info);
81 } else {
82 return -1;
85 base::Time changed = base::Time::Now() - base::TimeDelta::FromSeconds(age);
87 return changed.ToInternalValue();
90 bool CheckBlankPasswordWithPrefs(const WCHAR* username,
91 PasswordCheckPrefs* prefs) {
92 int64 last_changed = GetPasswordLastChanged(username);
94 // If we cannot determine when the password was last changed
95 // then assume the password is not blank
96 if (last_changed == -1)
97 return false;
99 bool blank_password = prefs->blank_password_;
100 bool need_recheck = true;
101 if (prefs->pref_last_changed_ > 0 &&
102 last_changed <= prefs->pref_last_changed_) {
103 need_recheck = false;
106 if (need_recheck) {
107 HANDLE handle = INVALID_HANDLE_VALUE;
109 // Attempt to login using blank password.
110 DWORD logon_result = LogonUser(username,
111 L".",
112 L"",
113 LOGON32_LOGON_NETWORK,
114 LOGON32_PROVIDER_DEFAULT,
115 &handle);
117 // Win XP and later return ERROR_ACCOUNT_RESTRICTION for blank password.
118 if (logon_result)
119 CloseHandle(handle);
121 // In the case the password is blank, then LogonUser returns a failure,
122 // handle is INVALID_HANDLE_VALUE, and GetLastError() is
123 // ERROR_ACCOUNT_RESTRICTION.
124 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms681385
125 blank_password = (logon_result ||
126 GetLastError() == ERROR_ACCOUNT_RESTRICTION);
129 // Account for clock skew between pulling the password age and
130 // writing to the preferences by adding a small skew factor here.
131 last_changed += base::Time::kMicrosecondsPerSecond;
133 // Update the preferences with new values.
134 prefs->pref_last_changed_ = last_changed;
135 prefs->blank_password_ = blank_password;
136 return blank_password;
139 // Wrapper around CheckBlankPasswordWithPrefs to be called on UI thread.
140 bool CheckBlankPassword(const WCHAR* username) {
141 PrefService* local_state = g_browser_process->local_state();
142 PasswordCheckPrefs prefs;
143 prefs.Read(local_state);
144 bool result = CheckBlankPasswordWithPrefs(username, &prefs);
145 prefs.Write(local_state);
146 return result;
149 void GetOsPasswordStatusInternal(PasswordCheckPrefs* prefs,
150 OsPasswordStatus* retVal) {
151 DWORD username_length = CREDUI_MAX_USERNAME_LENGTH;
152 WCHAR username[CREDUI_MAX_USERNAME_LENGTH+1] = {};
153 *retVal = PASSWORD_STATUS_UNKNOWN;
155 if (GetUserNameEx(NameUserPrincipal, username, &username_length)) {
156 // If we are on a domain, it is almost certain that the password is not
157 // blank, but we do not actively check any further than this to avoid any
158 // failed login attempts hitting the domain controller.
159 *retVal = PASSWORD_STATUS_WIN_DOMAIN;
160 } else {
161 username_length = CREDUI_MAX_USERNAME_LENGTH;
162 // CheckBlankPasswordWithPrefs() isn't safe to call on before Windows 7.
163 // http://crbug.com/345916
164 if (base::win::GetVersion() >= base::win::VERSION_WIN7 &&
165 GetUserName(username, &username_length)) {
166 *retVal = CheckBlankPasswordWithPrefs(username, prefs) ?
167 PASSWORD_STATUS_BLANK :
168 PASSWORD_STATUS_NONBLANK;
173 void ReplyOsPasswordStatus(const base::Callback<void(OsPasswordStatus)>& reply,
174 PasswordCheckPrefs* prefs,
175 OsPasswordStatus* retVal) {
176 PrefService* local_state = g_browser_process->local_state();
177 prefs->Write(local_state);
178 reply.Run(*retVal);
181 } // namespace
183 void GetOsPasswordStatus(const base::Callback<void(OsPasswordStatus)>& reply) {
184 // Preferences can be accessed on the UI thread only.
185 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
186 PrefService* local_state = g_browser_process->local_state();
187 PasswordCheckPrefs* prefs = new PasswordCheckPrefs;
188 prefs->Read(local_state);
189 OsPasswordStatus* retVal = new OsPasswordStatus(PASSWORD_STATUS_UNKNOWN);
190 bool posted = base::WorkerPool::PostTaskAndReply(
191 FROM_HERE,
192 base::Bind(&GetOsPasswordStatusInternal,
193 base::Unretained(prefs), base::Unretained(retVal)),
194 base::Bind(&ReplyOsPasswordStatus,
195 reply, base::Owned(prefs), base::Owned(retVal)),
196 true);
197 if (!posted)
198 reply.Run(PASSWORD_STATUS_UNKNOWN);
201 bool AuthenticateUser(gfx::NativeWindow window) {
202 bool retval = false;
203 CREDUI_INFO cui = {};
204 WCHAR username[CREDUI_MAX_USERNAME_LENGTH+1] = {};
205 WCHAR displayname[CREDUI_MAX_USERNAME_LENGTH+1] = {};
206 WCHAR password[CREDUI_MAX_PASSWORD_LENGTH+1] = {};
207 DWORD username_length = CREDUI_MAX_USERNAME_LENGTH;
208 base::string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
209 base::string16 password_prompt =
210 l10n_util::GetStringUTF16(IDS_PASSWORDS_PAGE_AUTHENTICATION_PROMPT);
211 HANDLE handle = INVALID_HANDLE_VALUE;
212 int tries = 0;
213 bool use_displayname = false;
214 bool use_principalname = false;
215 DWORD logon_result = 0;
217 // Disable password manager reauthentication before Windows 7.
218 // This is because of an interaction between LogonUser() and the sandbox.
219 // http://crbug.com/345916
220 if (base::win::GetVersion() < base::win::VERSION_WIN7)
221 return true;
223 // On a domain, we obtain the User Principal Name
224 // for domain authentication.
225 if (GetUserNameEx(NameUserPrincipal, username, &username_length)) {
226 use_principalname = true;
227 } else {
228 username_length = CREDUI_MAX_USERNAME_LENGTH;
229 // Otherwise, we're a workstation, use the plain local username.
230 if (!GetUserName(username, &username_length)) {
231 DLOG(ERROR) << "Unable to obtain username " << GetLastError();
232 return false;
233 } else {
234 // As we are on a workstation, it's possible the user
235 // has no password, so check here.
236 if (CheckBlankPassword(username))
237 return true;
241 // Try and obtain a friendly display name.
242 username_length = CREDUI_MAX_USERNAME_LENGTH;
243 if (GetUserNameEx(NameDisplay, displayname, &username_length))
244 use_displayname = true;
246 cui.cbSize = sizeof(CREDUI_INFO);
247 cui.hwndParent = NULL;
248 cui.hwndParent = window->GetHost()->GetAcceleratedWidget();
250 cui.pszMessageText = password_prompt.c_str();
251 cui.pszCaptionText = product_name.c_str();
253 cui.hbmBanner = NULL;
254 BOOL save_password = FALSE;
255 DWORD credErr = NO_ERROR;
257 do {
258 tries++;
260 // TODO(wfh) Make sure we support smart cards here.
261 credErr = CredUIPromptForCredentials(
262 &cui,
263 product_name.c_str(),
264 NULL,
266 use_displayname ? displayname : username,
267 CREDUI_MAX_USERNAME_LENGTH+1,
268 password,
269 CREDUI_MAX_PASSWORD_LENGTH+1,
270 &save_password,
271 kCredUiDefaultFlags |
272 (tries > 1 ? CREDUI_FLAGS_INCORRECT_PASSWORD : 0));
274 if (credErr == NO_ERROR) {
275 logon_result = LogonUser(username,
276 use_principalname ? NULL : L".",
277 password,
278 LOGON32_LOGON_NETWORK,
279 LOGON32_PROVIDER_DEFAULT,
280 &handle);
281 if (logon_result) {
282 retval = true;
283 CloseHandle(handle);
284 } else {
285 if (GetLastError() == ERROR_ACCOUNT_RESTRICTION &&
286 wcslen(password) == 0) {
287 // Password is blank, so permit.
288 retval = true;
289 } else {
290 DLOG(WARNING) << "Unable to authenticate " << GetLastError();
293 SecureZeroMemory(password, sizeof(password));
295 } while (credErr == NO_ERROR &&
296 (retval == false && tries < kMaxPasswordRetries));
297 return retval;
300 } // namespace password_manager_util