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

GitHubSync update #749

Merged
merged 1 commit into from
Nov 28, 2024
Merged
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
GitHubSync update
GeertvanHorrik committed Nov 28, 2024
commit 4f8d85e8aff4d4c2ccb09d124c671f0bf1281e9b
23 changes: 4 additions & 19 deletions deployment/cake/apps-wpf-tasks.cake
Original file line number Diff line number Diff line change
@@ -155,26 +155,11 @@ public class WpfProcessor : ProcessorBase
CakeContext.DeleteFiles(filesToDelete);
}

// We know we *highly likely* need to sign, so try doing this upfront
if (!string.IsNullOrWhiteSpace(BuildContext.General.CodeSign.CertificateSubjectName))
if (BuildContext.General.CodeSign.IsAvailable ||
BuildContext.General.AzureCodeSign.IsAvailable)
{
BuildContext.CakeContext.Information("Searching for packagable files to sign:");

var projectFilesToSign = new List<FilePath>();

var exeSignFilesSearchPattern = $"{BuildContext.General.OutputRootDirectory}/{wpfApp}/**/*.exe";
BuildContext.CakeContext.Information($" - {exeSignFilesSearchPattern}");
projectFilesToSign.AddRange(BuildContext.CakeContext.GetFiles(exeSignFilesSearchPattern));

var dllSignFilesSearchPattern = $"{BuildContext.General.OutputRootDirectory}/{wpfApp}/**/*.dll";
BuildContext.CakeContext.Information($" - {dllSignFilesSearchPattern}");
projectFilesToSign.AddRange(BuildContext.CakeContext.GetFiles(dllSignFilesSearchPattern));

var signToolCommand = string.Format("sign /a /t {0} /n {1} /fd {2}", BuildContext.General.CodeSign.TimeStampUri,
BuildContext.General.CodeSign.CertificateSubjectName, BuildContext.General.CodeSign.HashAlgorithm);

SignFiles(BuildContext, signToolCommand, projectFilesToSign);
}
SignFilesInDirectory(BuildContext, outputDirectory, string.Empty);
}
else
{
BuildContext.CakeContext.Warning("No signing certificate subject name provided, not signing any files");
40 changes: 19 additions & 21 deletions deployment/cake/components-tasks.cake
Original file line number Diff line number Diff line change
@@ -314,27 +314,7 @@ public class ComponentsProcessor : ProcessorBase
BuildContext.CakeContext.LogSeparator();
}

var codeSign = (!BuildContext.General.IsCiBuild &&
!BuildContext.General.IsLocalBuild &&
!string.IsNullOrWhiteSpace(BuildContext.General.CodeSign.CertificateSubjectName));
if (codeSign)
{
// For details, see https://docs.microsoft.com/en-us/nuget/create-packages/sign-a-package
// nuget sign MyPackage.nupkg -CertificateSubjectName <MyCertSubjectName> -Timestamper <TimestampServiceURL>
var filesToSign = CakeContext.GetFiles($"{BuildContext.General.OutputRootDirectory}/*.nupkg");

foreach (var fileToSign in filesToSign)
{
CakeContext.Information($"Signing NuGet package '{fileToSign}' using certificate subject '{BuildContext.General.CodeSign.CertificateSubjectName}'");

var exitCode = CakeContext.StartProcess(BuildContext.General.NuGet.Executable, new ProcessSettings
{
Arguments = $"sign \"{fileToSign}\" -CertificateSubjectName \"{BuildContext.General.CodeSign.CertificateSubjectName}\" -Timestamper \"{BuildContext.General.CodeSign.TimeStampUri}\""
});

CakeContext.Information("Signing NuGet package exited with '{0}'", exitCode);
}
}
await SignNuGetPackageAsync();
}

public override async Task DeployAsync()
@@ -378,4 +358,22 @@ public class ComponentsProcessor : ProcessorBase
{

}

private async Task SignNuGetPackageAsync()
{
if (BuildContext.General.IsCiBuild ||
BuildContext.General.IsLocalBuild)
{
return;
}

// For details, see https://docs.microsoft.com/en-us/nuget/create-packages/sign-a-package
// nuget sign MyPackage.nupkg -CertificateSubjectName <MyCertSubjectName> -Timestamper <TimestampServiceURL>
var filesToSign = CakeContext.GetFiles($"{BuildContext.General.OutputRootDirectory}/*.nupkg");

foreach (var fileToSign in filesToSign)
{
SignNuGetPackage(BuildContext, fileToSign.FullPath);
}
}
}
6 changes: 3 additions & 3 deletions deployment/cake/generic-tasks.cake
Original file line number Diff line number Diff line change
@@ -276,10 +276,10 @@ Task("CodeSign")
return;
}

