11 人が閲覧(直近 30 日) フレームワーク

Electron製Macアプリのビルド・コード署名・Apple公証|Electron 45とnotarytoolで実測した手順

Electron製Macアプリのビルド・コード署名・Apple公証|Electron 45とnotarytoolで実測した手順

Electronで作ったMacアプリをMac App Store外で配布し、利用者にセキュリティ設定の例外操作をさせずに起動させるには、Developer ID証明書によるコード署名とAppleの公証(Notarization)の両方が必要です。どちらかが欠けると、初回起動時にGatekeeperの警告が出ます。この記事では、Electron Forge 8で.appを作るところから始めて、Appleが公証に課す要件、entitlementsのキー、xcrun notarytoolの実コマンド、electron-builderとForgeでの自動化までを、2026年10月23日に手元のIntel Mac(macOS 26.6.2)とnpmレジストリで確かめた値でまとめます。対象はElectron 45.0.0(2026年10月21日公開)です。署名していないと不具合として現れるElectron API(safeStorageやautoUpdaterなど)も整理します。

まとめ:Electron製Macアプリの署名と公証で押さえる要点

  • Electron 45はmacOS 13 Ventura以降。macOS 12のサポートはElectron 44で終了した。ビルドした.appのLSMinimumSystemVersionは13.0だった。
  • 2段階が必須。コード署名(Developer ID Application証明書)と公証(Appleへ提出)の両方が要る。Mac App Store配布だけは公証不要。
  • universalビルドは署名設定とセットで考える。Forge 8.0.1の雛形のまま--arch=universalを実行すると、arm64側だけが再署名されて結合に失敗した。osxSignを書くと通る。
  • altoolは2023年11月1日に受付停止。本記事では後継のxcrun notarytoolを使う。手元のCommand Line Toolsに入っていたのはnotarytool 1.1.2 (41)。
  • 署名に--deepを使わない。Appleは署名時の--deepを明示的に禁じている(検証時の使用は可)。
  • entitlementsは@electron/osx-signの既定が出発点。arm64ビルドでcom.apple.security.cs.allow-jitが無いとアプリがクラッシュする、とelectron-builderが明記している。
  • 提出前の点検はsyspolicy_check。未署名のビルドにかけると、本体・ヘルパー・dylibの計15ファイルが「File is not signed at all」で止まった。
  • @electron/notarizeは3.1.2へ上げる。stapleの再試行は3.1.1では実際には走らず、3.1.2(2026年10月9日公開)で修正された。

MacでElectronアプリを.appにするまで:Forge 8の雛形とarm64・x64・universal

署名と公証は、ビルドした.appに対して行います。先に、何をどのアーキテクチャ向けに作るかを決めておきます。Electronそのものの仕組みや最初の1画面の作り方はElectronとは?Web技術でデスクトップアプリを作る仕組みと始め方に譲り、ここではMac向けに固有の点だけを扱います。

Electron 45が動くmacOSとCPU

Electron 45.0.0のREADMEは対応環境を「macOS (Ventura and up): Electron provides 64-bit Intel and Apple Silicon / ARM binaries for macOS.」としています。macOS 13 Ventura以降で、IntelとApple Siliconの両方のバイナリが配布されます。breaking-changes.mdでは、macOS 12 Montereyの切り捨てはElectron 44の変更として載っています。手元でビルドした.appのInfo.plistをPlistBuddyで読むと、LSMinimumSystemVersionは13.0でした。macOS 12以前の社内端末が残っている場合は、Electronを44未満に固定するか端末側を更新するかを、ビルド前に決める必要があります。

雛形の作成とパッケージ化のコマンド

Electron Forge 8.0.1(2026年9月29日公開)で雛形を作ります。テンプレートの指定方法が変わっており、以前の書き方の--template=vite-typescriptを渡すと「The “vite-typescript” template no longer exists. Use “–template vite –typescript” instead.」で止まりました。

npx [email protected] myapp --template vite --typescript
cd myapp

# .app を作る(out/myapp-darwin-<arch>/myapp.app)
npx electron-forge package --arch=arm64
npx electron-forge package --arch=x64

# 配布用の ZIP まで作る(out/make/zip/darwin/<arch>/)
# 既定は実行しているMacのCPU向け。別のCPU向けは -- --arch=arm64 を足す
npm run make

雛形はelectron@latestを--save-exactで入れるため、この日は45.0.0に固定されました。src/main.tsにはmacOS固有の処理が最初から入っています。全ウィンドウを閉じてもprocess.platform !== 'darwin'のときしかapp.quit()しない分岐と、Dockアイコンのクリックでactivateが来たらウィンドウを作り直す分岐です。Macのアプリはウィンドウを閉じてもCmd+Qまで終了しない、という作法に合わせたものなので、Windowsと同じ挙動にしたい場合もここを消すのではなく意図して書き換えます。

出力の大きさはdu -shでx64が258M、arm64が251M、両方を1つにしたuniversalが476Mでした。.appの中のファイル数は258で、開発ディレクトリのnode_modules(10,022ファイル)が丸ごと入るわけではありません。雛形はasar: trueでアプリのコードをapp.asar(この雛形では11,516バイト)1ファイルにまとめています。Appleは公証時間を抑える方法としてファイル総数の削減を挙げています。asarでファイル数を減らすことは有効な対策ですが、この実測では公証時間そのものの短縮までは確認していません。

