Skip to content

Commit

Permalink
Merge pull request #150 from gep13/verify-scripts
Browse files Browse the repository at this point in the history
(#149) Switch to only signing when required
  • Loading branch information
Windos authored May 16, 2024
2 parents 043be76 + 4b9f73b commit e772355
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 7 deletions.
4 changes: 2 additions & 2 deletions Chocolatey.Cake.Recipe/Content/build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -570,9 +570,9 @@ public class Builder
BuildParameters.Tasks.CreateChocolateyPackagesTask.IsDependentOn("Configuration-Builder");
BuildParameters.Tasks.CreateChocolateyPackagesTask.IsDependentOn("Obfuscate-Assemblies");
BuildParameters.Tasks.CreateChocolateyPackagesTask.IsDependentOn("Sign-Assemblies");
BuildParameters.Tasks.CreateNuGetPackagesTask.IsDependentOn("Sign-PowerShellScripts");
BuildParameters.Tasks.CreateNuGetPackagesTask.IsDependentOn("Verify-PowerShellScripts");
BuildParameters.Tasks.CreateNuGetPackagesTask.IsDependentOn("Sign-Assemblies");
BuildParameters.Tasks.CreateChocolateyPackagesTask.IsDependentOn("Sign-PowerShellScripts");
BuildParameters.Tasks.CreateChocolateyPackagesTask.IsDependentOn("Verify-PowerShellScripts");
BuildParameters.Tasks.SignMsisTask.IsDependentOn("Sign-Assemblies");
BuildParameters.Tasks.CreateChocolateyPackagesTask.IsDependentOn(prefix + "Build");
BuildParameters.Tasks.ObfuscateAssembliesTask.IsDependeeOf("Sign-Assemblies");
Expand Down
13 changes: 13 additions & 0 deletions Chocolatey.Cake.Recipe/Content/parameters.cake
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public static class BuildParameters
public static Func<List<PSScriptAnalyzerSettings>> GetPSScriptAnalyzerSettings { get; private set; }
public static Func<FilePathCollection> GetMsisToSign { get; private set; }
public static Func<FilePathCollection> GetProjectsToPack { get; private set; }
public static Func<FilePathCollection> GetScriptsToVerify { get; private set; }
public static Func<FilePathCollection> GetScriptsToSign { get; private set; }
public static GitReleaseManagerCredentials GitReleaseManager { get; private set; }
public static string IntegrationTestAssemblyFilePattern { get; private set; }
Expand Down Expand Up @@ -146,6 +147,7 @@ public static class BuildParameters
public static DirectoryPath RootDirectoryPath { get; private set; }
public static bool ShouldAuthenticodeSignMsis { get; private set; }
public static bool ShouldAuthenticodeSignOutputAssemblies { get; private set; }
public static bool ShouldVerifyPowerShellScripts { get; private set; }
public static bool ShouldAuthenticodeSignPowerShellScripts { get; private set; }
public static bool ShouldBuildMsi { get; private set; }
public static bool ShouldBuildNuGetSourcePackage { get; private set; }
Expand Down Expand Up @@ -280,6 +282,7 @@ public static class BuildParameters
context.Information("RestorePackagesDirectory: {0}", RestorePackagesDirectory);
context.Information("ShouldAuthenticodeSignMsis: {0}", BuildParameters.ShouldAuthenticodeSignMsis);
context.Information("ShouldAuthenticodeSignOutputAssemblies: {0}", BuildParameters.ShouldAuthenticodeSignOutputAssemblies);
context.Information("ShouldVerifyPowerShellScripts: {0}", BuildParameters.ShouldVerifyPowerShellScripts);
context.Information("ShouldAuthenticodeSignPowerShellScripts: {0}", BuildParameters.ShouldAuthenticodeSignPowerShellScripts);
context.Information("ShouldBuildMsi: {0}", BuildParameters.ShouldBuildMsi);
context.Information("ShouldBuildNuGetSourcePackage: {0}", BuildParameters.ShouldBuildNuGetSourcePackage);
Expand Down Expand Up @@ -360,6 +363,7 @@ public static class BuildParameters
Func<List<PSScriptAnalyzerSettings>> getPSScriptAnalyzerSettings = null,
Func<FilePathCollection> getMsisToSign = null,
Func<FilePathCollection> getProjectsToPack = null,
Func<FilePathCollection> getScriptsToVerify = null,
Func<FilePathCollection> getScriptsToSign = null,
string integrationTestAssemblyFilePattern = null,
string integrationTestAssemblyProjectPattern = null,
Expand Down Expand Up @@ -391,6 +395,7 @@ public static class BuildParameters
DirectoryPath rootDirectoryPath = null,
bool shouldAuthenticodeSignMsis = true,
bool shouldAuthenticodeSignOutputAssemblies = true,
bool shouldVerifyPowerShellScripts = true,
bool shouldAuthenticodeSignPowerShellScripts = true,
bool shouldBuildMsi = false,
bool shouldBuildNuGetSourcePackage = false,
Expand Down Expand Up @@ -495,6 +500,7 @@ public static class BuildParameters
GetPSScriptAnalyzerSettings = getPSScriptAnalyzerSettings;
GetMsisToSign = getMsisToSign;
GetProjectsToPack = getProjectsToPack;
GetScriptsToVerify = getScriptsToVerify;
GetScriptsToSign = getScriptsToSign;
GitReleaseManager = GetGitReleaseManagerCredentials(context);
IntegrationTestAssemblyFilePattern = integrationTestAssemblyFilePattern ?? "/**/*[tT]ests.[iI]ntegration.dll";
Expand Down Expand Up @@ -542,6 +548,13 @@ public static class BuildParameters
ShouldAuthenticodeSignOutputAssemblies = context.Argument<bool>("shouldAuthenticodeSignOutputAssemblies");
}