var certificateSubjectName = buildContext.General.CodeSign.CertificateSubjectName;
if (string.IsNullOrWhiteSpace(certificateSubjectName))
if (!buildContext.General.CodeSign.IsAvailable &&
!buildContext.General.AzureCodeSign.IsAvailable)
{
Information("Skipping code signing because the certificate subject name was not specified");
Information("Skipping code signing since no option is available");
return;
}

83 changes: 82 additions & 1 deletion deployment/cake/generic-variables.cake
Original file line number Diff line number Diff line change
@@ -39,6 +39,7 @@ public class GeneralContext : BuildContextWithItemsBase
public SolutionContext Solution { get; set; }
public SourceLinkContext SourceLink { get; set; }
public CodeSignContext CodeSign { get; set; }
public AzureCodeSignContext AzureCodeSign { get; set; }
public RepositoryContext Repository { get; set; }
public SonarQubeContext SonarQube { get; set; }

@@ -338,14 +339,27 @@ public class CodeSignContext : BuildContextBase
public string TimeStampUri { get; set; }
public string HashAlgorithm { get; set; }

public bool IsAvailable
{
get
{
if (string.IsNullOrWhiteSpace(CertificateSubjectName))
{
return false;
}

return true;
}
}

protected override void ValidateContext()
{

}

