From e40f21bcc4f8130de5b96ed6ce2fa9e901ae595e Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Tue, 9 Jun 2026 15:29:56 -0700 Subject: [PATCH 1/2] Remove old Store certs, replace test use with generated ones --- .../AppInstallerCLITests.vcxproj | 17 ++ .../AppInstallerCLITests.vcxproj.filters | 21 ++ src/AppInstallerCLITests/Certificates.cpp | 252 +++++++++--------- src/AppInstallerCLITests/GroupPolicy.cpp | 24 +- src/AppInstallerCLITests/HttpClientHelper.cpp | 13 +- src/AppInstallerCLITests/TestCertificates.cpp | 70 +++++ src/AppInstallerCLITests/TestCertificates.h | 67 +++++ .../TestData/TestIntermediate1.cer | Bin 0 -> 814 bytes .../TestData/TestIntermediate2.cer | Bin 0 -> 814 bytes .../TestData/TestLeaf1.cer | Bin 0 -> 798 bytes .../TestData/TestLeaf2.cer | Bin 0 -> 798 bytes .../TestData/TestRoot.cer | Bin 0 -> 772 bytes src/AppInstallerRepositoryCore/SourceList.cpp | 28 +- src/AppInstallerSharedLib/Certificates.cpp | 64 +++-- .../Public/winget/Certificates.h | 20 +- .../CertificateResources.h | 10 +- .../CertificateResources.rc | 8 - .../CertificateResources.vcxitems | 6 - .../CertificateResources.vcxitems.filters | 18 -- .../StoreIntermediate1.cer | Bin 864 -> 0 bytes .../StoreIntermediate2.cer | Bin 865 -> 0 bytes src/CertificateResources/StoreLeaf1.cer | Bin 2038 -> 0 bytes src/CertificateResources/StoreLeaf2.cer | Bin 2072 -> 0 bytes src/CertificateResources/StoreRoot1.cer | Bin 579 -> 0 bytes src/CertificateResources/StoreRoot2.cer | Bin 579 -> 0 bytes 25 files changed, 395 insertions(+), 223 deletions(-) create mode 100644 src/AppInstallerCLITests/TestCertificates.cpp create mode 100644 src/AppInstallerCLITests/TestCertificates.h create mode 100644 src/AppInstallerCLITests/TestData/TestIntermediate1.cer create mode 100644 src/AppInstallerCLITests/TestData/TestIntermediate2.cer create mode 100644 src/AppInstallerCLITests/TestData/TestLeaf1.cer create mode 100644 src/AppInstallerCLITests/TestData/TestLeaf2.cer create mode 100644 src/AppInstallerCLITests/TestData/TestRoot.cer delete mode 100644 src/CertificateResources/StoreIntermediate1.cer delete mode 100644 src/CertificateResources/StoreIntermediate2.cer delete mode 100644 src/CertificateResources/StoreLeaf1.cer delete mode 100644 src/CertificateResources/StoreLeaf2.cer delete mode 100644 src/CertificateResources/StoreRoot1.cer delete mode 100644 src/CertificateResources/StoreRoot2.cer 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 0000000000000000000000000000000000000000..5cc702440e33accfd7aeed6e70b32c14fefa0a22 GIT binary patch literal 814 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 MN#+AfjQf`W0O`t0;{X5v literal 0 HcmV?d00001 diff --git a/src/AppInstallerCLITests/TestData/TestLeaf2.cer b/src/AppInstallerCLITests/TestData/TestLeaf2.cer new file mode 100644 index 0000000000000000000000000000000000000000..d30d41411d40715a042b695339192e612ed4518c GIT binary patch literal 798 zcmXqLVwN&!Vq#jr%*4pVB#_tVd#EQwwacekwsYsv9r^~mY@Awc9&O)w85y}*84Q#R z6%6Fqm_u2ZdBnpr^W0NQLQ;!MJo8FYi*i#_G80QujSLjTd5z2r%nU4z3`~qo&7vgu zjSS480;V7V11UoZ12Kpx{0LKgQWMjR44N2~kS%3oWngY%~ zvGDR!e}1m4kd{9IKZ}0J7EH)^cdPN~&MQq@W7ash#_5EVI(=PxA)7}zyr{E9&U|Ke zq*_=dgYF*tqtbU$r&n=RJj&-?Xi&7|IH&zOm4AB{GdOOv`t{NJ(84cX*@>qXzfnAK z{O5(u*916OALl<_k$z|89gDl0%{NVT4ZUBIVzy_iyu*p^={}CLeAv%d*{?9!efs)> zNT$p2M{4ioe6Mhh&tA-s_VHXs!^Z$`Q?uO(eLSHr6Iwsp%I==jba5r$Cbx~Xe3FNX zW=sft#{B5Ru`0F|--I|9ah+Zj-mkztdB;4amjaus`d)MRCklL%pX{;O+bYT`XvqYH zi&A_~nV1gjQ9T?TXP-kSwD7orv zrIx(C=$GH|_$~42Wt@K(c<&J3A@J$ogqP>t-p1Edaoe$^**Z11tP|PzHTYfFgFO>n zWLQ3Ky?g!O(!z(ccJKJG;*9*|S*QQjUS24_PA%lw8T(B)Z2w7RcC45(|Fdj`#ir5# zu1|JzE8D^oUZ)>8o7IwKt@W~$&xTz6h_y1>yC!}j{ zQD2>M&A9Qp?D^D#Aua!3&y~BVYg?~#rnYHrmCRh+rzxJ?YeQzFf}$c zjKZh6iBSpJDn?cY<|amdkjuE3niv@wmZr_!mONMNih8Z+<;(-=T>AR2I6Qy8i<%cw z{DP}H+wr(wce}Ue&!hVN|K5FE zR`OtWuwCs#xnqyrW4(??bszuzWyQm954n#!)ji#i^R_Zjp7l6i)8zm5hje<@{1*AT zy8UjoW2;j1^?lnebE&@AmSC{7Ed3<=CU40@Kk9q>w=$XrXgyr9I3S;&bAMLgixaz; zb#7;#Pp`Tn?{=vAmQ`1wAgkspH)-~H5zn907CruVY|_`0`eidVhj!F5w>b+%J*n6h zUc2_@v#i&fTMYY_xWy=`7fhS5D!W~s_5PD{MFLJm5fho185tNCI~h0_@Bu?#mYr zU??*(WNe-1z4uu}?A;|LIhn?`Z_kCYU&{__o-<30lWWTRn5|_eI70q8Es%XNKkH!n zp2}e1-hEw-!VBtuZLqi5oxA1u0&A7n;Xxto#%4af3qjZZMtCQg!szGr^P2QcdLjk-usDrd)&Q>VhPo+Tt!U` zmS0&c-$k#{;;9M0QPEWPrtX1(X+|B_RLPk(0jp2myxwB^fAb1+#rX$L@yIiI#QTXj SZ+<@aLoadCertificate(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 ab1eb8067abf672b56042c252062b39276fa6dfe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 864 zcmXqLVvaFrVtTxQnTe5!Nr1`H*5f*pQ&i0s83t~?>!%F3*f_M>JkHs&Ff$t@8gd(O zvN4CUun9AT1{;bR2!l9W!aOdS>6y-{MI{QJdC7*72I3%LZefA)@^ZZtpj0wYN-sG- z*HFPg4kX1aEDqPlHlCWfhjRw(j5D;hKMV!!5_;ypPvBWpqDFg$4Y$vV3t9W0FA=W1N8y(6zF{jEw(T zI1Jc;6cZzZfh2?cWEo@_sKEFJjBOGbB_#z``uh3F#RVuaYv2G^#nfg3Rh66wiuhs_h5E3Fhr~N5 z+TD%yl8Z_VoIuW$XR$W0G%#Oay1=;25N1qKjxkYYmv2OC>@BM&1JBb>#A zoR*o>84OaG45Ds(m#c2Ob9jxY-ht}LCnrDf77cu;;j;I{eU-rC{U?9;3Qyd3@a_9r zoo^ny6l8z@U@}m&<31s_i(%yrov=vh-N$U==N)0%vg*%-t1(s=79GsGtn-u0Ew;2} OUFyWDU00=!DFFZlTMJ77 diff --git a/src/CertificateResources/StoreIntermediate2.cer b/src/CertificateResources/StoreIntermediate2.cer deleted file mode 100644 index aba393901eb39724e0f85390b37b7120d65e5933..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 865 zcmXqLVvaRvVtTxQnTe5!Nq}Eow6y8$S>s~wJMm|yNEsV&v2kd%d7QIlVP-Z+G~_nm zWMd9xVH0Kw4K@@t5C(C$gn3*t(=(k@i%Jwc^O6lE4a7mh+`h)QK&fP)lwNXv zuAzc~97u{;SRAgyJtsdYF-IXNKfgr5-Pk}*oY%($q4_ zAQsgLat1OGCy4rHCKu%w=ckn@IOi7?KW)jEL26vJ64qzr7E~OJ1d0v z1S@zJ7nf${r7Ji)Dj1j>lo<%Hv4j1~#K^{~-N?eA#GJ&y@?uhcqFEu6lkL*n$+lhR zR!%iCTgP!b;w7Kna@CD#a_=OIuHKK>xMF?qtq%?yjwi%F?hs6r}S+ux`G0C8bG0s2;=vrA}M#ldv z90qJaiiwfIKo%s%$0EieayV{{L)if~-^bchW*Q_Eo6OKW7HA+3l2&GsFc51H*?ik$ z$%$OJR_K#nWVYL`OhRtq<#RD$qZ~KFz2oIMB#0`+LlzS@}+WKsj;Af*5w&RdqL;z>a8ye+;{!mp~L{pL2Q!j$trIA%>m;EDIM% zKvsq#1O_<`FUhzCl+HpkbRJ7+Xp5P)XjzIjDP%C(>Bl6IPyOoBA`3~((rAdx`5DAq zhAE;<2127I;8YkAFeXL?6>yqDsZgk5-sH_#-c-6xXc*zU|4^2flQuW%W4_YQcUScS>eE5WIDPKW5pA(n*T%9^8H zZTD{2D$k#H9JmqNqey58%6#ws?SfAqZcXpp5qef0R90=Qy0WmnWpS2b+812xiG62o z?>1J7+Eh}{c|3-k$3KummkYe62v8h7zYO32=g)omf+fBf;mVrXLXmtxoZiwIK+OA+w+cgod*}lYNC*zS~wOC)RGB zVm#&ZpyJ`qn)K>i#o@V_2oR#JT{FcUxe=k)Y8EoT)_v0^UGgZ9zQ0JkZXNq&<2?mZ zgy?;b867X)YT|RH;$lsK$$*$3JGU-v|IGM= zq|%O>?%ZDM!Nf&bDx=S)s@D#U%ecB15XDF5tU2BNO1duYw|6po0(N;XK-Q+#j48=e zpPaYtfnh&VjJ$5_s4xD${PNYLJw?Yhhey;lP~RTq+P2TFuPpnhS&H`4!gd$!Y&Z){ zL~6tQkN@!J#ZL_E1)_HOy`{FU4LutK$U}8SLiUyQFDU_$7=V6&iaScH6YG1eF?VI! zXd=8f{j;0wp7wLe6{%z3B!Q3bU12O|65ZDd* zENhOJ%dJ-H& z3}rCMbQYEm+r2x8+9$e6X%k8781pb<=MW6$H4;1)_s9MA&A2Z1d_N^>jbp9-WcA6( zpU>Nr<`P}74~jmiD~XwonGjG%}iSE!K}784)oEAvK%Hkx^5Ip`@dcP8tjz4LI68!i5jHWTXtM z<#%ZqnsF{{D9A_}w7O-(Fi|66p0QqlnWbc|@P{oD$UCHzi5<~7(MT1OEM;^Z%g70g z(CJP6b4NK0=y& z9{R8HQ{C$Jjmf{Z>qO_<*UWdWPj4A34D6RrB<>IW21qgRq=ydvxndLo^MC@WZocI2 zwQ{y#Wfek~nnN4UtwdK4x;gmj=+Ef^UD?`NVcc!*DPP{6PYFrS1G_bO@lg3Or|qN_gabqX)yca-FMHuzk7b? zo_l`x0v8$qoS?;vV{)nbE`j4vk@gadBk?kV z6k}zjX)=)b454^(5v_MHEMs)aa~X%7ap;^hV@p#)s&5__CqAE7zgy#=6wrP)6+6Cr>DJJWpp% zw8u0L$krxLx|E%=izDNJBW1v$t4LuAi4y3gdW^sb4z=-L6>3Imkgz424&UF~J})CW zb*rHJ!N=D={bug+^u(*N?TMYb6N_T`Guk{mCuAFf5ICZNJWQ_2|E>Jis+fN?Wp{;- z2Xgn^(f0kzh?Zxr`1R|86ulyxMofA<@pe zk@paBA%^fCOY5ZDZ68Q>VoKz2#~ZH*ev)M-bqqYYh=0HG^u0maP?euH9~NM8rn=#; zGuca|Te+JXj_lr4Qqm(k*_Gv)Roxf)_s*&7PGYghp~X|ztav@Marea!Kj~j@D(~pm zZnz6)9v6S)c~6y-FGOmQ#g>b^Tt^$PUC%jC)3Yr;VM`l*@HqEFN5QV9l`Y*0bdZy81JF*B>_G+53vnoC1aI&grT1rd@znb_zuP46c6~(s!P4T^4HQ&Gt zzESXB9ZQ>RO1;C$haK1*pq|(ODPyA;J!>CD>>C0pFB54P5lKW`nDj(i(Q8v{9<4hj zA+vki&NWN?x|2>o(WeA~V}b_YuxY*p;RJ`tc|*b_=nXa{>tq}hWiU}jgVMm(DXpWZ zxL6DAqMWqF4+;*6wmWGBZF3Gf8yPA$g`W&Mkvu})3JYb@>FX7&Q)l9Do>5NXf?-Et z;zweFI~waw;gW_WL%}#`K9JxWP8klg+o>_Z38TQlUCM`{f+JE(pC45zrRT5fu|kyk z{jrAX;Lc>VcIz0Y4kBNvh<(L_0RmM%D~8a64wiW(S(tPIXm1d_ELP^r(7V&Q?#Q1pP zi71F{h&cXE=c}$`WYP7MOKqIB@&SkF+_@E&wA+a_ZK%KKw)4iEUINb z%g;Gl7G89Ff=YCLwz`UQ8IuxDpXl(U3)f``QX`sk%9}b1d%x`YB`FR|L)t~Kaqr{< tb*1hUUGkMX{gs^llyygE>HI}%Ebg^w+mp_EiUr4^=0u+{j(Moh`wvEwv(Eqk diff --git a/src/CertificateResources/StoreRoot1.cer b/src/CertificateResources/StoreRoot1.cer deleted file mode 100644 index 6dda6a38dc3d4d361acd02e4b52d4759d26ca563..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 579 zcmXqLVzM`AVm!KlnTe5!Nq{vpY|p2-C8nl_m+)U*?OJ5O#m1r4=5fxJg_+qP(U9AK zlZ`o)g-w_#G}utoKp4c~66SHqOwV*qEh~a|;WUmzV3M0HuD260TGTzWX6 z%s_yR9qeZ&MmARMMivGo<|GD|yUTCx?%Kiqo4a1|(uaNfia#62O6@Uub??~hJr|~a z`*7w0_pZd~K)1Siy7S-lCG&{CVK4Z4zD3WWdCm)a$6V{RmzRh{npMnYc77)yoKtjf zUB~i;*;}2@rQcEh&n&a}k=^gdpVegNSN3)(r*KAf72LSq%!wCQY diff --git a/src/CertificateResources/StoreRoot2.cer b/src/CertificateResources/StoreRoot2.cer deleted file mode 100644 index 6dda6a38dc3d4d361acd02e4b52d4759d26ca563..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 579 zcmXqLVzM`AVm!KlnTe5!Nq{vpY|p2-C8nl_m+)U*?OJ5O#m1r4=5fxJg_+qP(U9AK zlZ`o)g-w_#G}utoKp4c~66SHqOwV*qEh~a|;WUmzV3M0HuD260TGTzWX6 z%s_yR9qeZ&MmARMMivGo<|GD|yUTCx?%Kiqo4a1|(uaNfia#62O6@Uub??~hJr|~a z`*7w0_pZd~K)1Siy7S-lCG&{CVK4Z4zD3WWdCm)a$6V{RmzRh{npMnYc77)yoKtjf zUB~i;*;}2@rQcEh&n&a}k=^gdpVegNSN3)(r*KAf72LSq%!wCQY From 22179d4e426828c8bf8c7db29e0664323020f844 Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Tue, 9 Jun 2026 15:49:31 -0700 Subject: [PATCH 2/2] spelling --- .github/actions/spelling/allow.txt | 2 ++ 1 file changed, 2 insertions(+) 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