ゼロから始めるmacOSアプリの署名と公証 (独自配信用)

冒頭の画像はとある企業が配布するdmgファイルに入っているmacOSアプリをダブルクリックすると表示されたウィンドウです。 公証の対応を後回しにせざるを得なかったと想像します。

企業がMac App Store以外で独自に配信・配布するmacOSアプリで 公証 が未実施だったり、 そもそもアプリに 署名 をしていないと思われる場面に遭遇します。

公証 についてAppleはWWDC 2018から周知していますが、 macOSアプリのデペロッパー側としては予算や人的リソース面 (e.g. macOSアプリ専任者を体制に入れ続けるのが相当きつい) といった理由があるのかもしれません。

本稿では独自にmacOSアプリを配信する方向けにゼロからスタートの方でも活用できるようmacOSアプリの 署名公証 に必要な手順と手続きをまとめています。

環境によってはうまくいかないこともあるかもしれません。その場合はぜひとも教えてください。 Twitterアカウント @hiroakit にメンションを頂けたら幸いです。

はじめに

AppleはmacOSのセキュリティ強化の施策として以下を実施しています。

  1. Gatekeeperを導入。Mac App Store以外で入手したmacOSソフトウェアのDeveloper ID証明書を確認し、悪意のあるソフトウェアからエンドユーザーを保護する
  2. Apple Nortary Serviceを導入。前項のGatekeeperがNortary ServiceサーバーにmacOSソフトウェアの信頼性を照会することで悪意のあるソフトウェアからエンドユーザーを保護する

    • macOSがインターネットに繋がっていない場合はmacOSソフトウェアに添付済みの公証結果の値を確認することで対応する

本稿はEmacs.appを例にして上記2点に対応するmacOSアプリにしていきます。

習得内容

本稿では以下の手順が習得可能です。

  1. Developer ID Installer証明書の発行手順
  2. Developer ID Application証明書の発行手順
  3. entitlements.plistの作成手順
  4. macOSアプリの署名手順
  5. macOSアプリの公証申請手順

App用パスワード

本稿でApple ID管理画面で発行できるApp用パスワードが必要になります。 そのパスワード発行手順は以下リンク先にまとまっていますので別途ご用意ください。

特記事項としてはApple IDのパスワード変更またはリセットすると、 アカウントを保護するために発行したApp用パスワードがすべて無効になりますのでご注意ください。

App用パスワードのキーチェーン登録

App用パスワードを入手したらキーチェーンに登録しておくことをおすすめします。 これは今後入力するコマンドでApp用パスワードが履歴に残らないようにするためです。

1
2
3
$ security add-generic-password -s "AppStoreConnect" -a "${YOUR_APPLE_ID}" -w
password data for new item: "${YOUR_APP_PASSWORD}"
retype password for new item: "${YOUR_APP_PASSWORD}"

xcrun altool --store-password-in-keychain-item でも同様のことができるようですが別に譲ることにします。

対象読者

本稿は以下の環境を持つ読者を対象にしています。

  1. macOS v10.15.4 (macOS Catalina)が動くMacがあること
  2. Xcode v11.4をインストール済みであること

  3. Apple Developer Program (Individual)を契約した際に使用したApple IDを利用できること

    • Developer ID Application & Installer 証明書の取得する際に必要です
  4. 上記のApple ID宛に届くAppleからのメールを閲覧できること

    • 公証結果が記載されたメールが届きます

注意

本稿で紹介する公証手順は法人格のApple Developer Programを考慮しておりません。

そのため、Apple Notary ServiceにmacOSアプリをアップロードする際に必要な ProviderShortName には触れておりませんので、その点はご注意ください。

公証とは

macOSはGetekeeperを通じてmacOSソフトウェアのコード署名などに問題がないか Apple Notrory Serviceに問い合わせて確認をしますが、これが成立するのは事前に同サービスにソフトウェアの信頼性を登録済みだからです。

