diff --git a/dev/Common/IsWindowsVersion.h b/dev/Common/IsWindowsVersion.h index f7fe5ee17c..efabbdce0c 100644 --- a/dev/Common/IsWindowsVersion.h +++ b/dev/Common/IsWindowsVersion.h @@ -1,10 +1,12 @@ -// Copyright (c) Microsoft Corporation and Contributors. +// Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. #ifndef __ISWINDOWSVERSION_H #define __ISWINDOWSVERSION_H #include +#include +#include namespace WindowsVersion { @@ -62,6 +64,12 @@ inline bool IsWindows11_24H2OrGreater() // MsixIsPackageFeatureSupported() added to in Windows 11 24H2 (aka NTDDI_WIN11_GE) return IsExportPresent(L"appxdeploymentclient.dll", "MsixIsPackageFeatureSupported"); } + +inline bool SupportsIAppxFactory4() +{ + return wil::CoCreateInstanceNoThrow().get() != nullptr; +} + } #endif // __ISWINDOWSVERSION_H diff --git a/dev/Common/TerminalVelocityFeatures-PackageManager.h b/dev/Common/TerminalVelocityFeatures-PackageManager.h index 99edf97bbd..887cb6bc0c 100644 --- a/dev/Common/TerminalVelocityFeatures-PackageManager.h +++ b/dev/Common/TerminalVelocityFeatures-PackageManager.h @@ -1,20 +1,22 @@ -// Copyright (c) Microsoft Corporation and Contributors. +// Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. // THIS FILE IS AUTOMATICALLY GENERATED; DO NOT EDIT IT // INPUT FILE: dev\common\TerminalVelocityFeatures-PackageManager.xml -// OPTIONS: -Channel Experimental -Language C++ -Namespace Microsoft.Windows.Management.Deployment -Path dev\common\TerminalVelocityFeatures-PackageManager.xml -Output dev\common\TerminalVelocityFeatures-PackageManager.h +// OPTIONS: -Channel Experimental -Language C++ -Namespace Microsoft::Windows::Management::Deployment -Path dev\common\TerminalVelocityFeatures-PackageManager.xml -Output dev\common #if defined(__midlrt) namespace features { feature_name Feature_PackageManager = { DisabledByDefault, FALSE }; + feature_name Feature_PackageValidation = { DisabledByDefault, FALSE }; } #endif // defined(__midlrt) // Feature constants #define WINDOWSAPPRUNTIME_MICROSOFT_WINDOWS_MANAGEMENT_DEPLOYMENT_FEATURE_PACKAGEMANAGER_ENABLED 1 +#define WINDOWSAPPRUNTIME_MICROSOFT_WINDOWS_MANAGEMENT_DEPLOYMENT_FEATURE_PACKAGEVALIDATION_ENABLED 1 #if defined(__cplusplus) @@ -27,6 +29,12 @@ struct Feature_PackageManager static constexpr bool IsEnabled() { return WINDOWSAPPRUNTIME_MICROSOFT_WINDOWS_MANAGEMENT_DEPLOYMENT_FEATURE_PACKAGEMANAGER_ENABLED == 1; } }; -} // namespace Microsoft.Windows.Management.Deployment +__pragma(detect_mismatch("ODR_violation_WINDOWSAPPRUNTIME_MICROSOFT_WINDOWS_MANAGEMENT_DEPLOYMENT_FEATURE_PACKAGEVALIDATION_ENABLED_mismatch", "AlwaysEnabled")) +struct Feature_PackageValidation +{ + static constexpr bool IsEnabled() { return WINDOWSAPPRUNTIME_MICROSOFT_WINDOWS_MANAGEMENT_DEPLOYMENT_FEATURE_PACKAGEVALIDATION_ENABLED == 1; } +}; + +} // namespace Microsoft::Windows::Management::Deployment #endif // defined(__cplusplus) diff --git a/dev/Common/TerminalVelocityFeatures-PackageManager.xml b/dev/Common/TerminalVelocityFeatures-PackageManager.xml index 932aa39394..4fb6a2ed11 100644 --- a/dev/Common/TerminalVelocityFeatures-PackageManager.xml +++ b/dev/Common/TerminalVelocityFeatures-PackageManager.xml @@ -17,4 +17,15 @@ Stable + + + Feature_PackageValidation + PackageValidation support in PackageDeploymentManager + AlwaysEnabled + + Preview + Stable + + + diff --git a/dev/PackageManager/API/AppxPackagingObject.h b/dev/PackageManager/API/AppxPackagingObject.h new file mode 100644 index 0000000000..778670f224 --- /dev/null +++ b/dev/PackageManager/API/AppxPackagingObject.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +namespace winrt::Microsoft::Windows::Management::Deployment::implementation +{ + struct AppxPackagingObject : winrt::implements + { + AppxPackagingObject(IUnknown* object) + { + m_object.copy_from(object); + } + + int32_t query_interface_tearoff(winrt::guid const& id, void** object) const noexcept override + { + return m_object.as(id, object); + } + + private: + winrt::com_ptr m_object; + }; + +} diff --git a/dev/PackageManager/API/M.W.M.D.AddPackageOptions.cpp b/dev/PackageManager/API/M.W.M.D.AddPackageOptions.cpp index fb95f820a7..eba37f7940 100644 --- a/dev/PackageManager/API/M.W.M.D.AddPackageOptions.cpp +++ b/dev/PackageManager/API/M.W.M.D.AddPackageOptions.cpp @@ -4,7 +4,9 @@ #include "pch.h" #include +#include +#include "M.W.M.D.PackageValidationEventSource.h" #include "M.W.M.D.AddPackageOptions.h" #include "Microsoft.Windows.Management.Deployment.AddPackageOptions.g.cpp" @@ -172,4 +174,40 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation { m_limitToExistingPackages = value; } + + bool AddPackageOptions::IsPackageValidationSupported() + { + return WindowsVersion::SupportsIAppxFactory4(); + } + + winrt::Microsoft::Windows::Management::Deployment::PackageValidationEventSource AddPackageOptions::GetValidationEventSourceForUri(winrt::Windows::Foundation::Uri const& uri) + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Management::Deployment::Feature_PackageValidation::IsEnabled()); + + if (!m_packageValidators) + { + m_packageValidators = winrt::single_threaded_map(); + } + if (!m_packageValidators.HasKey(uri)) + { + m_packageValidators.Insert(uri, winrt::make()); + } + + return m_packageValidators.Lookup(uri); + } + + winrt::Windows::Foundation::Collections::IMapView AddPackageOptions::PackageValidators() + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Management::Deployment::Feature_PackageValidation::IsEnabled()); + + if (!m_packageValidators) + { + // return an empty view + return winrt::single_threaded_map().GetView(); + } + else + { + return m_packageValidators.GetView(); + } + } } diff --git a/dev/PackageManager/API/M.W.M.D.AddPackageOptions.h b/dev/PackageManager/API/M.W.M.D.AddPackageOptions.h index 7ad3423f6b..701e8847ff 100644 --- a/dev/PackageManager/API/M.W.M.D.AddPackageOptions.h +++ b/dev/PackageManager/API/M.W.M.D.AddPackageOptions.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation and Contributors. +// Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. #pragma once @@ -46,6 +46,9 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation bool IsLimitToExistingPackagesSupported(); bool LimitToExistingPackages(); void LimitToExistingPackages(bool value); + bool IsPackageValidationSupported(); + winrt::Microsoft::Windows::Management::Deployment::PackageValidationEventSource GetValidationEventSourceForUri(winrt::Windows::Foundation::Uri const& uri); + winrt::Windows::Foundation::Collections::IMapView PackageValidators(); private: winrt::Microsoft::Windows::Management::Deployment::PackageVolume m_targetVolume{ nullptr }; @@ -67,6 +70,7 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation bool m_deferRegistrationWhenPackagesAreInUse{}; winrt::Windows::Foundation::Collections::IMap m_expectedDigests; bool m_limitToExistingPackages{}; + winrt::Windows::Foundation::Collections::IMap m_packageValidators; }; } namespace winrt::Microsoft::Windows::Management::Deployment::factory_implementation diff --git a/dev/PackageManager/API/M.W.M.D.PackageCertificateEkuValidator.cpp b/dev/PackageManager/API/M.W.M.D.PackageCertificateEkuValidator.cpp new file mode 100644 index 0000000000..b469b5f35b --- /dev/null +++ b/dev/PackageManager/API/M.W.M.D.PackageCertificateEkuValidator.cpp @@ -0,0 +1,189 @@ +#include "pch.h" +#include "M.W.M.D.PackageCertificateEkuValidator.h" +#include "Microsoft.Windows.Management.Deployment.PackageCertificateEkuValidator.g.cpp" +#include + +namespace winrt::Microsoft::Windows::Management::Deployment::implementation +{ + PackageCertificateEkuValidator::PackageCertificateEkuValidator(hstring const& expectedCertificateEku) + { + m_expectedEku = expectedCertificateEku; + } + + bool PackageCertificateEkuValidator::IsPackageValid(winrt::Windows::Foundation::IInspectable const& appxPackagingObject) + { + winrt::com_ptr packageReader; + winrt::com_ptr bundleReader; + + if (SUCCEEDED(appxPackagingObject.as(IID_PPV_ARGS(&packageReader)))) + { + winrt::com_ptr signatureFile; + if (SUCCEEDED(packageReader->GetFootprintFile(APPX_FOOTPRINT_FILE_TYPE_SIGNATURE, signatureFile.put()))) + { + return CheckSignature(signatureFile.get()); + } + else + { + return false; // package is not valid because there is no signature present + } + } + else if (SUCCEEDED(appxPackagingObject.as(IID_PPV_ARGS(&bundleReader)))) + { + winrt::com_ptr signatureFile; + if (SUCCEEDED(bundleReader->GetFootprintFile(APPX_BUNDLE_FOOTPRINT_FILE_TYPE_SIGNATURE, signatureFile.put()))) + { + return CheckSignature(signatureFile.get()); + } + else + { + return false; // package is not valid because there is no signature present + } + } + else + { + THROW_WIN32(ERROR_NOT_SUPPORTED); + } + } + + bool PackageCertificateEkuValidator::CheckSignature(IAppxFile* signatureFile) + { + winrt::com_ptr signatureStream; + THROW_IF_FAILED(signatureFile->GetStream(signatureStream.put())); + + // The p7x signature should have a leading 4-byte PKCX header + static const DWORD P7xFileId = 0x58434b50; // PKCX + static const DWORD P7xFileIdSize = sizeof(P7xFileId); + + STATSTG streamStats{}; + THROW_IF_FAILED(signatureStream->Stat(&streamStats, STATFLAG_NONAME)); + if (streamStats.cbSize.HighPart > 0 || streamStats.cbSize.LowPart < P7xFileIdSize) + { + return false; // The signature has unexpected size + } + + DWORD streamSize = streamStats.cbSize.LowPart; + auto streamBuffer{ wil::make_unique_nothrow(streamSize) }; + THROW_IF_NULL_ALLOC(streamBuffer); + + ULONG bytesRead{}; + THROW_IF_FAILED(signatureStream->Read(streamBuffer.get(), streamSize, &bytesRead)); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_BAD_FORMAT), bytesRead != streamSize); + + if (*reinterpret_cast(streamBuffer.get()) != P7xFileId) + { + return false; // The signature does not have expected header + } + + CRYPT_DATA_BLOB signatureBlob{}; + signatureBlob.cbData = streamSize - P7xFileIdSize; + signatureBlob.pbData = streamBuffer.get() + P7xFileIdSize; + + wil::unique_hcertstore certStore; + wil::unique_hcryptmsg signedMessage; + DWORD queryContentType = 0; + DWORD queryFormatType = 0; + if (!CryptQueryObject( + CERT_QUERY_OBJECT_BLOB, + &signatureBlob, + CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED, + CERT_QUERY_FORMAT_FLAG_BINARY, + 0, // Reserved parameter + NULL, // No encoding info needed + &queryContentType, + &queryFormatType, + &certStore, + &signedMessage, + NULL // No additional params for signed message output + )) + { + return false; // CryptQueryObject could not read the signature + } + + if (queryContentType != CERT_QUERY_CONTENT_PKCS7_SIGNED || queryFormatType != CERT_QUERY_FORMAT_BINARY || !certStore || !signedMessage) + { + return false; // CryptQueryObject returned unexpected data + } + + CMSG_SIGNER_INFO* signerInfo = NULL; + DWORD signerInfoSize = 0; + if (!CryptMsgGetParam(signedMessage.get(), CMSG_SIGNER_INFO_PARAM, 0, NULL, &signerInfoSize)) + { + return false; // CryptMsgGetParam could not read the signature + } + + if (signerInfoSize == 0 || signerInfoSize >= STRSAFE_MAX_CCH) + { + return false; // Signer info has unexpected size + } + + auto signerInfoBuffer{ wil::make_unique_nothrow(signerInfoSize) }; + THROW_IF_NULL_ALLOC(signerInfoBuffer); + signerInfo = reinterpret_cast(signerInfoBuffer.get()); + if (!CryptMsgGetParam(signedMessage.get(), CMSG_SIGNER_INFO_PARAM, 0, signerInfo, &signerInfoSize)) + { + return false; // CryptMsgGetParam could not read the signature + } + + // Get the signing certificate from the certificate store based on the issuer and serial number of the signer info + CERT_INFO certInfo; + certInfo.Issuer = signerInfo->Issuer; + certInfo.SerialNumber = signerInfo->SerialNumber; + + wil::unique_cert_context certContext{ + CertGetSubjectCertificateFromStore( + certStore.get(), + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + &certInfo) + }; + if (!certContext) + { + return false; // CertGetSubjectCertificateFromStore could not find a cert context + } + + DWORD ekuBufferSize = 0; + if (!CertGetEnhancedKeyUsage( + certContext.get(), + 0, // Accept EKU from EKU extension or EKU extended properties + NULL, + &ekuBufferSize)) + { + return false; // CertGetEnhancedKeyUsage could not read EKUs + } + + if (ekuBufferSize < sizeof(CERT_ENHKEY_USAGE)) + { + return false; // No EKUs are found in the signature + } + + auto ekuBuffer{ wil::make_unique_nothrow(ekuBufferSize)}; + THROW_IF_NULL_ALLOC(ekuBuffer); + PCERT_ENHKEY_USAGE ekusFound = reinterpret_cast(ekuBuffer.get()); + if (!CertGetEnhancedKeyUsage( + certContext.get(), + 0, // Accept EKU from EKU extension or EKU extended properties + ekusFound, + &ekuBufferSize)) + { + return false; // CertGetEnhancedKeyUsage could not read EKUs + } + + for (DWORD i = 0; i < ekusFound->cUsageIdentifier; i++) + { + auto eku{ ekusFound->rgpszUsageIdentifier[i] }; + + int length = MultiByteToWideChar(CP_ACP, 0, eku, -1, nullptr, 0); + auto ekuWcharBuffer{ wil::make_unique_nothrow(length) }; + THROW_IF_NULL_ALLOC(ekuWcharBuffer); + + MultiByteToWideChar(CP_ACP, 0, eku, -1, ekuWcharBuffer.get(), length); + + if (wcscmp(ekuWcharBuffer.get(), m_expectedEku.c_str()) == 0) + { + return true; // Found expected EKU + } + } + + return false; // Did not find expected EKU + } + +} diff --git a/dev/PackageManager/API/M.W.M.D.PackageCertificateEkuValidator.h b/dev/PackageManager/API/M.W.M.D.PackageCertificateEkuValidator.h new file mode 100644 index 0000000000..2a5b79fb1b --- /dev/null +++ b/dev/PackageManager/API/M.W.M.D.PackageCertificateEkuValidator.h @@ -0,0 +1,25 @@ +#pragma once +#include "Microsoft.Windows.Management.Deployment.PackageCertificateEkuValidator.g.h" + +#include + +namespace winrt::Microsoft::Windows::Management::Deployment::implementation +{ + struct PackageCertificateEkuValidator : PackageCertificateEkuValidatorT + { + PackageCertificateEkuValidator(hstring const& expectedCertificateEku); + bool IsPackageValid(winrt::Windows::Foundation::IInspectable const& appxPackagingObject); + + private: + bool CheckSignature(IAppxFile* signatureFile); + + hstring m_expectedEku{}; + }; +} + +namespace winrt::Microsoft::Windows::Management::Deployment::factory_implementation +{ + struct PackageCertificateEkuValidator : PackageCertificateEkuValidatorT + { + }; +} diff --git a/dev/PackageManager/API/M.W.M.D.PackageDeploymentManager.cpp b/dev/PackageManager/API/M.W.M.D.PackageDeploymentManager.cpp index 8f233ba6cd..63aa2026ca 100644 --- a/dev/PackageManager/API/M.W.M.D.PackageDeploymentManager.cpp +++ b/dev/PackageManager/API/M.W.M.D.PackageDeploymentManager.cpp @@ -6,6 +6,7 @@ #include "Microsoft.Windows.Management.Deployment.PackageDeploymentManager.g.cpp" #include "M.W.M.D.PackageDeploymentResult.h" +#include "M.W.M.D.PackageValidationEventSource.h" #include "MsixPackageManager.h" #include "PackageDeploymentResolver.h" @@ -33,6 +34,7 @@ namespace ABI::Windows::Management::Deployment #include #include +#include static_assert(static_cast(winrt::Microsoft::Windows::Management::Deployment::StubPackageOption::Default) == static_cast(winrt::Windows::Management::Deployment::StubPackageOption::Default), "winrt::Microsoft::Windows::Management::Deployment::StubPackageOption::Default != winrt::Windows::Management::Deployment::StubPackageOption::Default"); @@ -554,7 +556,6 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation PackageDeploymentProgressStatus::Queued, 0} }; progress(packageDeploymentProgress); - winrt::Windows::Management::Deployment::AddPackageOptions addOptions{ ToOptions(options) }; const double progressMaxPerPackage{ 1.0 }; HRESULT error{}; HRESULT extendedError{}; @@ -562,7 +563,7 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::guid activityId{}; try { - error = LOG_IF_FAILED_MSG(AddPackage(packageUri, addOptions, packageDeploymentProgress, progress, progressMaxPerPackage, extendedError, errorText, activityId), + error = LOG_IF_FAILED_MSG(AddPackage(packageUri, options, packageDeploymentProgress, progress, progressMaxPerPackage, extendedError, errorText, activityId), "ExtendedError:0x%08X PackageUri:%ls", extendedError, packageUri.ToString().c_str()); } @@ -677,7 +678,6 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation PackageDeploymentProgressStatus::Queued, 0} }; progress(packageDeploymentProgress); - winrt::Windows::Management::Deployment::StagePackageOptions stageOptions{ ToOptions(options) }; const double progressMaxPerPackage{ 1.0 }; HRESULT error{}; HRESULT extendedError{}; @@ -685,7 +685,7 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::guid activityId{}; try { - error = LOG_IF_FAILED_MSG(StagePackage(packageUri, stageOptions, packageDeploymentProgress, progress, progressMaxPerPackage, extendedError, errorText, activityId), + error = LOG_IF_FAILED_MSG(StagePackage(packageUri, options, packageDeploymentProgress, progress, progressMaxPerPackage, extendedError, errorText, activityId), "ExtendedError:0x%08X PackageUri:%ls", extendedError, packageUri.ToString().c_str()); } @@ -1958,6 +1958,12 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation return S_OK; } + if (::Microsoft::Windows::Management::Deployment::Feature_PackageValidation::IsEnabled()) + { + auto expectedDigests{ options.AddPackageOptions().ExpectedDigests() }; + ValidatePackagesAndUpdateExpectedDigests(options.AddPackageOptions().PackageValidators(), expectedDigests); + } + const auto progressBefore{ packageDeploymentProgress.Progress }; winrt::Windows::Management::Deployment::AddPackageOptions addOptions{ ToOptions(options) }; auto deploymentOperation{ m_packageManager.AddPackageByUriAsync(packageUri, addOptions) }; @@ -2016,25 +2022,6 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::Microsoft::Windows::Management::Deployment::AddPackageOptions const& options, winrt::Microsoft::Windows::Management::Deployment::PackageDeploymentProgress& packageDeploymentProgress, wistd::function progress, - const double progressMaxPerPackageSetItem, - HRESULT& extendedError, - winrt::hstring& errorText, - winrt::guid& activityId) - { - extendedError = S_OK; - errorText.clear(); - activityId = winrt::guid{}; - - winrt::Windows::Management::Deployment::AddPackageOptions addOptions{ ToOptions(options) }; - RETURN_IF_FAILED(AddPackage(packageUri, addOptions, packageDeploymentProgress, progress, progressMaxPerPackageSetItem, extendedError, errorText, activityId)); - return S_OK; - } - - HRESULT PackageDeploymentManager::AddPackage( - winrt::Windows::Foundation::Uri const& packageUri, - winrt::Windows::Management::Deployment::AddPackageOptions const& addOptions, - winrt::Microsoft::Windows::Management::Deployment::PackageDeploymentProgress& packageDeploymentProgress, - wistd::function progress, const double progressMaxPerPackage, HRESULT& extendedError, winrt::hstring& errorText, @@ -2044,6 +2031,13 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation errorText.clear(); activityId = winrt::guid{}; + if (::Microsoft::Windows::Management::Deployment::Feature_PackageValidation::IsEnabled()) + { + auto expectedDigests{ options.ExpectedDigests() }; + ValidatePackagesAndUpdateExpectedDigests(options.PackageValidators(), expectedDigests); + } + + winrt::Windows::Management::Deployment::AddPackageOptions addOptions{ ToOptions(options) }; const auto progressBefore{ packageDeploymentProgress.Progress }; auto deploymentOperation{ m_packageManager.AddPackageByUriAsync(packageUri, addOptions) }; deploymentOperation.Progress([&](winrt::Windows::Foundation::IAsyncOperationWithProgress< @@ -2111,25 +2105,6 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::Microsoft::Windows::Management::Deployment::StagePackageOptions const& options, winrt::Microsoft::Windows::Management::Deployment::PackageDeploymentProgress& packageDeploymentProgress, wistd::function progress, - const double progressMaxPerPackageSetItem, - HRESULT& extendedError, - winrt::hstring& errorText, - winrt::guid& activityId) - { - extendedError = S_OK; - errorText.clear(); - activityId = winrt::guid{}; - - winrt::Windows::Management::Deployment::StagePackageOptions stageOptions{ ToOptions(options) }; - RETURN_IF_FAILED(StagePackage(packageUri, stageOptions, packageDeploymentProgress, progress, progressMaxPerPackageSetItem, extendedError, errorText, activityId)); - return S_OK; - } - - HRESULT PackageDeploymentManager::StagePackage( - winrt::Windows::Foundation::Uri const& packageUri, - winrt::Windows::Management::Deployment::StagePackageOptions const& stageOptions, - winrt::Microsoft::Windows::Management::Deployment::PackageDeploymentProgress& packageDeploymentProgress, - wistd::function progress, const double progressMaxPerPackage, HRESULT& extendedError, winrt::hstring& errorText, @@ -2139,6 +2114,13 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation errorText.clear(); activityId = winrt::guid{}; + if (::Microsoft::Windows::Management::Deployment::Feature_PackageValidation::IsEnabled()) + { + auto expectedDigests{ options.ExpectedDigests() }; + ValidatePackagesAndUpdateExpectedDigests(options.PackageValidators(), expectedDigests); + } + + winrt::Windows::Management::Deployment::StagePackageOptions stageOptions{ ToOptions(options) }; const auto progressBefore{ packageDeploymentProgress.Progress }; auto deploymentOperation{ m_packageManager.StagePackageByUriAsync(packageUri, stageOptions) }; deploymentOperation.Progress([&](winrt::Windows::Foundation::IAsyncOperationWithProgress< @@ -3693,4 +3675,25 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation const auto schemeName{ packageUri.SchemeName() }; return CompareStringOrdinal(schemeName.c_str(), -1, L"ms-uup", -1, TRUE) == CSTR_EQUAL; } + + void PackageDeploymentManager::ValidatePackagesAndUpdateExpectedDigests( + winrt::Windows::Foundation::Collections::IMapView const& packageValidators, + winrt::Windows::Foundation::Collections::IMap& expectedDigests + ) + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Management::Deployment::Feature_PackageValidation::IsEnabled()); + + for (const auto pair : packageValidators) + { + const auto packageUri{ pair.Key() }; + const auto validators{ pair.Value() }; + if (!packageUri || !validators) + { + continue; + } + + THROW_HR_IF(APPX_E_DIGEST_MISMATCH, !validators.as()->Run(packageUri, expectedDigests)); + } + } + } diff --git a/dev/PackageManager/API/M.W.M.D.PackageDeploymentManager.h b/dev/PackageManager/API/M.W.M.D.PackageDeploymentManager.h index 870dbacc6b..6745a0588b 100644 --- a/dev/PackageManager/API/M.W.M.D.PackageDeploymentManager.h +++ b/dev/PackageManager/API/M.W.M.D.PackageDeploymentManager.h @@ -106,15 +106,6 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation HRESULT& extendedError, winrt::hstring& errorText, winrt::guid& activityId); - HRESULT AddPackage( - winrt::Windows::Foundation::Uri const& packageUri, - winrt::Windows::Management::Deployment::AddPackageOptions const& addOptions, - winrt::Microsoft::Windows::Management::Deployment::PackageDeploymentProgress& packageDeploymentProgress, - wistd::function progress, - const double progressMaxPerPackage, - HRESULT& extendedError, - winrt::hstring& errorText, - winrt::guid& activityId); HRESULT StagePackage( winrt::Windows::Foundation::Uri const& packageUri, winrt::Microsoft::Windows::Management::Deployment::StagePackageOptions const& options, @@ -124,15 +115,6 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation HRESULT& extendedError, winrt::hstring& errorText, winrt::guid& activityId); - HRESULT StagePackage( - winrt::Windows::Foundation::Uri const& packageUri, - winrt::Windows::Management::Deployment::StagePackageOptions const& stageOptions, - winrt::Microsoft::Windows::Management::Deployment::PackageDeploymentProgress& packageDeploymentProgress, - wistd::function progress, - const double progressMaxPerPackage, - HRESULT& extendedError, - winrt::hstring& errorText, - winrt::guid& activityId); HRESULT RegisterPackage( winrt::Windows::Foundation::Uri const& packageUri, winrt::Microsoft::Windows::Management::Deployment::RegisterPackageOptions const& options, @@ -299,6 +281,11 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::Microsoft::Windows::Management::Deployment::PackageSetItem const& packageSetItem); static bool IsUriScheme_MsUup(winrt::Windows::Foundation::Uri const& packageUri); + void ValidatePackagesAndUpdateExpectedDigests( + winrt::Windows::Foundation::Collections::IMapView const& packageValidators, + winrt::Windows::Foundation::Collections::IMap& expectedDigests + ); + private: static size_t Count(winrt::Windows::Foundation::Collections::IIterable packages) { diff --git a/dev/PackageManager/API/M.W.M.D.PackageFamilyNameValidator.cpp b/dev/PackageManager/API/M.W.M.D.PackageFamilyNameValidator.cpp new file mode 100644 index 0000000000..abf384902b --- /dev/null +++ b/dev/PackageManager/API/M.W.M.D.PackageFamilyNameValidator.cpp @@ -0,0 +1,51 @@ +#include "pch.h" +#include "M.W.M.D.PackageFamilyNameValidator.h" +#include "Microsoft.Windows.Management.Deployment.PackageFamilyNameValidator.g.cpp" +#include + +namespace winrt::Microsoft::Windows::Management::Deployment::implementation +{ + PackageFamilyNameValidator::PackageFamilyNameValidator(hstring const& expectedPackageFamilyName) + { + m_packageFamilyName = expectedPackageFamilyName; + } + + bool PackageFamilyNameValidator::IsPackageValid(winrt::Windows::Foundation::IInspectable const& appxPackagingObject) + { + winrt::com_ptr packageReader; + winrt::com_ptr bundleReader; + + if (SUCCEEDED(appxPackagingObject.as(IID_PPV_ARGS(&packageReader)))) + { + winrt::com_ptr manifestReader; + THROW_IF_FAILED(packageReader->GetManifest(manifestReader.put())); + + winrt::com_ptr packageId; + THROW_IF_FAILED(manifestReader->GetPackageId(packageId.put())); + + return CheckIdentity(packageId.get()); + } + else if (SUCCEEDED(appxPackagingObject.as(IID_PPV_ARGS(&bundleReader)))) + { + winrt::com_ptr manifestReader; + THROW_IF_FAILED(bundleReader->GetManifest(manifestReader.put())); + + winrt::com_ptr packageId; + THROW_IF_FAILED(manifestReader->GetPackageId(packageId.put())); + + return CheckIdentity(packageId.get()); + } + else + { + THROW_WIN32(ERROR_NOT_SUPPORTED); + } + } + + bool PackageFamilyNameValidator::CheckIdentity(IAppxManifestPackageId* packageId) + { + wil::unique_cotaskmem_string familyName; + THROW_IF_FAILED(packageId->GetPackageFamilyName(&familyName)); + + return (CSTR_EQUAL == CompareStringOrdinal(familyName.get(), -1, m_packageFamilyName.c_str(), -1, true)); + } +} diff --git a/dev/PackageManager/API/M.W.M.D.PackageFamilyNameValidator.h b/dev/PackageManager/API/M.W.M.D.PackageFamilyNameValidator.h new file mode 100644 index 0000000000..6cb6bbe3c1 --- /dev/null +++ b/dev/PackageManager/API/M.W.M.D.PackageFamilyNameValidator.h @@ -0,0 +1,25 @@ +#pragma once +#include "Microsoft.Windows.Management.Deployment.PackageFamilyNameValidator.g.h" + +#include + +namespace winrt::Microsoft::Windows::Management::Deployment::implementation +{ + struct PackageFamilyNameValidator : PackageFamilyNameValidatorT + { + PackageFamilyNameValidator(hstring const& expectedPackageFamilyName); + bool IsPackageValid(winrt::Windows::Foundation::IInspectable const& appxPackagingObject); + + private: + bool CheckIdentity(IAppxManifestPackageId* packageId); + + hstring m_packageFamilyName; + }; +} + +namespace winrt::Microsoft::Windows::Management::Deployment::factory_implementation +{ + struct PackageFamilyNameValidator : PackageFamilyNameValidatorT + { + }; +} diff --git a/dev/PackageManager/API/M.W.M.D.PackageMinimumVersionValidator.cpp b/dev/PackageManager/API/M.W.M.D.PackageMinimumVersionValidator.cpp new file mode 100644 index 0000000000..f8841cf461 --- /dev/null +++ b/dev/PackageManager/API/M.W.M.D.PackageMinimumVersionValidator.cpp @@ -0,0 +1,51 @@ +#include "pch.h" +#include "M.W.M.D.PackageMinimumVersionValidator.h" +#include "Microsoft.Windows.Management.Deployment.PackageMinimumVersionValidator.g.cpp" +#include + +namespace winrt::Microsoft::Windows::Management::Deployment::implementation +{ + PackageMinimumVersionValidator::PackageMinimumVersionValidator(winrt::Windows::ApplicationModel::PackageVersion const& minimumVersion) + { + m_minimumVersion = ::AppModel::Identity::PackageVersion{ minimumVersion }.Version; + } + + bool PackageMinimumVersionValidator::IsPackageValid(winrt::Windows::Foundation::IInspectable const& appxPackagingObject) + { + winrt::com_ptr packageReader; + winrt::com_ptr bundleReader; + + if (SUCCEEDED(appxPackagingObject.as(IID_PPV_ARGS(&packageReader)))) + { + winrt::com_ptr manifestReader; + THROW_IF_FAILED(packageReader->GetManifest(manifestReader.put())); + + winrt::com_ptr packageId; + THROW_IF_FAILED(manifestReader->GetPackageId(packageId.put())); + + return CheckIdentity(packageId.get()); + } + else if (SUCCEEDED(appxPackagingObject.as(IID_PPV_ARGS(&bundleReader)))) + { + winrt::com_ptr manifestReader; + THROW_IF_FAILED(bundleReader->GetManifest(manifestReader.put())); + + winrt::com_ptr packageId; + THROW_IF_FAILED(manifestReader->GetPackageId(packageId.put())); + + return CheckIdentity(packageId.get()); + } + else + { + THROW_WIN32(ERROR_NOT_SUPPORTED); + } + } + + bool PackageMinimumVersionValidator::CheckIdentity(IAppxManifestPackageId* packageId) + { + UINT64 version; + THROW_IF_FAILED(packageId->GetVersion(&version)); + + return (version >= m_minimumVersion); + } +} diff --git a/dev/PackageManager/API/M.W.M.D.PackageMinimumVersionValidator.h b/dev/PackageManager/API/M.W.M.D.PackageMinimumVersionValidator.h new file mode 100644 index 0000000000..21759af3dd --- /dev/null +++ b/dev/PackageManager/API/M.W.M.D.PackageMinimumVersionValidator.h @@ -0,0 +1,25 @@ +#pragma once +#include "Microsoft.Windows.Management.Deployment.PackageMinimumVersionValidator.g.h" + +#include + +namespace winrt::Microsoft::Windows::Management::Deployment::implementation +{ + struct PackageMinimumVersionValidator : PackageMinimumVersionValidatorT + { + PackageMinimumVersionValidator(winrt::Windows::ApplicationModel::PackageVersion const& minimumVersion); + bool IsPackageValid(winrt::Windows::Foundation::IInspectable const& appxPackagingObject); + + private: + bool CheckIdentity(IAppxManifestPackageId* packageId); + + UINT64 m_minimumVersion; + }; +} + +namespace winrt::Microsoft::Windows::Management::Deployment::factory_implementation +{ + struct PackageMinimumVersionValidator : PackageMinimumVersionValidatorT + { + }; +} diff --git a/dev/PackageManager/API/M.W.M.D.PackageValidationEventArgs.cpp b/dev/PackageManager/API/M.W.M.D.PackageValidationEventArgs.cpp new file mode 100644 index 0000000000..9e67bce365 --- /dev/null +++ b/dev/PackageManager/API/M.W.M.D.PackageValidationEventArgs.cpp @@ -0,0 +1,32 @@ +#include "pch.h" + +#include "M.W.M.D.PackageValidationEventArgs.h" +#include "Microsoft.Windows.Management.Deployment.PackageValidationEventArgs.g.cpp" + +namespace winrt::Microsoft::Windows::Management::Deployment::implementation +{ + PackageValidationEventArgs::PackageValidationEventArgs(winrt::Windows::Foundation::Uri const& packageUri, winrt::Windows::Foundation::IInspectable const& appxObject) + { + m_packageUri = packageUri; + m_appxObject = appxObject; + m_cancel = false; + } + + winrt::Windows::Foundation::Uri PackageValidationEventArgs::PackageUri() + { + return m_packageUri; + } + winrt::Windows::Foundation::IInspectable PackageValidationEventArgs::AppxPackagingObject() + { + return m_appxObject; + } + + bool PackageValidationEventArgs::Cancel() + { + return m_cancel; + } + void PackageValidationEventArgs::Cancel(bool value) + { + m_cancel = value; + } +} diff --git a/dev/PackageManager/API/M.W.M.D.PackageValidationEventArgs.h b/dev/PackageManager/API/M.W.M.D.PackageValidationEventArgs.h new file mode 100644 index 0000000000..a7110281e5 --- /dev/null +++ b/dev/PackageManager/API/M.W.M.D.PackageValidationEventArgs.h @@ -0,0 +1,22 @@ +#pragma once +#include "Microsoft.Windows.Management.Deployment.PackageValidationEventArgs.g.h" + +namespace winrt::Microsoft::Windows::Management::Deployment::implementation +{ + struct PackageValidationEventArgs : PackageValidationEventArgsT, winrt::deferrable_event_args + { + public: + PackageValidationEventArgs(winrt::Windows::Foundation::Uri const& packageUri, winrt::Windows::Foundation::IInspectable const& appxObject); + + winrt::Windows::Foundation::Uri PackageUri(); + winrt::Windows::Foundation::IInspectable AppxPackagingObject(); + + bool Cancel(); + void Cancel(bool value); + + private: + winrt::Windows::Foundation::Uri m_packageUri{ nullptr }; + winrt::Windows::Foundation::IInspectable m_appxObject{}; + bool m_cancel{}; + }; +} diff --git a/dev/PackageManager/API/M.W.M.D.PackageValidationEventSource.cpp b/dev/PackageManager/API/M.W.M.D.PackageValidationEventSource.cpp new file mode 100644 index 0000000000..7308443053 --- /dev/null +++ b/dev/PackageManager/API/M.W.M.D.PackageValidationEventSource.cpp @@ -0,0 +1,69 @@ +#include "pch.h" +#include "Microsoft.Windows.Management.Deployment.PackageValidationEventSource.g.cpp" + +#include "AppxPackaging.h" +#include "AppxPackagingObject.h" +#include "M.W.M.D.PackageValidationEventSource.h" + +namespace winrt::Microsoft::Windows::Management::Deployment::implementation +{ + bool PackageValidationEventSource::Run(winrt::Windows::Foundation::Uri const& packageUri, winrt::Windows::Foundation::Collections::IMap& expectedDigests) + { + auto packageUriString{ packageUri.AbsoluteCanonicalUri().c_str() }; + auto expectedDigest{ expectedDigests.TryLookup(packageUri) }; + auto expectedDigestString{ expectedDigest.has_value() ? expectedDigest.value().c_str() : nullptr }; + + auto appxFactory{ wil::CoCreateInstance() }; + auto bundleFactory{ wil::CoCreateInstance() }; + + IInspectable appxObject{}; + winrt::com_ptr packageReader{}; + winrt::com_ptr bundleReader{}; + winrt::com_ptr digestProvider{}; + + if (SUCCEEDED(appxFactory->CreatePackageReaderFromSourceUri(packageUriString, expectedDigestString, packageReader.put()))) + { + appxObject = winrt::make(packageReader.get()).as(); + digestProvider = packageReader.as(); + } + else if (SUCCEEDED(bundleFactory->CreateBundleReaderFromSourceUri(packageUriString, expectedDigestString, bundleReader.put()))) + { + appxObject = winrt::make(bundleReader.get()).as(); + digestProvider = bundleReader.as(); + } + else + { + THROW_WIN32(ERROR_INSTALL_OPEN_PACKAGE_FAILED); + } + + auto args{ winrt::make(packageUri, appxObject) }; + m_validationRequested(*this, args); + args.as()->wait_for_deferrals().get(); + + if (args.Cancel()) + { + return false; + } + else + { + if (!expectedDigestString) + { + wil::unique_cotaskmem_string digest; + THROW_IF_FAILED(digestProvider->GetDigest(&digest)); + + expectedDigests.Insert(packageUri, digest.get()); + } + return true; + } + } + + winrt::event_token PackageValidationEventSource::ValidationRequested(winrt::Windows::Foundation::TypedEventHandler const& handler) + { + return m_validationRequested.add(handler); + } + + void PackageValidationEventSource::ValidationRequested(winrt::event_token const& token) noexcept + { + m_validationRequested.remove(token); + } +} diff --git a/dev/PackageManager/API/M.W.M.D.PackageValidationEventSource.h b/dev/PackageManager/API/M.W.M.D.PackageValidationEventSource.h new file mode 100644 index 0000000000..dbc66448d7 --- /dev/null +++ b/dev/PackageManager/API/M.W.M.D.PackageValidationEventSource.h @@ -0,0 +1,21 @@ +#pragma once +#include "Microsoft.Windows.Management.Deployment.PackageValidationEventSource.g.h" + +#include "M.W.M.D.PackageValidationEventArgs.h" + +namespace winrt::Microsoft::Windows::Management::Deployment::implementation +{ + struct PackageValidationEventSource : PackageValidationEventSourceT + { + public: + PackageValidationEventSource() = default; + + bool Run(winrt::Windows::Foundation::Uri const& packageUri, winrt::Windows::Foundation::Collections::IMap& expectedDigests); + + winrt::event_token ValidationRequested(winrt::Windows::Foundation::TypedEventHandler const& handler); + void ValidationRequested(winrt::event_token const& token) noexcept; + + private: + winrt::event> m_validationRequested{}; + }; +} diff --git a/dev/PackageManager/API/M.W.M.D.PackageValidationHandler.cpp b/dev/PackageManager/API/M.W.M.D.PackageValidationHandler.cpp new file mode 100644 index 0000000000..262f9ba8f8 --- /dev/null +++ b/dev/PackageManager/API/M.W.M.D.PackageValidationHandler.cpp @@ -0,0 +1,26 @@ +#include "pch.h" +#include "M.W.M.D.PackageValidationHandler.h" +#include "Microsoft.Windows.Management.Deployment.PackageValidationHandler.g.cpp" + +namespace winrt::Microsoft::Windows::Management::Deployment::implementation +{ + PackageValidationHandler::PackageValidationHandler(winrt::Microsoft::Windows::Management::Deployment::IPackageValidator const& validator) + { + m_validator = validator; + } + + winrt::Windows::Foundation::TypedEventHandler PackageValidationHandler::Handler() + { + return [this](winrt::Microsoft::Windows::Management::Deployment::PackageValidationEventSource const& sender, winrt::Microsoft::Windows::Management::Deployment::PackageValidationEventArgs const& args) + { + auto deferral = args.GetDeferral(); + + if (m_validator && !m_validator.IsPackageValid(args.AppxPackagingObject())) + { + args.Cancel(true); + } + + deferral.Complete(); + }; + } +} diff --git a/dev/PackageManager/API/M.W.M.D.PackageValidationHandler.h b/dev/PackageManager/API/M.W.M.D.PackageValidationHandler.h new file mode 100644 index 0000000000..a37f04ea77 --- /dev/null +++ b/dev/PackageManager/API/M.W.M.D.PackageValidationHandler.h @@ -0,0 +1,21 @@ +#pragma once +#include "Microsoft.Windows.Management.Deployment.PackageValidationHandler.g.h" + +namespace winrt::Microsoft::Windows::Management::Deployment::implementation +{ + struct PackageValidationHandler : PackageValidationHandlerT + { + PackageValidationHandler(winrt::Microsoft::Windows::Management::Deployment::IPackageValidator const& validator); + + winrt::Windows::Foundation::TypedEventHandler Handler(); + + private: + winrt::Microsoft::Windows::Management::Deployment::IPackageValidator m_validator{ nullptr }; + }; +} +namespace winrt::Microsoft::Windows::Management::Deployment::factory_implementation +{ + struct PackageValidationHandler : PackageValidationHandlerT + { + }; +} diff --git a/dev/PackageManager/API/M.W.M.D.StagePackageOptions.cpp b/dev/PackageManager/API/M.W.M.D.StagePackageOptions.cpp index dfd7810dff..956c993d01 100644 --- a/dev/PackageManager/API/M.W.M.D.StagePackageOptions.cpp +++ b/dev/PackageManager/API/M.W.M.D.StagePackageOptions.cpp @@ -4,6 +4,7 @@ #include "pch.h" #include +#include #include "M.W.M.D.StagePackageOptions.h" #include "Microsoft.Windows.Management.Deployment.StagePackageOptions.g.cpp" @@ -127,4 +128,40 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation } return m_expectedDigests; } + + bool StagePackageOptions::IsPackageValidationSupported() + { + return WindowsVersion::SupportsIAppxFactory4(); + } + + winrt::Microsoft::Windows::Management::Deployment::PackageValidationEventSource StagePackageOptions::GetValidationEventSourceForUri(winrt::Windows::Foundation::Uri const& uri) + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Management::Deployment::Feature_PackageValidation::IsEnabled()); + + if (!m_packageValidators) + { + m_packageValidators = winrt::single_threaded_map(); + } + if (!m_packageValidators.HasKey(uri)) + { + m_packageValidators.Insert(uri, winrt::make()); + } + + return m_packageValidators.Lookup(uri); + } + + winrt::Windows::Foundation::Collections::IMapView StagePackageOptions::PackageValidators() + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Management::Deployment::Feature_PackageValidation::IsEnabled()); + + if (!m_packageValidators) + { + // return an empty view + return winrt::single_threaded_map().GetView(); + } + else + { + return m_packageValidators.GetView(); + } + } } diff --git a/dev/PackageManager/API/M.W.M.D.StagePackageOptions.h b/dev/PackageManager/API/M.W.M.D.StagePackageOptions.h index 386be0cf3a..a329a68353 100644 --- a/dev/PackageManager/API/M.W.M.D.StagePackageOptions.h +++ b/dev/PackageManager/API/M.W.M.D.StagePackageOptions.h @@ -1,9 +1,10 @@ -// Copyright (c) Microsoft Corporation and Contributors. +// Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. #pragma once #include "Microsoft.Windows.Management.Deployment.StagePackageOptions.g.h" +#include "M.W.M.D.PackageValidationEventSource.h" namespace winrt::Microsoft::Windows::Management::Deployment::implementation { @@ -35,6 +36,9 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation void AllowUnsigned(bool value); bool IsExpectedDigestsSupported(); winrt::Windows::Foundation::Collections::IMap ExpectedDigests(); + bool IsPackageValidationSupported(); + winrt::Microsoft::Windows::Management::Deployment::PackageValidationEventSource GetValidationEventSourceForUri(winrt::Windows::Foundation::Uri const& uri); + winrt::Windows::Foundation::Collections::IMapView PackageValidators(); private: winrt::Microsoft::Windows::Management::Deployment::PackageVolume m_targetVolume{ nullptr }; @@ -51,6 +55,7 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation bool m_stageInPlace{}; bool m_allowUnsigned{}; winrt::Windows::Foundation::Collections::IMap m_expectedDigests; + winrt::Windows::Foundation::Collections::IMap m_packageValidators; }; } namespace winrt::Microsoft::Windows::Management::Deployment::factory_implementation diff --git a/dev/PackageManager/API/PackageManager.idl b/dev/PackageManager/API/PackageManager.idl index 1a2a0e27b2..b299fa1349 100644 --- a/dev/PackageManager/API/PackageManager.idl +++ b/dev/PackageManager/API/PackageManager.idl @@ -7,7 +7,7 @@ import "M.AM.DynamicDependency.idl"; namespace Microsoft.Windows.Management.Deployment { - [contractversion(2)] + [contractversion(3)] apicontract PackageDeploymentContract{}; /// Features can be queried if currently available/enabled. @@ -171,6 +171,63 @@ namespace Microsoft.Windows.Management.Deployment IVector Items { get; }; } + [feature(Feature_PackageValidation)] + [contract(PackageDeploymentContract, 3)] + runtimeclass PackageValidationEventArgs + { + Windows.Foundation.Uri PackageUri{ get; }; + IInspectable AppxPackagingObject{ get; }; + Boolean Cancel; + + Windows.Foundation.Deferral GetDeferral(); + } + + [feature(Feature_PackageValidation)] + [contract(PackageDeploymentContract, 3)] + runtimeclass PackageValidationEventSource + { + event Windows.Foundation.TypedEventHandler ValidationRequested; + } + + [feature(Feature_PackageValidation)] + [contract(PackageDeploymentContract, 3)] + interface IPackageValidator + { + // This IInspectable will support QueryInterface into either IAppxPackageReader or + // IAppxBundleReader (these are COM interfaces from AppxPackaging.h). + // One of these interfaces will be available depending on the type of file being validated. + Boolean IsPackageValid(IInspectable appxPackagingObject); + } + + [feature(Feature_PackageValidation)] + [contract(PackageDeploymentContract, 3)] + runtimeclass PackageValidationHandler + { + PackageValidationHandler(IPackageValidator validator); + Windows.Foundation.TypedEventHandler Handler{ get; }; + } + + [feature(Feature_PackageValidation)] + [contract(PackageDeploymentContract, 3)] + runtimeclass PackageFamilyNameValidator : [default] IPackageValidator + { + PackageFamilyNameValidator(String expectedPackageFamilyName); + } + + [feature(Feature_PackageValidation)] + [contract(PackageDeploymentContract, 3)] + runtimeclass PackageMinimumVersionValidator : [default] IPackageValidator + { + PackageMinimumVersionValidator(Windows.ApplicationModel.PackageVersion minimumVersion); + } + + [feature(Feature_PackageValidation)] + [contract(PackageDeploymentContract, 3)] + runtimeclass PackageCertificateEkuValidator : [default] IPackageValidator + { + PackageCertificateEkuValidator(String expectedCertificateEku); + } + // Requires Windows >= 10.0.19041.0 (aka 2004 aka 20H1) [contract(PackageDeploymentContract, 1)] runtimeclass AddPackageOptions @@ -200,6 +257,18 @@ namespace Microsoft.Windows.Management.Deployment Boolean IsLimitToExistingPackagesSupported { get; }; // Requires Windows >= 10.0.22621.0 (aka Win11 22H2) Boolean LimitToExistingPackages; + + [feature(Feature_PackageValidation)] + [contract(PackageDeploymentContract, 3)] + Boolean IsPackageValidationSupported{ get; }; + + [feature(Feature_PackageValidation)] + [contract(PackageDeploymentContract, 3)] + IMapView PackageValidators{ get; }; + + [feature(Feature_PackageValidation)] + [contract(PackageDeploymentContract, 3)] + PackageValidationEventSource GetValidationEventSourceForUri(Windows.Foundation.Uri uri); } // Requires Windows >= 10.0.19041.0 (aka 2004 aka 20H1) @@ -224,6 +293,18 @@ namespace Microsoft.Windows.Management.Deployment Boolean IsExpectedDigestsSupported { get; }; // Requires Windows >= 10.0.22621.0 (aka Win11 22H2) IMap ExpectedDigests{ get; }; + + [feature(Feature_PackageValidation)] + [contract(PackageDeploymentContract, 3)] + Boolean IsPackageValidationSupported{ get; }; + + [feature(Feature_PackageValidation)] + [contract(PackageDeploymentContract, 3)] + IMapView PackageValidators{ get; }; + + [feature(Feature_PackageValidation)] + [contract(PackageDeploymentContract, 3)] + PackageValidationEventSource GetValidationEventSourceForUri(Windows.Foundation.Uri uri); } // Requires Windows >= 10.0.19041.0 (aka 2004 aka 20H1) diff --git a/dev/PackageManager/API/PackageManager.vcxitems b/dev/PackageManager/API/PackageManager.vcxitems index 7b67a3efa0..c7baeb5aaf 100644 --- a/dev/PackageManager/API/PackageManager.vcxitems +++ b/dev/PackageManager/API/PackageManager.vcxitems @@ -16,13 +16,19 @@ + + + + + + @@ -31,15 +37,22 @@ + + + + + + + @@ -56,4 +69,4 @@ - + \ No newline at end of file diff --git a/dev/PackageManager/API/PackageManager.vcxitems.filters b/dev/PackageManager/API/PackageManager.vcxitems.filters index e41fd8b76e..35aa32109f 100644 --- a/dev/PackageManager/API/PackageManager.vcxitems.filters +++ b/dev/PackageManager/API/PackageManager.vcxitems.filters @@ -56,6 +56,24 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + @@ -106,8 +124,29 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + - + \ No newline at end of file diff --git a/specs/packagemanager/PackageManagement.md b/specs/packagemanager/PackageManagement.md index b8e4cb541b..e9d8817a6f 100644 --- a/specs/packagemanager/PackageManagement.md +++ b/specs/packagemanager/PackageManagement.md @@ -23,7 +23,7 @@ but with additional functionality, improved developer experience and performance - [3.10. PackageVolume Repair](#310-packagevolume-repair) - [3.11. Usability](#311-usability) - [3.12. Is\*Provisioned()](#312-312-isprovisioned) - - [3.13. PackageValidation](#313-packagevalidation) + - [3.13. Package Validation](#313-package-validation) - [4. Examples](#4-examples) - [4.1. AddPackageAsync()](#41-addpackageasync) - [4.2. AddPackageByUriAsync()](#42-addpackagebyuriasync) @@ -74,7 +74,7 @@ Additional functionality includes: * IsPackageRegistrationPending -- Is there an update waiting to register? * PackageSets -- Batch operations * PackageRuntimeManager -- Batch operations for use at runtime via Dynamic Dependencies -* PackageValidation -- Validate a package has expected identity, signature, etc. before adding/staging +* Package validation -- Validate a package has expected identity, signature, etc. before adding/staging * Usability -- Quality-of-Life enhancements ## 3.1. API Structure @@ -400,7 +400,7 @@ Is\*Provisioned\*() methods determine if the target is provisioned. These methods require administrative privileges. -## 3.13. PackageValidation +## 3.13. Package Validation This API allows callers to verify that packages being processed by Add*, Ensure*, and Stage* APIs of PackageDeploymentManager match what are expected from their URI. @@ -411,18 +411,19 @@ the package data being read, causing a malicious package to be installed instead one. The package might also be tampered at the source through supply-chain attacks. Verifying the identity and signature of target packages helps ensure that such attacks have not happened. -The following runtimeclasses implement `PackageValidation` handlers, and are available for use -directly: +The following package validators are available for use directly through their runtimeclasses: * `PackageFamilyNameValidator`: Validates that the package has the expected package family name. * `PackageMinimumVersionValidator`: Validates that the package has at least the expected minimum version number. * `PackageCertificateEkuValidator`: Validates that the certificate used to sign the package contains the expected Extended Key Usage (EKU) value. -Custom validators can be implemented as handlers for the -`PackageValidationEventSource.ValidationRequested` event. These can verify any part of packages' +Package validators can be customized to verify any part of packages' [footprint data](https://learn.microsoft.com/windows/win32/api/appxpackaging/ne-appxpackaging-appx_bundle_footprint_file_type) -(manifest, block map, and digital signature). +(manifest, block map, and digital signature). Custom package validators can be implemented as +handlers for the `PackageValidationEventSource.ValidationRequested` event. The +`PackageValidationHandler` runtimeclass provides a standard implementation of such a handler, +and accepts any implementation class derived from the `IPackageValidator` interface. # 4. Examples @@ -787,11 +788,11 @@ PackageVersion ToVersion(uint major, uint minor, uint build, uint revision) => }; ``` -## 4.9. PackageValidation +## 4.9. Package Validation -### 4.9.1. Using built-in PackageValidation handlers +### 4.9.1. Using built-in package validators -This example shows how to use built-in PackageValidation handlers to verify +This example shows how to use built-in package validators to verify package family name, minimum version, and certificate EKU. ```c# @@ -802,9 +803,9 @@ var packageUri = new Uri("https://contoso.com/package.msix"); var options = new AddPackageOptions(); var validators = options.GetValidationEventSourceForUri(packageUri).ValidationRequested; -validators += new PackageFamilyNameValidator("ExpectedFamilyName_1234567890abc").Handler; -validators += new PackageMinimumVersionValidator(new Windows.ApplicationModel.PackageVersion(2, 0, 0, 0)).Handler; -validators += new PackageCertificateEkuValidator("1.3.6.1.4.1.311.2.1.11").Handler; +validators += new PackageValidationHandler(new PackageFamilyNameValidator("ExpectedFamilyName_1234567890abc")).Handler; +validators += new PackageValidationHandler(new PackageMinimumVersionValidator(new Windows.ApplicationModel.PackageVersion(2, 0, 0, 0))).Handler; +validators += new PackageValidationHandler(new PackageCertificateEkuValidator("1.3.6.1.4.1.311.2.1.11")).Handler; var deploymentResult = await pdm.AddPackageAsync(packageUri, options); if (deploymentResult.Status == PackageDeploymentStatus.CompletedSuccess) @@ -816,7 +817,7 @@ else // deploymentResult.Status == PackageDeploymentStatus.CompletedFailure var error = deploymentResult.Error.HResult; if (error = 0x80080219 /*APPX_E_DIGEST_MISMATCH*/) { - Console.WriteLine("The package retrieved from the specified URI isn't expected according to PackageValidators"); + Console.WriteLine("The package retrieved from the specified URI doesn't have the expected family name, version, or certificate EKU"); } else { @@ -827,9 +828,10 @@ else // deploymentResult.Status == PackageDeploymentStatus.CompletedFailure } ``` -### 4.9.2. Using custom PackageValidation handlers +### 4.9.2. Using IPackageValidator to implement custom package validator -This example shows how to implement a custom PackageValidation handler, and use it to verify a package. +This example shows how to implement and use a custom package validator derived from the +`IPackageValidator` interface to validate a .msix package. ```c# using Microsoft.Windows.Management.Deployment; @@ -839,28 +841,72 @@ using Microsoft.Windows.Management.Deployment; [Guid("b5c49650-99bc-481c-9a34-3d53a4106708"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IAppxPackageReader { ... } -// Implementation of the custom package validation handler -bool IsPackageValid(object package) +// Implementation of the custom package validator +// This example validates that the package has no capabilities declared in its manifest. +class PackageCapabilitiesValidator : IPackageValidator { - var packageReader = package as IAppxPackageReader; - if (packageReader == null) + public bool IsPackageValid(object package) { - // object is not an msix package as expected (i.e. it is a bundle), reject it - return false; + var packageReader = package as IAppxPackageReader; + if (packageReader == null) + { + // object is not a .msix package as expected (i.e. it is a bundle), reject it + return false; + } + + var manifestReader = packageReader.GetManifest() as IAppxManifestReader3; + var capabilitiesEnumerator = manifestReader.GetCapabilitiesByCapabilityClass(APPX_CAPABILITY_CLASS_ALL); + bool hasCapabilities = capabilitiesEnumerator.GetHasCurrent(); + return !hasCapabilities; } +} - IAppxManifestReader manifestReader; - packageReader.GetManifest(out manifestReader); +// Code that utilizes the custom package validator +void InstallPackageWithCustomValidation() +{ + var pdm = PackageDeploymentManager().GetDefault(); + var packageUri = new Uri("https://contoso.com/package.msix"); - var manifestReader3 = manifestReader as IAppxManifestReader3; - IAppxManifestCapabilitiesEnumerator capabilitiesEnumerator; - manifestReader3.GetCapabilitiesByCapabilityClass(APPX_CAPABILITY_CLASS_ALL, out capabilitiesEnumerator); + var options = new AddPackageOptions(); + options.GetValidationEventSourceForUri(packageUri).ValidationRequested += new PackageValidationHandler(new PackageCapabilitiesValidator()).Handler; - bool hasCapabilities; - capabilitiesEnumerator.GetHasCurrent(out hasCapabilities); - return !hasCapabilities; + var deploymentResult = await pdm.AddPackageAsync(packageUri, options); + if (deploymentResult.Status == PackageDeploymentStatus.CompletedSuccess) + { + Console.WriteLine("Success"); + } + else // deploymentResult.Status == PackageDeploymentStatus.CompletedFailure + { + var error = deploymentResult.Error.HResult; + if (error = 0x80080219 /*APPX_E_DIGEST_MISMATCH*/) + { + Console.WriteLine("The package retrieved from the specified URI did not pass validation for capabilities"); + } + else + { + var extendedError = deploymentResult.ExtendedError.HResult; + var message = deploymentResult.MessageText; + Console.WriteLine($"An error occurred while adding the package. Error 0x{error:X08} ExtendedError 0x{extendedError:X08} {message}"); + } + } } +``` + +### 4.9.3. Using custom package validator as event handler + +This example shows how to implement and use a custom package validator as an event handler +for the `PackageValidationEventSource.ValidationRequested` event to validate a .msix package. + +```c# +using Microsoft.Windows.Management.Deployment; + +// Consuming COM APIs for IAppxPackageReader, IAppxManifestReader, etc. requires C# interop definitions. +// Assume standard interop definitions exist for relevant APIs in AppxPackaging.h. +[Guid("b5c49650-99bc-481c-9a34-3d53a4106708"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +public interface IAppxPackageReader { ... } +// Implementation of the custom package validator +// This example validates that the package has no capabilities declared in its manifest. void MyPackageValidationHandler(object sender, PackageValidationEventArgs args) { var deferral = args.GetDeferral(); @@ -868,7 +914,17 @@ void MyPackageValidationHandler(object sender, PackageValidationEventArgs args) bool isValid = false; try { - isValid = IsPackageValid(args.Package); + var packageReader = args.Package as IAppxPackageReader; + if (packageReader == null) + { + // object is not a .msix package as expected (i.e. it is a bundle), reject it + return false; + } + + var manifestReader = packageReader.GetManifest() as IAppxManifestReader3; + var capabilitiesEnumerator = manifestReader.GetCapabilitiesByCapabilityClass(APPX_CAPABILITY_CLASS_ALL); + bool hasCapabilities = capabilitiesEnumerator.GetHasCurrent(); + isValid = !hasCapabilities; } finally { @@ -905,7 +961,7 @@ void InstallPackageWithCustomValidation() var error = deploymentResult.Error.HResult; if (error = 0x80080219 /*APPX_E_DIGEST_MISMATCH*/) { - Console.WriteLine("The package retrieved from the specified URI isn't expected according to PackageValidators"); + Console.WriteLine("The package retrieved from the specified URI did not pass validation for capabilities"); } else { @@ -1030,24 +1086,37 @@ namespace Microsoft.Windows.Management.Deployment } [contract(PackageDeploymentContract, 3)] - runtimeclass PackageFamilyNameValidator + interface IPackageValidator + { + // This IInspectable will support QueryInterface into either IAppxPackageReader or + // IAppxBundleReader (these are COM interfaces from AppxPackaging.h). + // One of these interfaces will be available depending on the type of file being validated. + Boolean IsPackageValid(IInspectable appxPackagingObject); + } + + [contract(PackageDeploymentContract, 3)] + runtimeclass PackageValidationHandler { - PackageFamilyNameValidator(String packageUri, String expectedPackageFamilyName); + PackageValidationHandler(IPackageValidator validator); Windows.Foundation.TypedEventHandler Handler{ get; }; } [contract(PackageDeploymentContract, 3)] - runtimeclass PackageMinimumVersionValidator + runtimeclass PackageFamilyNameValidator : [default] IPackageValidator + { + PackageFamilyNameValidator(String expectedPackageFamilyName); + } + + [contract(PackageDeploymentContract, 3)] + runtimeclass PackageMinimumVersionValidator : [default] IPackageValidator { PackageMinimumVersionValidator(Windows.ApplicationModel.PackageVersion minimumVersion); - Windows.Foundation.TypedEventHandler Handler{ get; }; } [contract(PackageDeploymentContract, 3)] - runtimeclass PackageCertificateEkuValidator + runtimeclass PackageCertificateEkuValidator : [default] IPackageValidator { PackageCertificateEkuValidator(String expectedCertificateEku); - Windows.Foundation.TypedEventHandler Handler{ get; }; } /// The progress status of the deployment request.