应用使用正确的证书签名并使用 Apple 推荐的新公证手段公证后,将应用分发给其他人使用时,依然提示无法验证开发者,如下图所示:
先说结论,各位看到文章可以先检查,如果你的情况并不是这种原因,在参考下面的排查思路。 原因是 CMake 工程使用 Unix Makefile 而未使用 Xcode generator 编译了依赖库导致运行时未能正确校验开发者信息。
要验证问题是否解决需要先明确问题如何出现的(重现问题),应用在本地签名、公证后,如果你是通过类似 POPO 的软件内网传输给其他人,macOS 的检查机制是不会生效的,你需要将应用上传到某 Web 服务器后提供给用户下载时才会触发,这里是先要明确的一点。 根据 Apple 官方文档介绍,给出了几个明确的注意事项如使用正确的证书进行签名、启用强化运行时、启用时间戳选项等,参考官方文档。以下为逐一验证几个步骤的过程。
CI 签名输出没有任何异常:
/Users/yunxin/builds/jFP7tNUz/0/yunxin-app/xkit-desktop/exports/bin/NetEaseMeeting.app: replacing existing signature
/Users/yunxin/builds/jFP7tNUz/0/yunxin-app/xkit-desktop/exports/bin/NetEaseMeeting.app: signed app bundle with Mach-O thin (arm64) [com.netease.nmc.Meeting]
CI 公证结果没有任何异常
$ xcrun notarytool submit ${PACKAGE_NAME} --apple-id 2894220@qq.com --team-id 569GNZ5392 --password hjci-yfif-****-**** --wait
Conducting pre-submission checks for meeting-darwin-arm64-release-3-12-0-586-build-1934739.dmg and initiating connection to the Apple notary service...
Submission ID received
id: 60e58388-1725-4958-8975-04e48b0d9e09
Successfully uploaded file
id: 60e58388-1725-4958-8975-04e48b0d9e09
path: /Users/yunxin/builds/1ns5YV66/0/yunxin-app/xkit-desktop/meeting-darwin-arm64-release-3-12-0-586-build-1934739.dmg
Waiting for processing to complete.
Current status: Accepted....................Processing complete
id: 60e58388-1725-4958-8975-04e48b0d9e09
status: Accepted
$ xcrun stapler staple ${PACKAGE_NAME}
Processing: /Users/yunxin/builds/1ns5YV66/0/yunxin-app/xkit-desktop/meeting-darwin-arm64-release-3-12-0-586-build-1934739.dmg
Processing: /Users/yunxin/builds/1ns5YV66/0/yunxin-app/xkit-desktop/meeting-darwin-arm64-release-3-12-0-586-build-1934739.dmg
The staple and validate action worked!
Job succeeded
使用如下命令检查应用的签名信息:
codesign -vvv --deep --strict /Applications/网易会议.app
输出后结果一切正常,因为结果较多,仅贴一部分,因为不是重点:
--prepared:/Applications/网易会议.app/Contents/Frameworks/nem_hosting_module.framework/Versions/Current/.
--validated:/Applications/网易会议.app/Contents/Frameworks/nem_hosting_module.framework/Versions/Current/.
--prepared:/Applications/网易会议.app/Contents/Frameworks/NetEaseMeetingClient.app
--prepared:/Applications/网易会议.app/Contents/Frameworks/NetEaseMeetingClient.app/Contents/PlugIns/position/libqtposition_positionpoll.dylib
--validated:/Applications/网易会议.app/Contents/Frameworks/NetEaseMeetingClient.app/Contents/PlugIns/position/libqtposition_positionpoll.dylib
--prepared:/Applications/网易会议.app/Contents/Frameworks/NetEaseMeetingClient.app/Contents/PlugIns/position/libqtposition_cl.dylib
--validated:/Applications/网易会议.app/Contents/Frameworks/NetEaseMeetingClient.app/Contents/PlugIns/position/libqtposition_cl.dylib
使用如下命令检查应用是否已经在 Apple 进行公证,输出结果一切正常,accepted 表示已经公证:
➜ ~ spctl --assess --verbose /Applications/网易会议.app
/Applications/网易会议.app: accepted
source=Notarized Developer ID
给应用签名时明确指定了强化运行时配置文件并启用了时间戳选项,这里排除:
COMMAND codesign --entitlements=${CMAKE_SOURCE_DIR}/meeting/bin/NetEaseMeeting.entitlements --timestamp --options=runtime -f -s "06C66D0DDF51A99C6A5C0F65BF9B2ABB5FD409B4" -v ${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}.app --deep
经过以上步骤确认了签名、公证均没有异常。最后还是要对产物进行检查,因为历史版本相同的代码生成的应用是没有问题的,有差异的点只有工程组织方式,由原来的本地 QMake + CMake 全面修改为 CMake,并且依赖的三方库使用 conan 进行管理了。 为了排查差异,依次排除可能有异常的三方库,最后锁定到内部使用的一个名为 roomkit 的库上。当不拷贝该库文件到 App bundle 中时进行签名并公证,对端是可以正常显示是否打开应用的提示可以直接打开,如下所示:
当然 roomkit 是必须要依赖的模块,我们不可能直接移除掉该模块,接下来还是排查 roomkit 模块可能得影响点。
经过对比旧版与新版 Info.plist 文件有一些差异,将旧版 Info.plist 拷贝过来使用后依然有问题,该情况排除。
怀疑 framework 格式有问题导致无法验证开发者信息,随后将 roomkit 产物修改为 dylib 文件,修改后问题依然存在,该情况排除。
新的工程管理方案将 roomkit 使用 conan 管理了,在生成 roomkit 时虽然使用 CMake 驱动,但 generator 使用的是 Unix Makefile。当切换 CMake generator 为 Xcode 后依然没有解决问题。
不使用 conan 管理后,将源代码移动到主工程后该问题消失了,重新编译并签名公证后,对端是可以正常运行该程序的,不会提示无效的开发者。
于是对比基于同一工程和使用 conan 管理的两个打包后的产物,文件大小一致、代码一致、签名无误。当检查组件依赖时发现了端倪,有问题的包中包含很多 LC_RPATH 为本地 conan 缓存的目录,运行 otool -l libroomkit.dylib 后如下所示:
Load command 36
cmd LC_RPATH
cmdsize 144
path /Users/jj.deng/.conan/data/ne_chromium_base/0.3.0-alpha.15/yunxin/testing/package/7912296e46b47bb505a62f9071f79fb8a6b1cef1/lib (offset 12)
Load command 37
cmd LC_RPATH
cmdsize 128
path /Users/jj.deng/.conan/data/alog/1.1.3-alpha.56/yunxin/testing/package/2f2de4e3345f667bb03ed16a03f45c72c978d397/lib (offset 12)
Load command 38
cmd LC_RPATH
cmdsize 120
path /Users/jj.deng/.conan/data/nim/9.10.0/yunxin/testing/package/c03875a8be5fc1f094417d3708316280c7dde200/lib (offset 12)
Load command 39
cmd LC_RPATH
cmdsize 112
path /Users/jj.deng/.conan/data/libevent/2.1.12/_/_/package/98268102ce1d6461fd77de96dbfe521aa4569a60/lib (offset 12)
Load command 40
cmd LC_RPATH
cmdsize 112
path /Users/jj.deng/.conan/data/sqlcipher/4.5.0/_/_/package/05d14d2fa2a4ecf20bb6d2fc8daa0c4823efd82d/lib (offset 12)
Load command 41
cmd LC_RPATH
cmdsize 112
path /Users/jj.deng/.conan/data/libcurl/7.88.1/_/_/package/d699a8117ee89877a5435732a284bd66e73e8db3/lib (offset 12)
Load command 42
cmd LC_RPATH
cmdsize 112
path /Users/jj.deng/.conan/data/jsoncpp/1.9.4/_/_/package/2f2de4e3345f667bb03ed16a03f45c72c978d397/lib (offset 12)
Load command 43
cmd LC_RPATH
cmdsize 112
path /Users/jj.deng/.conan/data/gtest/1.11.0/_/_/package/fb16a498e820fb09d04ff9374a782b5b21da0601/lib (offset 12)
Load command 44
cmd LC_RPATH
cmdsize 112
path /Users/jj.deng/.conan/data/libuv/1.41.1/_/_/package/240c2182163325b213ca6886a7614c8ed2bf1738/lib (offset 12)
Load command 45
cmd LC_RPATH
cmdsize 136
path /Users/jj.deng/.conan/data/tinyNET/0.1.0-21-g1e1d3f15/yunxin/testing/package/7580092a53c9c8b599007449ce80de252bf43516/lib (offset 12)
Load command 46
cmd LC_RPATH
cmdsize 112
path /Users/jj.deng/.conan/data/openssl/1.1.1n/_/_/package/240c2182163325b213ca6886a7614c8ed2bf1738/lib (offset 12)
Load command 47
cmd LC_RPATH
cmdsize 112
path /Users/jj.deng/.conan/data/zlib/1.2.13/_/_/package/240c2182163325b213ca6886a7614c8ed2bf1738/lib (offset 12)
Load command 48
cmd LC_RPATH
cmdsize 136
path /Users/jj.deng/.conan/data/tinySAK/0.1.0-13-g07d29b61/yunxin/testing/package/2f2de4e3345f667bb03ed16a03f45c72c978d397/lib (offset 12)
Load command 49
cmd LC_RPATH
cmdsize 120
path /Users/jj.deng/.conan/data/nertc/5.3.3/yunxin/testing/package/9aa227859c38818147bd790129a73a89bce37027 (offset 12)
而正常可以运行的包是没有这样的问题的,本质的区别在于,当 roomkit 在主工程编译时,会执行 cmake install 流程,install 以后 LC_RPATH 的信息会被清理,而使用 conan 管理的 roomkit 仅仅进行了编译,并没有执行 cmake install。重新修改 conanfile.py 的导出包流程,使用 cmake install 后的产物作为依赖后,该问题消失。修改代码对比:
修改前,我们仅仅进行了 build,并且使用 conan 提供的 package 函数,将 cmake 缓存目录下的文件直接拷贝到了产物输出目录。而修改后,直接在 package 函数中执行cmake.install()这样 cmake 会自动拷贝产物到 package 目录并且删除了原产物的 LC_RPATH。conan 在调用 cmake 初始化包的时候,会自动设置 CMAKE_INSTALL_PREFIX 为 conan 包输出目录,所以这里你不用关心会 install 的目录设置问题。参考 conan 官方文档:https://docs.conan.io/1/howtos/cmake_install.html
至此该问题水落石出,最终还是 Gatekeeper 机制让我们再次踩了个坑,在解决完问题后,我尝试在 Google 中搜索(这个时候 ChatGPT 基本上是一本正经的胡说八道)类似问题,果然找到了与我问题贴近的帖子:https://developer.apple.com/forums/thread/128038