From 16148f7fbdf963ccee860b00b53d7ce1fed7ae7d Mon Sep 17 00:00:00 2001 From: TheCakeIsNaOH Date: Tue, 9 Jan 2024 20:57:26 -0600 Subject: [PATCH] (#1144) Implement usePackageHashValidation feature This adds a check to both install and upgrade to validate that the downloaded .nupkg file has the same hash as the source metadata. The check is conducted before the package is installed. If the source does not provide a sha512 checksum or if usePackageHashValidation is disabled, then the check is skipped. Only sha512 is supported because it is the only provided package hash type after download. The provided hash is used because it accounts for package signing correctly. --- .../services/NugetService.cs | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index ea7f171255..e8f7e12ca1 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -26,6 +26,7 @@ using chocolatey.infrastructure.adapters; using chocolatey.infrastructure.app.utility; using chocolatey.infrastructure.commandline; +using chocolatey.infrastructure.cryptography; using chocolatey.infrastructure.app.configuration; using chocolatey.infrastructure.app.domain; using chocolatey.infrastructure.guards; @@ -33,6 +34,7 @@ using chocolatey.infrastructure.app.nuget; using chocolatey.infrastructure.platforms; using chocolatey.infrastructure.results; +using chocolatey.infrastructure.services; using chocolatey.infrastructure.tolerance; using DateTime = chocolatey.infrastructure.adapters.DateTime; using Environment = System.Environment; @@ -47,7 +49,8 @@ using NuGet.Protocol.Core.Types; using NuGet.Resolver; using NuGet.Versioning; -using chocolatey.infrastructure.services; +using Newtonsoft.Json.Bson; +using chocolatey.infrastructure.configuration; namespace chocolatey.infrastructure.app.services { @@ -842,7 +845,7 @@ Version was specified as '{0}'. It is possible that version NuGetEnvironment.GetFolderPath(NuGetFolderPath.Temp), _nugetLogger, CancellationToken.None).GetAwaiter().GetResult()) { - //TODO, do check on downloadResult + ValidatePackageHash(config, packageDependencyInfo, downloadResult); nugetProject.InstallPackageAsync( packageDependencyInfo, @@ -1596,7 +1599,7 @@ public virtual ConcurrentDictionary Upgrade(ChocolateyCon NuGetEnvironment.GetFolderPath(NuGetFolderPath.Temp), _nugetLogger, CancellationToken.None).GetAwaiter().GetResult()) { - //TODO, do check on downloadResult + ValidatePackageHash(config, packageDependencyInfo, downloadResult); nugetProject.InstallPackageAsync( packageDependencyInfo, @@ -2971,6 +2974,50 @@ private void SetRemotePackageNamesIfAllSpecified(ChocolateyConfiguration config, } } + private void ValidatePackageHash(ChocolateyConfiguration config, SourcePackageDependencyInfo packageDependencyInfo, DownloadResourceResult downloadResult) + { + if (!config.Features.UsePackageHashValidation) + { + this.Log().Debug("Skipping package hash validation as feature '{0}' is not enabled.".FormatWith(ApplicationParameters.Features.UsePackageHashValidation)); + } + else if (packageDependencyInfo.PackageHash is null) + { + // Folder based sources and v3 api based sources do not provide package hashes when getting metadata + this.Log().Debug("Source does not provide a package hash, skipping package hash validation."); + } + else + { + var hashInfo = HashConverter.ConvertHashToHex(packageDependencyInfo.PackageHash); + + if (hashInfo.hashType == CryptoHashProviderType.Sha512) + { + using (var metadataFileStream = + downloadResult.PackageReader.GetStream(PackagingCoreConstants.NupkgMetadataFileExtension)) + { + var metadataFileContents = NupkgMetadataFileFormat.Read(metadataFileStream, _nugetLogger, + PackagingCoreConstants.NupkgMetadataFileExtension); + var metadataFileHashInfo = HashConverter.ConvertHashToHex(metadataFileContents.ContentHash); + if (hashInfo.convertedHash.Equals(metadataFileHashInfo.convertedHash, StringComparison.OrdinalIgnoreCase)) + { + this.Log().Debug("Package hash matches expected hash."); + } + else + { + var errorMessage = + "Package hash '{0}' did not match expected hash '{1}'." + .FormatWith(metadataFileContents.ContentHash, + hashInfo.convertedHash); + throw new InvalidDataException(errorMessage); + } + } + } + else + { + this.Log().Warn("Source is not providing a SHA512 hash, cannot validate package hash."); + } + } + } + #pragma warning disable IDE0022, IDE1006 [Obsolete("This overload is deprecated and will be removed in v3.")] public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction)