在Xcode之外以数字方式签署Mac应用程序失败

时间:2015-07-29 19:06:22

标签: xcode macos cocoa code-signing osx-gatekeeper

我开发了一款带有Qt5的Mac应用,所以在Xcode之外。我希望GateKeeper允许我的应用程序在客户端的计算机上运行,​​而不是发出“无法打开,因为无法确认开发人员的身份”警告。

我已成功对该应用程序进行了数字签名,但GateKeeper仍然附带此投诉。我有一个Apple开发人员证书(我是团队代理),我的钥匙串说它是有效的。我还安装了两个Apple root证书。

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

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

显示所有二进制文件已经验证。此外,它报告:

  

MyApp.app:在磁盘上有效
  MyApp.app:满足其指定要求

运行:

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

给出

  

可执行文件= / Users / xxx / trunk / yyy / deploy / release / MyApp.app / Contents / MacOS / MyApp
  Identifier = aaaa.MyApp
  格式=捆绑Mach-O thin(x86_64)
  CodeDirectory v = 20200 size = 12461 flags = 0x0(none)hashes = 616 + 3 location = embedded
  哈希类型= sha1大小= 20
  CDHash = d1c12c783dac0e8d9a2b749fb896b11558cec8b6
  签名尺寸= 8532
  Authority =开发者ID申请:XXXXX
  授权=开发者ID认证机构
  权限= Apple Root CA
  时间戳= 29岁。 2015 12; 04:40
  Info.plist entries = 8
  TeamIdentifier = YYYYY
  密封资源版本= 2规则= 12个文件= 10
  内部要求数= 1尺寸= 180

似乎没问题。

正在运行

$ spctl -a -t exec -vv MyApp.app
所有二进制文件上的

结果为

  

MyApp.app:接受了   source =开发者ID   origin =开发者ID申请:XXXX

也似乎没问题

运行XCode命令行工具check-signature  在app或app文件夹中的二进制文件上:

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

结果

  

(c)2014 Apple Inc.保留所有权利。
  是的

所有案例中

是期望的结果。

但是GateKeeper仍然不接受该应用程序,并抱怨开发人员无法确认。

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

我想我发现了这个问题。我不知道它是一个功能还是一个OSX错误。 stackoverflow question 19551298为我提供了很多帮助。

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

  1. 如果文件未签名,则会发出“未识别的开发者等”消息

  2. 当文件经过数字签名后,会发出“开发人员无法确认等”消息

  3. 在这两种情况下,MessageBox只有一个按钮,一个OK按钮。单击此按钮时,除了MessageBox关闭之外没有任何事情发生。

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

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

    基于Apple Support Documentation我期望在双击下载的应用程序时GateKeeper的另一个更好的行为(可能是文档已过时,或者我误读了它):

    1. 如果应用程序已签名,GateKeeper应显示一个MessageBox,其中包含“从网上下载等”和一个“仍然继续进行”的按钮?

    2. 如果应用程序未使用单个“确定”按钮和“未识别的开发人员等”文本对MessageBox进行签名,则

5 个答案:

答案 0 :(得分:16)

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

我终于解决了我的问题。首先是信用:(i)我的其他stackoverflow question的答案非常有用,(ii)我通过提交所谓的技术支持事件(TSI)得到了Apple官方开发人员的非常好(付费)建议)。

基于这一切,我现在能够在这里给出一个非常简洁的方法,说明如何通过GateKeeper成功处理您的Mac应用程序。详细说明食谱后,我将展示我原来的错误。

  

目标:在Xcode之外开发了一个Mac应用程序,让GateKeeper发出警告"从互联网下载......"有三个按钮,其中一个是"打开"

     

失败:当GateKeeper向文本" ..身份不明的开发者发出警告时。"或文本" ..未经证实的开发者.."在两种情况下都带有一个带有一个OK按钮的消息框。

获取应用程序GateKeeper就绪包含三个步骤:

  
      
  • 让您的应用程序独立,没有不可接受的外部依赖关系。唯一可接受的外部依赖是系统库。应将所有其他依赖项复制到MyApp.app文件夹。 GateKeeper拒绝任何具有非系统外部依赖性的应用程序
  •   
  • 二进制文件不应位于MyApp.app文件夹内的非法位置。库进入MyApp / Contents / Frameworks,可执行文件进入MyApp / Contents / MacOS
  •   
  • MyApp中的所有二进制文件都应进行数字签名。然后应签署MyApp.app文件夹。为此签署Apple"开发者ID申请......"证书是必要的
  •   

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

这是我们的食谱

  

