55
66using AsmResolver . DotNet ;
77using AsmResolver . PE . DotNet . Metadata . Tables ;
8+ using AsmResolver . PE . DotNet . StrongName ;
89using Microsoft . Build . Framework ;
910
1011namespace 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