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 // Win XP and later return ERROR_ACCOUNT_RESTRICTION for blank password.
136 // In the case the password is blank, then LogonUser returns a failure,
137 // handle is INVALID_HANDLE_VALUE, and GetLastError() is
138 // ERROR_ACCOUNT_RESTRICTION.
139 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms681385
140 blank_password
= (logon_result
||
141 GetLastError() == ERROR_ACCOUNT_RESTRICTION
);
144 // Account for clock skew between pulling the password age and
145 // writing to the preferences by adding a small skew factor here.
146 last_changed
+= base::Time::kMicrosecondsPerSecond
;
148 // Update the preferences with new values.
149 prefs
->pref_last_changed_
= last_changed
;
150 prefs
->blank_password_
= blank_password
;
151 return blank_password
;
154 // Wrapper around CheckBlankPasswordWithPrefs to be called on UI thread.
155 bool CheckBlankPassword(const WCHAR
* username
) {
156 PrefService
* local_state
= g_browser_process
->local_state();
157 PasswordCheckPrefs prefs
;
158 prefs
.Read(local_state
);
159 bool result
= CheckBlankPasswordWithPrefs(username
, &prefs
);
160 prefs
.Write(local_state
);
164 void GetOsPasswordStatusInternal(PasswordCheckPrefs
* prefs
,
165 OsPasswordStatus
* status
) {
166 DWORD username_length
= CREDUI_MAX_USERNAME_LENGTH
;
167 WCHAR username
[CREDUI_MAX_USERNAME_LENGTH
+1] = {};
168 *status
= PASSWORD_STATUS_UNKNOWN
;
170 if (GetUserNameEx(NameUserPrincipal
, username
, &username_length
)) {
171 // If we are on a domain, it is almost certain that the password is not
172 // blank, but we do not actively check any further than this to avoid any
173 // failed login attempts hitting the domain controller.
174 *status
= PASSWORD_STATUS_WIN_DOMAIN
;
176 username_length
= CREDUI_MAX_USERNAME_LENGTH
;
177 // CheckBlankPasswordWithPrefs() isn't safe to call on before Windows 7.
178 // http://crbug.com/345916
179 if (base::win::GetVersion() >= base::win::VERSION_WIN7
&&
180 GetUserName(username
, &username_length
)) {
181 *status
= CheckBlankPasswordWithPrefs(username
, prefs
) ?
182 PASSWORD_STATUS_BLANK
:
183 PASSWORD_STATUS_NONBLANK
;
188 void ReplyOsPasswordStatus(scoped_ptr
<PasswordCheckPrefs
> prefs
,
189 scoped_ptr
<OsPasswordStatus
> status
) {
190 PrefService
* local_state
= g_browser_process
->local_state();
191 prefs
->Write(local_state
);
192 UMA_HISTOGRAM_ENUMERATION("PasswordManager.OsPasswordStatus", *status
,
193 MAX_PASSWORD_STATUS
);
196 void GetOsPasswordStatus() {
197 // Preferences can be accessed on the UI thread only.
198 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
199 PrefService
* local_state
= g_browser_process
->local_state();
200 scoped_ptr
<PasswordCheckPrefs
> prefs(new PasswordCheckPrefs
);
201 prefs
->Read(local_state
);
202 scoped_ptr
<OsPasswordStatus
> status(
203 new OsPasswordStatus(PASSWORD_STATUS_UNKNOWN
));
204 bool posted
= base::WorkerPool::PostTaskAndReply(
206 base::Bind(&GetOsPasswordStatusInternal
, prefs
.get(), status
.get()),
207 base::Bind(&ReplyOsPasswordStatus
, base::Passed(&prefs
),
208 base::Passed(&status
)),
211 UMA_HISTOGRAM_ENUMERATION("PasswordManager.OsPasswordStatus",
212 PASSWORD_STATUS_UNKNOWN
, MAX_PASSWORD_STATUS
);
218 void DelayReportOsPassword() {
219 content::BrowserThread::PostDelayedTask(content::BrowserThread::UI
, FROM_HERE
,
220 base::Bind(&GetOsPasswordStatus
),
221 base::TimeDelta::FromSeconds(40));
224 bool AuthenticateUser(gfx::NativeWindow window
) {
226 CREDUI_INFO cui
= {};
227 WCHAR username
[CREDUI_MAX_USERNAME_LENGTH
+1] = {};
228 WCHAR displayname
[CREDUI_MAX_USERNAME_LENGTH
+1] = {};
229 WCHAR password
[CREDUI_MAX_PASSWORD_LENGTH
+1] = {};
230 DWORD username_length
= CREDUI_MAX_USERNAME_LENGTH
;
231 base::string16 product_name
= l10n_util::GetStringUTF16(IDS_PRODUCT_NAME
);
232 base::string16 password_prompt
=
233 l10n_util::GetStringUTF16(IDS_PASSWORDS_PAGE_AUTHENTICATION_PROMPT
);
234 HANDLE handle
= INVALID_HANDLE_VALUE
;
236 bool use_displayname
= false;
237 bool use_principalname
= false;
238 DWORD logon_result
= 0;
240 // Disable password manager reauthentication before Windows 7.
241 // This is because of an interaction between LogonUser() and the sandbox.
242 // http://crbug.com/345916
243 if (base::win::GetVersion() < base::win::VERSION_WIN7
)
246 // On a domain, we obtain the User Principal Name
247 // for domain authentication.
248 if (GetUserNameEx(NameUserPrincipal
, username
, &username_length
)) {
249 use_principalname
= true;
251 username_length
= CREDUI_MAX_USERNAME_LENGTH
;
252 // Otherwise, we're a workstation, use the plain local username.
253 if (!GetUserName(username
, &username_length
)) {
254 DLOG(ERROR
) << "Unable to obtain username " << GetLastError();
257 // As we are on a workstation, it's possible the user
258 // has no password, so check here.
259 if (CheckBlankPassword(username
))
264 // Try and obtain a friendly display name.
265 username_length
= CREDUI_MAX_USERNAME_LENGTH
;
266 if (GetUserNameEx(NameDisplay
, displayname
, &username_length
))
267 use_displayname
= true;
269 cui
.cbSize
= sizeof(CREDUI_INFO
);
270 cui
.hwndParent
= NULL
;
271 cui
.hwndParent
= window
->GetHost()->GetAcceleratedWidget();
273 cui
.pszMessageText
= password_prompt
.c_str();
274 cui
.pszCaptionText
= product_name
.c_str();
276 cui
.hbmBanner
= NULL
;
277 BOOL save_password
= FALSE
;
278 DWORD credErr
= NO_ERROR
;
283 // TODO(wfh) Make sure we support smart cards here.
284 credErr
= CredUIPromptForCredentials(
286 product_name
.c_str(),
289 use_displayname
? displayname
: username
,
290 CREDUI_MAX_USERNAME_LENGTH
+1,
292 CREDUI_MAX_PASSWORD_LENGTH
+1,
294 kCredUiDefaultFlags
|
295 (tries
> 1 ? CREDUI_FLAGS_INCORRECT_PASSWORD
: 0));
297 if (credErr
== NO_ERROR
) {
298 logon_result
= LogonUser(username
,
299 use_principalname
? NULL
: L
".",
301 LOGON32_LOGON_NETWORK
,
302 LOGON32_PROVIDER_DEFAULT
,
308 if (GetLastError() == ERROR_ACCOUNT_RESTRICTION
&&
309 wcslen(password
) == 0) {
310 // Password is blank, so permit.
313 DLOG(WARNING
) << "Unable to authenticate " << GetLastError();
316 SecureZeroMemory(password
, sizeof(password
));
318 } while (credErr
== NO_ERROR
&&
319 (retval
== false && tries
< kMaxPasswordRetries
));
323 } // namespace password_manager_util_win