MacOS:为什么 PortJump 包上的 'spctl --assess' return 'no usable signature' 在签名和公证后看似成功?

MacOS: Why does 'spctl --assess' return 'no usable signature' on a PortJump package, after signing and notarizing have seemingly succeeded?

[注: 如果背景信息无关紧要,请跳至下面的更新给你]

我有一个用于 Windows 的开源会计软件,并且有一个由 Codeweavers 构建的 Mac 端口(Wine 和 Crossover 在后台工作)。他们是聪明、友善和乐于助人的人,但他们的 PortJump 产品的客户文档为零(尽管 Codeweavers 声称已经制作端口一千次,但在互联网上的其他地方找不到任何东西)。也许我的问题太菜鸟了,他们甚至无法了解我的情况。所以我现在为自己尝试了几个月,绝望导致我的无力尝试之间的间隔越来越长。

我有一个 .zip 文件中的 .app 包,如果需要,您可以在这里找到:

https://www.codeweavers.com/xfer/oems/EasyCashTax/easyct-2.38.3-unsigned.zip

访问码:MUTmlUVm

在 Apple 开发人员门户上,我使用证书名称创建了分发标识 “Thomas Mielke”和应用程序 ID“de.easyct.easyct”的配置文件。 (如果需要,我还有来自官方 CA 的 X.509 软件代码签名证书。)

也许我应该做的第一件事就是签署代码,就像这个问题一样: codesign --deep on mavericks xcode 5.0 (5A1412)

或者这根本不应该是第一步...我完全被整个 Mac 环境所疏远,并且总觉得一次有太多未解决的问题而无法开始黑客攻击(为什么这是一个 zip 而不是 dmg?我必须签署多深以及有哪些选项?为什么这不能成为一个我可以简单地在 Xcode 中打开并使用 Organizer 签署的项目?)。

也许有人可以引导我到一个安全的地方,在那里我可以开始感到舒服并进入快乐的尝试和错误循环......或者,换句话说:如果你必须维护一个 PortJump 包,什么会是你的方法:git 回购,自制软件,Xcode,shell 脚本或使用 other software?

这里有 Mac 开发人员可以向我展示在 MacOS 上开发的魅力和力量吗?

更新:

我现在得到这个脚本来签署我的包裹:

#!/bin/bash

MAC_SIGNING_IDENTITY="Developer ID Application:"
entitlements="wine32on64.entitlements"
app=""
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(/"/, "", ) {print }')
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 }'
    )

    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=""; shift
    identifier=""; 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="" ; shift
  id_component="" ; shift
    
  find "$subdir/" -type f \( -name "*.so" -o -name "*dylib" -o -exec sh -c 'file "[=13=]" | 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"

权利文件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>

而这个做公证的是:

#!/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 }')

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

一切运行顺利,只有最后一行检查 resulting packet 使用 spctl --assess returns 我“没有可用的签名”。还有,下载包后,Gatekeeper还是需要安全例外。

问题是多方面的:首先,包中包含两个符号链接。 codesign 不介意,但 spctl -a 不喜欢。我随后用 spctl 测试了 .zip 包,即使删除了符号链接也没有通过包。在某些时候,我尝试 spctl -a 未压缩的 .app 文件夹,它起作用了。所以 spctl 似乎不喜欢 .zip 包。

这是 script 我现在用来签署和公证包裹的整理版本:

#!/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 }')

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

希望这对遇到类似问题的人有所帮助。