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 38 void 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 46 namespace password_manager_util { 47 48 const unsigned kMaxPasswordRetries = 3; 49 50 const 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 57 static 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 76 static 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 131 OsPasswordStatus 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 152 bool 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