Signature not accepted?

I have an app I developed for internal company use outside of xcode (python/pyinstaller based application). The app is a normal double-clickable application with the usual internal structure, as far as I can tell. In order to ease deployment, I figured I'd go ahead and code sign it using my Developer ID, which I did with the following command:


codesign --deep -f -s "Developer ID Application" SO2\ Explorer.app


Which ran without complaint. I can then check that it was applied using the command:


codesign -dv --strict --verbose=4 SO2\ Explorer.app


which gives the following output:


Executable=/Users/israel/Desktop/SO2 Explorer.app/Contents/MacOS/SO2 Explorer
Identifier=edu.alaska.avo.so2explorer
Format=app bundle with Mach-O thin (x86_64)
CodeDirectory v=20200 size=26842 flags=0x0(none) hashes=833+3 location=embedded
VersionPlatform=1
VersionMin=657152
VersionSDK=658176
Hash type=sha256 size=32
CandidateCDHash sha1=d0fd8ca348696e3e8ed218658a4a19de0f0fa346
CandidateCDHash sha256=c45d6c5c00a7dd457a55c37828f3b3953049f8dd
Hash choices=sha1,sha256
Page size=4096
CDHash=c45d6c5c00a7dd457a55c37828f3b3953049f8dd
Signature size=9012
Authority=Developer ID Application: Israel Brewster (59ED27HUEF)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=Mar 21, 2019 at 2:19:59 PM
Info.plist entries=10
TeamIdentifier=59ED27HUEF
Sealed Resources version=2 rules=13 files=1222
Internal requirements count=1 size=188


which all looks good to me. However, if I check it using the spctl tool:


spctl --assess -v --raw SO2\ Explorer.app


the application is rejected:


SO2 Explorer.app: rejected (the main executable or Info.plist must be a regular file (no symlinks, etc.))
<?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>assessment:authority</key>
  <dict>
  <key>assessment:authority:flags</key>
  <integer>0</integer>
  <key>assessment:authority:source</key>
  <string>obsolete resource envelope</string>
  <key>assessment:authority:weak</key>
  <true/>
  </dict>
  <key>assessment:cserror</key>
  <integer>-67015</integer>
  <key>assessment:remote</key>
  <true/>
  <key>assessment:verdict</key>
  <false/>
</dict>
</plist>


And, in fact, if I try to run the app on a machine with gatekeeper enabled, you do get the "can not open this app due to unidentified developer" warning.


What's the problem here, and how do I fix it? The first line of output would seem to indicate that it thinks that either the main executable or Info.plist file is not a regular file, but that is not the case - both are regular files:


israel$ ls -l SO2\ Explorer.app/Contents/Info.plist 
-rw-------  1 israel  staff  436 Mar 21 14:07 SO2 Explorer.app/Contents/Info.plist
israel$ file SO2\ Explorer.app/Contents/Info.plist 
SO2 Explorer.app/Contents/Info.plist: Apple binary property list
israel$ ls -l SO2\ Explorer.app/Contents/MacOS/SO2\ Explorer 
-rwxr-xr-x  1 israel  staff  3471792 Mar 21 14:19 SO2 Explorer.app/Contents/MacOS/SO2 Explorer
israel$ file SO2\ Explorer.app/Contents/MacOS/SO2\ Explorer 
SO2 Explorer.app/Contents/MacOS/SO2 Explorer: Mach-O 64-bit executable x86_64

I'm also noting the entry about "obsolete resource envelope", though I don't know if that means anything.


Any thoughts? Thanks!

Accepted Reply

[This post has been replaced in spirit by Embedding Nonstandard Code Structures in a Bundle. I’m leaving the original post in place because it’s an interesting historical artefact. I’ve made a number of minor edits to fix links and so on.]

The most common cause for problems like this is incorrectly nested code. You must follow the rules outlined in the Nested Code section of Technote 2206 macOS Code Signing In Depth Placing Content in a Bundle.

This can be tricky when dealing with open source projects, which have their own view on how code should be laid out on disk. One way to resolve that issue is to move the nested code to the right place and then replace it within the open source hierarchy with a relative symlink. The nested code will then be in the right place to be signed correctly (as will the symlink, which is signed as a resource, not as nested code), and the open source code can still find its dependencies via the symlink

IMPORTANT I want to stress that, in this context, code refers to Mach-O images. Something like a .py file is not considered code by the code signing machinery, and thus doesn’t have to follow these rules. I recently had an extended discussion with another developer about this very issue.

