在 Xcode 之外对 Mac 应用程序进行数字签名失败

Failure digitally signing a Mac app outside Xcode

我用 Qt5 开发了一个 Mac 应用程序,所以在 Xcode 之外。我希望 GateKeeper 允许我的应用程序在客户端计算机上 运行 而不是发出 "Can't be opened because the identity of the developer cannot be confirmed" 警告。

我已成功对应用程序进行数字签名,但 GateKeeper 仍然收到此投诉。我有 Apple 开发人员证书(我是团队代理),我的钥匙串显示它是有效的。我也安装了两个苹果根证书。

我使用命令行实用程序 codesign 对 app 文件夹内的所有二进制文件进行数字签名,此外我还对 app 文件夹本身进行数字签名。在所有情况下,协同设计的响应都是信息性的并且没有显示错误。使用 codesign 我可以检查所有二进制文件是否都已签名,运行ning

$ codesign --verify --deep --verbose=2 MyApp.app

表明所有 个二进制文件都有效。此外它还报告:

MyApp.app: valid on disk
MyApp.app: satisfies its Designated Requirement

运行:

$ codesign -v --verbose=4 --display MyApp.app   

给予

Executable=/Users/xxx/trunk/yyy/deploy/release/MyApp.app/Contents/MacOS/MyApp
Identifier=aaaa.MyApp
Format=bundle with Mach-O thin (x86_64)
CodeDirectory v=20200 size=12461 flags=0x0(none) hashes=616+3 location=embedded
Hash type=sha1 size=20
CDHash=d1c12c783dac0e8d9a2b749fb896b11558cec8b6
Signature size=8532
Authority=Developer ID Application: XXXXX
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=29 jul. 2015 12;04:40
Info.plist entries=8
TeamIdentifier=YYYYY
Sealed Resources version=2 rules=12 files=10
Internal requirements count=1 size=180

这看起来不错。

运行

$ spctl -a -t exec -vv MyApp.app

on all 二进制文件给出结果

MyApp.app: accepted
source=Developer ID
origin=Developer ID Application: XXXX

这看起来也不错

运行 XCode命令行工具check-signature 在应用程序或应用程序文件夹内的二进制文件上:

$ ./check-signature /Users/xxx/trunk/yyy/release/MyApp.app

给出结果

(c) 2014 Apple Inc. All rights reserved.
YES

所有个案例中,这就是期望的结果。

但GateKeeper仍然不接受该应用,并抱怨无法确认开发者。

[作者于 2015 年 7 月 17 日星期五添加]

我想我找到问题了。我不知道这是功能还是 OSX 错误。 Whosebug question 19551298.

对我帮助很大

每当从 Internet 下载文件时,它都会获得与之关联的扩展文件属性 com.apple.quarantine。在 Finder 中双击这个下载的文件时,GateKeeper 有两种可能:

  1. 当文件未签名时,它会发出 "Unidentified developer etc" 消息

  2. 文件经过数字签名后,它会发出 "Developer cannot be confirmed etc" 消息

在这两种情况下,MessageBox 只有一个按钮,一个确定按钮。单击此按钮时,除了 MessageBox 关闭外,什么也没有发生。

如果扩展属性被删除 (xattr -d) 应用程序 运行s,签名与否。

通过在应用程序的 Finder 中单击鼠标右键启动应用程序,然后单击 "open" 菜单操作,行为会有所不同。再次显示两个消息框之一,但现在有一个额外的按钮以允许用户无论如何打开应用程序。同样,签名和未签名之间的唯一区别是 "Unidentified" 或 "Not confirmed" 消息。我不希望我的客户能够分辨出差异。因此,签署该应用程序是徒劳的。

Apple Support Documentation 的基础上,我期待双击下载的应用程序时 GateKeeper 的另一个更好的行为(可能文档已过时,或者我看错了):

  1. 如果应用程序已签名 GateKeeper 应显示带有 "Downloaded from the internet etc" 的 MessageBox 和带有 "Proceed anyway?"

  2. 的按钮
  3. 如果应用程序未签名,则带有单个确定按钮和文本的 MessageBox "Unidentified developer etc.."

很抱歉回答我自己的问题,但我认为没有其他办法编辑原始问题会导致意大利面条文本。

我终于解决了我的问题。首先是功劳:(i)我的另一个 Whosebug 的答案非常有用,并且(ii)我通过提交所谓的技术支持事件(TSI)从官方 Apple 开发人员那里得到了非常好的(付费)建议).

基于所有这些,我现在可以在这里提供一个非常简洁的方法,说明如何让 GateKeeper 成功处理您的 Mac 应用程序。在详细介绍了食谱之后,我将展示我最初的错误是什么。

Goal: After having developed a Mac app outside Xcode to have GateKeeper issuing the warning "Downloaded from the Internet ..." with three buttons, one of which is "open".

