chromium/chrome/credential_provider/gaiacp/gcp_utils.h

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_CREDENTIAL_PROVIDER_GAIACP_GCP_UTILS_H_
#define CHROME_CREDENTIAL_PROVIDER_GAIACP_GCP_UTILS_H_

#include <windows.h>

#include <memory>
#include <string>
#include <string_view>

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "base/types/optional_ref.h"
#include "base/values.h"
#include "base/version.h"
#include "base/win/scoped_handle.h"
#include "base/win/windows_types.h"
#include "chrome/credential_provider/gaiacp/internet_availability_checker.h"
#include "chrome/credential_provider/gaiacp/scoped_lsa_policy.h"
#include "chrome/credential_provider/gaiacp/win_http_url_fetcher.h"
#include "url/gurl.h"

// These define are documented in
// https://msdn.microsoft.com/en-us/library/bb470234(v=vs.85).aspx not available
// in the user mode headers.
#define DIRECTORY_QUERY 0x00000001
#define DIRECTORY_TRAVERSE 0x00000002
#define DIRECTORY_CREATE_OBJECT 0x00000004
#define DIRECTORY_CREATE_SUBDIRECTORY 0x00000008
#define DIRECTORY_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | 0xF)

namespace base {

class CommandLine;
class FilePath;

}  // namespace base

namespace credential_provider {

// Windows supports a maximum of 20 characters plus null in username.
inline constexpr int kWindowsUsernameBufferLength = 21;

inline constexpr int kWindowsPasswordBufferLength = 32;

// Maximum domain length is 256 characters including null.
// https://support.microsoft.com/en-ca/help/909264/naming-conventions-in-active-directory-for-computers-domains-sites-and
constexpr int kWindowsDomainBufferLength = 256;

// According to:
// https://stackoverflow.com/questions/1140528/what-is-the-maximum-length-of-a-sid-in-sddl-format
constexpr int kWindowsSidBufferLength = 184;

// Max number of attempts to find a new username when a user already exists
// with the same username.
constexpr int kMaxUsernameAttempts = 10;

// First index to append to a username when another user with the same name
// already exists.
constexpr int kInitialDuplicateUsernameIndex = 2;

// Default extension used as a fallback if the picture_url returned from gaia
// does not have a file extension.
extern const wchar_t kDefaultProfilePictureFileExtension[];

// Name of the sub-folder under which all files for GCPW are stored.
extern const base::FilePath::CharType kCredentialProviderFolder[];

// Default URL for the GEM MDM API.
extern const wchar_t kDefaultMdmUrl[];

// Maximum number of consecutive Upload device details failures for which we do
// enforce auth.
extern const int kMaxNumConsecutiveUploadDeviceFailures;

// Maximum allowed time delta after which user policies should be refreshed
// again.
extern const base::TimeDelta kMaxTimeDeltaSinceLastUserPolicyRefresh;

// Maximum allowed time delta after which experiments should be fetched
// again.
extern const base::TimeDelta kMaxTimeDeltaSinceLastExperimentsFetch;

// Path elements for the path where the experiments are stored on disk.
extern const wchar_t kGcpwExperimentsDirectory[];
extern const wchar_t kGcpwUserExperimentsFileName[];

// Because of some strange dependency problems with windows header files,
// define STATUS_SUCCESS here instead of including ntstatus.h or SubAuth.h
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)

// A bitfield indicating which standard handles are to be created.
using StdHandlesToCreate = uint32_t;

enum : uint32_t {
  kStdOutput = 1 << 0,
  kStdInput = 1 << 1,
  kStdError = 1 << 2,
  kAllStdHandles = kStdOutput | kStdInput | kStdError
};

// Filled in by InitializeStdHandles to return the parent side of stdin/stdout/
// stderr pipes of the login UI process.
struct StdParentHandles {
  StdParentHandles();
  ~StdParentHandles();

  base::win::ScopedHandle hstdin_write;
  base::win::ScopedHandle hstdout_read;
  base::win::ScopedHandle hstderr_read;
};

// Class used in tests to set registration data for testing.
class GoogleRegistrationDataForTesting {
 public:
  explicit GoogleRegistrationDataForTesting(std::wstring serial_number);
  ~GoogleRegistrationDataForTesting();
};

