アップデートのパイプラインは正常で、bundleはエッジで利用可能、デバイスも更新を確認しています。しかし、生成されたJavaScriptに不審な変更があることに誰かが気づきました。ビルドランナーの侵害、開発者の認証情報の盗難、あるいはアーティファクトの改ざんによって、テスト完了後に悪意のあるペイロードがリリースへ混入した可能性があります。もし 署名検証がなければ、クライアント側では、チームがビルドしたbundleと、転送中や配信レイヤーで攻撃者によって改ざんされたものを確実に区別する手段がありません。
署名付きアップデートはその判断を変えます。デバイスは、現在実行中のcodeを置き換える前に、信頼できる公開鍵を使用してbundleを検証します。署名が一致しない場合、アップデートは適用されません。仕組みは単純ですが、本番環境での失敗は暗号技術そのものではなく、その運用周りで発生することがほとんどです。チームは鍵のローテーションを追えなくなり、誤ったアーティファクトに署名し、未検証のキャッシュファイルを適用し、テレメトリをほとんど収集していないため、どのデバイスがアップデートを拒否したのか説明できない——といったことが起きます。
以下のセクションでは、信頼モデルや検証フローからCI/CDコントロール、インシデント対応、署名だけでは解決できない限界まで、こうした運用上のエッジケースに焦点を当てます。
目次
- 署名検証が致命的なアップデートを防ぐ理由
- 暗号署名がいかに信頼を構築するか
- モバイルおよびWebシステムにおける実用例
- 完全な検証フローの構築方法
- 多くのチームが見落としがちな鍵管理の課題
- CI/CDとモニタリングへの検証機能の統合
- セキュリティのベストプラクティスとよくある落とし穴
署名検証が致命的なアップデートを防ぐ理由
人気の Capacitor プラグインの CI/CD パイプラインが、リリースプロセスの終盤で侵害されました。攻撃者は live update bundle を改ざんし、アプリケーションデータを読み取る code を追加した上で、パイプラインの通常の配信経路を使用してアーティファクトを公開しました。デバイスには見慣れないダウンロードドメインは見えません。見えるのは、インストール待ちの正規のアップデートです。
クライアント側での暗号学的検証がない場合、アプリはアップデートポリシーが許し次第、改ざんされた bundle をダウンロードして実行してしまう可能性があります。想定される被害範囲には、データの流出、認証情報の窃取、決済フローの改ざん、ビジネスロジックの隠れた改変が含まれます。また、調査も困難です。エンジニアは、どのアーティファクトが配信され、どのチャネルがそれを受け取り、どのデバイスがダウンロードし、どのデバイスが適用したのか、そしてリリースが撤回される前に悪意のある code が実行されたのかを特定しなければなりません。

署名検証を有効にしているチームでは、別の失敗モードになります。アプリは同じ汚染された bundle をダウンロードし、期待されるダイジェストを計算して、付随する署名を信頼できるキーで検証します。暗号学的検証が失敗するため、アップデーターは bundle の有効化を拒否し、新しい code が実行される前にセキュリティチームへイベントが届きます。
本番環境のルール: デバイスがアップデートの身元と正確なバイト列の両方を検証するまで、アップデートは敵対的なものとして扱うこと。
この保護が機能するためには、検証が抽出やアクティベーションの前、つまりデバイス上で実行されること、そして信頼されたキーがアップデート自体によって置き換えられないことが条件となります。これにより、証明書やキーの扱いは単なる管理上の詳細ではなく、アップデート設計の一部となります.Capacitorを使用するチームは、この境界を以下の項目とともにドキュメント化すべきです。 証明書管理プロセス、具体的には、誰が署名できるのか、キーはどこに保管されるのか、そしてクライアントがどのようにして承認された後継キーを認識するのか、といった内容です。
近年の研究では、署名検証は数十年にわたり、測定可能なエンジニアリング分野として扱われてきました。オフラインおよびオンライン署名検証に関する最初の研究論文は1977年に発表され、その後の研究ではHMMやFFTを含む手法へと拡張されました。広く引用されている比較データによると、専門家による誤受容率は約 0.5%、誤拒否率は7%であるのに対し、一般人は 誤受容率6.5%、誤拒否率26%に達しており、これらは 署名検証研究の歴史的概要にまとめられています。これらの数値はソフトウェアアップデートではなく手書き署名に関するものですが、検証の品質は検証器、参照データ、および決定ポリシーに依存するという重要な示唆を与えてくれます。
暗号署名がいかにして信頼を構築するか
リリース用のbundleを封印された封筒だと考えてください。ビルドシステムは正確なバイト列のダイジェストを計算し、 秘密鍵 を使用して、そのダイジェストに対してデジタル署名を作成します。アプリ側には、対応する 公開鍵が保持または安全に受信されており、これが封筒の紋章のような役割を果たします。攻撃者がbundleの一部でも改ざんした場合、アプリが計算するダイジェストが変わり、署名の検証に失敗します。

