问题现象

应用使用正确的证书签名并使用 Apple 推荐的新公证手段公证后,将应用分发给其他人使用时,依然提示无法验证开发者,如下图所示:

问题结论

先说结论,各位看到文章可以先检查,如果你的情况并不是这种原因,在参考下面的排查思路。 原因是 CMake 工程使用 Unix Makefile 而未使用 Xcode generator 编译了依赖库导致运行时未能正确校验开发者信息。

排查思路

要验证问题是否解决需要先明确问题如何出现的(重现问题),应用在本地签名、公证后,如果你是通过类似 POPO 的软件内网传输给其他人,macOS 的检查机制是不会生效的,你需要将应用上传到某 Web 服务器后提供给用户下载时才会触发,这里是先要明确的一点。 根据 Apple 官方文档介绍,给出了几个明确的注意事项如使用正确的证书进行签名、启用强化运行时、启用时间戳选项等,参考官方文档。以下为逐一验证几个步骤的过程。

确认签名结果

CI 签名输出没有任何异常:

1
2
/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 公证结果没有任何异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ 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

检查签名

使用如下命令检查应用的签名信息:

1
codesign -vvv --deep --strict /Applications/网易会议.app

输出后结果一切正常,因为结果较多,仅贴一部分,因为不是重点:

1
2
3
4
5
6
7
--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

官方文档:https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/resolving_common_notarization_issues#3087735

检查公证

使用如下命令检查应用是否已经在 Apple 进行公证,输出结果一切正常,accepted 表示已经公证:

1
2
3
➜  ~ spctl --assess --verbose /Applications/网易会议.app
/Applications/网易会议.app: accepted
source=Notarized Developer ID

检查强化运行时及时间戳

给应用签名时明确指定了强化运行时配置文件并启用了时间戳选项,这里排除:

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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