Appleの 公証 はそのソフトウェアの信頼性を登録するために設けた開発プロセスの1つです。 未対応の場合、ソフトウェアのインストールからエンドユーザーの体験が進まない可能性がありますので、 ビジネスでソフトウェア開発をしている方は必ずご自身らの開発プロセスに公証対応を組み込むことを検討してください。

AppleはmacOSソフトウェアデペロッパーに対して以下の要求を提示しています。

Beginning in macOS 10.14.5, software signed with a new Developer ID certificate and all new or updated kernel extensions must be notarized to run. Beginning in macOS 10.15, all software built after June 1, 2019, and distributed with Developer ID must be notarized. However, you aren’t required to notarize software that you distribute through the Mac App Store because the App Store submission process already includes equivalent security checks.

出典: Notarizing macOS Software Before Distribution

上記のようにmacOS Catalinaにおいては2019年6月1日以降にビルドされ、 Developer IDで配布されるすべてのソフトウェアは公証が必須となっています。

Appleが公証対象となるmacOS用ソフトウェアは以下の通りです。

  • macOSアプリ
  • カーネル拡張などの非アプリバンドル
  • ディスクイメージ(UDIF形式)
  • フラットインストーラーパッケージ

本稿では上記のうちmacOSアプリのみを取り扱います。

macOSアプリの公証通過要件

「はじめに」の通り、本稿ではmacOSアプリに対する署名と公証に焦点を絞ります。

macOSアプリが公証を通過するには以下の要件を満たす必要があります。

  1. macOS 10.9以上のSDKでビルドをすること (出典)

    • 公証はmacOS 10.9以降に対してリンクされたバイナリに対してのみ機能します。古いSDKを使用すると公証に失敗します
    • macOS 10.9以上のSDKでビルドします。場合により実装に修正が必要になります
  2. アプリ内のすべての実行ファイルにコード署名をすること (出典)

    • 配布するアプリに含めるすべての実行可能ファイルが対象です
    • codesign コマンドで対応します
  3. Developer ID証明書でコード署名をすること (出典1, 出典2)

    • Developer ID Application (Kernel Extension | System Extension), Installer 証明書でコード署名することをAppleが要求しています
    • Developer ID Application証明書を使うケース

      • Mach-Oファイル、ディスクイメージ、バンドル、アプリ、コマンドラインツール、写真などのアイテム
      • カーネル拡張(kext)の配布はAppleは推奨していませんが、kext機能を持つDeveloper ID Application証明書を使用して配布することは可能です
    • Developer ID Installer証明書を使うケース

      • インストーラーパッケージ
    • なお、コード署名ではMac Distribution, Ad hoc, Apple Developerらの証明書はAppleの規定により使用しないでください
    • codesign あるいは productsign コマンドでDeveloper ID証明書を指定して対応します
  4. セキュアタイムスタンプをコード署名時に含めること (出典)

    • Appleの要求事項です
    • codesign コマンドの引数 -–timestamp で対応します
  5. Hardened Runtimeに対応していること (出典)

    • Hardened RuntimeはSystem Integrity Protection1、コードインジェクション、ダイナミックリンクライブラリハイジャック、プロセスメモリ空間の改ざん、特定のクラスのエクスプロイトなどの防止を目的にした強化されたソフトウェアランタイムです
    • アプリが機能するために必要な資格を entitlement.plist に記載することでアプリ開発者が意図した制限緩和なのか明示します
    • codesign コマンドで runtime オプションと上述の entitlement.plist を指定して対応します
  6. Get-Task-AllowをEntitlementに含めていないこと (出典)

    • System Integrity Protectionが有効なmacOSでは開発時にデバッグを容易にするために Get-Task-Allow (com.apple.security.get-task-allow) を利用しますが、この状態のままアプリを出荷すると攻撃者が実行時にコードを挿入する可能性があります
    • そのため、出荷するアプリでは同項目を無効2にしてセキュリティリスクを抑えます
    • 前述の entitlement.plist と併せて対応します

Developer ID Application & Installer 証明書の取得