universalビルドが署名設定なしで失敗する理由

Intel機とApple Silicon機の両方に1つのファイルで配るなら--arch=universalを使います。ところが雛形のまま実行すると、x64とarm64を個別にビルドした後の結合で次のエラーになりました。

✖ Stitching darwin/x64 and darwin/arm64 into a darwin/universal package
  [FAILED: While trying to merge mach-o files across your apps we found a mismatch,
   the number of mach-o files is not the same between the arm64 and x64 builds]

直前に出る差分の一覧(uniqueToArm64)は、ヘルパーやフレームワークの_CodeSignature/CodeResourcesだけでした。arm64側にだけ署名ファイルが増えています。原因は雛形が有効にしている@electron-forge/plugin-fuses 8.0.1で、fuseを書き換えたあとresetAdHocDarwinSignatureをarm64のときだけ有効にしています。条件は!hasOSXSignConfig && applePlatforms.includes(platform) && arch === 'arm64'で、署名設定が無いarm64ビルドに限ってad hoc署名をかけ直すため、x64側と中身が揃わなくなります。

packagerConfigにosxSignを書くと条件から外れ、同じ環境で結合まで通りました。空オブジェクトのosxSign: {}でも通ります。配布用に署名するならosxSignはどのみち書くので、universalで配る構成では署名設定を入れてからビルドを試す、という順にしておくと詰まりません。結合後のメインバイナリはlipo -archsでx86_64 arm64と表示されました。

Mac App Store外配布で必要になる署名と公証の要件

Appleの「Notarizing macOS software before distribution」は、公証を「App Reviewではない」と明言しています。逐語では「Notarization of macOS software is not App Review. The Apple notary service is an automated system that scans your software for malicious content, checks for code-signing issues, and returns the results to you quickly.」。人間の審査ではなく、マルウェア混入と署名不備を機械的に走査する仕組みです。

問題が無ければ公証サービスがチケットを発行し、開発者はそれをアプリへ貼り付け(staple)ます。Appleはチケットをオンラインにも公開するため、Gatekeeperはアプリに貼られたチケットからでもオンラインからでも公証済みであることを確認できます。適用範囲は「Beginning in macOS 10.15, all software built after June 1, 2019, and distributed with Developer ID must be notarized.」で、2019年6月1日以降にビルドしたDeveloper ID配布物が対象です。Mac App Store配布は提出プロセスに同等の検査が含まれるため除外されます。

Appleが公証に要求する前提条件

提出前に満たしておく条件は同ドキュメントに列挙されています。Electron側の設定と対応づけると次のようになります。

Appleの要件 Electronでの実現方法
実行ファイル全てに有効なコード署名 @electron/osx-sign が入れ子の順に署名
Developer ID証明書を使用 Mac Distribution・ad hoc・Apple Development・ローカル開発証明書は不可
Hardened Runtime を有効化 electron-builder は hardenedRuntime 既定 true
secure timestamp を含める codesign の --timestamp(署名ツールが自動付与)
get-task-allow を true にしない デバッグ用entitlementsを本番plistに残さない
macOS 10.9 以降のSDKへリンク Electron本体が満たす
entitlements は整形済みXML・ASCII plutil -lint で検証(バイナリplist不可)

Developer ID以外の証明書を使わないという条件は取り違えやすい箇所です。Appleは逐語で「Don’t use a Mac Distribution, ad hoc, Apple Developer, or local development certificate.」と書いています。Xcodeが自動生成した開発用証明書で署名したビルドは、公証に出す前の段階で対象外です。前の章でForgeが作った.appも、codesign -dvで見るとSignature=adhoc・TeamIdentifier=not setで、このままでは提出できません。

Gatekeeperで弾かれたときの現行手順

公証が通っていないアプリをユーザーが開こうとすると、Gatekeeperが警告を出します。Appleのサポート記事「Safely open apps on your Mac」が示す回避手順は、アプリを一度開こうとしたうえで「Open System Settings. Click Privacy & Security, scroll down, and click the Open Anyway button」です。かつて広く案内されていたFinderでの右クリック(Control-click)からの「開く」は、このページの現行版には一切記載がありません。社内配布で「右クリックで開いてください」と案内している手順書は、現在のmacOSの画面と合わなくなっています。開発者側で公証を通しておけば、この案内自体が不要になります。

Apple Developer Programへの登録と証明書の準備

個人登録と組織登録で変わる要件

Developer ID Application証明書はApple Developer Programのメンバーシップがないと発行できません。費用はApple公式の日本語ページに「Apple Developer Programの年間登録料は99米ドル、Apple Developer Enterprise Programの年間登録料は299米ドルです」とあります。価格は地域により異なり、登録手続き時に現地通貨で表示されます。非営利団体・認定教育機関・政府機関は登録料免除を申請できます。

