GroupPolicy.cpp (19889B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "TestCommon.h" 5 #include "TestSettings.h" 6 #include "winget/GroupPolicy.h" 7 #include <AppInstallerStrings.h> 8 #include <CertificateResources.h> 9 10 using namespace TestCommon; 11 using namespace AppInstaller::Settings; 12 using namespace std::string_view_literals; 13 14 namespace 15 { 16 std::wstring GetSourceJson(std::wstring_view name, std::wstring_view arg, std::wstring_view type, std::wstring_view data, std::wstring_view identifier, std::wstring_view trustLevel, std::wstring_view isExplicit, std::wstring_view pinningConfig = {}) 17 { 18 std::wstringstream json; 19 json << L"{ \"Name\":\"" << name << L"\", \"Arg\":\"" << arg << L"\", \"Type\":\"" << type << L"\", \"Data\":\"" << data << L"\", \"Identifier\":\"" << identifier << L"\", \"TrustLevel\":" << trustLevel << L", \"Explicit\":" << isExplicit; 20 if (!pinningConfig.empty()) 21 { 22 json << L", \"CertificatePinning\":" << pinningConfig; 23 } 24 json << " }"; 25 return json.str(); 26 } 27 } 28 29 TEST_CASE("GroupPolicy_NoPolicies", "[groupPolicy]") 30 { 31 auto policiesKey = RegCreateVolatileTestRoot(); 32 GroupPolicy groupPolicy{ policiesKey.get() }; 33 34 // Policies setting a value should be empty 35 REQUIRE(!groupPolicy.GetValue<ValuePolicy::SourceAutoUpdateIntervalInMinutes>().has_value()); 36 REQUIRE(!groupPolicy.GetValue<ValuePolicy::AdditionalSources>().has_value()); 37 REQUIRE(!groupPolicy.GetValue<ValuePolicy::AllowedSources>().has_value()); 38 39 // Everything should be not configured 40 for (const auto& policy : TogglePolicy::GetAllPolicies()) 41 { 42 REQUIRE(groupPolicy.GetState(policy.GetPolicy()) == PolicyState::NotConfigured); 43 } 44 } 45 46 TEST_CASE("GroupPolicy_UpdateInterval", "[groupPolicy]") 47 { 48 auto policiesKey = RegCreateVolatileTestRoot(); 49 50 SECTION("Good value") 51 { 52 SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, 5); 53 GroupPolicy groupPolicy{ policiesKey.get() }; 54 55 auto policy = groupPolicy.GetValue<ValuePolicy::SourceAutoUpdateIntervalInMinutes>(); 56 REQUIRE(policy.has_value()); 57 REQUIRE(*policy == 5); 58 } 59 60 SECTION("Wrong type") 61 { 62 SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, L"Wrong"); 63 GroupPolicy groupPolicy{ policiesKey.get() }; 64 65 auto policy = groupPolicy.GetValue<ValuePolicy::SourceAutoUpdateIntervalInMinutes>(); 66 REQUIRE(!policy.has_value()); 67 } 68 } 69 70 71 TEST_CASE("GroupPolicy_UpdateInterval_OldName", "[groupPolicy]") 72 { 73 auto policiesKey = RegCreateVolatileTestRoot(); 74 75 SECTION("New name shadows old") 76 { 77 SECTION("When old is valid") 78 { 79 SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, 3); 80 } 81 SECTION("When old is invalid") 82 { 83 SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, L"Invalid type"); 84 } 85 86 SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, 1); 87 GroupPolicy groupPolicy{ policiesKey.get() }; 88 89 auto policy = groupPolicy.GetValue<ValuePolicy::SourceAutoUpdateIntervalInMinutes>(); 90 REQUIRE(policy.has_value()); 91 REQUIRE(*policy == 1); 92 } 93 94 SECTION("Fallback to old name") 95 { 96 SECTION("When new name has invalid data") 97 { 98 SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, L"Wrong type"); 99 SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, 20); 100 GroupPolicy groupPolicy{ policiesKey.get() }; 101 102 // We should not fall back on this case 103 auto policy = groupPolicy.GetValue<ValuePolicy::SourceAutoUpdateIntervalInMinutes>(); 104 REQUIRE(!policy.has_value()); 105 } 106 SECTION("When new name is missing") 107 { 108 // Don't add the registry value with the new name 109 SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, 20); 110 GroupPolicy groupPolicy{ policiesKey.get() }; 111 112 auto policy = groupPolicy.GetValue<ValuePolicy::SourceAutoUpdateIntervalInMinutes>(); 113 REQUIRE(policy.has_value()); 114 REQUIRE(*policy == 20); 115 } 116 } 117 } 118 119 TEST_CASE("GroupPolicy_Sources", "[groupPolicy]") 120 { 121 auto policiesKey = RegCreateVolatileTestRoot(); 122 123 // Note that the following tests mix using Additional/Allowed sources policy. 124 SECTION("Single source") 125 { 126 // We can read single source correctly 127 auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName); 128 SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"source-name", L"source-arg", L"source-type", L"source-data", L"source-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"true"), REG_SZ); 129 GroupPolicy groupPolicy{ policiesKey.get() }; 130 131 auto policy = groupPolicy.GetValue<ValuePolicy::AdditionalSources>(); 132 REQUIRE(policy.has_value()); 133 REQUIRE(policy->size() == 1); 134 REQUIRE(policy.value()[0].Name == "source-name"); 135 REQUIRE(policy.value()[0].Arg == "source-arg"); 136 REQUIRE(policy.value()[0].Type == "source-type"); 137 REQUIRE(policy.value()[0].Data == "source-data"); 138 REQUIRE(policy.value()[0].Identifier == "source-identifier"); 139 REQUIRE(policy.value()[0].TrustLevel[0] == "Trusted"); 140 REQUIRE(policy.value()[0].TrustLevel[1] == "StoreOrigin"); 141 REQUIRE(policy.value()[0].Explicit == true); 142 } 143 SECTION("Missing field") 144 { 145 // A single missing field causes the source to not be read. 146 // "Type" is missing here. 147 std::wstring sourceJson = L"{ \"Name\":\"source_name\", \"Arg\":\"source_arg\", \"Data\":\"source_data\", \"Identifier\":\"source_identifier\" }"; 148 auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName); 149 SetRegistryValue(additionalSourcesKey.get(), L"0", sourceJson, REG_SZ); 150 GroupPolicy groupPolicy{ policiesKey.get() }; 151 152 auto policy = groupPolicy.GetValue<ValuePolicy::AllowedSources>(); 153 REQUIRE(policy.has_value()); 154 REQUIRE(policy->empty()); 155 } 156 SECTION("Invalid field") 157 { 158 // A single invalid field causes the source to not be read. 159 // "Data" is invalid as it is an object, not a string. 160 std::wstring sourceJson = L"{ \"Name\":\"source_name\", \"Arg\":\"source_arg\", \"Data\":{}, \"Type\":\"source_type\", \"Identifier\":\"source_identifier\" }"; 161 auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName); 162 SetRegistryValue(additionalSourcesKey.get(), L"0", sourceJson, REG_SZ); 163 GroupPolicy groupPolicy{ policiesKey.get() }; 164 165 auto policy = groupPolicy.GetValue<ValuePolicy::AdditionalSources>(); 166 REQUIRE(policy.has_value()); 167 REQUIRE(policy->empty()); 168 } 169 SECTION("Invalid source JSON") 170 { 171 // An invalid source JSON causes the source to not be read. 172 auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName); 173 SetRegistryValue(additionalSourcesKey.get(), L"0", L"not a JSON", REG_SZ); 174 GroupPolicy groupPolicy{ policiesKey.get() }; 175 176 auto policy = groupPolicy.GetValue<ValuePolicy::AllowedSources>(); 177 REQUIRE(policy.has_value()); 178 REQUIRE(policy->empty()); 179 } 180 SECTION("Missing key") 181 { 182 // If the key does not exist we should not get anything. 183 GroupPolicy groupPolicy{ policiesKey.get() }; 184 185 auto policy = groupPolicy.GetValue<ValuePolicy::AdditionalSources>(); 186 REQUIRE_FALSE(policy.has_value()); 187 } 188 SECTION("Empty key") 189 { 190 // If the key is empty we should get an empty list. 191 // Note that the policy editor doesn't actually create empty keys. 192 auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName); 193 GroupPolicy groupPolicy{ policiesKey.get() }; 194 195 auto policy = groupPolicy.GetValue<ValuePolicy::AllowedSources>(); 196 REQUIRE(policy.has_value()); 197 REQUIRE(policy->empty()); 198 } 199 SECTION("Valid list") 200 { 201 // We should be able to read multiple values. 202 // No specific order is required, but it will likely be the same. 203 auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName); 204 SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"s0-name", L"s0-arg", L"s0-type", L"s0-data", L"s0-identifier", L"[\"None\"]", L"true"), REG_SZ); 205 SetRegistryValue(additionalSourcesKey.get(), L"1", GetSourceJson(L"s1-name", L"s1-arg", L"s1-type", L"s1-data", L"s1-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"false"), REG_SZ); 206 SetRegistryValue(additionalSourcesKey.get(), L"2", GetSourceJson(L"s2-name", L"s2-arg", L"s2-type", L"s2-data", L"s2-identifier", L"[\"StoreOrigin\", \"Trusted\"]", L"true"), REG_SZ); 207 GroupPolicy groupPolicy{ policiesKey.get() }; 208 209 auto policy = groupPolicy.GetValue<ValuePolicy::AdditionalSources>(); 210 REQUIRE(policy.has_value()); 211 REQUIRE(policy->size() == 3); 212 213 REQUIRE(policy.value()[0].Name == "s0-name"); 214 REQUIRE(policy.value()[0].Arg == "s0-arg"); 215 REQUIRE(policy.value()[0].Type == "s0-type"); 216 REQUIRE(policy.value()[0].Data == "s0-data"); 217 REQUIRE(policy.value()[0].Identifier == "s0-identifier"); 218 REQUIRE(policy.value()[0].TrustLevel[0] == "None"); 219 REQUIRE(policy.value()[0].Explicit == true); 220 221 REQUIRE(policy.value()[1].Name == "s1-name"); 222 REQUIRE(policy.value()[1].Arg == "s1-arg"); 223 REQUIRE(policy.value()[1].Type == "s1-type"); 224 REQUIRE(policy.value()[1].Data == "s1-data"); 225 REQUIRE(policy.value()[1].Identifier == "s1-identifier"); 226 REQUIRE(policy.value()[1].TrustLevel[0] == "Trusted"); 227 REQUIRE(policy.value()[1].TrustLevel[1] == "StoreOrigin"); 228 REQUIRE(policy.value()[1].Explicit == false); 229 230 REQUIRE(policy.value()[2].Name == "s2-name"); 231 REQUIRE(policy.value()[2].Arg == "s2-arg"); 232 REQUIRE(policy.value()[2].Type == "s2-type"); 233 REQUIRE(policy.value()[2].Data == "s2-data"); 234 REQUIRE(policy.value()[2].Identifier == "s2-identifier"); 235 REQUIRE(policy.value()[2].TrustLevel[0] == "StoreOrigin"); 236 REQUIRE(policy.value()[2].TrustLevel[1] == "Trusted"); 237 REQUIRE(policy.value()[2].Explicit == true); 238 } 239 SECTION("Invalid source in list") 240 { 241 // If a single source is invalid we should still get all others 242 auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName); 243 SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"s0-name", L"s0-arg", L"s0-type", L"s0-data", L"s0-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"false"), REG_SZ); 244 SetRegistryValue(additionalSourcesKey.get(), L"1", L"not a source", REG_SZ); 245 SetRegistryValue(additionalSourcesKey.get(), L"2", GetSourceJson(L"s2-name", L"s2-arg", L"s2-type", L"s2-data", L"s2-identifier", L"[\"StoreOrigin\", \"Trusted\"]", L"true"), REG_SZ); 246 GroupPolicy groupPolicy{ policiesKey.get() }; 247 248 auto policy = groupPolicy.GetValue<ValuePolicy::AdditionalSources>(); 249 REQUIRE(policy.has_value()); 250 REQUIRE(policy->size() == 2); 251 252 REQUIRE(policy.value()[0].Name == "s0-name"); 253 REQUIRE(policy.value()[0].Arg == "s0-arg"); 254 REQUIRE(policy.value()[0].Type == "s0-type"); 255 REQUIRE(policy.value()[0].Data == "s0-data"); 256 REQUIRE(policy.value()[0].Identifier == "s0-identifier"); 257 REQUIRE(policy.value()[0].TrustLevel[0] == "Trusted"); 258 REQUIRE(policy.value()[0].TrustLevel[1] == "StoreOrigin"); 259 REQUIRE(policy.value()[0].Explicit == false); 260 261 REQUIRE(policy.value()[1].Name == "s2-name"); 262 REQUIRE(policy.value()[1].Arg == "s2-arg"); 263 REQUIRE(policy.value()[1].Type == "s2-type"); 264 REQUIRE(policy.value()[1].Data == "s2-data"); 265 REQUIRE(policy.value()[1].Identifier == "s2-identifier"); 266 REQUIRE(policy.value()[1].TrustLevel[0] == "StoreOrigin"); 267 REQUIRE(policy.value()[1].TrustLevel[1] == "Trusted"); 268 REQUIRE(policy.value()[1].Explicit == true); 269 } 270 SECTION("Exported JSON") 271 { 272 // Policy should be able to use an exported JSON strings 273 SourceFromPolicy source; 274 source.Name = "json-name"; 275 source.Type = "json-type"; 276 source.Arg = "json-arg"; 277 source.Data = "json-data"; 278 source.Identifier = "json-id"; 279 source.TrustLevel = {"Trusted", "StoreOrigin"}; 280 source.Explicit = false; 281 282 auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName); 283 SetRegistryValue(additionalSourcesKey.get(), L"0", AppInstaller::Utility::ConvertToUTF16(source.ToJsonString())); 284 GroupPolicy groupPolicy{ policiesKey.get() }; 285 286 auto policy = groupPolicy.GetValue<ValuePolicy::AllowedSources>(); 287 REQUIRE(policy.has_value()); 288 REQUIRE(policy->size() == 1); 289 REQUIRE(policy.value()[0].Name == source.Name); 290 REQUIRE(policy.value()[0].Arg == source.Arg); 291 REQUIRE(policy.value()[0].Type == source.Type); 292 REQUIRE(policy.value()[0].Data == source.Data); 293 REQUIRE(policy.value()[0].Identifier == source.Identifier); 294 REQUIRE(policy.value()[0].TrustLevel[0] == source.TrustLevel[0]); // Trusted 295 REQUIRE(policy.value()[0].TrustLevel[1] == source.TrustLevel[1]); // StoreOrigin 296 REQUIRE(policy.value()[0].Explicit == source.Explicit); 297 } 298 SECTION("Source with PinningConfiguration") 299 { 300 using namespace AppInstaller::Certificates; 301 302 auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName); 303 304 PinningDetails rootCert; 305 rootCert.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE); 306 PinningDetails intermediateCert; 307 intermediateCert.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE); 308 PinningDetails leafCert; 309 leafCert.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); 310 311 auto getBytesString = [](const PinningDetails& details) 312 { 313 std::vector<BYTE> bytes; 314 bytes.assign(details.GetCertificate()->pbCertEncoded, details.GetCertificate()->pbCertEncoded + details.GetCertificate()->cbCertEncoded); 315 return AppInstaller::Utility::ConvertToUTF16(AppInstaller::Utility::ConvertToHexString(bytes)); 316 }; 317 318 std::wostringstream pinningConfig; 319 pinningConfig << 320 LR"({ 321 "Chains": [{ 322 "Chain":[ 323 { "Validation": ["publickey"], "EmbeddedCertificate": ")" << getBytesString(rootCert) << LR"(" }, 324 { "Validation": ["subject","issuer"], "EmbeddedCertificate": ")" << getBytesString(intermediateCert) << LR"(" }, 325 { "Validation": ["subject","issuer"], "EmbeddedCertificate": ")" << getBytesString(leafCert) << LR"(" } 326 ] 327 }] 328 })"; 329 330 SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"source-name", L"source-arg", L"source-type", L"source-data", L"source-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"true", pinningConfig.str()), REG_SZ); 331 GroupPolicy groupPolicy{ policiesKey.get() }; 332 333 auto policy = groupPolicy.GetValue<ValuePolicy::AdditionalSources>(); 334 REQUIRE(policy.has_value()); 335 REQUIRE(policy->size() == 1); 336 const auto& sourceInfo = policy.value()[0]; 337 REQUIRE(sourceInfo.Name == "source-name"); 338 REQUIRE(sourceInfo.Arg == "source-arg"); 339 REQUIRE(sourceInfo.Type == "source-type"); 340 REQUIRE(sourceInfo.Data == "source-data"); 341 REQUIRE(sourceInfo.Identifier == "source-identifier"); 342 REQUIRE(sourceInfo.TrustLevel[0] == "Trusted"); 343 REQUIRE(sourceInfo.TrustLevel[1] == "StoreOrigin"); 344 REQUIRE(sourceInfo.Explicit == true); 345 346 // Use loaded pinning config and validate against leaf certificate 347 REQUIRE(!sourceInfo.PinningConfiguration.IsEmpty()); 348 REQUIRE(sourceInfo.PinningConfiguration.Validate(leafCert.GetCertificate())); 349 } 350 } 351 352 TEST_CASE("GroupPolicy_Toggle", "[groupPolicy]") 353 { 354 auto policiesKey = RegCreateVolatileTestRoot(); 355 356 SECTION("'None' is not configured") 357 { 358 GroupPolicy groupPolicy{ policiesKey.get() }; 359 REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::None) == PolicyState::NotConfigured); 360 REQUIRE(groupPolicy.IsEnabled(TogglePolicy::Policy::None)); 361 } 362 363 SECTION("Enabled") 364 { 365 SetRegistryValue(policiesKey.get(), WinGetPolicyValueName, 1); 366 GroupPolicy groupPolicy{ policiesKey.get() }; 367 REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::WinGet) == PolicyState::Enabled); 368 REQUIRE(groupPolicy.IsEnabled(TogglePolicy::Policy::WinGet)); 369 } 370 371 SECTION("Disabled") 372 { 373 SetRegistryValue(policiesKey.get(), LocalManifestsPolicyValueName, 0); 374 GroupPolicy groupPolicy{ policiesKey.get() }; 375 REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::LocalManifestFiles) == PolicyState::Disabled); 376 REQUIRE_FALSE(groupPolicy.IsEnabled(TogglePolicy::Policy::LocalManifestFiles)); 377 } 378 379 SECTION("Wrong type") 380 { 381 SetRegistryValue(policiesKey.get(), ExperimentalFeaturesPolicyValueName, L"Wrong"); 382 GroupPolicy groupPolicy{ policiesKey.get() }; 383 REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::DefaultSource) == PolicyState::NotConfigured); 384 REQUIRE(groupPolicy.IsEnabled(TogglePolicy::Policy::DefaultSource)); 385 } 386 } 387 388 TEST_CASE("GroupPolicy_AllEnabled", "[groupPolicy]") 389 { 390 auto policiesKey = RegCreateVolatileTestRoot(); 391 SetRegistryValue(policiesKey.get(), WinGetPolicyValueName, 1); 392 SetRegistryValue(policiesKey.get(), WinGetSettingsPolicyValueName, 1); 393 SetRegistryValue(policiesKey.get(), ExperimentalFeaturesPolicyValueName, 1); 394 SetRegistryValue(policiesKey.get(), LocalManifestsPolicyValueName, 1); 395 SetRegistryValue(policiesKey.get(), EnableHashOverridePolicyValueName, 1); 396 SetRegistryValue(policiesKey.get(), EnableLocalArchiveMalwareScanOverridePolicyValueName, 1); 397 SetRegistryValue(policiesKey.get(), DefaultSourcePolicyValueName, 1); 398 SetRegistryValue(policiesKey.get(), MSStoreSourcePolicyValueName, 1);; 399 SetRegistryValue(policiesKey.get(), AdditionalSourcesPolicyValueName, 1); 400 SetRegistryValue(policiesKey.get(), AllowedSourcesPolicyValueName, 1); 401 SetRegistryValue(policiesKey.get(), BypassCertificatePinningForMicrosoftStoreValueName, 1); 402 SetRegistryValue(policiesKey.get(), EnableWindowsPackageManagerCommandLineInterfaces, 1); 403 SetRegistryValue(policiesKey.get(), ConfigurationPolicyValueName, 1); 404 SetRegistryValue(policiesKey.get(), ProxyCommandLineOptionsPolicyValueName, 1); 405 SetRegistryValue(policiesKey.get(), McpServerValueName, 1); 406 407 GroupPolicy groupPolicy{ policiesKey.get() }; 408 for (const auto& policy : TogglePolicy::GetAllPolicies()) 409 { 410 REQUIRE(groupPolicy.GetState(policy.GetPolicy()) == PolicyState::Enabled); 411 } 412 }