Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable Unpacking and Repacking of .pkg files and .app bundles #15206

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Microsoft.DotNet.Arcade.Sdk/tools/Sign.proj
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@

<!-- SN is only available on Windows -->
<SNBinaryPath Condition="$([MSBuild]::IsOSPlatform('Windows'))">$(NuGetPackageRoot)sn\$(SNVersion)\sn.exe</SNBinaryPath>

<!-- .pkgs and .app bundle tooling is only available on MacOS -->
<PkgToolPath Condition="$([MSBuild]::IsOSPlatform('OSX'))">$(NuGetPackageRoot)microsoft.dotnet.pkg\$(MicrosoftDotNetSignToolVersion)\tools\net9.0\any\Microsoft.Dotnet.Pkg.dll</PkgToolPath>
</PropertyGroup>

<Error Condition="'$(AllowEmptySignList)' != 'true' AND '@(ItemsToSign)' == ''"
Expand Down Expand Up @@ -98,6 +101,7 @@
MicroBuildCorePath="$(NuGetPackageRoot)microbuild.core\$(MicroBuildCoreVersion)"
WixToolsPath="$(WixInstallPath)"
TarToolPath="$(NuGetPackageRoot)microsoft.dotnet.tar\$(MicrosoftDotNetSignToolVersion)\tools\net9.0\any\Microsoft.Dotnet.Tar.dll"
PkgToolPath="$(PkgToolPath)"
RepackParallelism="$(SignToolRepackParallelism)"
MaximumParallelFileSize="$(SignToolRepackMaximumParallelFileSize)" />
</Target>
Expand Down
4 changes: 4 additions & 0 deletions src/Microsoft.DotNet.Arcade.Sdk/tools/Sign.props
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@
<FileExtensionSignInfo Include=".zip" CertificateName="None" />
<FileExtensionSignInfo Include=".tgz" CertificateName="None" />
<FileExtensionSignInfo Include=".tar.gz" CertificateName="None" />
<FileExtensionSignInfo Condition="$([MSBuild]::IsOSPlatform('OSX'))" Include=".pkg" CertificateName="None" />
<!-- .app bundles are technically directories, but the Microsoft.DotNet.Pkg
tool packs these bundles into zips when unpacking .pkgs -->
<FileExtensionSignInfo Condition="$([MSBuild]::IsOSPlatform('OSX'))" Include=".app" CertificateName="None" />
</ItemGroup>

<!-- The name of the .NET specific certificate, which is a general replacement for Microsoft400
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@
SkipGetTargetFrameworkProperties="true"
Private="false"
OutputItemType="_TarToolPath" />

<ProjectReference Condition="$([MSBuild]::IsOSPlatform('osx'))"
Include="..\Microsoft.DotNet.Pkg\Microsoft.DotNet.Pkg.csproj"
ReferenceOutputAssembly="false"
SetTargetFramework="TargetFramework=$(NetToolCurrent)"
SkipGetTargetFrameworkProperties="true"
Private="false"
OutputItemType="_PkgToolPath" />
</ItemGroup>

<ItemGroup>
Expand All @@ -47,4 +55,16 @@
</ItemGroup>
</Target>

<Target Name="_CopyPkgTool" AfterTargets="ResolveProjectReferences" Condition="$([MSBuild]::IsOSPlatform('osx'))">
<PropertyGroup>
<_PkgToolPattern>@(_PkgToolPath->'%(RootDir)%(Directory)')**\*.*</_PkgToolPattern>
</PropertyGroup>
<ItemGroup>
<_PkgToolFiles Include="$(_PkgToolPattern)"/>
</ItemGroup>
<ItemGroup>
<Content Include="@(_PkgToolFiles)" CopyToOutputDirectory="PreserveNewest" Link="tools\pkg\%(RecursiveDir)%(Filename)%(Extension)"/>
</ItemGroup>
</Target>