// Class used in tests to set gem device details for testing.
class GemDeviceDetailsForTesting {
 public:
  explicit GemDeviceDetailsForTesting(std::vector<std::string>& mac_addresses,
                                      std::string os_version);
  ~GemDeviceDetailsForTesting();
};

// Class used in tests to set chrome path for testing.
class GoogleChromePathForTesting {
 public:
  explicit GoogleChromePathForTesting(base::FilePath chrome_path);
  ~GoogleChromePathForTesting();
};

// Process startup options that allows customization of stdin/stdout/stderr
// handles.
class ScopedStartupInfo {
 public:
  ScopedStartupInfo();
  explicit ScopedStartupInfo(const wchar_t* desktop);
  ~ScopedStartupInfo();

  // This function takes ownership of the handles.
  HRESULT SetStdHandles(base::win::ScopedHandle* hstdin,
                        base::win::ScopedHandle* hstdout,
                        base::win::ScopedHandle* hstderr);

  LPSTARTUPINFOW GetInfo() { return &info_; }

  // Releases all resources held by this info.
  void Shutdown();

 private:
  STARTUPINFOW info_;
  std::wstring desktop_;
};

// Gets the brand specific path in which to install GCPW.
base::FilePath::StringType GetInstallParentDirectoryName();

// Gets the directory where the GCP is installed
base::FilePath GetInstallDirectory();

// Deletes versions of GCP found under |gcp_path| except for version
// |product_version|.
void DeleteVersionsExcept(const base::FilePath& gcp_path,
                          const std::wstring& product_version);

// Waits for the process specified by |procinfo| to terminate.  The handles
// in |read_handles| can be used to read stdout/err from the process.  Upon
// return, |exit_code| contains one of the UIEC_xxx constants listed above,
// and |stdout_buffer| and |stderr_buffer| contain the output, if any.
// Both buffers must be at least |buffer_size| characters long.
HRESULT WaitForProcess(base::win::ScopedHandle::Handle process_handle,
                       const StdParentHandles& parent_handles,
                       DWORD* exit_code,
                       char* output_buffer,
                       int buffer_size);

// Creates a restricted, batch or interactive login token for the given user.
HRESULT CreateLogonToken(const wchar_t* domain,
                         const wchar_t* username,
                         const wchar_t* password,
                         bool interactive,
                         base::win::ScopedHandle* token);

HRESULT CreateJobForSignin(base::win::ScopedHandle* job);

// Creates a pipe that can be used by a parent process to communicate with a
// child process.  If |child_reads| is false, then it is expected that the
// parent process will read from |reading| anything the child process writes
// to |writing|.  For example, this is used to read stdout/stderr of child.
//
// If |child_reads| is true, then it is expected that the child process will
// read from |reading| anything the parent process writes to |writing|.  For
// example, this is used to write to stdin of child.
//
// If |use_nul| is true, then the parent's handle is not used (can be passed
// as nullptr).  The child reads from or writes to the null device.
HRESULT CreatePipeForChildProcess(bool child_reads,
                                  bool use_nul,
                                  base::win::ScopedHandle* reading,
                                  base::win::ScopedHandle* writing);

// Initializes 3 pipes for communicating with a child process.  On return,
// |startupinfo| will be set with the handles needed by the child.  This is
// used when creating the child process.  |parent_handles| contains the
// corresponding handles to be used by the parent process.
//
// Communication direction is used to optimize handle creation.  If
// communication occurs in only one direction then some pipes will be directed
// to the nul device.
enum class CommDirection {
  kParentToChildOnly,
  kChildToParentOnly,
  kBidirectional,
};
HRESULT InitializeStdHandles(CommDirection direction,
                             StdHandlesToCreate to_create,
                             ScopedStartupInfo* startupinfo,
                             StdParentHandles* parent_handles);

// Fills |path_to_dll| with the short path to the dll referenced by
// |dll_handle|. The short path is needed to correctly call rundll32.exe in
// cases where there might be quotes or spaces in the path.
HRESULT GetPathToDllFromHandle(HINSTANCE dll_handle,
                               base::FilePath* path_to_dll);

// This function gets a correctly formatted entry point argument to pass to
// rundll32.exe for a dll referenced by the handle |dll_handle| and an entry
// point function with the name |entrypoint|. |entrypoint_arg| will be filled
// with the argument value.
HRESULT GetEntryPointArgumentForRunDll(HINSTANCE dll_handle,
                                       const wchar_t* entrypoint,
                                       std::wstring* entrypoint_arg);

