password_manager_util_win.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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_event_dispatcher.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