我正在尝试使用
jpackage
实用程序将 Java 应用程序打包为 Mac (Sonoma 14.3.1) 的应用程序包。在Mac终端的bash shell中运行,相关命令是
sudo jpackage \
-i deploy \
-d deploy-mac \
-n "MyApp Name" \
--type app-image \
--main-class "com.my.class.MyClass" \
--main-jar "MyApp.jar" \
--app-version 1.0 \
--copyright "..." \
--description "..." \
--icon "..." \
--vendor "..." \
--mac-app-category "public.app-category.education" \
--verbose \
--mac-sign \
--mac-signing-keychain login \
--mac-signing-key-user-name "XYZ Signing ID"
其中“XYZ 签名 ID”是有效 Apple 开发者 ID 证书的 ID。我目前使用的是 JDK 21。
当我省略最后三个参数(即
--mac-sign
、--mac-signing-keychain
和 --mac-signing-key-user-name
)时,该过程会顺利完成,但这需要大量的事后伏都以确保应用程序运行并被接受苹果的“看门人”。
当包含三个参数时,命令失败并出现神秘错误:
[12:02:04.264] Running /usr/bin/security
[12:02:04.289] Command [PID: 33259]:
/usr/bin/security find-certificate -c Developer ID Application: XYZ Signing ID -a login
[12:02:04.291] Returned: 0
[12:02:04.291] Running /usr/bin/security
[12:02:04.309] Command [PID: 33260]:
/usr/bin/security find-certificate -c Developer ID Application: XYZ Signing ID -a -p login
[12:02:04.309] Returned: 0
[12:02:04.312] Running /usr/bin/openssl
[12:02:04.321] Command [PID: 33261]:
/usr/bin/openssl x509 -noout -subject -in /private/var/folders/_g/dwvgjlm50sl23mp5w7s3cf8c0000gn/T/tempfile17263379665633563715.tmp
[12:02:04.321] Output:
unable to load certificate
140704282142656:error:09FFF06C:PEM routines:CRYPTO_internal:no start line:/AppleInternal/Library/BuildRoots/4e1473ee-9f66-11ee-8daf-cedaeb4cabe2/Library/Caches/com.apple.xbs/Sources/libressl/libressl-3.3/crypto/pem/pem_lib.c:694:Expecting: TRUSTED CERTIFICATE
[12:02:04.322] Returned: 1
[12:02:04.322] java.io.IOException: Command [/usr/bin/openssl, x509, -noout, -subject, -in, /private/var/folders/_g/dwvgjlm50sl23mp5w7s3cf8c0000gn/T/tempfile17263379665633563715.tmp] exited with 1 code
at jdk.jpackage/jdk.jpackage.internal.Executor.executeExpectSuccess(Executor.java:90)
at jdk.jpackage/jdk.jpackage.internal.IOUtils.exec(IOUtils.java:229)
...
[12:02:04.324] jdk.jpackage.internal.PackagerException: Bundler Mac Application Image skipped because of a configuration problem: Signature explicitly requested but no signing certificate found
Advice to fix: Specify a valid mac-signing-key-user-name and mac-signing-keychain
at jdk.jpackage/jdk.jpackage.internal.Arguments.generateBundle(Arguments.java:702)
at jdk.jpackage/jdk.jpackage.internal.Arguments.processArguments(Arguments.java:555)
...
[12:02:04.323] No certificate found matching [Developer ID Application: XYZ Signing ID] using keychain [login]
很明显,签名证书存在并且有效(对 /usr/bin/security 的调用均成功),但对
openssl
的调用失败,并在验证证书时出现临时文件解析错误。更糟糕的是,临时文件(可能包含证书数据)在每次调用时都会发生变化,并在 jpackage
中止之前被删除,这意味着无法检查该文件。我尝试过使用 --temp
参数来指定临时目录,但这不起作用(应用程序行为未更改)。
我不知道是什么原因导致此错误或如何诊断它。我怀疑这是
jpackage
中的错误,但我没有使用大多数 Java 应用程序不常见的任何功能。因此,除非每个人尝试为 Mac 构建基于 Java 的应用程序包时遇到此错误,否则我一定做错了什么。
我的问题是:这个错误的原因是什么,有什么简单的修复或解决方法吗?
万一没有简单的解决方法,我在下面提供了我自己的(更详尽的)解决方法作为答案。
使用 JDK 21,我能够运行应用程序,并使用 Apple 的公证工具使用以下解决方法成功公证应用程序包。
假设 .JAR 文件是
deploy/MyApp.jar
并且 XCode 已安装。另假设“开发者 ID 应用程序:XYZ 签名 ID (ABCDEFGHIJK)”是在本地(即 login
)钥匙串上注册的有效 Apple 开发者 ID 证书。
首先,运行不带
jpackage
参数的 --mac-sign
,即
sudo jpackage \
-i deploy \
-d deploy-mac \
-n "MyApp Name" \
--type app-image \
--main-class "com.my.class.MyClass" \
--main-jar "MyApp.jar" \
--app-version 1.0 \
--copyright "..." \
--description "..." \
--icon "..." \
--vendor "..." \
--mac-app-category "public.app-category.education" \
--verbose
这应该创建
deploy-mac/MyApp Name.app
作为应用程序包。
创建文件
resource/mac-entitlements.plist
,内容为:
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
</dict>
创建bash脚本
resource/notarize-for-mac
(赋予其可执行权限),内容为:
#!/bin/sh
# notarize-for-mac
# Notarize Java executable for Mac distribution.
#
# Batch file must be run with the project directory as the working directory.
# variables
USER_ID="[email protected]"
ENTITLEMENTS_FILE="resource/mac-entitlements.plist"
CERT_ID="Developer ID Application: XYZ Signing ID (ABCDEFGHIJK)"
# derived variables
APP_NAME="MyApp Name"
APP_PATH="deploy-mac/$APP_NAME.app"
ZIP_PATH="deploy-mac/$APP_NAME.zip"
BIN_PATH_MACOS="$APP_PATH/Contents/MacOS"
BIN_PATH_HOMELIB="$APP_PATH/Contents/runtime/Contents/Home/lib"
BIN_PATH_RUN_MACOS="$APP_PATH/Contents/runtime/Contents/MacOS"
# do NOT change the order of these; notarization will fail
BINARIES=(\
"$BIN_PATH_HOMELIB/jspawnhelper" \
"$BIN_PATH_RUN_MACOS/libjli.dylib" \
"$BIN_PATH_MACOS/$APP_NAME" \
)
DYLIBS=(\
"$BIN_PATH_HOMELIB/"*".dylib" \
"$BIN_PATH_HOMELIB/server/"*".dylib" \
"$BIN_PATH_RUN_MACOS/"*".dylib" \
)
if [[ "$1" = "sign" ]]
then
# sign the DLLs first
codesign \
--sign "$CERT_ID" \
--force \
--options runtime \
-vvv "${DYLIBS[@]}"
# sign the binaries; these MUST be signed with --strict so that the
# the Gatekeeper doesn't choke on them; the --entitlements are needed
# because the Gatekeeper doesn't normally allow JIT compiling (thanks
# for nothing, Apple)
codesign \
--sign "$CERT_ID" \
--force \
--strict \
--entitlements "$ENTITLEMENTS_FILE" \
--options runtime \
-vvv "${BINARIES[@]}"
# sign the bundle
codesign \
--sign "$CERT_ID" \
--verify \
--force \
--options runtime \
-vvv "$APP_PATH"
elif [[ "$1" = "setpwd" ]]
then
# load user ID/password information onto keychain
echo "Enter notary tool password: \c"
read NOTARY_PASSWORD
xcrun notarytool store-credentials "notarytool-password" \
--apple-id "$USER_ID" \
--password "$NOTARY_PASSWORD"
elif [[ "$1" = "notarize" ]]
then
# package as .zip file
rm "$ZIP_PATH"
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
# submit app for notarization
xcrun notarytool submit "$ZIP_PATH" \
--keychain-profile "notarytool-password" \
--wait
# if the command returns an error code, use
# xcrun notarytool log <ticket_ID> --keychain-profile "notarytool-password" \
# notary_error_log.json
# to obtain a list of errors to remedy
elif [[ "$1" = "staple" ]]
then
# staple Gatekeeper ticket to signed application
xcrun stapler staple "$APP_PATH"
elif [[ "$1" = "zip" ]]
then
# zip stapled app for distribution
rm "$ZIP_PATH"
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
fi
仅限一次,请致电
./resource/notarize-for-mac setpwd
并输入您的应用程序专用密码,将其加载到本地钥匙串上的“notarytool-password”ID 中。您也可以将密码直接插入脚本中,但如果您需要共享/分发脚本,这是一个坏主意,并且是不好的安全实践。
完成此操作后,每次您想要签署应用程序时,请使用(按顺序):
./resource/notarize-for-mac sign
- 签署应用程序包./resource/notarize-for-mac notarize
- 将捆绑包提交给 Apple 的公证服务./resource/notarize-for-mac staple
- 将公证票钉到捆绑包中(假设公证成功)./resource/notarize-for-mac zip
- 获取压缩版本的捆绑包进行分发
从 JDK 21 开始,这个过程对我有效。