Failure: When GateKeeper issues a warning with either the text ".. unidentified developer.." or the text ".. unconfirmed developer .. " with - in both cases - a messagebox with a single OK button.

让您的应用程序 GateKeeper 就绪包括三个步骤:

  • Make your app standalone with no unacceptable external dependencies. The only acceptable external dependences are system libraries. All other dependencies should have been copied to your MyApp.app folder. GateKeeper rejects any app that has non-system external dependencies
  • Binaries should not be located at illegal positions inside the MyApp.app folder. Libraries go into MyApp/Contents/Frameworks and the executable goes into MyApp/Contents/MacOS
  • All binaries inside MyApp should be digitally signed. Then the MyApp.app folder should be signed. For this signing an Apple "Developer ID Application ..." certificate is necessary

我们的食谱是自动的。所有的工作都由一个脚本完成。对于 Qt Creator,我们使用 qmake 脚本,通过 $$system 命令访问系统 shell。当使用 (Xcode) 系统命令 codesignspctlcheck-signature 中的任何一个时,我们假设您已将 stderr 重定向到 stdout 作为 中概述。否则,当 运行 这些实用程序时,您将无法捕捉到系统响应。在下文中我们不会明确显示此重定向。

这是我们的食谱

A. Making the app stand-alone:

  1. copy (with a script) all the needed binaries to the MyApp.folder
  2. run (with a script) install_name_tool -change and install_name_tool -id such that all dependences inside the app are of the relative type @executable_path/../MacOS.. or @executable_path/../Frameworks
  3. run (with a script) otool -L on all binaries inside the MyApp.app folder and flag any illegal dependence, like "@rpath..." or absolute file paths not being system paths. Note that otool -L is not guaranteed to find all dependencies. Plugins are often beyond the horizon of otool. That is why you need the next check.
  4. start a terminal at the location "MyApp.app/Contents/MacOS". Run export DYLD_PRINT_LIBRARIES=1. Then run inside the same terminal window ./MyApp. Your terminal will fill up with over hundred loaded libraries. Check this list again for forbidden libraries (libraries present on your computer, but not on the computer of your customers).
  5. proof of the pudding is in the eating. We use the MacInCloud virtual machines and check whether or not our app runs there. Alternative solution could be the Mac of a relative who is not a developer. Or you could also create a new user ("test") on your own Mac and copy the app to its Download (or Desktop folder, or ...). In the latter case you must temporarily rename the root folder of your IDE as otherwise the user "test" will find the missing binaries there.

B Signing the app

  1. Signing: With our script we run codesign --force --verify --verbose --sign \"Developer ID Application: ....\" \"/path/to/binary\" on all the binaries in the app and then on the app folder itself. In each case the system response is caught. It should contain in each case the string "signed Mach-O thin".
  2. Verification: Run (with a script) command codesign --verify --verbose \"/path/to/binary\" on each binary in your app and on the app itself and catch the system response. It should in each case contain the strings "valid on disk" and "satisfies its Designated Requirement".
  3. GateKeeper check: Run (with a script) spctl -a -t exec -vv /path/to/binary\" on each binary and on the app folder itself. The system response is caught. It should contain in all cases the string "accepted source".
  4. check-signature: Run (with a script) check-signature \"/path/to/banary\" on each binary and on the app folder itself. The system response is caught. It should contain the string "YES" in each case.

C External check

  1. zip your app into a single zip file. Upload to one of your cloud servers

  2. GateKeepers keeps a long list (typically hundreds of items) of exceptions on its general gate-keeper role. Your app must not be in that list if you want to test GateKeeper. Rather than editing this list a much simpler trick is creating a new user on your Mac. Log in to that user and download the zip file from the Internet cloud server. Finder will automatically uncompress it. Click on it. If GateKeeper tells you that it can open the application but it warns you at the same time that it is downloaded from the Internet, it is time to grab a (white) beer.

此处是所需的 GateKeeper 警告:

我的错误

我做了很多安装和签名,但没有明确检查每个二进制文件的结果。之后,我会对一些二进制文件使用 otool -L 但不是全部。我错过了一个事实,即从早期的 Qt 版本升级到 Qt 5.5 二进制 libqminimal.dylib 已经获得了额外的依赖性,即:QtDBus。我没有注意到它,但 GateKeeper 注意到了。

Qt 开发人员可能想知道为什么我们不直接使用 macdeployqt 在 Mac 上部署 Qt 应用程序。首先,我们不喜欢不使用记录不当的黑盒实用程序。在 Internet 论坛上,有相当多的人报告 macdeployqt 的问题。此外,在比较不同的 Qt 版本时,Qt 库可以有不同的安装位置(如 otool-L 所报告)。当我们有一个新的 Qt 版本时,我们的脚本将立即开始大喊禁止依赖项。通过这种方式,我们可以获得有关此新版本中更改内容的信息。

