Skip to content

Commit fb09cdd

Browse files
committed
code: add cert export option to sign code command
Add option to export the certificate used to sign code to all `code` commands, using the Exporter.
1 parent 262104a commit fb09cdd

File tree

7 files changed

+41
-12
lines changed

7 files changed

+41
-12
lines changed

src/Sign.Cli/CodeCommand.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ internal sealed class CodeCommand : Command
3131
internal Option<HashAlgorithmName> TimestampDigestOption { get; } = new(["--timestamp-digest", "-td"], HashAlgorithmParser.ParseHashAlgorithmName, description: Resources.TimestampDigestOptionDescription);
3232
internal Option<Uri?> TimestampUrlOption { get; } = new(["--timestamp-url", "-t"], ParseUrl, description: Resources.TimestampUrlOptionDescription);
3333
internal Option<LogLevel> VerbosityOption { get; } = new(["--verbosity", "-v"], () => LogLevel.Warning, Resources.VerbosityOptionDescription);
34+
internal Option<string> CertificateExportOption { get; } = new(["--certificate-export-path", "-co"], Resources.CertificateExportOptionDescription);
3435

3536
internal CodeCommand()
3637
: base("code", Resources.CodeCommandDescription)
@@ -56,9 +57,11 @@ internal CodeCommand()
5657
AddGlobalOption(TimestampDigestOption);
5758
AddGlobalOption(MaxConcurrencyOption);
5859
AddGlobalOption(VerbosityOption);
60+
AddGlobalOption(CertificateExportOption);
5961
}
6062

61-
internal async Task HandleAsync(InvocationContext context, IServiceProviderFactory serviceProviderFactory, ISignatureProvider signatureProvider, string fileArgument)
63+
internal async Task HandleAsync(InvocationContext context, IServiceProviderFactory serviceProviderFactory,
64+
ISignatureProvider signatureProvider, string fileArgument)
6265
{
6366
// Some of the options have a default value and that is why we can safely use
6467
// the null-forgiving operator (!) to simplify the code.
@@ -74,6 +77,7 @@ internal async Task HandleAsync(InvocationContext context, IServiceProviderFacto
7477
LogLevel verbosity = context.ParseResult.GetValueForOption(VerbosityOption);
7578
string? output = context.ParseResult.GetValueForOption(OutputOption);
7679
int maxConcurrency = context.ParseResult.GetValueForOption(MaxConcurrencyOption);
80+
string? certificateExportPath = context.ParseResult.GetValueForOption(CertificateExportOption);
7781

7882
// Make sure this is rooted
7983
if (!Path.IsPathRooted(baseDirectory.FullName))
@@ -184,7 +188,8 @@ internal async Task HandleAsync(InvocationContext context, IServiceProviderFacto
184188
timestampUrl,
185189
maxConcurrency,
186190
fileHashAlgorithmName,
187-
timestampHashAlgorithmName);
191+
timestampHashAlgorithmName,
192+
certificateExportPath);
188193
}
189194

190195
private static string ExpandFilePath(DirectoryInfo baseDirectory, string file)

src/Sign.Cli/Resources.Designer.cs

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Sign.Cli/Resources.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?xml version="1.0" encoding="utf-8"?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<root>
33
<!--
44
Microsoft ResX Schema
@@ -123,6 +123,9 @@
123123
<data name="BaseDirectoryOptionDescription" xml:space="preserve">
124124
<value>Base directory for files. Overrides the current working directory.</value>
125125
</data>
126+
<data name="CertificateExportOptionDescription" xml:space="preserve">
127+
<value>Path to export certificate.</value>
128+
</data>
126129
<data name="CertificateStoreCommandDescription" xml:space="preserve">
127130
<value>Use Windows Certificate Store or a local certificate file.</value>
128131
</data>

src/Sign.Core/ISigner.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Task<int> SignAsync(
2020
Uri timestampUrl,
2121
int maxConcurrency,
2222
HashAlgorithmName fileHashAlgorithm,
23-
HashAlgorithmName timestampHashAlgorithm);
23+
HashAlgorithmName timestampHashAlgorithm,
24+
string? certificateExportPath);
2425
}
25-
}
26+
}

src/Sign.Core/Signer.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ public async Task<int> SignAsync(
3939
Uri timestampUrl,
4040
int maxConcurrency,
4141
HashAlgorithmName fileHashAlgorithm,
42-
HashAlgorithmName timestampHashAlgorithm)
42+
HashAlgorithmName timestampHashAlgorithm,
43+
string? certificateExportPath)
4344
{
4445
IAggregatingDataFormatSigner signer = _serviceProvider.GetRequiredService<IAggregatingDataFormatSigner>();
4546
IDirectoryService directoryService = _serviceProvider.GetRequiredService<IDirectoryService>();
@@ -76,6 +77,12 @@ public async Task<int> SignAsync(
7677
{
7778
using (X509Certificate2 certificate = await certificateProvider.GetCertificateAsync())
7879
{
80+
if (!string.IsNullOrWhiteSpace(certificateExportPath))
81+
{
82+
IExporter exporter = _serviceProvider.GetRequiredService<IExporter>();
83+
await exporter.ExportAsync(certificateExportPath);
84+
}
85+
7986
ICertificateVerifier certificateVerifier = _serviceProvider.GetRequiredService<ICertificateVerifier>();
8087

8188
certificateVerifier.Verify(certificate);
@@ -186,4 +193,4 @@ private static string ExpandFilePath(DirectoryInfo baseDirectory, string file)
186193
return file;
187194
}
188195
}
189-
}
196+
}

test/Sign.Cli.Test/TestInfrastructure/SignerSpy.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ internal sealed class SignerSpy : ISigner
2121
internal int? MaxConcurrency { get; private set; }
2222
internal HashAlgorithmName? FileHashAlgorithm { get; private set; }
2323
internal HashAlgorithmName? TimestampHashAlgorithm { get; private set; }
24+
internal string? CertificateExportPath { get; private set; }
2425
internal int ExitCode { get; }
2526

2627
internal SignerSpy()
@@ -40,7 +41,8 @@ public Task<int> SignAsync(
4041
Uri timestampUrl,
4142
int maxConcurrency,
4243
HashAlgorithmName fileHashAlgorithm,
43-
HashAlgorithmName timestampHashAlgorithm)
44+
HashAlgorithmName timestampHashAlgorithm,
45+
string? certificateExportPath)
4446
{
4547
InputFiles = inputFiles;
4648
OutputFile = outputFile;
@@ -54,8 +56,9 @@ public Task<int> SignAsync(
5456
MaxConcurrency = maxConcurrency;
5557
FileHashAlgorithm = fileHashAlgorithm;
5658
TimestampHashAlgorithm = timestampHashAlgorithm;
59+
CertificateExportPath = certificateExportPath;
5760

5861
return Task.FromResult(ExitCode);
5962
}
6063
}
61-
}
64+
}

test/Sign.Core.Test/SignerTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ private async Task SignAsync(TemporaryDirectory temporaryDirectory, IReadOnlyLis
260260
_certificatesFixture.TimestampServiceUrl,
261261
maxConcurrency: 4,
262262
HashAlgorithmName.SHA256,
263-
HashAlgorithmName.SHA256);
263+
HashAlgorithmName.SHA256,
264+
null);
264265

265266
Assert.Equal(ExitCode.Success, exitCode);
266267

@@ -624,4 +625,4 @@ private static void RegisterSipsFromIniFile()
624625
Kernel32.LoadLibraryW($@"{baseDirectory}\mssign32.dll");
625626
}
626627
}
627-
}
628+
}

0 commit comments

Comments
 (0)