このフローは、大きく分けて4つの要素で構成されています:
- ハッシュ化 bundleを固定長のダイジェストに変換します。この整合性確認ステップでは、SHA-256やSHA-512が一般的に使用されます。
- 鍵生成 非対称鍵ペアを作成します。秘密鍵で署名を行い、公開鍵で検証を行います。
- 署名 ダイジェストをリリースメタデータ(理想的にはバージョン、channel、platform、対象ユーザーを含む)に紐付けます。
- 検証 ダウンロードしたバイト列からダイジェストを再計算し、署名が信頼できる秘密鍵によって生成されたものであるかを確認します。
検証者は、署名をアップデーターが適用するペイロードと紐付ける必要があります。マニフェストのハッシュとバンドルのハッシュが一致するかを確認せずにアプリがバンドルをダウンロードする場合、マニフェストに対する署名だけでは不十分です。同様に、ハッシュ自体が認証されていない限り、バンドルのハッシュを検証しても、誰がそれを承認したかは証明されません。
モバイル配信におけるアルゴリズムの選定
RSAは馴染み深く広くサポートされていますが、一般的に大きな鍵素材と慎重なパディングの選択が必要です。新しいモバイルアップデートプロトコルでは、Ed25519がよく選ばれます。鍵と署名がコンパクトで、モバイルプロセッサ上での検証パスが効率的だからです。互換性の要件でRSAが必要な場合は、RSA-PSSも適切です。選択にあたっては、他の環境のベンチマークを模倣するのではなく、プラットフォームの暗号ライブラリ、サポートされているハードウェア、相互運用性の要件、および移行計画に従うべきです。
役立つ独立した入門資料として、こちらのガイドがあります: ブロックチェーンのための暗号署名の理解トランザクションのコンテキストはOTA配信とは異なりますが、秘密鍵による承認と公開鍵による検証の説明はそのまま当てはまります。
信頼チェーンとピン留めされた鍵
証明書チェーンは、ルートから中間認証局を経てリーフ証明書へと信頼を委譲します。このモデルは広範なPKI運用を簡素化できますが、アプリのアップデートクライアントには、より限定的な要件が求められることがよくあります。つまり、「このアップデートを承認したパブリッシャーの鍵のみを信頼する」という要件です。公開鍵、あるいは少数の承認済み鍵をアプリのバイナリに直接埋め込む手法は、一種のピンニングです。これにより外部の認証局への依存を減らせますが、新しい鍵に置き換える際に、バイナリ側がその新しい鍵をあらかじめ信頼していなければならないという、鍵のローテーション問題が発生します。
署名されたエンベロープ(封筒)を完全な状態に保ってください。リリース用メタデータには、アーティファクト、そのダイジェスト、対象のチャンネル、および鍵の識別子を含める必要があります。Capacitor にこの仕組みを組み込むチームは、リリース前に鍵のスコープ、保存方法、および検証範囲を確認するために、特化した Capacitor アプリ向けのトークン署名チェックリストを 活用できます。
モバイルおよびWebシステムにおける実用例
署名検証はモバイル製品の複数のレイヤーに存在し、各レイヤーが異なる役割を担います。App Storeの署名は、インストール可能なパッケージが承認されたパブリッシャーからのものであるかをOSが判断するのに役立ちます。署名されたOTA bundleは、JavaScriptやアセットのペイロードが、アップデーターが信頼しているリリース権限者から送られてきたものかを確認します。JWT署名は、API が、トークンが期待通りのアイデンティティサービスによって発行されたものであるかを検証するのに役立ちます。
これらのレイヤーを混同すると、セキュリティホールが生じます。IPAの署名が有効であっても、その後のWeb bundleが認証されるとは限りません。また、JWTが有効でも、アップデートパッケージの安全性が証明されるわけではありません。TLS接続は通信経路を保護しますが、CDN、プロキシ、キャッシュ、またはビルドシステムが改ざんの起点となる場合、アーティファクトの署名に代わるものではありません。
| コンテキスト | 署名メカニズム | 防止される失敗モード | よくある脆弱性 |
|---|---|---|---|
| App Storeパッケージ | プラットフォームの code 署名とプラットフォーム審査による制御 | 再署名済み、または未承認のインストール可能パッケージ | ストアの署名がインストール後のWebアセットまでカバーしているという誤解 |
| Web bundle および Service Worker | 署名済みエクスチェンジ、または SRI スタイルの整合性リファレンス | 汚染されたCDNレスポンス、または改ざんされたアセット | エントリファイルのみがチェックされ、インポートされたアセットは検証されないままとなります |
| API トークン | JWT署名は、多くの場合JWKSエンドポイント経由で取得される信頼できる公開鍵を使用して検証されます | 偽造または改ざんされたトークン | サーバーは署名を検証しますが、発行者(issuer)、対象者(audience)、有効期限、またはトークンの用途を無視しています |
| Over-the-air アップデート | デバイス上で検証される、分離型または埋め込み型のbundle署名 | 中間者攻撃、または改ざんされたアップデートの注入 | クライアントが判定を適用する前に、コンテンツをダウンロード、キャッシュ、または展開してしまいます |
Webアセットの場合、Subresource Integrity(SRI)によってブラウザが参照リソースとして受け入れるものを制限できますが、動的インポート、service-workerのキャッシュ、または攻撃者が選択したファイルを指すアップデートマニフェストの問題を自動的に解決するわけではありません。実装側で完全なアーティファクトセットを定義し、実行されるバイト列を検証する必要があります。
JWTのデプロイでは、異なる種類の失敗が発生します。エンジニアは正しい公開鍵を公開していても、発行者や対象者が異なるトークンを受け入れてしまったり、トークンのヘッダーで指定されたアルゴリズムをそのまま信頼してしまったりすることがよくあります。暗号署名が有効であっても、認可の判断が誤っている可能性があるのです。
頻繁なリリースや顧客向けのモバイル体験に依存する製品では、運用コンテキストも重要です。評価を行うチームは 2026年に向けたリテールアプリのエンゲージメント戦略 迅速な実験(experimentation)を行うには、アップデートの整合性を前提条件として扱うべきです。リリースチャネル、アーティファクト、そして受信者がすべて同一の認可決定に紐付いている場合にのみ、高速なロールアウトは真価を発揮します。
完全な検証フローの構築
本番環境のアップデーターでは、検証をインストール直前に実行されるコールバックではなく、必須のゲート(関門)として実装すべきです。安全なシーケンスは、以下の決定論的な手順に従います。
- 認証済みトランスポート経由でマニフェストと署名を取得する。
- マニフェストの構造、バージョンポリシー、チャネル、有効期限、およびアーティファクトの識別情報を検証する。
- マニフェストで指定された正確なbundleをダウンロードする。
- ローカルでbundleのダイジェストを計算する。
- 信頼できるEd25519またはRSA-PSSの公開鍵を使用して署名を検証する。
- 検証済みのアーティファクトを隔離された場所に保存する。
- アトミックに適用し、ロールバックパスを保持する。

