Remove codesign before bundling (#57532)
authorAndy Gocke <angocke@microsoft.com>
Tue, 17 Aug 2021 18:52:10 +0000 (11:52 -0700)
committerGitHub <noreply@github.com>
Tue, 17 Aug 2021 18:52:10 +0000 (11:52 -0700)
* Remove codesign before bundling

* Change tests to sign by default and check for signing

* Use standard .NET style

Fixes #57242

src/installer/managed/Microsoft.NET.HostModel/AppHost/HostWriter.cs
src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs
src/installer/managed/Microsoft.NET.HostModel/HostModelUtils.cs [new file with mode: 0644]
src/installer/tests/Microsoft.NET.HostModel.Tests/Helpers/BundleHelper.cs
src/installer/tests/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleAndRun.cs

index 1ed4019..f17bbbe 100644 (file)
@@ -144,8 +144,14 @@ namespace Microsoft.NET.HostModel.AppHost
                         throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not set file permission {filePermissionOctal} for {appHostDestinationFilePath}.");
                     }
 
-                    if (enableMacOSCodeSign && RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-                        CodeSign(appHostDestinationFilePath);
+                    if (enableMacOSCodeSign && RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && HostModelUtils.IsCodesignAvailable())
+                    {
+                        (int exitCode, string stdErr) = HostModelUtils.RunCodesign("-s -", appHostDestinationFilePath);
+                        if (exitCode != 0)
+                        {
+                            throw new AppHostSigningException(exitCode, stdErr);
+                        }
+                    }
                 }
             }
             catch (Exception ex)
@@ -239,29 +245,6 @@ namespace Microsoft.NET.HostModel.AppHost
             return headerOffset != 0;
         }
 
-        private static void CodeSign(string appHostPath)
-        {
-            Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.OSX));
-            const string codesign = @"/usr/bin/codesign";
-            if (!File.Exists(codesign))
-                return;
-
-            var psi = new ProcessStartInfo()
-            {
-                Arguments = $"-s - \"{appHostPath}\"",
-                FileName = codesign,
-                RedirectStandardError = true,
-                UseShellExecute = false,
-            };
-
-            using (var p = Process.Start(psi))
-            {
-                p.WaitForExit();
-                if (p.ExitCode != 0)
-                    throw new AppHostSigningException(p.ExitCode, p.StandardError.ReadToEnd());
-            }
-        }
-
         [DllImport("libc", SetLastError = true)]
         private static extern int chmod(string pathname, int mode);
     }