組織として登録する場合はD-U-N-S番号が要ります。Dun & Bradstreetが割り当てる9桁の事業者識別番号で、Appleは登録審査で組織の実在と法人格の確認に使います。Appleの日本語ページは「組織(政府機関を除く)を登録するには、組織の情報、法人のステータス、および住所を確認するためのD-U-N-S番号を有している必要があります」とし、さらに「DBA、架空事業名、商号、支店名での登録は認められません」と明記しています。屋号や支店名義では通りません。申請担当者は組織の所有者・創設者・経営管理者・上級プロジェクト管理者か、上級社員から法的権限を付与された従業員である必要があります。D-U-N-S番号の取得自体に日数がかかるため、法人配布を計画しているなら最初に着手する項目です。

証明書の確認と秘密鍵の保管

証明書をインストールしたあと、手元に使える署名IDがあるかはコマンドで確認できます。Electron Forgeの公式ガイドが案内しているのと同じコマンドです。

security find-identity -p codesigning -v

ここにDeveloper ID Application: 組織名 (TEAMID) の行が出れば署名に使えます。証明書が無い環境では0 valid identities foundだけが返ります。出てこない場合は証明書が未インストールか、秘密鍵がキーチェーンに無い(別のマシンで発行したまま書き出していない)状態です。証明書署名要求(CSR)はキーチェーンアクセスの「認証局に証明書を要求」から作りますが、このとき生成される秘密鍵はそのMacのキーチェーンにしか存在しません。秘密鍵だけを後から再取得する手段は無いため、証明書を発行したマシンから.p12形式で書き出して保管しておきます。

entitlements.plistに書くキー:既定で足りる範囲と足すべき範囲

Hardened Runtimeを有効にすると、JITコンパイルや署名されていないライブラリの読み込みが既定で禁止されます。ChromiumのV8を抱えるElectronはここに正面からぶつかるため、entitlementsで例外を宣言する必要があります。

@electron/osx-sign 2.7.1(2026年9月28日公開)のパッケージに同梱されている既定のentitlementsを展開すると、直接配布用のdefault.darwin.plistには8つのキーが入っていました。com.apple.security.cs.allow-jit、com.apple.security.device.audio-input、com.apple.security.device.bluetooth、com.apple.security.device.camera、com.apple.security.device.print、com.apple.security.device.usb、com.apple.security.personal-information.location、com.apple.security.personal-information.photos-library です。GPUプロセス用とレンダラープロセス用はcom.apple.security.cs.allow-jitのみ、プラグイン用だけがallow-unsigned-executable-memoryとdisable-library-validationを追加で持ちます。この構成は2.7.0から変わっていません。2.7.1の変更はMac App Store向けで、リリースノートの「throw when MAS entitlements are missing app-sandbox key」のとおり、MAS用entitlementsにapp-sandboxが無いとエラーで止まるようになりました。

自前のplistを書くなら、まずallow-jitだけの最小構成から始め、使う機能に応じて上の一覧からキーを足していく形にします。ただし既定の8キーは性質が揃っていません。AppleのHardened Runtimeのページが資源アクセスとして挙げているのはマイク(audio-input)・カメラ・位置情報・写真ライブラリなどで、device.usbはAppleの説明が「Use this key to allow your sandboxed app to interact with USB devices」とあるとおりApp Sandbox用のキーです。Mac App Store外で配るアプリでは、使う機能に対応するHardened Runtime側のキーを選び、カメラやマイクのように利用者の許可を求める機能ではInfo.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-jit</key>
    <true/>
  </dict>
</plist>

electron-builderの設定には親用のentitlementsと、フレームワークやヘルパーに適用するentitlementsInheritの2つがあります。プロセスごとに権限を分けない構成なら、両方に同じ内容のファイルを指定して構いません。分ける場合は、ヘルパー側をallow-jitだけにしたbuild/entitlements.mac.inherit.plistを上と同じ書式で別途用意します。

このキーを落としたときの症状は起動失敗です。electron-builderの型定義には「Be aware that your app may crash if the right entitlements are not set like com.apple.security.cs.allow-jit for example on arm64 builds with Electron 20+」と書かれています。Intel機では動いたのにApple Silicon機だけ落ちる、という報告はこの条件に一致します。

迷いやすいのがcom.apple.security.cs.allow-unsigned-executable-memoryです。古い記事では必ず足すよう書かれていますが、@electron/notarizeのREADMEは「If you are using Electron 11 or below, you must add the com.apple.security.cs.allow-unsigned-executable-memory entitlement too. When using version 12+, this entitlement should not be applied as it increases your app’s attack surface.」としています。Electron 12以降では付けるべきではない、という判断です。disable-library-validationも同様で、ネイティブアドオンを別のTeam IDで署名している場合を除いて、むやみに付けるとHardened Runtimeの保護を自分から外すことになります。

絶対に入れてはいけないのがcom.apple.security.get-task-allowです。デバッガのアタッチを許可するキーで、値をtrueにしたまま提出すると公証が通りません。Xcodeは新規macOSプロジェクトでCODE_SIGN_INJECT_BASE_ENTITLEMENTSをYESに設定し、ビルド時にこのキーを自動で足すため、Xcode由来のplistを本番へ流用すると混入します。