protected override void LogStateInfoForContext()
{
if (string.IsNullOrWhiteSpace(CertificateSubjectName))
if (!IsAvailable)
{
CakeContext.Information($"Code signing is not configured");
return;
@@ -359,6 +373,62 @@ public class CodeSignContext : BuildContextBase

//-------------------------------------------------------------

public class AzureCodeSignContext : BuildContextBase
{
public AzureCodeSignContext(IBuildContext parentBuildContext)
: base(parentBuildContext)
{
}

public string VaultName { get; set; }
public string VaultUrl { get { return $"https://{VaultName}.vault.azure.net"; } }
public string CertificateName { get; set; }
public string TimeStampUri { get; set; }
public string HashAlgorithm { get; set; }
public string TenantId { get; set; }
public string ClientId { get; set; }
public string ClientSecret { get; set; }

public bool IsAvailable
{
get
{
if (string.IsNullOrWhiteSpace(VaultName) ||
string.IsNullOrWhiteSpace(CertificateName) ||
string.IsNullOrWhiteSpace(TenantId) ||
string.IsNullOrWhiteSpace(ClientId) ||
string.IsNullOrWhiteSpace(ClientSecret))
{
return false;
}

return true;
}
}

protected override void ValidateContext()
{

}

protected override void LogStateInfoForContext()
{
if (!IsAvailable)
{
CakeContext.Information($"Azure Code signing is not configured");
return;
}

CakeContext.Information($"Azure Code vault name: '{VaultName}'");
CakeContext.Information($"Azure Code vault URL: '{VaultUrl}'");
CakeContext.Information($"Azure Code signing certificate name: '{CertificateName}'");
CakeContext.Information($"Azure Code signing timestamp uri: '{TimeStampUri}'");
CakeContext.Information($"Azure Code signing hash algorithm: '{HashAlgorithm}'");
}
}

//-------------------------------------------------------------

public class RepositoryContext : BuildContextBase
{
public RepositoryContext(IBuildContext parentBuildContext)
@@ -498,6 +568,17 @@ private GeneralContext InitializeGeneralContext(BuildContext buildContext, IBuil
HashAlgorithm = buildContext.BuildServer.GetVariable("CodeSignHashAlgorithm", "SHA256", showValue: true)
};

data.AzureCodeSign = new AzureCodeSignContext(data)
{
VaultName = buildContext.BuildServer.GetVariable("AzureCodeSignVaultName", showValue: true),
CertificateName = buildContext.BuildServer.GetVariable("AzureCodeSignCertificateName", showValue: true),
TimeStampUri = buildContext.BuildServer.GetVariable("AzureCodeSignTimeStampUri", "http://timestamp.digicert.com", showValue: true),
HashAlgorithm = buildContext.BuildServer.GetVariable("AzureCodeSignHashAlgorithm", "SHA256", showValue: true),
TenantId = buildContext.BuildServer.GetVariable("AzureCodeSignTenantId", showValue: false),
ClientId = buildContext.BuildServer.GetVariable("AzureCodeSignClientId", showValue: false),
ClientSecret = buildContext.BuildServer.GetVariable("AzureCodeSignClientSecret", showValue: false),
};

data.Repository = new RepositoryContext(data)
{
Url = buildContext.BuildServer.GetVariable("RepositoryUrl", showValue: true),
120 changes: 100 additions & 20 deletions deployment/cake/installers-innosetup.cake
Original file line number Diff line number Diff line change
@@ -73,33 +73,64 @@ public class InnoSetupInstaller : IInstaller
fileContents = fileContents.Replace("[VERSION_DISPLAY]", BuildContext.General.Version.FullSemVer);
fileContents = fileContents.Replace("[WIZARDIMAGEFILE]", string.Format("logo_large{0}", setupSuffix));

var signTool = string.Empty;
if (!string.IsNullOrWhiteSpace(BuildContext.General.CodeSign.CertificateSubjectName))
var signToolIndex = GetRandomSignToolIndex();

try
{
signTool = string.Format("SignTool={0}", BuildContext.General.CodeSign.CertificateSubjectName);
}
var codeSignContext = BuildContext.General.CodeSign;
var azureCodeSignContext = BuildContext.General.AzureCodeSign;

var signTool = string.Empty;

fileContents = fileContents.Replace("[SIGNTOOL]", signTool);
System.IO.File.WriteAllText(innoSetupScriptFileName, fileContents);
var signToolFileName = GetSignToolFileName(BuildContext);
if (!string.IsNullOrWhiteSpace(signToolFileName))
{
var signToolName = DateTime.Now.ToString("yyyyMMddHHmmss");
var signToolCommandLine = GetSignToolCommandLine(BuildContext);

BuildContext.CakeContext.Information("Generating Inno Setup packages, this can take a while, especially when signing is enabled...");
BuildContext.CakeContext.Information("Adding random sign tool config for Inno Setup");

BuildContext.CakeContext.InnoSetup(innoSetupScriptFileName, new InnoSetupSettings
{
OutputDirectory = innoSetupReleasesRoot
});
using (var registryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(GetRegistryKey(), true))
{
var registryValueName = GetSignToolIndexName(signToolIndex);

if (BuildContext.Wpf.UpdateDeploymentsShare)
{
BuildContext.CakeContext.Information("Copying Inno Setup files to deployments share at '{0}'", installersOnDeploymentsShare);
// Important: must end with "$f"
var signToolRegistryValue = $"{signToolName}=\"{signToolFileName}\" {signToolCommandLine} \"$f\"";

// Copy the following files:
// - Setup.exe => [projectName]-[version].exe
// - Setup.exe => [projectName]-[channel].exe
registryKey.SetValue(registryValueName, signToolRegistryValue);
}

signTool = string.Format("SignTool={0}", signToolName);
}

fileContents = fileContents.Replace("[SIGNTOOL]", signTool);
System.IO.File.WriteAllText(innoSetupScriptFileName, fileContents);

BuildContext.CakeContext.Information("Generating Inno Setup packages, this can take a while, especially when signing is enabled...");

BuildContext.CakeContext.InnoSetup(innoSetupScriptFileName, new InnoSetupSettings
{
OutputDirectory = innoSetupReleasesRoot
});

var installerSourceFile = System.IO.Path.Combine(innoSetupReleasesRoot, $"{projectName}_{BuildContext.General.Version.FullSemVer}.exe");
BuildContext.CakeContext.CopyFile(installerSourceFile, System.IO.Path.Combine(installersOnDeploymentsShare, $"{projectName}_{BuildContext.General.Version.FullSemVer}.exe"));
BuildContext.CakeContext.CopyFile(installerSourceFile, System.IO.Path.Combine(installersOnDeploymentsShare, $"{projectName}{setupSuffix}.exe"));
if (BuildContext.Wpf.UpdateDeploymentsShare)
{
BuildContext.CakeContext.Information("Copying Inno Setup files to deployments share at '{0}'", installersOnDeploymentsShare);

// Copy the following files:
// - Setup.exe => [projectName]-[version].exe
// - Setup.exe => [projectName]-[channel].exe

var installerSourceFile = System.IO.Path.Combine(innoSetupReleasesRoot, $"{projectName}_{BuildContext.General.Version.FullSemVer}.exe");
BuildContext.CakeContext.CopyFile(installerSourceFile, System.IO.Path.Combine(installersOnDeploymentsShare, $"{projectName}_{BuildContext.General.Version.FullSemVer}.exe"));
BuildContext.CakeContext.CopyFile(installerSourceFile, System.IO.Path.Combine(installersOnDeploymentsShare, $"{projectName}{setupSuffix}.exe"));
}
}
finally
{
BuildContext.CakeContext.Information("Removing random sign tool config for Inno Setup");

RemoveSignToolFromRegistry(signToolIndex);
}
}

@@ -222,4 +253,53 @@ public class InnoSetupInstaller : IInstaller

return installersOnDeploymentsShare;
}

//-------------------------------------------------------------

private string GetRegistryKey()
{
return "Software\\Jordan Russell\\Inno Setup\\SignTools";
}

//-------------------------------------------------------------

private int GetRandomSignToolIndex()
{
using (var registryKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(GetRegistryKey()))
{
for (int i = 0; i < 100; i++)
{
var valueName = GetSignToolIndexName(i);

if (registryKey.GetValue(valueName) is null)
{
// Immediately lock it
registryKey.SetValue(valueName, "reserved");

return i;
}
}
}

throw new Exception("Could not find any empty slots for the sign tool, please clean up the sign tool registry for Inno Setup");
}

//-------------------------------------------------------------

private string GetSignToolIndexName(int index)
{
return $"SignTool{index}";
}

//-------------------------------------------------------------

private void RemoveSignToolFromRegistry(int index)
{
using (var registryKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(GetRegistryKey()))
{
var valueName = GetSignToolIndexName(index);

registryKey.DeleteValue(valueName, false);
}
}
}
30 changes: 10 additions & 20 deletions deployment/cake/installers-msix.cake
Original file line number Diff line number Diff line change
@@ -51,17 +51,6 @@ public class MsixInstaller : IInstaller
return;
}

