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