まず、以下の電子証明書を用意します。

  • Developer ID Installer証明書
  • Developer ID Application証明書

上記の証明書はApple Developer Programのウェブサイトで発行可能ですのでそちらで作業をします。 (Xcode 11で発行可能ですが、諸事情により同アプリから発行が難しい方が多いだろうと想像したのでウェブサイトにしています)

Apple Developer Programで証明書の発行

Apple Developer Program4のサイトにアクセスします。

./images/figure-01.png

左メニューにある Certificates, IDs & Profiles をクリックします。

./images/figure-14.png

下図で赤枠で囲っているプラスボタンをクリックします。

./images/figure-13.png

以下の画面に移りますので発行したい証明書の種類を選びます。 ずらっと選択肢が列挙されているので一瞬戸惑うかもしれませんが、 Developer ID Application を選択しContinueボタンをクリックします。 なお、別ページに証明書の種類を紹介しているAppleの日本語ヘルプ5があります。

./images/figure-03.png

さて、先ほどの画面でContinueボタンを押すと下図の画面に移動します。 その画面ではCertificate Signing Request (以後、CSRと略します) をアップロードするのですが、 お手元にはそのファイルがないと思います。

./images/figure-12.png

ということで作ります。

Certificate Signing Requestの作成

CSRを作成するにはキーチェーンアクセスを使います。

キーチェーンアクセスを起動してメニューから 認証局に証明書を要求... をクリックします。(下図参照)

./images/figure-05.png

次に以下の項目を入力ないし選択をします。(下図参照)

  • ユーザのメールアドレス: 任意のメールアドレス
  • 通称: 任意な文字列を入れます

    • 通称は任意で構わないのですが、キーチェーンアクセスで表示される名前になるので気をつけておかないと同じ名前でどれがどれだかわかりづらくて発狂します(事例)
  • 要求の処理: ディスクに保存 を選択して、 鍵ペア情報を指定 にチェックを入れます

./images/figure-06.png

CSRの保存場所を指定します。

./images/figure-08.png

次に公開鍵と秘密鍵を作成します。鍵のサイズは2048ビット6、アルゴリズムはRSAにします。続けるボタンをクリックします。

./images/figure-07.png

以上の作業で公開鍵と秘密鍵がキーチェーンアクセスに追加されました。

./images/figure-11.png

アプリをウェブブラウザに切り替えてApple Developer ProgramのCertificates, IDs & Profilesに戻ります。

Developer ID Application 証明書の発行

Choose File(下図赤枠)で先ほど作成したCSRファイルを指定します。 ファイルを指定したらContinueボタンをクリックします。

./images/figure-12.png

ちなみに先のキーチェンアクセスの作業で鍵のサイズに2048ビットを選択せずに CSRをアップロードすると下図の表示になります。

./images/figure-15.png

さて、CSRファイルが無事にアップロードできると下図の画面に遷移します。 この画面で Developer ID Application 証明書が作成されたことがわかります。 Downloadボタンをクリックすると証明書をダウンロードできます。

./images/figure-17.png

ダウンロードしたらお手元に developerID_application.cer がありますから、それをダブルクリックします。 そうすると下図の画面が表示されるのでキーチェーンを「ログイン」にしてから追加ボタンをクリックして キーチェーンアクセスに取り込みます。

./images/figure-18.png

以上の作業でキーチェーンアクセスの秘密鍵と developerID_application.cer が紐づきます。 (余談になりますが、CSR作成時に入力する 通称 は確かに任意の文字列で構わないのですが、 上図のようにキーチェーンアクセスでの表示名に使われるので、その点を考慮しておかないと同じ名前だらけになり、 パッと見ただけではどれがどの証明書なのかわからず発狂します。)

./images/figure-16.png

Developer ID Installer 証明書の発行

Developer ID Installerの発行はApple Developer Programの Certificates, IDs & Profiles で 証明書の種類を選択する画面でDeveloper ID Installerを選び作業を進めます。その後の手順は前述とおりです。 なお、CSRファイルは同じ人物が管理するなら使いまわしして構いません。

