xcodeapp-store-connectcodesignmac-app-store

How do you codesign framework bundles for the Mac App Store?


After a recent submission I have gotten the following error:

Invalid Signature - the nested app bundle (FooBar.app/Contents/Frameworks/GData.framework) is not signed, the signature is invalid, or it is not signed with an Apple submission certificate. Refer to the Code Signing and Application Sandboxing Guide for more information.

Invalid Signature - the nested app bundle (FooBar.app/Contents/Frameworks/Growl.framework) is not signed, the signature is invalid, or it is not signed with an Apple submission certificate. Refer to the Code Signing and Application Sandboxing Guide for more information.

Invalid Signature - the nested app bundle libcurl (FooBar.app/Contents/Frameworks/libcurl.framework) is not signed, the signature is invalid, or it is not signed with an Apple submission certificate. Refer to the Code Signing and Application Sandboxing Guide for more information.

So I signed all the framework bundles per Technote 2206:

codesign -f -v -s "3rd Party Mac Developer Application: Name" ./libcurl.framework/Versions/A/libcurl
codesign -f -v -s "3rd Party Mac Developer Application: Name" ./libcurl.framework/Versions/A/libssh2.1.dylib
codesign -f -v -s "3rd Party Mac Developer Application: Name" ./Growl.framework/Versions/A/Growl
codesign -f -v -s "3rd Party Mac Developer Application: Name" ./GData.framework/Versions/A/GData

Technote 2206 says:

Signing Frameworks

Seeing as frameworks are bundles it would seem logical to conclude that you can sign a framework directly. However, this is not the case. To avoid problems when signing frameworks make sure that you sign a specific version as opposed to the whole framework:

# This is the wrong way:

codesign -s my-signing-identity ../FooBarBaz.framework

# This is the right way:

codesign -s my-signing-identity ../FooBarBaz.framework/Versions/A

And when I try to verify the results, it looks good to me:

% codesign -vvv FooBar.app/Contents/Frameworks/libcurl.framework
FooBar.app/Contents/Frameworks/libcurl.framework: valid on disk
FooBar.app/Contents/Frameworks/libcurl.framework: satisfies its Designated Requirement
% codesign -vvv FooBar.app/Contents/Frameworks/Growl.framework
FooBar.app/Contents/Frameworks/Growl.framework: valid on disk
FooBar.app/Contents/Frameworks/Growl.framework: satisfies its Designated Requirement

For fun, I did try signing the framework bundle directly and it was still rejected. But that is exactly what the documentation said not to do.

Any guesses why that would be considered invalid? I am using the same cert that I use to code sign my app -- the one that has worked in the past.