I was recently helping a different developer with this issue via a DTS incident and, as part of that incident, I created a concrete example of this technique . The full example is to to big to share here, but I can share the final packaging script (see below).

This example uses a dummy open source package inside Contents/Resources/. This package consists of a tool (Contents/Resources/WaffleVarnish/bin/WaffleVarnishTool) and a library used by that tool (WaffleVarnishApp.app/Contents/Resources/WaffleVarnish/lib/libWaffleVarnish.dylib). The script:

  1. Moves the nested code to its correct place (/Contents/MacOS/ and /Contents/Frameworks/, respectively)

  2. Creates a relative symlink in the original location

  3. Signs the library in its new location

  4. Signs the tool in its new location

  5. Signs the overall app

IMPORTANT The order of steps 3 through 5 is critical. As discussed in TN2206, you must sign code ‘inside out’, that it, sign the code’s dependencies before signing the code itself.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


#! /bin/sh

set -e

if [ $# -ne 1 ]
then
    echo "usage: PackageAndSign.sh /path/to/your.app" >& 2
    exit 1
fi

# Create a directory on the desktop with a copy of the app that we're going to package and sign.

DEST_DIR=~/Desktop/WaffleVarnishApp-`uuidgen`
APP_DIR="$DEST_DIR"/WaffleVarnishApp.app
mkdir "$DEST_DIR"
cp -r "$1" "$DEST_DIR"

# Move the library and tool to the correct place per the "Nested Code" section of TN2206, 
# replacing them with symlinks.

mkdir -p "$APP_DIR"/Contents/Frameworks
mv "$APP_DIR"/Contents/Resources/WaffleVarnish/lib/libWaffleVarnish.dylib "$APP_DIR"/Contents/Frameworks/
pushd "$APP_DIR"/Contents/Resources/WaffleVarnish/lib > /dev/null
    ln -s ../../../Frameworks/libWaffleVarnish.dylib
popd > /dev/null

mv "$APP_DIR"/Contents/Resources/WaffleVarnish/bin/WaffleVarnishTool "$APP_DIR"/Contents/MacOS/
pushd "$APP_DIR"/Contents/Resources/WaffleVarnish/bin > /dev/null
    ln -s ../../../MacOS/WaffleVarnishTool
popd > /dev/null

# Code sign the library.

codesign -s \
    --force \
    --identifier=com.example.apple-samplecode.WaffleVarnishApp.libWaffleVarnish \
    --sign ADC03B244F4C1018384DCAFFC920F26136F6B59B \
    --timestamp \
    --requirement='=designated => anchor apple generic and identifier "com.example.apple-samplecode.WaffleVarnishApp.libWaffleVarnish" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = SKMME9E2Y8)' \
    "$APP_DIR"/Contents/Frameworks/libWaffleVarnish.dylib

# Code sign the tool.

codesign -s \
    --force \
    --identifier=com.example.apple-samplecode.WaffleVarnishApp.WaffleVarnishTool \
    --sign ADC03B244F4C1018384DCAFFC920F26136F6B59B \
    --timestamp \
    --requirement='=designated => anchor apple generic and identifier "com.example.apple-samplecode.WaffleVarnishApp.WaffleVarnishTool" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = SKMME9E2Y8)' \
    "$APP_DIR"/Contents/MacOS/WaffleVarnishTool

# Code sign the overall app.

codesign -s \
    --force \
    --identifier=com.example.apple-samplecode.WaffleVarnishApp \
    --sign ADC03B244F4C1018384DCAFFC920F26136F6B59B \
    --timestamp \
    --requirement='=designated => anchor apple generic and identifier "com.example.apple-samplecode.WaffleVarnishApp" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = SKMME9E2Y8)' \
    "${APP_DIR}"

echo "done"
echo "$APP_DIR"

Replies

[This post has been replaced in spirit by Embedding Nonstandard Code Structures in a Bundle. I’m leaving the original post in place because it’s an interesting historical artefact. I’ve made a number of minor edits to fix links and so on.]

The most common cause for problems like this is incorrectly nested code. You must follow the rules outlined in the Nested Code section of Technote 2206 macOS Code Signing In Depth Placing Content in a Bundle.

This can be tricky when dealing with open source projects, which have their own view on how code should be laid out on disk. One way to resolve that issue is to move the nested code to the right place and then replace it within the open source hierarchy with a relative symlink. The nested code will then be in the right place to be signed correctly (as will the symlink, which is signed as a resource, not as nested code), and the open source code can still find its dependencies via the symlink

IMPORTANT I want to stress that, in this context, code refers to Mach-O images. Something like a .py file is not considered code by the code signing machinery, and thus doesn’t have to follow these rules. I recently had an extended discussion with another developer about this very issue.

I was recently helping a different developer with this issue via a DTS incident and, as part of that incident, I created a concrete example of this technique . The full example is to to big to share here, but I can share the final packaging script (see below).

This example uses a dummy open source package inside Contents/Resources/. This package consists of a tool (Contents/Resources/WaffleVarnish/bin/WaffleVarnishTool) and a library used by that tool (WaffleVarnishApp.app/Contents/Resources/WaffleVarnish/lib/libWaffleVarnish.dylib). The script:

  1. Moves the nested code to its correct place (/Contents/MacOS/ and /Contents/Frameworks/, respectively)

  2. Creates a relative symlink in the original location

  3. Signs the library in its new location

  4. Signs the tool in its new location

  5. Signs the overall app

IMPORTANT The order of steps 3 through 5 is critical. As discussed in TN2206, you must sign code ‘inside out’, that it, sign the code’s dependencies before signing the code itself.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


#! /bin/sh

set -e

if [ $# -ne 1 ]
then
    echo "usage: PackageAndSign.sh /path/to/your.app" >& 2
    exit 1
fi

# Create a directory on the desktop with a copy of the app that we're going to package and sign.

DEST_DIR=~/Desktop/WaffleVarnishApp-`uuidgen`
APP_DIR="$DEST_DIR"/WaffleVarnishApp.app
mkdir "$DEST_DIR"
cp -r "$1" "$DEST_DIR"

# Move the library and tool to the correct place per the "Nested Code" section of TN2206, 
# replacing them with symlinks.

mkdir -p "$APP_DIR"/Contents/Frameworks
mv "$APP_DIR"/Contents/Resources/WaffleVarnish/lib/libWaffleVarnish.dylib "$APP_DIR"/Contents/Frameworks/
pushd "$APP_DIR"/Contents/Resources/WaffleVarnish/lib > /dev/null
    ln -s ../../../Frameworks/libWaffleVarnish.dylib
popd > /dev/null

mv "$APP_DIR"/Contents/Resources/WaffleVarnish/bin/WaffleVarnishTool "$APP_DIR"/Contents/MacOS/
pushd "$APP_DIR"/Contents/Resources/WaffleVarnish/bin > /dev/null
    ln -s ../../../MacOS/WaffleVarnishTool
popd > /dev/null

# Code sign the library.

codesign -s \
    --force \
    --identifier=com.example.apple-samplecode.WaffleVarnishApp.libWaffleVarnish \
    --sign ADC03B244F4C1018384DCAFFC920F26136F6B59B \
    --timestamp \
    --requirement='=designated => anchor apple generic and identifier "com.example.apple-samplecode.WaffleVarnishApp.libWaffleVarnish" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = SKMME9E2Y8)' \
    "$APP_DIR"/Contents/Frameworks/libWaffleVarnish.dylib

# Code sign the tool.

codesign -s \
    --force \
    --identifier=com.example.apple-samplecode.WaffleVarnishApp.WaffleVarnishTool \
    --sign ADC03B244F4C1018384DCAFFC920F26136F6B59B \
    --timestamp \
    --requirement='=designated => anchor apple generic and identifier "com.example.apple-samplecode.WaffleVarnishApp.WaffleVarnishTool" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = SKMME9E2Y8)' \
    "$APP_DIR"/Contents/MacOS/WaffleVarnishTool

# Code sign the overall app.

codesign -s \
    --force \
    --identifier=com.example.apple-samplecode.WaffleVarnishApp \
    --sign ADC03B244F4C1018384DCAFFC920F26136F6B59B \
    --timestamp \
    --requirement='=designated => anchor apple generic and identifier "com.example.apple-samplecode.WaffleVarnishApp" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = SKMME9E2Y8)' \
    "${APP_DIR}"

echo "done"
echo "$APP_DIR"

Thanks, I think that must have been the issue. I told pyinstaller to make a single-file app, which packages everything up into one executable file, and that worked (although I did have to do some tweaking to get it to run). Before there was all sorts of stuff scattered all over the place in the MacOS directory (although a lot of it was symlinks), so it's quite likely something was in the wrong place. Thanks again!