形式面の落とし穴もあります。Appleはentitlementsを整形済みXMLかつASCIIで要求しており、バイナリplist(bplist)のまま渡すと公証サービスが拒否します。手で編集したplistは提出前に検証しておきます。

plutil -convert xml1 build/entitlements.mac.plist
plutil -lint build/entitlements.mac.plist

codesignでの署名:--deepを使わない理由

ElectronアプリはMyApp Helper (GPU).app・(Renderer).app・(Plugin).appやElectron Frameworkなど、入れ子の実行コードを多数抱えています。これらは内側から順に署名し、最後にアプリ本体を署名する必要があります。

ここで--deepを使ってまとめて済ませたくなりますが、Appleは「Creating distribution-signed code for the Mac」で明確に禁じています。逐語は「Don’t pass the --deep option to codesign when you sign code.」で、理由も2つ挙げられています。1つは「The --deep option applies the same code-signing options to every code item that it signs.」で、親と子で別のentitlementsが必要な構成では成り立ちません。もう1つは「codesign only signs nested code when you use the --deep option if that code is in particular locations in the top-level bundle.」で、想定外の場所に置いたコードは署名されないまま残ります。Appleは同じ箇所で、検証目的での--deepは有用だと補足しています。

アプリ本体に対する署名は次の形になります。dist/mac-arm64/MyApp.appはelectron-builderの出力先を例にしたパスで、Forgeの雛形ならout/myapp-darwin-arm64/myapp.appに読み替えます(以降のコマンドも同じ)。ヘルパーとフレームワークは種類に応じた署名オプションで先に署名してから実行します。

codesign --force --options runtime --timestamp \
  --entitlements build/entitlements.mac.plist \
  --sign "Developer ID Application: 組織名 (TEAMID)" \
  "dist/mac-arm64/MyApp.app"

Electronのバンドル構成では署名対象が数十個になるため、この順序制御を手書きのスクリプトで維持するのは現実的ではありません。@electron/osx-signがこの処理を担っており、electron-builderとElectron Forgeはどちらも内部でこれを呼びます。手作業のcodesignは、署名が落ちている箇所を特定するときの調査手段として使うのが実際的です。

notarytoolの実コマンド:認証情報の保存から提出・ログ確認まで

Appleは「Starting November 1, 2023, the Apple notary service no longer accepts uploads from altool or Xcode 13 or earlier.」として、2023年11月1日にaltool経由の受付を停止しました。現在の提出手段はコマンドラインのxcrun notarytoolのほか、Xcodeの画面操作、REST APIのNotary APIがあります。ここではCIに載せやすいnotarytoolを扱います。手元のmacOS 26.6.2でxcrun --find notarytoolを実行すると/Library/Developer/CommandLineTools/usr/bin/notarytoolが返り、バージョンは1.1.2 (41)でした。この環境にはXcode.appを入れておらずxcode-select -pもCommand Line Toolsを指しているため、公証コマンドを動かすだけならXcode本体は必須ではありません。ただし証明書の取得とインストールにはXcodeを使う手順が公式に案内されています。

認証情報の3方式と使い分け

notarytool submit --helpが受け付ける認証オプションは3系統に分かれます。いずれか1つを選びます。

方式 渡す値 向く場面
App Store Connect APIキー --key --key-id --issuer CI。チーム用キーは個人のApple IDに紐づかない
Apple ID --apple-id --password --team-id 手元での初回確認
キーチェーンプロファイル --keychain-profile 開発機での反復実行

--issuerの扱いには注意点があります。--helpの逐語は「App Store Connect API Issuer ID, UUID format. Required for Team API Keys. Do not provide for Individual API Keys.」で、チーム用キーでは必須、個人用キーでは渡してはいけません。渡すと401が返ります。

--passwordに渡すのはApple IDのパスワードではなく、appleid.apple.comで発行するApp用パスワードです。開発機で繰り返し実行するなら、資格情報をキーチェーンに保存しておくと毎回渡さずに済みます。

xcrun notarytool store-credentials "issoh-notary" \
  --key ./AuthKey_XXXXXXXXXX.p8 \
  --key-id XXXXXXXXXX \
  --issuer 00000000-0000-0000-0000-000000000000

提出からstapleまでのコマンド列

署名済みのアプリを公証に通す手順は次の3ステップです。.appはディレクトリなのでそのままでは提出できず、ZIPに固める必要があります。Appleの公式サンプルスクリプトは「Create a ZIP archive suitable for notarization.」というコメント付きでdittoを使っており、ここに合わせます(@electron/notarizeは--sequesterRsrcも付けています)。dittoはバンドルのシンボリックリンクや拡張属性を保ったまま固められるため、署名済みバンドルの受け渡しに向きます。

# 1. 提出用ZIPを作る
ditto -c -k --sequesterRsrc --keepParent \
  "dist/mac-arm64/MyApp.app" "dist/MyApp-submit.zip"

# 2. 公証へ提出して処理完了まで待つ
xcrun notarytool submit "dist/MyApp-submit.zip" \
  --keychain-profile "issoh-notary" --wait

# 3. チケットをアプリ本体へ貼り付ける
xcrun stapler staple "dist/mac-arm64/MyApp.app"

