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