ShouldVerifyPowerShellScripts = shouldVerifyPowerShellScripts;

if (context.HasArgument("shouldVerifyPowerShellScripts"))
{
ShouldVerifyPowerShellScripts = context.Argument<bool>("shouldVerifyPowerShellScripts");
}

ShouldAuthenticodeSignPowerShellScripts = shouldAuthenticodeSignPowerShellScripts;

if (context.HasArgument("shouldAuthenticodeSignPowerShellScripts"))
Expand Down
10 changes: 8 additions & 2 deletions Chocolatey.Cake.Recipe/Content/paths.cake
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public class BuildPaths
var chocolateyPackagesOutputDirectory = packagesDirectory + "/Chocolatey";

var dependencyCheckReportsDirectory = buildDirectoryPath + "/DependencyCheckReports";

var signedFilesDirectory = buildDirectoryPath + "/SignedFiles";

// Files
var dotNetFormatOutputFilePath = ((DirectoryPath)testResultsDirectory).CombineWithFilePath("dotnet-format.json");
Expand Down Expand Up @@ -84,7 +86,8 @@ public class BuildPaths
chocolateyPackagesOutputDirectory,
packagesDirectory,
environmentSettingsDirectory,
dependencyCheckReportsDirectory
dependencyCheckReportsDirectory,
signedFilesDirectory
);