index 603ddba..6807356 100644 (file)
@@ -21,17 +21,18 @@ namespace Microsoft.NET.HostModel.Bundle
     {
         public const uint BundlerMajorVersion = 6;
         public const uint BundlerMinorVersion = 0;
+        public readonly Manifest BundleManifest;
 
-        private readonly string HostName;
-        private readonly string OutputDir;
-        private readonly string DepsJson;
-        private readonly string RuntimeConfigJson;
-        private readonly string RuntimeConfigDevJson;
+        private readonly string _hostName;
+        private readonly string _outputDir;
+        private readonly string _depsJson;
+        private readonly string _runtimeConfigJson;
+        private readonly string _runtimeConfigDevJson;
 
-        private readonly Trace Tracer;
-        public readonly Manifest BundleManifest;
-        private readonly TargetInfo Target;
-        private readonly BundleOptions Options;
+        private readonly Trace _tracer;
+        private readonly TargetInfo _target;
+        private readonly BundleOptions _options;
+        private readonly bool _macosCodesign;
 
         public Bundler(string hostName,
                        string outputDir,
@@ -40,32 +41,34 @@ namespace Microsoft.NET.HostModel.Bundle
                        Architecture? targetArch = null,
                        Version targetFrameworkVersion = null,
                        bool diagnosticOutput = false,
-                       string appAssemblyName = null)
+                       string appAssemblyName = null,
+                       bool macosCodesign = true)
         {
-            Tracer = new Trace(diagnosticOutput);
+            _tracer = new Trace(diagnosticOutput);
 
-            HostName = hostName;
-            OutputDir = Path.GetFullPath(string.IsNullOrEmpty(outputDir) ? Environment.CurrentDirectory : outputDir);
-            Target = new TargetInfo(targetOS, targetArch, targetFrameworkVersion);
+            _hostName = hostName;
+            _outputDir = Path.GetFullPath(string.IsNullOrEmpty(outputDir) ? Environment.CurrentDirectory : outputDir);
+            _target = new TargetInfo(targetOS, targetArch, targetFrameworkVersion);
 
-            if (Target.BundleMajorVersion < 6 &&
+            if (_target.BundleMajorVersion < 6 &&
                 (options & BundleOptions.EnableCompression) != 0)
             {
                 throw new ArgumentException("Compression requires framework version 6.0 or above", nameof(options));
             }
 
-            appAssemblyName ??= Target.GetAssemblyName(hostName);
-            DepsJson = appAssemblyName + ".deps.json";
-            RuntimeConfigJson = appAssemblyName + ".runtimeconfig.json";
-            RuntimeConfigDevJson = appAssemblyName + ".runtimeconfig.dev.json";
+            appAssemblyName ??= _target.GetAssemblyName(hostName);
+            _depsJson = appAssemblyName + ".deps.json";
+            _runtimeConfigJson = appAssemblyName + ".runtimeconfig.json";
+            _runtimeConfigDevJson = appAssemblyName + ".runtimeconfig.dev.json";
 
-            BundleManifest = new Manifest(Target.BundleMajorVersion, netcoreapp3CompatMode: options.HasFlag(BundleOptions.BundleAllContent));
-            Options = Target.DefaultOptions | options;
+            BundleManifest = new Manifest(_target.BundleMajorVersion, netcoreapp3CompatMode: options.HasFlag(BundleOptions.BundleAllContent));
+            _options = _target.DefaultOptions | options;
+            _macosCodesign = macosCodesign;
         }
 
         private bool ShouldCompress(FileType type)
         {
-            if (!Options.HasFlag(BundleOptions.EnableCompression))
+            if (!_options.HasFlag(BundleOptions.EnableCompression))
             {
                 return false;
             }
@@ -116,11 +119,11 @@ namespace Microsoft.NET.HostModel.Bundle
 
             if (type == FileType.Assembly)
             {
-                long misalignment = (bundle.Position % Target.AssemblyAlignment);
+                long misalignment = (bundle.Position % _target.AssemblyAlignment);
 
                 if (misalignment != 0)
                 {
-                    long padding = Target.AssemblyAlignment - misalignment;
+                    long padding = _target.AssemblyAlignment - misalignment;
                     bundle.Position += padding;
                 }
             }
@@ -134,12 +137,12 @@ namespace Microsoft.NET.HostModel.Bundle
 
         private bool IsHost(string fileRelativePath)
         {
-            return fileRelativePath.Equals(HostName);
+            return fileRelativePath.Equals(_hostName);
         }
 
         private bool ShouldIgnore(string fileRelativePath)
         {
-            return fileRelativePath.Equals(RuntimeConfigDevJson);
+            return fileRelativePath.Equals(_runtimeConfigDevJson);
         }
 
         private bool ShouldExclude(FileType type, string relativePath)
@@ -152,13 +155,13 @@ namespace Microsoft.NET.HostModel.Bundle
                     return false;
 
                 case FileType.NativeBinary:
-                    return !Options.HasFlag(BundleOptions.BundleNativeBinaries) || Target.ShouldExclude(relativePath);
+                    return !_options.HasFlag(BundleOptions.BundleNativeBinaries) || _target.ShouldExclude(relativePath);
 
                 case FileType.Symbols:
-                    return !Options.HasFlag(BundleOptions.BundleSymbolFiles);
+                    return !_options.HasFlag(BundleOptions.BundleSymbolFiles);
 
                 case FileType.Unknown:
-                    return !Options.HasFlag(BundleOptions.BundleOtherFiles);
+                    return !_options.HasFlag(BundleOptions.BundleOtherFiles);
 
                 default:
                     Debug.Assert(false);
@@ -190,12 +193,12 @@ namespace Microsoft.NET.HostModel.Bundle
 
         private FileType InferType(FileSpec fileSpec)
         {
-            if (fileSpec.BundleRelativePath.Equals(DepsJson))
+            if (fileSpec.BundleRelativePath.Equals(_depsJson))
             {
                 return FileType.DepsJson;
             }
 
-            if (fileSpec.BundleRelativePath.Equals(RuntimeConfigJson))
+            if (fileSpec.BundleRelativePath.Equals(_runtimeConfigJson))
             {
                 return FileType.RuntimeConfigJson;
             }
@@ -211,7 +214,7 @@ namespace Microsoft.NET.HostModel.Bundle
                 return FileType.Assembly;
             }
 
-            bool isNativeBinary = Target.IsWindows ? isPE : Target.IsNativeBinary(fileSpec.SourcePath);
+            bool isNativeBinary = _target.IsWindows ? isPE : _target.IsNativeBinary(fileSpec.SourcePath);
 
             if (isNativeBinary)
             {
@@ -240,10 +243,10 @@ namespace Microsoft.NET.HostModel.Bundle
         /// </exceptions>
         public string GenerateBundle(IReadOnlyList<FileSpec> fileSpecs)
         {
-            Tracer.Log($"Bundler Version: {BundlerMajorVersion}.{BundlerMinorVersion}");
-            Tracer.Log($"Bundle  Version: {BundleManifest.BundleVersion}");
-            Tracer.Log($"Target Runtime: {Target}");
-            Tracer.Log($"Bundler Options: {Options}");
+            _tracer.Log($"Bundler Version: {BundlerMajorVersion}.{BundlerMinorVersion}");
+            _tracer.Log($"Bundle  Version: {BundleManifest.BundleVersion}");
+            _tracer.Log($"Target Runtime: {_target}");
+            _tracer.Log($"Bundler Options: {_options}");
 
             if (fileSpecs.Any(x => !x.IsValid()))
             {
@@ -253,21 +256,26 @@ namespace Microsoft.NET.HostModel.Bundle
             string hostSource;
             try
             {
-                hostSource = fileSpecs.Where(x => x.BundleRelativePath.Equals(HostName)).Single().SourcePath;
+                hostSource = fileSpecs.Where(x => x.BundleRelativePath.Equals(_hostName)).Single().SourcePath;
             }
             catch (InvalidOperationException)
             {
                 throw new ArgumentException("Invalid input specification: Must specify the host binary");
             }
 
-            string bundlePath = Path.Combine(OutputDir, HostName);
+            string bundlePath = Path.Combine(_outputDir, _hostName);
             if (File.Exists(bundlePath))
             {
-                Tracer.Log($"Ovewriting existing File {bundlePath}");
+                _tracer.Log($"Ovewriting existing File {bundlePath}");
             }
 
             BinaryUtils.CopyFile(hostSource, bundlePath);
 
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && HostModelUtils.IsCodesignAvailable())
+            {
+                RemoveCodesignIfNecessary(bundlePath);
+            }
+
             // Note: We're comparing file paths both on the OS we're running on as well as on the target OS for the app
             // We can't really make assumptions about the file systems (even on Linux there can be case insensitive file systems
             // and vice versa for Windows). So it's safer to do case sensitive comparison everywhere.
@@ -290,7 +298,7 @@ namespace Microsoft.NET.HostModel.Bundle
 
                     if (ShouldIgnore(relativePath))
                     {
-                        Tracer.Log($"Ignore: {relativePath}");
+                        _tracer.Log($"Ignore: {relativePath}");
                         continue;
                     }
 
@@ -298,7 +306,7 @@ namespace Microsoft.NET.HostModel.Bundle
 
                     if (ShouldExclude(type, relativePath))
                     {
-                        Tracer.Log($"Exclude [{type}]: {relativePath}");
+                        _tracer.Log($"Exclude [{type}]: {relativePath}");
                         fileSpec.Excluded = true;
                         continue;
                     }
@@ -320,23 +328,50 @@ namespace Microsoft.NET.HostModel.Bundle
 
                     using (FileStream file = File.OpenRead(fileSpec.SourcePath))
                     {
-                        FileType targetType = Target.TargetSpecificFileType(type);
+                        FileType targetType = _target.TargetSpecificFileType(type);
                         (long startOffset, long compressedSize) = AddToBundle(bundle, file, targetType);
-                        FileEntry entry = BundleManifest.AddEntry(targetType, file, relativePath, startOffset, compressedSize, Target.BundleMajorVersion);
-                        Tracer.Log($"Embed: {entry}");
+                        FileEntry entry = BundleManifest.AddEntry(targetType, file, relativePath, startOffset, compressedSize, _target.BundleMajorVersion);
+                        _tracer.Log($"Embed: {entry}");
                     }
                 }
 
                 // Write the bundle manifest
                 headerOffset = BundleManifest.Write(writer);
-                Tracer.Log($"Header Offset={headerOffset}");
-                Tracer.Log($"Meta-data Size={writer.BaseStream.Position - headerOffset}");
-                Tracer.Log($"Bundle: Path={bundlePath}, Size={bundle.Length}");
+                _tracer.Log($"Header Offset={headerOffset}");
+                _tracer.Log($"Meta-data Size={writer.BaseStream.Position - headerOffset}");
+                _tracer.Log($"Bundle: Path={bundlePath}, Size={bundle.Length}");
             }
 
             HostWriter.SetAsBundle(bundlePath, headerOffset);
 
+            // Sign the bundle if requested
+            if (_macosCodesign && RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && HostModelUtils.IsCodesignAvailable())
+            {
+                var (exitCode, stdErr) = HostModelUtils.RunCodesign("-s -", bundlePath);
+                if (exitCode != 0)
+                {
+                    throw new InvalidOperationException($"Failed to codesign '{bundlePath}': {stdErr}");
+                }
+            }
+
             return bundlePath;
+
+            // Remove mac code signature if applied before bundling
+            static void RemoveCodesignIfNecessary(string bundlePath)
+            {
+                Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.OSX));
+                Debug.Assert(HostModelUtils.IsCodesignAvailable());
+
+                // `codesign -v` returns 0 if app is signed
+                if (HostModelUtils.RunCodesign("-v", bundlePath).ExitCode == 0)
+                {
+                    var (exitCode, stdErr) = HostModelUtils.RunCodesign("--remove-signature", bundlePath);
+                    if (exitCode != 0)
+                    {
+                        throw new InvalidOperationException($"Removing codesign from '{bundlePath}' failed: {stdErr}");
+                    }
+                }
+            }
         }
     }
 }