提出用ZIPと配布用ZIPは別物です。チケットが貼られるのは手順3の.appであって、手順1で作ったZIPの中身は公証前の状態のままです。ZIPで配布するなら、stapleのあとにdittoをもう一度実行して配布用アーカイブを作り直します。DMGで配る場合は、署名済みの.appをDMGに入れてDMGにも署名し、最外層のDMGを公証・stapleします。Appleの「Packaging Mac software for distribution」は「If you distribute your product using nested containers, only notarize the outermost container.」としており、中の.appとDMGを別々に提出する必要はありません。

--waitの役割は「提出を待つ」ことではなく「公証処理の完了を待つ」ことです。Appleの説明は「Use the wait flag to tell notarytool to exit only after the Notary service finishes processing the submission. This eliminates the need to poll the service for status.」で、省略した場合はアップロードと受付までで戻り、結果はinfoやwaitで別途確認することになります。--helpの既定値も--wait/--no-wait ... (default: false)です。

所要時間についてAppleは「Notarization completes for most software within 5 minutes, and for 98 percent of software within 15 minutes.」と公称しています。超過したときはまずnotarytool infoで提出が受理されているかを確認します。そのうえで処理自体が長引いているなら、原因候補の1つがファイル数です。Appleは長時間化を避ける方法の筆頭に「Minimize the total number of files, even when the individual files are small.」を挙げています。前述のForge雛形ではnode_modulesの10,022ファイルが.appでは258ファイルに収まっていました。asarを外したり、依存をapp.asar.unpackedへ大量に逃がしたりすると、この数が一気に増えます。長時間ブロックしたくないCIでは--webhookに公開URLを渡して完了通知を受ける方法もあります。

提出前の点検と、失敗時のログ取得

提出して落ちてから直すより、ローカルで先に検査したほうが速く終わります。本記事のmacOS 26.6.2環境にはsyspolicy_checkが標準で入っており、manページの説明は「syspolicy_check combines checks from multiple parts of macOS, including the frameworks and subsystems exposed by the existing codesign, spctl, and stapler commands.」です。サブコマンドは2つあり、notary-submissionは「Runs the same checks on an app as the Apple notary service」、distributionは「Runs the same checks on your app as macOS does when determining if your app can be executed. This includes Gatekeeper checks, XProtect checks, provisioning profile checks, and more.」と説明されています。

# 提出前:公証サービスと同じ検査をローカルで実行
syspolicy_check notary-submission "dist/mac-arm64/MyApp.app"

# staple後:macOSが起動時に行う判定と同じ検査
syspolicy_check distribution "dist/mac-arm64/MyApp.app" --json

署名前のForge雛形(x64)にかけた結果は「App has failed one or more pre-notarization checks.」で、指摘は17件でした。うち15件がSeverity: Fatalの「File is not signed at all.」で、本体の実行ファイル、4つのヘルパー、libvk_swiftshader.dylibとlibffmpeg.dylib、Squirrel・Mantle・ReactiveObjCの各フレームワークが並びます。残りは「This app is adhoc signed. While it may run locally, adhoc signed apps are not suitable for distribution.」と、Squirrel.frameworkのResources/ShipItに対する「Resources directory contains Mach-o binaries.」の警告2件です。universalの出力でも同じ17件でした。署名ツールを通した後にこの一覧が空になるかを見れば、提出前に署名漏れを拾えます。

それでも提出がInvalidで返ったときに原因を特定できるのはlogサブコマンドです。submitが返すsubmission IDを渡します。

# 過去の提出一覧
xcrun notarytool history --keychain-profile "issoh-notary"

# 失敗理由の詳細(JSONで返る)
# SUBMISSION_IDにsubmitが返したIDを設定してから実行する
xcrun notarytool log "${SUBMISSION_ID:?submission IDを設定してください}" \
  --keychain-profile "issoh-notary" notary-log.json

ログのissues配列には、署名されていない実行ファイルのパスやHardened Runtimeが無効なバイナリが個別に並びます。多いのはnode_modules配下に紛れ込んだ実行属性付きのバイナリやヘルパーです。署名そのものの整合性はcodesignで、チケットが貼れているかはstaplerで確認します。ここでの--deepは検証用途なので問題ありません。

codesign -vvv --deep --strict "dist/mac-arm64/MyApp.app"
xcrun stapler validate "dist/mac-arm64/MyApp.app"

従来よく使われてきたspctl --assessも動きますが、AppleはcodesignとspctlだけではGatekeeperの判定を完全には再現しないとしています。syspolicy_checkはその3コマンドが露出しているフレームワークをまとめて呼ぶため、実際の起動時判定に近い結果が得られます。electron-builderがgatekeeperAssessの既定値をfalseにしている(型定義の逐語は「Whether to let @electron/osx-sign validate the signing or not. @default false」)のも、ビルド中のspctl実行を前提にしない設計です。

electron-builderとElectron Forgeの署名・公証設定

ビルドツールを使うと、署名・ZIP化・提出・stapleが1回のビルドコマンドで走ります。ただし有効化に必要な設定は明示的に書く必要があり、その書き方は世代によって変わっています。

electron-builder:notarizeは真偽値、資格情報は環境変数

