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.
10 // SECURITY_WIN32 must be defined in order to get
11 // EXTENDED_NAME_FORMAT enumeration.
12 #define SECURITY_WIN32 1
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
{
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.
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_
;
72 void PasswordCheckPrefs::Read(PrefService
* local_state
) {
74 local_state
->GetBoolean(password_manager::prefs::kOsPasswordBlank
);
76 local_state
->GetInt64(password_manager::prefs::kOsPasswordLastChanged
);
79 void PasswordCheckPrefs::Write(PrefService
* local_state
) {
80 local_state
->SetBoolean(password_manager::prefs::kOsPasswordBlank
,
82 local_state
->SetInt64(password_manager::prefs::kOsPasswordLastChanged
,
86 int64
GetPasswordLastChanged(const WCHAR
* username
) {
87 LPUSER_INFO_1 user_info
= NULL
;
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
);
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)
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;
122 HANDLE handle
= INVALID_HANDLE_VALUE
;
124 // Attempt to login using blank password.
125 DWORD logon_result
= LogonUser(username
,
128 LOGON32_LOGON_NETWORK
,
129 LOGON32_PROVIDER_DEFAULT
,
132 auto last_error
= GetLastError();
133 // Win XP and later return ERROR_ACCOUNT_RESTRICTION for blank password.
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
);
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
;
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(
207 base::Bind(&GetOsPasswordStatusInternal
, prefs
.get(), status
.get()),
208 base::Bind(&ReplyOsPasswordStatus
, base::Passed(&prefs
),
209 base::Passed(&status
)),
212 UMA_HISTOGRAM_ENUMERATION("PasswordManager.OsPasswordStatus",
213 PASSWORD_STATUS_UNKNOWN
, MAX_PASSWORD_STATUS
);
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
) {
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
;
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
)
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;
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();
258 // As we are on a workstation, it's possible the user
259 // has no password, so check here.
260 if (CheckBlankPassword(username
))
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
;
284 // TODO(wfh) Make sure we support smart cards here.
285 credErr
= CredUIPromptForCredentials(
287 product_name
.c_str(),
290 use_displayname
? displayname
: username
,
291 CREDUI_MAX_USERNAME_LENGTH
+1,
293 CREDUI_MAX_PASSWORD_LENGTH
+1,
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
".",
302 LOGON32_LOGON_NETWORK
,
303 LOGON32_PROVIDER_DEFAULT
,
309 if (GetLastError() == ERROR_ACCOUNT_RESTRICTION
&&
310 wcslen(password
) == 0) {
311 // Password is blank, so permit.
314 DLOG(WARNING
) << "Unable to authenticate " << GetLastError();
317 SecureZeroMemory(password
, sizeof(password
));
319 } while (credErr
== NO_ERROR
&&
320 (retval
== false && tries
< kMaxPasswordRetries
));
324 } // namespace password_manager_util_win