Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions specs/packagemanager/PackageManagement.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +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. PackageValidator](#313-packagevalidator)
- [4. Examples](#4-examples)
- [4.1. AddPackageAsync()](#41-addpackageasync)
- [4.2. AddPackageByUriAsync()](#42-addpackagebyuriasync)
Expand Down Expand Up @@ -73,6 +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
* PackageValidator -- Validate a package has expected identity, signature, etc. before adding/staging
* Usability -- Quality-of-Life enhancements

## 3.1. API Structure
Expand Down Expand Up @@ -398,6 +400,24 @@ Is\*Provisioned\*() methods determine if the target is provisioned.

These methods require administrative privileges.

## 3.13. PackageValidator

This API allows callers to verify that packages being processed by PackageDeploymentManager.AddPackageAsync
and PackageDeploymentManager.StagePackageAsync match what are expected from their URI.

When adding or staging a package from an external source such as HTTP URI or uncontrolled file location,
an attacker can potentially intercept and tamper with the package data being read, causing a malicious
package to be installed instead of the expected one. Verifying the identity and signature of target
packages helps ensure that such tampering has not happened.

The following PackageValidators are provided and available for use directly:
* 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 also be implemented using the IPackageValidator interface, which allows verifying
any part of the package’s footprint data (manifest, block map, and digital signature) in any desired manner.

# 4. Examples

## 4.1. AddPackageAsync()
Expand Down Expand Up @@ -761,6 +781,100 @@ PackageVersion ToVersion(uint major, uint minor, uint build, uint revision) =>
};
```

## 4.9. PackageValidator

### 4.9.1. Using built-in PackageValidators

This example shows how to use built-in PackageValidators to verify package family name, minimum version, and certificate EKU.

```c++/winrt
auto packageDeploymentManager{ winrt::Microsoft::Windows::Management::Deployment::PackageDeploymentManager::GetDefault() };
const auto packageUri{ L"https://example.com/package.msix"};
const winrt::hstring package{ packageUri.c_str() };

IVector<IPackageValidator> validators{ winrt::single_threaded_vector<IPackageValidator>() };
validators.Append(winrt::Microsoft::Windows::Management::Deployment::PackageFamilyNameValidator(L"ExpectedFamilyName_abcdefg123"));
validators.Append(winrt::Microsoft::Windows::Management::Deployment::PackageMinimumVersionValidator(2, 0, 0, 0));
validators.Append(winrt::Microsoft::Windows::Management::Deployment::PackageCertificateEkuValidator(L"1.3.6.1.4.1.311.2.1.11");

winrt::Microsoft::Windows::Management::Deployment::AddPackageOptions addOptions;
addOptions.PackageValidators.Insert(packageUri, validators);

auto deploymentOperation{ packageDeploymentManager.AddPackageAsync(package, addOptions) };
auto deploymentResult{ WaitForDeploymentOperation(deploymentOperation) };
// deploymentOperation will fail with APPX_E_DIGEST_MISMATCH if any packageValidator rejects the package
```

### 4.9.2. Using custom PackageValidators

This example shows how to implement a custom PackageValidator using IPackageValidator interface, and use it to verify a package.

```idl
// MyCustom.idl
namespace MyCustom
{
runtimeclass MyPackageValidator : [default] Microsoft.Windows.Management.Deployment.IPackageValidator
{
Boolean IsPackageValid(AppxPackagingObject packagingObject);
}
}
```

```c++/winrt
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Samples should be written in C# unless there's a need for other language(s)

You should be able to do this in C# with C#/WinRT, but I'm not familiar with the syntax. Plus in this case the custom validator needs to interact with the Packaging API (e.g. IAppxPackageReader) which is COM. Can this be done in C#? I suspect so, but the details probably differ significantly from C++. If so that warrants C++ and C#, to illustrate how to do it despite the significant and not well known differences.

RECOMMEND: Provide this example in C# and C++

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes the COM stuff should be doable in C# too, with some interop code. I'll add an equivalent C# example for the custom validator case once the API is implemented and I can confirm a working sample. [Pending]

// MyCustom.cpp
// (Assume other standard cppwinrt-generated code related to MyCustom.MyPackageValidator are present.
// This sample only shows the custom hand-written parts.)
namespace winrt::MyCustom::implementation
{
// Implements a custom package validator that checks that the package does not delcare any capabilities.
struct MyCustomPackageValidator : MyCustomPackageValidatorT<MyCustomPackageValidator>
{
MyCustomPackageValidator();

bool IsPackageValid(winrt::Microsoft::Windows::Management::Deployment::AppxPackagingObject object)
{
winrt::com_ptr<IAppxPackageReader> packageReader;
if (FAILED(object.as<IAppxPackageReader>(packageReader.put())))
{
// object is not an msix package as expected (i.e. it is a bundle), reject it
return false;
}

winrt::com_ptr<IAppxManifestReader> manifestReader;
THROW_IF_FAILED(packageReader->GetManifest(manifestReader.put()));

winrt::com_ptr<IAppxManifestReader3> manifestReader3;
THROW_IF_FAILED(manifestReader->QueryInterface(manifestReader3.put()));

winrt::com_ptr<IAppxManifestCapabilitiesEnumerator> capabilitiesEnumerator;
THROW_IF_FAILED(manifestReader3->GetCapabilitiesByCapabilityClass(APPX_CAPABILITY_CLASS_ALL, capabilitiesEnumerator.put()));

BOOL hasCapabilities{};
THROW_IF_FAILED(capabilitiesEnumerator->GetHasCurrent(&hasCapabilities));
return !hasCapabilities;
}
};
}
```

```c++/winrt
// At the call site to PackageDeploymentManager, using the custom package validator
auto packageDeploymentManager{ winrt::Microsoft::Windows::Management::Deployment::PackageDeploymentManager::GetDefault() };

const auto packageUri{ L"https://example.com/package.msix"};
const winrt::hstring package{ packageUri.c_str() };

IVector<IPackageValidator> validators{ winrt::single_threaded_vector<IPackageValidator>() };
validators.Append(winrt::MyCustom::MyPackageValidator());

winrt::Microsoft::Windows::Management::Deployment::AddPackageOptions options;
options.PackageValidators.Insert(packageUri, validators);

auto deploymentOperation{ packageDeploymentManager.AddPackageAsync(package, options) };
auto deploymentResult{ WaitForDeploymentOperation(deploymentOperation) };
// deploymentOperation will fail with APPX_E_DIGEST_MISMATCH if any packageValidator rejects the package
```

# 5. Remarks

## 5.1. Platform Support
Expand Down Expand Up @@ -857,6 +971,41 @@ namespace Microsoft.Windows.Management.Deployment
NewerAvailable = 2,
};

[contract(PackageDeploymentContract, 3)]
[default_interface]
runtimeclass AppxPackagingObject{
// This is an interop class for COM types defined in AppxPackaging.idl.
// The WinRT side has no methods or properties, but the object supports QueryInterface into the COM interfaces
// IAppxPackageReader or IAppxBundleReader, whichever is relevant for the object being read.
}

[contract(PackageDeploymentContract, 3)]
interface IPackageValidator
{
Boolean IsPackageValid(AppxPackagingObject packagingObject);
}

[contract(PackageDeploymentContract, 3)]
runtimeclass PackageFamilyNameValidator : [default] IPackageValidator
{
PackageFamilyNameValidator(String expectedPackageFamilyName);
Boolean IsPackageValid(AppxPackagingObject packagingObject);
}

[contract(PackageDeploymentContract, 3)]
runtimeclass PackageMinimumVersionValidator : [default] IPackageValidator
{
PackageMinimumVersionValidator(UINT major, UINT minor, UINT build, UINT revision);
Boolean IsPackageValid(AppxPackagingObject packagingObject);
}

[contract(PackageDeploymentContract, 3)]
runtimeclass PackageCertificateEkuValidator : [default] IPackageValidator
{
PackageCertificateEkuValidator(String expectedCertificateEku);
Boolean IsPackageValid(AppxPackagingObject packagingObject);
}

/// The progress status of the deployment request.
/// @see https://learn.microsoft.com/uwp/api/windows.management.deployment.deploymentprogress.state
[contract(PackageDeploymentContract, 1)]
Expand Down Expand Up @@ -964,6 +1113,9 @@ namespace Microsoft.Windows.Management.Deployment

Boolean IsLimitToExistingPackagesSupported { get; }; // Requires Windows >= 10.0.22621.0 (aka Win11 22H2)
Boolean LimitToExistingPackages;

Boolean IsPackageValidatorsSupported{ get; };
IMap<Windows.Foundation.Uri, IVector<IPackageValidator>> PackageValidators{ get; };
}

// Requires Windows >= 10.0.19041.0 (aka 2004 aka 20H1)
Expand All @@ -988,6 +1140,9 @@ namespace Microsoft.Windows.Management.Deployment

Boolean IsExpectedDigestsSupported { get; }; // Requires Windows >= 10.0.22621.0 (aka Win11 22H2)
IMap<Windows.Foundation.Uri, String> ExpectedDigests{ get; };

Boolean IsPackageValidatorsSupported{ get; };
IMap<Windows.Foundation.Uri, IVector<IPackageValidator>> PackageValidators{ get; };
}

// Requires Windows >= 10.0.19041.0 (aka 2004 aka 20H1)
Expand Down