Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(#1144) Add validation for package hash #3269

Merged
merged 3 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions src/chocolatey/chocolatey.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@
<Compile Include="infrastructure.app\utility\ArgumentsUtility.cs" />
<Compile Include="infrastructure.app\utility\PackageUtility.cs" />
<Compile Include="infrastructure.app\validations\SystemStateValidation.cs" />
<Compile Include="infrastructure\cryptography\HashConverter.cs" />
<Compile Include="infrastructure\filesystem\FileSystem.cs" />
<Compile Include="FileSystemExtensions.cs" />
<Compile Include="infrastructure\information\ExtensionInformation.cs" />
Expand Down
1 change: 1 addition & 0 deletions src/chocolatey/infrastructure.app/ApplicationParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ public static class Features
public static readonly string LogValidationResultsOnWarnings = "logValidationResultsOnWarnings";
public static readonly string UsePackageRepositoryOptimizations = "usePackageRepositoryOptimizations";
public static readonly string DisableCompatibilityChecks = "disableCompatibilityChecks";
public static readonly string UsePackageHashValidation = "usePackageHashValidation";
}

public static class Messages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ private static void SetAllFeatureFlags(ChocolateyConfiguration config, ConfigFil
config.Features.LogWithoutColor = SetFeatureFlag(ApplicationParameters.Features.LogWithoutColor, configFileSettings, defaultEnabled: false, description: "Log without color - Do not show colorization in logging output.");
config.Features.LogValidationResultsOnWarnings = SetFeatureFlag(ApplicationParameters.Features.LogValidationResultsOnWarnings, configFileSettings, defaultEnabled: true, description: "Log validation results on warnings - Should the validation results be logged if there are warnings?");
config.Features.UsePackageRepositoryOptimizations = SetFeatureFlag(ApplicationParameters.Features.UsePackageRepositoryOptimizations, configFileSettings, defaultEnabled: true, description: "Use Package Repository Optimizations - Turn on optimizations for reducing bandwidth with repository queries during package install/upgrade/outdated operations. Should generally be left enabled, unless a repository needs to support older methods of query. When disabled, this makes queries similar to the way they were done in earlier versions of Chocolatey.");
config.Features.UsePackageHashValidation = SetFeatureFlag(ApplicationParameters.Features.UsePackageHashValidation, configFileSettings, defaultEnabled: false, description: "Use Package Hash Validation - Check the hash of the downloaded package file against the source provided hash. Only supports sources that provide SHA512 hashes. Disabled by default. Available in 2.3.0+");
config.PromptForConfirmation = !SetFeatureFlag(ApplicationParameters.Features.AllowGlobalConfirmation, configFileSettings, defaultEnabled: false, description: "Prompt for confirmation in scripts or bypass.");
config.DisableCompatibilityChecks = SetFeatureFlag(ApplicationParameters.Features.DisableCompatibilityChecks, configFileSettings, defaultEnabled: false, description: "Disable Compatibility Checks - Disable showing a warning when there is an incompatibility between Chocolatey CLI and Chocolatey Licensed Extension. Available in 1.1.0+");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ public sealed class FeaturesConfiguration
public bool ExitOnRebootDetected { get; set; }
public bool LogValidationResultsOnWarnings { get; set; }
public bool UsePackageRepositoryOptimizations { get; set; }
public bool UsePackageHashValidation { get; set; }
}

//todo: #2565 retrofit other command configs this way
Expand Down
59 changes: 53 additions & 6 deletions src/chocolatey/infrastructure.app/services/NugetService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@
using System.Threading;
using Chocolatey.NuGet.Frameworks;
using chocolatey.infrastructure.adapters;
using chocolatey.infrastructure.app.utility;
using chocolatey.infrastructure.commandline;
using chocolatey.infrastructure.app.configuration;
using chocolatey.infrastructure.app.domain;
using chocolatey.infrastructure.app.nuget;
using chocolatey.infrastructure.app.utility;
using chocolatey.infrastructure.commandline;
using chocolatey.infrastructure.cryptography;
using chocolatey.infrastructure.guards;
using chocolatey.infrastructure.logging;
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;
Expand All @@ -47,7 +49,6 @@
using NuGet.Protocol.Core.Types;
using NuGet.Resolver;
using NuGet.Versioning;
using chocolatey.infrastructure.services;

namespace chocolatey.infrastructure.app.services
{
Expand Down Expand Up @@ -842,7 +843,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,
Expand Down Expand Up @@ -1596,7 +1597,7 @@ public virtual ConcurrentDictionary<string, PackageResult> Upgrade(ChocolateyCon
NuGetEnvironment.GetFolderPath(NuGetFolderPath.Temp),
_nugetLogger, CancellationToken.None).GetAwaiter().GetResult())
{
//TODO, do check on downloadResult
ValidatePackageHash(config, packageDependencyInfo, downloadResult);

nugetProject.InstallPackageAsync(
packageDependencyInfo,
Expand Down Expand Up @@ -2971,6 +2972,52 @@ 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<PackageResult, ChocolateyConfiguration> ensureAction)
Expand Down
65 changes: 65 additions & 0 deletions src/chocolatey/infrastructure/cryptography/HashConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright © 2017 - 2021 Chocolatey Software, Inc
// Copyright © 2011 - 2017 RealDimensions Software, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
//
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;

namespace chocolatey.infrastructure.cryptography
{
public static class HashConverter
{
public static (string ConvertedHash, CryptoHashProviderType HashType) ConvertHashToHex(string hash)
{
// Sha 1 in base64
if (hash.Length.Equals(28))
{
return (BitConverter.ToString(Convert.FromBase64String(hash)).Replace("-", string.Empty), CryptoHashProviderType.Sha1);
}

// Sha 1 in hex
if (hash.Length.Equals(40))
{
return (hash, CryptoHashProviderType.Sha1);
}

// Sha 256 in base64
if (hash.Length.Equals(44))
{
return (BitConverter.ToString(Convert.FromBase64String(hash)).Replace("-", string.Empty), CryptoHashProviderType.Sha256);
}

// Sha 256 in hex
if (hash.Length.Equals(64))
{
return (hash, CryptoHashProviderType.Sha256);
}

// Sha 512 in base64
if (hash.Length.Equals(88))
{
return (BitConverter.ToString(Convert.FromBase64String(hash)).Replace("-", string.Empty), CryptoHashProviderType.Sha512);
}

// Sha 512 in hex
if (hash.Length.Equals(128))
{
return (hash, CryptoHashProviderType.Sha512);
}

"chocolatey".Log().Warn("Unknown Hash type, Length '{0}', Hash '{1}'".FormatWith(hash, hash.Length));
return (hash, CryptoHashProviderType.Unknown);
}
}
}
Loading