</Project>
Binary file not shown.
Binary file not shown.
Binary file not shown.
163 changes: 158 additions & 5 deletions src/Microsoft.DotNet.SignTool.Tests/SignToolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public class SignToolTests : IDisposable
{".vsix", new List<SignInfo>{ new SignInfo("VsixSHA2") } },
{".zip", new List<SignInfo>{ SignInfo.Ignore } },
{".tgz", new List<SignInfo>{ SignInfo.Ignore } },
{".pkg", new List<SignInfo>{ SignInfo.Ignore } },
{".app", new List<SignInfo>{ SignInfo.Ignore } },
{".nupkg", new List<SignInfo>{ new SignInfo("NuGet") } },
{".symbols.nupkg", new List<SignInfo>{ SignInfo.Ignore } },
};
Expand All @@ -62,6 +64,8 @@ public class SignToolTests : IDisposable
{ ".vsix", new List<SignInfo>{ new SignInfo("VsixSHA2", collisionPriorityId: "123") } },
{ ".zip", new List<SignInfo>{ SignInfo.Ignore } },
{ ".tgz", new List<SignInfo>{ SignInfo.Ignore } },
{ ".pkg", new List<SignInfo>{ SignInfo.Ignore } },
{ ".app", new List<SignInfo>{ SignInfo.Ignore } },
{ ".nupkg", new List<SignInfo>{ new SignInfo("NuGet", collisionPriorityId: "123") } },
{ ".symbols.nupkg", new List<SignInfo>{ SignInfo.Ignore } },
};
Expand Down Expand Up @@ -113,6 +117,14 @@ public class SignToolTests : IDisposable
{ "CertificateName", "None" },
{ SignToolConstants.CollisionPriorityId, "123" }
}),
new TaskItem(".pkg", new Dictionary<string, string> {
{ "CertificateName", "None" },
{ SignToolConstants.CollisionPriorityId, "123" }
}),
new TaskItem(".app", new Dictionary<string, string> {
{ "CertificateName", "None" },
{ SignToolConstants.CollisionPriorityId, "123" }
}),
new TaskItem(".nupkg", new Dictionary<string, string> {
{ "CertificateName", "NuGet" },
{ SignToolConstants.CollisionPriorityId, "123" }
Expand Down Expand Up @@ -255,6 +267,7 @@ private string GetWixToolPath()
}

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.Pkg.dll");

private string GetResourcePath(string name, string relativePath = null)
{
Expand Down Expand Up @@ -314,10 +327,10 @@ private void ValidateGeneratedProject(
// The path to MSBuild and 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, msBuildPath: null, dotnetPath: null, _tmpDir, enclosingDir: "", "", wixToolsPath: wixToolsPath, tarToolPath: s_tarToolPath);
var signToolArgs = new SignToolArgs(_tmpDir, microBuildCorePath: "MicroBuildCorePath", testSign: true, msBuildPath: null, 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, task.Log);
var configuration = new Configuration(signToolArgs.TempDir, itemsToSign, strongNameSignInfo, fileSignInfo, extensionsSignInfo, dualCertificates, tarToolPath: s_tarToolPath, pkgToolPath: s_pkgToolPath, task.Log);
var signingInput = configuration.GenerateListOfFiles();
var util = new BatchSignUtil(
task.BuildEngine,
Expand Down Expand Up @@ -363,7 +376,7 @@ private void ValidateFileSignInfos(
{
var engine = new FakeBuildEngine();
var task = new SignToolTask { BuildEngine = engine };
var signingInput = new Configuration(_tmpDir, itemsToSign, strongNameSignInfo, fileSignInfo, extensionsSignInfo, dualCertificates, tarToolPath: s_tarToolPath, task.Log).GenerateListOfFiles();
var signingInput = new Configuration(_tmpDir, itemsToSign, strongNameSignInfo, fileSignInfo, extensionsSignInfo, dualCertificates, tarToolPath: s_tarToolPath, pkgToolPath: s_pkgToolPath, 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<string>());
Expand All @@ -381,7 +394,7 @@ public void EmptySigningList()
var fileSignInfo = new Dictionary<ExplicitCertificateKey, string>();

var task = new SignToolTask { BuildEngine = new FakeBuildEngine() };
var signingInput = new Configuration(_tmpDir, itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, null, tarToolPath: s_tarToolPath, task.Log).GenerateListOfFiles();
var signingInput = new Configuration(_tmpDir, itemsToSign, strongNameSignInfo, fileSignInfo, s_fileExtensionSignInfo, null, tarToolPath: s_tarToolPath, pkgToolPath: s_pkgToolPath, task.Log).GenerateListOfFiles();

signingInput.FilesToSign.Should().BeEmpty();
signingInput.ZipDataMap.Should().BeEmpty();
Expand All @@ -402,7 +415,8 @@ public void EmptySigningListForTask()
TestSign = true,
MSBuildPath = CreateTestResource("msbuild.fake"),
DotNetPath = CreateTestResource("dotnet.fake"),
SNBinaryPath = CreateTestResource("fake.sn.exe")
SNBinaryPath = CreateTestResource("fake.sn.exe"),
PkgToolPath = s_pkgToolPath,
};

task.Execute().Should().BeTrue();
Expand All @@ -424,6 +438,7 @@ public void SignWhenSnExeIsNotRequired()
DotNetPath = CreateTestResource("dotnet.fake"),
DoStrongNameCheck = false,
SNBinaryPath = null,
PkgToolPath = s_pkgToolPath,
};

task.Execute().Should().BeTrue();
Expand Down Expand Up @@ -1049,6 +1064,141 @@ public void SignZipFile()
});
}

[MacOSOnlyFact]
public void SignPkgFile()
{
// List of files to be considered for signing
var itemsToSign = new ITaskItem[]
{
new TaskItem(GetResourcePath("test.pkg"))
};

// Default signing information
var strongNameSignInfo = new Dictionary<string, List<SignInfo>>()
{
{ "581d91ccdfc4ea9c", new List<SignInfo>{ new SignInfo("ArcadeCertTest", "ArcadeStrongTest") } }
};

// Overriding information
var fileSignInfo = new Dictionary<ExplicitCertificateKey, string>();

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=''",
"File 'test.pkg' Certificate=''",
});

