password_manager_util_win.cc revision 23730a6e56a168d1879203e4b3819bb36e3d8f1f
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.
4
5// windows.h must be first otherwise Win8 SDK breaks.
6#include <windows.h>
7#include <wincred.h>
8#include <LM.h>
9
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
15
16#include "chrome/browser/password_manager/password_manager_util.h"
17
18#include "base/prefs/pref_registry_simple.h"
19#include "base/prefs/pref_service.h"
20#include "base/strings/utf_string_conversions.h"
21#include "base/time/time.h"
22#include "base/win/windows_version.h"
23#include "chrome/browser/browser_process.h"
24#include "components/password_manager/core/browser/password_manager.h"
25#include "components/password_manager/core/common/password_manager_pref_names.h"
26#include "content/public/browser/render_view_host.h"
27#include "content/public/browser/render_widget_host_view.h"
28#include "grit/chromium_strings.h"
29#include "grit/generated_resources.h"
30#include "ui/base/l10n/l10n_util.h"
31
32#if defined(USE_AURA)
33#include "ui/aura/window.h"
34#include "ui/aura/window_tree_host.h"
35#endif
36
37// static
38void PasswordManager::RegisterLocalPrefs(PrefRegistrySimple* registry) {
39  registry->RegisterInt64Pref(prefs::kOsPasswordLastChanged, 0);
40  registry->RegisterBooleanPref(prefs::kOsPasswordBlank, false);
41}
42
43namespace password_manager_util {
44
45const unsigned kMaxPasswordRetries = 3;
46
47const unsigned kCredUiDefaultFlags =
48    CREDUI_FLAGS_GENERIC_CREDENTIALS |
49    CREDUI_FLAGS_EXCLUDE_CERTIFICATES |
50    CREDUI_FLAGS_KEEP_USERNAME |
51    CREDUI_FLAGS_ALWAYS_SHOW_UI |
52    CREDUI_FLAGS_DO_NOT_PERSIST;
53
54static int64 GetPasswordLastChanged(WCHAR* username) {
55  LPUSER_INFO_1 user_info = NULL;
56  DWORD age = 0;
57
58  NET_API_STATUS ret = NetUserGetInfo(NULL, username, 1, (LPBYTE*) &user_info);
59
60  if (ret == NERR_Success) {
61    // Returns seconds since last password change.
62    age = user_info->usri1_password_age;
63    NetApiBufferFree(user_info);
64  } else {
65    return -1;
66  }
67
68  base::Time changed = base::Time::Now() - base::TimeDelta::FromSeconds(age);
69
70  return changed.ToInternalValue();
71}
72
73static bool CheckBlankPassword(WCHAR* username) {
74  PrefService* local_state = g_browser_process->local_state();
75  int64 last_changed = GetPasswordLastChanged(username);
76  bool need_recheck = true;
77  bool blank_password = false;
78
79  // If we cannot determine when the password was last changed
80  // then assume the password is not blank
81  if (last_changed == -1)
82    return false;
83
84  blank_password = local_state->GetBoolean(prefs::kOsPasswordBlank);
85  int64 pref_last_changed =
86      local_state->GetInt64(prefs::kOsPasswordLastChanged);
87  if (pref_last_changed > 0 && last_changed <= pref_last_changed) {
88    need_recheck = false;
89  }
90
91  if (need_recheck) {
92    HANDLE handle = INVALID_HANDLE_VALUE;
93
94    // Attempt to login using blank password.
95    DWORD logon_result = LogonUser(username,
96                                   L".",
97                                   L"",
98                                   LOGON32_LOGON_NETWORK,
99                                   LOGON32_PROVIDER_DEFAULT,
100                                   &handle);
101
102    // Win XP and later return ERROR_ACCOUNT_RESTRICTION for blank password.
103    if (logon_result)
104      CloseHandle(handle);
105
106    // In the case the password is blank, then LogonUser returns a failure,
107    // handle is INVALID_HANDLE_VALUE, and GetLastError() is
108    // ERROR_ACCOUNT_RESTRICTION.
109    // http://msdn.microsoft.com/en-us/library/windows/desktop/ms681385
110    blank_password = (logon_result ||
111                      GetLastError() == ERROR_ACCOUNT_RESTRICTION);
112  }
113
114  // Account for clock skew between pulling the password age and
115  // writing to the preferences by adding a small skew factor here.
116  last_changed += base::Time::kMicrosecondsPerSecond;
117
118  // Save the blank password status for later.
119  local_state->SetBoolean(prefs::kOsPasswordBlank, blank_password);
120  local_state->SetInt64(prefs::kOsPasswordLastChanged, last_changed);
121
122  return blank_password;
123}
124
125OsPasswordStatus GetOsPasswordStatus() {
126  DWORD username_length = CREDUI_MAX_USERNAME_LENGTH;
127  WCHAR username[CREDUI_MAX_USERNAME_LENGTH+1] = {};
128  OsPasswordStatus retVal = PASSWORD_STATUS_UNKNOWN;
129
130  if (GetUserNameEx(NameUserPrincipal, username, &username_length)) {
131    // If we are on a domain, it is almost certain that the password is not
132    // blank, but we do not actively check any further than this to avoid any
133    // failed login attempts hitting the domain controller.
134    retVal = PASSWORD_STATUS_WIN_DOMAIN;
135  } else {
136    username_length = CREDUI_MAX_USERNAME_LENGTH;
137    if (GetUserName(username, &username_length)) {
138      retVal = CheckBlankPassword(username) ? PASSWORD_STATUS_BLANK :
139          PASSWORD_STATUS_NONBLANK;
140    }
141  }
142
143  return retVal;
144}
145
146bool AuthenticateUser(gfx::NativeWindow window) {
147  bool retval = false;
148  CREDUI_INFO cui = {};
149  WCHAR username[CREDUI_MAX_USERNAME_LENGTH+1] = {};
150  WCHAR displayname[CREDUI_MAX_USERNAME_LENGTH+1] = {};
151  WCHAR password[CREDUI_MAX_PASSWORD_LENGTH+1] = {};
152  DWORD username_length = CREDUI_MAX_USERNAME_LENGTH;
153  std::wstring product_name =
154      base::UTF16ToWide(l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
155  std::wstring password_prompt =
156      base::UTF16ToWide(l10n_util::GetStringUTF16(
157                            IDS_PASSWORDS_PAGE_AUTHENTICATION_PROMPT));
158  HANDLE handle = INVALID_HANDLE_VALUE;
159  int tries = 0;
160  bool use_displayname = false;
161  bool use_principalname = false;
162  DWORD logon_result = 0;
163
164  // Disable password manager reauthentication before Windows 7.
165  // This is because of an interaction between LogonUser() and the sandbox.
166  // http://crbug.com/345916
167  if (base::win::GetVersion() < base::win::VERSION_WIN7)
168    return true;
169
170  // On a domain, we obtain the User Principal Name
171  // for domain authentication.
172  if (GetUserNameEx(NameUserPrincipal, username, &username_length)) {
173    use_principalname = true;
174  } else {
175    username_length = CREDUI_MAX_USERNAME_LENGTH;
176    // Otherwise, we're a workstation, use the plain local username.
177    if (!GetUserName(username, &username_length)) {
178      DLOG(ERROR) << "Unable to obtain username " << GetLastError();
179      return false;
180    } else {
181      // As we are on a workstation, it's possible the user
182      // has no password, so check here.
183      if (CheckBlankPassword(username))
184        return true;
185    }
186  }
187
188  // Try and obtain a friendly display name.
189  username_length = CREDUI_MAX_USERNAME_LENGTH;
190  if (GetUserNameEx(NameDisplay, displayname, &username_length))
191    use_displayname = true;
192
193  cui.cbSize = sizeof(CREDUI_INFO);
194  cui.hwndParent = NULL;
195#if defined(USE_AURA)
196  cui.hwndParent = window->GetHost()->GetAcceleratedWidget();
197#else
198  cui.hwndParent = window;
199#endif
200
201  cui.pszMessageText = password_prompt.c_str();
202  cui.pszCaptionText = product_name.c_str();
203
204  cui.hbmBanner = NULL;
205  BOOL save_password = FALSE;
206  DWORD credErr = NO_ERROR;
207
208  do {
209    tries++;
210
211    // TODO(wfh) Make sure we support smart cards here.
212    credErr = CredUIPromptForCredentials(
213        &cui,
214        product_name.c_str(),
215        NULL,
216        0,
217        use_displayname ? displayname : username,
218        CREDUI_MAX_USERNAME_LENGTH+1,
219        password,
220        CREDUI_MAX_PASSWORD_LENGTH+1,
221        &save_password,
222        kCredUiDefaultFlags |
223        (tries > 1 ? CREDUI_FLAGS_INCORRECT_PASSWORD : 0));
224
225    if (credErr == NO_ERROR) {
226      logon_result = LogonUser(username,
227                               use_principalname ? NULL : L".",
228                               password,
229                               LOGON32_LOGON_NETWORK,
230                               LOGON32_PROVIDER_DEFAULT,
231                               &handle);
232      if (logon_result) {
233        retval = true;
234        CloseHandle(handle);
235      } else {
236        if (GetLastError() == ERROR_ACCOUNT_RESTRICTION &&
237            wcslen(password) == 0) {
238          // Password is blank, so permit.
239          retval = true;
240        } else {
241          DLOG(WARNING) << "Unable to authenticate " << GetLastError();
242        }
243      }
244      SecureZeroMemory(password, sizeof(password));
245    }
246  } while (credErr == NO_ERROR &&
247           (retval == false && tries < kMaxPasswordRetries));
248  return retval;
249}
250
251}  // namespace password_manager_util
252