2026-09-08 公開 / アプリ開発
Macアプリを署名して配るまでに踏んだ4つ。どれもエラーを出さずに壊れる
macOSの小さな常駐アプリを作って、署名と公証(Notarization)を通して配るところまでやりました。ビルド自体は最初から通っていたのに、配る形にする過程で4回つまずきました。共通しているのは、どれも「失敗した」と言ってくれないことです。手元では正常に見えて、受け取った人の環境で初めて壊れます。
1. 証明書の名前が紛らわしい。開発用では配れない
署名するには証明書が要ります。手元にはこれがありました。
$ security find-identity -v -p codesigning
1) ... "Apple Development: 名前 (XXXXXXXXXX)"
「Development」とあるので開発用だと分かりますが、これで署名しても Gatekeeper は「署名されていない」と同じ扱いをします。配布に必要なのは Developer ID Application という別の証明書です。
紛らわしいのは、どちらも codesign コマンドは成功することです。署名は付きます。付いたうえで、配布したときだけ効かない。
そして Developer ID は、有料の開発者プログラムに登録しないと発行できません。「署名しておこう」と思い立った日には作れない、という時間差があります。配布の予定が少しでもあるなら、登録だけ先に済ませておくと後の作業が詰まりません。
2. zip で固めると、ゴミが同梱される
アプリをZIPにして配ろうとして、こうしていました。
zip -r MyApp.zip MyApp.app
できたZIPの中身を見ると、こうなっていました。
MyApp.app/
__MACOSX/ ← これ
__MACOSX/MyApp.app/
__MACOSX/MyApp.app/Contents/
...
macOS の拡張属性を別ファイルとして保存したものです。受け取った人が展開すると、アプリの隣に謎のフォルダが並びます。実害は小さいのですが、配布物としては雑に見えます。
Apple が案内しているのは ditto です。
ditto -c -k --keepParent MyApp.app MyApp.zip
ひとつ注意があります。--sequesterRsrc というオプションを足すと、また __MACOSX が入ります(拡張属性をそこへ隔離する、というのがこのオプションの意味なので当然です)。私は「Apple製ツールなら安全だろう」と付けて、12エントリ混入させました。
展開方法で見え方が変わる
もうひとつ、確認するときの落とし穴があります。ditto で作ったZIPをコマンドラインの unzip で展開すると、._MyApp のようなファイルが現れます。
これは unzip 側の挙動です。Finder でダブルクリックした場合(内部的には ditto 相当)は、拡張属性として正しく復元されるので何も現れません。
検証は、利用者と同じ経路で行う必要があります。私は最初 unzip で確認して「まだゴミが残っている」と誤判定し、直さなくていいものを直そうとしました。
# 利用者と同じ経路で展開して確かめる
ditto -x -k MyApp.zip /tmp/check
find /tmp/check -name '._*' -o -name '__MACOSX'
3. 署名の「後」に拡張属性を消すと、封印が壊れる
これがいちばん厄介でした。
2番の対策として、ZIPを作る前に拡張属性を落とそうと考えました。
codesign --sign "..." MyApp.app # 署名する
xattr -cr MyApp.app # 拡張属性を消す ← ここが間違い
ditto -c -k --keepParent MyApp.app MyApp.zip
手元では codesign --verify が通ります。ところが、できたZIPを展開して検証すると落ちます。
MyApp.app: a sealed resource is missing or invalid
file added: .../Contents/._Info.plist
署名は「このファイル群がこの状態であること」を封印する操作です。そのあとに属性を触ると、封印した内容と実物がずれます。順序を入れ替えるだけで直りました。
xattr -cr MyApp.app # 先に消す
codesign --sign "..." MyApp.app # そのあと署名
厄介な点は、壊れたことが手元では分かりにくいことです。署名した直後の .app を検証すると通ります。ZIPにして、展開して、初めて壊れているのが見えます。配布形態まで作って検証しないと気づけません。
4. CIに「未署名でも公開できる経路」を残さない
タグを打つと自動でリリースを作る仕組みを先に用意していました。証明書がまだ無かった時期なので、こういう分岐にしていました。
- 証明書が設定されていれば、署名して公証する
- 無ければ、仮の署名のまま「公証を受けていません」と明記して出す
当時は正しい判断でした。未公証でも先に出して使ってもらう、と決めていたからです。
問題は証明書を手に入れた後でした。手元で署名・公証まで通したものを配りたいのに、CI側の設定はまだ空のままです。この状態でタグを打つと、CIが仮署名のZIPでリリースを作り、手元の公証済みファイルを上書きします。
気づいたのは、タグを打つ直前に「この分岐は今どちらへ行くのか」を読み直したときでした。実行していたら、公証したはずのものが未公証で公開されていました。
直し方は、分岐を消して署名できないならリリースを作らないようにするだけです。
# 署名用の設定が無いときはリリースを作らない。
# ビルドと成果物の生成だけ行い、理由をログに残す。
- uses: softprops/action-gh-release@v2
if: steps.signing.outputs.signed == 'true'
...
「とりあえず出す」ための逃げ道は、必要がなくなった時点で塞ぐ。残しておくと、条件が変わったときに黙って古い方へ流れます。
ついでに: 自動化を止める要素がひとつある
Developer ID で初めて署名するとき、macOSが確認ダイアログを出します。「codesign がキーチェーンに保存されている機密情報を使おうとしています」というものです。
ここで「常に許可」を選ぶまで、codesign は無言で待ち続けます。タイムアウトもエラーも出ません。スクリプトから実行していると、ただ固まっているように見えます。
初回だけは、ダイアログが確実に見える状態で手動で実行するのが早いです。一度許可すれば、以降は止まりません。
持ち帰れること
- 4つとも、失敗を報告しません。コマンドは成功し、手元では正常に見えます。壊れているのは配布物の中だけです
- 検証は、利用者と同じ経路で。作った直後のフォルダではなく、配る形(ZIP)にして、利用者と同じ方法で展開して確かめます
- 署名は「その時点の状態」を封印する操作。後から中身に触る工程を置かない。順序が意味を持ちます
- 暫定の逃げ道は、要らなくなったら塞ぐ。条件が変わったとき、分岐は黙って古い方へ流れます
- 名前が似ているものは、実際に試すまで信用しない。Development と Developer ID は、どちらも署名に成功します
最後にひとつ。これらは全部「配布物を作ったあとに、それを開いて確かめる」だけで見つかりました。ビルドが通ったことと、配れる状態になっていることは別です。間に検証を1回挟むかどうかで、気づく場所が自分の手元か、受け取った人の手元かに分かれます。