2026年10月23日にnpmレジストリを引いたところ、npm i electron-builderで入るのはlatestタグの26.15.3(2026年6月9日公開)のままでした。26系の新しい版はv26タグに割り当てられており、現在は26.18.0(2026年10月20日公開)です。latestには4か月以上新版が乗っていません。26.16以降を使いたい場合はバージョンを明示するかelectron-builder@v26を指定します。

26系のmac.notarizeは真偽値です。26.18.0のmacOptions.tsでもreadonly notarize?: booleanで、teamIdのようなサブプロパティを書く形ではありません。資格情報は環境変数から読む設計で、型定義に3組が列挙されています。

{
  "build": {
    "appId": "jp.co.example.myapp",
    "mac": {
      "target": ["dmg", "zip"],
      "hardenedRuntime": true,
      "entitlements": "build/entitlements.mac.plist",
      "entitlementsInherit": "build/entitlements.mac.inherit.plist",
      "notarize": true
    }
  }
}
# 次の3組のうち、いずれか1組だけを設定する(併用しない)

# 1. App Store Connect APIキー(推奨)
export APPLE_API_KEY=./AuthKey_XXXXXXXXXX.p8
export APPLE_API_KEY_ID=XXXXXXXXXX
export APPLE_API_ISSUER=00000000-0000-0000-0000-000000000000

# 2. Apple ID
# export [email protected]
# export APPLE_APP_SPECIFIC_PASSWORD=xxxx-xxxx-xxxx-xxxx
# export APPLE_TEAM_ID=TEAMID

# 3. キーチェーンプロファイル
# export APPLE_KEYCHAIN=login.keychain-db
# export APPLE_KEYCHAIN_PROFILE=issoh-notary

どれを選ぶかについて、electron-builderは型定義の中で「For security reasons it is recommended to use the first option」と1番目のAPIキー方式を名指しで推奨しています。Apple IDとApp用パスワードの組は、Apple IDのパスワードを変更するたびにApp用パスワードの再生成が要る点でも運用が重くなります。方式を切り替えるときは前の方式の環境変数を残さないでください。entitlementsとentitlementsInheritを省略した場合はbuild/entitlements.mac.plistとbuild/entitlements.mac.inherit.plistが既定で参照されます。

Electron Forgeの署名・公証設定:osxSignとosxNotarize

ForgeはpackagerConfigの下に署名用と公証用の設定を別々に持ちます。公式ガイドはosxSignについて「object must exist even if empty」とし、空オブジェクトから始めることを勧めています。前述のuniversalビルドの失敗を避ける意味でも、この1行は先に書いておきます。公証側は認証情報を環境変数から読ませます。次の例は雛形のforge.config.mtsのpackagerConfigだけを書き換えたもので、ファイル全体を置き換えると雛形のmakers・plugins(Viteのビルドとfuses)が消えます。APPLE_PASSWORDにはApp用パスワードを入れます(Forgeのガイドの変数名に合わせています)。

import type { ForgeConfig } from '@electron-forge/shared-types';

// forge.config.mts の packagerConfig 部分(import・makers・plugins は雛形のまま残す)
const config: ForgeConfig = {
  packagerConfig: {
    asar: true,
    osxSign: {},
    osxNotarize: {
      appleId: process.env.APPLE_ID!,
      appleIdPassword: process.env.APPLE_PASSWORD!,
      teamId: process.env.APPLE_TEAM_ID!,
    },
  },
  // makers・plugins は雛形のまま
};

export default config;

@electron/notarize 3.1.2で直ったstapleの再試行

Forgeと@electron/packagerが内部で呼ぶのは@electron/notarizeです。現行版は3.1.2(2026年10月9日公開)で、Forge 8.0.1の雛形を作った時点でnode_modulesに入っていたのもこの版でした。v3.0.0でaltool用のLegacy strategyが削除され、Node.js 22.12.0以上・ESM専用になっています。

3.1.1と3.1.2のパッケージを展開して比べると、処理の違いはlib/index.jsの1行だけでした。どちらもnotarytoolでの提出後にpromise-retryでstapleApp()をretries: 3付きで呼びますが、3.1.1はretry(() => stapleApp({ appPath }), …)、3.1.2はretry((retry) => stapleApp({ appPath }).catch(retry), …)です。promise-retryは渡されたretry関数が呼ばれたときだけやり直すため、3.1.1の書き方では失敗しても再試行しません。毎回失敗する関数を両方の書き方で渡して数えると、3.1.1の形は1回、3.1.2の形は4回(初回+再試行3回)実行されました。リリースノートの表題も「Retry failed stapling after notarization」です。公証は通ったのにstapleだけが一時的なネットワーク失敗で落ちてビルドが止まる、という症状が3.1.1で出ていたなら、3.1.2へ上げるのが対処です。

引数の検証は3.1.1から変わっていません。tool引数は型定義から消えており、TypeScriptでは型エラーになり、JavaScriptで書いても実行時は単に無視されます。tool: 'notarytool'だけを頼りにしている設定は認証情報の不足として落ちます。validateAuthorizationArgsへ実際に渡して確かめた結果が次のとおりです。