Developer ID Application & Installer 証明書取り込み後

Developer ID Application & Installer証明書をキーチェーンアクセスに各々取り込むと下図のようになります。

./images/figure-19.png

これでアプリに署名をするための下準備が出来上がりました。 ここからが長いです・・・。

Developer ID Application & Installer 証明書でアプリを署名

それではアプリのコード署名に入ります。

まず、ディレクトリ構成を以下に示します。この構成に沿ってEmacs.appの.pkgを作成します。 最終成果物が Emacs-Distribution_SIGNED.pkg です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 開始時
. # ${WORK_DIR_ROOT}
├── entitlements.plist
└── pkg
    └── Applications
       └── Emacs
            └── Emacs.app


# 終了時: 本稿の公証手順をすべて実施したとき
. # ${WORK_DIR_ROOT}
├── entitlements.plist
└── pkg
    ├── Applications
    │   └── Emacs
    │       └── Emacs.app
    ├── Distribution.xml
    ├── Emacs-Distribution.pkg
    ├── Emacs-Distribution_SIGNED.pkg
    ├── Emacs.pkg
    └── packages.plist

例として使用する Emacs.app は下記からダウンロードしてください。

Developer ID証明書でアプリに署名を入れるには codesign 7コマンドを使います。 引数 --entitlements で指定する entitlements.plist は後述します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cd ${WORK_DIR_ROOT}
codesign --verify \
         --sign "Developer ID Application: ${DEVELOPER_ID}" \
         --deep \
         --force \
         --verbose \ 
         --option runtime \
         --entitlements entitlements.plist \
         --timestamp \
         ./pkg/Applications/Emacs/Emacs.app

DEVELOPER_ID はキーチェーンアクセスで確認します。(下図赤枠参照のこと)

./images/figure-20.png

コマンド codesign の引数を下表で補足します。

引数 解説
–verify コード署名を検証します。なお、 --sign の後ろに記述すると意図しない動きになります。
–sign 指定した署名IDで指定先ファイルを署名します。
–force 既存の署名があった場合に書き換えます。なお、このオプションを指定していない場合にその状況に遭遇すると署名が失敗します。
–verbose 標準出力の情報量を増やします。
–deep 再帰的に署名します。アプリにバンドルするフレームワークなども署名の対象になります。
–options 署名時に埋め込むオプションを指定します。本稿の例ではHardened Runtime対応のため runtime を指定しますが、host, libraryなどがあります。
–entitlements 署名時に埋め込む資格情報ファイルを指定します。(フォーマットは後述します。)
–timestamp Appleのタイムスタンプサーバーからセキュアなタイムスタンプを取得し署名時にアプリに埋め込みます。

引数 --entitlements で指定する entitlements.plist は以下の例のようなフォーマットで記載します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>com.apple.security.network.client</key>
        <true/>
        <key>com.apple.security.cs.disable-library-validation</key>
        <true/>
    </dict>
</plist>

キーについては下表で補足します。

キー 解説
com.apple.security.network.client サンドボックスアプリが別のコンピューターあるいはローカルホストで実行中のサーバープロセスへの接続を許可するブール値。trueで許可、falseでそれ以外を示します。
com.apple.security.cs.disable-library-validation アプリがコード署名が施されていないプラグインまたはフレームワークを読み込みできるかを示すブール値。trueで許可、falseでそれ以外を示します。(もちろん false のほうが望ましいのでしょうけど、必ずしも常にその対応ができるとは限りません。)

コード署名後に pkgutil --check-signature で結果を確認します。以下に失敗例と成功例を示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 失敗例
# cd ${WORK_DIR_ROOT}
$ pkgutil --check-signature ./pkg/Applications/Emacs/Emacs.app
Package "Emacs.app":
   Status: no signature

