diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt
index 03a7ed2f71..3666150db0 100644
--- a/.github/actions/spelling/allow.txt
+++ b/.github/actions/spelling/allow.txt
@@ -117,6 +117,7 @@ forcerestart
gdi
genai
HCCE
+HCERTCHAINENGINE
hcertstore
HCRYPTMSG
HGlobal
@@ -240,6 +241,7 @@ pinnable
pinningindex
pipssource
Pkcs
+PKI
portableindex
powertoys
pplx
diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
index ad2925f603..33eee0a3e3 100644
--- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
+++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
@@ -213,6 +213,7 @@
+
@@ -330,6 +331,7 @@
+
@@ -337,6 +339,21 @@
+
+ true
+
+
+ true
+
+
+ true
+
+
+ true
+
+
+ true
+
true
diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
index 348f9cdfda..a303792a0f 100644
--- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
+++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
@@ -72,6 +72,9 @@
Header Files
+
+ Header Files
+
Header Files
@@ -101,6 +104,9 @@
Source Files
+
+ Source Files
+
Source Files
@@ -417,6 +423,21 @@
+
+ TestData
+
+
+ TestData
+
+
+ TestData
+
+
+ TestData
+
+
+ TestData
+
TestData
diff --git a/src/AppInstallerCLITests/Certificates.cpp b/src/AppInstallerCLITests/Certificates.cpp
index 0a71d0731a..8608ab54dc 100644
--- a/src/AppInstallerCLITests/Certificates.cpp
+++ b/src/AppInstallerCLITests/Certificates.cpp
@@ -2,344 +2,358 @@
// Licensed under the MIT License.
#include "pch.h"
#include "TestCommon.h"
+#include "TestCertificates.h"
#include
-#include
using namespace TestCommon;
using namespace AppInstaller;
using namespace AppInstaller::Certificates;
-TEST_CASE("Certificates_NoPinningSucceeds", "[certificates]")
+TEST_CASE("Certificates_NoPinningSucceeds", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningDetails expected;
- expected.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::None);
+ expected.LoadCertificate(testChain.Root().View()).SetPinning(PinningVerificationType::None);
PinningDetails actual;
- actual.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
+ actual.LoadCertificate(testChain.Leaf2().View());
REQUIRE(CertificatePinningValidationResult::Accepted == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Leaf));
}
-TEST_CASE("Certificates_PublicKeyMismatch", "[certificates]")
+TEST_CASE("Certificates_PublicKeyMismatch", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningDetails expected;
- expected.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey);
+ expected.LoadCertificate(testChain.Root().View()).SetPinning(PinningVerificationType::PublicKey);
PinningDetails actual;
- actual.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
+ actual.LoadCertificate(testChain.Leaf2().View());
REQUIRE(CertificatePinningValidationResult::Rejected == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Leaf));
}
-TEST_CASE("Certificates_PublicKeyMatch", "[certificates]")
+TEST_CASE("Certificates_PublicKeyMatch", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningDetails expected;
- expected.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey);
+ expected.LoadCertificate(testChain.Root().View()).SetPinning(PinningVerificationType::PublicKey);
PinningDetails actual;
- actual.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE);
+ actual.LoadCertificate(testChain.Root().View());
REQUIRE(CertificatePinningValidationResult::Accepted == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Root));
}
-TEST_CASE("Certificates_SubjectMismatch", "[certificates]")
+TEST_CASE("Certificates_SubjectMismatch", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningDetails expected;
- expected.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject);
+ expected.LoadCertificate(testChain.Root().View()).SetPinning(PinningVerificationType::Subject);
PinningDetails actual;
- actual.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE);
+ actual.LoadCertificate(testChain.Intermediate2().View());
REQUIRE(CertificatePinningValidationResult::Rejected == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Intermediate));
}
-TEST_CASE("Certificates_SubjectMatch", "[certificates]")
+TEST_CASE("Certificates_SubjectMatch", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningDetails expected;
- expected.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject);
+ expected.LoadCertificate(testChain.Intermediate2().View()).SetPinning(PinningVerificationType::Subject);
PinningDetails actual;
- actual.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE);
+ actual.LoadCertificate(testChain.Intermediate2().View());
REQUIRE(CertificatePinningValidationResult::Accepted == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Intermediate));
}
-TEST_CASE("Certificates_IssuerMismatch", "[certificates]")
+TEST_CASE("Certificates_IssuerMismatch", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningDetails expected;
- expected.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Issuer);
+ expected.LoadCertificate(testChain.Intermediate2().View()).SetPinning(PinningVerificationType::Issuer);
PinningDetails actual;
- actual.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
+ actual.LoadCertificate(testChain.Leaf2().View());
REQUIRE(CertificatePinningValidationResult::Rejected == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Leaf));
}
-TEST_CASE("Certificates_IssuerMatch", "[certificates]")
+TEST_CASE("Certificates_IssuerMatch", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningDetails expected;
- expected.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Issuer);
+ expected.LoadCertificate(testChain.Leaf2().View()).SetPinning(PinningVerificationType::Issuer);
PinningDetails actual;
- actual.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
+ actual.LoadCertificate(testChain.Leaf2().View());
REQUIRE(CertificatePinningValidationResult::Accepted == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Leaf));
}
-TEST_CASE("Certificates_ChainLengthDiffers", "[certificates]")
+TEST_CASE("Certificates_ChainLengthDiffers", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningChain chain;
auto chainElement = chain.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey);
+ chainElement->LoadCertificate(testChain.Root().View()).SetPinning(PinningVerificationType::PublicKey);
chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
+ chainElement->LoadCertificate(testChain.Leaf2().View()).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
PinningConfiguration config;
config.AddChain(chain);
- PinningDetails details;
- details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
-
- REQUIRE(!config.Validate(details.GetCertificate()));
+ auto chainCtx = testChain.BuildChain(testChain.Leaf2());
+ REQUIRE(!config.Validate(testChain.Leaf2(), chainCtx.get()));
}
-TEST_CASE("Certificates_ChainLengthDiffers_Partial", "[certificates]")
+TEST_CASE("Certificates_ChainLengthDiffers_Partial", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningChain chain;
auto chainElement = chain.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey);
+ chainElement->LoadCertificate(testChain.Root().View()).SetPinning(PinningVerificationType::PublicKey);
chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
+ chainElement->LoadCertificate(testChain.Intermediate2().View()).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
chain.PartialChain();
PinningConfiguration config;
config.AddChain(chain);
- PinningDetails details;
- details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
-
- REQUIRE(config.Validate(details.GetCertificate()));
+ auto chainCtx = testChain.BuildChain(testChain.Leaf2());
+ REQUIRE(config.Validate(testChain.Leaf2(), chainCtx.get()));
}
-TEST_CASE("CertificateChain_AnyIssuer_Intermediate", "[certificates]")
+TEST_CASE("CertificateChain_AnyIssuer_Intermediate", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningChain chain;
auto chainElement = chain.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf);
+ chainElement->LoadCertificate(testChain.Intermediate2().View()).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf);
chain.PartialChain();
PinningConfiguration config;
config.AddChain(chain);
- PinningDetails details;
- details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
-
- REQUIRE(config.Validate(details.GetCertificate()));
+ auto chainCtx = testChain.BuildChain(testChain.Leaf2());
+ REQUIRE(config.Validate(testChain.Leaf2(), chainCtx.get()));
}
-TEST_CASE("CertificateChain_AnyIssuer_IntermediateDiffers", "[certificates]")
+TEST_CASE("CertificateChain_AnyIssuer_IntermediateDiffers", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningChain chain;
auto chainElement = chain.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf);
+ chainElement->LoadCertificate(testChain.Intermediate1().View()).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf);
chain.PartialChain();
PinningConfiguration config;
config.AddChain(chain);
- PinningDetails details;
- details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
-
- REQUIRE(!config.Validate(details.GetCertificate()));
+ auto chainCtx = testChain.BuildChain(testChain.Leaf2());
+ REQUIRE(!config.Validate(testChain.Leaf2(), chainCtx.get()));
}
-TEST_CASE("CertificateChain_AnyIssuer_IntermediateAndLeaf", "[certificates]")
+TEST_CASE("CertificateChain_AnyIssuer_IntermediateAndLeaf", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningChain chain;
auto chainElement = chain.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf);
+ chainElement->LoadCertificate(testChain.Intermediate2().View()).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf);
chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
+ chainElement->LoadCertificate(testChain.Leaf2().View()).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
chain.PartialChain();
PinningConfiguration config;
config.AddChain(chain);
- PinningDetails details;
- details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
-
- REQUIRE(config.Validate(details.GetCertificate()));
+ auto chainCtx = testChain.BuildChain(testChain.Leaf2());
+ REQUIRE(config.Validate(testChain.Leaf2(), chainCtx.get()));
}
-TEST_CASE("CertificateChain_AnyIssuer_Leaf", "[certificates]")
+TEST_CASE("CertificateChain_AnyIssuer_Leaf", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningChain chain;
auto chainElement = chain.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer);
+ chainElement->LoadCertificate(testChain.Leaf2().View()).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer);
chain.PartialChain();
PinningConfiguration config;
config.AddChain(chain);
- PinningDetails details;
- details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
-
- REQUIRE(config.Validate(details.GetCertificate()));
+ auto chainCtx = testChain.BuildChain(testChain.Leaf2());
+ REQUIRE(config.Validate(testChain.Leaf2(), chainCtx.get()));
}
-TEST_CASE("CertificateChain_AnyIssuer_LeafDiffers", "[certificates]")
+TEST_CASE("CertificateChain_AnyIssuer_LeafDiffers", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningChain chain;
auto chainElement = chain.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer);
+ chainElement->LoadCertificate(testChain.Leaf1().View()).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer);
chain.PartialChain();
PinningConfiguration config;
config.AddChain(chain);
- PinningDetails details;
- details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
-
- REQUIRE(!config.Validate(details.GetCertificate()));
+ auto chainCtx = testChain.BuildChain(testChain.Leaf2());
+ REQUIRE(!config.Validate(testChain.Leaf2(), chainCtx.get()));
}
-TEST_CASE("CertificateChain_AnyIssuer_SameLeaf_RequireNonLeaf", "[certificates]")
+TEST_CASE("CertificateChain_AnyIssuer_SameLeaf_RequireNonLeaf", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningChain chain;
auto chainElement = chain.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf);
+ chainElement->LoadCertificate(testChain.Leaf2().View()).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf);
chain.PartialChain();
PinningConfiguration config;
config.AddChain(chain);
- PinningDetails details;
- details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
-
- REQUIRE(!config.Validate(details.GetCertificate()));
+ auto chainCtx = testChain.BuildChain(testChain.Leaf2());
+ REQUIRE(!config.Validate(testChain.Leaf2(), chainCtx.get()));
}
-TEST_CASE("Certificates_EmptyChainRejects", "[certificates]")
+TEST_CASE("Certificates_EmptyChainRejects", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningChain chain;
PinningConfiguration config;
config.AddChain(chain);
- PinningDetails details;
- details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
-
- REQUIRE(!config.Validate(details.GetCertificate()));
+ auto chainCtx = testChain.BuildChain(testChain.Leaf2());
+ REQUIRE(!config.Validate(testChain.Leaf2(), chainCtx.get()));
}
-TEST_CASE("Certificates_ChainOrderDiffers", "[certificates]")
+TEST_CASE("Certificates_ChainOrderDiffers", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningChain chain;
auto chainElement = chain.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey);
+ chainElement->LoadCertificate(testChain.Root().View()).SetPinning(PinningVerificationType::PublicKey);
chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
+ chainElement->LoadCertificate(testChain.Leaf2().View()).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
+ chainElement->LoadCertificate(testChain.Intermediate2().View()).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
PinningConfiguration config;
config.AddChain(chain);
- PinningDetails details;
- details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
-
- REQUIRE(!config.Validate(details.GetCertificate()));
+ auto chainCtx = testChain.BuildChain(testChain.Leaf2());
+ REQUIRE(!config.Validate(testChain.Leaf2(), chainCtx.get()));
}
-TEST_CASE("Certificates_StoreChain_BuiltInTest", "[certificates]")
+TEST_CASE("Certificates_StoreChain_BuiltInTest", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningChain chain;
auto chainElement = chain.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey);
+ chainElement->LoadCertificate(testChain.Root().View()).SetPinning(PinningVerificationType::PublicKey);
chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
+ chainElement->LoadCertificate(testChain.Intermediate2().View()).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
+ chainElement->LoadCertificate(testChain.Leaf2().View()).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
PinningConfiguration config;
config.AddChain(chain);
- PinningDetails details;
- details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
-
- REQUIRE(config.Validate(details.GetCertificate()));
+ auto chainCtx = testChain.BuildChain(testChain.Leaf2());
+ REQUIRE(config.Validate(testChain.Leaf2(), chainCtx.get()));
}
-TEST_CASE("Certificates_MultipleChains_Success", "[certificates]")
+TEST_CASE("Certificates_MultipleChains_Success", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
PinningChain chainOutOfOrder;
auto chainElement = chainOutOfOrder.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey);
+ chainElement->LoadCertificate(testChain.Root().View()).SetPinning(PinningVerificationType::PublicKey);
chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
+ chainElement->LoadCertificate(testChain.Leaf2().View()).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
+ chainElement->LoadCertificate(testChain.Intermediate2().View()).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
PinningConfiguration config;
config.AddChain(chainOutOfOrder);
PinningChain chain;
chainElement = chain.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey);
+ chainElement->LoadCertificate(testChain.Root().View()).SetPinning(PinningVerificationType::PublicKey);
chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
+ chainElement->LoadCertificate(testChain.Intermediate2().View()).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
+ chainElement->LoadCertificate(testChain.Leaf2().View()).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
config.AddChain(chain);
- PinningDetails details;
- details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
-
- REQUIRE(config.Validate(details.GetCertificate()));
+ auto chainCtx = testChain.BuildChain(testChain.Leaf2());
+ REQUIRE(config.Validate(testChain.Leaf2(), chainCtx.get()));
}
-TEST_CASE("CertificateChain_Position", "[certificates]")
+TEST_CASE("CertificateChain_Position", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
CertificateChainPosition positions[3]{ CertificateChainPosition::Unknown, CertificateChainPosition::Unknown, CertificateChainPosition::Unknown };
PinningChain chain;
auto chainElement = chain.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[0] = position; return true; });
+ chainElement->LoadCertificate(testChain.Root().View()).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[0] = position; return true; });
chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[1] = position; return true; });
+ chainElement->LoadCertificate(testChain.Intermediate2().View()).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[1] = position; return true; });
chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[2] = position; return true; });
+ chainElement->LoadCertificate(testChain.Leaf2().View()).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[2] = position; return true; });
PinningConfiguration config;
config.AddChain(chain);
- PinningDetails details;
- details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
-
- REQUIRE(config.Validate(details.GetCertificate()));
+ auto chainCtx = testChain.BuildChain(testChain.Leaf2());
+ REQUIRE(config.Validate(testChain.Leaf2(), chainCtx.get()));
REQUIRE(CertificateChainPosition::Root == positions[0]);
REQUIRE(CertificateChainPosition::Intermediate == positions[1]);
REQUIRE(CertificateChainPosition::Leaf == positions[2]);
}
-TEST_CASE("CertificateChain_Position_SelfSigned", "[certificates]")
+TEST_CASE("CertificateChain_Position_SelfSigned", "[certificates][uses-test-certificates]")
{
+ TestCertificateChain testChain;
+
CertificateChainPosition positions[1]{ CertificateChainPosition::Unknown };
PinningChain chain;
auto chainElement = chain.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[0] = position; return true; });
+ chainElement->LoadCertificate(testChain.Root().View()).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[0] = position; return true; });
PinningConfiguration config;
config.AddChain(chain);
- PinningDetails details;
- details.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE);
-
- REQUIRE(config.Validate(details.GetCertificate()));
+ auto chainCtx = testChain.BuildChain(testChain.Root());
+ REQUIRE(config.Validate(testChain.Root(), chainCtx.get()));
REQUIRE((CertificateChainPosition::Root | CertificateChainPosition::Leaf) == positions[0]);
}
diff --git a/src/AppInstallerCLITests/GroupPolicy.cpp b/src/AppInstallerCLITests/GroupPolicy.cpp
index 28d1fdc961..7c53d16908 100644
--- a/src/AppInstallerCLITests/GroupPolicy.cpp
+++ b/src/AppInstallerCLITests/GroupPolicy.cpp
@@ -2,10 +2,10 @@
// Licensed under the MIT License.
#include "pch.h"
#include "TestCommon.h"
+#include "TestCertificates.h"
#include "TestSettings.h"
#include "winget/GroupPolicy.h"
#include
-#include
using namespace TestCommon;
using namespace AppInstaller::Settings;
@@ -116,7 +116,7 @@ TEST_CASE("GroupPolicy_UpdateInterval_OldName", "[groupPolicy]")
}
}
-TEST_CASE("GroupPolicy_Sources", "[groupPolicy]")
+TEST_CASE("GroupPolicy_Sources", "[groupPolicy][uses-test-certificates]")
{
auto policiesKey = RegCreateVolatileTestRoot();
@@ -301,17 +301,12 @@ TEST_CASE("GroupPolicy_Sources", "[groupPolicy]")
auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName);
- PinningDetails rootCert;
- rootCert.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE);
- PinningDetails intermediateCert;
- intermediateCert.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE);
- PinningDetails leafCert;
- leafCert.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE);
+ TestCertificateChain testChain;
- auto getBytesString = [](const PinningDetails& details)
+ auto certToHexString = [](PCCERT_CONTEXT cert)
{
std::vector bytes;
- bytes.assign(details.GetCertificate()->pbCertEncoded, details.GetCertificate()->pbCertEncoded + details.GetCertificate()->cbCertEncoded);
+ bytes.assign(cert->pbCertEncoded, cert->pbCertEncoded + cert->cbCertEncoded);
return AppInstaller::Utility::ConvertToUTF16(AppInstaller::Utility::ConvertToHexString(bytes));
};
@@ -320,9 +315,9 @@ TEST_CASE("GroupPolicy_Sources", "[groupPolicy]")
LR"({
"Chains": [{
"Chain":[
- { "Validation": ["publickey"], "EmbeddedCertificate": ")" << getBytesString(rootCert) << LR"(" },
- { "Validation": ["subject","issuer"], "EmbeddedCertificate": ")" << getBytesString(intermediateCert) << LR"(" },
- { "Validation": ["subject","issuer"], "EmbeddedCertificate": ")" << getBytesString(leafCert) << LR"(" }
+ { "Validation": ["publickey"], "EmbeddedCertificate": ")" << certToHexString(testChain.Root()) << LR"(" },
+ { "Validation": ["subject","issuer"], "EmbeddedCertificate": ")" << certToHexString(testChain.Intermediate2()) << LR"(" },
+ { "Validation": ["subject","issuer"], "EmbeddedCertificate": ")" << certToHexString(testChain.Leaf2()) << LR"(" }
]
}]
})";
@@ -345,7 +340,8 @@ LR"({
// Use loaded pinning config and validate against leaf certificate
REQUIRE(!sourceInfo.PinningConfiguration.IsEmpty());
- REQUIRE(sourceInfo.PinningConfiguration.Validate(leafCert.GetCertificate()));
+ auto chainCtx = testChain.BuildChain(testChain.Leaf2());
+ REQUIRE(sourceInfo.PinningConfiguration.Validate(testChain.Leaf2(), chainCtx.get()));
}
}
diff --git a/src/AppInstallerCLITests/HttpClientHelper.cpp b/src/AppInstallerCLITests/HttpClientHelper.cpp
index 0705c815b2..a1d4ad5c3d 100644
--- a/src/AppInstallerCLITests/HttpClientHelper.cpp
+++ b/src/AppInstallerCLITests/HttpClientHelper.cpp
@@ -3,12 +3,12 @@
#include "pch.h"
#include "TestCommon.h"
#include "TestRestRequestHandler.h"
+#include "TestCertificates.h"
#include
#include
#include
#include
#include
-#include
#include
using namespace AppInstaller::Http;
@@ -61,16 +61,17 @@ TEST_CASE("EnsureDefaultUserAgent", "[RestSource]")
}
}
-TEST_CASE("HttpClientHelper_PinningConfiguration", "[RestSource]")
+TEST_CASE("HttpClientHelper_PinningConfiguration", "[RestSource][uses-test-certificates]")
{
- // Create the Store chain config
+ // Create a pinning chain with test certs that won't match any real server
+ TestCommon::TestCertificateChain testChain;
PinningChain chain;
auto chainElement = chain.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey);
+ chainElement->LoadCertificate(testChain.Root().View()).SetPinning(PinningVerificationType::PublicKey);
chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
+ chainElement->LoadCertificate(testChain.Intermediate2().View()).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
+ chainElement->LoadCertificate(testChain.Leaf2().View()).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
PinningConfiguration config;
config.AddChain(chain);
diff --git a/src/AppInstallerCLITests/TestCertificates.cpp b/src/AppInstallerCLITests/TestCertificates.cpp
new file mode 100644
index 0000000000..0b939f1781
--- /dev/null
+++ b/src/AppInstallerCLITests/TestCertificates.cpp
@@ -0,0 +1,70 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+#include "pch.h"
+#include "TestCertificates.h"
+#include "TestCommon.h"
+#include
+
+namespace TestCommon
+{
+ namespace
+ {
+ AppInstaller::Certificates::PinningDetails LoadCertFromTestData(const char* filename)
+ {
+ std::filesystem::path path = TestDataFile(filename).GetPath();
+ std::ifstream file(path, std::ios::binary);
+ THROW_HR_IF(E_FAIL, !file.is_open());
+ auto bytes = AppInstaller::Utility::ReadEntireStreamAsByteArray(file);
+ AppInstaller::Certificates::PinningDetails details;
+ details.LoadCertificate(bytes);
+ return details;
+ }
+
+ wil::unique_hcertstore OpenMemoryStore()
+ {
+ wil::unique_hcertstore store{ CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, nullptr) };
+ THROW_LAST_ERROR_IF(!store);
+ return store;
+ }
+ }
+
+ TestCertificateChain::TestCertificateChain()
+ {
+ m_root = LoadCertFromTestData("TestRoot.cer");
+ m_intermediate1 = LoadCertFromTestData("TestIntermediate1.cer");
+ m_intermediate2 = LoadCertFromTestData("TestIntermediate2.cer");
+ m_leaf1 = LoadCertFromTestData("TestLeaf1.cer");
+ m_leaf2 = LoadCertFromTestData("TestLeaf2.cer");
+
+ // Root store: used as hExclusiveRoot so only TestRoot is trusted.
+ m_rootStore = OpenMemoryStore();
+ THROW_IF_WIN32_BOOL_FALSE(CertAddCertificateContextToStore(m_rootStore.get(), m_root.GetCertificate(), CERT_STORE_ADD_NEW, nullptr));
+
+ CERT_CHAIN_ENGINE_CONFIG engineConfig = {};
+ engineConfig.cbSize = sizeof(engineConfig);
+ engineConfig.hExclusiveRoot = m_rootStore.get();
+ HCERTCHAINENGINE rawEngine = nullptr;
+ THROW_IF_WIN32_BOOL_FALSE(CertCreateCertificateChainEngine(&engineConfig, &rawEngine));
+ m_chainEngine.reset(rawEngine);
+
+ // Additional store: holds intermediates so the chain builder can find issuers.
+ m_additionalStore = OpenMemoryStore();
+ THROW_IF_WIN32_BOOL_FALSE(CertAddCertificateContextToStore(m_additionalStore.get(), m_intermediate1.GetCertificate(), CERT_STORE_ADD_NEW, nullptr));
+ THROW_IF_WIN32_BOOL_FALSE(CertAddCertificateContextToStore(m_additionalStore.get(), m_intermediate2.GetCertificate(), CERT_STORE_ADD_NEW, nullptr));
+ }
+
+ wil::unique_cert_chain_context TestCertificateChain::BuildChain(PCCERT_CONTEXT certContext) const
+ {
+ return AppInstaller::Certificates::PinningConfiguration::BuildCertificateChain(
+ certContext,
+ m_chainEngine.get(),
+ m_additionalStore.get(),
+ 0); // No revocation checking for test certs (no CRL endpoints)
+ }
+
+ CertContextRef TestCertificateChain::Root() const { return m_root.GetCertificate(); }
+ CertContextRef TestCertificateChain::Intermediate1() const { return m_intermediate1.GetCertificate(); }
+ CertContextRef TestCertificateChain::Intermediate2() const { return m_intermediate2.GetCertificate(); }
+ CertContextRef TestCertificateChain::Leaf1() const { return m_leaf1.GetCertificate(); }
+ CertContextRef TestCertificateChain::Leaf2() const { return m_leaf2.GetCertificate(); }
+}
diff --git a/src/AppInstallerCLITests/TestCertificates.h b/src/AppInstallerCLITests/TestCertificates.h
new file mode 100644
index 0000000000..b30c0a7fce
--- /dev/null
+++ b/src/AppInstallerCLITests/TestCertificates.h
@@ -0,0 +1,67 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+#pragma once
+#include "TestCommon.h"
+#include
+#include
+
+namespace TestCommon
+{
+ using unique_cert_chain_engine = wil::unique_any;
+
+ // Non-owning view of a PCCERT_CONTEXT.
+ // operator-> accesses the underlying CERT_CONTEXT fields directly.
+ // View() returns the encoded bytes as a pair suitable for PinningDetails::LoadCertificate.
+ // Implicitly converts to PCCERT_CONTEXT for APIs that take the raw handle.
+ struct CertContextRef
+ {
+ CertContextRef(PCCERT_CONTEXT cert) : m_cert(cert) {}
+
+ PCCERT_CONTEXT operator->() const { return m_cert; }
+ operator PCCERT_CONTEXT() const { return m_cert; }
+
+ std::pair View() const
+ {
+ return { m_cert->pbCertEncoded, static_cast(m_cert->cbCertEncoded) };
+ }
+
+ private:
+ PCCERT_CONTEXT m_cert;
+ };
+
+ // Provides a self-contained PKI hierarchy for unit tests, loaded from TestData files.
+ // The hierarchy is:
+ // TestRoot -> TestIntermediate1 -> TestLeaf1
+ // -> TestIntermediate2 -> TestLeaf2
+ //
+ // Use BuildChain() to get a PCCERT_CHAIN_CONTEXT built with a custom chain engine that
+ // trusts TestRoot exclusively, suitable for passing to PinningConfiguration::Validate().
+ // Revocation checking is disabled since the test certs have no CRL.
+ struct TestCertificateChain
+ {
+ TestCertificateChain();
+
+ TestCertificateChain(const TestCertificateChain&) = delete;
+ TestCertificateChain& operator=(const TestCertificateChain&) = delete;
+
+ // Builds a certificate chain for certContext using the test chain engine.
+ wil::unique_cert_chain_context BuildChain(PCCERT_CONTEXT certContext) const;
+
+ CertContextRef Root() const;
+ CertContextRef Intermediate1() const;
+ CertContextRef Intermediate2() const;
+ CertContextRef Leaf1() const;
+ CertContextRef Leaf2() const;
+
+ private:
+ AppInstaller::Certificates::PinningDetails m_root;
+ AppInstaller::Certificates::PinningDetails m_intermediate1;
+ AppInstaller::Certificates::PinningDetails m_intermediate2;
+ AppInstaller::Certificates::PinningDetails m_leaf1;
+ AppInstaller::Certificates::PinningDetails m_leaf2;
+
+ unique_cert_chain_engine m_chainEngine;
+ wil::unique_hcertstore m_rootStore;
+ wil::unique_hcertstore m_additionalStore;
+ };
+}
diff --git a/src/AppInstallerCLITests/TestData/TestIntermediate1.cer b/src/AppInstallerCLITests/TestData/TestIntermediate1.cer
new file mode 100644
index 0000000000..5cc702440e
Binary files /dev/null and b/src/AppInstallerCLITests/TestData/TestIntermediate1.cer differ
diff --git a/src/AppInstallerCLITests/TestData/TestIntermediate2.cer b/src/AppInstallerCLITests/TestData/TestIntermediate2.cer
new file mode 100644
index 0000000000..4a842db863
Binary files /dev/null and b/src/AppInstallerCLITests/TestData/TestIntermediate2.cer differ
diff --git a/src/AppInstallerCLITests/TestData/TestLeaf1.cer b/src/AppInstallerCLITests/TestData/TestLeaf1.cer
new file mode 100644
index 0000000000..4ab349ef63
Binary files /dev/null and b/src/AppInstallerCLITests/TestData/TestLeaf1.cer differ
diff --git a/src/AppInstallerCLITests/TestData/TestLeaf2.cer b/src/AppInstallerCLITests/TestData/TestLeaf2.cer
new file mode 100644
index 0000000000..d30d41411d
Binary files /dev/null and b/src/AppInstallerCLITests/TestData/TestLeaf2.cer differ
diff --git a/src/AppInstallerCLITests/TestData/TestRoot.cer b/src/AppInstallerCLITests/TestData/TestRoot.cer
new file mode 100644
index 0000000000..d9ed5028b7
Binary files /dev/null and b/src/AppInstallerCLITests/TestData/TestRoot.cer differ
diff --git a/src/AppInstallerRepositoryCore/SourceList.cpp b/src/AppInstallerRepositoryCore/SourceList.cpp
index 87c5c4e302..7f14cacbfd 100644
--- a/src/AppInstallerRepositoryCore/SourceList.cpp
+++ b/src/AppInstallerRepositoryCore/SourceList.cpp
@@ -368,38 +368,20 @@ namespace AppInstaller::Repository
{
using namespace AppInstaller::Certificates;
- PinningChain chain;
- auto chainElement = chain.Root();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey);
- chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
- chainElement = chainElement.Next();
- chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
-
- PinningChain chain2;
- auto chainElement2 = chain2.Root();
- chainElement2->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey);
- chainElement2 = chainElement2.Next();
- chainElement2->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
- chainElement2 = chainElement2.Next();
- chainElement2->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer);
-
// See https://aka.ms/AzureTLSCAs (internal) for the source of these CAs
- PinningChain chain3;
- chain3.PartialChain().Root()->
+ PinningChain chain1;
+ chain1.PartialChain().Root()->
LoadCertificate(IDX_CERTIFICATE_MS_TLS_ECC_ROOT_G2, CERTIFICATE_RESOURCE_TYPE).
SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf);
- PinningChain chain4;
- chain4.PartialChain().Root()->
+ PinningChain chain2;
+ chain2.PartialChain().Root()->
LoadCertificate(IDX_CERTIFICATE_MS_TLS_RSA_ROOT_G2, CERTIFICATE_RESOURCE_TYPE).
SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf);
details.CertificatePinningConfiguration = PinningConfiguration("Microsoft Store Source");
- details.CertificatePinningConfiguration.AddChain(std::move(chain));
+ details.CertificatePinningConfiguration.AddChain(std::move(chain1));
details.CertificatePinningConfiguration.AddChain(std::move(chain2));
- details.CertificatePinningConfiguration.AddChain(std::move(chain3));
- details.CertificatePinningConfiguration.AddChain(std::move(chain4));
}
return details;
diff --git a/src/AppInstallerSharedLib/Certificates.cpp b/src/AppInstallerSharedLib/Certificates.cpp
index 9f25e3534c..cb881839eb 100644
--- a/src/AppInstallerSharedLib/Certificates.cpp
+++ b/src/AppInstallerSharedLib/Certificates.cpp
@@ -268,6 +268,11 @@ namespace AppInstaller::Certificates
return *this;
}
+ PinningDetails& PinningDetails::LoadCertificate(const BYTE* certData, size_t certSize)
+ {
+ return LoadCertificate(std::make_pair(certData, certSize));
+ }
+
PinningDetails& PinningDetails::SetPinning(PinningVerificationType type)
{
m_pinning = type;
@@ -689,7 +694,38 @@ namespace AppInstaller::Certificates
m_configuration.emplace_back(std::move(chain));
}
- bool PinningConfiguration::Validate(PCCERT_CONTEXT certContext) const
+ wil::unique_cert_chain_context PinningConfiguration::BuildCertificateChain(
+ PCCERT_CONTEXT certContext,
+ HCERTCHAINENGINE engine,
+ HCERTSTORE additionalStore,
+ DWORD flags)
+ {
+ char oidPkixKpServerAuth[] = szOID_PKIX_KP_SERVER_AUTH;
+ std::array chainUses = {
+ oidPkixKpServerAuth,
+ };
+
+ CERT_CHAIN_PARA chainParameters = {};
+ chainParameters.cbSize = sizeof(chainParameters);
+ chainParameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
+ chainParameters.RequestedUsage.Usage.cUsageIdentifier = static_cast(chainUses.size());
+ chainParameters.RequestedUsage.Usage.rgpszUsageIdentifier = chainUses.data();
+
+ wil::unique_cert_chain_context chainContext;
+ THROW_IF_WIN32_BOOL_FALSE(CertGetCertificateChain(
+ engine,
+ certContext,
+ nullptr,
+ additionalStore ? additionalStore : certContext->hCertStore,
+ &chainParameters,
+ flags,
+ nullptr,
+ &chainContext));
+
+ return chainContext;
+ }
+
+ bool PinningConfiguration::Validate(PCCERT_CONTEXT certContext, PCCERT_CHAIN_CONTEXT chainContext) const
{
if (m_configuration.empty())
{
@@ -706,27 +742,11 @@ namespace AppInstaller::Certificates
return true;
}
- // Get the chain for the given leaf certificate
- wil::unique_cert_chain_context chainContext;
-
- char oidPkixKpServerAuth[] = szOID_PKIX_KP_SERVER_AUTH;
- std::array chainUses = {
- oidPkixKpServerAuth,
- };
-
- CERT_CHAIN_PARA chainParameters = {};
- chainParameters.cbSize = sizeof(chainParameters);
- chainParameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
- chainParameters.RequestedUsage.Usage.cUsageIdentifier = static_cast(chainUses.size());
- chainParameters.RequestedUsage.Usage.rgpszUsageIdentifier = chainUses.data();
-
- THROW_IF_WIN32_BOOL_FALSE(CertGetCertificateChain(nullptr, certContext, nullptr, certContext->hCertStore, &chainParameters, CERT_CHAIN_REVOCATION_CHECK_CHAIN, nullptr, &chainContext));
-
bool result = false;
for (const auto& chain : m_configuration)
{
- if (chain->Validate(chainContext.get()))
+ if (chain->Validate(chainContext))
{
AICLI_LOG(Core, Verbose, << "Certificate `" << GetSimpleDisplayName(certContext) << "` accepted by pinning configuration:\n" << chain->GetDescription());
result = true;
@@ -741,12 +761,18 @@ namespace AppInstaller::Certificates
}
else
{
- AICLI_LOG(Core, Error, << "Rejecting certificate [" << GetSimpleDisplayName(certContext) << "] as it did not match anything in pinning configuration [" << m_identifier << "]:\n" << GetDescriptionOfCertChain(chainContext.get()));
+ AICLI_LOG(Core, Error, << "Rejecting certificate [" << GetSimpleDisplayName(certContext) << "] as it did not match anything in pinning configuration [" << m_identifier << "]:\n" << GetDescriptionOfCertChain(chainContext));
}
return result;
}
+ bool PinningConfiguration::Validate(PCCERT_CONTEXT certContext) const
+ {
+ auto chainContext = BuildCertificateChain(certContext);
+ return Validate(certContext, chainContext.get());
+ }
+
// The JSON is expected to look like:
// {
// "Chains":[
diff --git a/src/AppInstallerSharedLib/Public/winget/Certificates.h b/src/AppInstallerSharedLib/Public/winget/Certificates.h
index c2034fa468..443161af2f 100644
--- a/src/AppInstallerSharedLib/Public/winget/Certificates.h
+++ b/src/AppInstallerSharedLib/Public/winget/Certificates.h
@@ -87,6 +87,7 @@ namespace AppInstaller::Certificates
PinningDetails& LoadCertificate(int resource, int resourceType);
PinningDetails& LoadCertificate(const std::vector& certificateBytes);
PinningDetails& LoadCertificate(const std::pair certificateBytes);
+ PinningDetails& LoadCertificate(const BYTE* certData, size_t certSize);
PCCERT_CONTEXT GetCertificate() const { return m_certificateContext.get(); }
PinningDetails& SetPinning(PinningVerificationType type);
@@ -227,9 +228,24 @@ namespace AppInstaller::Certificates
void AddChain(PinningChain chain);
void AddChain(std::shared_ptr chain);
+ // Builds a certificate chain for the given leaf certificate.
+ // Provide a custom chain engine and additional store to use non-system-trusted roots
+ // (e.g., for testing with self-signed certificates).
+ // Pass nullptr for engine to use the system default engine.
+ // Pass nullptr for additionalStore to use only certContext->hCertStore.
+ // Pass 0 for flags to skip revocation checking (e.g., for test certs with no CRL).
+ static wil::unique_cert_chain_context BuildCertificateChain(
+ PCCERT_CONTEXT certContext,
+ HCERTCHAINENGINE engine = nullptr,
+ HCERTSTORE additionalStore = nullptr,
+ DWORD flags = CERT_CHAIN_REVOCATION_CHECK_CHAIN);
+
+ // Validates the given leaf certificate against the configuration using a pre-built chain.
+ // Use BuildCertificateChain to construct the chain, providing a custom engine if needed.
+ bool Validate(PCCERT_CONTEXT certContext, PCCERT_CHAIN_CONTEXT chainContext) const;
+
// Validates the given leaf certificate against the configuration.
- // Returns true to indicate that the certificate meets the pinning configuration criteria.
- // Returns false to indicate the it does not.
+ // Builds the certificate chain internally using the system chain engine.
bool Validate(PCCERT_CONTEXT certContext) const;
// True if no pinning is configured.
diff --git a/src/CertificateResources/CertificateResources.h b/src/CertificateResources/CertificateResources.h
index 67ef17722f..7ebae2c394 100644
--- a/src/CertificateResources/CertificateResources.h
+++ b/src/CertificateResources/CertificateResources.h
@@ -4,11 +4,5 @@
#define CERTIFICATE_RESOURCE_TYPE 400
-#define IDX_CERTIFICATE_STORE_ROOT_1 401
-#define IDX_CERTIFICATE_STORE_INTERMEDIATE_1 402
-#define IDX_CERTIFICATE_STORE_LEAF_1 403
-#define IDX_CERTIFICATE_STORE_ROOT_2 404
-#define IDX_CERTIFICATE_STORE_INTERMEDIATE_2 405
-#define IDX_CERTIFICATE_STORE_LEAF_2 406
-#define IDX_CERTIFICATE_MS_TLS_ECC_ROOT_G2 407
-#define IDX_CERTIFICATE_MS_TLS_RSA_ROOT_G2 408
+#define IDX_CERTIFICATE_MS_TLS_ECC_ROOT_G2 401
+#define IDX_CERTIFICATE_MS_TLS_RSA_ROOT_G2 402
diff --git a/src/CertificateResources/CertificateResources.rc b/src/CertificateResources/CertificateResources.rc
index 8c290de704..1ac6a834d7 100644
--- a/src/CertificateResources/CertificateResources.rc
+++ b/src/CertificateResources/CertificateResources.rc
@@ -64,13 +64,5 @@ END
// Packages schema
//
-IDX_CERTIFICATE_STORE_ROOT_1 CERTIFICATE_RESOURCE_TYPE "StoreRoot1.cer"
-IDX_CERTIFICATE_STORE_INTERMEDIATE_1 CERTIFICATE_RESOURCE_TYPE "StoreIntermediate1.cer"
-IDX_CERTIFICATE_STORE_LEAF_1 CERTIFICATE_RESOURCE_TYPE "StoreLeaf1.cer"
-
-IDX_CERTIFICATE_STORE_ROOT_2 CERTIFICATE_RESOURCE_TYPE "StoreRoot2.cer"
-IDX_CERTIFICATE_STORE_INTERMEDIATE_2 CERTIFICATE_RESOURCE_TYPE "StoreIntermediate2.cer"
-IDX_CERTIFICATE_STORE_LEAF_2 CERTIFICATE_RESOURCE_TYPE "StoreLeaf2.cer"
-
IDX_CERTIFICATE_MS_TLS_ECC_ROOT_G2 CERTIFICATE_RESOURCE_TYPE "Microsoft_TLS_ECC_Root_G2.crt"
IDX_CERTIFICATE_MS_TLS_RSA_ROOT_G2 CERTIFICATE_RESOURCE_TYPE "Microsoft_TLS_RSA_Root_G2.crt"
diff --git a/src/CertificateResources/CertificateResources.vcxitems b/src/CertificateResources/CertificateResources.vcxitems
index 2a532157ee..74f34a4762 100644
--- a/src/CertificateResources/CertificateResources.vcxitems
+++ b/src/CertificateResources/CertificateResources.vcxitems
@@ -16,12 +16,6 @@
-
-
-
-
-
-
diff --git a/src/CertificateResources/CertificateResources.vcxitems.filters b/src/CertificateResources/CertificateResources.vcxitems.filters
index 0352aedb4f..704e0b563a 100644
--- a/src/CertificateResources/CertificateResources.vcxitems.filters
+++ b/src/CertificateResources/CertificateResources.vcxitems.filters
@@ -6,24 +6,6 @@
-
- Certificates
-
-
- Certificates
-
-
- Certificates
-
-
- Certificates
-
-
- Certificates
-
-
- Certificates
-
Certificates
diff --git a/src/CertificateResources/StoreIntermediate1.cer b/src/CertificateResources/StoreIntermediate1.cer
deleted file mode 100644
index ab1eb8067a..0000000000
Binary files a/src/CertificateResources/StoreIntermediate1.cer and /dev/null differ
diff --git a/src/CertificateResources/StoreIntermediate2.cer b/src/CertificateResources/StoreIntermediate2.cer
deleted file mode 100644
index aba393901e..0000000000
Binary files a/src/CertificateResources/StoreIntermediate2.cer and /dev/null differ
diff --git a/src/CertificateResources/StoreLeaf1.cer b/src/CertificateResources/StoreLeaf1.cer
deleted file mode 100644
index 6ea8bdbcb9..0000000000
Binary files a/src/CertificateResources/StoreLeaf1.cer and /dev/null differ
diff --git a/src/CertificateResources/StoreLeaf2.cer b/src/CertificateResources/StoreLeaf2.cer
deleted file mode 100644
index cd603b8ed2..0000000000
Binary files a/src/CertificateResources/StoreLeaf2.cer and /dev/null differ
diff --git a/src/CertificateResources/StoreRoot1.cer b/src/CertificateResources/StoreRoot1.cer
deleted file mode 100644
index 6dda6a38dc..0000000000
Binary files a/src/CertificateResources/StoreRoot1.cer and /dev/null differ
diff --git a/src/CertificateResources/StoreRoot2.cer b/src/CertificateResources/StoreRoot2.cer
deleted file mode 100644
index 6dda6a38dc..0000000000
Binary files a/src/CertificateResources/StoreRoot2.cer and /dev/null differ