渡した引数 挙動
{ tool: 'notarytool' } のみ 例外「No authentication properties provided」
tool + Apple ID一式 通る(toolは無視される)
appleId + appleIdPassword 例外「The teamId property is required」
appleApiKey + appleApiKeyId 通る(teamIdもappleApiIssuerも不要)
appleId と keychainProfile の併用 例外「Cannot use … at once」

teamIdが必須なのはApple ID方式のときだけで、APIキー方式とキーチェーン方式では要りません。この違いは移植時に踏みやすい落とし穴です。Electron本体のドキュメント(v45.0.0タグのcode-signing.md)に載っている@electron/packagerの例は、今もosxNotarizeにappleIdとappleIdPasswordだけを書いており、teamIdがありません。@electron/packager 20.3.0のcreateNotarizeOptsは受け取ったオブジェクトをそのまま返すだけでteamIdを補わないため、この例をコピーすると上表の3行目で止まります。Forge側のガイドがappleId・appleIdPassword・teamIdの3つを表に載せており、そちらが現行の正しい書き方です。

notarize()の処理順は「署名の検証(codesign -vvv --deep --strict)、.appならditto -c -k --sequesterRsrc --keepParentでZIP化、notarytool submit --wait --output-format json、xcrun stapler staple」で、.dmgと.pkgはZIP化せずそのまま提出します。ビルドツール経由ならxcrun stapler stapleを後続ステップに書き足す必要はありません。

署名しないと壊れるElectron API:バグに見えて原因が署名であるケース

自動更新やキーチェーン連携を使うアプリでは、署名と公証を配布直前の作業に回すべきではありません。Electron公式ドキュメント(v45.0.0タグのcode-signing.md)は「macOS APIs that require code signing」という節を設け、署名済みでないと正しく動かないAPIを名指ししています。同じ節は「an unsigned or ad-hoc signed app may behave inconsistently, and issues that look like Electron bugs are often resolved by properly signing (and notarizing) your app」とも書いており、これらのAPIについては、未署名ビルドでの確認だけでは配布時の動作を判断できない、という話です。

  • safeStorage … 逐語で「Without a valid, consistent code signature, macOS may be unable to tell that two builds of your unsigned app are "the same app", which can cause the Keychain to prompt the user for permission again after every update.」。アップデートのたびにキーチェーンの許可を求められる場合、暗号化処理の実装より先に、署名の有無とビルド間で署名が一貫しているかを確認する価値があります。
  • app.setLoginItemSettings() … パッケージ化・署名・公証がされていないと、ログイン項目の登録が無言で失敗することがあります。エラーが出ないため実装ミスと誤認しやすい挙動です。
  • cookieEncryption fuse … Cookie暗号化はsafeStorageと同じOSレベルのキーチェーンアクセスを使うため、同じ署名要件がかかります。Forge 8.0.1の雛形のforge.config.mtsは[FuseV1Options.EnableCookieEncryption]: trueを最初から書いているので、雛形から始めたアプリは何もしなくてもこの要件の対象です。
  • autoUpdater … 逐語で「Squirrel.Mac requires the app to be signed for automatic updates to work at all」。自動更新は署名なしでは端的に動きません。

逆に、これらのAPIを一切使わない単機能の社内ツールなら、開発中はad hoc署名(identity: "-")で進める判断が成り立ちます。ただしad hoc署名にも条件があります。electron-builderの型定義は「When using ad-hoc signing (identity: "-"), hardened runtime enforces library validation which will reject pre-signed Electron frameworks that carry a different Team ID.」と書いており、hardenedRuntimeを既定のtrueのままad hoc署名すると、別のTeam IDで署名済みのElectronフレームワークがライブラリ検証で弾かれます。回避するにはhardenedRuntime: falseにするかcom.apple.security.cs.disable-library-validationを足すことになりますが、どちらも本番構成としては採るべきではありません。開発用と配布用で設定を分ける前提の話です。

CIで署名と公証を通す:秘密情報の受け渡しとネットワーク要件

codesignもnotarytoolもmacOSのシステムコマンドなので、CIではmacOSランナーが必要です。GitHub Actionsならruns-on: macos-latestを指定します。ワークフローの組み立て自体はGitHub Actionsでビルド・自動テストを設定する方法で扱っている手順と同じで、macOS固有なのは証明書と資格情報の渡し方です。

証明書はBase64化してリポジトリのSecretsに入れ、ジョブの中で一時キーチェーンへ取り込みます。.p8のAPIキーも同様にSecretsから復元します。

- name: 証明書を一時キーチェーンへ取り込む
  env:
    MAC_CERT_P12: ${{ secrets.MAC_CERT_P12 }}
    MAC_CERT_PASSWORD: ${{ secrets.MAC_CERT_PASSWORD }}
    KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
  run: |
    echo "$MAC_CERT_P12" | base64 --decode > /tmp/cert.p12
    security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
    security default-keychain -s build.keychain
    security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
    security import /tmp/cert.p12 -k build.keychain \
      -P "$MAC_CERT_PASSWORD" -T /usr/bin/codesign
    security set-key-partition-list -S apple-tool:,apple: \
      -s -k "$KEYCHAIN_PASSWORD" build.keychain