var signToolCommand = string.Empty;
if (!string.IsNullOrWhiteSpace(BuildContext.General.CodeSign.CertificateSubjectName))
{
signToolCommand = string.Format("sign /a /t {0} /n {1} /fd {2}", BuildContext.General.CodeSign.TimeStampUri,
BuildContext.General.CodeSign.CertificateSubjectName, BuildContext.General.CodeSign.HashAlgorithm);
}
else
{
BuildContext.CakeContext.Warning("No sign tool is defined, MSIX will not be installable to (most or all) users");
}

BuildContext.CakeContext.LogSeparator($"Packaging app '{projectName}' using MSIX");

var deploymentShare = BuildContext.Wpf.GetDeploymentShareForProject(projectName);
@@ -119,15 +108,16 @@ public class MsixInstaller : IInstaller

BuildContext.CakeContext.CopyFiles(appSourceDirectory, appTargetDirectory, true);

BuildContext.CakeContext.Information($"Signing files in '{appTargetDirectory}'");

var filesToSign = new List<string>();

filesToSign.AddRange(BuildContext.CakeContext.GetFiles($"{appTargetDirectory}/**/*.dll").Select(x => x.FullPath));
filesToSign.AddRange(BuildContext.CakeContext.GetFiles($"{appTargetDirectory}/**/*.exe").Select(x => x.FullPath));
if (BuildContext.General.CodeSign.IsAvailable ||
BuildContext.General.AzureCodeSign.IsAvailable)
{
SignFilesInDirectory(BuildContext, appTargetDirectory, string.Empty);
}
else
{
BuildContext.CakeContext.Warning("No sign tool is defined, MSIX will not be installable to (most or all) users");
}

SignFiles(BuildContext, signToolCommand, filesToSign);

BuildContext.CakeContext.Information("Generating MSIX packages using MakeAppX...");

var processSettings = new ProcessSettings
@@ -154,7 +144,7 @@ public class MsixInstaller : IInstaller
}
}

SignFile(BuildContext, signToolCommand, installerSourceFile);
SignFile(BuildContext, installerSourceFile);

