Skip to content

Commit 24decbd

Browse files
timcassellclaude
andauthored
Re-sign the assembly after weaving it. (#3258)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent e2eefbc commit 24decbd

4 files changed

Lines changed: 89 additions & 2 deletions

File tree

‎build/common.props‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060

6161
<PropertyGroup>
6262
<!-- Increment this when the BenchmarkDotNet.Weaver package needs to be re-packed. -->
63-
<WeaverVersionSuffix>-8</WeaverVersionSuffix>
63+
<WeaverVersionSuffix>-9</WeaverVersionSuffix>
6464
</PropertyGroup>
6565

6666
<ItemGroup>

‎src/BenchmarkDotNet.Weaver/buildTransitive/netstandard2.0/BenchmarkDotNet.Weaver.Common.targets‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,18 @@
2929
Inputs="$(BenchmarkDotNetWeaveAssemblyPath)"
3030
Outputs="$(BenchmarkDotNetWeaveAssembliesStampFile)">
3131

32-
<WeaveAssemblyTask TargetAssembly="$(BenchmarkDotNetWeaveAssemblyPath)" ReferencePaths="@(ReferencePath)" TreatWarningsAsErrors="$(TreatWarningsAsErrors)" />
32+
<!--
33+
Weaving rewrites the assembly the compiler already signed, so the signing properties are passed along
34+
for it to sign the result again with the same key.
35+
-->
36+
<WeaveAssemblyTask
37+
TargetAssembly="$(BenchmarkDotNetWeaveAssemblyPath)"
38+
ReferencePaths="@(ReferencePath)"
39+
TreatWarningsAsErrors="$(TreatWarningsAsErrors)"
40+
SignAssembly="$(SignAssembly)"
41+
AssemblyOriginatorKeyFile="$(AssemblyOriginatorKeyFile)"
42+
DelaySign="$(DelaySign)"
43+
PublicSign="$(PublicSign)" />
3344

3445
<!-- Create stamp file for incrementality -->
3546
<Touch Files="$(BenchmarkDotNetWeaveAssembliesStampFile)" AlwaysCreate="true" />

src/BenchmarkDotNet.Weaver/packages/BenchmarkDotNet.Weaver.0.16.0-develop-8.nupkg renamed to src/BenchmarkDotNet.Weaver/packages/BenchmarkDotNet.Weaver.0.16.0-develop-9.nupkg

458 KB
Binary file not shown.

‎src/BenchmarkDotNet.Weaver/src/WeaveAssemblyTask.cs‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using AsmResolver.DotNet;
77
using AsmResolver.PE.DotNet.Metadata.Tables;
8+
using AsmResolver.PE.DotNet.StrongName;
89
using Microsoft.Build.Framework;
910

1011
namespace BenchmarkDotNet.Weaver;
@@ -31,6 +32,29 @@ public sealed class WeaveAssemblyTask : Microsoft.Build.Utilities.Task
3132
/// </summary>
3233
public bool TreatWarningsAsErrors { get; set; }
3334

35+
/// <summary>
36+
/// Whether the compiler strong named the assembly.
37+
/// </summary>
38+
public bool SignAssembly { get; set; }
39+
40+
/// <summary>
41+
/// The key the compiler strong named the assembly with. Weaving invalidates the signature it produced,
42+
/// so the assembly has to be signed again with the same key.
43+
/// </summary>
44+
public string? AssemblyOriginatorKeyFile { get; set; }
45+
46+
/// <summary>
47+
/// Whether only the public key was available to the compiler, leaving the signature to be written after
48+
/// the build. There is then no signature for weaving to invalidate.
49+
/// </summary>
50+
public bool DelaySign { get; set; }
51+
52+
/// <summary>
53+
/// Whether the assembly carries a public key without ever being signed with the matching private one.
54+
/// There is then no signature for weaving to invalidate.
55+
/// </summary>
56+
public bool PublicSign { get; set; }
57+
3458
/// <summary>
3559
/// Runs the weave assembly task.
3660
/// </summary>
@@ -99,12 +123,15 @@ void ApplyAggressiveOptimizationToMethods(TypeDefinition type)
99123

100124
if (anyAdjustments)
101125
{
126+
var signer = GetStrongNameSigner(module);
127+
102128
// Write to a memory stream before overwriting the original file in case an exception occurs during the write (like unsupported platform).
103129
// https://github.com/Washi1337/AsmResolver/issues/640
104130
var memoryStream = new MemoryStream();
105131
try
106132
{
107133
module.Write(memoryStream);
134+
SignStrongName(memoryStream, module, signer);
108135
using var fileStream = new FileStream(TargetAssembly, FileMode.Truncate, FileAccess.Write);
109136
memoryStream.WriteTo(fileStream);
110137
}
@@ -116,6 +143,12 @@ void ApplyAggressiveOptimizationToMethods(TypeDefinition type)
116143
GC.Collect();
117144
module.Write(Stream.Null);
118145
module.Write(TargetAssembly);
146+
147+
if (signer is not null)
148+
{
149+
using var fileStream = new FileStream(TargetAssembly, FileMode.Open, FileAccess.ReadWrite);
150+
SignStrongName(fileStream, module, signer);
151+
}
119152
}
120153
finally
121154
{
@@ -138,6 +171,49 @@ void ApplyAggressiveOptimizationToMethods(TypeDefinition type)
138171
return !Log.HasLoggedErrors;
139172
}
140173

174+
/// <summary>
175+
/// Rewriting the assembly invalidates the signature the compiler wrote, so it has to be signed again with
176+
/// the same key. Returns null when there is no signature to restore, or no key to restore it with.
177+
/// </summary>
178+
private StrongNameSigner? GetStrongNameSigner(ModuleDefinition module)
179+
{
180+
// Delay and public signing leave the signature to be written after the build, and an assembly the
181+
// compiler did not sign has none in the first place.
182+
if (!SignAssembly || DelaySign || PublicSign || module.Assembly?.PublicKey is null)
183+
return null;
184+
185+
if (string.IsNullOrEmpty(AssemblyOriginatorKeyFile) || !File.Exists(AssemblyOriginatorKeyFile))
186+
{
187+
LogStrongNameWarning("the key it was signed with was not passed to the weaver");
188+
return null;
189+
}
190+
191+
try
192+
{
193+
return new StrongNameSigner(StrongNamePrivateKey.FromFile(AssemblyOriginatorKeyFile!));
194+
}
195+
catch (Exception e)
196+
{
197+
// A key container or a public-key-only file cannot sign, and neither can a file we cannot read.
198+
LogStrongNameWarning($"the key it was signed with could not be read: {e.Message}");
199+
return null;
200+
}
201+
}
202+
203+
private void LogStrongNameWarning(string reason)
204+
=> Log.LogWarning(
205+
$"Weaving invalidated the strong name signature of {Path.GetFileName(TargetAssembly)}, because {reason}. " +
206+
"The assembly will fail strong name verification. Set BenchmarkDotNetShouldWeaveAssemblies to false to opt out of weaving.");
207+
208+
private static void SignStrongName(Stream imageStream, ModuleDefinition module, StrongNameSigner? signer)
209+
{
210+
if (signer is null)
211+
return;
212+
213+
imageStream.Position = 0;
214+
signer.SignImage(imageStream, module.Assembly!.HashAlgorithm);
215+
}
216+
141217
private static bool IsBenchmarkAttribute(CustomAttribute attribute, RuntimeContext runtimeContext)
142218
{
143219
// BenchmarkAttribute is unsealed, so we need to walk its hierarchy.

0 commit comments

Comments
 (0)