set-key-partition-listを省くと、codesign実行時にキーチェーンのパスワード入力ダイアログを待ってジョブがタイムアウトします。CIで「署名が固まる」場合はここを疑います。electron-builderに証明書の取り込みを任せている場合も同じ箇所で、26.16.1のリリースノートには「fix(mac): use keychain password for set-key-partition-list」という修正が入っています。latestの26.15.3を使っているCIはこの修正を含みません。環境変数とシークレットの扱い全般は環境変数とは?Linux・Windowsの設定方法とDocker・CIでの受け渡しにまとめています。

見落とされやすいのがネットワーク要件です。secure timestampの取得にはインターネット接続が必須で、Appleは「macOS accepts only one secure timestamp server, namely timestamp.apple.com」と明記し、IPv4は17.32.213.0/24・17.157.80.0/24・17.179.249.0/24、IPv6は2620:149:980:500::/64・2620:149:981:603::/64・2620:149:982:404::/64の範囲を挙げています。2026年10月23日に手元から確認したところ、このホストは名前解決で17.157.80.35(上記IPv4範囲内)を返し、TCP 80番のHTTPには応答する一方、443番は接続できませんでした。タイムスタンプ取得はHTTPで行われるため、公証アップロード用のHTTPSとは別に80番の疎通を確保する必要があります。man codesignの記述も「If the timestamp authority service cannot be contacted over the Internet, or it malfunctions or refuses service, the signing operation will fail.」で、警告ではなくエラーです。

署名済みバイナリにタイムスタンプが入ったかはcodesign -dvvで確認できます。Appleの説明では、出力にTimestampと日付が出れば secure timestamp 付き、Signed Timeが出ている場合は付いていません。

切り分けの順序も決めておきます。ローカルでsyspolicy_check notary-submissionが通るかを先に確認し、通ったうえでCIで落ちるならネットワークと資格情報を疑う。この順を守らないと、署名の不備をCI設定の問題として追いかけることになります。ログにAPIキーやApp用パスワードが混ざらないよう、--verboseを常用しない運用にしておきます。

よくある質問

altoolはまだ使えますか?

公証の用途では使えません。Appleは2023年11月1日にaltoolからのアップロード受付を停止しており、テクニカルノートでも公証目的でのaltoolを非推奨としています。ただしApp Storeへの提出など他の用途では引き続き使えます。@electron/notarizeもv3.0.0でaltool用の実装を削除しました。

Developer ID Application証明書の有効期限が切れたらどうなりますか?

配布済みのアプリは動き続けます。Appleは「If your certificate expires, users can still download, install, and run versions of your Mac applications that were signed with this certificate. However, you’ll need a new certificate to sign updates and new applications.」としており、期限切れで止まるのは新しいビルドへの署名だけです。ただし証明書が失効(revoke)された場合は別で、その証明書で署名したアプリはインストールできなくなります。期限切れと失効では結果が大きく違うため、鍵の取り扱いを見直すときも安易にrevokeを選ばないでください。

App Store Connect APIキーはどこで発行しますか?

App Store Connectにサインインし、「ユーザーとアクセス」からIntegrationsのキー管理画面で発行します。発行時にダウンロードできる.p8ファイルは再取得できないため、その場で安全な場所へ保管します。あわせてKey ID(10文字前後の英数字)とIssuer ID(UUID形式)を控えます。チーム用キーではIssuer IDが必須ですが、個人用キーでは指定してはいけません。CIではこの3つを環境変数APPLE_API_KEY・APPLE_API_KEY_ID・APPLE_API_ISSUERに入れて渡します。

Macアプリが「開けません」と表示されるのは署名が原因ですか?

Gatekeeperの警告文で切り分けられます。「開発元を確認できないため開けません」は署名または公証の不備です。「アプリケーションが壊れているため開けません」は、ファイルの改変や証明書の失効を検出した場合に出ます。前者であればユーザー側の暫定回避はシステム設定のプライバシーとセキュリティにある「このまま開く」ですが、恒久的な解決は開発者側で公証を通すことです。

法人でApple Developer Programに登録するには何が必要ですか?

組織登録では、政府機関を除きD-U-N-S番号と法人格の確認が必要です。申請担当者には、組織を法的に拘束できる立場か、上級社員から権限を付与されていることが求められます。屋号や支店名義では登録できません。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.07.21 テックブログ Apache Tomcatの脆弱性一覧【2026年10月】最新版11.0.27・10.1.61・9.0.123とCVE-2026-59083ほかの影響確認・対応手順
  2. 2026.10.21 テックブログ NEC委託先の不正アクセスとは|顧客システム暗号化・情報漏えいの経緯と委託元が点検する正規アカウント
  3. 2026.10.21 テックブログ ふるさとチョイス不正アクセスで445万件流出:対象確認・パスワード変更と再開時期
  4. 2026.10.22 テックブログ Tomcatバージョン一覧とサポート期限【2026年10月】9.0系は2027年3月末で終了・9.1系と11.0系への移行判断
  5. 2024.07.05 コラム XAMPPの脆弱性とは?危険性とCVE-2024-4577への対策をわかりやすく解説【2026年10月】

RELATED POSTS 関連記事

目次