// Always copy the AppInstaller if available
if (BuildContext.CakeContext.FileExists(msixUpdateScriptFileName))
246 changes: 220 additions & 26 deletions deployment/cake/lib-signing.cake
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#tool "dotnet:?package=AzureSignTool&version=6.0.0"
#tool "dotnet:?package=NuGetKeyVaultSignTool&version=3.2.3"

private static string _signToolFileName;
private static string _azureSignToolFileName;

//-------------------------------------------------------------

@@ -25,43 +29,80 @@ public static bool ShouldSignImmediately(BuildContext buildContext, string proje

public static void SignProjectFiles(BuildContext buildContext, string projectName)
{
var certificateSubjectName = buildContext.General.CodeSign.CertificateSubjectName;
if (string.IsNullOrWhiteSpace(certificateSubjectName))
{
buildContext.CakeContext.Information("Skipping code signing because the certificate subject name was not specified");
return;
}
var outputDirectory = string.Format("{0}/{1}", buildContext.General.OutputRootDirectory, projectName);

var codeSignWildCard = buildContext.General.CodeSign.WildCard;
var codeSignContext = buildContext.General.CodeSign;
var codeSignWildCard = codeSignContext.WildCard;
if (string.IsNullOrWhiteSpace(codeSignWildCard))
{
// Empty, we need to override with project name for valid default value
codeSignWildCard = projectName;
}

var outputDirectory = string.Format("{0}/{1}", buildContext.General.OutputRootDirectory, projectName);
SignFilesInDirectory(buildContext, outputDirectory, codeSignWildCard);
}

//-------------------------------------------------------------

public static void SignFilesInDirectory(BuildContext buildContext, string directory, string codeSignWildCard)
{
var codeSignContext = buildContext.General.CodeSign;
var azureCodeSignContext = buildContext.General.AzureCodeSign;

var certificateSubjectName = buildContext.General.CodeSign.CertificateSubjectName;
if (!codeSignContext.IsAvailable &&
!azureCodeSignContext.IsAvailable)
{
buildContext.CakeContext.Information("Skipping code signing because none of the options is available");
return;
}

var projectFilesToSign = new List<FilePath>();

var exeSignFilesSearchPattern = string.Format("{0}/**/*{1}*.exe", outputDirectory, codeSignWildCard);
if (!string.IsNullOrWhiteSpace(codeSignWildCard))
{
// Make sure the pattern becomes *[wildcard]*
codeSignWildCard += "*";
}
else
{
codeSignWildCard = string.Empty;
}

var exeSignFilesSearchPattern = string.Format("{0}/**/*{1}.exe", directory, codeSignWildCard);
buildContext.CakeContext.Information(exeSignFilesSearchPattern);
projectFilesToSign.AddRange(buildContext.CakeContext.GetFiles(exeSignFilesSearchPattern));

var dllSignFilesSearchPattern = string.Format("{0}/**/*{1}*.dll", outputDirectory, codeSignWildCard);
var dllSignFilesSearchPattern = string.Format("{0}/**/*{1}.dll", directory, codeSignWildCard);
buildContext.CakeContext.Information(dllSignFilesSearchPattern);
projectFilesToSign.AddRange(buildContext.CakeContext.GetFiles(dllSignFilesSearchPattern));

buildContext.CakeContext.Information("Found '{0}' files to code sign for '{1}'", projectFilesToSign.Count, projectName);
buildContext.CakeContext.Information("Found '{0}' files to code sign", projectFilesToSign.Count);

var signToolCommand = string.Format("sign /a /t {0} /n {1} /fd {2}", buildContext.General.CodeSign.TimeStampUri,
certificateSubjectName, buildContext.General.CodeSign.HashAlgorithm);
var signToolCommand = GetSignToolCommandLine(buildContext);

SignFiles(buildContext, signToolCommand, projectFilesToSign);
SignFiles(buildContext, signToolCommand, projectFilesToSign, null);
}

//-------------------------------------------------------------

public static void SignFiles(BuildContext buildContext, string signToolCommand, IEnumerable<FilePath> fileNames, string additionalCommandLineArguments = null)
public static void SignFile(BuildContext buildContext, FilePath filePath)
{
SignFile(buildContext, filePath.FullPath);
}

//-------------------------------------------------------------

public static void SignFile(BuildContext buildContext, string fileName)
{
var signToolCommand = GetSignToolCommandLine(buildContext);

SignFiles(buildContext, signToolCommand, new [] { fileName }, null);
}

//-------------------------------------------------------------

public static void SignFiles(BuildContext buildContext, string signToolCommand, IEnumerable<FilePath> fileNames, string additionalCommandLineArguments)
{
if (fileNames.Any())
{
@@ -76,7 +117,7 @@ public static void SignFiles(BuildContext buildContext, string signToolCommand,

//-------------------------------------------------------------

public static void SignFiles(BuildContext buildContext, string signToolCommand, IEnumerable<string> fileNames, string additionalCommandLineArguments = null)
public static void SignFiles(BuildContext buildContext, string signToolCommand, IEnumerable<string> fileNames, string additionalCommandLineArguments)
{
if (fileNames.Any())
{
@@ -91,22 +132,46 @@ public static void SignFiles(BuildContext buildContext, string signToolCommand,

//-------------------------------------------------------------

public static void SignFile(BuildContext buildContext, string signToolCommand, string fileName, string additionalCommandLineArguments = null)
public static void SignFile(BuildContext buildContext, string signToolCommand, string fileName, string additionalCommandLineArguments)
{
// Skip code signing in specific scenarios
if (string.IsNullOrWhiteSpace(signToolCommand))
var codeSignContext = buildContext.General.CodeSign;
var azureCodeSignContext = buildContext.General.AzureCodeSign;

if (string.IsNullOrWhiteSpace(_signToolFileName))
{
return;
// Always fetch, it is used for verification
_signToolFileName = FindWindowsSignToolFileName(buildContext);
}

if (string.IsNullOrWhiteSpace(_signToolFileName))
if (string.IsNullOrWhiteSpace(_azureSignToolFileName))
{
_signToolFileName = FindSignToolFileName(buildContext);
_azureSignToolFileName = FindAzureSignToolFileName(buildContext);
}

if (string.IsNullOrWhiteSpace(_signToolFileName))
var signToolFileName = _signToolFileName;

// Azure always wins
if (azureCodeSignContext.IsAvailable)
{
signToolFileName = _azureSignToolFileName;
}

SignFile(buildContext, signToolFileName, signToolCommand, fileName, additionalCommandLineArguments);
}

//-------------------------------------------------------------

public static void SignFile(BuildContext buildContext, string signToolFileName, string signToolCommand, string fileName, string additionalCommandLineArguments)
{
// Skip code signing in specific scenarios
if (string.IsNullOrWhiteSpace(signToolCommand))
{
throw new InvalidOperationException("Cannot find signtool.exe, make sure to install a Windows Development Kit");
return;
}

if (string.IsNullOrWhiteSpace(signToolFileName))
{
throw new InvalidOperationException("Cannot find signtool, make sure to install a Windows Development Kit");
}

buildContext.CakeContext.Information(string.Empty);
@@ -127,6 +192,8 @@ public static void SignFile(BuildContext buildContext, string signToolCommand, s
RedirectStandardOutput = true
};

// Note: we can safely use SignTool.exe here

using (var checkProcess = buildContext.CakeContext.StartAndReturnProcess(_signToolFileName, checkProcessSettings))
{
checkProcess.WaitForExit();
@@ -156,7 +223,7 @@ public static void SignFile(BuildContext buildContext, string signToolCommand, s
Silent = true
};

using (var signProcess = buildContext.CakeContext.StartAndReturnProcess(_signToolFileName, signProcessSettings))
using (var signProcess = buildContext.CakeContext.StartAndReturnProcess(signToolFileName, signProcessSettings))
{
signProcess.WaitForExit();

@@ -181,7 +248,58 @@ public static void SignFile(BuildContext buildContext, string signToolCommand, s

//-------------------------------------------------------------

public static string FindSignToolFileName(BuildContext buildContext)
public static void SignNuGetPackage(BuildContext buildContext, string fileName)
{
var codeSignContext = buildContext.General.CodeSign;
var azureCodeSignContext = buildContext.General.AzureCodeSign;

if (buildContext.General.IsCiBuild ||
buildContext.General.IsLocalBuild)
{
return;
}

buildContext.CakeContext.Information($"Signing NuGet package '{fileName}'");

if (azureCodeSignContext.IsAvailable)
{
var signToolFileName = FindNuGetAzureSignToolFileName(buildContext);
var signToolCommandLine = string.Format("sign -kvu {0} -kvt {1} -kvi {2} -kvs {3} -kvc {4} -tr {5} -fd {6}",
azureCodeSignContext.VaultUrl,
azureCodeSignContext.TenantId,
azureCodeSignContext.ClientId,
azureCodeSignContext.ClientSecret,
azureCodeSignContext.CertificateName,
azureCodeSignContext.TimeStampUri,
azureCodeSignContext.HashAlgorithm);

var finalCommand = $"{signToolFileName} {signToolCommandLine} {fileName}";

buildContext.CakeContext.Information($"{finalCommand}'");

SignFile(buildContext, signToolFileName, signToolCommandLine, fileName, null);

return;
}

if (codeSignContext.IsAvailable)
{
var exitCode = buildContext.CakeContext.StartProcess(buildContext.General.NuGet.Executable, new ProcessSettings
{
Arguments = $"sign \"{fileName}\" -CertificateSubjectName \"{codeSignContext.CertificateSubjectName}\" -Timestamper \"{codeSignContext.TimeStampUri}\""
});

buildContext.CakeContext.Information("Signing NuGet package exited with '{0}'", exitCode);

return;
}

throw new NotSupportedException("No supported code signing method could be found");
}

//-------------------------------------------------------------

public static string FindWindowsSignToolFileName(BuildContext buildContext)
{
var directory = FindLatestWindowsKitsDirectory(buildContext);
if (directory != null)
@@ -191,3 +309,79 @@ public static string FindSignToolFileName(BuildContext buildContext)

return null;
}

//-------------------------------------------------------------

public static string FindAzureSignToolFileName(BuildContext buildContext)
{
var path = buildContext.CakeContext.Tools.Resolve("AzureSignTool.exe");

buildContext.CakeContext.Information("Found path '{0}'", path);

return path.FullPath;
}

//-------------------------------------------------------------

public static string FindNuGetAzureSignToolFileName(BuildContext buildContext)
{
var path = buildContext.CakeContext.Tools.Resolve("NuGetKeyVaultSignTool.exe");

buildContext.CakeContext.Information("Found path '{0}'", path);

return path.FullPath;
}

//-------------------------------------------------------------

public static string GetSignToolFileName(BuildContext buildContext)
{
var codeSignContext = buildContext.General.CodeSign;
var azureCodeSignContext = buildContext.General.AzureCodeSign;

// Azure first
if (azureCodeSignContext.IsAvailable)
{
return FindAzureSignToolFileName(buildContext);
}

if (codeSignContext.IsAvailable)
{
return FindWindowsSignToolFileName(buildContext);
}

return string.Empty;
}

//-------------------------------------------------------------

public static string GetSignToolCommandLine(BuildContext buildContext)
{
var codeSignContext = buildContext.General.CodeSign;
var azureCodeSignContext = buildContext.General.AzureCodeSign;

var signToolCommand = string.Empty;

if (codeSignContext.IsAvailable)
{
signToolCommand = string.Format("sign /a /t {0} /n {1} /fd {2}",
codeSignContext.TimeStampUri,
codeSignContext.CertificateSubjectName,
codeSignContext.HashAlgorithm);
}

// Note: Azure always wins
if (azureCodeSignContext.IsAvailable)
{
signToolCommand = string.Format("sign -kvu {0} -kvt {1} -kvi {2} -kvs {3} -kvc {4} -tr {5} -fd {6}",
azureCodeSignContext.VaultUrl,
azureCodeSignContext.TenantId,
azureCodeSignContext.ClientId,
azureCodeSignContext.ClientSecret,
azureCodeSignContext.CertificateName,
azureCodeSignContext.TimeStampUri,
azureCodeSignContext.HashAlgorithm);
}

return signToolCommand;
}
62 changes: 23 additions & 39 deletions deployment/cake/tools-tasks.cake
Original file line number Diff line number Diff line change
@@ -283,24 +283,10 @@ SHA512 CHECKSUMS GENERATED BY BUILD TOOL:
CakeContext.DeleteFiles(objFiles);

// We know we *highly likely* need to sign, so try doing this upfront
if (!string.IsNullOrWhiteSpace(BuildContext.General.CodeSign.CertificateSubjectName))
if (BuildContext.General.CodeSign.IsAvailable ||
BuildContext.General.AzureCodeSign.IsAvailable)
{
BuildContext.CakeContext.Information("Searching for packagable files to sign:");

var projectFilesToSign = new List<FilePath>();

var exeSignFilesSearchPattern = $"{BuildContext.General.OutputRootDirectory}/{tool}/**/*.exe";
BuildContext.CakeContext.Information($" - {exeSignFilesSearchPattern}");
projectFilesToSign.AddRange(BuildContext.CakeContext.GetFiles(exeSignFilesSearchPattern));

var dllSignFilesSearchPattern = $"{BuildContext.General.OutputRootDirectory}/{tool}/**/*.dll";
BuildContext.CakeContext.Information($" - {dllSignFilesSearchPattern}");
projectFilesToSign.AddRange(BuildContext.CakeContext.GetFiles(dllSignFilesSearchPattern));

var signToolCommand = string.Format("sign /a /t {0} /n {1} /fd {2}", BuildContext.General.CodeSign.TimeStampUri,
BuildContext.General.CodeSign.CertificateSubjectName, BuildContext.General.CodeSign.HashAlgorithm);

SignFiles(BuildContext, signToolCommand, projectFilesToSign);
SignFilesInDirectory(BuildContext, outputDirectory, string.Empty);
}
else
{
@@ -372,28 +358,8 @@ SHA512 CHECKSUMS GENERATED BY BUILD TOOL:

BuildContext.CakeContext.LogSeparator();
}

var codeSign = (!BuildContext.General.IsCiBuild &&
!BuildContext.General.IsLocalBuild &&
!string.IsNullOrWhiteSpace(BuildContext.General.CodeSign.CertificateSubjectName));
if (codeSign)
{
// For details, see https://docs.microsoft.com/en-us/nuget/create-packages/sign-a-package
// nuget sign MyPackage.nupkg -CertificateSubjectName <MyCertSubjectName> -Timestamper <TimestampServiceURL>
var filesToSign = CakeContext.GetFiles($"{BuildContext.General.OutputRootDirectory}/*.nupkg");

foreach (var fileToSign in filesToSign)
{
CakeContext.Information($"Signing NuGet package '{fileToSign}' using certificate subject '{BuildContext.General.CodeSign.CertificateSubjectName}'");

var exitCode = CakeContext.StartProcess(BuildContext.General.NuGet.Executable, new ProcessSettings
{
Arguments = $"sign \"{fileToSign}\" -CertificateSubjectName \"{BuildContext.General.CodeSign.CertificateSubjectName}\" -Timestamper \"{BuildContext.General.CodeSign.TimeStampUri}\""
});

CakeContext.Information("Signing NuGet package exited with '{0}'", exitCode);
}
}

await SignNuGetPackageAsync();
}

public override async Task DeployAsync()
@@ -446,4 +412,22 @@ SHA512 CHECKSUMS GENERATED BY BUILD TOOL:
{

}

private async Task SignNuGetPackageAsync()
{
if (BuildContext.General.IsCiBuild ||
BuildContext.General.IsLocalBuild)
{
return;
}

// For details, see https://docs.microsoft.com/en-us/nuget/create-packages/sign-a-package
// nuget sign MyPackage.nupkg -CertificateSubjectName <MyCertSubjectName> -Timestamper <TimestampServiceURL>
var filesToSign = CakeContext.GetFiles($"{BuildContext.General.OutputRootDirectory}/*.nupkg");

foreach (var fileToSign in filesToSign)
{
SignNuGetPackage(BuildContext, fileToSign.FullPath);
}
}
}
4 changes: 2 additions & 2 deletions src/Directory.Build.analyzers.props
Original file line number Diff line number Diff line change
@@ -12,11 +12,11 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="IDisposableAnalyzers" Version="4.0.7">
<PackageReference Include="IDisposableAnalyzers" Version="4.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NUnit.Analyzers" Version="4.2.0">
<PackageReference Include="NUnit.Analyzers" Version="4.4.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

Unchanged files with check annotations Beta

{
public ConfigurationAnalyticsValue(ConfigurationContainer container, string key, object? defaultValue)
{
ArgumentNullException.ThrowIfNull(container);

Check failure on line 10 in src/Orc.Analytics/Models/ConfigurationAnalyticsValue.cs

GitHub Actions / build-and-test

Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264)

Check failure on line 10 in src/Orc.Analytics/Models/ConfigurationAnalyticsValue.cs

GitHub Actions / build-and-test

Calling 'ArgumentNullException.ThrowIfNull' and passing a non-nullable value is a no-op (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264)
Category = "Configuration";
Container = container;