[Electron] macOS用アプリに「コード署名」と「公証」を行う

今回はインターネット経由で配布したmacOS用のアプリが安全であることを証明するための作業を行います。

ぶっちゃけ面倒です← あとmacOSがないと作業できませんのでWindowsユーザーの方は(お小遣いを)準備してから挑んでください。

そもそも何が問題なのか

インターネット上からダウンロードしたファイルは安全とは限りません。悪意のある第三者によって書き換えられている可能性があるためです。そこでmacOSでもWindowsでも「コード署名」と呼ばれる物を利用し改ざんされていないか検証するための仕組みが用意されています。

macOSではインターネットからダウンロードした署名がされていないアプリを実行しようとするとGatekeeperなる機能により以下のようなダイアログが表示され起動できなくする安全装置が備わっています。

セキュリティの設定を緩めれば起動できますが、ユーザーにそれを求めるのはさすがに怪しすぎるので一般に配布する際には事実上「コード署名」に対応する必要が出てくるというわけです。

最終的なファイル

追加

GitHubにこのページで解説したファイルの一覧をアップしました。必要に応じてご利用ください。 github.com

準備

macOSが動く環境を準備

お好きなマシンをどうぞw www.apple.com

最近はAWSでmacOSのインスタンスが契約できるので、使いたい時だけ利用するのもありですね。もしくはTravis CIGitHub ActionsのようなCIサービスを利用するのも良さそうです。

Apple Developer Programに登録

macOSiPhoneなどiOS用のアプリをAppStoreで販売する際などに利用するApple Developer Programに登録します。 developer.apple.com

年会費が毎年発生するのでご注意ください。私は仕事の関係ですでに契約していたのですが、2020年の10月に更新した際の料金は税込み12,980円でした。高いw 昔はもっと安かった気がするのですが……。

AppleDeveloperProgramのレシート

