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:
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)