winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 57907ac7fa681703a4536e3b4deca8e17ee875d1
parent ffab684f4d893e4d4f0bed0d9faa8f9929aa8408
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Wed, 19 Jun 2024 14:35:42 -0700

Use digest verification APIs for MSIX when available (#4564)

## Change
When available (10.0.23504.0 according to MSDN), use the MSIX digest
APIs to verify the package during streaming installation. When not
available, download instead.
Diffstat:
Msrc/AppInstallerCLICore/ExecutionContextData.h | 9++++++++-
Msrc/AppInstallerCLICore/Workflows/DownloadFlow.cpp | 6+++++-
Msrc/AppInstallerCLICore/Workflows/InstallFlow.cpp | 8++++++--
Msrc/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj | 2+-
Msrc/AppInstallerCommonCore/Deployment.cpp | 136++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/AppInstallerCommonCore/MsixInfo.cpp | 18++++++++++++++++++
Msrc/AppInstallerCommonCore/Public/AppInstallerDeployment.h | 22+++++++++++++++++++---
Msrc/AppInstallerCommonCore/Public/AppInstallerMsixInfo.h | 5++++-
Msrc/AppInstallerCommonCore/pch.h | 1+
Msrc/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp | 3+--
10 files changed, 164 insertions(+), 46 deletions(-)

diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h @@ -65,6 +65,7 @@ namespace AppInstaller::CLI::Execution DownloadDirectory, ModifyPath, RepairString, + MsixDigests, Max }; @@ -276,11 +277,17 @@ namespace AppInstaller::CLI::Execution using value_t = std::string; }; - template<> struct DataMapping<Data::RepairString> { using value_t = std::string; }; + + template<> + struct DataMapping<Data::MsixDigests> + { + // The pair is { URL, Digest } + using value_t = std::vector<std::pair<std::string, std::wstring>>; + }; } } diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp @@ -4,6 +4,7 @@ #include "DownloadFlow.h" #include "MSStoreInstallerHandler.h" #include <winget/Filesystem.h> +#include <AppInstallerDeployment.h> #include <AppInstallerDownloader.h> #include <AppInstallerRuntime.h> #include <AppInstallerMsixInfo.h> @@ -229,9 +230,11 @@ namespace AppInstaller::CLI::Workflow // we can just verify signature hash without a full download and do a streaming install. // Even if we have the signature hash, we still do a full download if InstallerDownloadOnly // flag is set, or if we need to use a proxy (as deployment APIs won't use proxy for us). + // Finally, we require the digest API for streaming install as well. if (installer.SignatureSha256.empty() || installerDownloadOnly - || Network().GetProxyUri()) + || Network().GetProxyUri() + || !Deployment::IsExpectedDigestsSupported()) { context << DownloadInstallerFile; } @@ -409,6 +412,7 @@ namespace AppInstaller::CLI::Workflow auto signatureHash = msixInfo.GetSignatureHash(); context.Add<Execution::Data::HashPair>(std::make_pair(installer.SignatureSha256, signatureHash)); + context.Add<Execution::Data::MsixDigests>({ std::make_pair(installer.Url, msixInfo.GetDigest()) }); } catch (...) { diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -189,6 +189,7 @@ namespace AppInstaller::CLI::Workflow void MsixInstall(Execution::Context& context) { std::string uri; + Deployment::Options deploymentOptions; if (context.Contains(Execution::Data::InstallerPath)) { uri = context.Get<Execution::Data::InstallerPath>().u8string(); @@ -196,8 +197,11 @@ namespace AppInstaller::CLI::Workflow else { uri = context.Get<Execution::Data::Installer>()->Url; + deploymentOptions.ExpectedDigests = context.Get<Execution::Data::MsixDigests>(); } + deploymentOptions.SkipReputationCheck = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted); + bool isMachineScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine; // TODO: There was a bug in deployment api if provision api was called in packaged context. @@ -220,11 +224,11 @@ namespace AppInstaller::CLI::Workflow { if (isMachineScope) { - return Deployment::AddPackageMachineScope(uri, callback); + return Deployment::AddPackageMachineScope(uri, deploymentOptions, callback); } else { - return Deployment::AddPackageWithDeferredFallback(uri, WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted), callback); + return Deployment::AddPackageWithDeferredFallback(uri, deploymentOptions, callback); } }); } diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -9,7 +9,7 @@ <ProjectGuid>{5890d6ed-7c3b-40f3-b436-b54f640d9e65}</ProjectGuid> <Keyword>Win32Proj</Keyword> <RootNamespace>AppInstallerLoggingCore</RootNamespace> - <WindowsTargetPlatformVersion>10.0.22000.0</WindowsTargetPlatformVersion> + <WindowsTargetPlatformVersion>10.0.22621.0</WindowsTargetPlatformVersion> <WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion> <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport> <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support> diff --git a/src/AppInstallerCommonCore/Deployment.cpp b/src/AppInstallerCommonCore/Deployment.cpp @@ -66,49 +66,108 @@ namespace AppInstaller::Deployment return S_OK; } + + bool ShouldUseReputationCheck(const Options& options) + { + return options.ExpectedDigests.empty() && !options.SkipReputationCheck; + } + + IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> StartAddPackage(PackageManager& packageManager, const winrt::Windows::Foundation::Uri& uri, const Options& options) + { + if (!options.ExpectedDigests.empty()) + { + // Must use API that supports digests + THROW_WIN32_IF(ERROR_NOT_SUPPORTED, !IsExpectedDigestsSupported()); + + AddPackageOptions addPackageOptions; + + for (const auto& digest : options.ExpectedDigests) + { + addPackageOptions.ExpectedDigests().Insert(Uri{ Utility::ConvertToUTF16(digest.first) }, digest.second); + } + + return packageManager.AddPackageByUriAsync(uri, addPackageOptions); + } + else if (options.SkipReputationCheck) + { + return packageManager.AddPackageAsync( + uri, + nullptr, /*dependencyPackageUris*/ + DeploymentOptions::None, + nullptr, /*targetVolume*/ + nullptr, /*optionalAndRelatedPackageFamilyNames*/ + nullptr, /*optionalPackageUris*/ + nullptr /*relatedPackageUris*/); + } + else + { + return packageManager.RequestAddPackageAsync( + uri, + nullptr, /*dependencyPackageUris*/ + DeploymentOptions::None, + nullptr, /*targetVolume*/ + nullptr, /*optionalAndRelatedPackageFamilyNames*/ + nullptr /*relatedPackageUris*/); + } + } + + IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> StartStagePackage(PackageManager& packageManager, const winrt::Windows::Foundation::Uri& uri, const Options& options) + { + if (!options.ExpectedDigests.empty()) + { + // Must use API that supports digests + THROW_WIN32_IF(ERROR_NOT_SUPPORTED, !IsExpectedDigestsSupported()); + + StagePackageOptions stagePackageOptions; + + for (const auto& digest : options.ExpectedDigests) + { + stagePackageOptions.ExpectedDigests().Insert(Uri{ Utility::ConvertToUTF16(digest.first) }, digest.second); + } + + return packageManager.StagePackageByUriAsync(uri, stagePackageOptions); + } + else + { + return packageManager.StagePackageAsync( + uri, + nullptr /*dependencyPackageUris*/); + } + } + } + + std::ostream& operator<<(std::ostream& out, const Options& options) + { + out << " { SkipReputationCheck = " << options.SkipReputationCheck << ", ExpectedDigests = {"; + + for (const auto& digest : options.ExpectedDigests) + { + out << " { URI = " << digest.first << ", Digest = " << Utility::ConvertToUTF8(digest.second) << " } "; + } + + out << "} }"; + + return out; } void AddPackage( const winrt::Windows::Foundation::Uri& uri, - winrt::Windows::Management::Deployment::DeploymentOptions options, - bool skipSmartScreen, + const Options& options, IProgressCallback& callback) { size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting AddPackage operation #" << id << ": " << Utility::ConvertToUTF8(uri.AbsoluteUri().c_str()) << " SkipSmartScreen: " << skipSmartScreen); + AICLI_LOG(Core, Info, << "Starting AddPackage operation #" << id << ": " << Utility::ConvertToUTF8(uri.AbsoluteUri().c_str()) << " Options: " << options); PackageManager packageManager; - IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> deployOperation; - - if (skipSmartScreen) - { - deployOperation = packageManager.AddPackageAsync( - uri, - nullptr, /*dependencyPackageUris*/ - options, - nullptr, /*targetVolume*/ - nullptr, /*optionalAndRelatedPackageFamilyNames*/ - nullptr, /*optionalPackageUris*/ - nullptr /*relatedPackageUris*/); - } - else - { - deployOperation = packageManager.RequestAddPackageAsync( - uri, - nullptr, /*dependencyPackageUris*/ - options, - nullptr, /*targetVolume*/ - nullptr, /*optionalAndRelatedPackageFamilyNames*/ - nullptr /*relatedPackageUris*/); - } + IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> deployOperation = StartAddPackage(packageManager, uri, options); WaitForDeployment(deployOperation, id, callback); } bool AddPackageWithDeferredFallback( std::string_view uri, - bool skipSmartScreen, + const Options& options, IProgressCallback& callback) { PackageManager packageManager; @@ -129,24 +188,24 @@ namespace AppInstaller::Deployment Uri uriObject(Utility::ConvertToUTF16(uri)); - if (!skipSmartScreen) + if (ShouldUseReputationCheck(options)) { // The only way to get SmartScreen is to use RequestAddPackageAsync, so we will have to start with that. size_t id = GetDeploymentOperationId(); AICLI_LOG(Core, Info, << "Starting RequestAddPackageAsync operation #" << id << ": " << uri); - DeploymentOptions options = DeploymentOptions::None; + DeploymentOptions deploymentOptions = DeploymentOptions::None; // Optimization to keep files if the package is in use. Only available in a newer OS per: // https://docs.microsoft.com/en-us/uwp/api/Windows.Management.Deployment.DeploymentOptions if (Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version{ "10.0.18362.0" })) { - options = DeploymentOptions::RetainFilesOnFailure; + deploymentOptions = DeploymentOptions::RetainFilesOnFailure; } IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> deployOperation = packageManager.RequestAddPackageAsync( uriObject, nullptr, /*dependencyPackageUris*/ - options, + deploymentOptions, nullptr, /*targetVolume*/ nullptr, /*optionalAndRelatedPackageFamilyNames*/ nullptr /*relatedPackageUris*/); @@ -167,9 +226,9 @@ namespace AppInstaller::Deployment progress.SetRange(0, 95); { size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting StagePackageAsync operation #" << id << ": " << uri); + AICLI_LOG(Core, Info, << "Starting StagePackageAsync operation #" << id << ": " << uri << " Options: " << options); - IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> stageOperation = packageManager.StagePackageAsync(uriObject, nullptr); + IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> stageOperation = StartStagePackage(packageManager, uriObject, options); WaitForDeployment(stageOperation, id, progress); } @@ -214,6 +273,7 @@ namespace AppInstaller::Deployment bool AddPackageMachineScope( std::string_view uri, + const Options& options, IProgressCallback& callback) { PackageManager packageManager; @@ -240,9 +300,9 @@ namespace AppInstaller::Deployment progress.SetRange(0, 90); { size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting StagePackageAsync operation #" << id << ": " << uri); + AICLI_LOG(Core, Info, << "Starting StagePackageAsync operation #" << id << ": " << uri << " Options: " << options); - IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> stageOperation = packageManager.StagePackageAsync(uriObject, nullptr); + IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> stageOperation = StartStagePackage(packageManager, uriObject, options); WaitForDeployment(stageOperation, id, progress); } @@ -338,4 +398,10 @@ namespace AppInstaller::Deployment WaitForDeployment(deployOperation, id, callback); } + + bool IsExpectedDigestsSupported() + { + static bool s_IsExpectedDigestsSupported = Metadata::ApiInformation::IsPropertyPresent(winrt::name_of<AddPackageOptions>(), L"ExpectedDigests"); + return s_IsExpectedDigestsSupported; + } } diff --git a/src/AppInstallerCommonCore/MsixInfo.cpp b/src/AppInstallerCommonCore/MsixInfo.cpp @@ -625,6 +625,24 @@ namespace AppInstaller::Msix return Utility::SHA256::ComputeHash(signature.data(), static_cast<uint32_t>(signature.size())); } + std::wstring MsixInfo::GetDigest() + { + ComPtr<IAppxDigestProvider> digestProvider; + if (m_isBundle) + { + THROW_IF_FAILED(m_bundleReader.As(&digestProvider)); + } + else + { + THROW_IF_FAILED(m_packageReader.As(&digestProvider)); + } + + wil::unique_cotaskmem_string result; + THROW_IF_FAILED(digestProvider->GetDigest(&result)); + + return result.get(); + } + std::wstring MsixInfo::GetPackageFullNameWide() { ComPtr<IAppxManifestPackageId> packageId; diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h b/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h @@ -7,12 +7,24 @@ namespace AppInstaller::Deployment { + // A set of optional values useful across many of the deployment functions. + struct Options + { + Options() = default; + explicit Options(bool skipReputationCheck) : SkipReputationCheck(skipReputationCheck) {} + + // Avoid using APIs that make a reputation check. + bool SkipReputationCheck = false; + + // The pairs of URI+Digest to enforce. + std::vector<std::pair<std::string, std::wstring>> ExpectedDigests; + }; + // Calls winrt::Windows::Management::Deployment::PackageManager::AddPackageAsync if skipSmartScreen is true, // Otherwise, calls winrt::Windows::Management::Deployment::PackageManager::RequestAddPackageAsync void AddPackage( const winrt::Windows::Foundation::Uri& uri, - winrt::Windows::Management::Deployment::DeploymentOptions options, - bool skipSmartScreen, + const Options& options, IProgressCallback& callback); // Calls winrt::Windows::Management::Deployment::PackageManager::AddPackageAsync if skipSmartScreen is true, @@ -22,7 +34,7 @@ namespace AppInstaller::Deployment // Returns true if the registration was deferred; false if not. bool AddPackageWithDeferredFallback( std::string_view uri, - bool skipSmartScreen, + const Options& options, IProgressCallback& callback); // Calls winrt::Windows::Management::Deployment::PackageManager::RemovePackageAsync @@ -36,6 +48,7 @@ namespace AppInstaller::Deployment // winrt::Windows::Management::Deployment::PackageManager::RegisterPackageByFullNameAsync if not running as system bool AddPackageMachineScope( std::string_view uri, + const Options& options, IProgressCallback& callback); // Calls winrt::Windows::Management::Deployment::PackageManager::DeprovisionPackageForAllUsersAsync @@ -52,4 +65,7 @@ namespace AppInstaller::Deployment void RegisterPackage( std::string_view packageFamilyName, IProgressCallback& callback); + + // Determines if the ExpectedDigests property (and thus feture) is supported on the current version of Windows. + bool IsExpectedDigestsSupported(); } diff --git a/src/AppInstallerCommonCore/Public/AppInstallerMsixInfo.h b/src/AppInstallerCommonCore/Public/AppInstallerMsixInfo.h @@ -73,9 +73,12 @@ namespace AppInstaller::Msix // If skipP7xFileId is true, returns content of converted .p7s std::vector<byte> GetSignature(bool skipP7xFileId = false); - // Get the signature sha256 hash. + // Gets the signature sha256 hash. Utility::SHA256::HashBuffer GetSignatureHash(); + // Gets the digest of the package. + std::wstring GetDigest(); + // Gets the package full name. std::wstring GetPackageFullNameWide(); std::string GetPackageFullName(); diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h @@ -86,6 +86,7 @@ #include <winrt/Windows.ApplicationModel.AppExtensions.h> #include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.Collections.h> +#include <winrt/Windows.Foundation.Metadata.h> #include <winrt/Windows.Management.Deployment.h> #include <winrt/Windows.Security.Cryptography.h> #include <winrt/Windows.Services.Store.h> diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp @@ -527,8 +527,7 @@ namespace AppInstaller::Repository::Microsoft winrt::Windows::Foundation::Uri uri = winrt::Windows::Foundation::Uri(localFile.c_str()); Deployment::AddPackage( uri, - winrt::Windows::Management::Deployment::DeploymentOptions::None, - WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::Trusted), + Deployment::Options{ WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::Trusted) }, progress); if (download)