+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-
-namespace Microsoft.NET.HostModel.AppHost
-{
- /// <summary>
- /// The application host executable cannot be customized because adding resources requires
- /// that the build be performed on Windows (excluding Nano Server).
- /// </summary>
- public class AppHostCustomizationUnsupportedOSException : AppHostUpdateException
- {
- }
-}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.NET.HostModel.AppHost
+{
+ /// <summary>
+ /// An instance of this exception is thrown when an AppHost binary update
+ /// fails due to known user errors.
+ /// </summary>
+ public class AppHostUpdateException : Exception
+ {
+ internal AppHostUpdateException(string message = null)
+ : base(message)
+ {
+ }
+ }
+
+ /// <summary>
+ /// The application host executable cannot be customized because adding resources requires
+ /// that the build be performed on Windows (excluding Nano Server).
+ /// </summary>
+ public sealed class AppHostCustomizationUnsupportedOSException : AppHostUpdateException
+ {
+ internal AppHostCustomizationUnsupportedOSException()
+ {
+ }
+ }
+
+ /// <summary>
+ /// The MachO application host executable cannot be customized because
+ /// it was not in the expected format
+ /// </summary>
+ public sealed class AppHostMachOFormatException : AppHostUpdateException
+ {
+ public readonly MachOFormatError Error;
+
+ internal AppHostMachOFormatException(MachOFormatError error)
+ {
+ Error = error;
+ }
+ }
+
+ /// <summary>
+ /// Unable to use the input file as application host executable because it's not a
+ /// Windows executable for the CUI (Console) subsystem.
+ /// </summary>
+ public sealed class AppHostNotCUIException : AppHostUpdateException
+ {
+ internal AppHostNotCUIException()
+ {
+ }
+ }
+
+ /// <summary>
+ /// Unable to use the input file as an application host executable
+ /// because it's not a Windows PE file
+ /// </summary>
+ public sealed class AppHostNotPEFileException : AppHostUpdateException
+ {
+ internal AppHostNotPEFileException()
+ {
+ }
+ }
+
+ /// <summary>
+ /// Unable to sign the apphost binary.
+ /// </summary>
+ public sealed class AppHostSigningException : AppHostUpdateException
+ {
+ public readonly int ExitCode;
+
+ internal AppHostSigningException(int exitCode, string signingErrorMessage)
+ : base(signingErrorMessage)
+ {
+ }
+ }
+
+ /// <summary>
+ /// Given app file name is longer than 1024 bytes
+ /// </summary>
+ public sealed class AppNameTooLongException : AppHostUpdateException
+ {
+ public string LongName { get; }
+
+ internal AppNameTooLongException(string name)
+ {
+ LongName = name;
+ }
+ }
+}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-
-namespace Microsoft.NET.HostModel.AppHost
-{
- /// <summary>
- /// Unable to use the input file as application host executable because it's not a
- /// Windows executable for the CUI (Console) subsystem.
- /// </summary>
- public class AppHostNotCUIException : AppHostUpdateException
- {
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-
-namespace Microsoft.NET.HostModel.AppHost
-{
- /// <summary>
- /// Unable to use the input file as an application host executable
- /// because it's not a Windows PE file
- /// </summary>
- public class AppHostNotPEFileException : AppHostUpdateException
- {
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-
-namespace Microsoft.NET.HostModel.AppHost
-{
- /// <summary>
- /// An instance of this exception is thrown when an AppHost binary update
- /// fails due to known user errors.
- /// </summary>
- public class AppHostUpdateException : Exception
- {
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-
-namespace Microsoft.NET.HostModel.AppHost
-{
- /// <summary>
- /// Given app file name is longer than 1024 bytes
- /// </summary>
- public class AppNameTooLongException : AppHostUpdateException
- {
- public string LongName { get; }
- public AppNameTooLongException(string name)
- {
- LongName = name;
- }
-
- }
-}
using System;
using System.ComponentModel;
+using System.Diagnostics;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
/// <param name="appBinaryFilePath">Full path to app binary or relative path to the result apphost file</param>
/// <param name="windowsGraphicalUserInterface">Specify whether to set the subsystem to GUI. Only valid for PE apphosts.</param>
/// <param name="assemblyToCopyResorcesFrom">Path to the intermediate assembly, used for copying resources to PE apphosts.</param>
+ /// <param name="enableMacOSCodeSign">Sign the app binary using codesign with an anonymous certificate.</param>
public static void CreateAppHost(
string appHostSourceFilePath,
string appHostDestinationFilePath,
string appBinaryFilePath,
bool windowsGraphicalUserInterface = false,
- string assemblyToCopyResorcesFrom = null)
+ string assemblyToCopyResorcesFrom = null,
+ bool enableMacOSCodeSign = false)
{
var bytesToWrite = Encoding.UTF8.GetBytes(appBinaryFilePath);
if (bytesToWrite.Length > 1024)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not set file permission {filePermissionOctal} for {appHostDestinationFilePath}.");
}
+
+ if (enableMacOSCodeSign && RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ CodeSign(appHostDestinationFilePath);
}
}
catch (Exception ex)
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,
+ };
+
+ 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);
}
-// 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;
-
namespace Microsoft.NET.HostModel.AppHost
{
/// <summary>
InvalidUTF8, // UTF8 decoding failed
SignNotRemoved, // Signature not removed from the host (while processing a single-file bundle)
}
-
- /// <summary>
- /// The MachO application host executable cannot be customized because
- /// it was not in the expected format
- /// </summary>
- public class AppHostMachOFormatException : AppHostUpdateException
- {
- public readonly MachOFormatError Error;
-
- public AppHostMachOFormatException(MachOFormatError error)
- {
- Error = error;
- }
- }
}
using Xunit;
using Microsoft.NET.HostModel.AppHost;
using Microsoft.DotNet.CoreSetup.Test;
+using System.Diagnostics;
namespace Microsoft.NET.HostModel.Tests
{
}
}
+ [Theory]
+ [PlatformSpecific(TestPlatforms.OSX)]
+ [InlineData("")]
+ [InlineData("dir with spaces")]
+ public void CanCodeSignAppHostOnMacOS(string subdir)
+ {
+ using (TestDirectory testDirectory = TestDirectory.Create(subdir))
+ {
+ string sourceAppHostMock = PrepareAppHostMockFile(testDirectory);
+ File.SetAttributes(sourceAppHostMock, FileAttributes.ReadOnly);
+ string destinationFilePath = Path.Combine(testDirectory.Path, "DestinationAppHost.exe.mock");
+ string appBinaryFilePath = "Test/App/Binary/Path.dll";
+ HostWriter.CreateAppHost(
+ sourceAppHostMock,
+ destinationFilePath,
+ appBinaryFilePath,
+ windowsGraphicalUserInterface: false,
+ enableMacOSCodeSign: true);
+
+ const string codesign = @"/usr/bin/codesign";
+ var psi = new ProcessStartInfo()
+ {
+ Arguments = $"-d \"{destinationFilePath}\"",
+ FileName = codesign,
+ RedirectStandardError = true,
+ };
+
+ using (var p = Process.Start(psi))
+ {
+ p.Start();
+ p.StandardError.ReadToEnd()
+ .Should().Contain($"Executable=/private{Path.GetFullPath(destinationFilePath)}");
+ p.WaitForExit();
+ // Successfully signed the apphost.
+ Assert.True(p.ExitCode == 0, $"Expected exit code was '0' but '{codesign}' returned '{p.ExitCode}' instead.");
+ }
+ }
+ }
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.OSX)]
+ public void ItDoesNotCodeSignAppHostByDefault()
+ {
+ using (TestDirectory testDirectory = TestDirectory.Create())
+ {
+ string sourceAppHostMock = PrepareAppHostMockFile(testDirectory);
+ File.SetAttributes(sourceAppHostMock, FileAttributes.ReadOnly);
+ string destinationFilePath = Path.Combine(testDirectory.Path, "DestinationAppHost.exe.mock");
+ string appBinaryFilePath = "Test/App/Binary/Path.dll";
+ HostWriter.CreateAppHost(
+ sourceAppHostMock,
+ destinationFilePath,
+ appBinaryFilePath,
+ windowsGraphicalUserInterface: false);
+
+ const string codesign = @"/usr/bin/codesign";
+ var psi = new ProcessStartInfo()
+ {
+ Arguments = $"-d {destinationFilePath}",
+ FileName = codesign,
+ RedirectStandardError = true,
+ };
+
+ using (var p = Process.Start(psi))
+ {
+ p.Start();
+ p.StandardError.ReadToEnd()
+ .Should().Contain($"{Path.GetFullPath(destinationFilePath)}: code object is not signed at all");
+ p.WaitForExit();
+ }
+ }
+ }
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.OSX)]
+ public void CodeSigningFailuresThrow()
+ {
+ using (TestDirectory testDirectory = TestDirectory.Create())
+ {
+ string sourceAppHostMock = PrepareAppHostMockFile(testDirectory);
+ File.SetAttributes(sourceAppHostMock, FileAttributes.ReadOnly);
+ string destinationFilePath = Path.Combine(testDirectory.Path, "DestinationAppHost.exe.mock");
+ string appBinaryFilePath = "Test/App/Binary/Path.dll";
+ HostWriter.CreateAppHost(
+ sourceAppHostMock,
+ destinationFilePath,
+ appBinaryFilePath,
+ windowsGraphicalUserInterface: false,
+ enableMacOSCodeSign: true);
+
+ // Run CreateAppHost again to sign the apphost a second time,
+ // causing codesign to fail.
+ var exception = Assert.Throws<AppHostSigningException>(() =>
+ HostWriter.CreateAppHost(
+ sourceAppHostMock,
+ destinationFilePath,
+ appBinaryFilePath,
+ windowsGraphicalUserInterface: false,
+ enableMacOSCodeSign: true));
+ Assert.Contains($"{destinationFilePath}: is already signed", exception.Message);
+ }
+ }
+
private string PrepareAppHostMockFile(TestDirectory testDirectory, Action<byte[]> customize = null)
{
// For now we're testing the AppHost on Windows PE files only.
Directory.CreateDirectory(path);
}
- public static TestDirectory Create([CallerMemberName] string callingMethod = "")
+ public static TestDirectory Create([CallerMemberName] string callingMethod = "", string subDir = "")
{
string path = System.IO.Path.Combine(
System.IO.Path.GetTempPath(),
- "dotNetSdkUnitTest_" + callingMethod + (Guid.NewGuid().ToString().Substring(0, 8)));
+ "dotNetSdkUnitTest_" + callingMethod + (Guid.NewGuid().ToString().Substring(0, 8)),
+ subDir);
return new TestDirectory(path);
}