问题现象
应用使用正确的证书签名并使用 Apple 推荐的新公证手段公证后,将应用分发给其他人使用时,依然提示无法验证开发者,如下图所示:
问题结论
先说结论,各位看到文章可以先检查,如果你的情况并不是这种原因,在参考下面的排查思路。 原因是 CMake 工程使用 Unix Makefile 而未使用 Xcode generator 编译了依赖库导致运行时未能正确校验开发者信息。
排查思路
要验证问题是否解决需要先明确问题如何出现的(重现问题),应用在本地签名、公证后,如果你是通过类似 POPO 的软件内网传输给其他人,macOS 的检查机制是不会生效的,你需要将应用上传到某 Web 服务器后提供给用户下载时才会触发,这里是先要明确的一点。 根据 Apple 官方文档介绍,给出了几个明确的注意事项如使用正确的证书进行签名、启用强化运行时、启用时间戳选项等,参考官方文档。以下为逐一验证几个步骤的过程。
确认签名结果
CI 签名输出没有任何异常:
1 | /Users/yunxin/builds/jFP7tNUz/0/yunxin-app/xkit-desktop/exports/bin/NetEaseMeeting.app: replacing existing signature |
确认公证结果
CI 公证结果没有任何异常
1 | $ xcrun notarytool submit ${PACKAGE_NAME} --apple-id 2894220@qq.com --team-id 569GNZ5392 --password hjci-yfif-****-**** --wait |
检查签名
使用如下命令检查应用的签名信息:
1 | codesign -vvv --deep --strict /Applications/网易会议.app |
输出后结果一切正常,因为结果较多,仅贴一部分,因为不是重点:
1 | --prepared:/Applications/网易会议.app/Contents/Frameworks/nem_hosting_module.framework/Versions/Current/. |
检查公证
使用如下命令检查应用是否已经在 Apple 进行公证,输出结果一切正常,accepted 表示已经公证:
1 | ➜ ~ spctl --assess --verbose /Applications/网易会议.app |
检查强化运行时及时间戳
给应用签名时明确指定了强化运行时配置文件并启用了时间戳选项,这里排除:
1 | 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 文件有一些差异,将旧版 Info.plist 拷贝过来使用后依然有问题,该情况排除。
替换 framework 为 dylib
怀疑 framework 格式有问题导致无法验证开发者信息,随后将 roomkit 产物修改为 dylib 文件,修改后问题依然存在,该情况排除。
检查 CMake generator
新的工程管理方案将 roomkit 使用 conan 管理了,在生成 roomkit 时虽然使用 CMake 驱动,但 generator 使用的是 Unix Makefile。当切换 CMake generator 为 Xcode 后依然没有解决问题。
将工 roomkit 移动到主工程
不使用 conan 管理后,将源代码移动到主工程后该问题消失了,重新编译并签名公证后,对端是可以正常运行该程序的,不会提示无效的开发者。
于是对比基于同一工程和使用 conan 管理的两个打包后的产物,文件大小一致、代码一致、签名无误。当检查组件依赖时发现了端倪,有问题的包中包含很多 LC_RPATH 为本地 conan 缓存的目录,运行 otool -l libroomkit.dylib 后如下所示:
1 | Load command 36 |
而正常可以运行的包是没有这样的问题的,本质的区别在于,当 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