diff --git a/src/Squirrel.CommandLine/OSX/Commands.cs b/src/Squirrel.CommandLine/OSX/Commands.cs index 4108c378b..e001ccd51 100644 --- a/src/Squirrel.CommandLine/OSX/Commands.cs +++ b/src/Squirrel.CommandLine/OSX/Commands.cs @@ -126,19 +126,20 @@ private static void Pack(PackOptions options) // code signing all mach-o binaries if (SquirrelRuntimeInfo.IsOSX && !String.IsNullOrEmpty(options.signAppIdentity) && !String.IsNullOrEmpty(options.notaryProfile)) { - HelperExe.CodeSign(options.signAppIdentity, options.signEntitlements, new []{ appBundlePath }); - HelperExe.AssessCodeSign(appBundlePath); - - // notarize and staple the .app before creating a Squirrel release + HelperExe.CodeSign(options.signAppIdentity, options.signEntitlements, appBundlePath); HelperExe.CreateDittoZip(appBundlePath, zipPath); HelperExe.Notarize(zipPath, options.notaryProfile); - HelperExe.Staple(appBundlePath); - - // re-create the zip from the app with the stapled notarization - File.Delete(zipPath); - HelperExe.CreateDittoZip(appBundlePath, zipPath); + } else if (SquirrelRuntimeInfo.IsOSX && !String.IsNullOrEmpty(options.signAppIdentity)) { + HelperExe.CodeSign(options.signAppIdentity, options.signEntitlements, appBundlePath); + Log.Warn("Package was signed but will not be notarized. Must supply the --notaryProfile option."); } else { Log.Warn("Package will not be signed or notarized. Only supported on OSX with the --signAppIdentity and --notaryProfile options."); + } + + // create a portable zip package from signed/notarized bundle + if (SquirrelRuntimeInfo.IsOSX) { + HelperExe.CreateDittoZip(appBundlePath, zipPath); + } else { EasyZip.CreateZipFromDirectory(zipPath, appBundlePath, nestDirectory: true); } @@ -168,15 +169,18 @@ private static void Pack(PackOptions options) releases.Add(ReleaseEntry.GenerateFromFile(newPkgPath)); ReleaseEntry.WriteReleaseFile(releases, releaseFilePath); - // create installer package and notarize - if (SquirrelRuntimeInfo.IsOSX && !String.IsNullOrEmpty(options.signInstallIdentity) && !String.IsNullOrEmpty(options.notaryProfile)) { + // create installer package, sign and notarize + if (SquirrelRuntimeInfo.IsOSX) { var pkgPath = Path.Combine(releaseDir.FullName, options.packId + ".pkg"); - if (File.Exists(pkgPath)) File.Delete(pkgPath); HelperExe.CreateInstallerPkg(appBundlePath, pkgPath, options.signInstallIdentity); - HelperExe.Notarize(pkgPath, options.notaryProfile); - HelperExe.Staple(pkgPath); + if (!String.IsNullOrEmpty(options.signInstallIdentity) && !String.IsNullOrEmpty(options.notaryProfile)) { + HelperExe.Notarize(pkgPath, options.notaryProfile); + } else { + Log.Warn("Package installer (.pkg) will not be Notarized. " + + "This is supported with the --signInstallIdentity and --notaryProfile arguments."); + } } else { - Log.Warn("Package installer (.pkg) will not be created. Only supported on OSX with the --signInstallIdentity and --notaryProfile options."); + Log.Warn("Package installer (.pkg) will not be created - this is only supported on OSX."); } Log.Info("Done."); diff --git a/src/Squirrel.CommandLine/OSX/HelperExe.cs b/src/Squirrel.CommandLine/OSX/HelperExe.cs index 60dd2765a..e413b4616 100644 --- a/src/Squirrel.CommandLine/OSX/HelperExe.cs +++ b/src/Squirrel.CommandLine/OSX/HelperExe.cs @@ -17,7 +17,7 @@ public static string UpdateMacPath public static string SquirrelEntitlements => FindHelperFile("Squirrel.entitlements"); [SupportedOSPlatform("osx")] - public static void CodeSign(string identity, string entitlements, string[] files) + public static void CodeSign(string identity, string entitlements, string filePath) { if (String.IsNullOrEmpty(entitlements)) { Log.Info("No codesign entitlements provided, using default dotnet entitlements: " + @@ -36,14 +36,21 @@ public static void CodeSign(string identity, string entitlements, string[] files "--deep", "--timestamp", "--options", "runtime", - "--entitlements", entitlements + "--entitlements", entitlements, + filePath }; - - args.AddRange(files); - Log.Info($"Preparing to codesign package..."); + Log.Info($"Beginning codesign for package..."); Console.WriteLine(InvokeAndThrowIfNonZero("codesign", args, null)); + + var args2 = new List { + "--assess", + "-vvvv", + filePath + }; + + Console.WriteLine(InvokeAndThrowIfNonZero("spctl", args2, null)); Log.Info("codesign completed successfully"); } @@ -52,6 +59,8 @@ public static void CodeSign(string identity, string entitlements, string[] files public static void CreateInstallerPkg(string appBundlePath, string pkgOutputPath, string signIdentity) { Log.Info($"Creating installer '.pkg' for app at '{appBundlePath}'"); + + if (File.Exists(pkgOutputPath)) File.Delete(pkgOutputPath); using var _1 = Utility.GetTempDirectory(out var tmp); using var _2 = Utility.GetTempDirectory(out var tmpPayload1); @@ -66,7 +75,7 @@ public static void CreateInstallerPkg(string appBundlePath, string pkgOutputPath var pkgPlistPath = Path.Combine(tmp, "tmp.plist"); InvokeAndThrowIfNonZero("pkgbuild", new[] { "--analyze", "--root", tmpPayload1, pkgPlistPath }, null); InvokeAndThrowIfNonZero("plutil", new[] { "-replace", "BundleIsRelocatable", "-bool", "NO", pkgPlistPath }, null); - + var pkg1Path = Path.Combine(tmpPayload2, "1.pkg"); string[] args1 = { "--root", tmpPayload1, @@ -74,51 +83,41 @@ public static void CreateInstallerPkg(string appBundlePath, string pkgOutputPath "--install-location", "/Applications", pkg1Path, }; - + InvokeAndThrowIfNonZero("pkgbuild", args1, null); // create product package that installs to home dir var distributionPath = Path.Combine(tmp, "distribution.xml"); InvokeAndThrowIfNonZero("productbuild", new[] { "--synthesize", "--package", pkg1Path, distributionPath }, null); - + // disable local system installation and build final package var distXml = File.ReadAllLines(distributionPath).ToList(); distXml.Insert(2, ""); File.WriteAllLines(distributionPath, distXml); - - List args2 = new () { - "--distribution", distributionPath, + + List args2 = new() { + "--distribution", distributionPath, "--package-path", tmpPayload2, pkgOutputPath }; - + if (!String.IsNullOrEmpty(signIdentity)) { args2.Add("--sign"); args2.Add(signIdentity); } else { Log.Warn("No Installer signing identity provided. The '.pkg' will not be signed."); } - + InvokeAndThrowIfNonZero("productbuild", args2, null); - - Log.Info("Installer created successfully"); - } - [SupportedOSPlatform("osx")] - public static void Staple(string filePath) - { - Log.Info($"Stapling Notarization to '{filePath}'"); - var args = new List { - "stapler", "staple", filePath, - }; - Console.WriteLine(InvokeAndThrowIfNonZero("xcrun", args, null)); + Log.Info("Installer created successfully"); } [SupportedOSPlatform("osx")] public static void Notarize(string filePath, string keychainProfileName) { Log.Info($"Preparing to Notarize '{filePath}'. This will upload to Apple and usually takes minutes, but could take hours."); - + var args = new List { "notarytool", "submit", @@ -127,7 +126,7 @@ public static void Notarize(string filePath, string keychainProfileName) "--wait", filePath }; - + var ntresultjson = PlatformUtil.InvokeProcess("xcrun", args, null, CancellationToken.None); Log.Info(ntresultjson.StdOutput); @@ -146,13 +145,17 @@ public static void Notarize(string filePath, string keychainProfileName) var result = PlatformUtil.InvokeProcess("xcrun", logargs, null, CancellationToken.None); Log.Warn(result.StdOutput); } + throw new Exception("Notarization failed: " + ntresultjson.StdOutput); } } catch (JsonReaderException) { throw new Exception("Notarization failed: " + ntresultjson.StdOutput); } - + Log.Info("Notarization completed successfully"); + + Log.Info($"Stapling Notarization to '{filePath}'"); + Console.WriteLine(InvokeAndThrowIfNonZero("xcrun", new[] { "stapler", "staple", filePath }, null)); } private class NotaryToolResult @@ -165,6 +168,8 @@ private class NotaryToolResult [SupportedOSPlatform("osx")] public static void CreateDittoZip(string folder, string outputZip) { + if (File.Exists(outputZip)) File.Delete(outputZip); + var args = new List { "-c", "-k", @@ -178,17 +183,5 @@ public static void CreateDittoZip(string folder, string outputZip) Log.Info($"Creating ditto bundle '{outputZip}'"); InvokeAndThrowIfNonZero("ditto", args, null); } - - [SupportedOSPlatform("osx")] - public static void AssessCodeSign(string filePath) - { - var args = new List { - "--assess", - "-vvvv", - filePath - }; - - Console.WriteLine(InvokeAndThrowIfNonZero("spctl", args, null)); - } } } \ No newline at end of file diff --git a/src/Squirrel.CommandLine/OSX/Options.cs b/src/Squirrel.CommandLine/OSX/Options.cs index e4634016f..2c97d9005 100644 --- a/src/Squirrel.CommandLine/OSX/Options.cs +++ b/src/Squirrel.CommandLine/OSX/Options.cs @@ -38,10 +38,13 @@ public PackOptions() Add("e=|mainExe=", "The file {NAME} of the main executable", v => mainExe = v); Add("i=|icon=", "{PATH} to the .icns file for this bundle", v => icon = v); Add("noDelta", "Skip the generation of delta packages", v => noDelta = true); - Add("signAppIdentity=", "The {SUBJECT} name of the cert to use for app code signing", v => signAppIdentity = v); - Add("signInstallIdentity=", "The {SUBJECT} name of the cert to use for installation packages", v => signInstallIdentity = v); - Add("signEntitlements=", "{PATH} to entitlements file for hardened runtime", v => signEntitlements = v); - Add("notaryProfile=", "{NAME} of profile containing Apple credentials stored with notarytool", v => notaryProfile = v); + + if (SquirrelRuntimeInfo.IsOSX) { + Add("signAppIdentity=", "The {SUBJECT} name of the cert to use for app code signing", v => signAppIdentity = v); + Add("signInstallIdentity=", "The {SUBJECT} name of the cert to use for installation packages", v => signInstallIdentity = v); + Add("signEntitlements=", "{PATH} to entitlements file for hardened runtime", v => signEntitlements = v); + Add("notaryProfile=", "{NAME} of profile containing Apple credentials stored with notarytool", v => notaryProfile = v); + } } public override void Validate()