password_manager_util_win.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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/safe_numerics.h"
21#include "base/strings/utf_string_conversions.h"
22#include "base/time/time.h"
23#include "chrome/browser/browser_process.h"
24#include "chrome/browser/password_manager/password_manager.h"
25#include "chrome/common/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/root_window.h"
34#include "ui/aura/window.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
125bool AuthenticateUser(gfx::NativeWindow window) {
126  bool retval = false;
127  CREDUI_INFO cui = {};
128  WCHAR username[CREDUI_MAX_USERNAME_LENGTH+1] = {};
129  WCHAR displayname[CREDUI_MAX_USERNAME_LENGTH+1] = {};
130  WCHAR password[CREDUI_MAX_PASSWORD_LENGTH+1] = {};
131  DWORD username_length = CREDUI_MAX_USERNAME_LENGTH;
132  std::wstring product_name =
133      UTF16ToWide(l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
134  std::wstring password_prompt =
135      UTF16ToWide(l10n_util::GetStringUTF16(
136                  IDS_PASSWORDS_PAGE_AUTHENTICATION_PROMPT));
137  HANDLE handle = INVALID_HANDLE_VALUE;
138  int tries = 0;
139  bool use_displayname = false;
140  bool use_principalname = false;
141  DWORD logon_result = 0;
142
143  // On a domain, we obtain the User Principal Name
144  // for domain authentication.
145  if (GetUserNameEx(NameUserPrincipal, username, &username_length)) {
146    use_principalname = true;
147  } else {
148    username_length = CREDUI_MAX_USERNAME_LENGTH;
149    // Otherwise, we're a workstation, use the plain local username.
150    if (!GetUserName(username, &username_length)) {
151      DLOG(ERROR) << "Unable to obtain username " << GetLastError();
152      return false;
153    } else {
154      // As we are on a workstation, it's possible the user
155      // has no password, so check here.
156      if (CheckBlankPassword(username))
157        return true;
158    }
159  }
160
161  // Try and obtain a friendly display name.
162  username_length = CREDUI_MAX_USERNAME_LENGTH;
163  if (GetUserNameEx(NameDisplay, displayname, &username_length))
164    use_displayname = true;
165
166  cui.cbSize = sizeof(CREDUI_INFO);
167  cui.hwndParent = NULL;
168#if defined(USE_AURA)
169  cui.hwndParent = window->GetDispatcher()->host()->GetAcceleratedWidget();
170#else
171  cui.hwndParent = window;
172#endif
173
174  cui.pszMessageText = password_prompt.c_str();
175  cui.pszCaptionText = product_name.c_str();
176
177  cui.hbmBanner = NULL;
178  BOOL save_password = FALSE;
179  DWORD credErr = NO_ERROR;
180
181  do {
182    tries++;
183
184    // TODO(wfh) Make sure we support smart cards here.
185    credErr = CredUIPromptForCredentials(
186        &cui,
187        product_name.c_str(),
188        NULL,
189        0,
190        use_displayname ? displayname : username,
191        CREDUI_MAX_USERNAME_LENGTH+1,
192        password,
193        CREDUI_MAX_PASSWORD_LENGTH+1,
194        &save_password,
195        kCredUiDefaultFlags |
196        (tries > 1 ? CREDUI_FLAGS_INCORRECT_PASSWORD : 0));
197
198    if (credErr == NO_ERROR) {
199      logon_result = LogonUser(username,
200                               use_principalname ? NULL : L".",
201                               password,
202                               LOGON32_LOGON_NETWORK,
203                               LOGON32_PROVIDER_DEFAULT,
204                               &handle);
205      if (logon_result) {
206        retval = true;
207        CloseHandle(handle);
208      } else {
209        if (GetLastError() == ERROR_ACCOUNT_RESTRICTION &&
210            wcslen(password) == 0) {
211          // Password is blank, so permit.
212          retval = true;
213        } else {
214          DLOG(WARNING) << "Unable to authenticate " << GetLastError();
215        }
216      }
217      SecureZeroMemory(password, sizeof(password));
218    }
219  } while (credErr == NO_ERROR &&
220           (retval == false && tries < kMaxPasswordRetries));
221  return retval;
222}
223
224}  // namespace password_manager_util
225