diff --git a/Arcade.sln b/Arcade.sln index 3bd9171814a..b2eeb0ff6e2 100644 --- a/Arcade.sln +++ b/Arcade.sln @@ -97,8 +97,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Build.Task EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.RemoteExecutor.Tests", "src\Microsoft.DotNet.RemoteExecutor\tests\Microsoft.DotNet.RemoteExecutor.Tests.csproj", "{D6AC20A4-1719-49FE-B112-B2AB564496F8}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestProjects", "TestProjects", "{6F517597-E9E2-43B2-B7E2-757132EA525C}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Build.Tasks.Archives", "src\Microsoft.DotNet.Build.Tasks.Archives\Microsoft.DotNet.Build.Tasks.Archives.csproj", "{5579768A-CC07-477C-ACE4-06FE9B0686A7}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.SourceBuild.Tasks", "src\Microsoft.DotNet.SourceBuild\tasks\Microsoft.DotNet.SourceBuild.Tasks.csproj", "{F9D72AF5-9320-43C8-A24F-CBE294FCED0A}" @@ -129,10 +127,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Build.Task EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Build.Tasks.Templating.Tests", "src\Microsoft.DotNet.Build.Tasks.Templating\test\Microsoft.DotNet.Build.Tasks.Templating.Tests.csproj", "{FB4168D5-6EA6-4777-AD4F-95758C177FE8}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6DA9F58A-34D5-45A6-998E-5D2B8037C3FE}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.DotNet.XUnitAssert", "Microsoft.DotNet.XUnitAssert", "{3C542789-2576-48C8-9772-C9D7575F7E42}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.XUnitAssert", "src\Microsoft.DotNet.XUnitAssert\src\Microsoft.DotNet.XUnitAssert.csproj", "{AB8D5F86-60FA-416A-B047-83B1E9118425}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.XUnitAssert.Tests", "src\Microsoft.DotNet.XUnitAssert\tests\Microsoft.DotNet.XUnitAssert.Tests.csproj", "{14462553-E4E1-4F67-B954-4BF24B1DAAFE}" @@ -151,9 +145,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Internal.S EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.ArcadeAzureIntegration", "src\Microsoft.DotNet.ArcadeAzureIntegration\Microsoft.DotNet.ArcadeAzureIntegration.csproj", "{CA159C84-CD7D-4364-9121-3842F97D4B60}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.MacOsPkg", "src\Microsoft.DotNet.MacOsPkg\Microsoft.DotNet.MacOsPkg.csproj", "{CE0FAEB2-4B8A-4A37-840D-7FF88ECB42A0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.MacOsPkg", "src\Microsoft.DotNet.MacOsPkg\Microsoft.DotNet.MacOsPkg.csproj", "{CE0FAEB2-4B8A-4A37-840D-7FF88ECB42A0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.MacOsPkg.Tests", "src\Microsoft.DotNet.MacOsPkg.Tests\Microsoft.DotNet.MacOsPkg.Tests.csproj", "{1F5118A8-A5C5-4D18-AF34-FFB60FECCD45}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.MacOsPkg.Tests", "src\Microsoft.DotNet.MacOsPkg.Tests\Microsoft.DotNet.MacOsPkg.Tests.csproj", "{1F5118A8-A5C5-4D18-AF34-FFB60FECCD45}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1009,13 +1003,10 @@ Global {B5E9D9D8-59E0-49F8-9C3C-75138A2D452C} = {C53DD924-C212-49EA-9BC4-1827421361EF} {0B5D3C20-EB58-4A82-A3AA-2E626A17B35D} = {C53DD924-C212-49EA-9BC4-1827421361EF} {FB4168D5-6EA6-4777-AD4F-95758C177FE8} = {C53DD924-C212-49EA-9BC4-1827421361EF} - {3C542789-2576-48C8-9772-C9D7575F7E42} = {6DA9F58A-34D5-45A6-998E-5D2B8037C3FE} - {AB8D5F86-60FA-416A-B047-83B1E9118425} = {3C542789-2576-48C8-9772-C9D7575F7E42} - {14462553-E4E1-4F67-B954-4BF24B1DAAFE} = {3C542789-2576-48C8-9772-C9D7575F7E42} + {14462553-E4E1-4F67-B954-4BF24B1DAAFE} = {C53DD924-C212-49EA-9BC4-1827421361EF} {650B7526-7B8A-45B5-B14E-C16D828891B2} = {C53DD924-C212-49EA-9BC4-1827421361EF} {6BA81447-C61D-4F91-BF0F-5B17AF4CFFAC} = {C53DD924-C212-49EA-9BC4-1827421361EF} - {CE0FAEB2-4B8A-4A37-840D-7FF88ECB42A0} = {6DA9F58A-34D5-45A6-998E-5D2B8037C3FE} - {1F5118A8-A5C5-4D18-AF34-FFB60FECCD45} = {6DA9F58A-34D5-45A6-998E-5D2B8037C3FE} + {1F5118A8-A5C5-4D18-AF34-FFB60FECCD45} = {C53DD924-C212-49EA-9BC4-1827421361EF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {32B9C883-432E-4FC8-A1BF-090EB033DD5B} diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/Sign.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/Sign.proj index b4c7aab2df6..aa556444b58 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/Sign.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/Sign.proj @@ -20,6 +20,7 @@ + @@ -57,6 +58,9 @@ $(NuGetPackageRoot)sn\$(SNVersion)\sn.exe + + + $(NuGetPackageRoot)microsoft.dotnet.macospkg\$(MicrosoftDotNetMacOsPkgVersion)\tools\$(NetToolCurrent)\any\Microsoft.Dotnet.MacOsPkg.dll diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/Sign.props b/src/Microsoft.DotNet.Arcade.Sdk/tools/Sign.props index 5889e5bacf8..b07c3d4dbc3 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/Sign.props +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/Sign.props @@ -18,11 +18,14 @@ + + + + + @@ -60,6 +63,13 @@ + + + + + + + + + <_PkgToolPattern>@(_PkgToolPath->'%(RootDir)%(Directory)')**\*.* + + + <_PkgToolFiles Include="$(_PkgToolPattern)"/> + + + + + diff --git a/src/Microsoft.DotNet.SignTool.Tests/Resources/NestedPkg.pkg b/src/Microsoft.DotNet.SignTool.Tests/Resources/NestedPkg.pkg new file mode 100644 index 00000000000..9e72d029f1b Binary files /dev/null and b/src/Microsoft.DotNet.SignTool.Tests/Resources/NestedPkg.pkg differ diff --git a/src/Microsoft.DotNet.SignTool.Tests/Resources/WithApp.pkg b/src/Microsoft.DotNet.SignTool.Tests/Resources/WithApp.pkg new file mode 100644 index 00000000000..ee5974edf8a Binary files /dev/null and b/src/Microsoft.DotNet.SignTool.Tests/Resources/WithApp.pkg differ diff --git a/src/Microsoft.DotNet.SignTool.Tests/Resources/filewithoutextension b/src/Microsoft.DotNet.SignTool.Tests/Resources/filewithoutextension new file mode 100644 index 00000000000..0637880d70d --- /dev/null +++ b/src/Microsoft.DotNet.SignTool.Tests/Resources/filewithoutextension @@ -0,0 +1 @@ +This is a file diff --git a/src/Microsoft.DotNet.SignTool.Tests/Resources/test.pkg b/src/Microsoft.DotNet.SignTool.Tests/Resources/test.pkg new file mode 100644 index 00000000000..c7e5584a39f Binary files /dev/null and b/src/Microsoft.DotNet.SignTool.Tests/Resources/test.pkg differ diff --git a/src/Microsoft.DotNet.SignTool.Tests/SignToolTests.cs b/src/Microsoft.DotNet.SignTool.Tests/SignToolTests.cs index e02be9a663f..891853d0003 100644 --- a/src/Microsoft.DotNet.SignTool.Tests/SignToolTests.cs +++ b/src/Microsoft.DotNet.SignTool.Tests/SignToolTests.cs @@ -42,6 +42,8 @@ public class SignToolTests : IDisposable {".vsix", new List{ new SignInfo("VsixSHA2") } }, {".zip", new List{ SignInfo.Ignore } }, {".tgz", new List{ SignInfo.Ignore } }, + {".pkg", new List{ new SignInfo("MacDeveloperHarden") } }, // lgtm [cs/common-default-passwords] Safe, these are certificate names + {".app", new List{ new SignInfo("MacDeveloperHarden") } }, // lgtm [cs/common-default-passwords] Safe, these are certificate names {".py", new List{ new SignInfo("Microsoft400") } }, // lgtm [cs/common-default-passwords] Safe, these are certificate names {".nupkg", new List{ new SignInfo("NuGet") } }, {".symbols.nupkg", new List{ SignInfo.Ignore } }, @@ -69,6 +71,8 @@ public class SignToolTests : IDisposable { ".vsix", new List{ new SignInfo("VsixSHA2", collisionPriorityId: "123") } }, { ".zip", new List{ SignInfo.Ignore } }, { ".tgz", new List{ SignInfo.Ignore } }, + { ".pkg", new List{ new SignInfo("Microsoft400", collisionPriorityId: "123") } }, + { ".app", new List{ new SignInfo("Microsoft400", collisionPriorityId: "123") } }, { ".nupkg", new List{ new SignInfo("NuGet", collisionPriorityId: "123") } }, { ".symbols.nupkg", new List{ SignInfo.Ignore } }, }; @@ -124,6 +128,14 @@ public class SignToolTests : IDisposable { "CertificateName", "None" }, { SignToolConstants.CollisionPriorityId, "123" } }), + new TaskItem(".pkg", new Dictionary { + { "CertificateName", "Microsoft400" }, + { SignToolConstants.CollisionPriorityId, "123" } + }), + new TaskItem(".app", new Dictionary { + { "CertificateName", "Microsoft400" }, + { SignToolConstants.CollisionPriorityId, "123" } + }), new TaskItem(".nupkg", new Dictionary { { "CertificateName", "NuGet" }, { SignToolConstants.CollisionPriorityId, "123" } @@ -270,6 +282,7 @@ private string GetWixToolPath() private static string s_snPath = Path.Combine(Path.GetDirectoryName(typeof(SignToolTests).Assembly.Location), "tools", "sn", "sn.exe"); private static string s_tarToolPath = Path.Combine(Path.GetDirectoryName(typeof(SignToolTests).Assembly.Location), "tools", "tar", "Microsoft.Dotnet.Tar.dll"); + private static string s_pkgToolPath = Path.Combine(Path.GetDirectoryName(typeof(SignToolTests).Assembly.Location), "tools", "pkg", "Microsoft.Dotnet.MacOsPkg.dll"); private string GetResourcePath(string name, string relativePath = null) { @@ -314,12 +327,12 @@ public void Dispose() } private void ValidateGeneratedProject( - ITaskItem[] itemsToSign, + List itemsToSign, Dictionary> strongNameSignInfo, Dictionary fileSignInfo, Dictionary> extensionsSignInfo, string[] expectedXmlElementsPerSigningRound, - ITaskItem[] dualCertificates = null, + Dictionary> additionalCertificateInfo = null, string wixToolsPath = null) { var buildEngine = new FakeBuildEngine(); @@ -329,10 +342,10 @@ private void ValidateGeneratedProject( // The path to DotNet will always be null in these tests, this will force // the signing logic to call our FakeBuildEngine.BuildProjectFile with a path // to the XML that store the content of the would be Microbuild sign request. - var signToolArgs = new SignToolArgs(_tmpDir, microBuildCorePath: "MicroBuildCorePath", testSign: true, dotnetPath: null, _tmpDir, enclosingDir: "", "", wixToolsPath: wixToolsPath, tarToolPath: s_tarToolPath); + var signToolArgs = new SignToolArgs(_tmpDir, microBuildCorePath: "MicroBuildCorePath", testSign: true, dotnetPath: null, _tmpDir, enclosingDir: "", "", wixToolsPath: wixToolsPath, tarToolPath: s_tarToolPath, pkgToolPath: s_pkgToolPath); var signTool = new FakeSignTool(signToolArgs, task.Log); - var configuration = new Configuration(signToolArgs.TempDir, itemsToSign, strongNameSignInfo, fileSignInfo, extensionsSignInfo, dualCertificates, tarToolPath: s_tarToolPath, snPath: s_snPath, task.Log); + var configuration = new Configuration(signToolArgs.TempDir, itemsToSign, strongNameSignInfo, fileSignInfo, extensionsSignInfo, additionalCertificateInfo, tarToolPath: s_tarToolPath, pkgToolPath: s_pkgToolPath, snPath: s_snPath, task.Log); var signingInput = configuration.GenerateListOfFiles(); var util = new BatchSignUtil( task.BuildEngine, @@ -366,19 +379,19 @@ private void ValidateGeneratedProject( } private void ValidateFileSignInfos( - ITaskItem[] itemsToSign, + List itemsToSign, Dictionary> strongNameSignInfo, Dictionary fileSignInfo, Dictionary> extensionsSignInfo, string[] expected, string[] expectedCopyFiles = null, - ITaskItem[] dualCertificates = null, + Dictionary> additionalCertificateInfo = null, string[] expectedErrors = null, string[] expectedWarnings = null) { var engine = new FakeBuildEngine(); var task = new SignToolTask { BuildEngine = engine }; - var signingInput = new Configuration(_tmpDir, itemsToSign, strongNameSignInfo, fileSignInfo, extensionsSignInfo, dualCertificates, tarToolPath: s_tarToolPath, snPath: s_snPath, task.Log).GenerateListOfFiles(); + var signingInput = new Configuration(_tmpDir, itemsToSign, strongNameSignInfo, fileSignInfo, extensionsSignInfo, additionalCertificateInfo, tarToolPath: s_tarToolPath, pkgToolPath: s_pkgToolPath, snPath: s_snPath, task.Log).GenerateListOfFiles(); signingInput.FilesToSign.Select(f => f.ToString()).Should().BeEquivalentTo(expected); signingInput.FilesToCopy.Select(f => $"{f.Key} -> {f.Value}").Should().BeEquivalentTo(expectedCopyFiles ?? Array.Empty()); @@ -507,14 +520,14 @@ private void ValidateProducedRpmContent( [Fact] public void EmptySigningList() { - var itemsToSign = new ITaskItem[0]; + var itemsToSign = new List(); var strongNameSignInfo = new Dictionary>(); var fileSignInfo = new Dictionary(); var task = new SignToolTask { BuildEngine = new FakeBuildEngine() }; - var signingInput = new Configuration(_tmpDir, itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, null, tarToolPath: s_tarToolPath, snPath: s_snPath, task.Log).GenerateListOfFiles(); + var signingInput = new Configuration(_tmpDir, itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, null, tarToolPath: s_tarToolPath, pkgToolPath: s_pkgToolPath, snPath: s_snPath, task.Log).GenerateListOfFiles(); signingInput.FilesToSign.Should().BeEmpty(); signingInput.ZipDataMap.Should().BeEmpty(); @@ -534,7 +547,8 @@ public void EmptySigningListForTask() DryRun = false, TestSign = true, DotNetPath = CreateTestResource("dotnet.fake"), - SNBinaryPath = CreateTestResource("fake.sn.exe") + SNBinaryPath = CreateTestResource("fake.sn.exe"), + PkgToolPath = s_pkgToolPath, }; task.Execute().Should().BeTrue(); @@ -555,6 +569,7 @@ public void SignWhenSnExeIsNotRequired() DotNetPath = CreateTestResource("dotnet.fake"), DoStrongNameCheck = false, SNBinaryPath = null, + PkgToolPath = s_pkgToolPath, }; task.Execute().Should().BeTrue(); @@ -564,18 +579,15 @@ public void SignWhenSnExeIsNotRequired() public void OnlyContainer() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("ContainerOne.1.0.0.nupkg"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }) + new ItemToSign(GetResourcePath("ContainerOne.1.0.0.nupkg"), "") }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List {new SignInfo("3PartySHA2", "ArcadeStrongTest", "123") } } + { "581d91ccdfc4ea9c", new List {new SignInfo(certificate: "3PartySHA2", strongName: "ArcadeStrongTest", collisionPriorityId: "123") } } }; // Overriding information @@ -602,15 +614,15 @@ public void OnlyContainer() public void SkipSigning() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("ContainerOne.1.0.0.nupkg")) + new ItemToSign(GetResourcePath("ContainerOne.1.0.0.nupkg")) }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List {new SignInfo("3PartySHA2", "ArcadeStrongTest") } } + { "581d91ccdfc4ea9c", new List {new SignInfo(certificate: "3PartySHA2", strongName: "ArcadeStrongTest") } } }; // Overriding information @@ -634,15 +646,16 @@ public void SkipSigning() public void SkipStrongNamingForAlreadyStrongNamedBinary() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("SignedLibrary.dll")) + new ItemToSign(GetResourcePath("SignedLibrary.dll")), + new ItemToSign(GetResourcePath("StrongNamedWithEcmaKey.dll")) }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "31bf3856ad364e35", new List {new SignInfo("FooCert", "Blah.snk") } } + { "31bf3856ad364e35", new List {new SignInfo(certificate: "FooCert", strongName: "Blah.snk") } } }; // Overriding information @@ -655,15 +668,15 @@ public void SkipStrongNamingForAlreadyStrongNamedBinary() public void DoNotSkipStrongNamingForDelaySignedBinary() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("DelaySigned.dll")) + new ItemToSign(GetResourcePath("DelaySigned.dll")) }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "b03f5f7f11d50a3a", new List {new SignInfo("3PartySHA2", "ArcadeStrongTest") } } + { "b03f5f7f11d50a3a", new List {new SignInfo(certificate: "3PartySHA2", strongName: "ArcadeStrongTest") } } }; // Overriding information @@ -679,15 +692,15 @@ public void DoNotSkipStrongNamingForDelaySignedBinary() public void SkipStrongNamingForCrossGennedBinary() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("Crossgenned.exe")) + new ItemToSign(GetResourcePath("Crossgenned.exe")) }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "b03f5f7f11d50a3a", new List {new SignInfo("3PartySHA2", "ArcadeStrongTest") } } + { "b03f5f7f11d50a3a", new List {new SignInfo(certificate: "3PartySHA2", strongName: "ArcadeStrongTest") } } }; // Overriding information @@ -703,15 +716,15 @@ public void SkipStrongNamingForCrossGennedBinary() public void SkipStrongNamingBinaryButDontSkipAuthenticode() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("OpenSigned.dll")) + new ItemToSign(GetResourcePath("OpenSigned.dll")) }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "cc7b13ffcd2ddd51", new List {new SignInfo("3PartySHA2", "ArcadeStrongTest") } } + { "cc7b13ffcd2ddd51", new List {new SignInfo(certificate: "3PartySHA2", strongName: "ArcadeStrongTest") } } }; // Overriding information @@ -731,12 +744,9 @@ public void OnlyAuthenticodeSignByPKT() var certificateToTest = "3PartySHA2"; // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath(fileToTest), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }) + new ItemToSign(GetResourcePath(fileToTest), "123") }; // Default signing information @@ -767,15 +777,15 @@ public void OnlyAuthenticodeSignByPKT() public void OnlyContainerAndOverridingByPKT() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath(GetResourcePath("ContainerOne.1.0.0.nupkg"))) + new ItemToSign(GetResourcePath(GetResourcePath("ContainerOne.1.0.0.nupkg"))) }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List { new SignInfo("3PartySHA2", "ArcadeStrongTest") } } + { "581d91ccdfc4ea9c", new List { new SignInfo(certificate: "3PartySHA2", strongName: "ArcadeStrongTest") } } }; // Overriding information @@ -809,18 +819,15 @@ public void OnlyContainerAndOverridingByPKT() public void OnlyContainerAndOverridingByFileName() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("ContainerOne.1.0.0.nupkg"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }) + new ItemToSign(GetResourcePath("ContainerOne.1.0.0.nupkg"), "123") }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List { new SignInfo("ArcadeCertTest", "ArcadeStrongTest", collisionPriorityId: "123") } } + { "581d91ccdfc4ea9c", new List { new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest", collisionPriorityId: "123") } } }; // Overriding information @@ -852,9 +859,9 @@ public void OnlyContainerAndOverridingByFileName() public void EmptyPKT() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("EmptyPKT.dll")) + new ItemToSign(GetResourcePath("EmptyPKT.dll")) }; // Default signing information @@ -879,23 +886,17 @@ public void EmptyPKT() public void CrossGenerated() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("CoreLibCrossARM.dll"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(GetResourcePath("AspNetCoreCrossLib.dll"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }) + new ItemToSign(GetResourcePath("CoreLibCrossARM.dll"), "123"), + new ItemToSign(GetResourcePath("AspNetCoreCrossLib.dll"), "123") }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "7cec85d7bea7798e", new List{ new SignInfo("ArcadeCertTest", "ArcadeStrongTest", "123") } }, - { "adb9793829ddae60", new List{ new SignInfo("Microsoft400", "AspNetCore", "123") } } // lgtm [cs/common-default-passwords] Safe, these are certificate names + { "7cec85d7bea7798e", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest", collisionPriorityId: "123") } }, + { "adb9793829ddae60", new List{ new SignInfo(certificate: "Microsoft400", strongName: "AspNetCore", collisionPriorityId: "123") } } // lgtm [cs/common-default-passwords] Safe, these are certificate names }; // Overriding information @@ -925,12 +926,9 @@ public void CrossGenerated() public void DefaultCertificateForAssemblyWithoutStrongName() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("EmptyPKT.dll"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }) + new ItemToSign(GetResourcePath("EmptyPKT.dll"), "123") }; var strongNameSignInfo = new Dictionary>() @@ -950,12 +948,9 @@ public void DefaultCertificateForAssemblyWithoutStrongName() public void CustomTargetFrameworkAttribute() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("CustomTargetFrameworkAttribute.dll"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }) + new ItemToSign(GetResourcePath("CustomTargetFrameworkAttribute.dll"), "123") }; var strongNameSignInfo = new Dictionary>() @@ -978,9 +973,9 @@ public void CustomTargetFrameworkAttribute() public void ThirdPartyLibraryMicrosoftCertificate() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("EmptyPKT.dll")) + new ItemToSign(GetResourcePath("EmptyPKT.dll")) }; var strongNameSignInfo = new Dictionary>() { }; @@ -1001,16 +996,10 @@ public void ThirdPartyLibraryMicrosoftCertificate() public void DoubleNestedContainer() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("PackageWithWix.nupkg"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(GetResourcePath("MsiBootstrapper.exe.wixpack.zip"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }) + new ItemToSign(GetResourcePath("PackageWithWix.nupkg"), "123"), + new ItemToSign(GetResourcePath("MsiBootstrapper.exe.wixpack.zip"), "123") }; // Default signing information @@ -1058,18 +1047,15 @@ public void DoubleNestedContainer() public void NestedContainer() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("NestedContainer.1.0.0.nupkg"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }) + new ItemToSign(GetResourcePath("NestedContainer.1.0.0.nupkg"), "123") }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("3PartySHA2", "ArcadeStrongTest", "123") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "3PartySHA2", strongName: "ArcadeStrongTest", collisionPriorityId: "123") } } }; // Overriding information @@ -1139,18 +1125,15 @@ public void NestedContainer() public void NestedContainerWithCollisions() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("NestedContainer.1.0.0.nupkg"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }) + new ItemToSign(GetResourcePath("NestedContainer.1.0.0.nupkg"), "123") }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("3PartySHA2", "ArcadeStrongTest", "123") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "3PartySHA2", strongName: "ArcadeStrongTest", collisionPriorityId: "123") } } }; // Overriding information. Since ContainerOne.dll collides with ContainerTwo.dll already in the hash mapping @@ -1225,15 +1208,15 @@ public void NestedContainerWithCollisions() public void SignZipFile() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("test.zip")) + new ItemToSign(GetResourcePath("test.zip")) }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("ArcadeCertTest", "ArcadeStrongTest") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest") } } }; // Overriding information @@ -1273,19 +1256,288 @@ public void SignZipFile() }); } + /// + /// Verifies that signing of pkgs can be done on Windows, even though + /// we will not unpack or repack them. + /// + [WindowsOnlyFact] + public void SignJustPkgWithoutUnpack() + { + // List of files to be considered for signing + var itemsToSign = new List() + { + new ItemToSign(GetResourcePath("test.pkg")) + }; + + // Default signing information + var strongNameSignInfo = new Dictionary>() + { + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest") } } + }; + + // Overriding information + var fileSignInfo = new Dictionary(); + + ValidateFileSignInfos(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] + { + "File 'test.pkg' Certificate='MacDeveloperHarden'", + }); + + // OSX files need to be zipped first before being signed + // This is why the .pkgs are listed as .zip files below + ValidateGeneratedProject(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] + { + $@" + + MacDeveloperHarden + ", + }); + } + + [MacOSOnlyFact] + public void UnpackAndSignPkg() + { + // List of files to be considered for signing + var itemsToSign = new List() + { + new ItemToSign(GetResourcePath("test.pkg")) + }; + + // Default signing information + var strongNameSignInfo = new Dictionary>() + { + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest") } } + }; + + // Overriding information + var fileSignInfo = new Dictionary(); + + ValidateFileSignInfos(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] + { + "File 'NativeLibrary.dll' Certificate='Microsoft400'", + "File 'SOS.NETCore.dll' TargetFramework='.NETCoreApp,Version=v1.0' Certificate='Microsoft400'", + "File 'Nested.NativeLibrary.dll' Certificate='Microsoft400'", + "File 'Nested.SOS.NETCore.dll' TargetFramework='.NETCoreApp,Version=v1.0' Certificate='Microsoft400'", + "File 'NestedPkg.pkg' Certificate='MacDeveloperHarden'", + "File 'test.pkg' Certificate='MacDeveloperHarden'", + }); + + // OSX files need to be zipped first before being signed + // This is why the .pkgs are listed as .zip files below + ValidateGeneratedProject(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] + { + $@" + + Microsoft400 + + + Microsoft400 + + + Microsoft400 + + + Microsoft400 + + ", + $@" + + MacDeveloperHarden + ", + $@" + + MacDeveloperHarden + ", + }); + } + + [MacOSOnlyFact] + public void SignAndNotarizePkgFile() + { + // List of files to be considered for signing + var itemsToSign = new List() + { + new ItemToSign(GetResourcePath("test.pkg")) + }; + + // Default signing information + var strongNameSignInfo = new Dictionary>() + { + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest") } } + }; + + // Set up the cert to allow for signing and notarization. + var additionalCertificateInfo = new Dictionary>() + { + { "MacDeveloperHardenWithNotarization", + new List() { + new AdditionalCertificateInformation() { MacNotarizationAppName = "dotnet", MacSigningOperation = "MacDeveloperHarden" } + } + } + }; + + // Overriding information + var fileSignInfo = new Dictionary() + { + { new ExplicitCertificateKey("test.pkg"), "MacDeveloperHardenWithNotarization" } + }; + + ValidateFileSignInfos(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] + { + "File 'NativeLibrary.dll' Certificate='Microsoft400'", + "File 'SOS.NETCore.dll' TargetFramework='.NETCoreApp,Version=v1.0' Certificate='Microsoft400'", + "File 'Nested.NativeLibrary.dll' Certificate='Microsoft400'", + "File 'Nested.SOS.NETCore.dll' TargetFramework='.NETCoreApp,Version=v1.0' Certificate='Microsoft400'", + "File 'NestedPkg.pkg' Certificate='MacDeveloperHarden'", + "File 'test.pkg' Certificate='MacDeveloperHarden' NotarizationAppName='com.microsoft.dotnet'", + }, additionalCertificateInfo: additionalCertificateInfo); + + // OSX files need to be zipped first before being signed + // This is why the .pkgs are listed as .zip files below + ValidateGeneratedProject(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] + { + $@" + + Microsoft400 + + + Microsoft400 + + + Microsoft400 + + + Microsoft400 + + ", + $@" + + MacDeveloperHarden + ", + $@" + + MacDeveloperHarden + + ", + $@" + + 8020 + com.microsoft.dotnet + ", + }, additionalCertificateInfo: additionalCertificateInfo); + } + + [MacOSOnlyFact] + public void SignNestedPkgFile() + { + // List of files to be considered for signing + var itemsToSign = new List() + { + new ItemToSign( GetResourcePath("NestedPkg.pkg")) + }; + + // Default signing information + var strongNameSignInfo = new Dictionary>() + { + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest") } } + }; + + // Overriding information + var fileSignInfo = new Dictionary(); + + ValidateFileSignInfos(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] + { + "File 'NativeLibrary.dll' Certificate='Microsoft400'", + "File 'SOS.NETCore.dll' TargetFramework='.NETCoreApp,Version=v1.0' Certificate='Microsoft400'", + "File 'Nested.SOS.NETCore.dll' TargetFramework='.NETCoreApp,Version=v1.0' Certificate='Microsoft400'", + "File 'Nested.NativeLibrary.dll' Certificate='Microsoft400'", + "File 'NestedPkg.pkg' Certificate='MacDeveloperHarden'", + }); + + // OSX files need to be zipped first before being signed + // This is why the .pkgs and .apps are listed as .zip files below + ValidateGeneratedProject(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] + { + $@" + + Microsoft400 + + + Microsoft400 + + + Microsoft400 + + + Microsoft400 + + ", + $@" + + MacDeveloperHarden + " + }); + } + + [MacOSOnlyFact] + public void SignPkgFileWithApp() + { + // List of files to be considered for signing + var itemsToSign = new List() + { + new ItemToSign( GetResourcePath("WithApp.pkg")) + }; + + // Default signing information + var strongNameSignInfo = new Dictionary>() + { + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest") } } + }; + + // Overriding information + var fileSignInfo = new Dictionary(); + + // When .apps are unpacked from .pkgs, they get zipped so they can be signed + ValidateFileSignInfos(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] + { + "File 'libexample.dylib' Certificate='DylibCertificate'", + "File 'test.app' Certificate='MacDeveloperHarden'", + "File 'WithApp.pkg' Certificate='MacDeveloperHarden'", + }); + + ValidateGeneratedProject(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] + { + // This dylib does not go to a zip file because the cert chosen is DylibCertificate. + $@" + + DylibCertificate + + ", + $@" + + MacDeveloperHarden + + ", + $@" + + MacDeveloperHarden + " + }); + } + [Fact] public void SignTarGZipFile() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("test.tgz")) + new ItemToSign(GetResourcePath("test.tgz")) }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("ArcadeCertTest", "ArcadeStrongTest") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest") } } }; // Overriding information @@ -1329,15 +1581,15 @@ public void SignTarGZipFile() public void SymbolsNupkg() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("test.symbols.nupkg")) + new ItemToSign(GetResourcePath("test.symbols.nupkg")) }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("ArcadeCertTest", "ArcadeStrongTest") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest") } } }; // Overriding information @@ -1381,15 +1633,15 @@ public void SymbolsNupkg() public void SignedSymbolsNupkg() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("test.symbols.nupkg")) + new ItemToSign(GetResourcePath("test.symbols.nupkg")) }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("ArcadeCertTest", "ArcadeStrongTest") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest") } } }; // Overriding information @@ -1435,9 +1687,9 @@ public void SignedSymbolsNupkg() public void CheckDebSigning() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List { - new TaskItem(GetResourcePath("test.deb")) + new ItemToSign(GetResourcePath("test.deb")) }; // Default signing information @@ -1475,13 +1727,41 @@ public void CheckDebSigning() ValidateProducedDebContent(Path.Combine(_tmpDir, "test.deb"), expectedFilesOriginalHashes, signableFiles, expectedControlFileContent); } + [WindowsOnlyFact] + public void CheckRpmSigningOnWindows() + { + // List of files to be considered for signing + var itemsToSign = new List + { + new ItemToSign(GetResourcePath("test.rpm")) + }; + + // Default signing information + var strongNameSignInfo = new Dictionary>(); + + // Overriding information + var fileSignInfo = new Dictionary(); + + ValidateFileSignInfos(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] + { + "File 'test.rpm' Certificate='LinuxSign'" + }); + + ValidateGeneratedProject(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] + { +$@" + LinuxSign +" + }); + } + [LinuxOnlyFact] public void CheckRpmSigning() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List { - new TaskItem(GetResourcePath("test.rpm")) + new ItemToSign(GetResourcePath("test.rpm")) }; // Default signing information @@ -1521,10 +1801,10 @@ public void CheckRpmSigning() public void VerifyDebIntegrity() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List { - new TaskItem(GetResourcePath("SignedDeb.deb")), - new TaskItem(GetResourcePath("IncorrectlySignedDeb.deb")) + new ItemToSign(GetResourcePath("SignedDeb.deb")), + new ItemToSign(GetResourcePath("IncorrectlySignedDeb.deb")) }; // Default signing information @@ -1553,10 +1833,10 @@ public void VerifyDebIntegrity() public void CheckPowershellSigning() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("SignedScript.ps1")), - new TaskItem(GetResourcePath("UnsignedScript.ps1")) + new ItemToSign(GetResourcePath("SignedScript.ps1")), + new ItemToSign(GetResourcePath("UnsignedScript.ps1")) }; // Default signing information @@ -1571,18 +1851,18 @@ public void CheckPowershellSigning() }); } -/* These tests return different results on netcoreapp. ie, we can only truly validate nuget integrity when running on framework. - * NuGet behaves differently on core vs framework - * - https://github.com/NuGet/NuGet.Client/blob/e88a5a03a1b26099f8be225d3ee3a897b2edb1d0/build/common.targets#L18-L25 - */ + /* These tests return different results on netcoreapp. ie, we can only truly validate nuget integrity when running on framework. + * NuGet behaves differently on core vs framework + * - https://github.com/NuGet/NuGet.Client/blob/e88a5a03a1b26099f8be225d3ee3a897b2edb1d0/build/common.targets#L18-L25 + */ #if NETFRAMEWORK [Fact] public void VerifyNupkgIntegrity() { - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("SignedPackage.1.0.0.nupkg")), - new TaskItem(GetResourcePath("IncorrectlySignedPackage.1.0.0.nupkg")) + new ItemToSign(GetResourcePath("SignedPackage.1.0.0.nupkg")), + new ItemToSign(GetResourcePath("IncorrectlySignedPackage.1.0.0.nupkg")) }; ValidateFileSignInfos(itemsToSign, @@ -1596,10 +1876,10 @@ public void VerifyNupkgIntegrity() public void SignNupkgWithUnsignedContents() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("UnsignedContents.nupkg")), - new TaskItem(GetResourcePath("FakeSignedContents.nupkg")) + new ItemToSign(GetResourcePath("UnsignedContents.nupkg")), + new ItemToSign(GetResourcePath("FakeSignedContents.nupkg")) }; // Default signing information @@ -1621,16 +1901,16 @@ public void SignNupkgWithUnsignedContents() public void SignMsiEngine() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("MsiBootstrapper.exe")), - new TaskItem(GetResourcePath("MsiBootstrapper.exe.wixpack.zip")) + new ItemToSign(GetResourcePath("MsiBootstrapper.exe")), + new ItemToSign(GetResourcePath("MsiBootstrapper.exe.wixpack.zip")) }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("ArcadeCertTest", "ArcadeStrongTest") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest") } } }; // Overriding information @@ -1668,22 +1948,16 @@ public void SignMsiEngine() public void MsiWithWixpack() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("MsiSetup.msi"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(GetResourcePath("MsiSetup.msi.wixpack.zip"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }) + new ItemToSign(GetResourcePath("MsiSetup.msi"), "123"), + new ItemToSign(GetResourcePath("MsiSetup.msi.wixpack.zip"), "123") }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("ArcadeCertTest", "ArcadeStrongTest", "123") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest", collisionPriorityId: "123") } } }; // Overriding information @@ -1740,15 +2014,15 @@ public void BadWixToolsetPath() public void MPackFile() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("test.mpack")) + new ItemToSign(GetResourcePath("test.mpack")) }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("3PartySHA2") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "3PartySHA2") } } }; // Overriding information @@ -1774,22 +2048,16 @@ public void MPackFile() public void VsixPackage_DuplicateVsixAfter() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("test.vsix"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(GetResourcePath("PackageWithRelationships.vsix"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }) + new ItemToSign(GetResourcePath("test.vsix"), "123"), + new ItemToSign(GetResourcePath("PackageWithRelationships.vsix"), "123") }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("3PartySHA2", "ArcadeStrongTest", "123") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "3PartySHA2", strongName: "ArcadeStrongTest", collisionPriorityId: "123") } } }; // Overriding information @@ -1841,22 +2109,16 @@ public void VsixPackage_DuplicateVsixAfter() public void VsixPackage_WithSpaces() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("TestSpaces.vsix"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(GetResourcePath("PackageWithRelationships.vsix"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }) + new ItemToSign(GetResourcePath("TestSpaces.vsix"), "123"), + new ItemToSign(GetResourcePath("PackageWithRelationships.vsix"), "123") }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("3PartySHA2", "ArcadeStrongTest", "123") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "3PartySHA2", strongName: "ArcadeStrongTest", collisionPriorityId: "123") } } }; // Overriding information @@ -1908,16 +2170,16 @@ public void VsixPackage_WithSpaces() public void VsixPackage_DuplicateVsixBefore() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("PackageWithRelationships.vsix")), - new TaskItem(GetResourcePath("test.vsix")) + new ItemToSign(GetResourcePath("PackageWithRelationships.vsix")), + new ItemToSign(GetResourcePath("test.vsix")) }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("3PartySHA2", "ArcadeStrongTest") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "3PartySHA2", strongName: "ArcadeStrongTest") } } }; // Overriding information @@ -1965,26 +2227,17 @@ public void VsixPackage_DuplicateVsixBefore() public void VsixPackage_DuplicateVsixBeforeAndAfter() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("PackageWithRelationships.vsix", relativePath: "A"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(GetResourcePath("test.vsix"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(GetResourcePath("PackageWithRelationships.vsix", relativePath: "B"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }) + new ItemToSign(GetResourcePath("PackageWithRelationships.vsix", relativePath: "A"), "123"), + new ItemToSign(GetResourcePath("test.vsix"), "123"), + new ItemToSign(GetResourcePath("PackageWithRelationships.vsix", relativePath: "B"), "123") }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("3PartySHA2", "ArcadeStrongTest", "123") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "3PartySHA2", strongName: "ArcadeStrongTest", collisionPriorityId: "123") } } }; // Overriding information @@ -2036,15 +2289,15 @@ public void VsixPackage_DuplicateVsixBeforeAndAfter() public void VsixPackageWithRelationships() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("PackageWithRelationships.vsix")) + new ItemToSign(GetResourcePath("PackageWithRelationships.vsix")) }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("3PartySHA2", "ArcadeStrongTest") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "3PartySHA2", strongName: "ArcadeStrongTest") } } }; // Overriding information @@ -2075,9 +2328,9 @@ public void VsixPackageWithRelationships() public void ZeroLengthFilesShouldNotBeSigned() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("ZeroLengthPythonFile.py")) + new ItemToSign(GetResourcePath("ZeroLengthPythonFile.py")) }; // Default signing information var strongNameSignInfo = new Dictionary>(); @@ -2093,36 +2346,15 @@ public void ZeroLengthFilesShouldNotBeSigned() public void CheckFileExtensionSignInfo() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(CreateTestResource("dynalib.dylib"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(CreateTestResource("javascript.js"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(CreateTestResource("javatest.jar"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(CreateTestResource("power.ps1"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(CreateTestResource("powerc.psc1"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(CreateTestResource("powerd.psd1"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(CreateTestResource("powerm.psm1"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), + new ItemToSign(CreateTestResource("dynalib.dylib"), "123"), + new ItemToSign(CreateTestResource("javascript.js"), "123"), + new ItemToSign(CreateTestResource("javatest.jar"), "123"), + new ItemToSign(CreateTestResource("power.ps1"), "123"), + new ItemToSign(CreateTestResource("powerc.psc1"), "123"), + new ItemToSign(CreateTestResource("powerd.psd1"), "123"), + new ItemToSign(CreateTestResource("powerm.psm1"), "123"), }; // Default signing information @@ -2210,16 +2442,10 @@ public void ValidateParseFileExtensionEntriesForTarGzExtensionPasses() public void FilesAreUniqueByName() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("SameFiles1.zip"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(GetResourcePath("SameFiles2.zip"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }) + new ItemToSign(GetResourcePath("SameFiles1.zip"), "123"), + new ItemToSign(GetResourcePath("SameFiles2.zip"), "123"), }; ValidateFileSignInfos(itemsToSign, new Dictionary>(), new Dictionary(), s_fileExtensionSignInfoWithCollisionId, new[] @@ -2293,7 +2519,14 @@ public void ValidateSignToolTaskParsing() { new TaskItem("DualSignCertificate", new Dictionary { - { "DualSigningAllowed", "true" } + { "DualSigningAllowed", "true" }, + { "CollisionPriorityId", "123" } + }), + new TaskItem("MacDeveloperHardenWithNotarization", new Dictionary + { + { "MacCertificate", "MacDeveloperHarden" }, + { "MacNotarizationAppName", "com.microsoft.dotnet" }, + { "CollisionPriorityId", "123" } }) }; @@ -2313,6 +2546,7 @@ public void ValidateSignToolTaskParsing() DoStrongNameCheck = false, SNBinaryPath = null, TarToolPath = s_tarToolPath, + PkgToolPath = s_pkgToolPath, }; task.Execute().Should().BeTrue(); @@ -2354,46 +2588,73 @@ private bool runTask(ITaskItem[] itemsToSign = null, ITaskItem[] strongNameSignI public void ValidateAppendingCertificate() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("SignedLibrary.dll")), + new ItemToSign(GetResourcePath("SignedLibrary.dll")), }; - var dualCertificates = new ITaskItem[] + const string dualCertName = "DualCertificateName"; + var additionalCertInfo = new Dictionary>() { - new TaskItem("DualCertificateName"), + {dualCertName, new List(){new AdditionalCertificateInformation() { DualSigningAllowed = true } } }, }; var strongNameSignInfo = new Dictionary>() { - { "31bf3856ad364e35", new List{ new SignInfo(dualCertificates.First().ItemSpec, null) } } + { "31bf3856ad364e35", new List{ new SignInfo(certificate: dualCertName, strongName: null) } } }; var fileSignInfo = new Dictionary(); ValidateFileSignInfos(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] { - $"File 'SignedLibrary.dll' TargetFramework='.NETCoreApp,Version=v2.0' Certificate='{dualCertificates.First()}'", + $"File 'SignedLibrary.dll' TargetFramework='.NETCoreApp,Version=v2.0' Certificate='{dualCertName}'", }, - dualCertificates: dualCertificates); + additionalCertificateInfo: additionalCertInfo); } [Fact] - public void PackageWithZipFile() + public void ValidateCertNotAppendedWithNonMatchingCollisionId() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() + { + new ItemToSign(GetResourcePath("SignedLibrary.dll")), + }; + + const string dualCertName = "DualCertificateName"; + var additionalCertInfo = new Dictionary>() { - new TaskItem( GetResourcePath("PackageWithZip.nupkg"), new Dictionary + { dualCertName, new List(){new AdditionalCertificateInformation() { - { SignToolConstants.CollisionPriorityId, "123" } - }) + DualSigningAllowed = true, + CollisionPriorityId = "123" + } } }, + }; + + var strongNameSignInfo = new Dictionary>() + { + { "31bf3856ad364e35", new List{ new SignInfo(certificate: dualCertName, strongName: null) } } + }; + + var fileSignInfo = new Dictionary(); + + ValidateFileSignInfos(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new string[] { }, additionalCertificateInfo: additionalCertInfo); + } + + [Fact] + public void PackageWithZipFile() + { + // List of files to be considered for signing + var itemsToSign = new List() + { + new ItemToSign( GetResourcePath("PackageWithZip.nupkg"), "123") }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("ArcadeCertTest", "ArcadeStrongTest", "123") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest", collisionPriorityId: "123") } } }; // Overriding information @@ -2417,15 +2678,15 @@ public void PackageWithZipFile() public void NestedZipFile() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem( GetResourcePath("NestedZip.zip")) + new ItemToSign( GetResourcePath("NestedZip.zip")) }; // Default signing information var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List{ new SignInfo("ArcadeCertTest", "ArcadeStrongTest") } } + { "581d91ccdfc4ea9c", new List{ new SignInfo(certificate: "ArcadeCertTest", strongName: "ArcadeStrongTest") } } }; // Overriding information @@ -2452,59 +2713,32 @@ public void NestedZipFile() public void SpecificFileSignInfos() { // List of files to be considered for signing - var itemsToSign = new ITaskItem[] - { - new TaskItem(CreateTestResource("test.js"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(CreateTestResource("test.jar"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(CreateTestResource("test.ps1"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(CreateTestResource("test.psd1"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(CreateTestResource("test.psm1"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(CreateTestResource("test.psc1"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(CreateTestResource("test.dylib"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(GetResourcePath("EmptyPKT.dll"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(GetResourcePath("test.vsix"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), - new TaskItem(GetResourcePath("Simple.nupkg"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), + var itemsToSign = new List() + { + new ItemToSign(CreateTestResource("test.js"), "123"), + new ItemToSign(CreateTestResource("test.jar"), "123"), + new ItemToSign(CreateTestResource("test.ps1"), "123"), + new ItemToSign(CreateTestResource("test.psd1"), "123"), + new ItemToSign(CreateTestResource("test.psm1"), "123"), + new ItemToSign(CreateTestResource("test.psc1"), "123"), + new ItemToSign(CreateTestResource("test.dylib"), "123"), + new ItemToSign(GetResourcePath("EmptyPKT.dll"), "123"), + new ItemToSign(GetResourcePath("test.vsix"), "123"), + new ItemToSign(GetResourcePath("Simple.nupkg"), "123"), // This symbols nupkg has the same hash as Simple.nupkg. // It should still get signed with a different signature. - new TaskItem(GetResourcePath("Simple.symbols.nupkg"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }), + new ItemToSign(GetResourcePath("Simple.symbols.nupkg"), "123"), + // A few extra interesting cases. This has no file extension + new ItemToSign(GetResourcePath("filewithoutextension"), "123"), + // This will be marked as not having any cert. + new ItemToSign(GetResourcePath("SPCNoPKT.dll"), "123"), + // This will be marked to have hardening and notarization + new ItemToSign(GetResourcePath("Simple.exe"), "1234") }; var strongNameSignInfo = new Dictionary>() { - { "581d91ccdfc4ea9c", new List {new SignInfo("ArcadeCertTest", "StrongNameValue", "123") } }, + { "581d91ccdfc4ea9c", new List {new SignInfo(certificate: "ArcadeCertTest", strongName: "StrongNameValue", collisionPriorityId: "123") } }, }; // Overriding information @@ -2526,6 +2760,19 @@ public void SpecificFileSignInfos() { new ExplicitCertificateKey("ProjectOne.dll", "581d91ccdfc4ea9c", ".NETFramework,Version=v4.6.1", "123"), "DLLCertificate3" }, { new ExplicitCertificateKey("ProjectOne.dll", "581d91ccdfc4ea9c", ".NETStandard,Version=v2.0", "123"), "DLLCertificate4" }, { new ExplicitCertificateKey("ProjectOne.dll", "581d91ccdfc4ea9c", ".NETCoreApp,Version=v2.0", "123"), "DLLCertificate5" }, + { new ExplicitCertificateKey("filewithoutextension", collisionPriorityId: "123"), "MacDeveloperHarden" }, + { new ExplicitCertificateKey("SPCNoPKT.dll", collisionPriorityId: "123"), "None" }, + { new ExplicitCertificateKey("Simple.exe", collisionPriorityId: "1234"), "MacDeveloperHardenWithNotarization" }, + }; + + // Set up the cert to allow for signing and notarization. + var certificatesSignInfo = new Dictionary>() + { + { "MacDeveloperHardenWithNotarization", + new List() { + new AdditionalCertificateInformation() { MacNotarizationAppName = "dotnet", MacSigningOperation = "MacDeveloperHarden" } + } + } }; ValidateFileSignInfos(itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, new[] @@ -2546,14 +2793,18 @@ public void SpecificFileSignInfos() "File 'Simple.dll' TargetFramework='.NETCoreApp,Version=v2.1' Certificate='DLLCertificate2'", "File 'Simple.nupkg' Certificate='NUPKGCertificate'", "File 'Simple.symbols.nupkg' Certificate='NUPKGCertificate2'", + "File 'filewithoutextension' Certificate='MacDeveloperHarden'", + "File 'Simple.exe' TargetFramework='.NETCoreApp,Version=v2.1' Certificate='MacDeveloperHarden' NotarizationAppName='dotnet'", }, + additionalCertificateInfo: certificatesSignInfo, expectedWarnings: new[] { $@"SIGN004: Signing 3rd party library '{Path.Combine(_tmpDir, "EmptyPKT.dll")}' with Microsoft certificate 'DLLCertificate'. The library is considered 3rd party library due to its copyright: ''.", $@"SIGN004: Signing 3rd party library '{Path.Combine(_tmpDir, "ContainerSigning", "9", "lib/net461/ProjectOne.dll")}' with Microsoft certificate 'DLLCertificate3'. The library is considered 3rd party library due to its copyright: ''.", $@"SIGN004: Signing 3rd party library '{Path.Combine(_tmpDir, "ContainerSigning", "10", "lib/netstandard2.0/ProjectOne.dll")}' with Microsoft certificate 'DLLCertificate4'. The library is considered 3rd party library due to its copyright: ''.", $@"SIGN004: Signing 3rd party library '{Path.Combine(_tmpDir, "ContainerSigning", "16", "Contents/Common7/IDE/PrivateAssemblies/ProjectOne.dll")}' with Microsoft certificate 'DLLCertificate5'. The library is considered 3rd party library due to its copyright: ''.", - $@"SIGN004: Signing 3rd party library '{Path.Combine(_tmpDir, "ContainerSigning", "23", "Simple.dll")}' with Microsoft certificate 'DLLCertificate2'. The library is considered 3rd party library due to its copyright: ''." + $@"SIGN004: Signing 3rd party library '{Path.Combine(_tmpDir, "ContainerSigning", "23", "Simple.dll")}' with Microsoft certificate 'DLLCertificate2'. The library is considered 3rd party library due to its copyright: ''.", + $@"SIGN004: Signing 3rd party library '{Path.Combine(_tmpDir, "Simple.exe")}' with Microsoft certificate 'MacDeveloperHarden'. The library is considered 3rd party library due to its copyright: ''." }); } @@ -2576,9 +2827,9 @@ public void MissingCertificateName(string extension) GetResourcePath(resourcePath) : CreateTestResource("test" + extension); - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(inputFilePath) + new ItemToSign(inputFilePath) }; new Configuration(_tmpDir, @@ -2586,8 +2837,9 @@ public void MissingCertificateName(string extension) new Dictionary>(), new Dictionary(), new Dictionary>(), - new ITaskItem[0], + new(), tarToolPath: s_tarToolPath, + pkgToolPath: s_pkgToolPath, snPath: s_snPath, task.Log) .GenerateListOfFiles(); @@ -2615,9 +2867,9 @@ public void MissingCertificateNameButExtensionIsIgnored(string extension) GetResourcePath(value.ResourcePath) : CreateTestResource("test" + extension); - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(inputFilePath) + new ItemToSign(inputFilePath) }; var extensionSignInfo = new Dictionary>() @@ -2635,8 +2887,9 @@ public void MissingCertificateNameButExtensionIsIgnored(string extension) new Dictionary>(), new Dictionary(), extensionSignInfo, - new ITaskItem[0], + new(), tarToolPath: s_tarToolPath, + pkgToolPath: s_pkgToolPath, snPath: s_snPath, task.Log) .GenerateListOfFiles(); @@ -2647,12 +2900,9 @@ public void MissingCertificateNameButExtensionIsIgnored(string extension) [Fact] public void CrossGeneratedLibraryWithoutPKT() { - var itemsToSign = new ITaskItem[] + var itemsToSign = new List() { - new TaskItem(GetResourcePath("SPCNoPKT.dll"), new Dictionary - { - { SignToolConstants.CollisionPriorityId, "123" } - }) + new ItemToSign(GetResourcePath("SPCNoPKT.dll"), "123") }; ValidateFileSignInfos( diff --git a/src/Microsoft.DotNet.SignTool/src/AdditionalCertificateInformation.cs b/src/Microsoft.DotNet.SignTool/src/AdditionalCertificateInformation.cs new file mode 100644 index 00000000000..e7e2ed2f4a2 --- /dev/null +++ b/src/Microsoft.DotNet.SignTool/src/AdditionalCertificateInformation.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.DotNet.SignTool +{ + public class AdditionalCertificateInformation + { + /// + /// If true, the certificate name can be used to sign already signed binaries. + /// + public bool DualSigningAllowed { get; set; } + /// + /// If the certificate name represents a sign+notarize operation, this is the name of the sign operation. + /// + public string MacSigningOperation { get; set; } + /// + /// If the certificate name represents a sign+notarize operation, this is the name of the notarize operation. + /// + public string MacNotarizationAppName { get; set; } + public string CollisionPriorityId { get; set; } + } +} diff --git a/src/Microsoft.DotNet.SignTool/src/BatchSignUtil.cs b/src/Microsoft.DotNet.SignTool/src/BatchSignUtil.cs index bab9051aee8..20efc0422a4 100644 --- a/src/Microsoft.DotNet.SignTool/src/BatchSignUtil.cs +++ b/src/Microsoft.DotNet.SignTool/src/BatchSignUtil.cs @@ -27,8 +27,6 @@ internal sealed class BatchSignUtil private readonly int _repackParallelism; private readonly long _maximumParallelFileSizeInBytes; - internal bool SkipZipContainerSignatureMarkerCheck { get; set; } - internal BatchSignUtil(IBuildEngine buildEngine, TaskLoggingHelper log, SignTool signTool, @@ -126,17 +124,20 @@ private bool SignFiles() bool signGroup(IEnumerable files, out int signedCount) { var filesToSign = files.Where(fileInfo => fileInfo.SignInfo.ShouldSign).ToArray(); + var filesToNotarize = files.Where(fileInfo => fileInfo.SignInfo.ShouldNotarize).ToArray(); signedCount = filesToSign.Length; if (filesToSign.Length == 0) return true; - _log.LogMessage(MessageImportance.High, $"Round {round}: Signing {filesToSign.Length} files."); + _log.LogMessage(MessageImportance.High, $"Round {round}: Signing {filesToSign.Length} files" + + $"{(filesToNotarize.Length > 0? $", Notarizing {filesToNotarize.Length} files" : "")}"); foreach (var file in filesToSign) { string collisionIdInfo = string.Empty; if(_hashToCollisionIdMap != null) { - if(_hashToCollisionIdMap.TryGetValue(file.FileContentKey, out string collisionPriorityId)) + if(_hashToCollisionIdMap.TryGetValue(file.FileContentKey, out string collisionPriorityId) && + !string.IsNullOrEmpty(collisionPriorityId)) { collisionIdInfo = $"Collision Id='{collisionPriorityId}'"; } @@ -153,7 +154,7 @@ bool signGroup(IEnumerable files, out int signedCount) bool signEngines(IEnumerable files, out int signedCount) { var enginesToSign = files.Where(fileInfo => fileInfo.SignInfo.ShouldSign && - fileInfo.IsWixContainer() && + fileInfo.IsUnpackableWixContainer() && Path.GetExtension(fileInfo.FullPath) == ".exe").ToArray(); signedCount = enginesToSign.Length; if (enginesToSign.Length == 0) @@ -278,15 +279,10 @@ void repackGroup(IEnumerable files, out int repackCount) void repackContainer(FileSignInfo file) { - if (file.IsZipContainer()) + if (file.IsUnpackableContainer()) { _log.LogMessage($"Repacking container: '{file.FileName}'"); - _batchData.ZipDataMap[file.FileContentKey].Repack(_log, _signTool.TempDir, _signTool.WixToolsPath, _signTool.TarToolPath); - } - else if (file.IsWixContainer()) - { - _log.LogMessage($"Packing wix container: '{file.FileName}'"); - _batchData.ZipDataMap[file.FileContentKey].Repack(_log, _signTool.TempDir, _signTool.WixToolsPath, _signTool.TarToolPath); + _batchData.ZipDataMap[file.FileContentKey].Repack(_log, _signTool.TempDir, _signTool.WixToolsPath, _signTool.TarToolPath, _signTool.PkgToolPath); } else { @@ -298,7 +294,7 @@ void repackContainer(FileSignInfo file) // signed, don't need signing, and are repacked. bool isReady(FileSignInfo file) { - if (file.IsContainer()) + if (file.IsUnpackableContainer()) { var zipData = _batchData.ZipDataMap[file.FileContentKey]; return zipData.NestedParts.Values.All(x => (!x.FileSignInfo.SignInfo.ShouldSign || @@ -532,6 +528,30 @@ private void VerifyCertificates(TaskLoggingHelper log) log.LogError($"Nupkg {fileName} cannot be strong name signed."); } } + else if (fileName.IsPkg()) + { + if(isInvalidEmptyCertificate) + { + log.LogError($"Pkg {fileName} should have a certificate name."); + } + + if (fileName.SignInfo.StrongName != null) + { + log.LogError($"Pkg {fileName} cannot be strong name signed."); + } + } + else if (fileName.IsAppBundle()) + { + if (isInvalidEmptyCertificate) + { + log.LogError($"AppBundle {fileName} should have a certificate name."); + } + + if (fileName.SignInfo.StrongName != null) + { + log.LogError($"AppBundle {fileName} cannot be strong name signed."); + } + } else if (fileName.IsZip()) { if (fileName.SignInfo.Certificate != null) @@ -559,101 +579,79 @@ private void VerifyCertificates(TaskLoggingHelper log) } } + /// + /// Recursively verify that files are signed properly. + /// + /// + /// private void VerifyAfterSign(TaskLoggingHelper log, FileSignInfo file) { - if (file.IsPEFile()) + // No need to check if the file should not have been signed. + if (file.SignInfo.ShouldSign) { - using (var stream = File.OpenRead(file.FullPath)) + if (file.IsPEFile()) { - if (!_signTool.VerifySignedPEFile(stream)) - { - _log.LogError($"Assembly {file.FullPath} is NOT signed properly"); - } - else + using (var stream = File.OpenRead(file.FullPath)) { - _log.LogMessage(MessageImportance.Low, $"Assembly {file.FullPath} is signed properly"); + var status = _signTool.VerifySignedPEFile(stream); + LogSigningStatus(file, status, "PE file"); } } - } - else if (file.IsDeb()) - { -# if NET472 - _log.LogMessage(MessageImportance.Low, $"Cannot verify deb package {file.FullPath} signature on .NET Framework."); -#else - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + else if (file.IsDeb()) { - _log.LogMessage(MessageImportance.Low, $"Cannot verify deb package {file.FullPath} signature on Windows."); + var status = _signTool.VerifySignedDeb(log, file.FullPath); + LogSigningStatus(file, status, "Debian package"); } - else if (!_signTool.VerifySignedDeb(log, file.FullPath)) + else if (file.IsRpm()) { - _log.LogError($"Deb package {file.FullPath} is not signed properly."); + var status = _signTool.VerifySignedRpm(log, file.FullPath); + LogSigningStatus(file, status, "RPM package"); } - else + else if (file.IsPowerShellScript()) { - _log.LogMessage(MessageImportance.Low, $"Deb package {file.FullPath} is signed properly"); + var status = _signTool.VerifySignedPowerShellFile(file.FullPath); + LogSigningStatus(file, status, "Powershell file"); } -#endif - } - else if (file.IsRpm()) - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + else if (file.IsPkg() || file.IsAppBundle()) { - _log.LogMessage(MessageImportance.Low, $"Skipping signature verification of {file.FullPath} on non-Linux platform."); + var status = _signTool.VerifySignedPkgOrAppBundle(_log, file.FullPath, _signTool.PkgToolPath); + LogSigningStatus(file, status, "Pkg or app"); } - else if (!_signTool.VerifySignedRpm(log, file.FullPath)) + else if (file.IsNupkg()) { - _log.LogError($"Rpm package {file.FullPath} is not signed properly."); - } - else + var status = _signTool.VerifySignedNuGet(file.FullPath); + LogSigningStatus(file, status, "Nuget package"); + } + else if (file.IsVsix()) { - _log.LogMessage(MessageImportance.Low, $"Rpm package {file.FullPath} is signed properly"); + var status = _signTool.VerifySignedVSIX(file.FullPath); + LogSigningStatus(file, status, "VSIX package"); } } - else if (file.IsPowerShellScript()) + + if (file.IsUnpackableContainer()) { - if (!_signTool.VerifySignedPowerShellFile(file.FullPath)) + var zipData = _batchData.ZipDataMap[file.FileContentKey]; + + foreach (var nestedPart in zipData.NestedParts.Values) { - _log.LogError($"Powershell file {file.FullPath} does not have a signature mark."); + VerifyAfterSign(log, nestedPart.FileSignInfo); } } - else if (file.IsZipContainer()) - { - var zipData = _batchData.ZipDataMap[file.FileContentKey]; - bool signedContainer = false; - foreach (var (relativeName, _, _) in ZipData.ReadEntries(file.FullPath, _signTool.TempDir, _signTool.TarToolPath, ignoreContent: true)) + void LogSigningStatus(FileSignInfo file, SigningStatus status, string fileType) + { + if (status == SigningStatus.NotSigned) { - if (!SkipZipContainerSignatureMarkerCheck) - { - if (file.IsNupkg() && _signTool.VerifySignedNugetFileMarker(relativeName)) - { - signedContainer = true; - } - else if (file.IsVsix() && _signTool.VerifySignedVSIXFileMarker(relativeName)) - { - signedContainer = true; - } - } - - var zipPart = zipData.FindNestedPart(relativeName); - if (!zipPart.HasValue) - { - continue; - } - - VerifyAfterSign(_log, zipPart.Value.FileSignInfo); + _log.LogError($"{fileType} {file.FullPath} is not signed properly."); } - - if (!SkipZipContainerSignatureMarkerCheck) + else if (status == SigningStatus.Unknown) { - if ((file.IsNupkg() || file.IsVsix()) && !signedContainer) - { - _log.LogError($"Container {file.FullPath} does not have signature marker."); - } - else - { - _log.LogMessage(MessageImportance.Low, $"Container {file.FullPath} has a signature marker."); - } + _log.LogMessage(MessageImportance.Low, $"Signing status of {file.FullPath} could not be determined."); + } + else + { + _log.LogMessage(MessageImportance.Low, $"{fileType} {file.FullPath} is signed properly"); } } } @@ -668,13 +666,16 @@ private void VerifyStrongNameSigning() continue; } - if (file.IsManaged() && !file.IsCrossgened() && !_signTool.VerifyStrongNameSign(file.FullPath)) + if (file.IsManaged() && !file.IsCrossgened()) { - _log.LogError($"Assembly {file.FullPath} is not strong-name signed correctly."); - } - else - { - _log.LogMessage(MessageImportance.Low, $"Assembly {file.FullPath} strong-name signature is valid."); + if (_signTool.VerifyStrongNameSign(file.FullPath) != SigningStatus.Signed) + { + _log.LogError($"Assembly {file.FullPath} is not strong-name signed correctly."); + } + else + { + _log.LogMessage(MessageImportance.Low, $"Assembly {file.FullPath} strong-name signature is valid."); + } } } } diff --git a/src/Microsoft.DotNet.SignTool/src/Configuration.cs b/src/Microsoft.DotNet.SignTool/src/Configuration.cs index 48f530c4676..7e9da4e0dd5 100644 --- a/src/Microsoft.DotNet.SignTool/src/Configuration.cs +++ b/src/Microsoft.DotNet.SignTool/src/Configuration.cs @@ -1,19 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.IO; -using System.IO.Compression; using System.Linq; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; -using System.Runtime.ConstrainedExecution; using System.Runtime.Versioning; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; namespace Microsoft.DotNet.SignTool { @@ -21,7 +19,7 @@ internal class Configuration { private readonly TaskLoggingHelper _log; - private readonly ITaskItem[] _itemsToSign; + private readonly List _itemsToSign; /// /// This store content information for container files. @@ -77,7 +75,7 @@ internal class Configuration /// This is a list of the friendly name of certificates that can be used to /// sign already signed binaries. /// - private readonly ITaskItem[] _dualCertificates; + private readonly Dictionary> _additionalCertificateInformation; /// /// Use the content hash in the path of the extracted file paths. @@ -101,16 +99,18 @@ internal class Configuration private string _tarToolPath; + private string _pkgToolPath; private string _snPath; public Configuration( string tempDir, - ITaskItem[] itemsToSign, + List itemsToSign, Dictionary> strongNameInfo, Dictionary fileSignInfo, Dictionary> extensionSignInfo, - ITaskItem[] dualCertificates, + Dictionary> additionalCertificateInformation, string tarToolPath, + string pkgToolPath, string snPath, TaskLoggingHelper log, bool useHashInExtractionPath = false, @@ -133,13 +133,14 @@ public Configuration( _zipDataMap = new Dictionary(); _filesByContentKey = new Dictionary(); _itemsToSign = itemsToSign; - _dualCertificates = dualCertificates == null ? new ITaskItem[0] : dualCertificates; + _additionalCertificateInformation = additionalCertificateInformation == null ? new() : additionalCertificateInformation; _whichPackagesTheFileIsIn = new Dictionary>(); _errors = new Dictionary>(); - _wixPacks = _itemsToSign.Where(w => WixPackInfo.IsWixPack(w.ItemSpec))?.Select(s => new WixPackInfo(s.ItemSpec)).ToList(); + _wixPacks = _itemsToSign.Where(w => WixPackInfo.IsWixPack(w.FullPath))?.Select(s => new WixPackInfo(s.FullPath)).ToList(); _hashToCollisionIdMap = new Dictionary(); _telemetry = telemetry; _tarToolPath = tarToolPath; + _pkgToolPath = pkgToolPath; _snPath = snPath; } @@ -148,22 +149,20 @@ internal BatchSignInput GenerateListOfFiles() Stopwatch gatherInfoTime = Stopwatch.StartNew(); foreach (var itemToSign in _itemsToSign) { - string fullPath = itemToSign.ItemSpec; - string collisionPriorityId = itemToSign.GetMetadata(SignToolConstants.CollisionPriorityId); - var contentHash = ContentUtil.GetContentHash(fullPath); - var fileUniqueKey = new SignedFileContentKey(contentHash, Path.GetFileName(fullPath)); + var contentHash = ContentUtil.GetContentHash(itemToSign.FullPath); + var fileUniqueKey = new SignedFileContentKey(contentHash, Path.GetFileName(itemToSign.FullPath)); if (!_whichPackagesTheFileIsIn.TryGetValue(fileUniqueKey, out var packages)) { packages = new HashSet(); } - packages.Add(fullPath); + packages.Add(itemToSign.FullPath); _whichPackagesTheFileIsIn[fileUniqueKey] = packages; - PathWithHash pathWithHash = new PathWithHash(fullPath, contentHash); - TrackFile(pathWithHash, null, collisionPriorityId); + PathWithHash pathWithHash = new PathWithHash(itemToSign.FullPath, contentHash); + TrackFile(pathWithHash, null, itemToSign.CollisionPriorityId); } gatherInfoTime.Stop(); if (_telemetry != null) @@ -221,21 +220,29 @@ private FileSignInfo TrackFile(PathWithHash file, PathWithHash parentContainer, return fileSignInfo; } - if (fileSignInfo.IsContainer()) + if (fileSignInfo.IsUnpackableContainer()) { - if (fileSignInfo.IsZipContainer()) + if (fileSignInfo.IsUnpackableWixContainer()) { - if (TryBuildZipData(fileSignInfo, out var zipData)) + _log.LogMessage($"Trying to gather data for wix container {fileSignInfo.FullPath}"); + if (TryBuildWixData(fileSignInfo, out var msiData)) { - _zipDataMap[fileSignInfo.FileContentKey] = zipData; + _zipDataMap[fileSignInfo.FileContentKey] = msiData; + } + else + { + _log.LogError($"Failed to build wix data for {fileSignInfo.FullPath}"); } } - else if (fileSignInfo.IsWixContainer()) + else { - _log.LogMessage($"Trying to gather data for wix container {fileSignInfo.FullPath}"); - if (TryBuildWixData(fileSignInfo, out var msiData)) + if (TryBuildZipData(fileSignInfo, out var zipData)) { - _zipDataMap[fileSignInfo.FileContentKey] = msiData; + _zipDataMap[fileSignInfo.FileContentKey] = zipData; + } + else + { + _log.LogError($"Failed to build zip data for {fileSignInfo.FullPath}"); } } } @@ -243,7 +250,7 @@ private FileSignInfo TrackFile(PathWithHash file, PathWithHash parentContainer, _filesByContentKey.Add(fileSignInfo.FileContentKey, fileSignInfo); bool hasSignableParts = false; - if (fileSignInfo.IsContainer()) + if (fileSignInfo.IsUnpackableContainer()) { // Only sign containers if the file itself is unsigned, or // an item in the container is unsigned. @@ -362,27 +369,8 @@ private FileSignInfo ExtractSignInfo( if (FileSignInfo.IsPEFile(file.FullPath)) { - isAlreadyAuthenticodeSigned = ContentUtil.IsAuthenticodeSigned(file.FullPath); - isAlreadyStrongNamed = StrongName.IsSigned(file.FullPath, snPath:_snPath, log: _log); - - - if (!isAlreadyAuthenticodeSigned) - { - _log.LogMessage(MessageImportance.Low, $"PE file {file.FullPath} does not have a signature marker."); - } - else - { - _log.LogMessage(MessageImportance.Low, $"PE file {file.FullPath} has a signature marker."); - } - - if (!isAlreadyStrongNamed) - { - _log.LogMessage(MessageImportance.Low, $"PE file {file.FullPath} does not have a valid strong name signature."); - } - else - { - _log.LogMessage(MessageImportance.Low, $"PE file {file.FullPath} has a valid strong name signature."); - } + isAlreadyAuthenticodeSigned = IsSigned(file, VerifySignatures.IsSignedPE(file.FullPath)); + isAlreadyStrongNamed = IsStrongNameSigned(file); peInfo = GetPEInfo(file.FullPath); @@ -429,65 +417,29 @@ private FileSignInfo ExtractSignInfo( fileSpec = matchedNameTokenFramework ? $" (PublicKeyToken = {peInfo.PublicKeyToken}, Framework = {peInfo.TargetFramework})" : matchedNameToken ? $" (PublicKeyToken = {peInfo.PublicKeyToken})" : string.Empty; } - else if (FileSignInfo.IsPackage(file.FullPath)) + else if (FileSignInfo.IsPkg(file.FullPath) || FileSignInfo.IsAppBundle(file.FullPath)) { - isAlreadyAuthenticodeSigned = VerifySignatures.IsSignedContainer(file.FullPath, _pathToContainerUnpackingDirectory, _tarToolPath); - if(!isAlreadyAuthenticodeSigned) - { - _log.LogMessage(MessageImportance.Low, $"Container {file.FullPath} does not have a signature marker."); - } - else - { - _log.LogMessage(MessageImportance.Low, $"Container {file.FullPath} has a signature marker."); - } + isAlreadyAuthenticodeSigned = IsSigned(file, VerifySignatures.IsSignedPkgOrAppBundle(_log, file.FullPath, _pkgToolPath)); } - else if (FileSignInfo.IsWix(file.FullPath)) + else if (FileSignInfo.IsNupkg(file.FullPath)) { - isAlreadyAuthenticodeSigned = VerifySignatures.IsDigitallySigned(file.FullPath); - if (!isAlreadyAuthenticodeSigned) - { - _log.LogMessage(MessageImportance.Low, $"File {file.FullPath} is not digitally signed."); - } - else - { - _log.LogMessage(MessageImportance.Low, $"File {file.FullPath} is digitally signed."); - } + isAlreadyAuthenticodeSigned = IsSigned(file, VerifySignatures.IsSignedNupkg(file.FullPath)); } - else if(FileSignInfo.IsDeb(file.FullPath)) + else if (FileSignInfo.IsWixInstaller(file.FullPath)) { - isAlreadyAuthenticodeSigned = VerifySignatures.VerifySignedDeb(_log, file.FullPath); - if (!isAlreadyAuthenticodeSigned) - { - _log.LogMessage(MessageImportance.Low, $"File {file.FullPath} is not signed."); - } - else - { - _log.LogMessage(MessageImportance.Low, $"File {file.FullPath} is signed."); - } + isAlreadyAuthenticodeSigned = IsSigned(file, VerifySignatures.IsWixSigned(file.FullPath)); + } + else if (FileSignInfo.IsDeb(file.FullPath)) + { + isAlreadyAuthenticodeSigned = IsSigned(file, VerifySignatures.IsSignedDeb(_log, file.FullPath)); } else if (FileSignInfo.IsRpm(file.FullPath)) { - isAlreadyAuthenticodeSigned = VerifySignatures.VerifySignedRpm(_log, file.FullPath); - if (!isAlreadyAuthenticodeSigned) - { - _log.LogMessage(MessageImportance.Low, $"File {file.FullPath} is not signed."); - } - else - { - _log.LogMessage(MessageImportance.Low, $"File {file.FullPath} is signed."); - } + isAlreadyAuthenticodeSigned = IsSigned(file, VerifySignatures.IsSignedRpm(_log, file.FullPath));; } else if (FileSignInfo.IsPowerShellScript(file.FullPath)) { - isAlreadyAuthenticodeSigned = VerifySignatures.VerifySignedPowerShellFile(file.FullPath); - if (!isAlreadyAuthenticodeSigned) - { - _log.LogMessage(MessageImportance.Low, $"File {file.FullPath} does not have a signature block."); - } - else - { - _log.LogMessage(MessageImportance.Low, $"File {file.FullPath} has a signature block."); - } + isAlreadyAuthenticodeSigned = IsSigned(file, VerifySignatures.IsSignedPowershellFile(file.FullPath)); } // We didn't find any specific information for PE files using PKT + TargetFramework @@ -513,16 +465,36 @@ private FileSignInfo ExtractSignInfo( if (hasSignInfo) { - bool dualCerts = _dualCertificates - .Where(d => d.ItemSpec == signInfo.Certificate && - (d.GetMetadata(SignToolConstants.CollisionPriorityId) == "" || - d.GetMetadata(SignToolConstants.CollisionPriorityId) == _hashToCollisionIdMap[signedFileContentKey])).Any(); + bool dualCertsAllowed = false; + string macSignOperation = null; + string macNotarizationAppName = null; + if (signInfo.Certificate != null && _additionalCertificateInformation.TryGetValue(signInfo.Certificate, out var additionalInfo)) + { + var additionalCertInfo = additionalInfo.FirstOrDefault(a => string.IsNullOrEmpty(a.CollisionPriorityId) || + a.CollisionPriorityId == _hashToCollisionIdMap[signedFileContentKey]); + if (additionalCertInfo != null) + { + dualCertsAllowed = additionalCertInfo.DualSigningAllowed; + macSignOperation = additionalCertInfo.MacSigningOperation; + macNotarizationAppName = additionalCertInfo.MacNotarizationAppName; + } + } - if (isAlreadyAuthenticodeSigned && !dualCerts) + // If the file is already signed and we are not allowed to dual sign, and we are not doing a mac notarization operation, + // then we should not sign the file. + if (isAlreadyAuthenticodeSigned && !dualCertsAllowed && string.IsNullOrEmpty(macNotarizationAppName)) { return new FileSignInfo(file, signInfo.WithIsAlreadySigned(isAlreadyAuthenticodeSigned), wixContentFilePath: wixContentFilePath); } + // If the certificate indicates that the file has a split sign/notarize operation, + // then replace the certificate with the sign certificate and add the notarization operation. + if (!string.IsNullOrEmpty(macNotarizationAppName)) + { + signInfo = signInfo.WithCertificateName(macSignOperation, _hashToCollisionIdMap[signedFileContentKey]); + signInfo = signInfo.WithNotarization(macNotarizationAppName, _hashToCollisionIdMap[signedFileContentKey]); + } + if (signInfo.ShouldSign && peInfo != null) { bool isMicrosoftLibrary = IsMicrosoftLibrary(peInfo.Copyright); @@ -558,7 +530,7 @@ private FileSignInfo ExtractSignInfo( return new FileSignInfo(file, signInfo, (peInfo != null && peInfo.TargetFramework != "") ? peInfo.TargetFramework : null, wixContentFilePath: wixContentFilePath); } - if (SignToolConstants.SignableExtensions.Contains(extension) || SignToolConstants.SignableOSXExtensions.Contains(extension)) + if (SignToolConstants.SignableExtensions.Contains(extension)) { // Extract the relative path inside the package / otherwise just return the full path of the file LogError(SigningToolErrorCode.SIGN002, signedFileContentKey); @@ -569,6 +541,39 @@ private FileSignInfo ExtractSignInfo( } return new FileSignInfo(file, SignInfo.Ignore, wixContentFilePath: wixContentFilePath); + + bool IsSigned(PathWithHash file, SigningStatus signingStatus) + { + switch (signingStatus) + { + case SigningStatus.Signed: + _log.LogMessage(MessageImportance.Low, $"File '{file.FullPath}' is already signed."); + return true; + case SigningStatus.NotSigned: + _log.LogMessage(MessageImportance.Low, $"File '{file.FullPath}' is not signed."); + return false; + case SigningStatus.Unknown: + _log.LogMessage(MessageImportance.Low, $"File '{file.FullPath}' signing status is unknown, treating as unsigned."); + return false; + default: + throw new Exception($"Unexpected signing status {signingStatus}"); + } + } + + bool IsStrongNameSigned(PathWithHash file) + { + bool isAlreadyStrongNamed = StrongName.IsSigned(file.FullPath, snPath: _snPath, log: _log); + if (!isAlreadyStrongNamed) + { + _log.LogMessage(MessageImportance.Low, $"PE file {file.FullPath} does not have a valid strong name signature."); + } + else + { + _log.LogMessage(MessageImportance.Low, $"PE file {file.FullPath} has a valid strong name signature."); + } + + return isAlreadyStrongNamed; + } } private void LogWarning(SigningToolErrorCode code, string message) @@ -727,14 +732,14 @@ private bool TryBuildZipData(FileSignInfo zipFileSignInfo, out ZipData zipData, } else { - Debug.Assert(zipFileSignInfo.IsZipContainer()); + Debug.Assert(zipFileSignInfo.IsUnpackableContainer()); } try { var nestedParts = new Dictionary(); - foreach (var (relativePath, contentStream, contentSize) in ZipData.ReadEntries(archivePath, _pathToContainerUnpackingDirectory, _tarToolPath)) + foreach (var (relativePath, contentStream, contentSize) in ZipData.ReadEntries(archivePath, _pathToContainerUnpackingDirectory, _tarToolPath, _pkgToolPath)) { if (contentStream == null) { @@ -790,7 +795,7 @@ private bool TryBuildZipData(FileSignInfo zipFileSignInfo, out ZipData zipData, } catch (Exception e) { - _log.LogErrorFromException(e); + _log.LogErrorFromException(e, true); zipData = null; return false; } diff --git a/src/Microsoft.DotNet.SignTool/src/ContentUtil.cs b/src/Microsoft.DotNet.SignTool/src/ContentUtil.cs index 3af482cb15f..ae725881541 100644 --- a/src/Microsoft.DotNet.SignTool/src/ContentUtil.cs +++ b/src/Microsoft.DotNet.SignTool/src/ContentUtil.cs @@ -108,25 +108,6 @@ public static bool IsCrossgened(string filePath) } } - public static bool IsAuthenticodeSigned(Stream assemblyStream) - { - using (var peReader = new PEReader(assemblyStream)) - { - var headers = peReader.PEHeaders; - var entry = headers.PEHeader.CertificateTableDirectory; - - return entry.Size > 0; - } - } - - public static bool IsAuthenticodeSigned(string filePath) - { - using (var stream = new FileStream(filePath, FileMode.Open)) - { - return IsAuthenticodeSigned(stream); - } - } - public static string GetPublicKeyToken(string fullPath) { try diff --git a/src/Microsoft.DotNet.SignTool/src/FileSignInfo.cs b/src/Microsoft.DotNet.SignTool/src/FileSignInfo.cs index c7694c1d1a7..62e5c8636f6 100644 --- a/src/Microsoft.DotNet.SignTool/src/FileSignInfo.cs +++ b/src/Microsoft.DotNet.SignTool/src/FileSignInfo.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; namespace Microsoft.DotNet.SignTool { @@ -39,6 +40,14 @@ internal static bool IsMPack(string path) internal static bool IsNupkg(string path) => Path.GetExtension(path).Equals(".nupkg", StringComparison.OrdinalIgnoreCase); + // Note: unpacking, repacking, and notarization can only happen on a Mac. + internal static bool IsPkg(string path) + => Path.GetExtension(path).Equals(".pkg", StringComparison.OrdinalIgnoreCase); + + // Note: unpacking, repacking, and notarization can only happen on a Mac. + internal static bool IsAppBundle(string path) + => Path.GetExtension(path).Equals(".app", StringComparison.OrdinalIgnoreCase); + internal static bool IsSymbolsNupkg(string path) => path.EndsWith(".symbols.nupkg", StringComparison.OrdinalIgnoreCase); @@ -50,7 +59,7 @@ internal static bool IsTarGZip(string path) || (Path.GetExtension(path).Equals(".gz", StringComparison.OrdinalIgnoreCase) && Path.GetExtension(Path.GetFileNameWithoutExtension(path)).Equals(".tar", StringComparison.OrdinalIgnoreCase)); - internal static bool IsWix(string path) + internal static bool IsWixInstaller(string path) => (Path.GetExtension(path).Equals(".msi", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(path).Equals(".wixlib", StringComparison.OrdinalIgnoreCase)); @@ -59,12 +68,6 @@ internal static bool IsPowerShellScript(string path) || Path.GetExtension(path).Equals(".psd1", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(path).Equals(".psm1", StringComparison.OrdinalIgnoreCase); - internal static bool IsPackage(string path) - => IsVsix(path) || IsNupkg(path); - - internal static bool IsZipContainer(string path) - => IsPackage(path) || IsMPack(path) || IsZip(path) || IsTarGZip(path) || IsDeb(path) || IsRpm(path); - internal bool IsDeb() => IsDeb(FileName); internal bool IsRpm() => IsRpm(FileName); @@ -79,31 +82,43 @@ internal static bool IsZipContainer(string path) internal bool IsNupkg() => IsNupkg(FileName) && !IsSymbolsNupkg(); + internal bool IsPkg() => IsPkg(FileName); + + internal bool IsAppBundle() => IsAppBundle(FileName); + internal bool IsSymbolsNupkg() => IsSymbolsNupkg(FileName); internal bool IsZip() => IsZip(FileName); internal bool IsTarGZip() => IsTarGZip(FileName); - internal bool IsZipContainer() => IsZipContainer(FileName); + internal bool IsWixInstaller() => IsWixInstaller(FileName); - internal bool IsWix() => IsWix(FileName); + internal bool IsMPack() => IsMPack(FileName); // A wix file is an Container if it has the proper extension AND the content // (ie *.wixpack.zip) is available, otherwise it's treated like a normal file - internal bool IsWixContainer() => + internal bool IsUnpackableWixContainer() => WixContentFilePath != null - && (IsWix(FileName) + && (IsWixInstaller(FileName) || Path.GetExtension(FileName).Equals(".exe", StringComparison.OrdinalIgnoreCase)); internal bool IsExecutableWixContainer() => - IsWixContainer() && + IsUnpackableWixContainer() && (Path.GetExtension(FileName).Equals(".exe", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(FileName).Equals(".msi", StringComparison.OrdinalIgnoreCase)); - internal bool IsContainer() => IsZipContainer() || IsWixContainer(); - - internal bool IsPackage() => IsPackage(FileName); + internal bool IsUnpackableContainer() => IsZip() || + (IsUnpackableWixContainer() && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) || + IsMPack() || + IsTarGZip() || + IsDeb() || + (IsPkg() && RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) || + (IsAppBundle() && RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) || + IsNupkg() || + IsVsix() || + IsSymbolsNupkg() || + (IsRpm() && RuntimeInformation.IsOSPlatform(OSPlatform.Linux)); internal bool IsPowerShellScript() => IsPowerShellScript(FileName); @@ -131,7 +146,8 @@ public override string ToString() => $"File '{FileName}'" + (TargetFramework != null ? $" TargetFramework='{TargetFramework}'" : "") + (SignInfo.ShouldSign ? $" Certificate='{SignInfo.Certificate}'" : "") + - (SignInfo.ShouldStrongName ? $" StrongName='{SignInfo.StrongName}'" : ""); + (SignInfo.ShouldStrongName ? $" StrongName='{SignInfo.StrongName}'" : "") + + (SignInfo.ShouldNotarize ? $" NotarizationAppName='{SignInfo.NotarizationAppName}'" : ""); internal FileSignInfo WithSignableParts() => new FileSignInfo(File, SignInfo.WithIsAlreadySigned(false), TargetFramework, WixContentFilePath, true); diff --git a/src/Microsoft.DotNet.SignTool/src/ItemToSign.cs b/src/Microsoft.DotNet.SignTool/src/ItemToSign.cs new file mode 100644 index 00000000000..abc5b01c305 --- /dev/null +++ b/src/Microsoft.DotNet.SignTool/src/ItemToSign.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.DotNet.SignTool +{ + internal class ItemToSign + { + public ItemToSign(string fullPath, string collisionPriorityId = "") + { + FullPath = fullPath; + CollisionPriorityId = collisionPriorityId; + } + + public string FullPath { get; } + public string CollisionPriorityId { get; } + } +} diff --git a/src/Microsoft.DotNet.SignTool/src/RealSignTool.cs b/src/Microsoft.DotNet.SignTool/src/RealSignTool.cs index c7b1a5090db..39d947795ce 100644 --- a/src/Microsoft.DotNet.SignTool/src/RealSignTool.cs +++ b/src/Microsoft.DotNet.SignTool/src/RealSignTool.cs @@ -73,64 +73,73 @@ public override void RemoveStrongNameSign(string assemblyPath) StrongName.ClearStrongNameSignedBit(assemblyPath); } - public override bool VerifySignedPEFile(Stream assemblyStream) + public override SigningStatus VerifySignedPEFile(Stream assemblyStream) { - // The assembly won't verify by design when doing test signing. + // The assembly won't verify by design when doing test signing, but pretend it is. if (TestSign) { - return true; + return SigningStatus.Signed; } - return ContentUtil.IsAuthenticodeSigned(assemblyStream); + return VerifySignatures.IsSignedPE(assemblyStream); } - public override bool VerifyStrongNameSign(string fileFullPath) + public override SigningStatus VerifyStrongNameSign(string fileFullPath) { // The assembly won't verify by design when doing test signing. if (TestSign) { - return true; + return SigningStatus.Signed; } - return StrongName.IsSigned(fileFullPath, snPath:_snPath, log: _log); + return StrongName.IsSigned(fileFullPath, snPath:_snPath, log: _log) ? SigningStatus.Signed : SigningStatus.NotSigned; + } + + public override SigningStatus VerifySignedDeb(TaskLoggingHelper log, string filePath) + { + return VerifySignatures.IsSignedDeb(log, filePath); } - public override bool VerifySignedDeb(TaskLoggingHelper log, string filePath) + public override SigningStatus VerifySignedRpm(TaskLoggingHelper log, string filePath) { - return VerifySignatures.VerifySignedDeb(log, filePath); + return VerifySignatures.IsSignedRpm(log, filePath); } - public override bool VerifySignedRpm(TaskLoggingHelper log, string filePath) + public override SigningStatus VerifySignedPowerShellFile(string filePath) { - return VerifySignatures.VerifySignedRpm(log, filePath); + return VerifySignatures.IsSignedPowershellFile(filePath); } - public override bool VerifySignedPowerShellFile(string filePath) + public override SigningStatus VerifySignedNuGet(string filePath) { - return VerifySignatures.VerifySignedPowerShellFile(filePath); + return VerifySignatures.IsSignedNupkg(filePath); } - public override bool VerifySignedNugetFileMarker(string filePath) + public override SigningStatus VerifySignedVSIX(string filePath) { - return VerifySignatures.VerifySignedNupkgByFileMarker(filePath); + // Open the VSIX and check for the digital signature file. + return VerifySignatures.IsSignedVSIXByFileMarker(filePath); } - public override bool VerifySignedVSIXFileMarker(string filePath) + public override SigningStatus VerifySignedPkgOrAppBundle(TaskLoggingHelper log, string fullPath, string pkgToolPath) { - return VerifySignatures.VerifySignedVSIXByFileMarker(filePath); + return VerifySignatures.IsSignedPkgOrAppBundle(log, fullPath, pkgToolPath); } - + public override bool LocalStrongNameSign(IBuildEngine buildEngine, int round, IEnumerable files) { var filesToLocallyStrongNameSign = files.Where(f => f.SignInfo.ShouldLocallyStrongNameSign); - _log.LogMessage($"Locally strong naming {filesToLocallyStrongNameSign.Count()} files."); - - foreach (var file in filesToLocallyStrongNameSign) + if (filesToLocallyStrongNameSign.Any()) { - if (!LocalStrongNameSign(file)) + _log.LogMessage($"Locally strong naming {filesToLocallyStrongNameSign.Count()} files."); + + foreach (var file in filesToLocallyStrongNameSign) { - _log.LogMessage(MessageImportance.High, $"Failed to locally strong name sign '{file.FileName}'"); - return false; + if (!LocalStrongNameSign(file)) + { + _log.LogMessage(MessageImportance.High, $"Failed to locally strong name sign '{file.FileName}'"); + return false; + } } } diff --git a/src/Microsoft.DotNet.SignTool/src/SignInfo.cs b/src/Microsoft.DotNet.SignTool/src/SignInfo.cs index b691e34f4e9..a6bb1087218 100644 --- a/src/Microsoft.DotNet.SignTool/src/SignInfo.cs +++ b/src/Microsoft.DotNet.SignTool/src/SignInfo.cs @@ -30,6 +30,12 @@ internal readonly struct SignInfo /// internal string StrongName { get; } + /// + /// The app name com.microsoft.[APP NAME] that should be used to notarize the binary. + /// If empty, the binary is not notarized. + /// + internal string NotarizationAppName { get; } + internal bool ShouldIgnore { get; } internal bool IsAlreadyStrongNamed { get; } @@ -49,7 +55,9 @@ internal readonly struct SignInfo public bool ShouldStrongName => !IsAlreadyStrongNamed && !string.IsNullOrEmpty(StrongName); - public SignInfo(string certificate, string strongName, string collisionPriorityId, bool shouldIgnore, bool isAlreadySigned, bool isAlreadyStrongNamed) + public bool ShouldNotarize => !string.IsNullOrEmpty(NotarizationAppName) && !ShouldIgnore; + + public SignInfo(string certificate, string strongName, string notarizationAppName, string collisionPriorityId, bool shouldIgnore, bool isAlreadySigned, bool isAlreadyStrongNamed) { ShouldIgnore = shouldIgnore; IsAlreadySigned = isAlreadySigned; @@ -57,30 +65,34 @@ public SignInfo(string certificate, string strongName, string collisionPriorityI StrongName = strongName; CollisionPriorityId = collisionPriorityId; IsAlreadyStrongNamed = isAlreadyStrongNamed; + NotarizationAppName = notarizationAppName; } private SignInfo(bool ignoreThisFile, bool alreadySigned, bool isAlreadyStrongNamed) - : this(certificate: null, strongName: null, collisionPriorityId: null, ignoreThisFile, alreadySigned, isAlreadyStrongNamed) + : this(certificate: null, strongName: null, notarizationAppName: null, collisionPriorityId: null, ignoreThisFile, alreadySigned, isAlreadyStrongNamed) { } - internal SignInfo(string certificate, string strongName = null, string collisionPriorityId = null) - : this(certificate, strongName, collisionPriorityId, shouldIgnore: false, isAlreadySigned: false, isAlreadyStrongNamed: false) + internal SignInfo(string certificate, string strongName = null, string notarization = null, string collisionPriorityId = null) + : this(certificate, strongName, notarization, collisionPriorityId, shouldIgnore: false, isAlreadySigned: false, isAlreadyStrongNamed: false) { } internal SignInfo WithCertificateName(string value, string collisionPriorityId) - => new SignInfo(value, StrongName, collisionPriorityId, ShouldIgnore, IsAlreadySigned, IsAlreadyStrongNamed); + => new SignInfo(value, StrongName, NotarizationAppName, collisionPriorityId, false, false, IsAlreadyStrongNamed); + + internal SignInfo WithNotarization(string appName, string collisionPriorityId) + => new SignInfo(Certificate, StrongName, appName, collisionPriorityId, false, false, IsAlreadyStrongNamed); internal SignInfo WithCollisionPriorityId(string collisionPriorityId) - => new SignInfo(Certificate, StrongName, collisionPriorityId, ShouldIgnore, IsAlreadySigned, IsAlreadyStrongNamed); + => new SignInfo(Certificate, StrongName, NotarizationAppName, collisionPriorityId, ShouldIgnore, IsAlreadySigned, IsAlreadyStrongNamed); internal SignInfo WithIsAlreadySigned(bool value = false) => Certificate != null ? - new SignInfo(Certificate, StrongName, CollisionPriorityId, value, value, IsAlreadyStrongNamed) : - new SignInfo(Certificate, StrongName, CollisionPriorityId, true, value, IsAlreadyStrongNamed); + new SignInfo(Certificate, StrongName, NotarizationAppName, CollisionPriorityId, value, value, IsAlreadyStrongNamed) : + new SignInfo(Certificate, StrongName, NotarizationAppName, CollisionPriorityId, true, value, IsAlreadyStrongNamed); internal SignInfo WithIsAlreadyStrongNamed(bool value = false) => - new SignInfo(Certificate, StrongName, CollisionPriorityId, ShouldIgnore, IsAlreadySigned, value); + new SignInfo(Certificate, StrongName, NotarizationAppName, CollisionPriorityId, ShouldIgnore, IsAlreadySigned, value); } } diff --git a/src/Microsoft.DotNet.SignTool/src/SignTool.cs b/src/Microsoft.DotNet.SignTool/src/SignTool.cs index cb512121fee..1e48241244d 100644 --- a/src/Microsoft.DotNet.SignTool/src/SignTool.cs +++ b/src/Microsoft.DotNet.SignTool/src/SignTool.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.IO.Compression; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -21,6 +23,7 @@ internal abstract class SignTool internal string WixToolsPath => _args.WixToolsPath; internal string TarToolPath => _args.TarToolPath; + internal string PkgToolPath => _args.PkgToolPath; internal SignTool(SignToolArgs args, TaskLoggingHelper log) { @@ -32,83 +35,140 @@ internal SignTool(SignToolArgs args, TaskLoggingHelper log) public abstract bool LocalStrongNameSign(IBuildEngine buildEngine, int round, IEnumerable files); - public abstract bool VerifySignedDeb(TaskLoggingHelper log, string filePath); - public abstract bool VerifySignedRpm(TaskLoggingHelper log, string filePath); - public abstract bool VerifySignedPEFile(Stream stream); - public abstract bool VerifySignedPowerShellFile(string filePath); - public abstract bool VerifySignedNugetFileMarker(string filePath); - public abstract bool VerifySignedVSIXFileMarker(string filePath); + public abstract SigningStatus VerifySignedDeb(TaskLoggingHelper log, string filePath); + public abstract SigningStatus VerifySignedRpm(TaskLoggingHelper log, string filePath); + public abstract SigningStatus VerifySignedPEFile(Stream stream); + public abstract SigningStatus VerifySignedPowerShellFile(string filePath); + public abstract SigningStatus VerifySignedNuGet(string filePath); + public abstract SigningStatus VerifySignedVSIX(string filePath); + public abstract SigningStatus VerifySignedPkgOrAppBundle(TaskLoggingHelper log, string filePath, string pkgToolPath); - public abstract bool VerifyStrongNameSign(string fileFullPath); + public abstract SigningStatus VerifyStrongNameSign(string fileFullPath); public abstract bool RunMSBuild(IBuildEngine buildEngine, string projectFilePath, string binLogPath); public bool Sign(IBuildEngine buildEngine, int round, IEnumerable files) { return LocalStrongNameSign(buildEngine, round, files) - && AuthenticodeSign(buildEngine, round, files); + && AuthenticodeSignAndNotarize(buildEngine, round, files); } - private bool AuthenticodeSign(IBuildEngine buildEngine, int round, IEnumerable filesToSign) + /// + /// Zip up the mac files. Note that the Microbuild task can automatically zip files, but only does so on Mac, + /// so may as well make this generic. + /// + /// Files to sign + /// Dictionary of any files in filesToSign that were zipped + /// + private Dictionary ZipMacFiles(IEnumerable filesToSign) { - var signingDir = Path.Combine(_args.TempDir, "Signing"); - var nonOSXFilesToSign = filesToSign.Where(fsi => !SignToolConstants.SignableOSXExtensions.Contains(Path.GetExtension(fsi.FileName))); - var osxFilesToSign = filesToSign.Where(fsi => SignToolConstants.SignableOSXExtensions.Contains(Path.GetExtension(fsi.FileName))); + var zipPaths = new Dictionary(); + var osxFilesToZip = filesToSign.Where(fsi => SignToolConstants.MacSigningOperationsRequiringZipping.Contains(fsi.SignInfo.Certificate)); - var nonOSXSigningStatus = true; - var osxSigningStatus = true; - - Directory.CreateDirectory(signingDir); - - if (nonOSXFilesToSign.Any()) + foreach (var file in osxFilesToZip) { - var nonOSXBuildFilePath = Path.Combine(signingDir, $"Round{round}.proj"); - var nonOSXProjContent = GenerateBuildFileContent(filesToSign); + string zipFilePath = GetZipFilePath(file.FullPath); + zipPaths.Add(file.FullPath, zipFilePath); - File.WriteAllText(nonOSXBuildFilePath, nonOSXProjContent); - nonOSXSigningStatus = RunMSBuild(buildEngine, nonOSXBuildFilePath, Path.Combine(_args.LogDir, $"Signing{round}.binlog")); + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var process = Process.Start(new ProcessStartInfo() + { + FileName = "ditto", + Arguments = $"-V -ck --sequesterRsrc \"{file.FullPath}\" \"{zipFilePath}\"", + UseShellExecute = false, + WorkingDirectory = TempDir, + }); + + process.WaitForExit(); + if (process.ExitCode != 0) + { + _log.LogError($"Failed to zip file {file.FullPath} to {zipFilePath}"); + throw new InvalidOperationException($"Failed to zip file {file.FullPath} to {zipFilePath}"); + } + } + else + { + using (var archive = ZipFile.Open(zipFilePath, ZipArchiveMode.Create)) + { + archive.CreateEntryFromFile(file.FullPath, Path.GetFileName(file.FullPath)); + } + } } - if (osxFilesToSign.Any()) - { - // The OSX signing target requires all files to be in the same folder. - // Also all files on the folder will be signed using the same certificate. - // Therefore below we group the files to be signed by certificate. - var filesGroupedByCertificate = osxFilesToSign.GroupBy(fsi => fsi.SignInfo.Certificate); + return zipPaths; + } - var osxFilesZippingDir = Path.Combine(_args.TempDir, "OSXFilesZippingDir"); + private void UnzipMacFiles(Dictionary zippedOSXFiles) + { + foreach (var item in zippedOSXFiles) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var process = Process.Start(new ProcessStartInfo() + { + FileName = "ditto", + Arguments = $"-V -xk \"{item.Value}\" \"{Path.GetDirectoryName(item.Key)}\"", + UseShellExecute = false, + WorkingDirectory = TempDir, + }); + + process.WaitForExit(); + if (process.ExitCode != 0) + { + _log.LogError($"Failed to unzip file {item.Value} to {item.Key}"); + throw new InvalidOperationException($"Failed to unzip file {item.Value} to {item.Key}"); + } + } + else + { + // Delete the file first so that we can overwrite it. ExtractToDirectory's overwrite is not + // available on framework. +#if NETFRAMEWORK + File.Delete(item.Key); + ZipFile.ExtractToDirectory(item.Value, Path.GetDirectoryName(item.Key)); +#else + ZipFile.ExtractToDirectory(item.Value, Path.GetDirectoryName(item.Key), true); +#endif + } + } + } - Directory.CreateDirectory(osxFilesZippingDir); + private bool AuthenticodeSignAndNotarize(IBuildEngine buildEngine, int round, IEnumerable filesToSign) + { + var dir = Path.Combine(_args.TempDir, "Signing"); + bool status = true; - foreach (var osxFileGroup in filesGroupedByCertificate) - { - var certificate = osxFileGroup.Key; - var osxBuildFilePath = Path.Combine(signingDir, $"Round{round}-OSX-Cert{certificate}.proj"); - var osxProjContent = GenerateOSXBuildFileContent(osxFilesZippingDir, certificate); + Directory.CreateDirectory(dir); + + var zippedPaths = ZipMacFiles(filesToSign); - File.WriteAllText(osxBuildFilePath, osxProjContent); + // First the signing pass + var signProjectPath = Path.Combine(dir, $"Round{round}-Sign.proj"); + File.WriteAllText(signProjectPath, GenerateBuildFileContent(filesToSign, zippedPaths, false)); + status = RunMSBuild(buildEngine, signProjectPath, Path.Combine(_args.LogDir, $"SigningRound{round}.binlog")); - foreach (var item in osxFileGroup) - { - File.Copy(item.FullPath, Path.Combine(osxFilesZippingDir, item.FileName), overwrite: true); - } + if (!status) + { + return false; + } - osxSigningStatus = RunMSBuild(buildEngine, osxBuildFilePath, Path.Combine(_args.LogDir, $"Signing{round}-OSX.binlog")); + // Now unzip. Notarization does not expect zipped packages. + UnzipMacFiles(zippedPaths); - if (osxSigningStatus) - { - foreach (var item in osxFileGroup) - { - File.Copy(Path.Combine(osxFilesZippingDir, item.FileName), item.FullPath, overwrite: true); - } - } - } + // Then an additional notarization pass. + var filesToNotarize = filesToSign.Where(f => !string.IsNullOrEmpty(f.SignInfo.NotarizationAppName)); + if (filesToNotarize.Any()) + { + var notarizeProjectPath = Path.Combine(dir, $"Round{round}-Notarize.proj"); + File.WriteAllText(notarizeProjectPath, GenerateBuildFileContent(filesToNotarize, null, true)); + status = RunMSBuild(buildEngine, notarizeProjectPath, Path.Combine(_args.LogDir, $"NotarizationRound{round}.binlog")); } - return nonOSXSigningStatus && osxSigningStatus; + return status; } - private string GenerateBuildFileContent(IEnumerable filesToSign) + private string GenerateBuildFileContent(IEnumerable filesToSign, Dictionary zippedPaths, bool notarize) { var builder = new StringBuilder(); AppendLine(builder, depth: 0, text: @""); @@ -123,13 +183,20 @@ private string GenerateBuildFileContent(IEnumerable filesToSign) AppendLine(builder, depth: 1, text: @""); AppendLine(builder, depth: 1, text: $@""); - AppendLine(builder, depth: 1, text: $@""); foreach (var fileToSign in filesToSign) { - AppendLine(builder, depth: 2, text: $@""); - AppendLine(builder, depth: 3, text: $@"{fileToSign.SignInfo.Certificate}"); + if (zippedPaths == null || !zippedPaths.TryGetValue(fileToSign.FullPath, out string filePath)) + { + filePath = fileToSign.FullPath; + } + AppendLine(builder, depth: 2, text: $@""); + AppendLine(builder, depth: 3, text: $@"{(notarize ? SignToolConstants.MacNotarizationOperation : fileToSign.SignInfo.Certificate)}"); + if (notarize) + { + AppendLine(builder, depth: 3, text: $@"{fileToSign.SignInfo.NotarizationAppName}"); + } if (fileToSign.SignInfo.ShouldStrongName && !fileToSign.SignInfo.ShouldLocallyStrongNameSign) { AppendLine(builder, depth: 3, text: $@"{fileToSign.SignInfo.StrongName}"); @@ -141,7 +208,7 @@ private string GenerateBuildFileContent(IEnumerable filesToSign) // The MicroBuild targets hook AfterBuild to do the signing hence we just make it our no-op default target AppendLine(builder, depth: 1, text: @""); - AppendLine(builder, depth: 2, text: @""); + AppendLine(builder, depth: 2, text: @""); AppendLine(builder, depth: 1, text: @""); AppendLine(builder, depth: 1, text: $@""); @@ -150,30 +217,9 @@ private string GenerateBuildFileContent(IEnumerable filesToSign) return builder.ToString(); } - private string GenerateOSXBuildFileContent(string fullPathOSXFilesFolder, string osxCertificateName) + protected virtual string GetZipFilePath(string fullPath) { - var builder = new StringBuilder(); - var signKind = _args.TestSign ? "test" : "real"; - - AppendLine(builder, depth: 0, text: @""); - AppendLine(builder, depth: 0, text: @""); - - AppendLine(builder, depth: 1, text: $@""); - - AppendLine(builder, depth: 1, text: $@""); - AppendLine(builder, depth: 2, text: $@"{fullPathOSXFilesFolder}"); - AppendLine(builder, depth: 2, text: $@"{osxCertificateName}"); - AppendLine(builder, depth: 2, text: $@"{signKind}"); - AppendLine(builder, depth: 1, text: $@""); - - AppendLine(builder, depth: 1, text: @""); - AppendLine(builder, depth: 2, text: @""); - AppendLine(builder, depth: 1, text: @""); - - AppendLine(builder, depth: 1, text: $@""); - AppendLine(builder, depth: 0, text: @""); - - return builder.ToString(); + return Path.Combine(Path.GetDirectoryName(fullPath), Path.GetFileNameWithoutExtension(fullPath) + ".zip"); } private static void AppendLine(StringBuilder builder, int depth, string text) diff --git a/src/Microsoft.DotNet.SignTool/src/SignToolArgs.cs b/src/Microsoft.DotNet.SignTool/src/SignToolArgs.cs index 92f664cb025..23dc0791f71 100644 --- a/src/Microsoft.DotNet.SignTool/src/SignToolArgs.cs +++ b/src/Microsoft.DotNet.SignTool/src/SignToolArgs.cs @@ -14,8 +14,9 @@ internal readonly struct SignToolArgs internal string EnclosingDir { get; } internal string WixToolsPath { get; } internal string TarToolPath { get; } + internal string PkgToolPath { get; } - internal SignToolArgs(string tempPath, string microBuildCorePath, bool testSign, string dotnetPath, string logDir, string enclosingDir, string snBinaryPath, string wixToolsPath, string tarToolPath) + internal SignToolArgs(string tempPath, string microBuildCorePath, bool testSign, string dotnetPath, string logDir, string enclosingDir, string snBinaryPath, string wixToolsPath, string tarToolPath, string pkgToolPath) { TempDir = tempPath; MicroBuildCorePath = microBuildCorePath; @@ -26,6 +27,7 @@ internal SignToolArgs(string tempPath, string microBuildCorePath, bool testSign, SNBinaryPath = snBinaryPath; WixToolsPath = wixToolsPath; TarToolPath = tarToolPath; + PkgToolPath = pkgToolPath; } } } diff --git a/src/Microsoft.DotNet.SignTool/src/SignToolConstants.cs b/src/Microsoft.DotNet.SignTool/src/SignToolConstants.cs index 619bfb0a42d..f0b8b879869 100644 --- a/src/Microsoft.DotNet.SignTool/src/SignToolConstants.cs +++ b/src/Microsoft.DotNet.SignTool/src/SignToolConstants.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; namespace Microsoft.DotNet.SignTool { @@ -113,20 +114,31 @@ internal static class SignToolConstants ".pyd", ".deb", + ".pkg", + ".app", + ".dylib", ".rpm", }; - /// - /// List of known signable extensions for OSX files. - /// - public static readonly HashSet SignableOSXExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) - { - ".pkg" - }; + + public static readonly HashSet MacSigningOperationsRequiringZipping = + new HashSet(StringComparer.OrdinalIgnoreCase) + { + "MacDeveloperHarden", + "MacDeveloper", + "MacDeveloperVNext", + "MacDeveloperVNextHarden", + "MacNotarize", + }; /// /// Attribute for the CollisionPriorityId /// public const string CollisionPriorityId = "CollisionPriorityId"; + + /// + /// Notarization operation microbuild ID. Microbuild does not currently support the friendly name, MacNotarize + /// + public const string MacNotarizationOperation = "8020"; } } diff --git a/src/Microsoft.DotNet.SignTool/src/SignToolTask.cs b/src/Microsoft.DotNet.SignTool/src/SignToolTask.cs index 26494b2b0ab..8a74d5b7473 100644 --- a/src/Microsoft.DotNet.SignTool/src/SignToolTask.cs +++ b/src/Microsoft.DotNet.SignTool/src/SignToolTask.cs @@ -11,9 +11,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; -using System.Resources; using System.Runtime.Versioning; +using System.Runtime.InteropServices; namespace Microsoft.DotNet.SignTool { @@ -47,12 +46,6 @@ public class SignToolTask : BuildTask /// public bool AllowEmptySignList { get; set; } - /// - /// By default in non-DryRun cases we verify the vsix and nuget packages contain a signature file - /// This option disables that check in cases you want to sign the container at a later step. - /// - public bool SkipZipContainerSignatureMarkerCheck { get; set; } - /// /// For some cases you may need to run the sign tool more than once and if you do you want to /// share the same cache directory which contains already signed binaries. In those cases @@ -140,6 +133,11 @@ public class SignToolTask : BuildTask /// public string TarToolPath { get; set; } + /// + /// Path to Microsoft.DotNet.MacOsPkg.dll. Required for signing pkg files on MacOS. + /// + public string PkgToolPath { get; set; } + /// /// Number of containers to repack in parallel. Zero will default to the processor count /// @@ -201,10 +199,15 @@ public void ExecuteImpl() return; } - if(!Path.IsPathRooted(TempDir)) + if (!Path.IsPathRooted(TempDir)) { Log.LogWarning($"TempDir ('{TempDir}' is not rooted, this can cause unexpected behavior in signtool. Please provide a fully qualified 'TempDir' path."); } + + if (PkgToolPath == null && RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Log.LogError($"PkgToolPath ('{PkgToolPath}') does not exist & is required for unpacking, repacking, and notarizing .pkg files and .app bundles on MacOS."); + } } if(WixToolsPath != null && !Directory.Exists(WixToolsPath)) { @@ -219,25 +222,28 @@ public void ExecuteImpl() var strongNameInfo = ParseStrongNameSignInfo(); var fileSignInfo = ParseFileSignInfo(); var extensionSignInfo = ParseFileExtensionSignInfo(); - var dualCertificates = ParseCertificateInfo(); + var dualCertificates = ParseAdditionalCertificateInformation(); if (Log.HasLoggedErrors) return; - var signToolArgs = new SignToolArgs(TempDir, MicroBuildCorePath, TestSign, DotNetPath, LogDir, enclosingDir, SNBinaryPath, WixToolsPath, TarToolPath); + var signToolArgs = new SignToolArgs(TempDir, MicroBuildCorePath, TestSign, DotNetPath, LogDir, enclosingDir, SNBinaryPath, WixToolsPath, TarToolPath, PkgToolPath); var signTool = DryRun ? new ValidationOnlySignTool(signToolArgs, Log) : (SignTool)new RealSignTool(signToolArgs, Log); + var itemsToSign = ItemsToSign.Select(i => new ItemToSign(i.ItemSpec, i.GetMetadata(SignToolConstants.CollisionPriorityId))).OrderBy(i => i.CollisionPriorityId).ToList(); + Telemetry telemetry = new Telemetry(); try { Configuration configuration = new Configuration( TempDir, - ItemsToSign.OrderBy(i => i.GetMetadata(SignToolConstants.CollisionPriorityId)).ToArray(), + itemsToSign, strongNameInfo, fileSignInfo, extensionSignInfo, dualCertificates, - TarToolPath, - SNBinaryPath, + tarToolPath: TarToolPath, + pkgToolPath: PkgToolPath, + snPath: SNBinaryPath, Log, useHashInExtractionPath: UseHashInExtractionPath, telemetry: telemetry); @@ -263,8 +269,6 @@ public void ExecuteImpl() maximumParallelFileSizeInBytes: MaximumParallelFileSize * 1024 * 1024, telemetry: telemetry); - util.SkipZipContainerSignatureMarkerCheck = this.SkipZipContainerSignatureMarkerCheck; - if (Log.HasLoggedErrors) return; util.Go(DoStrongNameCheck); @@ -284,11 +288,58 @@ private void PrintConfigInformation() Log.LogMessage(MessageImportance.High, $"MicroBuild signing configuration will be in (Round*.proj): {TempDir}"); } - private ITaskItem[] ParseCertificateInfo() + private Dictionary> ParseAdditionalCertificateInformation() { - return CertificatesSignInfo? - .Where(item => item.GetMetadata("DualSigningAllowed").Equals("true", StringComparison.OrdinalIgnoreCase)) - .ToArray(); + if (CertificatesSignInfo != null) + { + // Parse the additional certificate information, which has the following + // potential metadata: + // - DualSigningAllowed: boolean + // - CollisionPriorityId: string + // - MacCertificate: Name of actual cert if the cert name represents a sign+notarize operation. If present, requires "Notarize" metadata. + // - MacNotarizationOperation: Name of the notarize operation if the cert name represents a sign+notarize operation. If present, requires "Certificate" metadata. + + var map = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (var certificateSignInfo in CertificatesSignInfo) + { + var certificateName = certificateSignInfo.ItemSpec; + var dualSigningAllowed = certificateSignInfo.GetMetadata("DualSigningAllowed"); + bool dualSignAllowedValue = false; + var macSigningOperation = certificateSignInfo.GetMetadata("MacCertificate"); + var macNotarizationAppName = certificateSignInfo.GetMetadata("MacNotarizationAppName"); + var collisionPriorityId = certificateSignInfo.GetMetadata(SignToolConstants.CollisionPriorityId); + + if (string.IsNullOrEmpty(macSigningOperation) != string.IsNullOrEmpty(macNotarizationAppName)) + { + Log.LogError($"Both MacCertificate and MacNotarizationAppName must be specified"); + continue; + } + if (!string.IsNullOrEmpty(dualSigningAllowed) && !bool.TryParse(dualSigningAllowed, out dualSignAllowedValue)) + { + Log.LogError($"DualSigningAllowed must be 'true' or 'false"); + continue; + } + + var additionalCertInfo = new AdditionalCertificateInformation + { + DualSigningAllowed = dualSignAllowedValue, + MacSigningOperation = macSigningOperation, + MacNotarizationAppName = macNotarizationAppName, + CollisionPriorityId = collisionPriorityId + }; + + if (!map.TryGetValue(certificateName, out var additionalCertificateInformation)) + { + additionalCertificateInformation = new List(); + map.Add(certificateName, additionalCertificateInformation); + } + additionalCertificateInformation.Add(additionalCertInfo); + } + + return map; + } + + return null; } private string GetEnclosingDirectoryOfItemsToSign() diff --git a/src/Microsoft.DotNet.SignTool/src/SigningStatus.cs b/src/Microsoft.DotNet.SignTool/src/SigningStatus.cs new file mode 100644 index 00000000000..b551231c2b4 --- /dev/null +++ b/src/Microsoft.DotNet.SignTool/src/SigningStatus.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.DotNet.SignTool +{ + internal enum SigningStatus + { + /// + /// The file is signed. + /// + Signed, + /// + /// The file is not signed. + /// + NotSigned, + /// + /// The status of the file could not be determined. + /// + Unknown + } +} diff --git a/src/Microsoft.DotNet.SignTool/src/ValidationOnlySignTool.cs b/src/Microsoft.DotNet.SignTool/src/ValidationOnlySignTool.cs index 2e037a5c679..ed926ef3cd5 100644 --- a/src/Microsoft.DotNet.SignTool/src/ValidationOnlySignTool.cs +++ b/src/Microsoft.DotNet.SignTool/src/ValidationOnlySignTool.cs @@ -43,17 +43,17 @@ public override void RemoveStrongNameSign(string assemblyPath) { } - public override bool VerifySignedDeb(TaskLoggingHelper log, string filePath) - => true; + public override SigningStatus VerifySignedDeb(TaskLoggingHelper log, string filePath) + => SigningStatus.Signed; - public override bool VerifySignedRpm(TaskLoggingHelper log, string filePath) - => true; + public override SigningStatus VerifySignedRpm(TaskLoggingHelper log, string filePath) + => SigningStatus.Signed; - public override bool VerifySignedPEFile(Stream assemblyStream) - => true; + public override SigningStatus VerifySignedPEFile(Stream assemblyStream) + => SigningStatus.Signed; - public override bool VerifyStrongNameSign(string fileFullPath) - => true; + public override SigningStatus VerifyStrongNameSign(string fileFullPath) + => SigningStatus.Signed; public override bool RunMSBuild(IBuildEngine buildEngine, string projectFilePath, string binLogPath) { @@ -67,19 +67,12 @@ public override bool RunMSBuild(IBuildEngine buildEngine, string projectFilePath } } - public override bool VerifySignedPowerShellFile(string filePath) - { - return true; - } + public override SigningStatus VerifySignedPowerShellFile(string filePath) => SigningStatus.Signed; - public override bool VerifySignedNugetFileMarker(string filePath) - { - return true; - } + public override SigningStatus VerifySignedNuGet(string filePath) => SigningStatus.Signed; - public override bool VerifySignedVSIXFileMarker(string filePath) - { - return true; - } + public override SigningStatus VerifySignedVSIX(string filePath) => SigningStatus.Signed; + + public override SigningStatus VerifySignedPkgOrAppBundle(TaskLoggingHelper log, string filePath, string pkgToolPath) => SigningStatus.Signed; } } diff --git a/src/Microsoft.DotNet.SignTool/src/VerifySignatures.cs b/src/Microsoft.DotNet.SignTool/src/VerifySignatures.cs index 4fda36bfd28..ea27d78398d 100644 --- a/src/Microsoft.DotNet.SignTool/src/VerifySignatures.cs +++ b/src/Microsoft.DotNet.SignTool/src/VerifySignatures.cs @@ -17,6 +17,8 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; namespace Microsoft.DotNet.SignTool { @@ -25,17 +27,17 @@ internal class VerifySignatures #if !NET472 private static readonly HttpClient client = new(new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(10) }); #endif - internal static bool VerifySignedDeb(TaskLoggingHelper log, string filePath) + internal static SigningStatus IsSignedDeb(TaskLoggingHelper log, string filePath) { # if NET472 // Debian unpack tooling is not supported on .NET Framework log.LogMessage(MessageImportance.Low, $"Skipping signature verification of {filePath} for .NET Framework"); - return false; + return SigningStatus.Unknown; # else - if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { log.LogMessage(MessageImportance.Low, $"Skipping signature verification of {filePath} for Windows."); - return false; + return SigningStatus.Unknown; } string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); @@ -66,14 +68,14 @@ internal static bool VerifySignedDeb(TaskLoggingHelper log, string filePath) string output = RunCommand($"gpg --verify {gpgOrigin} {tempDir}/combined-contents", throwOnError: false); if (output.Contains("Good signature")) { - return true; + return SigningStatus.Signed; } - return false; + return SigningStatus.NotSigned; } catch(Exception e) { log.LogMessage(MessageImportance.Low, $"Failed to verify signature of {filePath} with the following error: {e}"); - return false; + return SigningStatus.NotSigned; } finally { @@ -82,22 +84,20 @@ internal static bool VerifySignedDeb(TaskLoggingHelper log, string filePath) # endif } - internal static bool VerifySignedRpm(TaskLoggingHelper log, string filePath) + internal static SigningStatus IsSignedRpm(TaskLoggingHelper log, string filePath) { // RPM signature verification is not yet implemented log.LogMessage(MessageImportance.Low, $"Skipping signature verification of {filePath} - not yet implemented."); - return true; + return SigningStatus.Unknown; } - internal static bool VerifySignedPowerShellFile(string filePath) + internal static SigningStatus IsSignedPowershellFile(string filePath) { - return File.ReadLines(filePath).Any(line => line.IndexOf("# SIG # Begin Signature Block", StringComparison.OrdinalIgnoreCase) >= 0); + return File.ReadLines(filePath).Any(line => line.IndexOf("# SIG # Begin Signature Block", StringComparison.OrdinalIgnoreCase) >= 0) + ? SigningStatus.Signed : SigningStatus.NotSigned; } - internal static bool VerifySignedNupkgByFileMarker(string filePath) - { - return Path.GetFileName(filePath).Equals(".signature.p7s", StringComparison.OrdinalIgnoreCase); - } - internal static bool VerifySignedNupkgIntegrity(string filePath) + + internal static SigningStatus IsSignedNupkg(string filePath) { bool isSigned = false; using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(filePath))) @@ -130,47 +130,47 @@ internal static bool VerifySignedNupkgIntegrity(string filePath) } #endif } - return isSigned; + return isSigned ? SigningStatus.Signed : SigningStatus.NotSigned; } - internal static bool VerifySignedVSIXByFileMarker(string filePath) + internal static SigningStatus IsSignedVSIXByFileMarker(string filePath) { - return filePath.StartsWith("package/services/digital-signature/", StringComparison.OrdinalIgnoreCase); + using var archive = new ZipArchive(File.OpenRead(filePath), ZipArchiveMode.Read, leaveOpen: false); + return archive.GetFiles().Any(f => f.StartsWith("package/services/digital-signature/", StringComparison.OrdinalIgnoreCase)) ? + SigningStatus.Signed : SigningStatus.NotSigned; } - internal static bool IsSignedContainer(string fullPath, string tempDir, string tarToolPath) + internal static SigningStatus IsSignedPkgOrAppBundle(TaskLoggingHelper log, string filePath, string pkgToolPath) { - if (FileSignInfo.IsZipContainer(fullPath)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - bool signedContainer = false; + log.LogMessage(MessageImportance.Low, $"Skipping signature verification of {filePath} for Windows."); + return SigningStatus.Unknown; + } - foreach (var (relativePath, _, _) in ZipData.ReadEntries(fullPath, tempDir, tarToolPath, ignoreContent: false)) - { - if (FileSignInfo.IsNupkg(fullPath) && VerifySignedNupkgByFileMarker(relativePath)) - { - if (!VerifySignedNupkgIntegrity(fullPath)) - { - return false; - } - signedContainer = true; - break; - } - else if (FileSignInfo.IsVsix(fullPath) && VerifySignedVSIXByFileMarker(relativePath)) - { - signedContainer = true; - break; - } - } + return ZipData.RunPkgProcess(filePath, null, "verify", pkgToolPath) ? SigningStatus.Signed : SigningStatus.NotSigned; + } - if (!signedContainer) - { - return false; - } + public static SigningStatus IsSignedPE(string filePath) + { + using (var stream = new FileStream(filePath, FileMode.Open)) + { + return IsSignedPE(stream); + } + } + + public static SigningStatus IsSignedPE(Stream assemblyStream) + { + using (var peReader = new PEReader(assemblyStream)) + { + var headers = peReader.PEHeaders; + var entry = headers.PEHeader.CertificateTableDirectory; + + return entry.Size > 0 ? SigningStatus.Signed : SigningStatus.NotSigned; } - return true; } - internal static bool IsDigitallySigned(string fullPath) + internal static SigningStatus IsWixSigned(string fullPath) { X509Certificate2 certificate; try @@ -180,7 +180,7 @@ internal static bool IsDigitallySigned(string fullPath) var certContentType = X509Certificate2.GetCertContentType(fullPath); if (certContentType != X509ContentType.Authenticode) { - return false; + return SigningStatus.NotSigned; } #pragma warning disable SYSLIB0057 // Suppress obsoletion warning for CreateFromSignedFile @@ -190,9 +190,9 @@ internal static bool IsDigitallySigned(string fullPath) } catch (Exception) { - return false; + return SigningStatus.NotSigned; } - return certificate.Verify(); + return certificate.Verify() ? SigningStatus.Signed : SigningStatus.NotSigned; } private static string RunCommand(string command, bool throwOnError = true) diff --git a/src/Microsoft.DotNet.SignTool/src/ZipData.cs b/src/Microsoft.DotNet.SignTool/src/ZipData.cs index 2893c60f3f6..e9ee4649209 100644 --- a/src/Microsoft.DotNet.SignTool/src/ZipData.cs +++ b/src/Microsoft.DotNet.SignTool/src/ZipData.cs @@ -11,8 +11,9 @@ using System.Linq; using System.Data; using System.Diagnostics; -using Microsoft.DotNet.Build.Tasks.Installers; using System.Runtime.InteropServices; +using NuGet.Packaging; +using Microsoft.DotNet.Build.Tasks.Installers; #if NET472 using System.IO.Packaging; @@ -53,7 +54,7 @@ internal ZipData(FileSignInfo fileSignInfo, ImmutableDictionary return null; } - public static IEnumerable<(string relativePath, Stream content, long contentSize)> ReadEntries(string archivePath, string tempDir, string tarToolPath, bool ignoreContent = false) + public static IEnumerable<(string relativePath, Stream content, long contentSize)> ReadEntries(string archivePath, string tempDir, string tarToolPath, string pkgToolPath, bool ignoreContent = false) { if (FileSignInfo.IsTarGZip(archivePath)) { @@ -65,6 +66,10 @@ internal ZipData(FileSignInfo fileSignInfo, ImmutableDictionary .Select(entry => (entry.Name, entry.DataStream, entry.Length)); #endif } + else if (FileSignInfo.IsPkg(archivePath) || FileSignInfo.IsAppBundle(archivePath)) + { + return ReadPkgOrAppBundleEntries(archivePath, tempDir, pkgToolPath, ignoreContent); + } else if (FileSignInfo.IsDeb(archivePath)) { #if NET472 @@ -86,14 +91,16 @@ internal ZipData(FileSignInfo fileSignInfo, ImmutableDictionary return ReadRpmContainerEntries(archivePath); #endif } - - return ReadZipEntries(archivePath); + else + { + return ReadZipEntries(archivePath); + } } /// /// Repack the zip container with the signed files. /// - public void Repack(TaskLoggingHelper log, string tempDir, string wixToolsPath, string tarToolPath) + public void Repack(TaskLoggingHelper log, string tempDir, string wixToolsPath, string tarToolPath, string pkgToolPath) { #if NET472 if (FileSignInfo.IsVsix()) @@ -106,10 +113,14 @@ public void Repack(TaskLoggingHelper log, string tempDir, string wixToolsPath, s { RepackTarGZip(log, tempDir, tarToolPath); } - else if (FileSignInfo.IsWixContainer()) + else if (FileSignInfo.IsUnpackableWixContainer()) { RepackWixPack(log, tempDir, wixToolsPath); } + else if (FileSignInfo.IsPkg() || FileSignInfo.IsAppBundle()) + { + RepackPkgOrAppBundles(log, tempDir, pkgToolPath); + } else if (FileSignInfo.IsDeb()) { #if NET472 @@ -181,22 +192,23 @@ string getPartRelativeFileName(PackagePart part) private static IEnumerable<(string relativePath, Stream content, long contentSize)> ReadZipEntries(string archivePath) { - using var archive = new ZipArchive(File.OpenRead(archivePath), ZipArchiveMode.Read, leaveOpen: false); - - foreach (var entry in archive.Entries) + using (var archive = new ZipArchive(File.OpenRead(archivePath), ZipArchiveMode.Read, leaveOpen: false)) { - string relativePath = entry.FullName; // lgtm [cs/zipslip] Archive from trusted source - - // `entry` might be just a pointer to a folder. We skip those. - if (relativePath.EndsWith("/") && entry.Name == "") + foreach (var entry in archive.Entries) { - yield return (relativePath, null, 0); - } - else - { - var contentStream = entry.Open(); - yield return (relativePath, contentStream, entry.Length); - contentStream.Close(); + string relativePath = entry.FullName; // lgtm [cs/zipslip] Archive from trusted source + + // `entry` might be just a pointer to a folder. We skip those. + if (relativePath.EndsWith("/") && entry.Name == "") + { + yield return (relativePath, null, 0); + } + else + { + var contentStream = entry.Open(); + yield return (relativePath, contentStream, entry.Length); + contentStream.Close(); + } } } } @@ -289,6 +301,97 @@ private void RepackWixPack(TaskLoggingHelper log, string tempDir, string wixTool } } + internal static bool RunPkgProcess(string srcPath, string dstPath, string action, string pkgToolPath) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + throw new Exception($"Pkg tooling is only supported on MacOS."); + } + + string args = $@"{action} ""{srcPath}"""; + + if (action != "verify") + { + args += $@" ""{dstPath}"""; + } + + var process = Process.Start(new ProcessStartInfo() + { + FileName = "dotnet", + Arguments = $@"exec ""{pkgToolPath}"" {args}", + UseShellExecute = false, + RedirectStandardError = true + }); + + process.WaitForExit(); + return process.ExitCode == 0; + } + + private static IEnumerable<(string relativePath, Stream content, long contentSize)> ReadPkgOrAppBundleEntries(string archivePath, string tempDir, string pkgToolPath, bool ignoreContent) + { + string extractDir = Path.Combine(tempDir, Guid.NewGuid().ToString()); + try + { + if (!RunPkgProcess(archivePath, extractDir, "unpack", pkgToolPath)) + { + throw new Exception($"Failed to unpack pkg {archivePath}"); + } + + foreach (var path in Directory.EnumerateFiles(extractDir, "*.*", SearchOption.AllDirectories)) + { + var relativePath = path.Substring(extractDir.Length + 1).Replace(Path.DirectorySeparatorChar, '/'); + using var stream = ignoreContent ? null : (Stream)File.Open(path, FileMode.Open); + yield return (relativePath, stream, stream?.Length ?? 0); + } + } + finally + { + if (Directory.Exists(extractDir)) + { + Directory.Delete(extractDir, recursive: true); + } + } + } + + private void RepackPkgOrAppBundles(TaskLoggingHelper log, string tempDir, string pkgToolPath) + { + string extractDir = Path.Combine(tempDir, Guid.NewGuid().ToString()); + try + { + if (!RunPkgProcess(srcPath: FileSignInfo.FullPath, dstPath: extractDir, "unpack", pkgToolPath)) + { + return; + } + + foreach (var path in Directory.EnumerateFiles(extractDir, "*.*", SearchOption.AllDirectories)) + { + var relativePath = path.Substring(extractDir.Length + 1).Replace(Path.DirectorySeparatorChar, '/'); + + var signedPart = FindNestedPart(relativePath); + if (!signedPart.HasValue) + { + log.LogMessage(MessageImportance.Low, $"Didn't find signed part for nested file: {FileSignInfo.FullPath} -> {relativePath}"); + continue; + } + + log.LogMessage(MessageImportance.Low, $"Copying signed stream from {signedPart.Value.FileSignInfo.FullPath} to {FileSignInfo.FullPath} -> {relativePath}."); + File.Copy(signedPart.Value.FileSignInfo.FullPath, path, overwrite: true); + } + + if (!RunPkgProcess(srcPath: extractDir, dstPath: FileSignInfo.FullPath, "pack", pkgToolPath)) + { + return; + } + } + finally + { + if (Directory.Exists(extractDir)) + { + Directory.Delete(extractDir, recursive: true); + } + } + } + #if NETFRAMEWORK private static bool RunTarProcess(string srcPath, string dstPath, string tarToolPath) { @@ -312,7 +415,7 @@ private static bool RunTarProcess(string srcPath, string dstPath, string tarTool if (!RunTarProcess(archivePath, extractDir, tarToolPath)) { - yield break; + throw new Exception($"Failed to unpack tar archive: {archivePath}"); } foreach (var path in Directory.EnumerateFiles(extractDir, "*.*", SearchOption.AllDirectories)) diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Microsoft.DotNet.XUnitConsoleRunner.csproj b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Microsoft.DotNet.XUnitConsoleRunner.csproj index f62fd3e86cd..68bdb062ee1 100644 --- a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Microsoft.DotNet.XUnitConsoleRunner.csproj +++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Microsoft.DotNet.XUnitConsoleRunner.csproj @@ -38,7 +38,7 @@ - + $"Certificate \"{Include}\" has DualSigningAllowed set to {DualSigningAllowed}"; public XElement ToXml() => new XElement( diff --git a/src/VersionTools/Microsoft.DotNet.VersionTools/Util/EnumerableExtensions.cs b/src/VersionTools/Microsoft.DotNet.VersionTools/Util/EnumerableExtensions.cs index 5268c9472b7..132fd6238ac 100644 --- a/src/VersionTools/Microsoft.DotNet.VersionTools/Util/EnumerableExtensions.cs +++ b/src/VersionTools/Microsoft.DotNet.VersionTools/Util/EnumerableExtensions.cs @@ -1,11 +1,11 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; namespace Microsoft.DotNet.VersionTools.Util { - internal static class DictionaryExtensions + public static class DictionaryExtensions { public static TValue GetOrDefault( this IDictionary attributes,