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

(#3477) Replace Get-ChecksumValid with Assert-ValidChecksum cmdlet. #3525

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions src/Chocolatey.PowerShell/Chocolatey.PowerShell.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,18 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Commands\AssertValidChecksumCommand.cs" />
<Compile Include="Shared\ChecksumExeNotFoundException.cs" />
<Compile Include="Shared\ChecksumVerificationFailedException.cs" />
<Compile Include="Shared\ChecksumMissingException.cs" />
<Compile Include="Commands\GetEnvironmentVariableCommand.cs" />
<Compile Include="Commands\GetEnvironmentVariableNamesCommand.cs" />
<Compile Include="Commands\InstallChocolateyPathCommand.cs" />
<Compile Include="Commands\SetEnvironmentVariableCommand.cs" />
<Compile Include="Commands\TestProcessAdminRightsCommand.cs" />
<Compile Include="Commands\UninstallChocolateyPathCommand.cs" />
<Compile Include="Commands\UpdateSessionEnvironmentCommand.cs" />
<Compile Include="Helpers\ChecksumValidator.cs" />
<Compile Include="Helpers\Elevation.cs" />
<Compile Include="Helpers\EnvironmentHelper.cs" />
<Compile Include="Helpers\Paths.cs" />
Expand All @@ -74,6 +79,7 @@
<Compile Include="..\SolutionVersion.cs">
<Link>Properties\SolutionVersion.cs</Link>
</Compile>
<Compile Include="Shared\ChecksumType.cs" />
<Compile Include="Shared\ChocolateyCmdlet.cs" />
<Compile Include="Shared\EnvironmentVariables.cs" />
<Compile Include="Win32\NativeMethods.cs" />
Expand Down
72 changes: 72 additions & 0 deletions src/Chocolatey.PowerShell/Commands/AssertValidChecksumCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright © 2017 - 2024 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;
using System.IO;
using System.Management.Automation;
using Chocolatey.PowerShell.Helpers;
using Chocolatey.PowerShell.Shared;

namespace Chocolatey.PowerShell.Commands
{
[Cmdlet(VerbsLifecycle.Assert, "ValidChecksum")]
[OutputType(typeof(void))]
public class AssertValidChecksumCommand : ChocolateyCmdlet
{
[Parameter(Mandatory = true, Position = 0)]
[Alias("File", "FilePath")]
public string Path { get; set; }

[Parameter(Position = 1)]
public string Checksum { get; set; } = string.Empty;

[Parameter(Position = 2)]
public ChecksumType ChecksumType { get; set; } = ChecksumType.Md5;

[Parameter(Position = 3)]
[Alias("OriginalUrl")]
public string Url { get; set; } = string.Empty;

protected override void End()
{
try
{
ChecksumValidator.AssertChecksumValid(this, Path, Checksum, ChecksumType, Url);
}
catch (ChecksumMissingException error)
{
ThrowTerminatingError(new ErrorRecord(error, $"{ErrorId}.ChecksumMissing", ErrorCategory.MetadataError, Checksum));
}
catch (ChecksumVerificationFailedException error)
{
ThrowTerminatingError(new ErrorRecord(error, $"{ErrorId}.BadChecksum", ErrorCategory.InvalidResult, Checksum));
}
catch (FileNotFoundException error)
{
ThrowTerminatingError(new ErrorRecord(error, $"{ErrorId}.FileNotFound", ErrorCategory.ObjectNotFound, Path));
}
catch (ChecksumExeNotFoundException error)
{
ThrowTerminatingError(new ErrorRecord(error, $"{ErrorId}.ChecksumExeNotFound", ErrorCategory.ObjectNotFound, targetObject: null));
}
catch (Exception error)
{
ThrowTerminatingError(new ErrorRecord(error, $"{ErrorId}.Unknown", ErrorCategory.NotSpecified, Path));
}
}

}
}
181 changes: 181 additions & 0 deletions src/Chocolatey.PowerShell/Helpers/ChecksumValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Copyright © 2017 - 2024 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;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Management.Automation;
using System.Management.Automation.Host;
using Chocolatey.PowerShell.Shared;
using static Chocolatey.PowerShell.Helpers.PSHelper;

namespace Chocolatey.PowerShell.Helpers
{
/// <summary>
/// Helper class to validate checksums. Used by <see cref="Commands.AssertValidChecksumCommand"/>, and any other commands that need to validate checksums.
/// </summary>
public static class ChecksumValidator
{
/// <summary>
/// Tests whether a given <paramref name="checksum"/> matches the checksum of a given file.
/// </summary>
/// <param name="cmdlet">The cmdlet calling the method.</param>
/// <param name="path">The path to the file to verify the checksum of.</param>
/// <param name="checksum">The checksum value to validate against.</param>
/// <param name="checksumType">The type of the checksum.</param>
/// <param name="url">The original url that the file was downloaded from, if any.</param>
/// <param name="error">If this method returns false, this will contain an exception that can be raised if needed.</param>
/// <returns>True if the actual checksum of the file matches the given checksum, otherwise False.</returns>
public static bool IsValid(PSCmdlet cmdlet, string path, string checksum, ChecksumType? checksumType, string url, out Exception error)
{
if (checksumType is null)
{
checksumType = ChecksumType.Md5;
}

if (IsEqual(Environment.GetEnvironmentVariable(EnvironmentVariables.ChocolateyIgnoreChecksums), "true"))
{
cmdlet.WriteWarning("Ignoring checksums due to feature checksumFiles turned off or option --ignore-checksums set.");
error = null;
return true;
}

if (string.IsNullOrWhiteSpace(checksum))
{
if (IsEqual(Environment.GetEnvironmentVariable(EnvironmentVariables.ChocolateyAllowEmptyChecksums), "true"))
{
cmdlet.WriteDebug("Empty checksums are allowed due to allowEmptyChecksums feature or option.");
error = null;
return true;
}

var isHttpsUrl = !string.IsNullOrEmpty(url) && url.ToLower().StartsWith("https");

if (isHttpsUrl && IsEqual(Environment.GetEnvironmentVariable(EnvironmentVariables.ChocolateyAllowEmptyChecksumsSecure), "true"))
{
cmdlet.WriteDebug("Download from HTTPS source with feature 'allowEmptyChecksumsSecure' enabled.");
error = null;
return true;
}

cmdlet.WriteWarning("Missing package checksums are not allowed (by default for HTTP/FTP, \n HTTPS when feature 'allowEmptyChecksumsSecure' is disabled) for \n safety and security reasons. Although we strongly advise against it, \n if you need this functionality, please set the feature \n 'allowEmptyChecksums' ('choco feature enable -n \n allowEmptyChecksums') \n or pass in the option '--allow-empty-checksums'. You can also pass \n checksums at runtime (recommended). See `choco install -?` for details.");
cmdlet.WriteDebug("If you are a maintainer attempting to determine the checksum for packaging purposes, please run \n 'choco install checksum' and run 'checksum -t sha256 -f $file' \n Ensure you do this for all remote resources.");

if (GetPSVersion().Major >= 4)
{
cmdlet.WriteDebug("Because you are running PowerShell with a major version of v4 or greater, you could also opt to run \n '(Get-FileHash -Path $file -Algorithm SHA256).Hash' \n rather than install a separate tool.");
}

if (IsEqual(Environment.GetEnvironmentVariable(EnvironmentVariables.ChocolateyPowerShellHost), "true")
&& !(cmdlet.Host is null))
{
const string prompt = "Do you wish to allow the install to continue (not recommended)?";
var info = string.Format(
"The integrity of the file '{0}'{1} has not been verified by a checksum in the package scripts",
GetFileName(path),
string.IsNullOrWhiteSpace(url) ? string.Empty : $" from '{url}'");

var choices = new Collection<ChoiceDescription>
{
new ChoiceDescription("&Yes"),
new ChoiceDescription("&No"),
};

var selection = cmdlet.Host.UI.PromptForChoice(info, prompt, choices, defaultChoice: 1);

if (selection == 0)
{
error = null;
return true;
}
}

var errorMessage = isHttpsUrl
? "This package downloads over HTTPS but does not yet have package checksums to verify the package. We recommend asking the maintainer to add checksums to this package. In the meantime if you need this package to work correctly, please enable the feature allowEmptyChecksumsSecure, provide the runtime switch '--allow-empty-checksums-secure', or pass in checksums at runtime (recommended - see 'choco install -?' / 'choco upgrade -?' for details)."
: "Empty checksums are no longer allowed by default for non-secure sources. Please ask the maintainer to add checksums to this package. In the meantime if you need this package to work correctly, please enable the feature allowEmptyChecksums, provide the runtime switch '--allow-empty-checksums', or pass in checksums at runtime (recommended - see 'choco install -?' / 'choco upgrade -?' for details). It is strongly advised against allowing empty checksums for non-internal HTTP/FTP sources.";

error = new ChecksumMissingException(errorMessage);
return false;
}

if (!FileExists(cmdlet, path))
{
error = new FileNotFoundException($"Unable to checksum a file that doesn't exist - Could not find file '{path}'", path);
return false;
}

var checksumExe = CombinePaths(cmdlet, GetInstallLocation(cmdlet), "tools", "checksum.exe");
if (!FileExists(cmdlet, checksumExe))
{
error = new FileNotFoundException("Unable to locate 'checksum.exe', your Chocolatey installation may be incomplete or damaged. Try reinstalling chocolatey with 'choco install chocolatey --force'.", checksumExe);
return false;
}

cmdlet.WriteDebug($"checksum.exe found at '{checksumExe}'");
var arguments = string.Format(
"-c=\"{0}\" -t=\"{1}\" -f=\"{2}\"",
checksum,
checksumType.ToString().ToLower(),
path);

cmdlet.WriteDebug($"Executing command ['{checksumExe}' {arguments}]");

var process = new Process
{
StartInfo = new ProcessStartInfo(checksumExe, arguments)
{
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
},
};

process.Start();
process.WaitForExit();

var exitCode = process.ExitCode;
process.Dispose();

cmdlet.WriteDebug($"Command ['{checksumExe}' {arguments}] exited with '{exitCode}'");

if (exitCode != 0)
{
error = new ChecksumVerificationFailedException($"Checksum for '{path}' did not match '{checksum}' for checksum type '{checksumType}'. Consider passing the actual checksums through with `--checksum --checksum64` once you validate the checksums are appropriate. A less secure option is to pass `--ignore-checksums` if necessary.", checksum, path);
return false;
}

error = null;
return true;
}

/// <summary>
/// Validate the checksum of a file against an expected <paramref name="checksum"/> and throw if the checksum does not match.
/// </summary>
/// <param name="cmdlet">The cmdlet calling the method.</param>
/// <param name="path">The path to the file to verify the checksum for.</param>
/// <param name="checksum">The expected checksum value.</param>
/// <param name="checksumType">The type of the checksum to look for.</param>
/// <param name="url">The url the file was downloaded from originally, if any.</param>
public static void AssertChecksumValid(PSCmdlet cmdlet, string path, string checksum, ChecksumType? checksumType, string url)
{
if (!IsValid(cmdlet, path, checksum, checksumType, url, out var exception))
{
throw exception;
}
}
}
}
24 changes: 22 additions & 2 deletions src/Chocolatey.PowerShell/Helpers/EnvironmentHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ public static class EnvironmentHelper
private const string MachineEnvironmentRegistryKeyName = @"SYSTEM\CurrentControlSet\Control\Session Manager\Environment\";
private const string UserEnvironmentRegistryKeyName = "Environment";

/// <summary>
/// Get an environment variable from the current process scope by name.
/// </summary>
/// <param name="name">The name of the variable to retrieve.</param>
/// <returns>The value of the environment variable.</returns>
public static string GetVariable(string name)
{
return Environment.GetEnvironmentVariable(name);
}

/// <summary>
/// Gets the value of the environment variable with the target <paramref name="name"/>, expanding environment names that may be present in the value.
/// </summary>
Expand All @@ -53,7 +63,7 @@ public static string GetVariable(PSCmdlet cmdlet, string name, EnvironmentVariab
{
if (scope == EnvironmentVariableTarget.Process)
{
return Environment.GetEnvironmentVariable(name, scope);
return GetVariable(name);
}

var value = string.Empty;
Expand Down Expand Up @@ -128,6 +138,16 @@ public static string[] GetVariableNames(EnvironmentVariableTarget scope)
}
}

/// <summary>
/// Sets the value of an environment variable for the current process only.
/// </summary>
/// <param name="name">The name of the environment variable to set.</param>
/// <param name="value">The value to set the environment variable to.</param>
public static void SetVariable(string name, string value)
{
Environment.SetEnvironmentVariable(name, value);
}

/// <summary>
/// Sets the value of an environment variable at the target <paramref name="scope"/>, and updates the current session environment.
/// </summary>
Expand All @@ -139,7 +159,7 @@ public static void SetVariable(PSCmdlet cmdlet, string name, EnvironmentVariable
{
if (scope == EnvironmentVariableTarget.Process)
{
Environment.SetEnvironmentVariable(name, value);
SetVariable(name, value);
return;
}

Expand Down
Loading
Loading