一个。让应用程序独立:

     
      
  1. 将所有需要的二进制文件复制(使用脚本)到MyApp.folder
  2.   
  3. 运行(使用脚本)install_name_tool -changeinstall_name_tool -id,以便应用内的所有依赖关系都是相对类型@executable_path/../MacOS..@executable_path/../Frameworks
  4.   
  5. 在MyApp.app文件夹中的所有二进制文件上运行(使用脚本)otool -L并标记任何非法依赖,例如" @rpath ..."或绝对文件路径不是系统路径。请注意,otool -L无法保证找到所有依赖项。插件通常超出otool的范围。这就是你需要下一次检查的原因。
  6.   
  7. 在位置" MyApp.app/Contents/MacOS"处启动终端。运行export DYLD_PRINT_LIBRARIES=1。然后在同一个终端窗口./MyApp内运行。您的终端将填满数百个已加载的库。再次检查此列表是否有禁止的库(计算机上存在的库,但客户的计算机上没有)。
  8.   
  9. 布丁的证据就在吃。我们使用MacInCloud virtual machines并检查我们的应用是否在那里运行。替代解决方案可能是不是开发人员的亲属的Mac。或者您也可以在自己的Mac上创建一个新用户(" test")并将应用程序复制到其下载(或桌面文件夹,或......)。在后一种情况下,您必须临时重命名IDE的根文件夹,否则用户" test"会在那里找到丢失的二进制文件。
  10.         

    B签署应用

         
        
    1. 签名:使用我们的脚本,我们在应用程序中的所有二进制文件上运行codesign --force --verify --verbose --sign \"Developer ID Application: ....\" \"/path/to/binary\",然后在应用程序文件夹本身上运行。在每种情况下都会捕获系统响应。它应该在每种情况下都包含字符串"签名的Mach-O thin"。
    2.   
    3. 验证:对应用程序中的每个二进制文件和应用程序本身运行(使用脚本)命令codesign --verify --verbose \"/path/to/binary\",并捕获系统响应。它应该在每种情况下包含字符串"在磁盘上有效"并且"满足其指定要求"。
    4.   
    5. GateKeeper检查:在每个二进制文件和app文件夹本身上运行(使用脚本)spctl -a -t exec -vv /path/to/binary\"。捕获系统响应。它应该在所有情况下都包含字符串"接受的来源"。
    6.   
    7. check-signature:在每个二进制文件和app文件夹本身上运行(带脚本)check-signature \"/path/to/banary\"。捕获系统响应。它应该包含字符串" YES"在每种情况下。
    8.         

      C外部检查

           
          
      1. 将您的应用压缩到一个zip文件中。上传到您的某个云服务器

      2.   
      3. GateKeepers在其常规守门员角色上保留了长列表(通常为数百项)异常。如果要测试GateKeeper,您的应用必须不在该列表中。而不是编辑此列表更简单的技巧是在Mac上创建新用户。登录该用户并从Internet云服务器下载zip文件。 Finder会自动解压缩它。点击它。如果GateKeeper告诉您它可以打开应用程序,但它会在从Internet下载的同时向您发出警告,那么现在是时候抓一个(白色)啤酒了。

      4.   

这里是所需的GateKeeper警告:enter image description here

我的错误

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

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

答案 1 :(得分:1)

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
  • 不要在dylib文件上运行spctl -a -t exec -vv /path/to/binary。您将收到有关资源信封的错误。这是预期的,而不是问题。
  • 根据我的经验,macdeployqt工作正常。我通过更改构建来解决问题,这样绝对路径没有进入有问题的dylib文件(libquazip)。我仍然使用install_name_tool来删除Qt安装的绝对路径。然后,我使用macdeployqt创建捆绑包,签署捆绑包并创建DMG文件。

答案 2 :(得分:1)

我的两位:

  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"
    

    如果您的应用已正确签名,您应该会看到一个系统对话框 一个“打开”按钮。

    我通过查看另一个来找到隔离属性的值 .app从网上下载。我不知道有什么价值 装置

    我真的不明白为什么spctl命令说“接受”了 如果Gatekeeper服务拒绝打开应用程序。

  2. 我有“未识别的开发人员”消息框,因为我的Qt框架被引用为“ @rpath /QtCore.framework”。使用install_name_tool将其更改为“ @application_path /../ Frameworks /QtCore.framework”修复了我的应用中的问题。

答案 3 :(得分:0)

经过多次尝试后想出问题。

在我的情况下: 流行消息 - 损坏的应用程序是由于库丢失。 我使用QT创建了.app文件。 要生成dmg,我使用的是deploymacqt命令工具。 deploymacqt工具在.app中创建动态库,所以基本上如果我们在创建dmg之前进行协同编码,这个alter会操纵代码符号。 所以正确的解决方法是。

# 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>

一旦完成这些操作,您将看不到弹出消息,说应用已损坏或损坏或身份不明的开发人员。

答案 4 :(得分:0)

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