マニフェストは、判定に影響を与えるすべての値を紐付ける必要があります。最低限、bundleのdigest、version、channel、platform、およびkey identifierを含めるべきです。検証後に、ダウンローダーがURLやファイル名、channelを勝手に差し替えられないようにしてください。検証器は、不変のバイト列と不変のメタデータを受け取り、最終的に「承認」または「拒否」の単一の結果を返す必要があります。
簡略化したTypeScriptの型定義は以下の通りです:
type UpdateManifest = {
version: string
channel: string
platform: string
sha256: string
signature: string
keyId: string
}
async function verifyBundle(
bundle: Uint8Array,
manifest: UpdateManifest,
trustedKeys: Map<string, Uint8Array>
): Promise<boolean> {
const publicKey = trustedKeys.get(manifest.keyId)
if (!publicKey) return false
const digest = await sha256(bundle)
if (!constantTimeEqual(digest, hexToBytes(manifest.sha256))) {
return false
}
try {
return await ed25519Verify(
base64ToBytes(manifest.signature),
digest,
publicKey
)
} catch {
return false
}
}
この例は意図的に厳格に設計しています。不正なbase64、不明なkey identifier、digestの不一致、または署名の失敗は、すべて拒否(rejection)として扱う必要があります。ダウンロード中のタイムアウトを理由に、以前の不完全なファイルを適用してはいけません。不完全なアーティファクトは削除し、最後に確認された正常なバージョンを保持した上で、制限されたポリシーに基づいてリトライしてください。
レースコンディションとロールバックミスを防ぐ
一時的なパスにダウンロードを行います。ファイルをクローズしてフラッシュし、内容が完全であることを検証した後、バージョン管理された検証済みストアへとリネームします。アクティベーションのステップでは、その検証済みパスのみを参照するようにしてください。Capacitor や Electron 環境では、非同期のダウンロード完了イベントが、検証のPromiseとは無関係にアクティベーションをトリガーしないように注意してください。以下のような遷移は、単一のアップデート状態マシン(state machine)で管理すべきです。 downloading, verified, pending, active, rejected、および rolled_back.
機密性の高いバイト列の比較には、定数時間比較(constant-time comparison)が適しています。特に攻撃者が検証動作の繰り返しを観測できる場合に重要です。運用面でより重要なのは、過去の試行によってバージョンが「利用可能」とマークされたからといって、bundleを無条件に受け入れるようなショートカットを作らないことです。「利用可能(Availability)」と「真正性(Authenticity)」は、切り離して考えるべき別々の状態です。
この Capacitor updates の整合性チェック は、ハッシュ検証とアクティベーションのロジックを分離して実装するための有用なリファレンスとなります。ファイルの切り詰め、不正な署名、未知のキー、古いマニフェスト、バージョンの重複、アクティベーション中のプロセス終了など、失敗するパターンを意図的にテストしてください。
署名検証の自動化に関する研究では、他の領域においても閾値や参照データがいかに重要であるかが示されています。1994年のオンライン調査では、 22個の特徴量の中から最適なものを選択し、 10、 99.5% 、 86% 、 、、
多くのチームが見落としがちな鍵管理の課題
最初の署名鍵は簡単です。しかし、2つ目の鍵からアーキテクチャの真価が問われます。
鍵ペアを生成し、公開鍵をアプリに組み込み、最初のbundleに署名するだけであれば、チームは1日で完了できます。しかし数ヶ月後、エンジニアがノートPCを持ち出したまま退職したり、CIのシークレットがビルドログに露出したり、署名ジョブを別のランナーへ移行する必要が生じたりします。その時、「単に鍵をローテーションすればいい」という考えは、更新が確認できず、新しい鍵を認識する手段を持たないデバイスを切り捨てることを意味しかねません。

