From a44871f9c50b8840a5f8e78f56260681c7a93e0c Mon Sep 17 00:00:00 2001 From: Sam Butcher Date: Fri, 22 Nov 2024 14:31:01 +0000 Subject: [PATCH] Improve Apple code signing (#419) - We remove the `skipIfSigned` functionality from the `AppleCodeSigner` - We add verbose logging to `xcrun` calls if it is enabled We've concluded that it is not the responsibility of a maintainer of an open-source library to sign their code for MacOS installers, as such we want to sign these packages ourselves. This may cause us to assume responsibility for issues in our application caused by these packages; this seems reasonable. We have previously encountered issues when releasing `typedb-studio` due to a package that was signed, but not specifically for MacOS installers, failing Apple's checks. As such, we want to sign *all* packages captured by our `deepSignJarsRegex` - not just unsigned ones. This ensures that all signatures are valid. However, we still value the `deepSignJarsRegex` to avoid unnecessarily unpackaging and re-signing every single jar we depend on. We remove the `skipIfSigned` argument and the functionality gated behind it. We also fix an issue where the `verbose` logging flag was not being correctly propagated to our code signing commands. --- common/shell/Shell.kt | 4 +-- platform/jvm/AppleCodeSigner.kt | 20 ++------------- platform/jvm/JVMPlatformAssembler.kt | 6 +++-- platform/jvm/MacAppNotarizer.kt | 38 ++++++++++++++++++---------- 4 files changed, 32 insertions(+), 36 deletions(-) diff --git a/common/shell/Shell.kt b/common/shell/Shell.kt index 026a4317..9cdf2837 100644 --- a/common/shell/Shell.kt +++ b/common/shell/Shell.kt @@ -38,8 +38,8 @@ class Shell(private val logger: Logger, private val verbose: Boolean = false, pr return verbose && (!sensitive || printSensitiveData) } - class Command(vararg args: Argument) { - val args = args.toList() + class Command(val args: List) { + constructor(vararg args: Argument): this(args.toList()) override fun toString(): String { return args.toString() diff --git a/platform/jvm/AppleCodeSigner.kt b/platform/jvm/AppleCodeSigner.kt index dcd0314f..45f0ca1c 100644 --- a/platform/jvm/AppleCodeSigner.kt +++ b/platform/jvm/AppleCodeSigner.kt @@ -7,12 +7,7 @@ import com.vaticle.bazel.distribution.platform.jvm.AppleCodeSigner.Codesign.Args import com.vaticle.bazel.distribution.platform.jvm.AppleCodeSigner.Codesign.Args.KEYCHAIN import com.vaticle.bazel.distribution.platform.jvm.AppleCodeSigner.Codesign.Args.OPTIONS import com.vaticle.bazel.distribution.platform.jvm.AppleCodeSigner.Codesign.Args.SIGN -import com.vaticle.bazel.distribution.platform.jvm.AppleCodeSigner.Codesign.Args.STRICT import com.vaticle.bazel.distribution.platform.jvm.AppleCodeSigner.Codesign.Args.TIMESTAMP -import com.vaticle.bazel.distribution.platform.jvm.AppleCodeSigner.Codesign.Args.VERIFY -import com.vaticle.bazel.distribution.platform.jvm.AppleCodeSigner.Paths.CONTENTS -import com.vaticle.bazel.distribution.platform.jvm.AppleCodeSigner.Paths.MAC_OS -import com.vaticle.bazel.distribution.platform.jvm.AppleCodeSigner.Paths.RUNTIME import com.vaticle.bazel.distribution.platform.jvm.AppleCodeSigner.Paths.TMP import com.vaticle.bazel.distribution.platform.jvm.AppleCodeSigner.Security.CN import com.vaticle.bazel.distribution.platform.jvm.AppleCodeSigner.Security.CREATE_KEYCHAIN @@ -140,7 +135,7 @@ class AppleCodeSigner(private val shell: Shell, private val macEntitlements: Fil val nativeLibs = tmpDir.listFilesRecursively().filter { it.extension in listOf(JNILIB, DYLIB) } if (nativeLibs.isNotEmpty()) { - nativeLibs.forEach { signFile(file = it, skipIfSigned = true) } + nativeLibs.forEach { signFile(file = it) } jar.setWritable(true) jar.delete() shell.execute(listOf(ShellArgs.Programs.JAR, "cMf", "../${jar.path}", "."), baseDir = tmpPath) @@ -150,18 +145,7 @@ class AppleCodeSigner(private val shell: Shell, private val macEntitlements: Fil } } - fun signFile(file: File, skipIfSigned: Boolean = false) { - if (skipIfSigned) { - val verifySignatureResult = VerifySignatureResult( - shell.execute(listOf(CODESIGN, VERIFY, STRICT, file.path), throwOnError = false) - ) - if (verifySignatureResult.status == VerifySignatureResult.Status.SIGNED) return - else if (verifySignatureResult.status == VerifySignatureResult.Status.ERROR) { - throw IllegalStateException("Command '${CODESIGN}' failed with exit code " + - "${verifySignatureResult.exitValue} and output: ${verifySignatureResult.outputString()}") - } - } - + fun signFile(file: File) { file.setWritable(true) val signCommand: MutableList = mutableListOf( CODESIGN, SIGN, certSubject, diff --git a/platform/jvm/JVMPlatformAssembler.kt b/platform/jvm/JVMPlatformAssembler.kt index fb66815e..753dab3a 100644 --- a/platform/jvm/JVMPlatformAssembler.kt +++ b/platform/jvm/JVMPlatformAssembler.kt @@ -230,8 +230,10 @@ object JVMPlatformAssembler { null -> logger.debug { "Skipping notarizing step: Apple code signing is not enabled" } else -> { MacAppNotarizer( - dmgPath = Path.of(distDir.path, "${options.image.filename}-$version.dmg") - ).notarize(codeSigningOptions) + dmgPath = Path.of(distDir.path, "${options.image.filename}-$version.dmg"), + appleCodeSigning = codeSigningOptions, + logging = options.logging, + ).notarize() appleCodeSigner!!.deleteKeychain() } } diff --git a/platform/jvm/MacAppNotarizer.kt b/platform/jvm/MacAppNotarizer.kt index 57f0c720..81a6b003 100644 --- a/platform/jvm/MacAppNotarizer.kt +++ b/platform/jvm/MacAppNotarizer.kt @@ -1,6 +1,10 @@ package com.vaticle.bazel.distribution.platform.jvm +import com.vaticle.bazel.distribution.common.Logging.LogLevel.DEBUG +import com.vaticle.bazel.distribution.common.Logging.LogLevel.ERROR +import com.vaticle.bazel.distribution.common.Logging.Logger import com.vaticle.bazel.distribution.common.shell.Shell +import com.vaticle.bazel.distribution.common.shell.Shell.Command.Companion.arg import com.vaticle.bazel.distribution.platform.jvm.JVMPlatformAssembler.shell import com.vaticle.bazel.distribution.platform.jvm.MacAppNotarizer.Args.APPLE_ID import com.vaticle.bazel.distribution.platform.jvm.MacAppNotarizer.Args.NOTARYTOOL @@ -11,29 +15,34 @@ import com.vaticle.bazel.distribution.platform.jvm.MacAppNotarizer.Args.STAPLER import com.vaticle.bazel.distribution.platform.jvm.MacAppNotarizer.Args.SUBMIT import com.vaticle.bazel.distribution.platform.jvm.MacAppNotarizer.Args.TEAM_ID import com.vaticle.bazel.distribution.platform.jvm.MacAppNotarizer.Args.TIMEOUT +import com.vaticle.bazel.distribution.platform.jvm.MacAppNotarizer.Args.VERBOSE import com.vaticle.bazel.distribution.platform.jvm.MacAppNotarizer.Args.WAIT import com.vaticle.bazel.distribution.platform.jvm.ShellArgs.Programs.XCRUN import java.nio.file.Path -class MacAppNotarizer(private val dmgPath: Path) { - fun notarize(appleCodeSigning: Options.AppleCodeSigning) { - shell.execute(notarizeCommand(appleCodeSigning)).outputString() +class MacAppNotarizer( + private val dmgPath: Path, appleCodeSigning: Options.AppleCodeSigning, private val logging: Options.Logging +) { + private val logger = Logger(logLevel = if (logging.verbose) DEBUG else ERROR) + + fun notarize() { + shell.execute(notarizeCommand).outputString() + logger.debug { "\nUse `xcrun notarytool log ` to view further information about this notarization\n" } markPackageAsApproved() } - private fun notarizeCommand(appleCodeSigning: Options.AppleCodeSigning): Shell.Command { - return Shell.Command( - Shell.Command.arg(XCRUN), Shell.Command.arg(NOTARYTOOL), Shell.Command.arg(SUBMIT), - Shell.Command.arg(APPLE_ID), Shell.Command.arg(appleCodeSigning.appleID), - Shell.Command.arg(PASSWORD), Shell.Command.arg(appleCodeSigning.appleIDPassword, printable = false), - Shell.Command.arg(TEAM_ID), Shell.Command.arg(appleCodeSigning.appleTeamID, printable = false), - Shell.Command.arg(WAIT), Shell.Command.arg(TIMEOUT), Shell.Command.arg(ONE_HOUR), - Shell.Command.arg(dmgPath.toString()), - ) - } + private val notarizeCommand = Shell.Command(listOfNotNull( + arg(XCRUN), arg(NOTARYTOOL), arg(SUBMIT), + if (logging.verbose) arg(VERBOSE) else null, + arg(APPLE_ID), arg(appleCodeSigning.appleID), + arg(PASSWORD), arg(appleCodeSigning.appleIDPassword, printable = false), + arg(TEAM_ID), arg(appleCodeSigning.appleTeamID, printable = false), + arg(WAIT), arg(TIMEOUT), arg(ONE_HOUR), + arg(dmgPath.toString()), + )) private fun markPackageAsApproved() { - shell.execute(listOf(XCRUN, STAPLER, STAPLE, dmgPath.toString())) + shell.execute(listOfNotNull(XCRUN, STAPLER, STAPLE, if (logging.verbose) VERBOSE else null, dmgPath.toString())) } private object Args { @@ -46,6 +55,7 @@ class MacAppNotarizer(private val dmgPath: Path) { const val SUBMIT = "submit" const val TIMEOUT = "--timeout" const val TEAM_ID = "--team-id" + const val VERBOSE = "-v" const val WAIT = "--wait" } }