public override bool Execute() { if (Assemblies == null || Assemblies.Length == 0) { return(true); } if (SignedAssemblyFolder == null || string.IsNullOrEmpty(SignedAssemblyFolder.ItemSpec)) { Log.LogError($"{nameof(SignedAssemblyFolder)} not specified"); return(false); } if (KeyFile == null || string.IsNullOrEmpty(KeyFile.ItemSpec)) { Log.LogError("KeyFile not specified"); return(false); } if (!File.Exists(KeyFile.ItemSpec)) { Log.LogError($"KeyFile not found: ${KeyFile.ItemSpec}"); return(false); } var keyBytes = File.ReadAllBytes(KeyFile.ItemSpec); SignedAssembliesToReference = new ITaskItem[Assemblies.Length]; Dictionary <string, string> updatedReferencePaths = new Dictionary <string, string>(); using (var resolver = new StrongNamerAssemblyResolver(Assemblies.Select(a => a.ItemSpec))) { for (int i = 0; i < Assemblies.Length; i++) { SignedAssembliesToReference[i] = ProcessAssembly(Assemblies[i], keyBytes, resolver); if (SignedAssembliesToReference[i].ItemSpec != Assemblies[i].ItemSpec) { // Path was updated to signed version updatedReferencePaths[Assemblies[i].ItemSpec] = SignedAssembliesToReference[i].ItemSpec; } } } if (CopyLocalFiles != null) { NewCopyLocalFiles = new ITaskItem[CopyLocalFiles.Length]; for (int i = 0; i < CopyLocalFiles.Length; i++) { string updatedPath; if (updatedReferencePaths.TryGetValue(CopyLocalFiles[i].ItemSpec, out updatedPath)) { NewCopyLocalFiles[i] = new TaskItem(CopyLocalFiles[i]); NewCopyLocalFiles[i].ItemSpec = updatedPath; } else { NewCopyLocalFiles[i] = CopyLocalFiles[i]; } } } return(true); }
ITaskItem ProcessAssembly(ITaskItem assemblyItem, byte[] keyBytes, StrongNamerAssemblyResolver resolver) { string signedAssemblyFolder = Path.GetFullPath(SignedAssemblyFolder.ItemSpec); if (!Directory.Exists(signedAssemblyFolder)) { Directory.CreateDirectory(signedAssemblyFolder); } string assemblyOutputPath = Path.Combine(signedAssemblyFolder, Path.GetFileName(assemblyItem.ItemSpec)); // Check if the signed assembly for this assembly already exists and the Mvid matches. // Avoid doing the work again if we can just re-use the existing assembly. // This also helps with build incrementality since we won't touch the timestamp of the signed // assembly if it didn't change (thus invalidating the entire build that depends on it). Guid existingAssemblyMvid = Guid.Empty; if (File.Exists(assemblyOutputPath)) { try { using (var existingModule = ModuleDefinition.ReadModule(assemblyOutputPath)) { existingAssemblyMvid = existingModule.Mvid; } } catch (Exception ex) { Log.LogMessage(MessageImportance.High, $"Assembly file '{assemblyItem.ItemSpec}' failed to load. Skipping. {ex}"); throw; } } if (!File.Exists(assemblyItem.ItemSpec)) { Log.LogMessage(MessageImportance.Low, $"Assembly file '{assemblyItem.ItemSpec}' does not exist (yet). Skipping."); return(assemblyItem); } using (var assembly = AssemblyDefinition.ReadAssembly(assemblyItem.ItemSpec, new ReaderParameters() { AssemblyResolver = resolver })) { if (assembly.Name.HasPublicKey) { Log.LogMessage(MessageImportance.Low, $"Assembly file '{assemblyItem.ItemSpec}' is already signed. Skipping."); return(assemblyItem); } if (existingAssemblyMvid != Guid.Empty && assembly.MainModule.Mvid == existingAssemblyMvid) { Log.LogMessage(MessageImportance.Low, $"Signed assembly already exists for '{assemblyItem.ItemSpec}' and the Mvid matches. Using existing signed assembly."); assemblyItem = new TaskItem(assemblyItem); assemblyItem.ItemSpec = assemblyOutputPath; return(assemblyItem); } var publicKey = GetPublicKey(keyBytes); var token = GetKeyTokenFromKey(publicKey); string formattedKeyToken = BitConverter.ToString(token).Replace("-", ""); Log.LogMessage(MessageImportance.Low, $"Signing assembly {assembly.FullName} with key with token {formattedKeyToken}"); assembly.Name.HashAlgorithm = Mono.Cecil.AssemblyHashAlgorithm.SHA1; assembly.Name.PublicKey = publicKey; assembly.Name.HasPublicKey = true; assembly.Name.Attributes &= AssemblyAttributes.PublicKey; foreach (var reference in assembly.MainModule.AssemblyReferences.Where(r => r.PublicKeyToken == null || r.PublicKeyToken.Length == 0)) { reference.PublicKeyToken = token; Log.LogMessage(MessageImportance.Low, $"Updating reference in assembly {assembly.FullName} to {reference.FullName} to use token {formattedKeyToken}"); } string fullPublicKey = BitConverter.ToString(publicKey).Replace("-", ""); var internalsVisibleToAttributes = assembly.CustomAttributes.Where(att => att.AttributeType.FullName == typeof(System.Runtime.CompilerServices.InternalsVisibleToAttribute).FullName).ToList(); foreach (var internalsVisibleToAttribute in internalsVisibleToAttributes) { string internalsVisibleToAssemblyName = (string)internalsVisibleToAttribute.ConstructorArguments[0].Value; string newInternalsVisibleToAssemblyName = internalsVisibleToAssemblyName + ", PublicKey=" + fullPublicKey; Log.LogMessage(MessageImportance.Low, $"Updating InternalsVisibleToAttribute in {assembly.FullName} from {internalsVisibleToAssemblyName} to {newInternalsVisibleToAssemblyName}"); internalsVisibleToAttribute.ConstructorArguments[0] = new CustomAttributeArgument(internalsVisibleToAttribute.ConstructorArguments[0].Type, newInternalsVisibleToAssemblyName); } Log.LogMessage(MessageImportance.Low, $"Writing signed assembly to {assemblyOutputPath}"); try { assembly.Write(assemblyOutputPath, new WriterParameters() { StrongNameKeyBlob = keyBytes }); } catch (Exception ex) { Log.LogMessage(MessageImportance.High, $"Failed to write signed assembly to '{assemblyOutputPath}'. {ex}"); File.Delete(assemblyOutputPath); } var ret = new TaskItem(assemblyItem); ret.ItemSpec = assemblyOutputPath; return(ret); } }