# 成功例
# cd ${WORK_DIR_ROOT}
$ pkgutil --check-signature ./pkg/Applications/Emacs/Emacs.app
Package "Emacs.app":
   Status: signed by a certificate trusted by macOS
   Certificate Chain:
    1. Developer ID Application: Sam Porter Bridges (ABCDEF1234)
       Expires: 2025-04-29 13:15:31 +0000
       SHA256 Fingerprint:
           00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
           00 00 00 00 00 00 00 00 00 00
       ------------------------------------------------------------------------
    2. Developer ID Certification Authority
       Expires: 2027-02-01 22:12:15 +0000
       SHA256 Fingerprint:
           00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
           00 00 00 00 00 00 00 00 00 00
       ------------------------------------------------------------------------
    3. Apple Root CA
       Expires: 2035-02-09 21:40:36 +0000
       SHA256 Fingerprint:
           00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
           00 00 00 00 00 00 00 00 00 00

以上の作業でmacOSアプリの署名が完了しました。次は配布用パッケージを作成します。

アプリのパッケージング

Mac App Storeを除くと、macOSアプリの配布方法は以下の選択肢があります。

  1. zip形式で配布する

    • 後述のステープラ作業が手間になるので私はオススメしません。(ツールやフレームワーク側が処理するなら、前提が異なるのでもちろん別です)
  2. dmg形式で配布する

    • dmg形式の利点を把握できていないため本稿では割愛します。選択肢としてあるということだけにとどめ、別の機会に書き起こしたいと思います。
  3. pkg形式で配布する

    • いわゆるインストーラー。一番無難で楽です。

本稿ではインストーラーとして振る舞うpkg形式を採用します。 pkg形式は以下の流れで作成します。

  1. pkgbuildでpkgファイルの作成
  2. productbuildでpkgファイルを配布形式に変換

詳しくは次の節で解説します。

pkgbuildでpkgファイルの作成

pkgファイルに含めるファイルをplistに書きます。下記コマンドを実行すると plist-output-path にそのplistが入手できます。

1
2
cd pkg
pkgbuild --analyze --root Applications packages.plist

引数については下表で補足します。

引数 解説
–analyze --root で指定したパスからテンプレートコンポーネントプロパティリスト8を作成します。
–root パッケージング対象のファイルが存在するディレクトリを指定します。

上記コマンドの出力結果例は以下の通りです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?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">
<array>
        <dict>
                <key>BundleHasStrictIdentifier</key>
                <true/>
                <key>BundleIsRelocatable</key>
                <true/>
                <key>BundleIsVersionChecked</key>
                <true/>
                <key>BundleOverwriteAction</key>
                <string>upgrade</string>
                <key>RootRelativeBundlePath</key>
                <string>Emacs/Emacs.app</string>
        </dict>
</array>
</plist>

書き換えます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?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">
<array>
        <dict>
                <key>BundleHasStrictIdentifier</key>
                <true/>
                <key>BundleIsRelocatable</key>
                <false/> <!-- true から false に変更 -->
                <key>BundleIsVersionChecked</key>
                <true/>
                <key>BundleOverwriteAction</key>
                <string>upgrade</string>
                <key>RootRelativeBundlePath</key>
                <string>Emacs/Emacs.app</string>
        </dict>
</array>
</plist>

キーについては下表で補足します。

キー 解説
BundleHasStrictIdentifier 正直、よくわからない。 man pkgbuild によるとインストール先に同一のバンドルIDが必要かどうかを示すブール値のようです。
BundleIsRelocatable 古いバージョンをインストール済みの場合、それを上書きするように振る舞うかを示します。trueで上書き、falseで上書きをせずインストーラーのディレクトリ構成に従います。trueにしておくと意図しない結果になりがちなので、大体の場合はfalseにします。この項目はエンドユーザーがアプリの配置場所を変更する可能性が高い場合にtrueにします。
BundleIsVersionChecked 新しいバージョンがある場合でもインストールをするかどうか。trueで実施します。
BundleOverwriteAction 上書きの仕方を指定します。 upgrade で既存のものをすべて削除して差し替えます update は該当ファイルのみ上書きをします。
RootRelativeBundlePath ルートを起点としたときのインストール先。上記の例では Emacs/Emacs.app のため、pkgbuildの–rootの傘下にEmacs/Emacs.appがあることを期待します。
BundlePostInstallScriptPath 省略可。インストール直前に実行するスクリプトの相対パスをpkgbuildで指定する --scripts ディレクトリを起点にして記述します。
BundlePreInstallScriptPath 省略可。インストール直後に実行するスクリプトの相対パスをpkgbuildで指定する --scripts ディレクトリを起点にして記述します。

