| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/enterprise/signals/device_info_fetcher_win.h" |
| |
| #include <Windows.h> |
| // SECURITY_WIN32 must be defined in order to get |
| // EXTENDED_NAME_FORMAT enumeration. |
| #define SECURITY_WIN32 1 |
| #include <security.h> |
| #undef SECURITY_WIN32 |
| #include <wincred.h> |
| |
| #include "base/files/file_path.h" |
| #include "base/path_service.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/system/sys_info.h" |
| #include "base/win/registry.h" |
| #include "base/win/win_util.h" |
| #include "base/win/windows_types.h" |
| #include "base/win/windows_version.h" |
| #include "base/win/wmi.h" |
| #include "chrome/browser/enterprise/signals/signals_common.h" |
| #include "net/base/network_interfaces.h" |
| |
| // Those headers need defines from windows_types.h, thus have to come after it. |
| #include <DSRole.h> // NOLINT(build/include_order) |
| #include <iphlpapi.h> // NOLINT(build/include_order) |
| #include <powersetting.h> // NOLINT(build/include_order) |
| #include <propsys.h> // NOLINT(build/include_order) |
| #include <shobjidl.h> // NOLINT(build/include_order) |
| |
| namespace enterprise_signals { |
| |
| namespace { |
| |
| constexpr wchar_t kSecureBootRegPath[] = |
| L"SYSTEM\\CurrentControlSet\\Control\\SecureBoot\\State"; |
| constexpr wchar_t kSecureBootRegKey[] = L"UEFISecureBootEnabled"; |
| |
| // Possible results of the "System.Volume.BitLockerProtection" shell property. |
| // These values are undocumented but were directly validated on a Windows 10 |
| // machine. See the comment above the GetDiskEncryption() method. |
| // The values in this enum should be kep in sync with the analogous definiotion |
| // in the native app implementation. |
| enum class BitLockerStatus { |
| // Encryption is on, and the volume is unlocked |
| kOn = 1, |
| // Encryption is off |
| kOff = 2, |
| // Encryption is in progress |
| kEncryptionInProgress = 3, |
| // Decryption is in progress |
| kDecryptionInProgress = 4, |
| // Encryption is on, but temporarily suspended |
| kSuspended = 5, |
| // Encryption is on, and the volume is locked |
| kLocked = 6, |
| }; |
| |
| // Retrieves the computer serial number from WMI. |
| std::string GetSerialNumber() { |
| base::win::WmiComputerSystemInfo sys_info = |
| base::win::WmiComputerSystemInfo::Get(); |
| return base::WideToUTF8(sys_info.serial_number()); |
| } |
| |
| // Retrieves the FQDN of the comeputer and if this fails reverts to the hostname |
| // as known to the net subsystem. |
| std::string GetComputerName() { |
| DWORD size = 1024; |
| std::string result(size, '\0'); |
| |
| if (!::GetComputerNameExA(ComputerNameDnsFullyQualified, &result[0], &size)) |
| return net::GetHostName(); |
| result.resize(size); |
| |
| return result; |
| } |
| |
| // Retrieves the state of the screen locking feature from the screen saver |
| // settings. |
| absl::optional<bool> GetScreenLockStatus() { |
| absl::optional<bool> status; |
| BOOL value = FALSE; |
| if (::SystemParametersInfo(SPI_GETSCREENSAVESECURE, 0, &value, 0)) |
| status = static_cast<bool>(value); |
| return status; |
| } |
| |
| // Checks if locking is enabled at the currently active power scheme. |
| absl::optional<bool> GetConsoleLockStatus() { |
| absl::optional<bool> status; |
| SYSTEM_POWER_STATUS sps; |
| // https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getsystempowerstatus |
| // Retrieves the power status of the system. The status indicates whether the |
| // system is running on AC or DC power. |
| if (!::GetSystemPowerStatus(&sps)) |
| return status; |
| |
| LPGUID p_active_policy = nullptr; |
| // https://docs.microsoft.com/en-us/windows/desktop/api/powersetting/nf-powersetting-powergetactivescheme |
| // Retrieves the active power scheme and returns a GUID that identifies the |
| // scheme. |
| if (::PowerGetActiveScheme(nullptr, &p_active_policy) == ERROR_SUCCESS) { |
| constexpr GUID kConsoleLock = { |
| 0x0E796BDB, |
| 0x100D, |
| 0x47D6, |
| {0xA2, 0xD5, 0xF7, 0xD2, 0xDA, 0xA5, 0x1F, 0x51}}; |
| const GUID active_policy = *p_active_policy; |
| ::LocalFree(p_active_policy); |
| |
| auto const power_read_current_value_func = |
| sps.ACLineStatus != 0U ? &PowerReadACValue : &PowerReadDCValue; |
| ULONG type; |
| DWORD value; |
| DWORD value_size = sizeof(value); |
| // https://docs.microsoft.com/en-us/windows/desktop/api/powersetting/nf-powersetting-powerreadacvalue |
| // Retrieves the AC/DC power value for the specified power setting. |
| // NO_SUBGROUP_GUID to retrieve the setting for the default power scheme. |
| // LPBYTE case is safe and is needed as the function expects generic byte |
| // array buffer regardless of the exact value read as it is a generic |
| // interface. |
| if (power_read_current_value_func( |
| nullptr, &active_policy, &NO_SUBGROUP_GUID, &kConsoleLock, &type, |
| reinterpret_cast<LPBYTE>(&value), &value_size) == ERROR_SUCCESS) { |
| status = value != 0U; |
| } |
| } |
| |
| return status; |
| } |
| |
| // Gets cumulative screen locking policy based on the screen saver and console |
| // lock status. |
| SettingValue GetScreenlockSecured() { |
| const absl::optional<bool> screen_lock_status = GetScreenLockStatus(); |
| if (screen_lock_status.value_or(false)) |
| return SettingValue::ENABLED; |
| |
| const absl::optional<bool> console_lock_status = GetConsoleLockStatus(); |
| if (console_lock_status.value_or(false)) |
| return SettingValue::ENABLED; |
| |
| if (screen_lock_status.has_value() || console_lock_status.has_value()) { |
| return SettingValue::DISABLED; |
| } |
| |
| return SettingValue::UNKNOWN; |
| } |
| |
| // Returns the volume where the Windows OS is installed. |
| absl::optional<std::wstring> GetOsVolume() { |
| absl::optional<std::wstring> volume; |
| base::FilePath windows_dir; |
| if (base::PathService::Get(base::DIR_WINDOWS, &windows_dir) && |
| windows_dir.IsAbsolute()) { |
| std::vector<std::wstring> components = windows_dir.GetComponents(); |
| DCHECK(components.size()); |
| volume = components[0]; |
| } |
| return volume; |
| } |
| |
| bool GetPropVariantAsInt64(PROPVARIANT variant, int64_t* out_value) { |
| switch (variant.vt) { |
| case VT_I2: |
| *out_value = variant.iVal; |
| return true; |
| case VT_UI2: |
| *out_value = variant.uiVal; |
| return true; |
| case VT_I4: |
| *out_value = variant.lVal; |
| return true; |
| case VT_UI4: |
| *out_value = variant.ulVal; |
| return true; |
| case VT_INT: |
| *out_value = variant.intVal; |
| return true; |
| case VT_UINT: |
| *out_value = variant.uintVal; |
| return true; |
| } |
| return false; |
| } |
| |
| // The ideal solution to check the disk encryption (BitLocker) status is to |
| // use the WMI APIs (Win32_EncryptableVolume). However, only programs running |
| // with elevated priledges can access those APIs. |
| // |
| // Our alternative solution is based on the value of the undocumented (shell) |
| // property: "System.Volume.BitLockerProtection". That property is essentially |
| // an enum containing the current BitLocker status for a given volume. This |
| // approached was suggested here: |
| // https://stackoverflow.com/questions/41308245/detect-bitlocker-programmatically-from-c-sharp-without-admin/41310139 |
| // |
| // Note that the link above doesn't give any explanation / meaning for the |
| // enum values, it simply says that 1, 3 or 5 means the disk is encrypted. |
| // |
| // I directly tested and validated this strategy on a Windows 10 machine. |
| // The values given in the BitLockerStatus enum contain the relevant values |
| // for the shell property. I also directly validated them. |
| SettingValue GetDiskEncrypted() { |
| // |volume| has to be a |wstring| because SHCreateItemFromParsingName() only |
| // accepts |PCWSTR| which is |wchar_t*|. |
| absl::optional<std::wstring> volume = GetOsVolume(); |
| if (!volume.has_value()) |
| return SettingValue::UNKNOWN; |
| |
| PROPERTYKEY key; |
| const HRESULT property_key_result = |
| PSGetPropertyKeyFromName(L"System.Volume.BitLockerProtection", &key); |
| if (FAILED(property_key_result)) |
| return SettingValue::UNKNOWN; |
| |
| Microsoft::WRL::ComPtr<IShellItem2> item; |
| const HRESULT create_item_result = SHCreateItemFromParsingName( |
| volume.value().c_str(), nullptr, IID_IShellItem2, &item); |
| if (FAILED(create_item_result) || !item) |
| return SettingValue::UNKNOWN; |
| |
| PROPVARIANT prop_status; |
| const HRESULT property_result = item->GetProperty(key, &prop_status); |
| int64_t status; |
| if (FAILED(property_result) || !GetPropVariantAsInt64(prop_status, &status)) |
| return SettingValue::UNKNOWN; |
| |
| // Note that we are not considering BitLockerStatus::Suspended as ENABLED. |
| if (status == static_cast<int64_t>(BitLockerStatus::kOn) || |
| status == static_cast<int64_t>(BitLockerStatus::kEncryptionInProgress) || |
| status == static_cast<int64_t>(BitLockerStatus::kLocked)) { |
| return SettingValue::ENABLED; |
| } |
| |
| return SettingValue::DISABLED; |
| } |
| |
| std::vector<std::string> GetMacAddresses() { |
| std::vector<std::string> mac_addresses; |
| ULONG adapter_info_size = 0; |
| // Get the right buffer size in case of overflow |
| if (::GetAdaptersInfo(nullptr, &adapter_info_size) != ERROR_BUFFER_OVERFLOW || |
| adapter_info_size == 0) { |
| return mac_addresses; |
| } |
| |
| std::vector<byte> adapters(adapter_info_size); |
| if (::GetAdaptersInfo(reinterpret_cast<PIP_ADAPTER_INFO>(adapters.data()), |
| &adapter_info_size) != ERROR_SUCCESS) { |
| return mac_addresses; |
| } |
| |
| // The returned value is not an array of IP_ADAPTER_INFO elements but a linked |
| // list of such |
| PIP_ADAPTER_INFO adapter = |
| reinterpret_cast<PIP_ADAPTER_INFO>(adapters.data()); |
| while (adapter) { |
| if (adapter->AddressLength == 6) { |
| mac_addresses.push_back( |
| base::StringPrintf("%02X-%02X-%02X-%02X-%02X-%02X", |
| static_cast<unsigned int>(adapter->Address[0]), |
| static_cast<unsigned int>(adapter->Address[1]), |
| static_cast<unsigned int>(adapter->Address[2]), |
| static_cast<unsigned int>(adapter->Address[3]), |
| static_cast<unsigned int>(adapter->Address[4]), |
| static_cast<unsigned int>(adapter->Address[5]))); |
| } |
| adapter = adapter->Next; |
| } |
| return mac_addresses; |
| } |
| |
| absl::optional<std::string> GetWindowsMachineDomain() { |
| if (!base::win::IsEnrolledToDomain()) |
| return absl::nullopt; |
| std::string domain; |
| ::DSROLE_PRIMARY_DOMAIN_INFO_BASIC* info = nullptr; |
| if (::DsRoleGetPrimaryDomainInformation(nullptr, |
| ::DsRolePrimaryDomainInfoBasic, |
| (PBYTE*)&info) == ERROR_SUCCESS) { |
| if (info->DomainNameFlat) |
| domain = base::WideToUTF8(info->DomainNameFlat); |
| ::DsRoleFreeMemory(info); |
| } |
| return domain.empty() ? absl::nullopt : absl::make_optional(domain); |
| } |
| |
| absl::optional<std::string> GetWindowsUserDomain() { |
| WCHAR username[CREDUI_MAX_USERNAME_LENGTH + 1] = {}; |
| DWORD username_length = sizeof(username); |
| if (!::GetUserNameExW(::NameSamCompatible, username, &username_length) || |
| username_length <= 0) { |
| return absl::nullopt; |
| } |
| // The string corresponds to DOMAIN\USERNAME. If there isn't a domain, the |
| // domain name is replaced by the name of the machine, so the function |
| // returns nothing in that case. |
| std::string username_str = base::WideToUTF8(username); |
| std::string domain = username_str.substr(0, username_str.find("\\")); |
| |
| return domain == base::ToUpperASCII(GetComputerNameW()) |
| ? absl::nullopt |
| : absl::make_optional(domain); |
| } |
| |
| std::string GetSecurityPatchLevel() { |
| base::win::OSInfo* gi = base::win::OSInfo::GetInstance(); |
| |
| return base::NumberToString(gi->version_number().patch); |
| } |
| |
| SettingValue GetSecureBootEnabled() { |
| base::win::RegKey key; |
| auto result = key.Open(HKEY_LOCAL_MACHINE, kSecureBootRegPath, |
| KEY_QUERY_VALUE | KEY_WOW64_64KEY); |
| |
| if (result != ERROR_SUCCESS || !key.Valid()) { |
| return SettingValue::UNKNOWN; |
| } |
| |
| DWORD secure_boot_dw; |
| result = key.ReadValueDW(kSecureBootRegKey, &secure_boot_dw); |
| |
| if (result != ERROR_SUCCESS) { |
| return SettingValue::UNKNOWN; |
| } |
| |
| return secure_boot_dw == 1 ? SettingValue::ENABLED : SettingValue::DISABLED; |
| } |
| |
| } // namespace |
| |
| DeviceInfoFetcherWin::DeviceInfoFetcherWin() = default; |
| |
| DeviceInfoFetcherWin::~DeviceInfoFetcherWin() = default; |
| |
| DeviceInfo DeviceInfoFetcherWin::Fetch() { |
| DeviceInfo device_info; |
| device_info.os_name = "windows"; |
| device_info.os_version = base::SysInfo::OperatingSystemVersion(); |
| device_info.security_patch_level = GetSecurityPatchLevel(); |
| device_info.device_host_name = GetComputerName(); |
| device_info.device_model = base::SysInfo::HardwareModelName(); |
| device_info.serial_number = GetSerialNumber(); |
| device_info.screen_lock_secured = GetScreenlockSecured(); |
| device_info.disk_encrypted = GetDiskEncrypted(); |
| device_info.mac_addresses = GetMacAddresses(); |
| device_info.windows_machine_domain = GetWindowsMachineDomain(); |
| device_info.windows_user_domain = GetWindowsUserDomain(); |
| device_info.secure_boot_enabled = GetSecureBootEnabled(); |
| |
| return device_info; |
| } |
| |
| } // namespace enterprise_signals |