// 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[]
{
$@"
<FilesToSign Include=""{Uri.EscapeDataString(Path.Combine(_tmpDir, "ContainerSigning", "3", "Payload/SOS.NETCore.dll"))}"">
<Authenticode>Microsoft400</Authenticode>
</FilesToSign>
<FilesToSign Include=""{Uri.EscapeDataString(Path.Combine(_tmpDir, "ContainerSigning", "4", "Payload/NativeLibrary.dll"))}"">
<Authenticode>Microsoft400</Authenticode>
</FilesToSign>
<FilesToSign Include=""{Uri.EscapeDataString(Path.Combine(_tmpDir, "ContainerSigning", "5", "Payload/this_is_a_big_folder_name_look/this_is_an_even_more_longer_folder_name/but_this_one_is_ever_longer_than_the_previous_other_two/Nested.SOS.NETCore.dll"))}"">
<Authenticode>Microsoft400</Authenticode>
</FilesToSign>
<FilesToSign Include=""{Uri.EscapeDataString(Path.Combine(_tmpDir, "ContainerSigning", "6", "Payload/this_is_a_big_folder_name_look/this_is_an_even_more_longer_folder_name/but_this_one_is_ever_longer_than_the_previous_other_two/Nested.NativeLibrary.dll"))}"">
<Authenticode>Microsoft400</Authenticode>
</FilesToSign>
"
});
}

[MacOSOnlyFact]
public void SignNestedPkgFile()
{
// List of files to be considered for signing
var itemsToSign = new ITaskItem[]
{
new TaskItem( GetResourcePath("NestedPkg.pkg"))
};

// Default signing information
var strongNameSignInfo = new Dictionary<string, List<SignInfo>>()
{
{ "581d91ccdfc4ea9c", new List<SignInfo>{ new SignInfo("ArcadeCertTest", "ArcadeStrongTest") } }
};

// Overriding information
var fileSignInfo = new Dictionary<ExplicitCertificateKey, string>();

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=''",
});

