2020年2月最新のElectronアプリ、アップル公証(Notarization)対応

アップル公証(Notarization)

Macにはアップル公証という、Macの野良アプリを署名する仕組みがあり、
このアップル公証を通しておかないと、 macOS Catalinaではアプリを起動する際、そのアプリの起動がブロックされます。
(起動する術はある、らしい)


少し前、これに対応して、自前のElectronアプリで アップル公証を通していたのですが、
実は、その時は、公証の条件が少し緩くなっていたらしく・・・

アップル、macOS野良アプリの公証要件を一時的に緩和。Catalinaへのスムーズな移行のため https://japanese.engadget.com/2019/09/05/macos-catalina/


またもや、自家製Electronアプリで、 アップル公証が通らなくなってしまったのでありました。
(´・ω・`)


この記事は、Electron製アプリでアップル公証(Notarization)を通す、
その手順についてのお話となります。


Xcodeのバージョン

まず最初に、アップル公証を通すには、新しめのXcodeが必要です。 バージョンが低い場合はXcode 10以降にバージョンを上げておきましょう。
App Storeからインストールできない場合は、Apple Developerからダウンロードしてきて、インストールします。
https://developer.apple.com/jp/


作ったアプリに署名をつける

作ったアプリに署名をつけます。
electron-osx-signというライブラリを使うか、
codesignコマンドで、Electronアプリに署名をつけられます。
https://github.com/electron/electron-osx-sign


おそらく、electron-osx-signを使うか、
electron-builder、electron-packagerのコマンドで、 オプションを追加して署名をつける方が楽でしょう。


が、自分の場合は、
ElectronアプリでDynamicLibraryを使っていたり、
アプリに同梱している別の実行ファイルを叩いていたりしていまして、
アプリの構造が少し独特なので、
codesignコマンドを直に叩いて署名をつけています。


codesign

codesignコマンドでアップル公証用の署名をつける場合は、
次の2つのパラメータをつけて、codesignを実行します。

--options runtime
--timestamp


下のコードは、仮想コードです。

仮想コードですが、
"どのファイルに対して、どのパラメータでcodesignを実行するか"
というのが大事な情報なので、 きっとこの記事を読みに来た人にはこれでもわかるはず・・・
アプリ名とかIDは適当に読み替えてください。

APP='MYukkuriVoice'
identity='Developer ID Application: Foo Bar (99QXXXXXXX)'
PARENT_PLIST='build/darwin.app.plist'
CHILD_PLIST='build/darwin.app-child.plist'

/usr/bin/codesign --options runtime --timestamp -s "${identity}" -f --entitlements "${CHILD_PLIST}"
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libswiftshader_libEGL.dylib`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libGLESv2.dylib`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libswiftshader_libGLESv2.dylib`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libffmpeg.dylib`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/crashpad_handler`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Electron Framework.framework`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (GPU).app/Contents/MacOS/${APP} Helper (GPU)`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (GPU).app`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (Plugin).app/Contents/MacOS/${APP} Helper (Plugin)`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (Plugin).app`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (Renderer).app/Contents/MacOS/${APP} Helper (Renderer)`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper (Renderer).app`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper.app/Contents/MacOS/${APP} Helper`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/${APP} Helper.app`

/usr/bin/codesign --deep --options runtime --timestamp -s "${identity}" -f --entitlements "${CHILD_PLIST}"
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Mantle.framework/Mantle`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Mantle.framework/Versions/A`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/ReactiveCocoa.framework/ReactiveCocoa`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/ReactiveCocoa.framework/Versions/A`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Squirrel.framework/Versions/A/Resources/ShipIt`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Squirrel.framework/Squirrel`
    `${APP}-darwin-x64/${APP}.app/Contents/Frameworks/Squirrel.framework/Versions/A`

# アプリから読み込んでいるDynamicLibrary
# (このアプリ固有)
/usr/bin/codesign --options runtime --timestamp -s "${identity}" -f --entitlements "${CHILD_PLIST}"
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AqKanji2Koe.framework/Versions/A/AqKanji2Koe`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AqKanji2Koe.framework`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AqUsrDic.framework/Versions/A/AqUsrDic`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AqUsrDic.framework`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk10.framework/Versions/A/AquesTalk`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk10.framework`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk2.framework/Versions/A/AquesTalk2`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk2.framework`

# アプリから実行している外部実行ファイル。とそこから読んでいるDynamicLibrary
# (このアプリ固有)
/usr/bin/codesign --options runtime --timestamp -s "${identity}" -f
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/maquestalk1-ios`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/secret`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/maquestalk1`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk.framework/Versions/A/AquesTalk`
    `${APP}-darwin-x64/${APP}.app/Contents/Resources/app.asar.unpacked/vendor/AquesTalk.framework`

/usr/bin/codesign --options runtime --timestamp -s "${identity}" -f --entitlements "${CHILD_PLIST}"
    `${APP}-darwin-x64/${APP}.app/Contents/MacOS/${APP}`

/usr/bin/codesign --options runtime --timestamp -s "${identity}" -f --entitlements "${PARENT_PLIST}"
    `${APP}-darwin-x64/${APP}.app`


codesignコマンドで直接、Electronアプリを署名しようとすると、
対象のファイルがとても多くなりますね。
コマンドの実行順序は数カ所ほど、入れ替わると駄目な場所があります。


次の2つのバイナリは、
2020年1月まではcodesignで指定しなくてもアップル公証が通っていたのですが、
今月(2月)になったら、個別にcodesignしておかないとアップル公証が通らなくなってしまっていました。

Electron Framework.framework/Versions/A/Resources/crashpad_handler
Squirrel.framework/Versions/A/Resources/ShipIt


entitlements

codesignコマンドで指定するentitlementsは、 Electronの場合、
com.apple.security.cs.allow-unsigned-executable-memory が必須になります。


それから、このアプリの場合はDynamicLibraryを使っているので、 それとは別に、
com.apple.security.cs.disable-library-validation を追加しています。
アプリによっては、これら以外にも必要となる指定があるでしょう。

<?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/>
    <key>com.apple.security.cs.disable-library-validation</key>
    <true/>
  </dict>
</plist>


古いOSで作った実行ファイル

古いOSでビルドした実行ファイルが含まれていると、アップル公証がエラーになります。 これを解消するには新しく実行ファイルを作り直す必要があり、回避方法は無さそうです。

# このように怒られる
The binary uses an SDK older than the 10.9 SDK.


アプリによっては、この条件のせいで、アップル公証を通したくても通せなくなってしまうこともありそうです。

実行ファイルでなく、古いOSでビルドしたライブラリは、アップル公証でエラーにはなりませんでした。
助かった(>_<)


notarize

codesignで署名したアプリをアップル公証(notarization)に通します。
調べれば分かりますが手順がとてもとてもとても面倒くさい。


electron-notarizeライブラリを使うと、さくっと公証を通して、 完結まで処理してくれるので、
Electronアプリのアップル公証は、基本、これを使うべきだと思いました。
https://github.com/electron/electron-notarize


次のコードはgulpで、notarizeを実行するコードです。
実行結果が返ってくるまでは、毎回毎回少し時間がかかります。
5分、10分くらい。

const gulp = require('gulp');
const notarize = require('electron-notarize').notarize;

gulp.task('_notarize', () => {
    const APP = 'MYukkuriVoice';
    const platform = 'darwin';
    const APP_PATH = `${APP}-${platform}-x64/${APP}.app`;
    const bundleId = 'jp.nanasi.foobar';
    const teamId = '99QXXXXXXX';

    const appleId = DEVELOPER_APPLE_ID;
    const env = process.env;
    const password = env.AC_PASSWORD;

    return notarize({
      appBundleId: bundleId,
      appPath: APP_PATH,
      appleId: appleId,
      appleIdPassword: password,
      ascProvider: teamId,
    });
});


verify notarization

アップル公証を通したら、
本当にアップル公証が通っているか、次のコマンドで確認します。

# アップル公証の実行履歴の表示
xcrun altool --notarization-history 0 -u 'hoge@example.jp' -p 'xxxx-xxxx-xxxx-xxxx'

# 指定のアプリがアップル公証を通っているか
xcrun stapler validate -v MYukkuriVoice-darwin-x64/MYukkuriVoice.app


もちろんこれで確認が取れてもアプリが動かないことがあります。
その場合は、単純にアプリのバグでなければ、

  • アップル公証を通した後にアプリが変更された
    (一度、アンチウィルスソフトにやられました)
  • entitlementsの指定が足りない

のどちらかが怪しそうです。


おわり

終わりでありますよ。


自分はまだmacOS Catalinaに移行していないので、
動作確認が少し面倒です。(´・ω・`)