17

After using macdeployqt I sign my application to avoid Gatekeeper problems.

I can use codesign on all the frameworks and everything inside the bundle but when I come to sign the bundle I get an error:

$ codesign --force --verify --verbose --sign "Developer ID Application: My ID" MyApplication.app
MyApplication.app: bundle format unrecognized, invalid, or unsuitable
In subcomponent: /Users/username/Dev/Apps/MyApplication/MyApplication.app/Contents/Frameworks/QtConcurrent.framework

If I check the signature:

$codesign -vvv MyApplication.app/Contents/Frameworks/QtConcurrent.framework/Versions/5/QtConcurrent 
MyApplication.app/Contents/Frameworks/QtConcurrent.framework/Versions/5/QtConcurrent: valid on disk
MyApplication.app/Contents/Frameworks/QtConcurrent.framework/Versions/5/QtConcurrent: satisfies its Designated Requirement

According to http://furbo.org/2013/10/17/code-signing-and-mavericks/ it seems that I should sign the framework bundle something like this

$ codesign --force --verify --verbose --sign "Developer ID Application: My ID" MyApplication.app/Contents/Frameworks/QtConcurrent.framework/Versions/5

but this results in

MyApplication.app/Contents/Frameworks/QtConcurrent.framework/Versions/5: bundle format unrecognized, invalid, or unsuitable
koan
  • 3,596
  • 2
  • 25
  • 35

6 Answers6

19

Got the same error this morning. The codesign application seems to be stricter on OSX 10.9

The problem is caused by macdeployqt not copying the Qt framework bundles' info.plist file into the app bundle.

You can work around this problem by telling your script to copy the files manually into the embedded framework's bundle file.

e.g.

$ cp ~/Qt5.2.0/5.2.0-beta1/clang_64/lib/QtCore.framework/Contents/Info.plist MyApplication.app/Contents/Frameworks/QtCore.framework/Resources/

Do this for each of the qt modules your app uses, AFTER calling macdeployqt*, but BEFORE calling codesign. (* or mkdir the destination directories)

Also, call codesign on the framework's folders, not on the version subfolder.

i.e.

$ codesign --force --verify --verbose --sign "Developer ID Application: My ID" MyApplication.app/Contents/Frameworks/QtConcurrent.framework

Hope this helps.

Benoît LeBlanc
  • 206
  • 2
  • 4
  • 1
    Thanks, that works. There's a mistake in your cp command though, it should be `.../clang_64/lib/QtCore.framework/...` – koan Oct 28 '13 at 16:24
  • 1
    Misplaced plist files do not seem to be the only cause of this error. I am bundling frameworks and I am encountering this error when the plists are in their respective destination folders. I still receive the cryptic "bundle format unrecognized, invalid, or unsuitable" error. Xcode 5.0.2 under Mac OS 10.9. – ctpenrose Dec 04 '13 at 00:04
  • 1
    This is a critical step. – Dan Nissenbaum Jan 12 '14 at 20:44
  • 1
    Very useful thanks. Have linked it from my post on signing for Mac OS X 10.8 : http://successfulsoftware.net/2012/08/30/how-to-sign-your-mac-os-x-app-for-gatekeeper/ – Andy Brice Jun 11 '14 at 16:14
  • Codesigning has changed again for 10.9.5; please see the excellent post at http://permalink.gmane.org/gmane.comp.lib.qt.devel/18480 for info on how to set up the correct Qt framework directory structure and especially refer to https://developer.apple.com/library/mac/technotes/tn2206/_index.html#//apple_ref/doc/uid/DTS40007919-CH1-TNTAG205 – koan Sep 30 '14 at 21:00
7

Summary

  1. Fix the malformed Qt frameworks by moving the Info.plist to the Resources folder at the current version level in the framework.
  2. Use the codesign --deep option to sign all the binaries in the package at once, including frameworks and plugins.

Details

The simplest way to solve this problem is to use the --deep codesign option. This option (which I believe was introduced with Mavericks) will sign all of the binaries in a target package, including the main application binary, and its frameworks and plugins.

However before using this option (or any of the other suggested signing techniques), you must fix the Qt frameworks as per other posts on this thread. For some odd reason, the Qt frameworks as built do not conform to Apple's framework norms and will confuse the codesign tool. This in turn can result in bad behaviors like replacing the top level symlink of a framework with an additional copy of the signed framework binary.

To fix the Qt frameworks, you should copy the Info.plist to a Resources folder in the specific version of the framework, not to the top level of the framework (as has been previously suggested). Also, you might want to throw away the original copy of the Info.plist in the application package framework, since it's basically in the wrong place.

Specifically you should do something like this to fix each framework as needed:

mkdir -p MyKillerApp.app/Contents/Frameworks/QtCore.framework/Versions/Current/Resources
cp lib/QtCore.framework/Contents/Info.plist MyKillerApp.app/Contents/Frameworks/QtCore.framework/Versions/Current/Resources
rm -rf MyKillerApp.app/Contents/Frameworks/QtCore.framework/Contents

The above assumes that there is a "Versions/Current" symlink in the framework. I think that this is the case for all built Qt libraries. But if that's not the case for your build, then you can use the specific version folder name (like "4") rather than the "Current" symlink.

After normalizing the frameworks, you can then use the --deep option to sign all of the binaries in the application package:

codesign --deep --force --verify --sign "Developer ID Application: My ID" MyKillerApp.app

Alternatively if you've enabled codesign at the project level for deployment post processing, you can just pass the --deep option in the "Other Code Signing Flags":

OTHER_CODE_SIGN_FLAGS = --deep

