冒頭の画像はとある企業が配布するdmgファイルに入っているmacOSアプリをダブルクリックすると表示されたウィンドウです。 公証の対応を後回しにせざるを得なかったと想像します。
企業がMac App Store以外で独自に配信・配布するmacOSアプリで 公証
が未実施だったり、
そもそもアプリに 署名
をしていないと思われる場面に遭遇します。
公証
についてAppleはWWDC 2018から周知していますが、
macOSアプリのデペロッパー側としては予算や人的リソース面
(e.g. macOSアプリ専任者を体制に入れ続けるのが相当きつい) といった理由があるのかもしれません。
本稿では独自にmacOSアプリを配信する方向けにゼロからスタートの方でも活用できるようmacOSアプリの 署名
と 公証
に必要な手順と手続きをまとめています。
環境によってはうまくいかないこともあるかもしれません。その場合はぜひとも教えてください。 Twitterアカウント @hiroakit にメンションを頂けたら幸いです。
はじめに
AppleはmacOSのセキュリティ強化の施策として以下を実施しています。
- Gatekeeperを導入。Mac App Store以外で入手したmacOSソフトウェアのDeveloper ID証明書を確認し、悪意のあるソフトウェアからエンドユーザーを保護する
-
Apple Nortary Serviceを導入。前項のGatekeeperがNortary ServiceサーバーにmacOSソフトウェアの信頼性を照会することで悪意のあるソフトウェアからエンドユーザーを保護する
- macOSがインターネットに繋がっていない場合はmacOSソフトウェアに添付済みの公証結果の値を確認することで対応する
本稿はEmacs.appを例にして上記2点に対応するmacOSアプリにしていきます。
習得内容
本稿では以下の手順が習得可能です。
- Developer ID Installer証明書の発行手順
- Developer ID Application証明書の発行手順
- entitlements.plistの作成手順
- macOSアプリの署名手順
- 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
でも同様のことができるようですが別に譲ることにします。
対象読者
本稿は以下の環境を持つ読者を対象にしています。
- macOS v10.15.4 (macOS Catalina)が動くMacがあること
-
Xcode v11.4をインストール済みであること
- コマンドラインツールもインストール済みであること (
xcode-select --install
) - Xcode v11.4を指定してください (
sudo xcode-select --switch /Application/Xcode.app
) - トラブルシューティング: xcrunコマンド実行したら「xcrun: error: invalid active developer path」でエラーになる - Qiita
- コマンドラインツールもインストール済みであること (
-
Apple Developer Program (Individual)を契約した際に使用したApple IDを利用できること
- Developer ID Application & Installer 証明書の取得する際に必要です
-
上記の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.
上記のようにmacOS Catalinaにおいては2019年6月1日以降にビルドされ、 Developer IDで配布されるすべてのソフトウェアは公証が必須となっています。
Appleが公証対象となるmacOS用ソフトウェアは以下の通りです。
- macOSアプリ
- カーネル拡張などの非アプリバンドル
- ディスクイメージ(UDIF形式)
- フラットインストーラーパッケージ
本稿では上記のうちmacOSアプリのみを取り扱います。
macOSアプリの公証通過要件
「はじめに」の通り、本稿ではmacOSアプリに対する署名と公証に焦点を絞ります。
macOSアプリが公証を通過するには以下の要件を満たす必要があります。
-
macOS 10.9以上のSDKでビルドをすること (出典)
- 公証はmacOS 10.9以降に対してリンクされたバイナリに対してのみ機能します。古いSDKを使用すると公証に失敗します
- macOS 10.9以上のSDKでビルドします。場合により実装に修正が必要になります
-
アプリ内のすべての実行ファイルにコード署名をすること (出典)
- 配布するアプリに含めるすべての実行可能ファイルが対象です
codesign
コマンドで対応します
-
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証明書を指定して対応します
-
セキュアタイムスタンプをコード署名時に含めること (出典)
- Appleの要求事項です
codesign
コマンドの引数-–timestamp
で対応します
-
Hardened Runtimeに対応していること (出典)
- Hardened RuntimeはSystem Integrity Protection1、コードインジェクション、ダイナミックリンクライブラリハイジャック、プロセスメモリ空間の改ざん、特定のクラスのエクスプロイトなどの防止を目的にした強化されたソフトウェアランタイムです
- アプリが機能するために必要な資格を
entitlement.plist
に記載することでアプリ開発者が意図した制限緩和なのか明示します codesign
コマンドでruntime
オプションと上述のentitlement.plist
を指定して対応します
-
Get-Task-AllowをEntitlementに含めていないこと (出典)
- System Integrity Protectionが有効なmacOSでは開発時にデバッグを容易にするために Get-Task-Allow (
com.apple.security.get-task-allow
) を利用しますが、この状態のままアプリを出荷すると攻撃者が実行時にコードを挿入する可能性があります - そのため、出荷するアプリでは同項目を無効2にしてセキュリティリスクを抑えます
- 前述の
entitlement.plist
と併せて対応します
- System Integrity Protectionが有効なmacOSでは開発時にデバッグを容易にするために Get-Task-Allow (
作業の流れ
Developer ID Application & Installer 証明書の取得
まず、以下の電子証明書を用意します。
- Developer ID Installer証明書
- Developer ID Application証明書
上記の証明書はApple Developer Programのウェブサイトで発行可能ですのでそちらで作業をします。 (Xcode 11で発行可能ですが、諸事情により同アプリから発行が難しい方が多いだろうと想像したのでウェブサイトにしています)
Apple Developer Programで証明書の発行
Apple Developer Program4のサイトにアクセスします。
左メニューにある Certificates, IDs & Profiles
をクリックします。
下図で赤枠で囲っているプラスボタンをクリックします。
以下の画面に移りますので発行したい証明書の種類を選びます。
ずらっと選択肢が列挙されているので一瞬戸惑うかもしれませんが、
Developer ID Application
を選択しContinueボタンをクリックします。
なお、別ページに証明書の種類を紹介しているAppleの日本語ヘルプ5があります。
さて、先ほどの画面でContinueボタンを押すと下図の画面に移動します。 その画面ではCertificate Signing Request (以後、CSRと略します) をアップロードするのですが、 お手元にはそのファイルがないと思います。
ということで作ります。
Certificate Signing Requestの作成
CSRを作成するにはキーチェーンアクセスを使います。
キーチェーンアクセスを起動してメニューから 認証局に証明書を要求...
をクリックします。(下図参照)
次に以下の項目を入力ないし選択をします。(下図参照)
- ユーザのメールアドレス: 任意のメールアドレス
-
通称: 任意な文字列を入れます
- 通称は任意で構わないのですが、キーチェーンアクセスで表示される名前になるので気をつけておかないと同じ名前でどれがどれだかわかりづらくて発狂します(事例)
- 要求の処理:
ディスクに保存
を選択して、鍵ペア情報を指定
にチェックを入れます
CSRの保存場所を指定します。
次に公開鍵と秘密鍵を作成します。鍵のサイズは2048ビット6、アルゴリズムはRSAにします。続けるボタンをクリックします。
以上の作業で公開鍵と秘密鍵がキーチェーンアクセスに追加されました。
アプリをウェブブラウザに切り替えてApple Developer ProgramのCertificates, IDs & Profilesに戻ります。
Developer ID Application 証明書の発行
Choose File(下図赤枠)で先ほど作成したCSRファイルを指定します。 ファイルを指定したらContinueボタンをクリックします。
ちなみに先のキーチェンアクセスの作業で鍵のサイズに2048ビットを選択せずに CSRをアップロードすると下図の表示になります。
さて、CSRファイルが無事にアップロードできると下図の画面に遷移します。
この画面で Developer ID Application
証明書が作成されたことがわかります。
Downloadボタンをクリックすると証明書をダウンロードできます。
ダウンロードしたらお手元に developerID_application.cer
がありますから、それをダブルクリックします。
そうすると下図の画面が表示されるのでキーチェーンを「ログイン」にしてから追加ボタンをクリックして
キーチェーンアクセスに取り込みます。
以上の作業でキーチェーンアクセスの秘密鍵と developerID_application.cer
が紐づきます。
(余談になりますが、CSR作成時に入力する 通称
は確かに任意の文字列で構わないのですが、
上図のようにキーチェーンアクセスでの表示名に使われるので、その点を考慮しておかないと同じ名前だらけになり、
パッと見ただけではどれがどの証明書なのかわからず発狂します。)
Developer ID Installer 証明書の発行
Developer ID Installerの発行はApple Developer Programの Certificates, IDs & Profiles
で
証明書の種類を選択する画面でDeveloper ID Installerを選び作業を進めます。その後の手順は前述とおりです。
なお、CSRファイルは同じ人物が管理するなら使いまわしして構いません。
Developer ID Application & Installer 証明書取り込み後
Developer ID Application & Installer証明書をキーチェーンアクセスに各々取り込むと下図のようになります。
これでアプリに署名をするための下準備が出来上がりました。 ここからが長いです・・・。
Developer ID Application & Installer 証明書でアプリを署名
それではアプリのコード署名に入ります。
まず、ディレクトリ構成を以下に示します。この構成に沿ってEmacs.appの.pkgを作成します。
最終成果物が Emacs-Distribution_SIGNED.pkg
です。
|
|
例として使用する Emacs.app は下記からダウンロードしてください。
Developer ID証明書でアプリに署名を入れるには codesign
7コマンドを使います。
引数 --entitlements
で指定する entitlements.plist
は後述します。
|
|
DEVELOPER_ID
はキーチェーンアクセスで確認します。(下図赤枠参照のこと)
コマンド codesign
の引数を下表で補足します。
引数 | 解説 |
–verify | コード署名を検証します。なお、 --sign の後ろに記述すると意図しない動きになります。 |
–sign | 指定した署名IDで指定先ファイルを署名します。 |
–force | 既存の署名があった場合に書き換えます。なお、このオプションを指定していない場合にその状況に遭遇すると署名が失敗します。 |
–verbose | 標準出力の情報量を増やします。 |
–deep | 再帰的に署名します。アプリにバンドルするフレームワークなども署名の対象になります。 |
–options | 署名時に埋め込むオプションを指定します。本稿の例ではHardened Runtime対応のため runtime を指定しますが、host, libraryなどがあります。 |
–entitlements | 署名時に埋め込む資格情報ファイルを指定します。(フォーマットは後述します。) |
–timestamp | Appleのタイムスタンプサーバーからセキュアなタイムスタンプを取得し署名時にアプリに埋め込みます。 |
引数 --entitlements
で指定する entitlements.plist
は以下の例のようなフォーマットで記載します。
|
|
キーについては下表で補足します。
キー | 解説 |
com.apple.security.network.client | サンドボックスアプリが別のコンピューターあるいはローカルホストで実行中のサーバープロセスへの接続を許可するブール値。trueで許可、falseでそれ以外を示します。 |
com.apple.security.cs.disable-library-validation | アプリがコード署名が施されていないプラグインまたはフレームワークを読み込みできるかを示すブール値。trueで許可、falseでそれ以外を示します。(もちろん false のほうが望ましいのでしょうけど、必ずしも常にその対応ができるとは限りません。) |
コード署名後に pkgutil --check-signature
で結果を確認します。以下に失敗例と成功例を示します。
|
|
以上の作業でmacOSアプリの署名が完了しました。次は配布用パッケージを作成します。
アプリのパッケージング
Mac App Storeを除くと、macOSアプリの配布方法は以下の選択肢があります。
-
zip形式で配布する
- 後述のステープラ作業が手間になるので私はオススメしません。(ツールやフレームワーク側が処理するなら、前提が異なるのでもちろん別です)
-
dmg形式で配布する
- dmg形式の利点を把握できていないため本稿では割愛します。選択肢としてあるということだけにとどめ、別の機会に書き起こしたいと思います。
-
pkg形式で配布する
- いわゆるインストーラー。一番無難で楽です。
本稿ではインストーラーとして振る舞うpkg形式を採用します。 pkg形式は以下の流れで作成します。
- pkgbuildでpkgファイルの作成
- productbuildでpkgファイルを配布形式に変換
詳しくは次の節で解説します。
pkgbuildでpkgファイルの作成
pkgファイルに含めるファイルをplistに書きます。下記コマンドを実行すると plist-output-path
にそのplistが入手できます。
|
|
引数については下表で補足します。
引数 | 解説 |
–analyze | --root で指定したパスからテンプレートコンポーネントプロパティリスト8を作成します。 |
–root | パッケージング対象のファイルが存在するディレクトリを指定します。 |
上記コマンドの出力結果例は以下の通りです。
|
|
書き換えます。
|
|
キーについては下表で補足します。
キー | 解説 |
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ファイルを作ります。
|
|
引数については下表で補足します。
引数 | 解説 |
root | pkgbuild --analyze と同じディレクトリを指定します |
component-plist | pkgbuild --analyze が出力したplistファイルを指定します |
scripts | スクリプトファイルを格納しているディレクトリを指定します。省略可能。 |
identifier | 逆URL形式で記述します (例: com.example.emacs) |
version | pkgインストーラーのバージョンを記述します |
install-location | インストール先のフォルダを指定します |
pkgファイルが作成できたら、ここで一旦動作確認をしておくといいと思います。 手戻りが防げます。
productbuildでpkgファイルを配布形式に変換
変換に先立ち構造をXMLにして出力します。下記コマンドを用います。
|
|
引数については下表で補足します。
引数 | 解説 |
–synthesize | Write the synthesized distribution directly instead of incorporating it into a product archive. |
–package | 配布物に入れる.pkgファイルを指定します。本稿の例ではEmacs.pkgです。 |
productbuild
が出力したXMLが以下です。
|
|
これだけでは足りないのでDistribution XML Referenceを見ながら書き足していきます。
例えば title
要素は追加しておいた方がいいです。
未指定の場合、インストーラーのウィンドウのタイトルバーが単語が抜けているように見えます。 書き加えたものがこちら。
|
|
準備が整ったらpkgを作ります。
|
|
引数については下表で補足します。
引数 | 解説 |
–distribution | (本コマンドの) 成果物によってインストールされる魅た目、選択肢、パッケージを定義します。本稿の例では前工程で生成した Distribution.xml を指定します。 |
–package-path | 前工程で生成した .pkg ファイルを指定します。(本稿の例ではEmacs.pkg) |
Developer ID Installer証明書でアプリを署名
Developer ID Installer証明書を使いパッケージに署名します。
|
|
次のコマンドでコード署名の結果を確認します。
|
|
ここまできたらあとはAppleに公証を依頼するのみです。
署名済みパッケージをApple Nortary Serviceにアップロード
署名したパッケージをApple Nortary Serviceにアップロードします。
security
コマンドの引数について下表で補足します。
引数 | 解説 |
add-generic-password | パスワードを登録します |
-s | キーチェーンに登録するときの名称 |
-a | アカウント名 |
-w | パスワード。Appleは上の例のようにパスワードはプロンプト上で入力することを推奨しています。 |
さて、話をApple Nortary Serviceにパッケージをアップロードするところに戻します。 同サービスには以下の制限事項があります。
- アップロードするサイズは50GBまで (出典)
- 公証は1日あたり75回まで (CI/CDで公証まで実施する場合は考慮が必要かもしれません)
以下のコマンドで公証をAppleに依頼します。
|
|
しばらくすると応答があります。
この段階ではAppleのサーバー側で処理中の場合があるので、
後述する xcrun altool --notarization-info
か xcrun altool --notarization-history
でステータスを確認します。
|
|
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
になっていれば完了しています。
|
|
なお、Apple Nortary Serviceに問い合わせることで公証を受けたアプリの一覧を入手できます。
|
|
この状態になっていたらApple IDのメールアドレスにAppleからメール9が届いているはずです。
ステープラとオフライン下でのGateKeeper対応
ステープラ
をしてmacOSがインターネットに繋がっていないときでもGateKeeperがmacOSアプリが公証済みと判断できるようにします。
|
|
さいごに
本稿ではmacOSアプリの署名と公証について解説しました。 エンドユーザー視点でみるとダウンロードしたソフトウェアが スムーズにインストールできないのは印象がよろしくないです。 UXやマーケティング担当者視点からすると、エンドユーザーがアプリインストールの部分でつまづくことになるのは避けたいところでしょう。 macOSアプリをSam Porter Bridges10のように送り届けたいならAppleの公証対応は必須です。
今後もセキュリティ強化のためソフトウェアデペロッパーの作業が増えるのは至極当然の流れでしょう。 今年のWWDC20で何らかのアナウンスがあるかもしれません。
本稿では公証のCI/CDに載せるところまでは言及しておりませんので、 この点は今後の課題としたいと考えつつ、 いまはただひたすら Unreal Engine 5のデモ映像 の余韻に浸っています。
参考資料
以下を参考資料として活用しています。
- Notarizing macOS Software Before Distribution - Apple Developer Documentation
- Resolving Common Notarization Issues - Apple Developer Documentation
- All About Notarization - WWDC 2019 - Videos - Apple Developer
- Your Apps and the Future of macOS Security - WWDC 2018 - Videos - Apple Developer
- Code Signing Tasks - Apple Developer Documentation Archive
- About Distribution Definition Files - Apple Developer Documentation Archive
- Technical Note TN2206: macOS Code Signing In Depth - Apple Developer Documentation Archive
- Using the OS X Keychain to store and retrieve passwords - Signs of Triviality
- Hardened Runtime and Sandboxing - The Desolation of Blog
- macOS Catalinaにおけるアプリの「公証(Notarization)」への対応方法
- リリース済みのアプリのApple Notarization Service (Notary Service) 対応 - アールケー開発
- macOS の App Notarization について - Qiita
- macOS向けにElectronアプリケーションの署名(code signing)・公証(notarization)をする - Qiita
- Command Line ToolのApple公証を行う - Qiita
- Mac用Installerの作り方 - Qiita
- コマンドラインでmacOSアプリのNotarization(公証) [Xcode11以降] - Qiita
Mac OS X El Capitanで導入されました。
iOSでは2009年ごろにPDFファイルで公開していた iPhone Development Guide
のManaging Application Entitlements (P25) で図示して開発者に対応を要求しています。AppleはこのPDFの公開を中止しているため、当時の様子を伺える記事として以下があります。
ほかにオススメの手順があれば教えてください。
以前はApple Developerポータルと呼んでいましたが、今の呼び方をご存知の方います?
codesignの引数 -v
は状況によって --verbose
か --verify
のどちらかになるコマンド。明示的に指定することをオススメします。また、この --verify
を --sign
の後ろで指定するとコード署名に失敗します。コマンドのIF設計としてはあまり良くない気がする。
man pkgbuildの–analyzeに記載があります。
SafariでOutlook.comで受信したメールを表示した様子 [$P{text.developer-URL}]
とありますがそのうち修正されるでしょう。
Tomorrow Is In Your Hands と書きたかっただけ。