// This function is used to build the command line for rundll32 to call an
// exported entrypoint from the DLL given by |dll_handle|.
// Returns S_FALSE if a command line can successfully be built but if the
// path to the "dll" actually points to a non ".dll" file. This allows
// detection of calls to this function via a unit test which will be
// running under an ".exe" module.
HRESULT GetCommandLineForEntrypoint(HINSTANCE dll_handle,
                                    const wchar_t* entrypoint,
                                    base::CommandLine* command_line);

// Looks up the name associated to the |sid| (if any). Returns an error on any
// failure or no name is associated with the |sid|.
HRESULT LookupLocalizedNameBySid(PSID sid, std::wstring* localized_name);

// Gets localalized name for builtin administrator account.
HRESULT GetLocalizedNameBuiltinAdministratorAccount(
    std::wstring* builtin_localized_admin_name);

// Looks up the name associated to the well known |sid_type| (if any). Returns
// an error on any failure or no name is associated with the |sid_type|.
HRESULT LookupLocalizedNameForWellKnownSid(WELL_KNOWN_SID_TYPE sid_type,
                                           std::wstring* localized_name);

// Handles the writing and deletion of a startup sentinel file used to ensure
// that the GCPW does not crash continuously on startup and render the
// winlogon process unusable.
bool WriteToStartupSentinel();
void DeleteStartupSentinel();
void DeleteStartupSentinelForVersion(const std::wstring& version);

// Gets a string resource from the DLL with the given id.
std::wstring GetStringResource(UINT base_message_id);

// Gets a string resource from the DLL with the given id after replacing the
// placeholders with the provided substitutions.
std::wstring GetStringResource(UINT base_message_id,
                               const std::vector<std::wstring>& subst);

// Gets the language selected by the base::win::i18n::LanguageSelector.
std::wstring GetSelectedLanguage();

// Securely clear a base::Value::Dict that may have a password field.
void SecurelyClearDictionaryValue(base::optional_ref<base::Value::Dict> dict);
void SecurelyClearDictionaryValueWithKey(
    base::optional_ref<base::Value::Dict> dict,
    const std::string& password_key);

// Securely clear std::wstring and std::string.
void SecurelyClearString(std::wstring& str);
void SecurelyClearString(std::string& str);

// Securely clear a given |buffer| with size |length|.
void SecurelyClearBuffer(void* buffer, size_t length);

// Helpers to get strings from base::Value::Dict.
std::wstring GetDictString(const base::Value::Dict& dict, const char* name);
std::string GetDictStringUTF8(const base::Value::Dict& dict, const char* name);

// Perform a recursive search on a nested dictionary object. Note that the
// names provided in the input should be in order. Below is an example : Lets
// say the json object is {"key1": {"key2": {"key3": "value1"}}, "key4":
// "value2"}. Then to search for the key "key3", this method should be called
// by providing the |path| as {"key1", "key2", "key3"}.
std::string SearchForKeyInStringDictUTF8(
    const std::string& json_string,
    const std::initializer_list<std::string_view>& path);

// Perform a recursive search on a nested dictionary object. Note that the
// names provided in the input should be in order. Below is an example : Lets
// say the json object is
// {"key1": {"key2": {"value": "value1", "value": "value2"}}}.
// Then to search for the key "key2" and list_key as "value", then this method
// should be called by providing |list_key| as "value", |path| as
// ["key1", "key2"] and the result returned would be ["value1", "value2"].
HRESULT SearchForListInStringDictUTF8(
    const std::string& list_key,
    const std::string& json_string,
    const std::initializer_list<std::string_view>& path,
    std::vector<std::string>* output);

// Returns the major build version of Windows by reading the registry.
// See:
// https://stackoverflow.com/questions/31072543/reliable-way-to-get-windows-version-from-registry
std::wstring GetWindowsVersion();

// Returns the minimum supported version of Chrome for GCPW.
base::Version GetMinimumSupportedChromeVersion();

class OSUserManager;
class OSProcessManager;

// This structure is used in tests to set fake objects in the credential
// provider dll.  See the function SetFakesForTesting() for details.
struct FakesForTesting {
  FakesForTesting();
  ~FakesForTesting();