法人ならまだしも個人の場合は手加減して欲しいなぁ(´・ω・`)

Xcodeをインストール

AppStoreからインストールします。

Xcode

Xcode

  • Apple
  • 開発ツール
  • 無料
apps.apple.com

ただ、AppStoreのXcodeはインストール時にSSD(HDD)に巨大な空き容量を要求されます。SSDの容量が切迫している場合は以下からインストールするとAppStoreよりも要求されるディスク容量が少なくてすみます(AppleIDでのログインが必要です) developer.apple.com

「コード署名」を行う

コード署名証明書を作成

では「コード署名証明書」を作成していきます。

Xcodeを起動したらメニュー「Xcode」から「Preferences」を選択。

設定ウィンドウが表示されたら「Accounts」ボタンをクリック、左下の「+」ボタンをクリックすると追加するアカウントの種類を訪ねてきますのでAppleIDを選択し、「continue」ボタンを押します。

登録するアカウント情報を聞いてきますので、デベロッパー登録した際のAppleIDを入力し「Next」ボタンを押します。

「Team」の箇所に自分の名前が表示されていますのでこれを選択、「Manage Certificates」ボタンを押します。

証明書を管理するためのダイアログが開きますので、左下の「+」ボタンをクリックし「Developer ID Application」を選択します。

無事に証明書が追加されると一覧に表示されますので、この状態になったら「Done」ボタンを押してダイアログを閉じます。

これで「コード署名証明書」の準備が完了しました。Xcodeは終了します。

ビルドしてみる

証明書ができたので試しに「electron-builder」を利用してビルドを行ってみます。結論から言うと失敗します。

プロジェクトのディレクトリへ移動しelectron-builderコマンドを実行します。メッセージの中に「signing」が出てきました。このようにelectron-builderでは自動的にコード署名証明書を取得して適用してくれます。

$ electron-builder --mac --x64
> 1stapp@1.0.0 build-mac /Users/katsube/Develop/electron-1stapp
> electron-builder --mac --x64

  • electron-builder  version=22.8.1 os=19.6.0
  • loaded configuration  file=package.json ("build" field)
  • description is missed in the package.json  appPackageFile=/Users/katsube/Develop/electron-1stapp/package.json
  • writing effective config  file=dist/builder-effective-config.yaml
  • packaging       platform=darwin arch=x64 electron=10.1.2 appOutDir=dist/mac
  • default Electron icon is used  reason=application icon is not set
  • signing         file=dist/mac/1stapp.app identityName=Developer ID Application: Makito Katsube (XXXXXXXXXX) identityHash=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA provisioningProfile=none
  • building        target=DMG arch=x64 file=dist/1stapp-1.0.0.dmg
  • building block map  blockMapFile=dist/1stapp-1.0.0.dmg.blockmap

signingが表示されたあたりでパスワードを聞かれるのでmacOSにログインする際の物を入力し「常に許可」をクリックします。これで署名済みのアプリが生成されます。

だがしかし…!

生成されたdmgファイルを早速GoogleDriveなど適当な場所にアップロードしダウンロード、インストールします。「なんだ簡単じゃーん」と喜び勇んでアプリを開こうとすると……冒頭とは違う内容のダイアログが表示され実行することができません(゚д゚)!

厳密には「macOS Catalina (10.15.x)」以降でこの現象が発生します。これを回避するには「公証」と呼ばれる第二の関門を突破する必要があります。

「公証」に対応する

公証とは?

2020年2月3日以降、MacAppStore以外で配布されるアプリもAppleの「審査」を受ける必要が出てきました。この審査を通過しないと先ほどのようにインターネットからダウンロードしたアプリは起動することができません。この審査のことを「公証」と呼びす。

未公証の“野良アプリ”を「macOS Catalina」で実行しても、従来は警告で済んでいたが、それ以降はエラーで起動不能となるため、アプリに修正を施す必要がある。

※「macOS Catalina」で実行する野良アプリにはAppleによる公証が必要に ~来年2月から(窓の杜)より

審査というと思わず身構えてしまいますが(それが目的の一つなんでしょうけど)、実際にはヤバい内容がアプリに含まれていないか機械的にスキャンしているだけのようです。

アプリ固有のパスワードを準備

このあと開発者登録したアカウントのIDとパスワードが必要になるのですが、2段階認証を行っているとコマンドラインから実行するのが面倒なのと、セキュリティ的な理由からアプリ専用のパスワードを以下のページから追加します。 appleid.apple.com

開発者登録したアカウントでログインしたら「パスワードを生成」のリンクをクリックして生成します。覚えやすい名前を入力して「作成」ボタンを押すと自動生成されたパスワードが表示されるのでメモします。

Team IDを確認

次に開発者アカウントが所属しているTeamIDなる10桁の文字列が必要になります。以下のページから開発者アカウントでログインします。 developer.apple.com

左メニューの「Membership」をクリック。右側にある「Team ID」をメモします。

必要なファイルの準備

この項目は以下のページを(Google翻訳を元に)参考にさせていただきました。 kilianvalkhof.com

モジュールを追加

「公証」を行うのに必要なNodeのモジュールを2つほど追加します。

$ npm install electron-notarize dotenv

[追加] .env

このあと作成するJS内で環境変数として利用する値をまとめます。上から順番に開発者登録した際の「ユーザーID(メールアドレス)」と先ほど作成した「アプリ固有のパスワード」「Team ID」を記述します。

APPLEID=katsube@example.com
APPLEIDPASS=xxxx-xxxx-xxxx-xxxx
ASC_PROVIDER=XXXXXXXXXX

このファイルをアプリ内に入れたり、第三者が見える場所に公開する、Gitのリポジトリへ追加しては絶対にダメです。あなた意外の人の目に入らないよう厳重に管理してください。

余談ですが、Webサーバを管理していると毎日のように…というかほぼ毎日「.env」狙いのアタックがサーバに来ますw 悪い人から狙われまくっているファイルの一つです。

[修正] .gitignore

Gitを利用している場合、先ほどの「.env」をリポジトリに登録するのを防ぐため.gitignoreを修正します。

node_modules/
dist/

# ↓以下を追加
.env

[追加] build/entitlements.mac.plist

以下の内容をコピペし、新しいファイルに保存します。buildフォルダが無い場合は作成してください。

<?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.cs.allow-unsigned-executable-memory</key>
    <true/>
  </dict>
</plist>

Hardened Runtimeというセキュリティを強化する機能を利用するために必要な機能を有効にしています。

[追加] scripts/notarize.js

先ほどと同様に以下の内容をコピペし、新しいファイルに保存します。scriptsフォルダが無い場合は作成してください(厳密にはpackage.json内のパスを変更すればどこに置いても構いません)

require('dotenv').config();
const { notarize } = require('electron-notarize');

exports.default = async function notarizing(context) {
  const { electronPlatformName, appOutDir } = context;
  if (electronPlatformName !== 'darwin') {
    return;
  }

  const appName = context.packager.appInfo.productFilename;

  return await notarize({
    appBundleId: 'net.makitokatsube.app.1st',  //★自分のアプリのBundleID(appId)に変更★
    appPath: `${appOutDir}/${appName}.app`,
    appleId: process.env.APPLEID,
    appleIdPassword: process.env.APPLEIDPASS,
    ascProvider: process.env.ASC_PROVIDER
  });
};

これは「コード署名」が行われたあとに何を行うかを定義したプログラムです。要はここに「公証」にまつわる処理がまとまっています。Appleへアップロードして公証が終わるのを待って……といった泥臭い部分は「electron-notarize」モジュールがすべて行ってくれています。

[修正] package.json

build内に以下の内容を追加します。

"build": {
  "appId": "net.makitokatsube.app.1st",  //★自分のアプリのBundleID(appId)に変更★
  "afterSign": "scripts/notarize.js",
  "mac": {
    "hardenedRuntime": true,
    "gatekeeperAssess": false,
    "entitlements": "build/entitlements.mac.plist",
    "entitlementsInherit": "build/entitlements.mac.plist"
  },
  "dmg": {
    "sign": false
  }
}

再びビルド

ここまで準備が整ったら再びビルドを行います。コマンドも表示内容も先ほどと変わりませんが、Appleへアップロード〜公証完了まで待機する処理が入るため3〜10分程度の時間がかかります。

$ electron-builder --mac --x64

Appleから「公証」完了メール

ビルドが正常に完了するとAppleから「Your Mac software was successfully notarized.」という件名のメールが送られてきます。これで準備万端。

起動した!

ドキドキしながら生成されたdmgファイルを再度GoogleDriveなど適当な場所にアップロードしダウンロード、インストールします。「今度こそ!」と言う思いとともに実行すると……。今度は「開く」ボタンが表示された━━━━(゚∀゚)━━━━!!

_|\○_ ヒャッ ε=ε=ε=ε=ε= \_○ノ ホーウ!!!

いやー、長い旅だったw お疲れ様でした。

参考ページ