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