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 <LM.h>
      8 #include <wincred.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 "chrome/grit/chromium_strings.h"
     25 #include "components/password_manager/core/browser/password_manager.h"
     26 #include "components/password_manager/core/common/password_manager_pref_names.h"
     27 #include "content/public/browser/render_view_host.h"
     28 #include "content/public/browser/render_widget_host_view.h"
     29 #include "ui/base/l10n/l10n_util.h"
     30 
     31 #if defined(USE_AURA)
     32 #include "ui/aura/window.h"
     33 #include "ui/aura/window_tree_host.h"
     34 #endif
     35 
     36 // static
     37 void password_manager::PasswordManager::RegisterLocalPrefs(
     38     PrefRegistrySimple* registry) {
     39   registry->RegisterInt64Pref(password_manager::prefs::kOsPasswordLastChanged,
     40                               0);
     41   registry->RegisterBooleanPref(password_manager::prefs::kOsPasswordBlank,
     42                                 false);
     43 }
     44 
     45 namespace password_manager_util {
     46 
     47 const unsigned kMaxPasswordRetries = 3;
     48 
     49 const unsigned kCredUiDefaultFlags =
     50     CREDUI_FLAGS_GENERIC_CREDENTIALS |
     51     CREDUI_FLAGS_EXCLUDE_CERTIFICATES |
     52     CREDUI_FLAGS_KEEP_USERNAME |
     53     CREDUI_FLAGS_ALWAYS_SHOW_UI |
     54     CREDUI_FLAGS_DO_NOT_PERSIST;
     55 
     56 static int64 GetPasswordLastChanged(WCHAR* username) {
     57   LPUSER_INFO_1 user_info = NULL;
     58   DWORD age = 0;
     59 
     60   NET_API_STATUS ret = NetUserGetInfo(NULL, username, 1, (LPBYTE*) &user_info);
     61 
     62   if (ret == NERR_Success) {
     63     // Returns seconds since last password change.
     64     age = user_info->usri1_password_age;
     65     NetApiBufferFree(user_info);
     66   } else {
     67     return -1;
     68   }
     69 
     70   base::Time changed = base::Time::Now() - base::TimeDelta::FromSeconds(age);
     71 
     72   return changed.ToInternalValue();
     73 }
     74 
     75 static bool CheckBlankPassword(WCHAR* username) {
     76   PrefService* local_state = g_browser_process->local_state();
     77   int64 last_changed = GetPasswordLastChanged(username);
     78   bool need_recheck = true;
     79   bool blank_password = false;
     80 
     81   // If we cannot determine when the password was last changed
     82   // then assume the password is not blank
     83   if (last_changed == -1)
     84     return false;
     85 
     86   blank_password =
     87       local_state->GetBoolean(password_manager::prefs::kOsPasswordBlank);
     88   int64 pref_last_changed =
     89       local_state->GetInt64(password_manager::prefs::kOsPasswordLastChanged);
     90   if (pref_last_changed > 0 && last_changed <= pref_last_changed) {
     91     need_recheck = false;
     92   }
     93 
     94   if (need_recheck) {
     95     HANDLE handle = INVALID_HANDLE_VALUE;
     96 
     97     // Attempt to login using blank password.
     98     DWORD logon_result = LogonUser(username,
     99                                    L".",
    100                                    L"",
    101                                    LOGON32_LOGON_NETWORK,
    102                                    LOGON32_PROVIDER_DEFAULT,
    103                                    &handle);
    104 
    105     // Win XP and later return ERROR_ACCOUNT_RESTRICTION for blank password.
    106     if (logon_result)
    107       CloseHandle(handle);
    108 
    109     // In the case the password is blank, then LogonUser returns a failure,
    110     // handle is INVALID_HANDLE_VALUE, and GetLastError() is
    111     // ERROR_ACCOUNT_RESTRICTION.
    112     // http://msdn.microsoft.com/en-us/library/windows/desktop/ms681385
    113     blank_password = (logon_result ||
    114                       GetLastError() == ERROR_ACCOUNT_RESTRICTION);
    115   }
    116 
    117   // Account for clock skew between pulling the password age and
    118   // writing to the preferences by adding a small skew factor here.
    119   last_changed += base::Time::kMicrosecondsPerSecond;
    120 
    121   // Save the blank password status for later.
    122   local_state->SetBoolean(password_manager::prefs::kOsPasswordBlank,
    123                           blank_password);
    124   local_state->SetInt64(password_manager::prefs::kOsPasswordLastChanged,
    125                         last_changed);
    126 
    127   return blank_password;
    128 }
    129 
    130 OsPasswordStatus GetOsPasswordStatus() {
    131   DWORD username_length = CREDUI_MAX_USERNAME_LENGTH;
    132   WCHAR username[CREDUI_MAX_USERNAME_LENGTH+1] = {};
    133   OsPasswordStatus retVal = PASSWORD_STATUS_UNKNOWN;
    134 
    135   if (GetUserNameEx(NameUserPrincipal, username, &username_length)) {
    136     // If we are on a domain, it is almost certain that the password is not
    137     // blank, but we do not actively check any further than this to avoid any
    138     // failed login attempts hitting the domain controller.
    139     retVal = PASSWORD_STATUS_WIN_DOMAIN;
    140   } else {
    141     username_length = CREDUI_MAX_USERNAME_LENGTH;
    142     if (GetUserName(username, &username_length)) {
    143       retVal = CheckBlankPassword(username) ? PASSWORD_STATUS_BLANK :
    144           PASSWORD_STATUS_NONBLANK;
    145     }
    146   }
    147 
    148   return retVal;
    149 }
    150 
    151 bool AuthenticateUser(gfx::NativeWindow window) {
    152   bool retval = false;
    153   CREDUI_INFO cui = {};
    154   WCHAR username[CREDUI_MAX_USERNAME_LENGTH+1] = {};
    155   WCHAR displayname[CREDUI_MAX_USERNAME_LENGTH+1] = {};
    156   WCHAR password[CREDUI_MAX_PASSWORD_LENGTH+1] = {};
    157   DWORD username_length = CREDUI_MAX_USERNAME_LENGTH;
    158   base::string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
    159   base::string16 password_prompt =
    160       l10n_util::GetStringUTF16(IDS_PASSWORDS_PAGE_AUTHENTICATION_PROMPT);
    161   HANDLE handle = INVALID_HANDLE_VALUE;
    162   int tries = 0;
    163   bool use_displayname = false;
    164   bool use_principalname = false;
    165   DWORD logon_result = 0;
    166 
    167   // Disable password manager reauthentication before Windows 7.
    168   // This is because of an interaction between LogonUser() and the sandbox.
    169   // http://crbug.com/345916
    170   if (base::win::GetVersion() < base::win::VERSION_WIN7)
    171     return true;
    172 
    173   // On a domain, we obtain the User Principal Name
    174   // for domain authentication.
    175   if (GetUserNameEx(NameUserPrincipal, username, &username_length)) {
    176     use_principalname = true;
    177   } else {
    178     username_length = CREDUI_MAX_USERNAME_LENGTH;
    179     // Otherwise, we're a workstation, use the plain local username.
    180     if (!GetUserName(username, &username_length)) {
    181       DLOG(ERROR) << "Unable to obtain username " << GetLastError();
    182       return false;
    183     } else {
    184       // As we are on a workstation, it's possible the user
    185       // has no password, so check here.
    186       if (CheckBlankPassword(username))
    187         return true;
    188     }
    189   }
    190 
    191   // Try and obtain a friendly display name.
    192   username_length = CREDUI_MAX_USERNAME_LENGTH;
    193   if (GetUserNameEx(NameDisplay, displayname, &username_length))
    194     use_displayname = true;
    195 
    196   cui.cbSize = sizeof(CREDUI_INFO);
    197   cui.hwndParent = NULL;
    198 #if defined(USE_AURA)
    199   cui.hwndParent = window->GetHost()->GetAcceleratedWidget();
    200 #else
    201   cui.hwndParent = window;
    202 #endif
    203 
    204   cui.pszMessageText = password_prompt.c_str();
    205   cui.pszCaptionText = product_name.c_str();
    206 
    207   cui.hbmBanner = NULL;
    208   BOOL save_password = FALSE;
    209   DWORD credErr = NO_ERROR;
    210 
    211   do {
    212     tries++;
    213 
    214     // TODO(wfh) Make sure we support smart cards here.
    215     credErr = CredUIPromptForCredentials(
    216         &cui,
    217         product_name.c_str(),
    218         NULL,
    219         0,
    220         use_displayname ? displayname : username,
    221         CREDUI_MAX_USERNAME_LENGTH+1,
    222         password,
    223         CREDUI_MAX_PASSWORD_LENGTH+1,
    224         &save_password,
    225         kCredUiDefaultFlags |
    226         (tries > 1 ? CREDUI_FLAGS_INCORRECT_PASSWORD : 0));
    227 
    228     if (credErr == NO_ERROR) {
    229       logon_result = LogonUser(username,
    230                                use_principalname ? NULL : L".",
    231                                password,
    232                                LOGON32_LOGON_NETWORK,
    233                                LOGON32_PROVIDER_DEFAULT,
    234                                &handle);
    235       if (logon_result) {
    236         retval = true;
    237         CloseHandle(handle);
    238       } else {
    239         if (GetLastError() == ERROR_ACCOUNT_RESTRICTION &&
    240             wcslen(password) == 0) {
    241           // Password is blank, so permit.
    242           retval = true;
    243         } else {
    244           DLOG(WARNING) << "Unable to authenticate " << GetLastError();
    245         }
    246       }
    247       SecureZeroMemory(password, sizeof(password));
    248     }
    249   } while (credErr == NO_ERROR &&
    250            (retval == false && tries < kMaxPasswordRetries));
    251   return retval;
    252 }
    253 
    254 }  // namespace password_manager_util
    255