var buildFiles = new BuildFiles(
Expand Down Expand Up @@ -176,6 +179,7 @@ public class BuildDirectories
public DirectoryPath Packages { get; private set; }
public DirectoryPath EnvironmentSettings { get; private set; }
public DirectoryPath DependencyCheckReports { get; private set; }
public DirectoryPath SignedFiles { get; private set; }
public ICollection<DirectoryPath> ToClean { get; private set; }

public BuildDirectories(
Expand All @@ -199,7 +203,8 @@ public class BuildDirectories
DirectoryPath chocolateyPackages,
DirectoryPath packages,
DirectoryPath environmentSettings,
DirectoryPath dependencyCheckReports
DirectoryPath dependencyCheckReports,
DirectoryPath signedFiles
)
{
Build = build;
Expand All @@ -222,6 +227,7 @@ public class BuildDirectories
ChocolateyPackages = chocolateyPackages;
EnvironmentSettings = environmentSettings;
DependencyCheckReports = dependencyCheckReports;
SignedFiles = signedFiles;
Packages = packages;

ToClean = new[] {
Expand Down
37 changes: 34 additions & 3 deletions Chocolatey.Cake.Recipe/Content/sign-powershell.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Param(
[String[]]
$ScriptsToSign,

[Parameter()]
[String]
$OutputFolder,

[Parameter()]
[String]
$TimeStampServer,
Expand All @@ -40,11 +44,38 @@ Param(
$CertificateSubjectName
)

$cert = if ($PSCmdlet.ParameterSetName -eq "File") {
$Cert = if ($PSCmdlet.ParameterSetName -eq "File") {
New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertificatePath, $CertificatePassword)
}
else {
Get-ChildItem Cert:\LocalMachine\My | Where-Object Subject -Like "*$CertificateSubjectName*"
Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Subject -Like "*$CertificateSubjectName*" -and
$_.Issuer -match 'DigiCert' -and
$_.NotAfter -ge [datetime]::Now}
}

Set-AuthenticodeSignature -FilePath $ScriptsToSign -Cert $cert -TimestampServer $TimeStampServer -IncludeChain NotRoot -HashAlgorithm $CertificateAlgorithm
if ($Cert) {
$CommonSignParams = @{
'TimestampServer' = $TimeStampServer
'IncludeChain' = 'NotRoot'
'HashAlgorithm' = $CertificateAlgorithm
'Cert' = $Cert
}

foreach ($Script in $ScriptsToSign) {
$ExistingSig = Get-AuthenticodeSignature -FilePath $Script

if ($ExistingSig.Status -ne 'Valid' -or $ExistingSig.SignerCertificate.Issuer -notmatch 'DigiCert' -or $ExistingSig.SignerCertificate.NotAfter -lt [datetime]::Now) {
$NewSig = Set-AuthenticodeSignature -FilePath $Script @CommonSignParams
Write-Host "Script file '$Script' signed with status: $($NewSig.Status)"

if (!(Test-Path -Path $OutputFolder)) {
$null = New-Item -Path $OutputFolder -Type Directory
}
Copy-Item -Path $Script -Destination $OutputFolder
} else {
Write-Host "Script file '$Script' does not need signing, current signature is valid."
}
}
} else {
Write-Warning "Skipping script signing, no currently valid DigiCert issued Authenticode signing certificate matching '$($CertificateSubjectName)' was found."
}
38 changes: 38 additions & 0 deletions Chocolatey.Cake.Recipe/Content/sign.cake
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,37 @@
// See the License for the specific language governing permissions and
// limitations under the License.

BuildParameters.Tasks.VerifyPowerShellScriptsTask = Task("Verify-PowerShellScripts")
.WithCriteria(() => BuildParameters.ShouldVerifyPowerShellScripts, "Skipping since verifying PowerShell scripts has been disabled")
.Does(() =>
{
if (BuildParameters.GetScriptsToVerify != null)
{
var powerShellVerifyScript = GetFiles("./tools/Chocolatey.Cake.Recipe*/Content/verify-powershell.ps1").FirstOrDefault();

if (powerShellVerifyScript == null)
{
Warning("Unable to find PowerShell verification script, so unable to verify PowerShell scripts.");
return;
}

var scriptsToVerify = new List<string>();
foreach (var filePath in BuildParameters.GetScriptsToVerify())
{
scriptsToVerify.Add(MakeAbsolute(filePath).FullPath);
}

StartPowershellFile(MakeAbsolute(powerShellVerifyScript), args =>
{
args.AppendArray("ScriptsToVerify", scriptsToVerify);
});
}
else
{
Information("There are no PowerShell Scripts defined to be verified.");
}
});

BuildParameters.Tasks.SignPowerShellScriptsTask = Task("Sign-PowerShellScripts")
.WithCriteria(() => (!string.IsNullOrWhiteSpace(BuildParameters.CertificateFilePath) && FileExists(BuildParameters.CertificateFilePath)) || BuildSystem.IsRunningOnTeamCity, "Skipping because unable to find certificate, and not running on TeamCity")
.WithCriteria(() => BuildParameters.ShouldAuthenticodeSignPowerShellScripts, "Skipping since authenticode signing of PowerShell scripts has been disabled")
Expand All @@ -39,6 +70,7 @@ BuildParameters.Tasks.SignPowerShellScriptsTask = Task("Sign-PowerShellScripts")
StartPowershellFile(MakeAbsolute(powerShellSigningScript), args =>
{
args.AppendArray("ScriptsToSign", scriptsToSign)
.Append("OutputFolder", BuildParameters.Paths.Directories.SignedFiles.FullPath)
.Append("TimeStampServer", BuildParameters.CertificateTimestampUrl)
.AppendQuoted("CertificateSubjectName", BuildParameters.CertificateSubjectName)
.Append("CertificateAlgorithm", BuildParameters.CertificateAlgorithm);
Expand All @@ -53,12 +85,18 @@ BuildParameters.Tasks.SignPowerShellScriptsTask = Task("Sign-PowerShellScripts")
StartPowershellFile(MakeAbsolute(powerShellSigningScript), args =>
{
args.AppendArray("ScriptsToSign", scriptsToSign)
.Append("OutputFolder", BuildParameters.Paths.Directories.SignedFiles.FullPath)
.Append("TimeStampServer", BuildParameters.CertificateTimestampUrl)
.Append("CertificatePath", BuildParameters.CertificateFilePath)
.AppendSecret("CertificatePassword", password)
.Append("CertificateAlgorithm", BuildParameters.CertificateAlgorithm);
});
}

foreach (var signedFile in GetFiles(BuildParameters.Paths.Directories.SignedFiles + "/**/*"))
{
BuildParameters.BuildProvider.UploadArtifact(signedFile);
}
}
else
{
Expand Down
1 change: 1 addition & 0 deletions Chocolatey.Cake.Recipe/Content/tasks.cake
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public class BuildTasks
public CakeTaskBuilder StrongNameSignerTask { get; set; }

// Signing Tasks
public CakeTaskBuilder VerifyPowerShellScriptsTask { get; set; }
public CakeTaskBuilder SignPowerShellScriptsTask { get; set; }
public CakeTaskBuilder SignAssembliesTask { get; set; }
public CakeTaskBuilder SignMsisTask { get; set; }
Expand Down
46 changes: 46 additions & 0 deletions Chocolatey.Cake.Recipe/Content/verify-powershell.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright © 2024 Chocolatey Software, Inc.
#
# 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.

[cmdletBinding()]
Param(
[Parameter()]
[String[]]
$ScriptsToVerify
)

$AllScriptsVerified = $true

Write-Output ""
Write-Output "========== Verifying PowerShell Scripts =========="
Write-Output ""

foreach ($ScriptToVerify in $ScriptsToVerify) {
$ExistingSig = Get-AuthenticodeSignature -FilePath $ScriptToVerify

if ($ExistingSig.Status -ne 'Valid' -or $ExistingSig.SignerCertificate.Issuer -notmatch 'DigiCert' -or $ExistingSig.SignerCertificate.NotAfter -lt [datetime]::Now) {
$AllScriptsVerified = $false
Write-Output "Script file '$ScriptToVerify' contains an invalid signature, which must be corrected before build can succeed."
} else {
Write-Output "Script file '$ScriptToVerify' does not need signing, current signature is valid."
}
}

Write-Output ""
Write-Output "========== Verification Complete =========="
Write-Output ""

if ($AllScriptsVerified -eq $false) {
throw "At least one PowerShell script had an invalid signature. Check output for details."
}

0 comments on commit e772355

Please sign in to comment.