diff --git a/src/installer/managed/Microsoft.NET.HostModel/HostModelUtils.cs b/src/installer/managed/Microsoft.NET.HostModel/HostModelUtils.cs
new file mode 100644 (file)
index 0000000..9b2d79c
--- /dev/null
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.NET.HostModel
+{
+    internal static class HostModelUtils
+    {
+        private const string CodesignPath = @"/usr/bin/codesign";
+
+        public static bool IsCodesignAvailable() => File.Exists(CodesignPath);
+
+        public static (int ExitCode, string StdErr) RunCodesign(string args, string appHostPath)
+        {
+            Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.OSX));
+            Debug.Assert(IsCodesignAvailable());
+
+            var psi = new ProcessStartInfo()
+            {
+                Arguments = $"{args} \"{appHostPath}\"",
+                FileName = CodesignPath,
+                RedirectStandardError = true,
+                UseShellExecute = false,
+            };
+
+            using (var p = Process.Start(psi))
+            {
+                p.WaitForExit();
+                return (p.ExitCode, p.StandardError.ReadToEnd());
+            }
+        }
+    }
+}
\ No newline at end of file
index ecd00f7..351461e 100644 (file)
@@ -195,7 +195,7 @@ namespace BundleTests.Helpers
             var targetOS = GetTargetOS(fixture.CurrentRid);
             var targetArch = GetTargetArch(fixture.CurrentRid);
 
