electron-builder を利用して macOS 向け Electron アプリをコード署名し、公証を通過させる。
tl;dr: コード署名と公証に対応した macOS アプリ Juno のリポジトリをGitHub で公開している。
Code Sign
アプリのコード署名はelectron-builderによって自動で行われる。内部的にはelectron-osx-signが使用される。
リリース用のアプリにコード署名をするには、Keychain に有効な Developer ID Certificate が格納されている必要がある。macOS Developer Certificate は開発用のコード署名にしか使えないため、リリース用としては不十分だ。
まだ証明書を発行していない場合は、Apple Developerで証明書の追加ウィザードに進み、Developer ID Applicationを選択して証明書を発行する。
Notarize
コード署名済みのアプリをelectron-notarizeを使用して Apple Notary Service に提出する。
const { notarize } = require("electron-notarize");
notarize({
appBundleId,
appPath,
appleId,
appleIdPassword,
ascProvider,
});
- appBundleId: アプリの Bundle ID 。
package.jsonのbuild.appIdと同じものを使う。 - appPath:
.appの絶対パスを指定する。 - appleId: Apple Developer として登録している Apple ID を指定する。
- appleIdPassword: Apple ID のパスワード。2 要素認証を必要としないパスワードが必要なので、Apple IDにアクセスしてApp-specific Passwordを発行する。
- ascProvider: Apple Developer の Membership に記載されているTeam IDを指定する。
electron-builder の afterSign フック
electron-builder の afterSign フックを使用して、コード署名が済んだアプリを自動で Notary に提出する。
フックスクリプトを./scripts/after-sign-mac.jsに置く。
const path = require("path");
const { notarize } = require("electron-notarize");
const appleId = process.env.APPLE_ID;
const appleIdPassword = process.env.APPLE_PASSWORD;
const ascProvider = process.env.ASC_PROVIDER;
const configPath = path.resolve(__dirname, "../package.json");
const appPath = path.resolve(__dirname, "../dist/mac/App.app");
const config = require(configPath);
const appBundleId = config.build.appId;
async function notarizeApp() {
console.log(`afterSign: Notarizing ${appBundleId} in ${appPath}`);
await notarize({
appBundleId,
appPath,
appleId,
appleIdPassword,
ascProvider,
});
console.log("afterSign: Notarized");
}
exports.default = async () => {
await notarizeApp();
};
package.jsonのbuildにafterSignを追加して、コード署名が終わった後にスクリプトが実行されるようにする。
"build": {
"afterSign": "./scripts/after-sign-mac.js"
}
Hardened Runtime and Entitlements
このままでは公証に失敗する。デフォルトで書き出されるバイナリでは、セキュリティの強化されたHardened Runtimeが有効になっていないためだ。以下のようなエラーメッセージが帰ってくる。
{
"status": "Invalid",
"statusSummary": "Archive contains critical validation errors",
"statusCode": 4000,
"issues": [
{
"severity": "error",
"code": null,
"path": "App.zip/App.app/Contents/MacOS/App",
"message": "The executable does not have the hardened runtime enabled.",
"docUrl": null,
"architecture": "x86_64"
},
}
}
そこで、package.jsonのbuild.mac.hardenedRuntimeをtrueにして Hardened Runtime を有効にする。
"build": {
"mac": {
"hardenedRuntime": true
}
}
Hardened Runtime 下では、必要に応じて Entitlement を指定しなければならない。Electron の実行にはallow-unsigned-executable-memory Entitlement が必要だ。そこで、entitlement.plistファイルをbuildフォルダに作成し、以下のような 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>
package.jsonのentitlements及びentitlementsInheritに Entitlement が記述された plist のファイルパスを指定する。
"build": {
"mac": {
"hardenedRuntime": true,
"entitlements": "./src/build/entitlement.plist",
"entitlementsInherit": "./src/build/entitlement.plist"
}
}
Hardened Runtime で Electron を実行することができるようになったので、Notary を通過できる状態になった。
実際にelectron-builderを実行して、すべてのプロセスが滞りなく動作することを確かめよう。
Verify Notary Status
ただしく公証を得られたかどうかはaltoolで調べることができる。
公証通過後に送られてくるメールにRequest Identifierが記載されているのでメモする。
Dear uetchy,
Your Mac software has been notarized. You can now export this software and distribute it directly to users.
Bundle Identifier: <Bundle ID>
Request Identifier: <UUID>
For details on exporting a notarized app, visit Xcode Help or the notarization guide.
Best Regards,
Apple Developer Relations
xcrun altool --notarization-infoコマンドに UUID と Apple ID、パスワードを指定して公証ステータスを確認する。
xcrun altool --notarization-info <UUID> -u $APPLE_ID -p $APPLE_PASSWORD
正しく公証が得られている場合は以下のようなメッセージが表示される。おめでとう!
2019-06-05 13:51:18.236 altool[5944:261201] No errors getting notarization info.
RequestUUID: <UUID>
Date: 2019-06-05 04:45:54 +0000
Status: success
LogFileURL: https://osxapps-ssl.itunes.apple.com/itunes-assets/Enigma123/v4/<Log file identifier>
Status Code: 0
Status Message: Package Approved