adlag 的问题和自我回答对帮助我克服同样的问题非常宝贵。不过,虽然他的菜谱很好,但有些说法不太对,所以我想补充几点。

  • 不必用@executable_path 语句替换二进制文件和动态库中的@rpath 条目。只要二进制文件中嵌入的实际 rpath 条目不是绝对的,@rpath 语句就可以。您可以找到大量使用 rpath 的有效 Qt 应用程序包。你可以按照 adlag 所说的去做,但你可能正在为自己工作。
  • 请参阅上面 jil 的评论,了解如何使用 otool -l $file | grep -A2 LC_RPATHinstall_name_tool -delete_rpath $path $file 检查和删除二进制文件和库中的嵌入路径
  • 请参阅 https://developer.apple.com/library/content/technotes/tn2206/_index.html#//apple_ref/doc/uid/DTS40007919-CH1-TNTAG207 了解 GateKeeper 为什么抱怨您的二进制文件中的路径,以及如何在 syslog 中查看具体抱怨的清楚解释。
  • 如果您遇到绝对路径问题,您应该首先尝试修复您的构建,而不是在事后使用 install_name_tool
  • 如果您使用的是 cmake,这可能会有所帮助:https://cmake.org/Wiki/CMake_RPATH_handling#Mac_OS_X_and_the_RPATH
  • 不要 运行 spctl -a -t exec -vv /path/to/binary dylib 文件。您将收到有关资源信封的错误。这是预期的,不是问题。
  • 根据我的经验,macdeployqt 工作正常。我通过更改我的构建解决了这个问题,这样绝对路径就不会进入有问题的 dylib 文件 (libquazip)。我仍然使用 install_name_tool 删除 Qt 安装的绝对路径。然后我使用 macdeployqt 创建捆绑包,签署捆绑包并创建 DMG 文件。

经过多次尝试解决了问题。

以我为例: Pop Message - 损坏的应用程序是由于库丢失而出现的。 我使用 QT 创建了 .app 文件。 为了生成 dmg,我使用的是 deploymacqt 命令工具。 deploymacqt 工具在 .app 中创建动态库,所以基本上如果我们在创建 dmg 之前进行代码签名,这个改变将操纵代码签名。 所以正确的解决方法是。

# Create dmg using 
    deploymacqt <yourapp.app> -dmg

# Open resulted dmg file, copy <yourapp.app> to different folder(let's say /Documents/<yourapp.app>)

# Codesign the /Documents/<yourapp.app> using 
    codesign --deep --force --verify --verbose --sign "Developer ID Application: <developerid>" <yourapp.app>

# Verify using
    codesign --verify --verbose=4 <yourapp.app>
 * you should see something like this
    <yourapp.app>: valid on disk
    <yourapp.app>: satisfies its Designated Requirement

# Now create again the dmg file using dropdmg(https://c-command.com/dropdmg/) application, download, install dropdmg. set the cofiguration preferences with your developer id certificate in signing option.

# drag and drop <yourapp.app> to dropdmg app, wait for creation of dmg to complete. voila you have now successfully created dmg with proper developer id certification.

# verify resulted dmg again using   
     codesign --verify --verbose=4 <yourapp.dmg>
# you can also verify with gatekeeper
     spctl -a -t exec -vv <yourapp.dmg>

完成这些操作后,您将不会看到提示应用已损坏或损坏或开发者身份不明的弹出消息。

我的两位:

  1. 为了真正验证协同签名,我不得不将我的 DMG 上传到 服务器并使用浏览器下载它或手动设置隔离属性:

    APP_PATH="Any.app"
    xattr -w com.apple.quarantine '0081;5a37dc6a;Google Chrome;F15F7E1C-F894-4B7D-91B4-E110D11C4858' "$APP_PATH"
    xattr -l "$APP_PATH" # You should see the quarantine attribute here
    open "$APP_PATH"
    

    如果您的应用已正确签名,您应该会看到一个系统对话框,其中包含 "Open" 按钮。

    我通过查看另一个找到了隔离属性的值 .app 从互联网上下载。我不知道有什么价值 意思是

    我真的不明白为什么 spctl 命令甚至说 "accepted" 如果 Gatekeeper 服务拒绝打开应用程序。

  2. 我有 "unidentified developer" 消息框,因为我的 Qt 框架被引用为“@rpath/QtCore.framework”。使用 install_name_tool 将其更改为“@application_path/../Frameworks/QtCore.framework”修复了我的应用程序中的问题。

您需要使用命令行 otool -l 验证您的可执行文件和您的框架的所有 rpath。如果您的可执行文件中有本地 rpath(例如:/user/name/Qt/),请将其删除(使用此命令 install_name_tool -delete_rpath)。