Mac App Store Build Validations

I recently submitted my first Mac App to the Mac App Store. Before submission, I ran a bunch of validation steps on my final production ready app. Craig Hockenberry’s blog post made it much easier. Here are the key steps

  1. Launch Terminal app and cd to the dir where the .app file is located.
  2. Recursively finds all Mach-O bundles in the app bundle.
    $ find -H Myapp.app -print0 | xargs -0 file | grep "Mach-O"
    
    Myapp.app/Contents/Frameworks/CorePlot.framework/CorePlot:                                                  Mach-O 64-bit dynamically linked shared library x86_64
    
    Myapp.app/Contents/Frameworks/CorePlot.framework/Versions/A/CorePlot:                                       Mach-O 64-bit dynamically linked shared library x86_64
    Myapp.app/Contents/Frameworks/CrashReporter.framework/CrashReporter:                                        Mach-O universal binary with 2 architectures
    Myapp.app/Contents/Frameworks/CrashReporter.framework/CrashReporter (for architecture x86_64):	            Mach-O 64-bit dynamically linked shared library x86_64
    Myapp.app/Contents/Frameworks/CrashReporter.framework/CrashReporter (for architecture i386):		    Mach-O dynamically linked shared library i386
    Myapp.app/Contents/Frameworks/CrashReporter.framework/Versions/A/CrashReporter:                             Mach-O universal binary with 2 architectures
    Myapp.app/Contents/Frameworks/CrashReporter.framework/Versions/A/CrashReporter (for architecture x86_64):   Mach-O 64-bit dynamically linked shared library x86_64
    Myapp.app/Contents/Frameworks/CrashReporter.framework/Versions/A/CrashReporter (for architecture i386):     Mach-O dynamically linked shared library i386
    Myapp.app/Contents/Frameworks/Growl.framework/Growl:                                                        Mach-O universal binary with 2 architectures
    Myapp.app/Contents/Frameworks/Growl.framework/Growl (for architecture x86_64):	                            Mach-O 64-bit dynamically linked shared library x86_64
    Myapp.app/Contents/Frameworks/Growl.framework/Growl (for architecture i386):	                            Mach-O dynamically linked shared library i386
    Myapp.app/Contents/Frameworks/Growl.framework/Versions/A/Growl:                                             Mach-O universal binary with 2 architectures
    Myapp.app/Contents/Frameworks/Growl.framework/Versions/A/Growl (for architecture x86_64):	            Mach-O 64-bit dynamically linked shared library x86_64
    Myapp.app/Contents/Frameworks/Growl.framework/Versions/A/Growl (for architecture i386):	                    Mach-O dynamically linked shared library i386
    Myapp.app/Contents/MacOS/Myapp:                                                                             Mach-O 64-bit executable x86_64

    I have colored the important ones in green. They are the bundles of interest. The others are just aliases. Certain universal (32 bit and 64 bit) bundles are shown twice but you will see only one bundle in the location pointed to. My application had only frameworks and the main executable but in your case there could be other bundles as well.

  3. Verify the Entitlements of all the bundles of interest.
    //This bundle has Entitlements
    $ codesign -d --entitlements - /Users/Sayeed/Desktop/Myapp.app/
    
    Executable=/Users/Sayeed/Desktop/Myapp.app/Contents/MacOS/Myapp
    ??qq?<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    	<key>com.apple.application-identifier</key>
    	<string>83YFQ426C5.com.app.*>/string>
    	<key>com.apple.security.app-sandbox</key>
    	<true/>
    	<key>com.apple.security.files.user-selected.read-write</key>
    	<true/>
    	<key>com.apple.security.network.client</key>
    	<true/>
    	<key>com.apple.security.print</key>
    	<true/>
    </dict>
    </plist>
    
    //This bundle doesn't have entitlements
    $ codesign -d --entitlements - /Users/Sayeed/Desktop/Myapp.app/Contents/Frameworks/CrashReporter.framework/Versions/A/CrashReporter
    
    Executable=/Users/Sayeed/Desktop/Myapp.app/Contents/Frameworks/CrashReporter.framework/Versions/A/CrashReporter
    
  4. Verify the Code signatures and Designated Requirements (DR) of all the bundles of interest.
    $ codesign --verify --verbose=4 /Users/Sayeed/Desktop/Myapp.app
    
    /Users/Sayeed/Desktop/Myapp.app: valid on disk
    /Users/Sayeed/Desktop/Myapp.app: satisfies its Designated Requirement
    
    $ codesign --verify --verbose=4 /Users/Sayeed/Desktop/Myapp.app/Contents/Frameworks/CorePlot.framework/Versions/A/Coreplot
    
    /Users/Sayeed/Desktop/Myapp.app/Contents/Frameworks/CorePlot.framework/Versions/A/Coreplot: valid on disk
    /Users/Sayeed/Desktop/Myapp.app/Contents/Frameworks/CorePlot.framework/Versions/A/Coreplot: satisfies its Designated Requirement
    Important: Xcode on Mac OS X Maverics doesn’t allow you to sign a bundle if any nested bundle in it is unsigned. These nested bundles could be things like helper executables, libraries, embedded frameworks, plug-ins, XPC services. Therefore

    1. You should sign the unsigned bundles with your own code signing identity i.e. your Developer Id Certificate or App Store Distribution Certificate.
    2. It is recommended that you re-sign the bundles that are already signed by third parties with your own code signing identity i.e. your Developer Id Certificate or App Store Distribution Certificate.

    Here is how you should do it.

  5. Verify the Identifiers, Signer Details and Trust Chain, Designated Requirements (DR) of all the bundles of interest.
    $ codesign --display --requirements - --verbose=4 Myapp.app/
    
    Executable=/Users/Sayeed/Desktop/Myapp.app/Contents/MacOS/Myapp
    Identifier=com.app.myapp
    Format=bundle with Mach-O thin (x86_64)
    CodeDirectory v=20200 size=3804 flags=0x0(none) hashes=181+5 location=embedded
    Hash type=sha1 size=20
    CDHash=4145a92202f852303290579c641dab3c36336b1f
    Signature size=4353
    Authority=3rd Party Mac Developer Application: Sayeed Hussain (83YFQ426C5)
    Authority=Apple Worldwide Developer Relations Certification Authority
    Authority=Apple Root CA
    Signed Time=13-Jun-2014 12:10:11 am
    Info.plist entries=35
    TeamIdentifier=83YFQ426C5
    Sealed Resources version=2 rules=12 files=49
    designated => identifier "com.app.myapp" and anchor apple generic and certificate leaf[subject.CN] = "3rd Party Mac Developer Application: Sayeed Hussain (83YFQ426C5)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */
    
    $ codesign --display --requirements - --verbose=4 Myapp.app/Contents/Frameworks/CrashReporter.framework/Versions/A/CrashReporter
    
    Executable=/Users/Sayeed/Desktop/Myapp.app/Contents/Frameworks/CrashReporter.framework/Versions/A/CrashReporter
    Identifier=coop.plausible.CrashReporter
    Format=bundle with Mach-O universal (i386 x86_64)
    CodeDirectory v=20200 size=1532 flags=0x0(none) hashes=69+3 location=embedded
    Hash type=sha1 size=20
    CDHash=3195a79f058becf75f663cd1fea96d9f75c7ba4b
    Signature size=4353
    Authority=3rd Party Mac Developer Application: Sayeed Hussain (83YFQ426C5)
    Authority=Apple Worldwide Developer Relations Certification Authority
    Authority=Apple Root CA
    Signed Time=13-Jun-2014 12:10:10 am
    Info.plist entries=16
    TeamIdentifier=83YFQ426C5
    Sealed Resources version=2 rules=12 files=22
    designated => identifier "coop.plausible.CrashReporter" and anchor apple generic and certificate leaf[subject.CN] = "3rd Party Mac Developer Application: Sayeed Hussain (83YFQ426C5)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */
  6. If you are building for Mac App Store skip this step. If you are distributing outside of the Mac App Store, this is the last step. Verify that Gatekeeper will accept your app.
    $ spctl --verbose=4 --assess --type execute /Users/Sayeed/Myapp.app
    
    /Users/Sayeed/Myapp.app: accepted
    source=Developer ID
    

    and you are done.

  7. If you are distributing via the Mac App Store, follow this and the next step. You need to first create an xcarchive by choosing Product > Archive in Xcode. Then, using Terminal, you need to export it as a .pkg file as shown.
    $ xcodebuild -exportArchive -exportFormat PKG -archivePath  /Users/Sayeed/Desktop/Myapp\ 13-06-14\ 12.10\ am.xcarchive -exportPath /Users/Sayeed/Desktop/Myapp -exportSigningIdentity "3rd Party Mac Developer Application: Sayeed Hussain (83YFQ426C5)" -exportInstallerIdentity "3rd Party Mac Developer Installer: Sayeed Hussain (83YFQ426C5)"
    productbuild: Adding component at /var/folders/g3/spw0ywfj4d3_rz8jbkxz4lt80000gn/T/28EB1C81-63CD-444C-96BE-9BF8DA01A34E-4171-000001432522843D/Myapp.app
    productbuild: Signing product with identity "3rd Party Mac Developer Installer: Sayeed Hussain (83YFQ426C5)" from keychain /Users/Sayeed/Library/Keychains/login.keychain
    productbuild: Adding certificate "Apple Worldwide Developer Relations Certification Authority"
    productbuild: Adding certificate "Apple Root CA"
    productbuild: Wrote product to /var/folders/g3/spw0ywfj4d3_rz8jbkxz4lt80000gn/T/28EB1C81-63CD-444C-96BE-9BF8DA01A34E-4171-000001432522843D/Myapp.pkg
    productbuild: Supported OS versions: [10.7, )
    Moving exported product to '/Users/Sayeed/Desktop/Myapp.pkg'
    ** EXPORT SUCCEEDED **
    
    

    Myapp.pkg file will get generated.

  8. Verify the Myapp.pkg file by running the installer with -store option.
    $ sudo installer -store -pkg /Users/Sayeed/Desktop/Myapp.pkg
    
    installer: Note: running installer as an admin user (instead of root) gives better Mac App Store fidelity
    installer: Myapp.pkg has valid signature for submission: 3rd Party Mac Developer Application: Sayeed Hussain (83YFQ426C5)
    installer: Installation Check: Passed
    installer: Volume Check: Passed
    installer: Bundle com.app.myapp will be installed to /Applications/Myapp.app
    installer: Starting install
    installer: Install 0.0% complete
    installer: Install 13.8% complete
    installer: Install 22.2% complete
    installer: Install 47.6% complete
    installer: Install 88.3% complete
    installer: Install 100.0% complete
    installer: Finished install
    

    and you are done.

Hope this comes handy to anyone submitting their app to the Mac App Store. That’s it for now. Good Luck And Good Life!!!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s