Skip to content
Merged
Changes from all commits
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
211 changes: 211 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. PackageValidation](#313-packagevalidation)
- [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
* PackageValidation -- 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,30 @@ Is\*Provisioned\*() methods determine if the target is provisioned.

These methods require administrative privileges.

## 3.13. PackageValidation

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.

When adding or staging a package from an external source such as HTTP URI or uncontrolled file
location, a malicious actor might perform a man-in-the-middle attack to intercept and tamper with
the package data being read, causing a malicious package to be installed instead of the expected
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:
* `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'
[footprint data](https://learn.microsoft.com/windows/win32/api/appxpackaging/ne-appxpackaging-appx_bundle_footprint_file_type)
(manifest, block map, and digital signature).

# 4. Examples

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

## 4.9. PackageValidation

### 4.9.1. Using built-in PackageValidation handlers

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

```c#
using Microsoft.Windows.Management.Deployment;

var pdm = PackageDeploymentManager().GetDefault();
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;

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 isn't expected according to PackageValidators");
}
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.2. Using custom PackageValidation handlers

This example shows how to implement a custom PackageValidation handler, and use it to verify a package.
Copy link
Member

Choose a reason for hiding this comment

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

NIT: "...implement a custom PackageValidation handler for a .msix, and use it..."

The code below doesn't handle bundles and, while it could, better to keep the sample smaller and more focused.


```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 validation handler
bool IsPackageValid(object package)
{
var packageReader = package as IAppxPackageReader;
if (packageReader == null)
{
// object is not an msix package as expected (i.e. it is a bundle), reject it
return false;
}

IAppxManifestReader manifestReader;
packageReader.GetManifest(out manifestReader);

var manifestReader3 = manifestReader as IAppxManifestReader3;
IAppxManifestCapabilitiesEnumerator capabilitiesEnumerator;
manifestReader3.GetCapabilitiesByCapabilityClass(APPX_CAPABILITY_CLASS_ALL, out capabilitiesEnumerator);

bool hasCapabilities;
capabilitiesEnumerator.GetHasCurrent(out hasCapabilities);
return !hasCapabilities;
}

void MyPackageValidationHandler(object sender, PackageValidationEventArgs args)
{
var deferral = args.GetDeferral();

bool isValid = false;
try
{
isValid = IsPackageValid(args.Package);
}
finally
{
if (isValid)
{
Log("Package at URI is valid: " + args.PackageUri);
}
else
{
Log("Package at URI is not valid: " + args.PackageUri);
args.Cancel = true;
}

deferral.Complete();
}
}

// Code that utilizes the custom package validator
void InstallPackageWithCustomValidation()
{
var pdm = PackageDeploymentManager().GetDefault();
var packageUri = new Uri("https://contoso.com/package.msix");

var options = new AddPackageOptions();
options.GetValidationEventSourceForUri(packageUri).ValidationRequested += MyPackageValidationHandler;

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 isn't expected according to PackageValidators");
}
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}");
}
}
}
```

# 5. Remarks

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

[contract(PackageDeploymentContract, 3)]
runtimeclass PackageValidationEventArgs
{
Windows.Foundation.Uri PackageUri{ get; };
IInspectable AppxPackagingObject{ get; };
Boolean Cancel;

Windows.Foundation.Deferral GetDeferral();
}

[contract(PackageDeploymentContract, 3)]
runtimeclass PackageValidationEventSource
{
event Windows.Foundation.TypedEventHandler<PackageValidationEventSource, PackageValidationEventArgs> ValidationRequested;
}

[contract(PackageDeploymentContract, 3)]
runtimeclass PackageFamilyNameValidator
{
PackageFamilyNameValidator(String packageUri, String expectedPackageFamilyName);
Windows.Foundation.TypedEventHandler<PackageValidationEventSource, PackageValidationEventArgs> Handler{ get; };
}

[contract(PackageDeploymentContract, 3)]
runtimeclass PackageMinimumVersionValidator
{
PackageMinimumVersionValidator(Windows.ApplicationModel.PackageVersion minimumVersion);
Windows.Foundation.TypedEventHandler<PackageValidationEventSource, PackageValidationEventArgs> Handler{ get; };
}

[contract(PackageDeploymentContract, 3)]
runtimeclass PackageCertificateEkuValidator
{
PackageCertificateEkuValidator(String expectedCertificateEku);
Windows.Foundation.TypedEventHandler<PackageValidationEventSource, PackageValidationEventArgs> Handler{ get; };
}

/// 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 +1157,15 @@ namespace Microsoft.Windows.Management.Deployment

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

[contract(PackageDeploymentContract, 3)]
Boolean IsPackageValidationSupported{ get; };

[contract(PackageDeploymentContract, 3)]
IMapView<Windows.Foundation.Uri, PackageValidationEventSource> PackageValidators{ get; };

[contract(PackageDeploymentContract, 3)]
PackageValidationEventSource GetValidationEventSourceForUri(Windows.Foundation.Uri uri);
}

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

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

[contract(PackageDeploymentContract, 3)]
Boolean IsPackageValidationSupported{ get; };

[contract(PackageDeploymentContract, 3)]
IMapView<Windows.Foundation.Uri, PackageValidationEventSource> PackageValidators{ get; };

[contract(PackageDeploymentContract, 3)]
PackageValidationEventSource GetValidationEventSourceForUri(Windows.Foundation.Uri uri);
}

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