My only guess would be something to do with the existing plists (do I need to own the identifiers in the framework's Info.plists?) or entitlements -- any suggestions?


Solution

  • Based on baptr’s answer, I have developed this shell script that codesigns all my frameworks and other binary resources/auxiliary executables (currently supported types: dylib, bundle, and login items):

    #!/bin/sh
    
    # WARNING: You may have to run Clean in Xcode after changing CODE_SIGN_IDENTITY! 
    
    # Verify that $CODE_SIGN_IDENTITY is set
    if [ -z "${CODE_SIGN_IDENTITY}" ] ; then
        echo "CODE_SIGN_IDENTITY needs to be set for framework code-signing!"
    
        if [ "${CONFIGURATION}" = "Release" ] ; then
            exit 1
        else
            # Code-signing is optional for non-release builds.
            exit 0
        fi
    fi
    
    if [ -z "${CODE_SIGN_ENTITLEMENTS}" ] ; then
        echo "CODE_SIGN_ENTITLEMENTS needs to be set for framework code-signing!"
    
        if [ "${CONFIGURATION}" = "Release" ] ; then
            exit 1
        else
            # Code-signing is optional for non-release builds.
            exit 0
        fi
    fi
    
    ITEMS=""
    
    FRAMEWORKS_DIR="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
    if [ -d "$FRAMEWORKS_DIR" ] ; then
        FRAMEWORKS=$(find "${FRAMEWORKS_DIR}" -depth -type d -name "*.framework" -or -name "*.dylib" -or -name "*.bundle" | sed -e "s/\(.*framework\)/\1\/Versions\/A\//")
        RESULT=$?
        if [[ $RESULT != 0 ]] ; then
            exit 1
        fi
    
        ITEMS="${FRAMEWORKS}"
    fi
    
    LOGINITEMS_DIR="${TARGET_BUILD_DIR}/${CONTENTS_FOLDER_PATH}/Library/LoginItems/"
    if [ -d "$LOGINITEMS_DIR" ] ; then
        LOGINITEMS=$(find "${LOGINITEMS_DIR}" -depth -type d -name "*.app")
        RESULT=$?
        if [[ $RESULT != 0 ]] ; then
            exit 1
        fi
    
        ITEMS="${ITEMS}"$'\n'"${LOGINITEMS}"
    fi
    
    # Prefer the expanded name, if available.
    CODE_SIGN_IDENTITY_FOR_ITEMS="${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
    if [ "${CODE_SIGN_IDENTITY_FOR_ITEMS}" = "" ] ; then
        # Fall back to old behavior.
        CODE_SIGN_IDENTITY_FOR_ITEMS="${CODE_SIGN_IDENTITY}"
    fi
    
    echo "Identity:"
    echo "${CODE_SIGN_IDENTITY_FOR_ITEMS}"
    
    echo "Entitlements:"
    echo "${CODE_SIGN_ENTITLEMENTS}"
    
    echo "Found:"
    echo "${ITEMS}"
    
    # Change the Internal Field Separator (IFS) so that spaces in paths will not cause problems below.
    SAVED_IFS=$IFS
    IFS=$(echo -en "\n\b")
    
    # Loop through all items.
    for ITEM in $ITEMS;
    do
        echo "Signing '${ITEM}'"
        codesign --force --verbose --sign "${CODE_SIGN_IDENTITY_FOR_ITEMS}" --entitlements "${CODE_SIGN_ENTITLEMENTS}" "${ITEM}"
        RESULT=$?
        if [[ $RESULT != 0 ]] ; then
            echo "Failed to sign '${ITEM}'."
            IFS=$SAVED_IFS
            exit 1
        fi
    done
    
    # Restore $IFS.
    IFS=$SAVED_IFS
    
    1. Save it to a file in your project. I keep my copy in a Scripts subdirectory in my project’s root.
      • Mine is called codesign-frameworks.sh.
    2. Add a “Run Script” build phase right after your “Copy Embedded Frameworks” build phase.
      • You can call it “Codesign Embedded Frameworks”.
    3. Paste ./codesign-frameworks.sh (or whatever you called your script above) into the script editor text field. Use ./Scripts/codesign-frameworks.sh if you store the script in a subdirectory.
    4. Build your app. All bundled frameworks will be codesigned.

    Should you still get an “Identity: ambiguous (matches: …” error, please comment below. This should not happen anymore.

    Updated 2012-11-14: Adding support for frameworks with special characters in their name (this does not include single quotes) to “codesign-frameworks.sh”.

    Updated 2013-01-30: Adding support for special characters in all paths (this should include single quotes) to “codesign-frameworks.sh”.

    Updated 2013-10-29: Adding experimental dylib support.

    Updated 2013-11-28: Adding entitlements support. Improving experimental dylib support.

    Updated 2014-06-13: Fixing codesigning issues with frameworks containing (nested) frameworks. This was done by adding -depth option to find, which causes find to do a depth-first traversal. This has become necessary, because of the issue described here. In short: a containing bundle can only be signed if its nested bundles are signed already.

    Updated 2014-06-28: Adding experimental bundle support.

    Updated 2014-08-22: Improving code and preventing failure to restore IFS.

    Updated 2014-09-26: Adding support for login items.

    Updated 2014-10-26: Quoting directory checks. This fixes the “line 31/42: too many arguments” errors and the resulting “ code object is not signed at all” error for paths including special characters.

    Updated 2014-11-07: Resolving the ambiguous identity error (like “Mac Developer: ambiguous …”) when using automatic identity resolution in Xcode. You don’t have to explicitly set the identity anymore and can just use “Mac Developer”!

    Updated 2015-08-07: Improving semantics.

    Improvements welcome!