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 "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
{
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_
;
57 void PasswordCheckPrefs::Read(PrefService
* local_state
) {
59 local_state
->GetBoolean(password_manager::prefs::kOsPasswordBlank
);
61 local_state
->GetInt64(password_manager::prefs::kOsPasswordLastChanged
);
64 void PasswordCheckPrefs::Write(PrefService
* local_state
) {
65 local_state
->SetBoolean(password_manager::prefs::kOsPasswordBlank
,
67 local_state
->SetInt64(password_manager::prefs::kOsPasswordLastChanged
,
71 int64
GetPasswordLastChanged(const WCHAR
* username
) {
72 LPUSER_INFO_1 user_info
= NULL
;
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
);
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)
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;
107 HANDLE handle
= INVALID_HANDLE_VALUE
;
109 // Attempt to login using blank password.
110 DWORD logon_result
= LogonUser(username
,
113 LOGON32_LOGON_NETWORK
,
114 LOGON32_PROVIDER_DEFAULT
,
117 // Win XP and later return ERROR_ACCOUNT_RESTRICTION for blank password.
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
);
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
;
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
);
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(
192 base::Bind(&GetOsPasswordStatusInternal
,
193 base::Unretained(prefs
), base::Unretained(retVal
)),
194 base::Bind(&ReplyOsPasswordStatus
,
195 reply
, base::Owned(prefs
), base::Owned(retVal
)),
198 reply
.Run(PASSWORD_STATUS_UNKNOWN
);
201 bool AuthenticateUser(gfx::NativeWindow window
) {
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
;
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
)
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;
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();
234 // As we are on a workstation, it's possible the user
235 // has no password, so check here.
236 if (CheckBlankPassword(username
))
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
;
260 // TODO(wfh) Make sure we support smart cards here.
261 credErr
= CredUIPromptForCredentials(
263 product_name
.c_str(),
266 use_displayname
? displayname
: username
,
267 CREDUI_MAX_USERNAME_LENGTH
+1,
269 CREDUI_MAX_PASSWORD_LENGTH
+1,
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
".",
278 LOGON32_LOGON_NETWORK
,
279 LOGON32_PROVIDER_DEFAULT
,
285 if (GetLastError() == ERROR_ACCOUNT_RESTRICTION
&&
286 wcslen(password
) == 0) {
287 // Password is blank, so permit.
290 DLOG(WARNING
) << "Unable to authenticate " << GetLastError();
293 SecureZeroMemory(password
, sizeof(password
));
295 } while (credErr
== NO_ERROR
&&
296 (retval
== false && tries
< kMaxPasswordRetries
));
300 } // namespace password_manager_util