下記コマンドでpkgファイルを作ります。

1
2
3
4
5
6
7
8
# cd ${WORK_DIR_ROOT}/pkg
pkgbuild Emacs.pkg \
         --root Applications \
         --component-plist packages.plist \
         # --scripts scriptDir \ 
         --identifier com.hiroakit.emacs \ # 例です
         --version 1.0.0 \ # 例です
         --install-location "/Applications"

引数については下表で補足します。

引数 解説
root pkgbuild --analyze と同じディレクトリを指定します
component-plist pkgbuild --analyze が出力したplistファイルを指定します
scripts スクリプトファイルを格納しているディレクトリを指定します。省略可能。
identifier 逆URL形式で記述します (例: com.example.emacs)
version pkgインストーラーのバージョンを記述します
install-location インストール先のフォルダを指定します

pkgファイルが作成できたら、ここで一旦動作確認をしておくといいと思います。 手戻りが防げます。

productbuildでpkgファイルを配布形式に変換

変換に先立ち構造をXMLにして出力します。下記コマンドを用います。

1
2
# cd ${WORK_DIR_ROOT}/pkg
productbuild --synthesize --package Emacs.pkg Distribution.xml

引数については下表で補足します。

引数 解説
–synthesize Write the synthesized distribution directly instead of incorporating it into a product archive.
–package 配布物に入れる.pkgファイルを指定します。本稿の例ではEmacs.pkgです。

productbuild が出力したXMLが以下です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<installer-gui-script minSpecVersion="1">
    <pkg-ref id="com.hiroakit.emacs"/>
    <options customize="never" require-scripts="false"/>
    <choices-outline>
        <line choice="default">
            <line choice="com.hiroakit.emacs"/>
        </line>
    </choices-outline>
    <choice id="default"/>
    <choice id="com.hiroakit.emacs" visible="false">
        <pkg-ref id="com.hiroakit.emacs"/>
    </choice>
    <pkg-ref id="com.hiroakit.emacs" version="1.0.0" onConclusion="none">Emacs.pkg</pkg-ref>
</installer-gui-script>

これだけでは足りないのでDistribution XML Referenceを見ながら書き足していきます。 例えば title 要素は追加しておいた方がいいです。

./images/figure-21.png

未指定の場合、インストーラーのウィンドウのタイトルバーが単語が抜けているように見えます。 書き加えたものがこちら。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<installer-gui-script minSpecVersion="1">
    <title>Emacs</title>
    <allowed-os-versions>
        <os-version min="10.15"/>
    </allowed-os-versions>
    <pkg-ref id="com.hiroakit.emacs"/>
    <options customize="never" require-scripts="false"/>
    <choices-outline>
        <line choice="default">
            <line choice="com.hiroakit.emacs"/>
        </line>
    </choices-outline>
    <choice id="default"/>
    <choice id="com.hiroakit.emacs" visible="false">
        <pkg-ref id="com.hiroakit.emacs"/>
    </choice>
    <pkg-ref id="com.hiroakit.emacs" version="1.0.0" onConclusion="none">Emacs.pkg</pkg-ref>
</installer-gui-script>

準備が整ったらpkgを作ります。

1
2
3
4
5
# Distribution.xmlとEmacs.pkgは同一フォルダ同一階層においておいた方が無難に作業できます。
cd ./pkg
productbuild --distribution Distribution.xml \
             --package-path Emacs.pkg \
             Emacs-Distribution.pkg

引数については下表で補足します。

