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