// 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.
#include "chrome/credential_provider/gaiacp/mdm_utils.h"
#include <windows.h>
#include <lm.h> // Needed for PNTSTATUS
#include <winternl.h>
#define _NTDEF_ // Prevent redefition errors, must come after <winternl.h>
#include <MDMRegistration.h> // For RegisterDeviceWithManagement()
#include <ntsecapi.h> // For LsaQueryInformationPolicy()
#include <atlconv.h>
#include "base/base64.h"
#include "base/files/file_path.h"
#include "base/json/json_writer.h"
#include "base/scoped_native_library.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/win_util.h"
#include "base/win/wmi.h"
#include "build/branding_buildflags.h"
#include "chrome/credential_provider/common/gcp_strings.h"
#include "chrome/credential_provider/gaiacp/device_policies_manager.h"
#include "chrome/credential_provider/gaiacp/gcp_utils.h"
#include "chrome/credential_provider/gaiacp/gcpw_strings.h"
#include "chrome/credential_provider/gaiacp/logging.h"
#include "chrome/credential_provider/gaiacp/reg_utils.h"
#include "chrome/credential_provider/gaiacp/user_policies_manager.h"
namespace credential_provider {
constexpr wchar_t kRegMdmEnforceOnlineLogin[] = L"enforce_online_login";
constexpr wchar_t kUserPasswordLsaStoreKeyPrefix[] =
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
L"Chrome-GCPW-";
#else
L"Chromium-GCPW-";
#endif
// Overridden in tests to force the MDM enrollment to either succeed or fail.
enum class EnrollmentStatus {
kForceSuccess,
kForceFailure,
kDontForce,
};
EnrollmentStatus g_enrollment_status = EnrollmentStatus::kDontForce;
// Overridden in tests to force the MDM enrollment check to either return true
// or false.
enum class EnrolledStatus {
kForceTrue,
kForceFalse,
kDontForce,
};
EnrolledStatus g_enrolled_status = EnrolledStatus::kDontForce;
#if !BUILDFLAG(GOOGLE_CHROME_BRANDING)
enum class EscrowServiceStatus {
kDisabled,
kEnabled,
};
EscrowServiceStatus g_escrow_service_enabled = EscrowServiceStatus::kDisabled;
#endif
enum class DeviceDetailsUploadNeeded {
kForceTrue,
kForceFalse,
kDontForce,
};
DeviceDetailsUploadNeeded g_device_details_upload_needed =
DeviceDetailsUploadNeeded::kDontForce;
namespace {
constexpr wchar_t kDefaultEscrowServiceServerUrl[] =
L"https://devicepasswordescrowforwindows-pa.googleapis.com";
template <typename T>
T GetMdmFunctionPointer(const base::ScopedNativeLibrary& library,
const char* function_name) {
if (!library.is_valid())
return nullptr;
return reinterpret_cast<T>(library.GetFunctionPointer(function_name));
}
#define GET_MDM_FUNCTION_POINTER(library, name) \
GetMdmFunctionPointer<decltype(&::name)>(library, #name)
bool IsEnrolledWithGoogleMdm(const std::wstring& mdm_url) {
switch (g_enrolled_status) {
case EnrolledStatus::kForceTrue:
return true;
case EnrolledStatus::kForceFalse:
return false;
case EnrolledStatus::kDontForce:
break;
}
base::ScopedNativeLibrary library(
base::FilePath(FILE_PATH_LITERAL("MDMRegistration.dll")));
auto get_device_registration_info_function =
GET_MDM_FUNCTION_POINTER(library, GetDeviceRegistrationInfo);
if (!get_device_registration_info_function) {
// On Windows < 1803 the function GetDeviceRegistrationInfo does not exist
// in MDMRegistration.dll so we have to fallback to the less accurate
// IsDeviceRegisteredWithManagement. This can return false positives if the
// machine is registered to MDM but to a different server.
LOGFN(ERROR) << "GET_MDM_FUNCTION_POINTER(GetDeviceRegistrationInfo)";
auto is_device_registered_with_management_function =
GET_MDM_FUNCTION_POINTER(library, IsDeviceRegisteredWithManagement);
if (!is_device_registered_with_management_function) {
LOGFN(ERROR)
<< "GET_MDM_FUNCTION_POINTER(IsDeviceRegisteredWithManagement)";
return false;
} else {
BOOL is_managed = FALSE;
HRESULT hr = is_device_registered_with_management_function(&is_managed, 0,
nullptr);
return SUCCEEDED(hr) && is_managed;
}
}
MANAGEMENT_REGISTRATION_INFO* info;
HRESULT hr = get_device_registration_info_function(
DeviceRegistrationBasicInfo, reinterpret_cast<void**>(&info));
bool is_enrolled = SUCCEEDED(hr) && info->fDeviceRegisteredWithManagement &&
GURL(base::AsStringPiece16(mdm_url)) ==
GURL(base::AsStringPiece16(info->pszMDMServiceUri));
if (SUCCEEDED(hr))
::HeapFree(::GetProcessHeap(), 0, info);
return is_enrolled;
}
HRESULT ExtractRegistrationData(const base::Value::Dict& registration_data,
std::wstring* out_email,
std::wstring* out_id_token,
std::wstring* out_access_token,
std::wstring* out_sid,
std::wstring* out_username,
std::wstring* out_domain,
std::wstring* out_is_ad_user_joined) {
DCHECK(out_email);
DCHECK(out_id_token);
DCHECK(out_access_token);
DCHECK(out_sid);
DCHECK(out_username);
DCHECK(out_domain);
DCHECK(out_is_ad_user_joined);
*out_email = GetDictString(registration_data, kKeyEmail);
*out_id_token = GetDictString(registration_data, kKeyMdmIdToken);
*out_access_token = GetDictString(registration_data, kKeyAccessToken);
*out_sid = GetDictString(registration_data, kKeySID);
*out_username = GetDictString(registration_data, kKeyUsername);
*out_domain = GetDictString(registration_data, kKeyDomain);
*out_is_ad_user_joined = GetDictString(registration_data, kKeyIsAdJoinedUser);
if (out_email->empty()) {
LOGFN(ERROR) << "Email is empty";
return E_INVALIDARG;
}
if (out_id_token->empty()) {
LOGFN(ERROR) << "MDM id token is empty";
return E_INVALIDARG;
}
if (out_access_token->empty()) {
LOGFN(ERROR) << "Access token is empty";
return E_INVALIDARG;
}
if (out_sid->empty()) {
LOGFN(ERROR) << "SID is empty";
return E_INVALIDARG;
}
if (out_username->empty()) {
LOGFN(ERROR) << "username is empty";
return E_INVALIDARG;
}
if (out_domain->empty()) {
LOGFN(ERROR) << "domain is empty";
return E_INVALIDARG;
}
if (out_is_ad_user_joined->empty()) {
LOGFN(ERROR) << "is_ad_user_joined is empty";
return E_INVALIDARG;
}
return S_OK;
}
HRESULT RegisterWithGoogleDeviceManagement(
const std::wstring& mdm_url,
const base::Value::Dict& properties) {
// Make sure all the needed data is present in the dictionary.
std::wstring email;
std::wstring id_token;
std::wstring access_token;
std::wstring sid;
std::wstring username;
std::wstring domain;
std::wstring is_ad_joined_user;
HRESULT hr =
ExtractRegistrationData(properties, &email, &id_token, &access_token,
&sid, &username, &domain, &is_ad_joined_user);
if (FAILED(hr)) {
LOGFN(ERROR) << "ExtractRegistrationData hr=" << putHR(hr);
return E_INVALIDARG;
}
LOGFN(INFO) << "MDM_URL=" << mdm_url
<< " token=" << std::wstring(id_token.c_str(), 10);
// Add the serial number to the registration data dictionary.
std::wstring serial_number = GetSerialNumber();
if (serial_number.empty()) {
LOGFN(ERROR) << "Failed to get serial number.";
return E_FAIL;
}
// Add machine_guid to the registration data dictionary.
std::wstring machine_guid;
hr = GetMachineGuid(&machine_guid);
if (FAILED(hr) || machine_guid.empty()) {
LOGFN(ERROR) << "Failed to get machine guid.";
return FAILED(hr) ? hr : E_FAIL;
}
// Need localized local user group name for Administrators group
// for supporting account elevation scenarios.
std::wstring local_administrators_group_name;
hr = LookupLocalizedNameForWellKnownSid(WinBuiltinAdministratorsSid,
&local_administrators_group_name);
if (FAILED(hr)) {
LOGFN(WARNING) << "Failed to fetch name for administrators group";
}
std::wstring builtin_administrator_name;
hr = GetLocalizedNameBuiltinAdministratorAccount(&builtin_administrator_name);
if (FAILED(hr)) {
LOGFN(WARNING) << "Failed to fetch name for builtin administrator account";
}
// Build the json data needed by the server.
auto registration_data =
base::Value::Dict()
.Set("id_token", base::WideToUTF8(id_token))
.Set("access_token", base::WideToUTF8(access_token))
.Set("sid", base::WideToUTF8(sid))
.Set("username", base::WideToUTF8(username))
.Set("domain", base::WideToUTF8(domain))
.Set("serial_number", base::WideToUTF8(serial_number))
.Set("machine_guid", base::WideToUTF8(machine_guid))
.Set("admin_local_user_group_name",
base::WideToUTF8(local_administrators_group_name))
.Set("builtin_administrator_name",
base::WideToUTF8(builtin_administrator_name))
.Set(kKeyIsAdJoinedUser, base::WideToUTF8(is_ad_joined_user));
// Send device resource ID if available as part of the enrollment payload.
// Enrollment backend should not assume that this will always be available.
std::wstring user_device_resource_id = GetUserDeviceResourceId(sid);
if (!user_device_resource_id.empty()) {
registration_data.Set("resource_id",
base::WideToUTF8(user_device_resource_id));
}
std::string registration_data_str;
if (!base::JSONWriter::Write(registration_data, ®istration_data_str)) {
LOGFN(ERROR) << "JSONWriter::Write(registration_data)";
return E_FAIL;
}
switch (g_enrollment_status) {
case EnrollmentStatus::kForceSuccess:
return S_OK;
case EnrollmentStatus::kForceFailure:
return E_FAIL;
case EnrollmentStatus::kDontForce:
break;
}
base::ScopedNativeLibrary library(
base::FilePath(FILE_PATH_LITERAL("MDMRegistration.dll")));
auto register_device_with_management_function =
GET_MDM_FUNCTION_POINTER(library, RegisterDeviceWithManagement);
if (!register_device_with_management_function) {
LOGFN(ERROR) << "GET_MDM_FUNCTION_POINTER(RegisterDeviceWithManagement)";
return false;
}
std::string data_encoded = base::Base64Encode(registration_data_str);
// This register call is blocking. It won't return until the machine is
// properly registered with the MDM server.
return register_device_with_management_function(
email.c_str(), mdm_url.c_str(), base::UTF8ToWide(data_encoded).c_str());
}
bool IsUserAllowedToEnrollWithMdm(const std::wstring& sid) {
UserPolicies policies;
UserPoliciesManager::Get()->GetUserPolicies(sid, &policies);
return policies.enable_dm_enrollment;
}
} // namespace
bool NeedsToEnrollWithMdm(const std::wstring& sid) {
if (UserPoliciesManager::Get()->CloudPoliciesEnabled()) {
if (!IsUserAllowedToEnrollWithMdm(sid))
return false;
}
std::wstring mdm_url = GetMdmUrl();
return !mdm_url.empty() && !IsEnrolledWithGoogleMdm(mdm_url);
}
bool UploadDeviceDetailsNeeded(const std::wstring& sid) {
switch (g_device_details_upload_needed) {
case DeviceDetailsUploadNeeded::kForceTrue:
return true;
case DeviceDetailsUploadNeeded::kForceFalse:
return false;
case DeviceDetailsUploadNeeded::kDontForce:
break;
}
DWORD status = 0;
GetUserProperty(sid, kRegDeviceDetailsUploadStatus, &status);
// GCPW token is required for ESA to communicate with the GEM backends. So
// enforce upload if this token is missing.
std::wstring gcpw_token;
HRESULT hr = GetGCPWDmToken(sid, &gcpw_token);
bool gcpw_token_upload_required = false;
if (UserPoliciesManager::Get()->CloudPoliciesEnabled() && FAILED(hr)) {
gcpw_token_upload_required = true;
}
if (status != 1 || gcpw_token_upload_required) {
DWORD device_upload_failures = 1;
GetUserProperty(sid, kRegDeviceDetailsUploadFailures,
&device_upload_failures);
if (device_upload_failures >
DWORD(kMaxNumConsecutiveUploadDeviceFailures)) {
LOGFN(WARNING) << "Reauth not enforced due to upload device details "
"failures exceeding threshhold.";
return false;
}
return true;
}
return false;
}
bool MdmEnrollmentEnabled() {
if (DevicePoliciesManager::Get()->CloudPoliciesEnabled()) {
DevicePolicies policies;
DevicePoliciesManager::Get()->GetDevicePolicies(&policies);
return policies.enable_dm_enrollment;
}
std::wstring mdm_url = GetMdmUrl();
return !mdm_url.empty();
}
std::wstring GetMdmUrl() {
std::wstring enrollment_url = L"";
if (UserPoliciesManager::Get()->CloudPoliciesEnabled()) {
enrollment_url = GetGlobalFlagOrDefault(kRegMdmUrl, kDefaultMdmUrl);
} else {
DWORD enable_dm_enrollment;
HRESULT hr = GetGlobalFlag(kRegEnableDmEnrollment, &enable_dm_enrollment);
if (SUCCEEDED(hr)) {
if (enable_dm_enrollment)
enrollment_url = kDefaultMdmUrl;
} else {
// Fallback to using the older flag to control mdm url.
enrollment_url = GetGlobalFlagOrDefault(kRegMdmUrl, kDefaultMdmUrl);
}
}
std::wstring dev = GetGlobalFlagOrDefault(kRegDeveloperMode, L"");
if (!dev.empty())
enrollment_url = GetDevelopmentUrl(enrollment_url, dev);
return enrollment_url;
}
GURL EscrowServiceUrl() {
DWORD disable_password_sync =
GetGlobalFlagOrDefault(kRegDisablePasswordSync, 0);
if (disable_password_sync)
return GURL();
std::wstring dev = GetGlobalFlagOrDefault(kRegDeveloperMode, L"");
if (!dev.empty())
return GURL(base::AsStringPiece16(
GetDevelopmentUrl(kDefaultEscrowServiceServerUrl, dev)));
// By default, the password recovery feature should be enabled.
return GURL(base::WideToUTF8(kDefaultEscrowServiceServerUrl));
}
bool PasswordRecoveryEnabled() {
return !EscrowServiceUrl().is_empty();
}
bool IsGemEnabled() {
// The gem features are enabled by default.
return GetGlobalFlagOrDefault(kKeyEnableGemFeatures, 1);
}
bool IsOnlineLoginEnforced(const std::wstring& sid) {
DWORD global_flag = GetGlobalFlagOrDefault(kRegMdmEnforceOnlineLogin, 0);
// Return true if global flag is set. If it is not set check for
// the user flag.
if (global_flag)
return true;
DWORD is_online_login_enforced_for_user = 0;
HRESULT hr = GetUserProperty(sid, kRegMdmEnforceOnlineLogin,
&is_online_login_enforced_for_user);
if (FAILED(hr)) {
LOGFN(VERBOSE) << "GetUserProperty for " << kRegMdmEnforceOnlineLogin
<< " failed. hr=" << putHR(hr);
// Fallback to the less obstructive option to not enforce login via google
// when fetching the registry entry fails.
return false;
}
return is_online_login_enforced_for_user;
}
HRESULT EnrollToGoogleMdmIfNeeded(const base::Value::Dict& properties) {
LOGFN(VERBOSE);
if (UserPoliciesManager::Get()->CloudPoliciesEnabled()) {
std::wstring sid = GetDictString(properties, kKeySID);
if (!IsUserAllowedToEnrollWithMdm(sid))
return S_OK;
}
// Only enroll with MDM if configured.
std::wstring mdm_url = GetMdmUrl();
if (mdm_url.empty())
return S_OK;
// TODO(crbug.com/41443432): Check if machine is already enrolled because
// attempting to enroll when already enrolled causes a crash.
if (IsEnrolledWithGoogleMdm(mdm_url)) {
LOGFN(VERBOSE) << "Already enrolled to Google MDM";
return S_OK;
}
HRESULT hr = RegisterWithGoogleDeviceManagement(mdm_url, properties);
if (FAILED(hr))
LOGFN(ERROR) << "RegisterWithGoogleDeviceManagement hr=" << putHR(hr);
return hr;
}
bool IsEnrolledWithGoogleMdm() {
std::wstring mdm_url = GetMdmUrl();
return !mdm_url.empty() && IsEnrolledWithGoogleMdm(mdm_url);
}
std::wstring GetUserPasswordLsaStoreKey(const std::wstring& sid) {
DCHECK(sid.size());
return kUserPasswordLsaStoreKeyPrefix + sid;
}
// GoogleMdmEnrollmentStatusForTesting ////////////////////////////////////////
GoogleMdmEnrollmentStatusForTesting::GoogleMdmEnrollmentStatusForTesting(
bool success) {
g_enrollment_status = success ? EnrollmentStatus::kForceSuccess
: EnrollmentStatus::kForceFailure;
}
GoogleMdmEnrollmentStatusForTesting::~GoogleMdmEnrollmentStatusForTesting() {
g_enrollment_status = EnrollmentStatus::kDontForce;
}
// GoogleMdmEnrolledStatusForTesting //////////////////////////////////////////
GoogleMdmEnrolledStatusForTesting::GoogleMdmEnrolledStatusForTesting(
bool success) {
g_enrolled_status =
success ? EnrolledStatus::kForceTrue : EnrolledStatus::kForceFalse;
}
GoogleMdmEnrolledStatusForTesting::~GoogleMdmEnrolledStatusForTesting() {
g_enrolled_status = EnrolledStatus::kDontForce;
}
// GoogleMdmEnrolledStatusForTesting //////////////////////////////////////////
// GoogleUploadDeviceDetailsNeededForTesting //////////////////////////////////
GoogleUploadDeviceDetailsNeededForTesting::
GoogleUploadDeviceDetailsNeededForTesting(bool success) {
g_device_details_upload_needed = success
? DeviceDetailsUploadNeeded::kForceTrue
: DeviceDetailsUploadNeeded::kForceFalse;
}
GoogleUploadDeviceDetailsNeededForTesting::
~GoogleUploadDeviceDetailsNeededForTesting() {
g_device_details_upload_needed = DeviceDetailsUploadNeeded::kDontForce;
}
// GoogleUploadDeviceDetailsNeededForTesting //////////////////////////////////
} // namespace credential_provider