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

(#149) Switch to only signing when required #150

Merged
merged 1 commit into from
May 16, 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
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."
}