如何公证macOS应用

509 阅读3分钟

macOS从Catalina版本后,引入了称为Gatekeeper的安全机制。该机制会阻止任何非App Store下载的,且未经过公证(notary)的应用运行。

那公证是要做什么事呢?从官方文档中,我们可以了解到公证不是应用审核,而是一项全自动的安全扫描。开发者提交应用到公证服务后,公证服务会自动检查:

  1. 代码签名是否正确、有效。
  2. 应用是否包含任何已知恶意软件。

如果一切顺利,公证服务会针对本次提交的应用生成一个标签(ticket)。可以将该标签下载后附加到应用中,该过程称为封印(staple)。当然封印这一步并不是必须的。

之后在应用被运行时,Gatekeeper会首先检查该应用中是否包含有效的封印。如果没有,则尝试通过应用的签名信息查询后台的公证服务中是否有对应的标签。如果仍然没有,则弹出对话框警告用户该应用无法运行。可见,封印这一步最好还是提前做,以免用户在网络不好的情况下无法正常打开应用。

Xcode提供了相应工具来帮助完成公证及封印过程。但Xcode 13后,公证工具做了变化。我们一起看一下具体如果进行公证及封印。

事前准备

  1. 一个用Develop ID证书签名的应用安装包(dmg或者pkg都可)。
  2. 创建一个App专用密码,具体教程见这儿

Xcode 13之前

Xcode 13版本之前是用altool来进行公证的。为安全起见,我们将App专用密码导入到钥匙串中保存。在终端窗口执行以下命令:

xcrun altool --store-password-in-keychain-item for-altool -u <username> -p <password> --sync
  • username:Apple开发者账号登录邮箱
  • password:App专用密码

用于公证的示例脚本如下:

#! /bin/bash
set -euaxo pipefail

base=`dirname $0`
if [ "$#" != "4" ]; then
    echo 'Usage: `basename $0` <filePath> <keychainItem> <teamId> <primaryBundleId>'
    exit 1
fi

filePath="$1"
keychainItem=$2
teamId=$3
primaryBundleId=$4

result=$(xcrun altool --notarize-app --primary-bundle-id "$primaryBundleId" --team-id "$teamId" -p "@keychain:${keychainItem}" --file "$filePath")
IN=$(echo ${result} | grep -o "RequestUUID = \S*")
arrIN=(${IN// = / })
RequestUUID=${arrIN[1]}

while true
do
    sleep 120
    result=$(xcrun altool --notarization-info $RequestUUID -p "@keychain:${keychainItem}")
    IN=$(echo ${result} | grep -o "Status: \S*")
    arrIN=(${IN//: / })
    Status=${arrIN[1]}
    if [ "$Status" = "success" ]; then
        echo "Notary successful."
        break
    fi
    if [ "$Status" = "invalid" ]; then
        echo "Notary failed."
        exit 1
    fi
done

xcrun stapler staple $filePath
echo "Stapler successful."

脚本需要4个输入参数:

  • filePath:待公证的应用安装包,可以是dmg或者pkg格式的。
  • keychainItem:我们开始创建的钥匙串项,即for-altool
  • teamId:如果开发者账号和多个team关联,需要提供使用的team id。
  • primaryBundleId:App的bundle id。

Xcode 13及之后

Xcode 13版本引入了一个新工具:notarytool。个人感觉最大的变化就是,不再需要轮询查询公证结果了。你可以选择等待公证执行完毕后命令再结束,或者公证完毕后回调一个webhook地址。

首先还是先将密码等信息存入钥匙串,执行以下命令:

xcrun notarytool store-credentials --apple-id "<apple-id>" --team-id "<team-id>" --password "<password>" --sync for-notarytool
  • apple-id:Apple开发者账号登录邮箱
  • team-id: Apple开发者账号所属team id
  • password:App专用密码

公证用脚本如下:

#! /bin/bash
set -euaxo pipefail

base=`dirname $0`
if [ "$#" != "2" ]; then
    echo 'Usage: `basename $0` <filePath> <keychainProfile>'
    exit 1
fi

filePath="$1"
keychainProfile=$2

result=$(xcrun notarytool submit --keychain-profile $keychainProfile --no-progress --wait --timeout 1h $filePath | tee /dev/tty)

IN=$(echo ${result} | grep -o "^\S*status: \S*")
arrIN=(${IN//: / })
Status=${arrIN[1]}
if [ "$Status" = "Accepted" ]; then
    echo "Notary successful."
else
    echo "Notary failed."
    exit 1
fi

xcrun stapler staple $filePath
echo "Stapler successful."

脚本需要2个输入参数:

  • filePath:待公证的应用安装包,可以是dmg或者pkg格式的。
  • keychainProfile:我们开始创建的钥匙串项,即for-notarytool