If you have different codesign requirements for your main application than for your frameworks or plugins, you can use a little trick. First you sign with the --deep option using the frameworks/plugins requirements. Then you can sign again with the --force option but without the --deep option, specifying the application level requirements. The result will be that the main application will be signed with the application requirements, and all the sub-binaries will be signed with the frameworks/plugin requirements.

This approach might be considered a bit lazy and wasteful, since you're signing the main application twice. But it's simpler than the alternative of finding and signing all the sub binaries separately from the main application binary.

acronce
  • 383
  • 4
  • 9
  • Does this work with using `spctl -a -t exec -vv Myapp.app` which verifies that the bundle is suitable with the new gatekeeper format? I am always getting a "rejected" result. – Hyndrix Aug 06 '14 at 12:10
  • Are you signing under Mavericks or above? The new Gatekeeper for Mavericks 10.9.5 and Yosemite require v2 signatures. If you sign with Mavericks (even the current shipping version), it will create the required v2 signature. – acronce Sep 19 '14 at 18:05
2

Thank you very much!

You also have to sign all plugins used by your application

i.e.

$ codesign --force --verify --verbose --sign "Developer ID Application: My ID" MyApplication.app/Contents/PlugIns/imageformats/libqgif.dylib

Best regards,

Rainer

PANGAEA
  • 39
  • 1
  • The question was about signing frameworks, not how to sign an application. – koan Nov 03 '13 at 14:26
  • Those plugins are plugins for the Qt framework and need to be signed as well for the framework to work. – jdkoftinoff Nov 29 '13 at 11:29
  • @jdkoftinoff Yes, but the question was about how to sign frameworks, no problem was reported about signing plugins. – koan Jan 12 '14 at 20:33
0

I wrote up a script based on Benoît's answer that does the work of patching the .app: https://gist.github.com/kainjow/8059407

The code:

#!/usr/bin/env ruby

# Copies missing Info.plist files for a .app's Qt frameworks installed
# from macdeployqt. Without the plists, 'codesign' fails on 10.9 with
# "bundle format unrecognized, invalid, or unsuitable".
#
# Example usage:
# ruby macdeployqt_fix_frameworks.rb /Users/me/Qt5.2.0/5.2.0/clang_64/ MyProgram.app
#
# Links:
# https://bugreports.qt-project.org/browse/QTBUG-23268
# http://stackoverflow.com/questions/19637131/sign-a-framework-for-osx-10-9
# http://qt-project.org/forums/viewthread/35312

require 'fileutils'

qtdir = ARGV.shift
dotapp = ARGV.shift

abort "Missing args." if !qtdir || !dotapp
abort "\"#{qtdir}\" invalid" if !File.exists?(qtdir) || !File.directory?(qtdir)
abort "\"#{dotapp}\" invalid" if !File.exists?(dotapp) || !File.directory?(dotapp)

frameworksDir = File.join(dotapp, 'Contents', 'Frameworks')
Dir.foreach(frameworksDir) do |framework|
next if !framework.match(/^Qt.*.framework$/)
fullPath = File.join(frameworksDir, framework)
destPlist = File.join(fullPath, 'Resources', 'Info.plist')
next if File.exists?(destPlist)
srcPlist = File.join(qtdir, 'lib', framework, 'Contents', 'Info.plist')
abort "Source plist not found: \"#{srcPlist}\"" if !File.exists?(srcPlist)
FileUtils.cp(srcPlist, destPlist)
end 
Makyen
  • 31,849
  • 12
  • 86
  • 121
kainjow
  • 3,955
  • 1
  • 20
  • 17
0

According to Apple docs you need to sign version folders, not the framework itself. I've tried that and run into two issues. First of all, some of the Info.plist files of Qt frameworks have incorrect executable names (a _debug suffix). After fixing that I've managed to sign all the frameworks the "Apple way". However, after doing that I was unable to sign the main app and got errors about the Qt frameworks I've just signed.

So the weird but working solution is to sign framework folders instead. This works even with incorrect executable names in Info.plist files.

Vitaly
  • 1
0

I use this script to correct the QT 4.8 frameworks before codesigning. Hope this helps. It took me and a coworker about 3-4 days to figure this out (this was before the blog post by QT).

# we need to massage qt frameworks so they get codesigned by the new codesign
# more information about this $#@$!@# piece of $@#$@$!
# http://blog.qt.digia.com/blog/2014/10/29/an-update-on-os-x-code-signing/

QT_LIB_DIR=$(qmake -query QT_INSTALL_LIBS)
for i in $(find "$APP/Contents/Frameworks/" -name Qt\*.framework); do
   FW_NAME=$(basename "$i")
   FW_SHORTNAME=$(basename -s ".framework" "$i")
   mv "$i/Resources" "$i/Versions/4"
   ln -s Versions/Current/Resources "$i/"
   ln -s Versions/Current/${FW_SHORTNAME} "$i/${FW_SHORTNAME}"
   ln -s 4 "$i/Versions/Current"
   cp "${QT_LIB_DIR}/${FW_NAME}/Contents/Info.plist" "$i/Resources"
   chmod u+w "$i/Resources/Info.plist"
   # now comes the real magic, we have to add CFBundleIdentifier and CFBundleVersion to the Info.plist
   awk "/<\/dict>/{print \"<key>CFBundleIdentifier</key>\n<string>org.qt-project.${FW_SHORTNAME}</string>\"}1" "$i/Resources/Info.plist" > "$i/Resources/Info.plist.tmp"
   mv "$i/Resources/Info.plist.tmp" "$i/Resources/Info.plist"
   awk '/<\/dict>/{print "<key>CFBundleVersion</key>\n<string>4.8</string>"}1' "$i/Resources/Info.plist" > "$i/Resources/Info.plist.tmp"
   mv "$i/Resources/Info.plist.tmp" "$i/Resources/Info.plist"
done
Manuel Odendahl
  • 221
  • 2
  • 6