Electron アプリケーションの公証に成功する方法

:::note info
この記事のアップデート版 (2023) は以下にあります。
:::

https://qiita.com/sprout2000/items/0fa5db89c43155f4f0c8

（この記事は [Kilian Valkhof](https://github.com/kilian) 氏による ["Notarizing your Electron application"](https://kilianvalkhof.com/2019/electron/notarizing-your-electron-application/) をご本人の了承を得て日本語訳したものです。）

https://kilianvalkhof.com/2019/electron/notarizing-your-electron-application/

macOS 10.14.5 のリリース以降、 _”新規の”_ 開発者によるすべての署名済みアプリケーションは公証を受けなければ Apple のゲートキーパーに引っ掛かり、ユーザーがインストールできないようになっています。

私は、[Polypane](https://polypane.rocks/) というアプリケーション（未公証でした）をローンチした２日後に macOS 10.14.5 が登場し、アプリをインストールできない多くのユーザからの苦情に迎えられる結果となりました。

残念なことに、これについては多くの情報が転がっているわけではありません。私が初めてこの問題に遭遇したとき、Google ではそのエラーメッセージすら見つけることができなかったのです！

未公証のアプリを macOS 10.14.5 にインストールしようとすると、つぎのような不吉な予兆をわめくエラーメッセージによる歓迎を受けることになるでしょう:

![58382976-69b6f080-7fd1-11e9-845e-5587dd500702.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609660/fec6c601-eaaa-6ed2-d584-a5950a20f579.png)

> "Appname.dmg"は、開発元を検証できないため開けません。
> このアプリケーションにマルウェアが含まれていないことを検証できません。

## Electron アプリケーションの公証に成功する方法

このエラーにどう対処すれば良いのかに 1 週間を費やした結果、公証について大量のことを学び、それに合わせた npm パッケージのアップデート方法も習得できました。

これが Electron アプリケーションの公証に成功するためのガイドです:

1. hardened ランタイムを有効にしてアプリをビルドする
2. 有効な Developer ID でコード署名する
3. アプリの公証に electron-notarize を利用する
4. dmg には署名**しない**

この通り、実際に公証を取得するのはこのうちの１つのステップだけなのですが、アプリの公証に成功するためにはこの４つのステップすべてが必要です。Apple が公証だけでなく、さらにいくつかの必要条件を我々にこっそり課しているからです。

この記事で私が使用するのは [electron-builder](https://www.electron.build/) のバージョン 20.43.0 です。他のビルドツールを使う場合には、これと似通った設定になるでしょうが、そのツールでも同じステップを踏む必要があります。

## 1. hardened ランタイムを有効にしてアプリをビルドする

Apple が公証に際してこっそり忍ばせたものの１つが hardened ランタイム有効化の要件です。これは基本的にデフォルトではアプリへより少ない権限しか与えないというものです。

これは必ずしも悪くありませんが、もう 1 つ - _one more thing_ - 考慮すべき点があります。

アプリで hardened ランタイムを有効化するためには次の２つが必要です:

1. electron-builder の `"mac"` 設定で `"hardenedRuntime": true` とする
2. entitlement を正しく設定する

hardened ランタイムを利用するためには、少なくとも 1 つの entitlement（`“allow-unsigned-executable-memory”`）を設定することが重要です。

さらにたくさんの entitlement もここで設定できますが、Electron アプリケーションに必要なのはこれ１つだけです。

ビルドフォルダにはこの entitlement を含んだ plist ファイルが置かれていなければなりません。

> **訳注:** [electron@12](https://github.com/electron/electron/releases/tag/v12.0.0) 以降では `allow-unsigned-executable-memory` の指定は不要となりました。
> つまり `entitlements.plist` ファイルそのものを作成しなくとも公証には成功します。

https://github.com/electron/electron/pull/26331

以下がそのコピーです:

```xml:entitlements.mac.plist
<?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>
```

electron-builder の"mac"設定で `entitlements` と `entitlementsInherit` の両方の値にこのファイルを指定してください。

Electron が同一の entitlement に内部的にアクセスするためには `entitlementsInherit` が必要となります。

```json:electron-builder.json
"mac": {
  "entitlements": "build/entitlements.mac.plist",
  "entitlementsInherit": "build/entitlements.mac.plist"
}
```

## 2. 有効な Developer ID でコード署名する

アプリが公証を受けることができるのは有効な Developer ID がインストールされている場合のみです。Electron-builder はキーチェーン内の有効な ID を自動的に使います。

そのためには [devloper.apple.com](https://developer.apple.com/) で購入した Apple Developer アカウントが必要で、その ID を Xcode を使ってキーチェーンへダウンロードしておかなければなりません（詳しくは [Create, export, and delete signing certificates](https://help.apple.com/xcode/mac/current/#/dev154b28f09) を参照のこと）。

https://developer.apple.com/

一方で electron-builder が内部的に利用しているコード署名ツール（[electron-osx-sign](https://github.com/electron-userland/electron-osx-sign)）は、署名成功の確認のためにサニティ・チェックを行います。これは macOS 10.14.5 より前であれば「正」を返すものですが、10.14.5 ではエラーを返します。なぜならコード署名がうまくいったとしても、そのアプリは（**まだ！**）公証を受けていないからです。

https://www.npmjs.com/electron-osx-sign

いまのところ、わたしたちはこのサニティ・チェックを無効化しておく必要があります。そのためには electron-builder の `"mac"` 設定へ `"gatekeeperAssess": false` を追加します。ここまでで `"mac"` 設定の最小構成はつぎのようになりました:

```json:electron-builder.json
"mac": {
  "hardenedRuntime" : true,
  "gatekeeperAssess": false,
  "entitlements": "build/entitlements.mac.plist",
  "entitlementsInherit": "build/entitlements.mac.plist"
}
```

## 3. アプリの公証に electron-notarize を利用する

やっと実際の公証ステップです。Electron 開発者（もしくは誰であれなんらかのアプリを持っているひと）ならば [Electron-notarize](https://github.com/electron-userland/electron-notarize) というツールが利用できます。
devDependency として `electron-notarize` をインストールしましょう。

https://www.npmjs.com/package/electron-notarize

このツールは全てのことをおこないます：アプリを zip にして Apple のサーバへアップロード、そして公証が成功するのを待ってアプリの梱包まで。これらはすべて非同期的におこなわれるため、アプリのビルドに要する時間は大幅に増加します（訳注：notary サーバの状態にもよりますが公証だけで３〜10 分ほどかかります）。

アプリはコード署名がなされたあと、dmg へパッケージされる前に公証されなければいけません。Electron-builder では `afterSign` というフックが用意されています。これにコード署名のあとで（コード署名されるのを待って）呼び出される JavaScript ファイルをリンクさせることができます。トップレベルの `"build"` 設定に追加してください:

```json:electron-builder.json
"build": {
  "afterSign": "scripts/notarize.js"
}
```

afterSign スクリプトはこんな感じです:

```javascript:scripts/notarize.js
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: 'com.yourcompany.yourAppId',
    appPath: `${appOutDir}/${appName}.app`,
    appleId: process.env.APPLEID,
    appleIdPassword: process.env.APPLEIDPASS,
  });
};
```

> **訳注:**
> 2020 年４月現在では上記に加えて **`ascProvider: process.env.ASC_PROVIDER`** の指定も必要であるようです。
> `ASC_PROVIDER` の値には Apple ID に割り振られた Team ID が入ります。Team ID は大文字と数字からなる 10 桁の文字列です。

macOS の場合にのみコード署名されれば良いので、darwin（macOS の内部的名称）環境でなければ return して公証のためのコードを実行しないようにします。

`".env"` ファイルから簡便に環境変数を読み込むために [dotenv](https://www.npmjs.com/package/dotenv) を利用しています。これによりユーザー認証情報をスクリプトへ記載することを避けられます。もし git を利用しているなら、`.gitignore` ファイルへ `".env"` を追加するのを忘れないようにしましょう。さらなるセキュリティの強化を望むなら、[Safety when using appleIdPassword](https://github.com/electron-userland/electron-notarize#safety-when-using-appleidpassword) で概説されている要領で macOS キーチェーンを利用する方法も考慮してみてください。

https://www.npmjs.com/package/dotenv

https://github.com/electron-userland/electron-notarize#safety-when-using-appleidpassword

ユーザー認証情報と言えばもう 1 つ。たしかにこれは Apple ID ではありますが、そのパスワードとしてはアプリ固有のパスワードを設定しておかなければなりません（Apple ID そのもののパスワードを流用しないこと！）。アプリ固有のパスワードは [appleid.apple.com](https://appleid.apple.com/) で作成できます。

> **訳注：**
> Apple ID ではログインに２要素認証が必須であるためスクリプトで認証を受けることができません。
> Apple ID 自体のパスワードの代わりにアプリ固有のパスワードで認証を受ける必要があります。

## 4. dmg には署名**しない**

バージョン 20.43.0 より前の electron-builder では DMG インストーラにもコード署名がなされていました。これは実際のところ問題ではなかったのですが、あたらしい公証ルールではコード署名されたものには何であろうとすべて公証を受ける必要があります。

しかし、もし DMG インストーラにもコード署名をおこない、公証を受けたとしても実際に起こることはこの記事のトップに挙げたエラーメッセージを引き起こすだけです。個人的にはこれは Apple のロジックのバグ（なにも今にはじまったことではない）だと考えていますが、現実にこうである以上、**DMG には署名も公証もしない**ほうが良いでしょう。

Apple のゲートキーパー・ソフトウェアでは、DMG 内の公証済みアプリを感知してユーザーにそのままインストールを許可しているのではないかと推測されます。

electron-builder@20.43.0 以降では、DMG はデフォルトでは署名されません。また、`"dmg"` 設定に `"sign": false` を追加することで明示的にこの挙動を指定できます。

```json:electron-builder
"dmg": {
  "sign": false
}
```

## Enjoy your notarized application!

これらのステップを踏んで .dmg が出来あがれば、それを自分自身へメールで送信したり、アップロード＆ダウンロードを繰り返したりしてゲートキーパーの挙動をテストできます（これらは [Apple 公式のテスト手続き](https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html#//apple_ref/doc/uid/TP40005929-CH4-SW26)です）。

すべてがうまくいっていれば、なんの警告も受けることなく .dmg を開けることでしょう。

https://polypane.app/