引数 解説
–distribution (本コマンドの) 成果物によってインストールされる魅た目、選択肢、パッケージを定義します。本稿の例では前工程で生成した Distribution.xml を指定します。
–package-path 前工程で生成した .pkg ファイルを指定します。(本稿の例ではEmacs.pkg)

Developer ID Installer証明書でアプリを署名

Developer ID Installer証明書を使いパッケージに署名します。

1
2
3
productsign --sign "Developer ID Installer: ${DEVELOPER_ID}" \
            Emacs-Distribution.pkg \
            Emacs-Distribution_SIGNED.pkg

次のコマンドでコード署名の結果を確認します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ pkgutil --check-signature Emacs-Distribution_SIGNED.pkg
Package "Emacs-Distribution_SIGNED.pkg":
   Status: signed by a developer certificate issued by Apple for distribution
   Signed with a trusted timestamp on: 2020-05-10 22:02:32 +0000
   Certificate Chain:
    1. Developer ID Installer: Sam Porter Bridges (ABCDEF1234)
       Expires: 2025-04-29 13:37:30 +0000
       SHA256 Fingerprint:
           00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
           00 00 00 00 00 00 00 00 00 00
       ------------------------------------------------------------------------
    2. Developer ID Certification Authority
       Expires: 2027-02-01 22:12:15 +0000
       SHA256 Fingerprint:
           00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
           00 00 00 00 00 00 00 00 00 00
       ------------------------------------------------------------------------
    3. Apple Root CA
       Expires: 2035-02-09 21:40:36 +0000
       SHA256 Fingerprint:
           00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
           00 00 00 00 00 00 00 00 00 00

ここまできたらあとはAppleに公証を依頼するのみです。

署名済みパッケージをApple Nortary Serviceにアップロード

署名したパッケージをApple Nortary Serviceにアップロードします。

security コマンドの引数について下表で補足します。

引数 解説
add-generic-password パスワードを登録します
-s キーチェーンに登録するときの名称
-a アカウント名
-w パスワード。Appleは上の例のようにパスワードはプロンプト上で入力することを推奨しています。

さて、話をApple Nortary Serviceにパッケージをアップロードするところに戻します。 同サービスには以下の制限事項があります。

  • アップロードするサイズは50GBまで (出典)
  • 公証は1日あたり75回まで (CI/CDで公証まで実施する場合は考慮が必要かもしれません)

以下のコマンドで公証をAppleに依頼します。

1
2
3
4
5
xcrun altool --notarize-app\
             --file "Emacs-Distribution_SIGNED.pkg"\
             --primary-bundle-id "com.hiroakit.emacs"\
             --username "${YOUR_APPLE_ID}"\
             --password "@keychain:AppStoreConnect"

しばらくすると応答があります。 この段階ではAppleのサーバー側で処理中の場合があるので、 後述する xcrun altool --notarization-infoxcrun altool --notarization-history でステータスを確認します。

1
2
No errors uploading 'Emacs-Distribution_SIGNED.pkg'.
RequestUUID = 0000000--0000-0000-0000-000000000000 # あくまでも例

xcrun altool の引数を下表で補足します。

引数 解説
–notarize-app 公証を要求していることを示す引数
–file 公証対象のファイル
–primary-bundle-id Apple Nortary Serviceでの処理状況を追跡するためにユニークな文字列を指定します。 文字は英数字(A- Z、a- z、0- 9)、ハイフン(-)、およびピリオド(.)のみ 使用可能
–username App Store Connectの認証情報として使えるApple ID
–password Apple IDのパスワード。2段階認証が有効になっているはずなのでApp用パスワードが必要です。App用パスワードについては App 用パスワードを使う - Apple サポート を参照してください

ステータスを確認します。 success になっていれば完了しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ xcrun altool --notarization-info ${RequestUUID} --username "${YOUR_APPLE_ID}" --password "@keychain:AppStoreConnect"
No errors getting notarization info.

          Date: 2020-05-10 22:29:32 +0000
          Hash: 0000000000000000000000000000000000000000000000000000000000000000
    LogFileURL: https://osxapps-ssl.itunes.apple.com/itunes-assets/<以下、省略>
   RequestUUID: 0000000--0000-0000-0000-000000000000
        Status: success
   Status Code: 0