-            var bundler = new Bundler(hostName, bundleDir.FullName, options, targetOS, targetArch, targetFrameworkVersion);
+            var bundler = new Bundler(hostName, bundleDir.FullName, options, targetOS, targetArch, targetFrameworkVersion, macosCodesign: true);
             singleFile = GenerateBundle(bundler, publishPath, bundleDir.FullName, copyExcludedFiles);
 
             return bundler;
index 3b0fe6f..e4e5cc5 100644 (file)
@@ -32,13 +32,10 @@ namespace Microsoft.NET.HostModel.Tests
                 .HaveStdOutContaining("Wow! We now say hello to the big world and you.");
         }
 
-        private void CheckFileNotarizable(string path)
+        private void CheckFileSigned(string path)
         {
-            // attempt to remove signature data.
-            // no-op if the file is not signed (it should not be)
-            // fail if the file structure is malformed
-            // i: input, o: output, r: remove
-            Command.Create("codesign_allocate", $"-i {path} -o {path} -r")
+            // Check if the file is signed (it should have been signed by the bundler)
+            Command.Create("codesign", $"-v {path}")
                 .CaptureStdErr()
                 .CaptureStdOut()
                 .Execute()
@@ -78,7 +75,7 @@ namespace Microsoft.NET.HostModel.Tests
             var targetOS = BundleHelper.GetTargetOS(fixture.CurrentRid);
             if (targetOS == OSPlatform.OSX)
             {
-                CheckFileNotarizable(singleFile);
+                CheckFileSigned(singleFile);
             }
 
             // Run the extracted app
@@ -100,7 +97,6 @@ namespace Microsoft.NET.HostModel.Tests
         }
 
         [Fact]
-        [ActiveIssue("https://github.com/dotnet/runtime/issues/57242", TestPlatforms.OSX)]
         public void TestWithAbsolutePaths()
         {
             var fixture = sharedTestState.TestFixture.Copy();
@@ -109,7 +105,6 @@ namespace Microsoft.NET.HostModel.Tests
         }
 
         [Fact]
-        [ActiveIssue("https://github.com/dotnet/runtime/issues/57242", TestPlatforms.OSX)]
         public void TestWithRelativePaths()
         {
             var fixture = sharedTestState.TestFixture.Copy();
@@ -118,7 +113,6 @@ namespace Microsoft.NET.HostModel.Tests
         }
 
         [Fact]
-        [ActiveIssue("https://github.com/dotnet/runtime/issues/57242", TestPlatforms.OSX)]
         public void TestWithRelativePathsDirSeparator()
         {
             var fixture = sharedTestState.TestFixture.Copy();