最初のリリース前に設計しておくべき3つの問題点:
- 行き止まりのないローテーション: 新しい鍵を必須にする前に、後継の鍵に対する信頼を確立しておきます。署名済みの鍵移行レコードを使用すれば、既存の信頼された鍵が次の鍵を承認でき、その間アプリは定義された移行期間中、古い鍵も引き続き受け入れることができます。
- 前提条件のない失効処理: アプリ内にピン留めされた鍵は、自動的にOCSPやCRLのような失効機能を提供するわけではありません。クライアント側には、署名済みのdenylist、許容される最小の鍵バージョン、あるいはネットワークが利用できない場合でも安全性を保てるサーバー制御のポリシーが必要です。
- 制限された署名アクセス権限: CI ランナーは、再利用可能な秘密鍵を平文の環境変数として受け取るのではなく、Vault や HSM に署名操作をリクエストすべきです。ログではコマンド出力をマスクし、信頼できないブランチからのプルリクエストが本番署名資格情報に到達できないようにする必要があります。
運用の実態: 鍵のローテーションはアップデートの問題です。アップデートの仕組みが信頼の変更を安全に配信できなければ、鍵が侵害されてもクリーンに復旧できません。
鍵の階層化は影響範囲(blast radius)を抑えます。ルート権限がリリース鍵を認可し、開発、ステージング、本番の各チャネルは別々の鍵で署名します。閾値署名(Threshold signing)を使えば、機密性の高い本番リリースに複数の承認者を必須にできるため、1つの資格情報が盗まれても有効なアップデートを作成できないようにできます。これらの制御はプロセスと遅延を増やすので、アップデートの影響度とチャネルの機密性に応じて適用すべきです。
モバイルアップデートでは、Trust on first use(初回使用時の信頼)は特に弱いです。最初の鍵がバンドルと同じチャネルで届くと、そのチャネルを制御する攻撃者が両方を置き換えられます。最初のトラストアンカーは、アプリのバイナリ、プラットフォームで保護された設定、または別の独立して認証された経路で届く必要があります。
Capacitor のチーム向けに、Capgo のドキュメントで示されているアプローチには、公開鍵ピンニングと鍵ローテーションのサポートが含まれています。その 安全な OTA アップデートのための鍵管理ガイダンスは、 鍵の生成を一度限りのセットアップとして扱うのではなく、そのライフサイクルを計画するための実用的なリファレンスを提供します。
CI/CDとモニタリングへの検証の統合
署名はリリース・トランザクションの一部であるべきです。パイプラインが先にbundleを公開し、後から別の手動ステップで署名を付与するような構成は避けてください。不変のアーティファクトをビルドし、そのダイジェストを計算して正確なバイト列に署名を行い、クリーンな検証ステップで署名を検証した上で、bundleとメタデータを一つのリリースユニットとして公開してください。
実用的なパイプラインでは、以下のアーティファクトを生成します:
- 不変のbundle: クライアントがダウンロードするファイルです。CDNが再パッケージ化するディレクトリではありません。
- マニフェスト: バージョン、channel、プラットフォーム、ダイジェスト、キー識別子、およびロールアウトポリシー。
- デタッチド署名: 正規化されたマニフェストデータ、または厳密に定義されたダイジェスト表現に対する署名。
- 検証結果: 署名がその環境で期待される公開鍵に対して有効であることを確認する、マシンリーダブルなチェック結果。
GitHub GitHub ActionsやGitLab CIは、構文こそ異なりますが、同様のパターンを強制できます。署名用のプライベートサービスが利用できない場合、署名が不正な場合、またはダウンロードしたテストコピーの検証に失敗した場合には、署名ジョブを失敗させるべきです。デプロイジョブは、単なるビルドの成功ではなく、その検証結果に依存させる必要があります。
パスに署名した後、後続のジョブでその内容が変更されるのを防いでください。署名後はアーティファクトをロックし、公開前にダイジェストを比較し、公開されるマニフェストがリリース記録から再現可能であることを確認してください。これにより、CIジョブが圧縮アーカイブに署名した一方で、配信レイヤーが再圧縮または再生成されたファイルを配信してしまうという、非常によくある統合時のギャップを検知できます。
オブザーバビリティはデバイス上で行うべきです
署名ジョブの成功は、パイプラインが有効な署名を作成したことを証明するに過ぎません。デバイスが意図したバイト列を受信したことや、アプリが意図したキーを使用したことまでは証明できません。検証結果は、アプリのバージョン、アップデートのバージョン、channel、platform、キー識別子、結果カテゴリ、およびプライバシーに配慮したリリース相関IDとともに記録してください。bundle、token、機密性の高いメタデータ、またはユーザーコンテンツのログ出力は避けてください。
有用なダッシュボードでは、以下を切り分けて表示します:
- 署名エラーとダウンロードエラー、
- ダイジェストの不一致とマニフェストの形式不正、
- 不明なキーとポリシー拒否、
- アプリのバージョン、リージョン、channel、リリース経過期間ごとの失敗率。
ダイジェストの不一致が急増した場合、キャッシュの破損、配信パスの改ざん、またはアーティファクトの公開エラーの可能性があります。不明なキーのイベントは、不完全なローテーションや不正なリリース試行を示しているかもしれません。検証テレメトリ単体で攻撃者を特定することはできませんが、インシデント対応者に調査のためのタイムラインと対象範囲を提供します。
この Capacitor OTAアップデートのためのCI/CDセキュリティガイド これにより、署名、検証、デプロイのゲートを一つのワークフローに組み込むことができます。重要な設計上の選択は「所有権」です。セキュリティチームが、検証が実行されたかどうかをエンジニアリングチームに確認する必要があってはいけません。リリース記録とクライアントのテレメトリが、その問いに直接答えるべきです。
セキュリティのベストプラクティスとよくある落とし穴
code を実行可能なすべてのアップデートパスにおいて、署名検証は必須要件であるべきです。アップデーターは、展開、インストール、またはアクティブ化の前に必ず検証を行わなければなりません。また、署名、ダイジェスト、キー、またはポリシーのチェックが利用できない場合は、必ず「フェイルクローズ(処理を停止)」する必要があります。
以下の項目を即時のレビューチェックリストとして活用してください:
- 秘密鍵の保護: 署名用資材は HSM またはマネージドな Vault で管理してください。ソース管理へのコミット、モバイルバイナリへの埋め込み、CI ログへの出力は絶対に避けてください。
- 信頼できる公開鍵のピン留め: 最初の信頼の起点(Trust Anchor)は、アップデートのペイロードの外に保存してください。複数の鍵をサポートする場合は、それぞれの用途と移行ルールを定義してください。
- リリース全体を紐付ける: 特定の bundle、channel、platform、および適用されるポリシーを特定する正規化されたメタデータに署名してください。
- すべての検証エラーを拒否する: タイムアウト、不正な署名、不明な鍵、またはマニフェストの欠落が発生した際、それを「検証されていない以前の結果で続行してよい」という合図として受け取ってはいけません。
- 侵害発生時の復旧テスト: ステージング環境で、キーのローテーション、失効したキーの処理、ロールバック、ダウンロードの中断、および古いデバイスへの対応を検証します。
- 検証ロジックの変更確認: 暗号化ライブラリ、正規化、パース、フォールバック動作、およびデバッグ設定については、セキュリティに精通した code レビューを必須とします。
- デバッグ動作の分離: 開発用のバイパス機能が、デフォルトのフラグや環境設定のミスによって、本番用ビルドに混入しないようにする必要があります。
また、署名だけでは、データの鮮度、受信者との紐付け、リプレイ攻撃への耐性、あるいはペイロードがプロバイダーの現在の状態と互換性を維持していることまでは証明できません。Webhookのセキュリティガイドラインではこの違いが明確に示されていますが、最近の脆弱性報告では、不正な形式やnullの署名、正規化の問題が、限定的な検証チェックを突破できてしまうことが示されています。周辺の認可ルールを設計する際は、Webhookのセキュリティギャップ分析を必ず参照してください。
閾値の選択については、生体認証システムからも教訓が得られます。ある研究では、 62個のパラメータ特徴量と に 102名から得られた1,232個の署名を用いて、 書き手によって異なる以下の閾値が報告されました。 誤拒否率 2.8% と 誤受容率 1.6%、一方で別の手法では 誤拒否率 2.68% と 誤受容率 1.99%、が 閾値選択の研究において報告されています。アプリケーションは異なりますが、動作原理は同じです。つまり、検証者の決定ポリシーは、暗号プリミティブと同じくらい重要です。
Capgo は、署名付きの Live Update bundle、デバイス上での検証、ロールアウト制御、ロールバック保護、そして配信のオブザーバビリティを単一のシステムで実現したい Capacitor や Electron チームにとって、有力な実装オプションとなります。詳細はこちら: Capgo そのアップデートワークフローが、鍵管理、CI/CD、およびモニタリングの要件を満たしているか評価するために。