[Note: Skip to Update below if the background info is of no concern to you]
I have an open source accounting software for Windows and had a Mac port built by Codeweavers (Wine and Crossover working under the hood). They are smart, nice, and helpful people but have zero documentation for their customers on their PortJump product (and there is nothing to find elsewhere on the internet, although Codeweavers claim to have made ports a thousand times). Maybe my question is too noobish for them to even grasp my situation. So I tried for myself for months now and desperation leads to longer and longer gaps between my feeble attempts.
I have an .app package in a .zip file you can find here if you want:
https://www.codeweavers.com/xfer/oems/EasyCashTax/easyct-2.38.3-unsigned.zip
Access Code: MUTmlUVm
On the Apple developer portal, I created a distribution identity with the Certificate Name "Thomas Mielke" and a provisioning profile for app id "de.easyct.easyct". (I also have a X.509 software code signing certificate from an official CA, if needed.)
Maybe the first thing I should do is to sign the code, like in this question: codesign --deep on mavericks xcode 5.0 (5A1412)
Or maybe this shouldn't be the first step at all... I am completely alienated by the whole Mac environment and always feel like there are too many open questions at once to just start hacking (why is this a zip and no dmg? how deep do I have to sign and with what options? why can't this be a project I can simply open in Xcode and sign using the Organizer?).
Maybe someone could guide me to a safe place for me where I can start to feel comforable and enter into happy try&error loops... Or, in other words: If you would have to maintain a PortJump package, what would be your approach: git repo, homebrew something, Xcode something, shell scripting or use other software?
Is there a Mac developer out here who can show me the beauty and power of developing on MacOS?
Update:
I now got this script to sign my package:
#!/bin/bash
MAC_SIGNING_IDENTITY="Developer ID Application:"
entitlements="wine32on64.entitlements"
app="$1"
product_id=
bundle_id=
SRCROOT=.
if [ ! -f $entitlements ]
then
echo "$entitlements not found. Make sure it's in your working directory."
exit 1
fi
if [ -z "$app" ]
then
echo "You must specify the absolute path to the .app"
exit 1
fi
if [ ! -d "$app" ]
then
echo "The path You specify is invalid. Please provide the absolute path to the .app"
exit 1
fi
if [[ ! "$app" = /* ]]
then
echo "The path you specified is not an absolute path. Please provide the absolue path to the .app"
exit 1
fi
if [ -z "$bundle_id" ]
then
bundle_id=`defaults read "$app/Contents/Info.plist" CFBundleIdentifier`
if [ -z "$bundle_id" ]
then
echo "Could not determine the product name from '$app'. Did you provide the absolute path to the .app?"
exit 1
fi
fi
echo "Bundle ID = \"$bundle_id\""
if [ -z "$product_id" ]
then
product_id=`ls -d "$app/Contents/SharedSupport"/* | grep -v '/X11'`
if [ ! -d "$product_id" ]
then
echo "could not determine the product id from '$app'"
exit 1
fi
product_id=`basename "$product_id"`
echo "$product_id" | LC_ALL=C egrep '^[a-zA-Z0-9_][a-zA-Z0-9_][a-zA-Z0-9_][a-zA-Z0-9_][a-zA-Z0-9_]*$' >/dev/null
if [ $? -ne 0 ]
then
echo "the product id '$product_id' is not valid"
exit 1
fi
fi
echo "Product ID = \"$product_id\""
keychain=$(security find-certificate -c "$MAC_SIGNING_IDENTITY" | grep keychain | awk 'gsub(/"/, "", $2) {print $2}')
locked=$(security show-keychain-info "$keychain" 2>&1 | grep "timeout")
if [ -z "$locked" ]
then
echo "Failed to find unlocked keychain with required certificate. Is your certificate in an unlocked keychain in your keychain search path?"
echo "Your keychain search path is:"
security list-keychain
exit 1
fi
if [ "$MAC_SIGNING_IDENTITY" != "-" ] ; then
# Figure out the Organizational Unit (OU) from the signing identity
ou=$(
set -x
security find-certificate -p -c "$MAC_SIGNING_IDENTITY" | \
openssl x509 -inform PEM -subject -noout -nameopt sname,sep_multiline,space_eq | \
awk '/ OU = / {print $3}'
)
if [ -z "$ou" ]; then
echo "error: Could not determine OU from signing identity '$MAC_SIGNING_IDENTITY'"
exit 1
fi
fi
set -e
# Sign the app. The designated requirements were obtained by watching what Xcode 4.3
# does when it signs for Developer ID.
function sign_one()
{
file="$1"; shift
identifier="$1"; shift
if [ "$MAC_SIGNING_IDENTITY" = "-" ] ; then
codesign --sign "$MAC_SIGNING_IDENTITY" \
--force \
"$file" "$@"
else
codesign --sign "$MAC_SIGNING_IDENTITY" \
--force \
--requirements "=designated => anchor apple generic and identifier \"$identifier\" \
and ((cert 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] = \"$ou\" \
))" \
"$file" "$@"
fi
}
function sign_subdir()
{
subdir="$1" ; shift
id_component="$1" ; shift
find "$subdir/" -type f \( -name "*.so" -o -name "*dylib" -o -exec sh -c 'file "$0" | fgrep -qsw Mach-O' {} \; \) -print0 |
while IFS= read -r -d '' file ; do
name=$(basename "$file")
name="${name//[^-a-zA-Z0-9]/-}"
if [ -z "${name/#[^a-zA-Z]*}" ] ; then
name="a-$name"
fi
if [ -z "${name/%*[^a-zA-Z0-9]}" ] ; then
name="$name-0"
fi
identifier="$bundle_id.$id_component.$name"
sign_one "$file" "$identifier" --identifier "$identifier" "$@"
done
}
set -x
# Sign Sparkle framework and pyobjc bundle separately from the app bundle
if [ -d "$app/Contents/Frameworks/Sparkle.framework" ]; then
sign_one "$app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/finish_installation.app" "org.andymatuschak.sparkle.finish-installation" --options runtime
sign_one "$app/Contents/Frameworks/Sparkle.framework" "org.andymatuschak.Sparkle"
fi
sign_subdir "$app/Contents/SharedSupport/$product_id/bin" "bin" --options runtime
for libdir in "$app/Contents/SharedSupport/$product_id"/lib* ; do
sign_subdir "$libdir" "$(basename "$libdir")"
done
# The wine (pre)loaders were already signed with the bin directory, above, but
# we need to re-do it with entitlements
for i in "$app/Contents/SharedSupport/$product_id/bin"/wine*loader*; do
sign_one "$i" "$bundle_id.wineloader" \
--options runtime \
--entitlements "$SRCROOT/wine32on64.entitlements"
done
sign_one "$app" "$bundle_id" --options runtime --entitlements "$SRCROOT/wine32on64.entitlements"
The entitlements file wine32on64.entitlements:
<?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.security.automation.apple-events</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.ldt-in-64bit-process</key>
<true/>
</dict>
</plist>
And this one does the notarization:
#!/bin/bash
ditto -c -k --keepParent EasyCT.app EasyCT.zip
output=$(xcrun altool --notarize-app --primary-bundle-id de.easyct.easyct --asc-provider "MYTEAMID" -u "my@apple.id" -p "abcd-efgh-ijkl-mnop" --file EasyCT.zip)
ticket_id=$(echo "$output" | grep RequestUUID | awk '{print $3}')
if [ -z "$ticket_id" ]
then
echo "Error: No ticket id was returned.\n\n$output"
exit 1
fi
echo "Notarization ticket: $ticket_id"
xcrun altool --notarization-info "$ticket_id" -u "my@apple.id" -p "abcd-efgh-ijkl-mnop"
xcrun stapler staple EasyCT.app
spctl --assess --type open --context context:primary-signature --verbose EasyCT.zip
Everything runs smooth, only the last line that checks the resulting packet using spctl --assess
returns me "no usable signature". Also, after downloading the package, Gatekeeper still requires a security exception.
The problem turned out to be multi-factor: First, there were two symbolic links included in the package. codesign
didn't bother but spctl -a
didn't like it. I subsequently tested the .zip package with spctl
, which didn't pass the package even after the symlinks were deleted. At some point I tried to spctl -a
the uncompressed .app folder, which worked. So spctl
doesn't like .zip packages, it seems.
Here is a tidied-up version of the script I now use to sign and notarize the package:
#!/bin/bash
# read config
echo "Reading myappleid.config"
. ./myappleid.config
if [ -z "$appleid" ]
then
echo "Error: Please add an entry 'appleid=<your_appleid_here>' to myappleid.config"
exit 1
fi
if [ -z "$aspw" ]
then
echo "Error: Please add an entry 'aspw=<your_app_specific_password_here>' to myappleid.config"
exit 1
fi
echo "Notarisation will use apple id '$appleid'"
# unzip archive to EasyCash&Tax.app folder
echo "Unzipping original package..."
rm -rf EasyCash\&Tax.app
unzip -q easyct-2.38.3-unsigned.zip
# delete symlinks that would prevent Gatekeeper/spctl from passing otherwise
echo "Deleting symlinks"
rm EasyCash\&Tax.app/Contents/SharedSupport/easyct/support/easyct/drive_c/users/crossover/Downloads
rm EasyCash\&Tax.app/Contents/SharedSupport/easyct/support/easyct/drive_c/users/crossover/Templates
# sign the package using Codeweavers script
echo "Signing package..."
./sign_codeV4 $(pwd)/EasyCash\&Tax.app
# archive signed package to a zip for notarisation
ditto -c -k --keepParent EasyCash\&Tax.app EasyCT.zip
# prepare option if team id was set
if [ -z "$ascprov" ]
then
ascprovoption=
else
ascprovoption="--asc-provider"
fi
output=$(xcrun altool --notarize-app --primary-bundle-id de.easyct.easyct $ascprovoption $ascprov -u "$appleid" -p "$aspw" --file EasyCT.zip)
ticket_id=$(echo "$output" | grep RequestUUID | awk '{print $3}')
if [ -z "$ticket_id" ]
then
echo "Error: No ticket id was returned.\n\n$output"
exit 1
fi
echo "Notarization ticket: $ticket_id"
xcrun altool --notarization-info "$ticket_id" -u "$appleid" -p "$aspw"
echo "Stapeling..."
output=$(xcrun stapler staple EasyCash\&Tax.app)
echo $output
stapling_worked=(echo "$output" | grep "The staple and validate action worked")
if [ -z "$stapling_worked" ]
then
echo "Error: stapling didn't work, it seems. Try to run 'xcrun stapler staple EasyCash\\\&Tax.app' and zip EasyCT4Mac.zip manually."
exit 1
fi
echo "Checking stapled EasyCash&Tax.app folder using spctl -a..."
spctl --assess --type open --context context:primary-signature --verbose EasyCash\&Tax.app
echo "Final packaging to EasyCT4Mac.zip..."
ditto -c -k --keepParent EasyCash\&Tax.app EasyCT4Mac.zip
# clean-up temporary zip, used for notarisation
rm EasyCT.zip
Hope this might help someone with similar problems.