  ScopedLsaPolicy::CreatorCallback scoped_lsa_policy_creator;
  raw_ptr<OSUserManager> os_user_manager_for_testing = nullptr;
  raw_ptr<OSProcessManager> os_process_manager_for_testing = nullptr;
  WinHttpUrlFetcher::CreatorCallback fake_win_http_url_fetcher_creator;
  raw_ptr<InternetAvailabilityChecker>
      internet_availability_checker_for_testing = nullptr;
};

// DLL entrypoint signature for settings testing fakes.  This is used by
// the setup tests to install fakes into the dynamically loaded gaia1_0 DLL
// static data.  This way the production DLL does not need to include binary
// code used only for testing.
typedef void CALLBACK (*SetFakesForTestingFn)(const FakesForTesting* fakes);

// Initializes the members of a Windows STRING struct (UNICODE_STRING or
// LSA_STRING) to point to the string pointed to by |string|.
template <class WindowsStringT,
          class WindowsStringCharT = decltype(WindowsStringT().Buffer[0])>
void InitWindowsStringWithString(const WindowsStringCharT* string,
                                 WindowsStringT* windows_string) {
  constexpr size_t buffer_char_size = sizeof(WindowsStringCharT);
  windows_string->Buffer = const_cast<WindowsStringCharT*>(string);
  windows_string->Length = static_cast<USHORT>(
      std::char_traits<WindowsStringCharT>::length((windows_string->Buffer)) *
      buffer_char_size);
  windows_string->MaximumLength = windows_string->Length + buffer_char_size;
}

// Extracts the provided keys from the given dictionary. Returns true if all
// keys are found. If any of the key isn't found, returns false.
bool ExtractKeysFromDict(
    const base::Value::Dict& dict,
    const std::vector<std::pair<std::string, std::string*>>& needed_outputs);

// Gets the bios serial number of the windows device.
std::wstring GetSerialNumber();

// Gets the mac addresses of the windows device.
std::vector<std::string> GetMacAddresses();

// Gets the OS version installed on the device. The format is
// "major.minor.build".
void GetOsVersion(std::string* version);

// Gets the obfuscated device_id that is a combination of multiple device
// identifiers.
HRESULT GenerateDeviceId(std::string* device_id);

// Overrides the gaia_url and gcpw_endpoint_path that is used to load GLS.
HRESULT SetGaiaEndpointCommandLineIfNeeded(const wchar_t* override_registry_key,
                                           const std::string& default_endpoint,
                                           bool provide_deviceid,
                                           bool show_tos,
                                           base::CommandLine* command_line);

// Returns the file path to installed chrome.exe.
base::FilePath GetChromePath();

// Returns the file path to system installed chrome.exe.
base::FilePath GetSystemChromePath();

// Generates gcpw dm token for the given |sid|. If any of the lsa operations
// fail, function returns a result other than S_OK.
HRESULT GenerateGCPWDmToken(const std::wstring& sid);

// Reads the gcpw dm token from lsa store for the given |sid| and writes it back
// in |token| output parameter.  If any of the lsa operations fail, function
// returns a result other than S_OK.
HRESULT GetGCPWDmToken(const std::wstring& sid, std::wstring* token);

// Gets the gcpw service URL.
GURL GetGcpwServiceUrl();

// Converts the |url| in the form of http://xxxxx.googleapis.com/...
// to a form that points to a development URL as specified with |dev|
// environment. Final url will be in the form
// https://{dev}-xxxxx.sandbox.googleapis.com/...
std::wstring GetDevelopmentUrl(const std::wstring& url,
                               const std::wstring& dev);

// Returns a handle to a file which is stored under DIR_COMMON_APP_DATA > |sid|
// > |file_dir| > |file_name|. The file is opened with the provided
// |open_flags|.
std::unique_ptr<base::File> GetOpenedFileForUser(const std::wstring& sid,
                                                 uint32_t open_flags,
                                                 const std::wstring& file_dir,
                                                 const std::wstring& file_name);

// Returns the time delta since the last fetch for the given |sid|. |flag|
// stores the last fetch time.
base::TimeDelta GetTimeDeltaSinceLastFetch(const std::wstring& sid,
                                           const std::wstring& flag);

}  // namespace credential_provider

#endif  // CHROME_CREDENTIAL_PROVIDER_GAIACP_GCP_UTILS_H_