提交 4530422b 编写于 作者: J Jared Parsons

Remove SignRoslyn source

The tool now lives under dotnet/roslyn-tools.
上级 8f7d2e41
......@@ -356,8 +356,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perf.Utilities", "src\Test\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perf.Runner", "src\Test\Perf\Runner\Perf.Runner.csproj", "{1AA6D2F0-2C40-4AF6-BB79-50AFDCC62720}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignRoslyn", "src\Tools\SignRoslyn\SignRoslyn.csproj", "{3DA711E1-055F-4352-A5E1-F9169C86A20F}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.CodeAnalysis.Metadata", "src\Dependencies\CodeAnalysis.Metadata\Microsoft.CodeAnalysis.Metadata.shproj", "{D73ADF7D-2C1C-42AE-B2AB-EDC9497E4B71}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.CodeAnalysis.PooledObjects", "src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.shproj", "{C1930979-C824-496B-A630-70F5369A636F}"
......@@ -3061,26 +3059,6 @@ Global
{1AA6D2F0-2C40-4AF6-BB79-50AFDCC62720}.Release|x64.Build.0 = Release|Any CPU
{1AA6D2F0-2C40-4AF6-BB79-50AFDCC62720}.Release|x86.ActiveCfg = Release|Any CPU
{1AA6D2F0-2C40-4AF6-BB79-50AFDCC62720}.Release|x86.Build.0 = Release|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Debug|ARM.ActiveCfg = Debug|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Debug|ARM.Build.0 = Debug|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Debug|x64.ActiveCfg = Debug|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Debug|x64.Build.0 = Debug|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Debug|x86.ActiveCfg = Debug|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Debug|x86.Build.0 = Debug|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Release|Any CPU.Build.0 = Release|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Release|ARM.ActiveCfg = Release|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Release|ARM.Build.0 = Release|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Release|x64.ActiveCfg = Release|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Release|x64.Build.0 = Release|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Release|x86.ActiveCfg = Release|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Release|x86.Build.0 = Release|Any CPU
{FE0D4BDD-1C30-488E-A870-854F5B8C5014}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FE0D4BDD-1C30-488E-A870-854F5B8C5014}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FE0D4BDD-1C30-488E-A870-854F5B8C5014}.Debug|ARM.ActiveCfg = Debug|Any CPU
......@@ -3323,7 +3301,6 @@ Global
{DD13507E-D5AF-4B61-B11A-D55D6F4A73A5} = {CAD2965A-19AB-489F-BE2E-7649957F914A}
{59AD474E-2A35-4E8A-A74D-E33479977FBF} = {DD13507E-D5AF-4B61-B11A-D55D6F4A73A5}
{1AA6D2F0-2C40-4AF6-BB79-50AFDCC62720} = {DD13507E-D5AF-4B61-B11A-D55D6F4A73A5}
{3DA711E1-055F-4352-A5E1-F9169C86A20F} = {FD0FAF5F-1DED-485C-99FA-84B97F3A8EEC}
{D73ADF7D-2C1C-42AE-B2AB-EDC9497E4B71} = {C2D1346B-9665-4150-B644-075CF1636BAA}
{C1930979-C824-496B-A630-70F5369A636F} = {C2D1346B-9665-4150-B644-075CF1636BAA}
{FE0D4BDD-1C30-488E-A870-854F5B8C5014} = {8DBA5174-B0AA-4561-82B1-A46607697753}
......
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
</startup>
</configuration>
{
sign: [
{
"certificate": "Microsoft402",
"strongName": "MsSharedLib72",
"values": [
"csc.exe",
"csccore\\csc.exe",
"csi.exe",
"csicore\\csi.exe",
"InteractiveHost.exe",
"Microsoft.Build.Tasks.CodeAnalysis.dll",
"Microsoft.CodeAnalysis.CSharp.dll",
"Microsoft.CodeAnalysis.CSharp.EditorFeatures.dll",
"Microsoft.CodeAnalysis.CSharp.Features.dll",
"Microsoft.CodeAnalysis.CSharp.InteractiveEditorFeatures.dll",
"Microsoft.CodeAnalysis.CSharp.Scripting.dll",
"Microsoft.CodeAnalysis.CSharp.Workspaces.dll",
"Microsoft.CodeAnalysis.dll",
"Microsoft.CodeAnalysis.EditorFeatures.dll",
"Microsoft.CodeAnalysis.EditorFeatures.Next.dll",
"Microsoft.CodeAnalysis.EditorFeatures.Text.dll",
"Microsoft.CodeAnalysis.Features.dll",
"Microsoft.CodeAnalysis.InteractiveEditorFeatures.dll",
"Microsoft.CodeAnalysis.InteractiveFeatures.dll",
"Microsoft.CodeAnalysis.Remote.ServiceHub.dll",
"Microsoft.CodeAnalysis.Remote.Workspaces.dll",
"Microsoft.CodeAnalysis.Scripting.dll",
"Microsoft.CodeAnalysis.VisualBasic.dll",
"Microsoft.CodeAnalysis.VisualBasic.EditorFeatures.dll",
"Microsoft.CodeAnalysis.VisualBasic.Features.dll",
"Microsoft.CodeAnalysis.VisualBasic.InteractiveEditorFeatures.dll",
"Microsoft.CodeAnalysis.VisualBasic.Scripting.dll",
"Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll",
"Microsoft.CodeAnalysis.Workspaces.Desktop.dll",
"Microsoft.CodeAnalysis.Workspaces.dll",
"Microsoft.DiaSymReader.PortablePdb.dll",
"Microsoft.VisualStudio.CSharp.Repl.dll",
"Microsoft.VisualStudio.InteractiveServices.dll",
"Microsoft.VisualStudio.InteractiveWindow.dll",
"Microsoft.VisualStudio.LanguageServices.CSharp.dll",
"Microsoft.VisualStudio.LanguageServices.dll",
"Microsoft.VisualStudio.LanguageServices.Implementation.dll",
"Microsoft.VisualStudio.LanguageServices.Next.dll",
"Microsoft.VisualStudio.LanguageServices.SolutionExplorer.dll",
"Microsoft.VisualStudio.LanguageServices.VisualBasic.dll",
"Microsoft.VisualStudio.VisualBasic.Repl.dll",
"Microsoft.VisualStudio.VsInteractiveWindow.dll",
"Pdb2Xml.exe",
"Roslyn.Compilers.Extension.dll",
"Roslyn.Hosting.Diagnostics.dll",
"Roslyn.VisualStudio.DiagnosticsWindow.dll",
"Roslyn.VisualStudio.InteractiveComponents.dll",
"Roslyn.VisualStudio.Setup.Interactive.dll",
"SDK\\Roslyn.SyntaxVisualizer.DgmlHelper.dll",
"SDK\\Roslyn.SyntaxVisualizer.Control.dll",
"SDK\\Roslyn.SyntaxVisualizer.Extension.dll",
"SDK\\Roslyn.Templates.dll",
"vbc.exe",
"vbccore\\vbc.exe",
"VBCSCompiler.exe",
"vbi.exe",
"vbicore\\vbi.exe"
]
},
{
"certificate": "WindowsPhone623",
"strongName": "MsSharedLib72",
"values": [
"Microsoft.CodeAnalysis.ExpressionEvaluator.ExpressionCompiler.dll",
"Microsoft.CodeAnalysis.ExpressionEvaluator.ResultProvider.dll",
"Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.ExpressionCompiler.dll",
"Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.ResultProvider.dll",
"Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator.ExpressionCompiler.dll",
"Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator.ResultProvider.dll"
]
},
{
"certificate": "MicrosoftSHA1Win8WinBlue",
"strongName": "MsSharedLib72",
"values": [
"NetFX20\\Microsoft.CodeAnalysis.ExpressionEvaluator.ResultProvider.dll",
"NetFX20\\Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.ResultProvider.dll",
"NetFX20\\Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator.ResultProvider.dll"
]
},
{
"certificate": "VsixSHA2",
"strongName": null,
"values": [
"ExpressionEvaluatorPackage.vsix",
"Microsoft.VisualStudio.VsInteractiveWindow.vsix",
"Roslyn.Compilers.Extension.vsix",
"Roslyn.Deployment.Full.vsix",
"Roslyn.Deployment.Full.Next.vsix",
"Roslyn.VisualStudio.DiagnosticsWindow.vsix",
"Roslyn.VisualStudio.InteractiveComponents.vsix",
"Roslyn.VisualStudio.Setup.Interactive.vsix",
"Roslyn.VisualStudio.Setup.Next.vsix",
"Roslyn.VisualStudio.Setup.vsix",
"RoslynDeployment.vsix",
"SDK\\Roslyn Templates\\Release\\Roslyn SDK.vsix"
]
}
],
// Binaries which are included in VSIX above which are not to be signed.
exclude: [
"Esent.Interop.dll",
"Microsoft.CodeAnalysis.Elfie.dll",
"Microsoft.DiaSymReader.dll",
"Microsoft.DiaSymReader.Native.amd64.dll",
"Microsoft.DiaSymReader.Native.x86.dll",
"Newtonsoft.Json.dll",
"StreamJsonRpc.dll",
"StreamJsonRpc.resources.dll",
"System.Reflection.Metadata.dll",
"System.Collections.Immutable.dll",
"System.Diagnostics.StackTrace.dll",
"System.IO.FileSystem.dll",
"System.IO.FileSystem.Primitives.dll",
"System.Composition.AttributedModel.dll",
"System.Composition.Convention.dll",
"System.Composition.Hosting.dll",
"System.Composition.TypedParts.dll",
"System.Composition.Runtime.dll",
"Microsoft.Build.Conversion.Core.dll",
"Microsoft.Build.dll",
"Microsoft.Build.Engine.dll",
"Microsoft.Build.Framework.dll",
"Microsoft.Build.Tasks.Core.dll",
"Microsoft.Build.Utilities.Core.dll",
"Microsoft.VisualStudio.Threading.dll",
"Microsoft.VisualStudio.Validation.dll"
]
}
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace SignRoslyn
{
/// <summary>
/// Represents all of the input to the batch signing process.
/// </summary>
internal sealed class BatchSignInput
{
/// <summary>
/// The path where the binaries are built to: e:\path\to\source\Binaries\Debug
/// </summary>
internal string RootBinaryPath { get; }
/// <summary>
/// The names of the binaries to be signed. These are all relative paths off of the <see cref="RootBinaryPath"/>
/// property.
/// </summary>
internal ImmutableArray<FileName> BinaryNames { get; }
/// <summary>
/// These are binaries which are included in our VSIX files but are already signed. This list is used for
/// validation purpsoes. These are all flat names and cannot be relative paths.
/// </summary>
internal ImmutableArray<string> ExternalBinaryNames { get;}
/// <summary>
/// Names of assemblies that need to be signed. This is a subste of <see cref="BinaryNames"/>
/// </summary>
internal ImmutableArray<FileName> AssemblyNames { get; }
/// <summary>
/// Names of VSIX that need to be signed. This is a subste of <see cref="BinaryNames"/>
/// </summary>
internal ImmutableArray<FileName> VsixNames { get; }
/// <summary>
/// A map of all of the binaries that need to be signed to the actual signing data.
/// </summary>
internal ImmutableDictionary<FileName, FileSignInfo> BinarySignDataMap { get; }
internal BatchSignInput(string rootBinaryPath, Dictionary<string, SignInfo> fileSignDataMap, IEnumerable<string> externalBinaryNames)
{
RootBinaryPath = rootBinaryPath;
// Use order by to make the output of this tool as predictable as possible.
var binaryNames = fileSignDataMap.Keys;
BinaryNames = binaryNames.OrderBy(x => x).Select(x => new FileName(rootBinaryPath, x)).ToImmutableArray();
ExternalBinaryNames = externalBinaryNames.OrderBy(x => x).ToImmutableArray();
AssemblyNames = BinaryNames.Where(x => x.IsAssembly).ToImmutableArray();
VsixNames = BinaryNames.Where(x => x.IsVsix).ToImmutableArray();
var builder = ImmutableDictionary.CreateBuilder<FileName, FileSignInfo>();
foreach (var name in BinaryNames)
{
var data = fileSignDataMap[name.RelativePath];
builder.Add(name, new FileSignInfo(name, data));
}
BinarySignDataMap = builder.ToImmutable();
}
}
}
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading.Tasks;
using static SignRoslyn.PathUtil;
namespace SignRoslyn
{
internal sealed class BatchSignUtil
{
internal static readonly StringComparer FilePathComparer = StringComparer.OrdinalIgnoreCase;
private readonly BatchSignInput _batchData;
private readonly ISignTool _signTool;
private readonly ContentUtil _contentUtil = new ContentUtil();
internal BatchSignUtil(ISignTool signTool, BatchSignInput batchData)
{
_signTool = signTool;
_batchData = batchData;
}
internal void Go()
{
// First validate our inputs and give a useful error message about anything that happens to be missing
// from the binaries directory.
var vsixDataMap = VerifyBeforeSign();
// Next remove public sign from all of the assemblies. It can interfere with the signing process.
RemovePublicSign();
// Next step is to sign all of the assemblies.
SignAssemblies();
// Last we sign the VSIX files (being careful to take into account nesting)
SignVsixes(vsixDataMap);
// Validate the signing worked and produced actual signed binaries in all locations.
VerifyAfterSign(vsixDataMap);
}
private void RemovePublicSign()
{
Console.WriteLine("Removing public sign");
foreach (var name in _batchData.AssemblyNames)
{
Console.WriteLine($"\t{name}");
_signTool.RemovePublicSign(name.FullPath);
}
}
/// <summary>
/// Sign all of the assembly files. No need to consider nesting here and it can be done in a single pass.
/// </summary>
private void SignAssemblies()
{
Console.WriteLine("Signing assemblies");
foreach (var name in _batchData.AssemblyNames)
{
Console.WriteLine($"\t{name.RelativePath}");
}
_signTool.Sign(_batchData.AssemblyNames.Select(x => _batchData.BinarySignDataMap[x]));
}
/// <summary>
/// Sign all of the VSIX files. It is possible for VSIX to nest other VSIX so we must consider this when
/// picking the order.
/// </summary>
private void SignVsixes(Dictionary<FileName, VsixData> vsixDataMap)
{
var round = 0;
var signedSet = new HashSet<FileName>(_batchData.AssemblyNames);
var toSignList = _batchData.VsixNames.ToList();
do
{
Console.WriteLine($"Signing VSIX round {round}");
var list = new List<FileName>();
var i = 0;
var progress = false;
while (i < toSignList.Count)
{
var vsixName = toSignList[i];
var vsixData = vsixDataMap[vsixName];
var areNestedBinariesSigned = vsixData.NestedBinaryParts.All(x => signedSet.Contains(x.BinaryName));
if (areNestedBinariesSigned)
{
list.Add(vsixName);
toSignList.RemoveAt(i);
Console.WriteLine($"\tRepacking {vsixName}");
Repack(vsixData);
progress = true;
}
else
{
i++;
}
}
if (!progress)
{
throw new Exception("No progress made on nested VSIX which indicates validation bug");
}
Console.WriteLine($"\tSigning ...");
_signTool.Sign(list.Select(x => _batchData.BinarySignDataMap[x]));
// Signing is complete so now we can update the signed set.
list.ForEach(x => signedSet.Add(x));
round++;
} while (toSignList.Count > 0);
}
/// <summary>
/// Repack the VSIX with the signed parts from the binaries directory.
/// </summary>
private void Repack(VsixData vsixData)
{
using (var package = Package.Open(vsixData.Name.FullPath, FileMode.Open, FileAccess.ReadWrite))
{
foreach (var part in package.GetParts())
{
var relativeName = GetPartRelativeFileName(part);
var vsixPart = vsixData.GetNestedBinaryPart(relativeName);
if (!vsixPart.HasValue)
{
continue;
}
using (var stream = File.OpenRead(vsixPart.Value.BinaryName.FullPath))
using (var partStream = part.GetStream(FileMode.Open, FileAccess.ReadWrite))
{
stream.CopyTo(partStream);
partStream.SetLength(stream.Length);
}
}
}
}
/// <summary>
/// Get the name of all VSIX which are nested inside this VSIX.
/// </summary>
private IEnumerable<string> GetNestedVsixRelativeNames(FileName vsixName)
{
return GetVsixPartRelativeNames(vsixName).Where(x => IsVsix(x));
}
private bool AreNestedVsixSigned(FileName vsixName, HashSet<string> signedSet)
{
foreach (var relativeName in GetNestedVsixRelativeNames(vsixName))
{
var name = Path.GetFileName(relativeName);
if (!signedSet.Contains(name))
{
return false;
}
}
return true;
}
/// <summary>
/// Return all the assembly and VSIX contents nested in the VSIX
/// </summary>
private List<string> GetVsixPartRelativeNames(FileName vsixName)
{
var list = new List<string>();
using (var package = Package.Open(vsixName.FullPath, FileMode.Open, FileAccess.Read))
{
foreach (var part in package.GetParts())
{
var name = GetPartRelativeFileName(part);
list.Add(name);
}
}
return list;
}
private Dictionary<FileName, VsixData> VerifyBeforeSign()
{
var allGood = true;
var map = VerifyBinariesBeforeSign(ref allGood);
var vsixDataMap = VerifyVsixContentsBeforeSign(map, ref allGood);
if (!allGood)
{
throw new Exception("Errors validating the state before signing");
}
return vsixDataMap;
}
/// <summary>
/// Validate all of the binaries which are specified to be signed exist on disk. Compute their
/// checksums at this time so we can use it for VSIX content validation.
/// </summary>
private Dictionary<string, FileName> VerifyBinariesBeforeSign(ref bool allGood)
{
var checksumToNameMap = new Dictionary<string, FileName>(StringComparer.Ordinal);
foreach (var binaryName in _batchData.BinaryNames)
{
if (!File.Exists(binaryName.FullPath))
{
Console.WriteLine($"Did not find {binaryName} at {binaryName.FullPath}");
allGood = false;
continue;
}
// Ensure we don't attempt to sign a VSIX with a non-VSIX certificate or vice versa.
var fileSignInfo = _batchData.BinarySignDataMap[binaryName];
if (binaryName.IsVsix != IsVsixCertificate(fileSignInfo.Certificate))
{
var msg = binaryName.IsVsix
? $"Vsix {binaryName} must be signed with a VSIX certificate"
: $"Non-Vsix {binaryName} must not be signed with a VSIX certificate";
Console.WriteLine(msg);
allGood = false;
}
var checksum = _contentUtil.GetChecksum(binaryName.FullPath);
checksumToNameMap[checksum] = binaryName;
}
return checksumToNameMap;
}
private Dictionary<FileName, VsixData> VerifyVsixContentsBeforeSign(Dictionary<string, FileName> checksumToNameMap, ref bool allGood)
{
var vsixDataMap = new Dictionary<FileName, VsixData>();
foreach (var vsixName in _batchData.VsixNames)
{
var data = VerifyVsixContentsBeforeSign(vsixName, checksumToNameMap, ref allGood);
vsixDataMap[vsixName] = data;
}
return vsixDataMap;
}
private VsixData VerifyVsixContentsBeforeSign(FileName vsixName, Dictionary<string, FileName> checksumToNameMap, ref bool allGood)
{
var nestedExternalBinaries = new List<string>();
var nestedParts = new List<VsixPart>();
using (var package = Package.Open(vsixName.FullPath, FileMode.Open, FileAccess.Read))
{
foreach (var part in package.GetParts())
{
var relativeName = GetPartRelativeFileName(part);
var name = Path.GetFileName(relativeName);
if (!IsVsix(name) && !IsAssembly(name))
{
continue;
}
if (_batchData.ExternalBinaryNames.Contains(name))
{
nestedExternalBinaries.Add(name);
continue;
}
if (!_batchData.BinaryNames.Any(x => FilePathComparer.Equals(x.Name, name)))
{
allGood = false;
Console.WriteLine($"VSIX {vsixName} has part {name} which is not listed in the sign or external list");
continue;
}
// This represents a binary that we need to sign. Ensure the content in the VSIX is the same as the
// content in the binaries directory by doing a chekcsum match.
using (var stream = part.GetStream())
{
string checksum = _contentUtil.GetChecksum(stream);
FileName checksumName;
if (!checksumToNameMap.TryGetValue(checksum, out checksumName))
{
allGood = false;
Console.WriteLine($"{vsixName} has part {name} which does not match the content in the binaries directory");
continue;
}
if (!FilePathComparer.Equals(checksumName.Name, name))
{
allGood = false;
Console.WriteLine($"{vsixName} has part {name} with a different name in the binaries directory: {checksumName}");
continue;
}
nestedParts.Add(new VsixPart(relativeName, checksumName));
}
}
}
return new VsixData(vsixName, nestedParts.ToImmutableArray(), nestedExternalBinaries.ToImmutableArray());
}
private static string GetPartRelativeFileName(PackagePart part)
{
var path = part.Uri.OriginalString;
if (!string.IsNullOrEmpty(path) && path[0] == '/')
{
path = path.Substring(1);
}
return path;
}
private void VerifyAfterSign(Dictionary<FileName, VsixData> vsixData)
{
if (!VerifyAssembliesAfterSign() || !VerifyVsixContentsAfterSign(vsixData))
{
throw new Exception("Verification of signed binaries failed");
}
}
private bool VerifyAssembliesAfterSign()
{
var allGood = true;
foreach (var name in _batchData.AssemblyNames)
{
using (var stream = File.OpenRead(name.FullPath))
{
if (!_signTool.VerifySignedAssembly(stream))
{
allGood = false;
Console.WriteLine($"Assembly {name.RelativePath} is not signed properly");
}
}
}
return allGood;
}
private bool VerifyVsixContentsAfterSign(Dictionary<FileName, VsixData> vsixDataMap)
{
var allGood = true;
foreach (var vsixName in _batchData.VsixNames)
{
var vsixData = vsixDataMap[vsixName];
using (var package = Package.Open(vsixName.FullPath, FileMode.Open, FileAccess.Read))
{
foreach (var part in package.GetParts())
{
var relativeName = GetPartRelativeFileName(part);
var vsixPart = vsixData.GetNestedBinaryPart(relativeName);
if (!vsixPart.HasValue || !vsixPart.Value.BinaryName.IsAssembly)
{
continue;
}
using (var stream = part.GetStream())
{
if (!_signTool.VerifySignedAssembly(stream))
{
allGood = false;
Console.WriteLine($"Vsix {vsixName.RelativePath} has part {relativeName} which is not signed.");
}
}
}
}
}
return allGood;
}
private static bool IsVsixCertificate(string certificate)
{
return certificate.StartsWith("Vsix", StringComparison.OrdinalIgnoreCase);
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace SignRoslyn
{
internal sealed class ContentUtil
{
private readonly Dictionary<string, string> _filePathCache = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private readonly MD5 _md5 = MD5.Create();
internal string GetChecksum(Stream stream)
{
var hash = _md5.ComputeHash(stream);
return HashBytesToString(hash);
}
internal string GetChecksum(string filePath)
{
string checksum;
if (!_filePathCache.TryGetValue(filePath, out checksum))
{
using (var stream = File.OpenRead(filePath))
{
checksum = GetChecksum(stream);
}
_filePathCache[filePath] = checksum;
}
return checksum;
}
private string HashBytesToString(byte[] hash)
{
var data = BitConverter.ToString(hash);
return data.Replace("-", "");
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SignRoslyn
{
internal struct FileName : IEquatable<FileName>
{
internal string Name { get; }
internal string FullPath { get; }
internal string RelativePath { get; }
internal bool IsAssembly => PathUtil.IsAssembly(Name);
internal bool IsVsix => PathUtil.IsVsix(Name);
internal FileName(string rootBinaryPath, string relativePath)
{
Name = Path.GetFileName(relativePath);
FullPath = Path.Combine(rootBinaryPath, relativePath);
RelativePath = relativePath;
}
public static bool operator ==(FileName left, FileName right) => left.FullPath == right.FullPath;
public static bool operator !=(FileName left, FileName right) => !(left == right);
public bool Equals(FileName other) => this == other;
public override int GetHashCode() => FullPath.GetHashCode();
public override string ToString() => RelativePath;
public override bool Equals(object obj) => obj is FileName && Equals((FileName)obj);
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SignRoslyn
{
internal sealed class FileSignInfo
{
/// <summary>
/// The binary to be signed.
/// </summary>
internal FileName FileName { get; }
internal SignInfo FileSignData { get; }
/// <summary>
/// The authenticode certificate which should be used to sign the binary.
/// </summary>
internal string Certificate => FileSignData.Certificate;
/// <summary>
/// This will be null in the case a strong name signing is not required.
/// </summary>
internal string StrongName => FileSignData.StrongName;
internal FileSignInfo(FileName name, SignInfo fileSignData)
{
Debug.Assert(name.IsAssembly || fileSignData.StrongName == null);
FileName = name;
FileSignData = fileSignData;
}
}
}
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SignRoslyn.Json
{
internal sealed class FileJson
{
[JsonProperty(PropertyName = "sign")]
public FileSignData[] SignList { get; set; }
[JsonProperty(PropertyName = "exclude")]
public string[] ExcludeList { get; set; }
public FileJson()
{
}
}
internal sealed class FileSignData
{
[JsonProperty(PropertyName = "certificate")]
public string Certificate { get; set; }
[JsonProperty(PropertyName = "strongName")]
public string StrongName { get; set; }
[JsonProperty(PropertyName = "values")]
public string[] FileList { get; set; }
public FileSignData()
{
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace SignRoslyn
{
internal static class PathUtil
{
internal static bool IsVsix(string fileName)
{
return Path.GetExtension(fileName) == ".vsix";
}
internal static bool IsAssembly(string fileName)
{
var ext = Path.GetExtension(fileName);
return ext == ".exe" || ext == ".dll";
}
}
}
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SignRoslyn
{
internal static class Program
{
internal static void Main(string[] args)
{
string binariesPath;
string sourcePath;
bool test;
if (!ParseCommandLineArguments(args, out binariesPath, out sourcePath, out test))
{
Console.WriteLine("signroslyn.exe [-test] [-binariesPath <path>]");
Environment.Exit(1);
}
var signTool = SignToolFactory.Create(AppContext.BaseDirectory, binariesPath, sourcePath, test);
var batchData = ReadBatchSignInput(binariesPath);
var util = new BatchSignUtil(signTool, batchData);
util.Go();
}
internal static BatchSignInput ReadBatchSignInput(string rootBinaryPath)
{
var filePath = Path.Combine(AppContext.BaseDirectory, "BatchSignData.json");
using (var file = File.OpenText(filePath))
{
var serializer = new JsonSerializer();
var fileJson = (Json.FileJson)serializer.Deserialize(file, typeof(Json.FileJson));
var map = new Dictionary<string, SignInfo>();
var allGood = true;
foreach (var item in fileJson.SignList)
{
var data = new SignInfo(certificate: item.Certificate, strongName: item.StrongName);
foreach (var name in item.FileList)
{
if (map.ContainsKey(name))
{
Console.WriteLine($"Duplicate file entry: {name}");
allGood = false;
}
else
{
map.Add(name, data);
}
}
}
if (!allGood)
{
Environment.Exit(1);
}
return new BatchSignInput(rootBinaryPath, map, fileJson.ExcludeList);
}
}
internal static bool ParseCommandLineArguments(
string[] args,
out string binariesPath,
out string sourcePath,
out bool test)
{
binariesPath = Path.GetDirectoryName(Path.GetDirectoryName(AppContext.BaseDirectory));
sourcePath = null;
test = false;
var i = 0;
while (i < args.Length)
{
var current = args[i];
switch (current.ToLower())
{
case "-test":
test = true;
i++;
break;
case "-binariespath":
if (i + 1 >= args.Length)
{
Console.WriteLine("-binariesPath needs an argument");
return false;
}
binariesPath = args[i + 1];
i += 2;
break;
default:
Console.WriteLine($"Unrecognized option {current}");
return false;
}
}
sourcePath = Path.GetDirectoryName(Path.GetDirectoryName(binariesPath));
return true;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SignRoslyn
{
internal struct SignInfo
{
/// <summary>
/// The authenticode certificate which should be used to sign the binary.
/// </summary>
internal string Certificate { get; }
/// <summary>
/// This will be null in the case a strong name signing is not required.
/// </summary>
internal string StrongName { get; }
internal SignInfo(string certificate, string strongName)
{
Certificate = certificate;
StrongName = strongName;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\build\Targets\VSL.Settings.targets" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{3DA711E1-055F-4352-A5E1-F9169C86A20F}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SignRoslyn</RootNamespace>
<AssemblyName>SignRoslyn</AssemblyName>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<OutDir>$(OutDir)\$(AssemblyName)</OutDir>
<TargetFrameworkProfile />
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' " />
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="FileName.cs" />
<Compile Include="FileSignInfo.cs" />
<Compile Include="ContentUtil.cs" />
<Compile Include="JsonTypes.cs" />
<Compile Include="SignInfo.cs" />
<Compile Include="PathUtil.cs" />
<Compile Include="Program.cs" />
<Compile Include="SignTool.RealSignTool.cs" />
<Compile Include="BatchSignUtil.cs" />
<Compile Include="BatchSignInput.cs" />
<Compile Include="SignTool.cs" />
<Compile Include="SignTool.SignToolBase.cs" />
<Compile Include="SignTool.TestSignTool.cs" />
<Compile Include="VsixData.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="project.json" />
</ItemGroup>
<ItemGroup>
<None Include="BatchSignData.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<Import Project="..\..\..\build\Targets\VSL.Imports.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
\ No newline at end of file

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.24720.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignRoslyn", "SignRoslyn.csproj", "{3DA711E1-055F-4352-A5E1-F9169C86A20F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Metadata", "..\..\Dependencies\Metadata\Metadata.csproj", "{E6796B97-D5C6-45B2-AE46-351D15DCFC71}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Immutable", "..\..\Dependencies\Immutable\Immutable.csproj", "{DCDA908D-EF5E-494B-ADDC-C26F5FD610CA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3DA711E1-055F-4352-A5E1-F9169C86A20F}.Release|Any CPU.Build.0 = Release|Any CPU
{E6796B97-D5C6-45B2-AE46-351D15DCFC71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E6796B97-D5C6-45B2-AE46-351D15DCFC71}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E6796B97-D5C6-45B2-AE46-351D15DCFC71}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E6796B97-D5C6-45B2-AE46-351D15DCFC71}.Release|Any CPU.Build.0 = Release|Any CPU
{DCDA908D-EF5E-494B-ADDC-C26F5FD610CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DCDA908D-EF5E-494B-ADDC-C26F5FD610CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DCDA908D-EF5E-494B-ADDC-C26F5FD610CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DCDA908D-EF5E-494B-ADDC-C26F5FD610CA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace SignRoslyn
{
internal static partial class SignToolFactory
{
/// <summary>
/// The signing implementation which actually signs binaries.
/// </summary>
private sealed class RealSignTool : SignToolBase
{
/// <summary>
/// The number of bytes from the start of the <see cref="CorHeader"/> to its <see cref="CorFlags"/>.
/// </summary>
internal const int OffsetFromStartOfCorHeaderToFlags =
sizeof(Int32) // byte count
+ sizeof(Int16) // major version
+ sizeof(Int16) // minor version
+ sizeof(Int64); // metadata directory
internal RealSignTool(string appPath, string binariesPath, string sourcePath)
: base(appPath, binariesPath, sourcePath)
{
}
protected override int RunMSBuild(ProcessStartInfo startInfo)
{
var process = Process.Start(startInfo);
process.OutputDataReceived += (sender, e) =>
{
Console.WriteLine(e.Data);
};
process.BeginOutputReadLine();
process.WaitForExit();
return process.ExitCode;
}
/// <summary>
/// Returns true if the PE file meets all of the pre-conditions to be Open Source Signed.
/// Returns false and logs msbuild errors otherwise.
/// </summary>
private static bool IsPublicSigned(PEReader peReader)
{
if (!peReader.HasMetadata)
{
return false;
}
var mdReader = peReader.GetMetadataReader();
if (!mdReader.IsAssembly)
{
return false;
}
CorHeader header = peReader.PEHeaders.CorHeader;
return (header.Flags & CorFlags.StrongNameSigned) == CorFlags.StrongNameSigned;
}
public override void RemovePublicSign(string assemblyPath)
{
using (var stream = new FileStream(assemblyPath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read))
using (var peReader = new PEReader(stream))
using (var writer = new BinaryWriter(stream))
{
if (!IsPublicSigned(peReader))
{
return;
}
stream.Position = peReader.PEHeaders.CorHeaderStartOffset + OffsetFromStartOfCorHeaderToFlags;
writer.Write((UInt32)(peReader.PEHeaders.CorHeader.Flags | CorFlags.StrongNameSigned));
}
}
public override bool VerifySignedAssembly(Stream assemblyStream)
{
using (var memoryStream = new MemoryStream())
{
assemblyStream.CopyTo(memoryStream);
var byteArray = memoryStream.ToArray();
unsafe
{
fixed (byte* bytes = byteArray)
{
int outFlags;
return NativeMethods.StrongNameSignatureVerificationFromImage(
bytes,
byteArray.Length,
NativeMethods.SN_INFLAG_FORCE_VER, out outFlags) &&
(outFlags & NativeMethods.SN_OUTFLAG_WAS_VERIFIED) == NativeMethods.SN_OUTFLAG_WAS_VERIFIED;
}
}
}
}
private unsafe static class NativeMethods
{
public const int SN_INFLAG_FORCE_VER = 0x1;
public const int SN_OUTFLAG_WAS_VERIFIED = 0x1;
[DllImport("mscoree.dll", CharSet = CharSet.Unicode)]
[PreserveSig]
public static extern bool StrongNameSignatureVerificationFromImage(byte* bytes, int length, int inFlags, out int outFlags);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using static SignRoslyn.PathUtil;
namespace SignRoslyn
{
internal static partial class SignToolFactory
{
private abstract class SignToolBase : ISignTool
{
internal string MSBuildPath { get; }
internal string BinariesPath { get; }
internal string SourcePath { get; }
internal string AppPath { get; }
internal SignToolBase(string appPath, string binariesPath, string sourcePath)
{
BinariesPath = binariesPath;
SourcePath = sourcePath;
AppPath = appPath;
var path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
MSBuildPath = Path.Combine(path, @"MSBuild\14.0\Bin\MSBuild.exe");
if (!File.Exists(MSBuildPath))
{
throw new Exception(@"Unable to locate MSBuild at the path {_msbuildPath}");
}
}
public abstract void RemovePublicSign(string assemblyPath);
public abstract bool VerifySignedAssembly(Stream assemblyStream);
protected abstract int RunMSBuild(ProcessStartInfo startInfo);
public void Sign(IEnumerable<FileSignInfo> filesToSign)
{
var buildFilePath = Path.Combine(AppPath, "build.proj");
var commandLine = new StringBuilder();
commandLine.Append(@"/v:m /target:RoslynSign ");
commandLine.Append($@"""{buildFilePath}"" ");
Console.WriteLine($"msbuild.exe {commandLine.ToString()}");
var content = GenerateBuildFileContent(filesToSign);
File.WriteAllText(buildFilePath, content);
Console.WriteLine("Generated project file");
Console.WriteLine(content);
var startInfo = new ProcessStartInfo()
{
FileName = MSBuildPath,
Arguments = commandLine.ToString(),
UseShellExecute = false,
RedirectStandardOutput = true,
WorkingDirectory = AppPath,
};
var exitCode = RunMSBuild(startInfo);
File.Delete(buildFilePath);
if (exitCode != 0)
{
Console.WriteLine("MSBuild failed!!!");
throw new Exception("Sign failed");
}
}
private string GenerateBuildFileContent(IEnumerable<FileSignInfo> filesToSign)
{
var builder = new StringBuilder();
AppendLine(builder, depth: 0, text: @"<?xml version=""1.0"" encoding=""utf-8""?>");
AppendLine(builder, depth: 0, text: @"<Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">");
AppendLine(builder, depth: 1, text: $@"<Import Project=""{Path.Combine(SourcePath, @"build\Targets\VSL.Settings.targets")}"" />");
AppendLine(builder, depth: 1, text: $@"<Import Project=""$(NuGetPackageRoot)\MicroBuild.Core\0.2.0\build\MicroBuild.Core.props"" />");
AppendLine(builder, depth: 1, text: $@"<Import Project=""$(NuGetPackageRoot)\MicroBuild.Core\0.2.0\build\MicroBuild.Core.targets"" />");
AppendLine(builder, depth: 1, text: $@"<ItemGroup>");
foreach (var fileToSign in filesToSign)
{
AppendLine(builder, depth: 2, text: $@"<FilesToSign Include=""{fileToSign.FileName.FullPath}"">");
AppendLine(builder, depth: 3, text: $@"<Authenticode>{fileToSign.Certificate}</Authenticode>");
if (fileToSign.StrongName != null)
{
AppendLine(builder, depth: 3, text: $@"<StrongName>{fileToSign.StrongName}</StrongName>");
}
AppendLine(builder, depth: 2, text: @"</FilesToSign>");
}
AppendLine(builder, depth: 1, text: $@"</ItemGroup>");
var intermediatesPath = Path.Combine(Path.GetDirectoryName(BinariesPath), "Obj");
AppendLine(builder, depth: 1, text: @"<Target Name=""RoslynSign"">");
AppendLine(builder, depth: 2, text: $@"<SignFiles Files=""@(FilesToSign)"" BinariesDirectory=""{BinariesPath}"" IntermediatesDirectory=""{intermediatesPath}"" Type=""real"" />");
AppendLine(builder, depth: 1, text: @"</Target>");
AppendLine(builder, depth: 0, text: @"</Project>");
return builder.ToString();
}
private static void AppendLine(StringBuilder builder, int depth, string text)
{
for (int i = 0; i < depth; i++)
{
builder.Append(" ");
}
builder.AppendLine(text);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SignRoslyn
{
internal static partial class SignToolFactory
{
/// <summary>
/// The <see cref="SignToolBase"/> implementation used for test / validation runs. Does not actually
/// change the sign state of the binaries.
/// </summary>
private sealed class TestSignTool : SignToolBase
{
internal TestSignTool(string appPath, string binariesPath, string sourcePath)
: base(appPath, binariesPath, sourcePath)
{
}
public override void RemovePublicSign(string assemblyPath)
{
}
public override bool VerifySignedAssembly(Stream assemblyStream)
{
return true;
}
protected override int RunMSBuild(ProcessStartInfo startInfo)
{
return 0;
}
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SignRoslyn
{
internal interface ISignTool
{
void RemovePublicSign(string assemblyPath);
bool VerifySignedAssembly(Stream assemblyStream);
void Sign(IEnumerable<FileSignInfo> filesToSign);
}
internal static partial class SignToolFactory
{
internal static ISignTool Create(string appPath, string binariesPath, string sourcePath, bool test)
{
if (test)
{
return new TestSignTool(appPath, binariesPath, sourcePath);
}
return new RealSignTool(appPath, binariesPath, sourcePath);
}
}
}
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SignRoslyn
{
internal sealed class VsixData
{
/// <summary>
/// Name of the VSIX
/// </summary>
internal FileName Name { get; }
/// <summary>
/// The set of binaries nested inside this VSIX.
/// </summary>
internal ImmutableArray<VsixPart> NestedBinaryParts;
/// <summary>
/// The set of external binaries this VSIX depends on.
/// </summary>
internal ImmutableArray<string> NestedExternalNames { get; }
internal VsixData(FileName name, ImmutableArray<VsixPart> nestedBinaryParts, ImmutableArray<string> nestedExternalNames)
{
Name = name;
NestedBinaryParts = nestedBinaryParts;
NestedExternalNames = nestedExternalNames;
}
internal VsixPart? GetNestedBinaryPart(string relativeName)
{
foreach (var part in NestedBinaryParts)
{
if (relativeName == part.RelativeName)
{
return part;
}
}
return null;
}
}
internal struct VsixPart
{
internal string RelativeName { get; }
internal FileName BinaryName { get; }
internal VsixPart(string relativeName, FileName binaryName)
{
RelativeName = relativeName;
BinaryName = binaryName;
}
public override string ToString() => $"{RelativeName} -> {BinaryName.RelativePath}";
}
}
{
"supports": {},
"dependencies": {
"System.Collections.Immutable": "1.2.0",
"System.Reflection.Metadata": "1.4.1-beta-24410-02",
"Newtonsoft.Json": "8.0.3"
},
"frameworks": {
".NETFramework,Version=v4.6": {}
},
"runtimes": {
"win": {},
"win7": {}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册