Status Message: Package Approved

なお、Apple Nortary Serviceに問い合わせることで公証を受けたアプリの一覧を入手できます。

1
2
3
4
5
6
7
8
9
$ xcrun altool --notarization-history 0 --username "${YOUR_APPLE_ID}" --password "@keychain:AppStoreConnect"

Notarization History - page 0

Date                      RequestUUID                          Status  Status Code Status Message   
------------------------- ------------------------------------ ------- ----------- ---------------- 
2020-05-10 22:29:32 +0000 0000000--0000-0000-0000-000000000000 success 0           Package Approved 

Next page value: 0000000000000

この状態になっていたらApple IDのメールアドレスにAppleからメール9が届いているはずです。

./images/figure-22.png

ステープラとオフライン下でのGateKeeper対応

ステープラ をしてmacOSがインターネットに繋がっていないときでもGateKeeperがmacOSアプリが公証済みと判断できるようにします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 失敗例。Apple Nortary Serviceがまだ処理中の可能性があります。ステータスを xcrun altool --notarization-history コマンドで確認します。
$ xcrun stapler staple Emacs-Distribution_SIGNED.pkg
Processing: /Users/hiroakit/emacs-on-apple/pkg/Emacs-Distribution_SIGNED.pkg
CloudKit query for Emacs-Distribution_SIGNED.pkg (0/0000000000000000000000000000000000000000) failed due to "record not found".
Could not find base64 encoded ticket in response for 0/0000000000000000000000000000000000000000
The staple and validate action failed! Error 65.

# 成功例
$ xcrun stapler staple Emacs-Distribution_SIGNED.pkg
Processing: /path/to/your/Emacs-Distribution_SIGNED.pkg
Processing: /path/to/your/Emacs-Distribution_SIGNED.pkg
The staple and validate action worked!

さいごに

本稿ではmacOSアプリの署名と公証について解説しました。 エンドユーザー視点でみるとダウンロードしたソフトウェアが スムーズにインストールできないのは印象がよろしくないです。 UXやマーケティング担当者視点からすると、エンドユーザーがアプリインストールの部分でつまづくことになるのは避けたいところでしょう。 macOSアプリをSam Porter Bridges10のように送り届けたいならAppleの公証対応は必須です。

今後もセキュリティ強化のためソフトウェアデペロッパーの作業が増えるのは至極当然の流れでしょう。 今年のWWDC20で何らかのアナウンスがあるかもしれません。

本稿では公証のCI/CDに載せるところまでは言及しておりませんので、 この点は今後の課題としたいと考えつつ、 いまはただひたすら Unreal Engine 5のデモ映像 の余韻に浸っています。


1

Mac OS X El Capitanで導入されました。

2

iOSでは2009年ごろにPDFファイルで公開していた iPhone Development Guide のManaging Application Entitlements (P25) で図示して開発者に対応を要求しています。AppleはこのPDFの公開を中止しているため、当時の様子を伺える記事として以下があります。

3

ほかにオススメの手順があれば教えてください。

4

以前はApple Developerポータルと呼んでいましたが、今の呼び方をご存知の方います?

6

私は気分で8192ビットを選択してファイルをアップロードしたら警告文が表示され2048ビットで作り直しました。

7

codesignの引数 -v は状況によって --verbose--verify のどちらかになるコマンド。明示的に指定することをオススメします。また、この --verify--sign の後ろで指定するとコード署名に失敗します。コマンドのIF設計としてはあまり良くない気がする。

8

man pkgbuildの–analyzeに記載があります。

9

SafariでOutlook.comで受信したメールを表示した様子 [$P{text.developer-URL}] とありますがそのうち修正されるでしょう。

10

Tomorrow Is In Your Hands と書きたかっただけ。