// 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[]
{
$@"
<FilesToSign Include=""{Uri.EscapeDataString(Path.Combine(_tmpDir, "ContainerSigning", "2", "Payload/SOS.NETCore.dll"))}"">
<Authenticode>Microsoft400</Authenticode>
</FilesToSign>
<FilesToSign Include=""{Uri.EscapeDataString(Path.Combine(_tmpDir, "ContainerSigning", "3", "Payload/NativeLibrary.dll"))}"">
<Authenticode>Microsoft400</Authenticode>
</FilesToSign>
<FilesToSign Include=""{Uri.EscapeDataString(Path.Combine(_tmpDir, "ContainerSigning", "4", "Payload/this_is_a_big_folder_name_look/this_is_an_even_more_longer_folder_name/but_this_one_is_ever_longer_than_the_previous_other_two/Nested.SOS.NETCore.dll"))}"">
<Authenticode>Microsoft400</Authenticode>
</FilesToSign>
<FilesToSign Include=""{Uri.EscapeDataString(Path.Combine(_tmpDir, "ContainerSigning", "5", "Payload/this_is_a_big_folder_name_look/this_is_an_even_more_longer_folder_name/but_this_one_is_ever_longer_than_the_previous_other_two/Nested.NativeLibrary.dll"))}"">
<Authenticode>Microsoft400</Authenticode>
</FilesToSign>
"
});
}

[MacOSOnlyFact]
public void SignPkgFileWithApp()
{
// List of files to be considered for signing
var itemsToSign = new ITaskItem[]
{
new TaskItem( GetResourcePath("WithApp.pkg"))
};

// Default signing information
var strongNameSignInfo = new Dictionary<string, List<SignInfo>>()
{
{ "581d91ccdfc4ea9c", new List<SignInfo>{ new SignInfo("ArcadeCertTest", "ArcadeStrongTest") } }
};

// Overriding information
var fileSignInfo = new Dictionary<ExplicitCertificateKey, string>();

// 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=''",
"File 'WithApp.pkg' Certificate=''",
});

// 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[]
{
$@"
<FilesToSign Include=""{Uri.EscapeDataString(Path.Combine(_tmpDir, "ContainerSigning", "4", "Contents/Resources/libexample.dylib"))}"">
<Authenticode>DylibCertificate</Authenticode>
</FilesToSign>
"
});
}

[Fact]
public void SignTarGZipFile()
{
Expand Down Expand Up @@ -1941,6 +2091,7 @@ public void ValidateSignToolTaskParsing()
DoStrongNameCheck = false,
SNBinaryPath = null,
TarToolPath = s_tarToolPath,
PkgToolPath = s_pkgToolPath,
};

task.Execute().Should().BeTrue();
Expand Down Expand Up @@ -2216,6 +2367,7 @@ public void MissingCertificateName(string extension)
new Dictionary<string, List<SignInfo>>(),
new ITaskItem[0],
tarToolPath: s_tarToolPath,
pkgToolPath: s_pkgToolPath,
task.Log)
.GenerateListOfFiles();

Expand Down Expand Up @@ -2252,6 +2404,7 @@ public void MissingCertificateNameButExtensionIsIgnored(string extension)
new Dictionary<string, List<SignInfo>>() { { extension, new List<SignInfo> { SignInfo.Ignore } } },
new ITaskItem[0],
tarToolPath: s_tarToolPath,
pkgToolPath: s_pkgToolPath,
task.Log)
.GenerateListOfFiles();

Expand Down
6 changes: 3 additions & 3 deletions src/Microsoft.DotNet.SignTool/src/BatchSignUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,12 +278,12 @@ void repackContainer(FileSignInfo file)
if (file.IsZipContainer())
{
_log.LogMessage($"Repacking 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 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
{
Expand Down Expand Up @@ -562,7 +562,7 @@ private void VerifyAfterSign(FileSignInfo file)
var zipData = _batchData.ZipDataMap[file.FileContentKey];
bool signedContainer = false;

foreach (var (relativeName, _, _) in ZipData.ReadEntries(file.FullPath, _signTool.TempDir, _signTool.TarToolPath, ignoreContent: true))
foreach (var (relativeName, _, _) in ZipData.ReadEntries(file.FullPath, _signTool.TempDir, _signTool.TarToolPath, _signTool.PkgToolPath, ignoreContent: true))
{
if (!SkipZipContainerSignatureMarkerCheck)
{
Expand Down
Loading
Loading