Skip to content

Commit 0cddda0

Browse files
authored
API Spec: PackageValidator API in PackageManagement.md (#5863)
Added documentation for new proposed API for PackageValidation
1 parent bd8e2c1 commit 0cddda0

File tree

1 file changed

+211
-0
lines changed

1 file changed

+211
-0
lines changed

specs/packagemanager/PackageManagement.md

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ but with additional functionality, improved developer experience and performance
2323
- [3.10. PackageVolume Repair](#310-packagevolume-repair)
2424
- [3.11. Usability](#311-usability)
2525
- [3.12. Is\*Provisioned()](#312-312-isprovisioned)
26+
- [3.13. PackageValidation](#313-packagevalidation)
2627
- [4. Examples](#4-examples)
2728
- [4.1. AddPackageAsync()](#41-addpackageasync)
2829
- [4.2. AddPackageByUriAsync()](#42-addpackagebyuriasync)
@@ -73,6 +74,7 @@ Additional functionality includes:
7374
* IsPackageRegistrationPending -- Is there an update waiting to register?
7475
* PackageSets -- Batch operations
7576
* PackageRuntimeManager -- Batch operations for use at runtime via Dynamic Dependencies
77+
* PackageValidation -- Validate a package has expected identity, signature, etc. before adding/staging
7678
* Usability -- Quality-of-Life enhancements
7779

7880
## 3.1. API Structure
@@ -398,6 +400,30 @@ Is\*Provisioned\*() methods determine if the target is provisioned.
398400

399401
These methods require administrative privileges.
400402

403+
## 3.13. PackageValidation
404+
405+
This API allows callers to verify that packages being processed by Add*, Ensure*, and Stage* APIs
406+
of PackageDeploymentManager match what are expected from their URI.
407+
408+
When adding or staging a package from an external source such as HTTP URI or uncontrolled file
409+
location, a malicious actor might perform a man-in-the-middle attack to intercept and tamper with
410+
the package data being read, causing a malicious package to be installed instead of the expected
411+
one. The package might also be tampered at the source through supply-chain attacks. Verifying
412+
the identity and signature of target packages helps ensure that such attacks have not happened.
413+
414+
The following runtimeclasses implement `PackageValidation` handlers, and are available for use
415+
directly:
416+
* `PackageFamilyNameValidator`: Validates that the package has the expected package family name.
417+
* `PackageMinimumVersionValidator`: Validates that the package has at least the expected minimum
418+
version number.
419+
* `PackageCertificateEkuValidator`: Validates that the certificate used to sign the package
420+
contains the expected Extended Key Usage (EKU) value.
421+
422+
Custom validators can be implemented as handlers for the
423+
`PackageValidationEventSource.ValidationRequested` event. These can verify any part of packages'
424+
[footprint data](https://learn.microsoft.com/windows/win32/api/appxpackaging/ne-appxpackaging-appx_bundle_footprint_file_type)
425+
(manifest, block map, and digital signature).
426+
401427
# 4. Examples
402428

403429
## 4.1. AddPackageAsync()
@@ -761,6 +787,136 @@ PackageVersion ToVersion(uint major, uint minor, uint build, uint revision) =>
761787
};
762788
```
763789

790+
## 4.9. PackageValidation
791+
792+
### 4.9.1. Using built-in PackageValidation handlers
793+
794+
This example shows how to use built-in PackageValidation handlers to verify
795+
package family name, minimum version, and certificate EKU.
796+
797+
```c#
798+
using Microsoft.Windows.Management.Deployment;
799+
800+
var pdm = PackageDeploymentManager().GetDefault();
801+
var packageUri = new Uri("https://contoso.com/package.msix");
802+
803+
var options = new AddPackageOptions();
804+
var validators = options.GetValidationEventSourceForUri(packageUri).ValidationRequested;
805+
validators += new PackageFamilyNameValidator("ExpectedFamilyName_1234567890abc").Handler;
806+
validators += new PackageMinimumVersionValidator(new Windows.ApplicationModel.PackageVersion(2, 0, 0, 0)).Handler;
807+
validators += new PackageCertificateEkuValidator("1.3.6.1.4.1.311.2.1.11").Handler;
808+
809+
var deploymentResult = await pdm.AddPackageAsync(packageUri, options);
810+
if (deploymentResult.Status == PackageDeploymentStatus.CompletedSuccess)
811+
{
812+
Console.WriteLine("Success");
813+
}
814+
else // deploymentResult.Status == PackageDeploymentStatus.CompletedFailure
815+
{
816+
var error = deploymentResult.Error.HResult;
817+
if (error = 0x80080219 /*APPX_E_DIGEST_MISMATCH*/)
818+
{
819+
Console.WriteLine("The package retrieved from the specified URI isn't expected according to PackageValidators");
820+
}
821+
else
822+
{
823+
var extendedError = deploymentResult.ExtendedError.HResult;
824+
var message = deploymentResult.MessageText;
825+
Console.WriteLine($"An error occurred while adding the package. Error 0x{error:X08} ExtendedError 0x{extendedError:X08} {message}");
826+
}
827+
}
828+
```
829+
830+
### 4.9.2. Using custom PackageValidation handlers
831+
832+
This example shows how to implement a custom PackageValidation handler, and use it to verify a package.
833+
834+
```c#
835+
using Microsoft.Windows.Management.Deployment;
836+
837+
// Consuming COM APIs for IAppxPackageReader, IAppxManifestReader, etc. requires C# interop definitions.
838+
// Assume standard interop definitions exist for relevant APIs in AppxPackaging.h.
839+
[Guid("b5c49650-99bc-481c-9a34-3d53a4106708"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
840+
public interface IAppxPackageReader { ... }
841+
842+
// Implementation of the custom package validation handler
843+
bool IsPackageValid(object package)
844+
{
845+
var packageReader = package as IAppxPackageReader;
846+
if (packageReader == null)
847+
{
848+
// object is not an msix package as expected (i.e. it is a bundle), reject it
849+
return false;
850+
}
851+
852+
IAppxManifestReader manifestReader;
853+
packageReader.GetManifest(out manifestReader);
854+
855+
var manifestReader3 = manifestReader as IAppxManifestReader3;
856+
IAppxManifestCapabilitiesEnumerator capabilitiesEnumerator;
857+
manifestReader3.GetCapabilitiesByCapabilityClass(APPX_CAPABILITY_CLASS_ALL, out capabilitiesEnumerator);
858+
859+
bool hasCapabilities;
860+
capabilitiesEnumerator.GetHasCurrent(out hasCapabilities);
861+
return !hasCapabilities;
862+
}
863+
864+
void MyPackageValidationHandler(object sender, PackageValidationEventArgs args)
865+
{
866+
var deferral = args.GetDeferral();
867+
868+
bool isValid = false;
869+
try
870+
{
871+
isValid = IsPackageValid(args.Package);
872+
}
873+
finally
874+
{
875+
if (isValid)
876+
{
877+
Log("Package at URI is valid: " + args.PackageUri);
878+
}
879+
else
880+
{
881+
Log("Package at URI is not valid: " + args.PackageUri);
882+
args.Cancel = true;
883+
}
884+
885+
deferral.Complete();
886+
}
887+
}
888+
889+
// Code that utilizes the custom package validator
890+
void InstallPackageWithCustomValidation()
891+
{
892+
var pdm = PackageDeploymentManager().GetDefault();
893+
var packageUri = new Uri("https://contoso.com/package.msix");
894+
895+
var options = new AddPackageOptions();
896+
options.GetValidationEventSourceForUri(packageUri).ValidationRequested += MyPackageValidationHandler;
897+
898+
var deploymentResult = await pdm.AddPackageAsync(packageUri, options);
899+
if (deploymentResult.Status == PackageDeploymentStatus.CompletedSuccess)
900+
{
901+
Console.WriteLine("Success");
902+
}
903+
else // deploymentResult.Status == PackageDeploymentStatus.CompletedFailure
904+
{
905+
var error = deploymentResult.Error.HResult;
906+
if (error = 0x80080219 /*APPX_E_DIGEST_MISMATCH*/)
907+
{
908+
Console.WriteLine("The package retrieved from the specified URI isn't expected according to PackageValidators");
909+
}
910+
else
911+
{
912+
var extendedError = deploymentResult.ExtendedError.HResult;
913+
var message = deploymentResult.MessageText;
914+
Console.WriteLine($"An error occurred while adding the package. Error 0x{error:X08} ExtendedError 0x{extendedError:X08} {message}");
915+
}
916+
}
917+
}
918+
```
919+
764920
# 5. Remarks
765921

766922
## 5.1. Platform Support
@@ -857,6 +1013,43 @@ namespace Microsoft.Windows.Management.Deployment
8571013
NewerAvailable = 2,
8581014
};
8591015

1016+
[contract(PackageDeploymentContract, 3)]
1017+
runtimeclass PackageValidationEventArgs
1018+
{
1019+
Windows.Foundation.Uri PackageUri{ get; };
1020+
IInspectable AppxPackagingObject{ get; };
1021+
Boolean Cancel;
1022+
1023+
Windows.Foundation.Deferral GetDeferral();
1024+
}
1025+
1026+
[contract(PackageDeploymentContract, 3)]
1027+
runtimeclass PackageValidationEventSource
1028+
{
1029+
event Windows.Foundation.TypedEventHandler<PackageValidationEventSource, PackageValidationEventArgs> ValidationRequested;
1030+
}
1031+
1032+
[contract(PackageDeploymentContract, 3)]
1033+
runtimeclass PackageFamilyNameValidator
1034+
{
1035+
PackageFamilyNameValidator(String packageUri, String expectedPackageFamilyName);
1036+
Windows.Foundation.TypedEventHandler<PackageValidationEventSource, PackageValidationEventArgs> Handler{ get; };
1037+
}
1038+
1039+
[contract(PackageDeploymentContract, 3)]
1040+
runtimeclass PackageMinimumVersionValidator
1041+
{
1042+
PackageMinimumVersionValidator(Windows.ApplicationModel.PackageVersion minimumVersion);
1043+
Windows.Foundation.TypedEventHandler<PackageValidationEventSource, PackageValidationEventArgs> Handler{ get; };
1044+
}
1045+
1046+
[contract(PackageDeploymentContract, 3)]
1047+
runtimeclass PackageCertificateEkuValidator
1048+
{
1049+
PackageCertificateEkuValidator(String expectedCertificateEku);
1050+
Windows.Foundation.TypedEventHandler<PackageValidationEventSource, PackageValidationEventArgs> Handler{ get; };
1051+
}
1052+
8601053
/// The progress status of the deployment request.
8611054
/// @see https://learn.microsoft.com/uwp/api/windows.management.deployment.deploymentprogress.state
8621055
[contract(PackageDeploymentContract, 1)]
@@ -964,6 +1157,15 @@ namespace Microsoft.Windows.Management.Deployment
9641157

9651158
Boolean IsLimitToExistingPackagesSupported { get; }; // Requires Windows >= 10.0.22621.0 (aka Win11 22H2)
9661159
Boolean LimitToExistingPackages;
1160+
1161+
[contract(PackageDeploymentContract, 3)]
1162+
Boolean IsPackageValidationSupported{ get; };
1163+
1164+
[contract(PackageDeploymentContract, 3)]
1165+
IMapView<Windows.Foundation.Uri, PackageValidationEventSource> PackageValidators{ get; };
1166+
1167+
[contract(PackageDeploymentContract, 3)]
1168+
PackageValidationEventSource GetValidationEventSourceForUri(Windows.Foundation.Uri uri);
9671169
}
9681170

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

9891191
Boolean IsExpectedDigestsSupported { get; }; // Requires Windows >= 10.0.22621.0 (aka Win11 22H2)
9901192
IMap<Windows.Foundation.Uri, String> ExpectedDigests{ get; };
1193+
1194+
[contract(PackageDeploymentContract, 3)]
1195+
Boolean IsPackageValidationSupported{ get; };
1196+
1197+
[contract(PackageDeploymentContract, 3)]
1198+
IMapView<Windows.Foundation.Uri, PackageValidationEventSource> PackageValidators{ get; };
1199+
1200+
[contract(PackageDeploymentContract, 3)]
1201+
PackageValidationEventSource GetValidationEventSourceForUri(Windows.Foundation.Uri uri);
9911202
}
9921203

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

0 commit comments

Comments
 (0)