Home | History | Annotate | Download | only in password_manager
      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