LASSIC Media らしくメディア
アプリの証明書ピンニング実装の進め方
LASSIC IT事業部|元請(プライムベンダー)としてシステム保守・運用を受託
この記事のポイント
- 証明書ピンニングは、アプリが通信先サーバーの証明書または公開鍵をあらかじめ指定した値に限定し、中間者攻撃(MITM)で不正なCAが発行した証明書を使われた場合の被害範囲を狭める通信路の対策です。
- AndroidはNetwork Security Configのpin-set、iOSはApp Transport SecurityのNSPinnedDomainsという異なる仕組みで実装するため、OS別に設計と検証の勘所が変わります。
- ピンには有効期限や証明書更新のタイミングがあり、ピンだけを更新してアプリ更新が端末に届いていないと通信が成立しなくなり得るため、バックアップピンの用意とローテーション運用の設計が要です。
目次
証明書ピンニングとは
証明書ピンニング(Certificate Pinning/SSL Pinning)とは、アプリがTLS通信を確立する際に、通信先サーバーの証明書または公開鍵をあらかじめアプリ内に指定した値に限定し、それ以外の証明書を提示するサーバーとの通信を拒否する仕組みです。OWASPは、この手法を「リモートエンドポイントを、信頼されたCAが署名した任意の証明書ではなく、特定のX.509証明書や公開鍵という身元に紐づける処理」と位置づけています*4。通常のTLS通信は、端末やOSが信頼するCA(認証局)のいずれかが署名した証明書であれば受け入れますが、ピンニングを実装すると、たとえ信頼されたCAが発行した証明書であっても、アプリが指定した値と一致しない限り通信が確立しません。これにより、不正に発行された証明書や、端末に追加された悪意あるルート証明書を使ったMITM(中間者攻撃)が成立する範囲を狭められます*5。
ここで扱うのは通信路(TLS層)における証明書の検証強化であり、アプリのバイナリ解析耐性を高める難読化・RASP(Runtime Application Self-Protection)や、脆弱性の有無を網羅的に確認するMASVSベースのセキュリティ診断とは論点が異なります。OWASP MASTGでも証明書ピンニングはMASVS-NETWORK(ネットワーク通信のセキュリティ要件)の一項目として扱われており、診断はピンニングが実装されているかどうかをMITMツールで検証するテスト手法として位置づけられています*5。本稿は診断や難読化には立ち入らず、通信路のピンニングという実装テーマに絞って解説します。
証明書ピンニングは、あくまで「不正な証明書での通信確立」を防ぐための対策であり、実装すれば通信のセキュリティ課題がすべて解消するわけではありません。アプリ自体の改ざんやリバースエンジニアリングへの耐性、認可・認証の設計不備といった論点は別の対策領域であり、ピンニングの実装可否とは切り分けて検討する必要があります。
AndroidのNetwork Security Configによるピンニング
Androidでは、Network Security Configuration(ネットワークセキュリティ構成)という宣言的なXML設定ファイルを使い、コードを変更せずにアプリの通信先ごとのセキュリティ設定を定義できます*3。証明書ピンニングもこの仕組みの一部として提供されており、res/xml/network_security_config.xmlに<domain-config>要素と<pin-set>要素を記述して指定します*3。
domain-configとpin-setの構造
公式ドキュメントに掲載されているサンプルは次のような構成です*3。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<pin-set expiration="2018-01-01">
<pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
<!-- backup pin -->
<pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin>
</pin-set>
</domain-config>
</network-security-config>
1つの<domain-config>には1つ以上の<domain>を含められ、includeSubdomains属性でサブドメインを対象に含めるかを制御します*3。<pin-set>は0または1つ含めることができ、内部に任意の数の<pin>要素を記述する構造です*3。
pin要素はSHA-256のSPKIダイジェストを指定する
<pin>要素のdigest属性は、ピンの生成に使うダイジェストアルゴリズムを指定するもので、現時点で公式にサポートされているのはSHA-256のみです*3。値の中身は、X.509証明書のSubjectPublicKeyInfo(SPKI)をBase64エンコードしたダイジェストです*3。証明書そのものではなく公開鍵情報のハッシュを指定する形式のため、算出にはOpenSSLなどのツールで証明書から公開鍵を抽出する手順が必要になります。
expiration属性とピン失効時の挙動
<pin-set>のexpiration属性には、ピンが失効する日付をyyyy-MM-dd形式で指定します*3。属性を設定しなければピンに期限は生じません*3。expirationを設定する目的は、アプリを更新していない端末で接続不能が長期化する事態を防ぐことにあります*3。一方で公式ドキュメントは、有効期限を設定するとピンニングによる保護が期限到達後に働かなくなり、攻撃者がピンを回避しやすくなる可能性がある点もあわせて明記しています*3。expirationは利便性とセキュリティのどちらを優先するかのトレードオフを伴う設定だと理解しておく必要があります。
公式ドキュメントは、証明書ピンニングを利用する場合はバックアップとなる鍵を含めるよう明記しています。鍵の切り替えやCA変更を強いられた場合に備えるためで、バックアップピンがなければアプリの更新を配信しない限り接続性が回復しないと注意喚起されています*3。
iOSのApp Transport Security/NSPinnedDomainsによるピンニング
iOSでは、App Transport Security(ATS)のIdentity Pinningという機能を使い、Info.plistへの宣言的な設定でピンニングを実装できます*1。Info.plistのNSAppTransportSecurityキー配下にNSPinnedDomainsを定義し、ドメインごとに期待する証明書を指定する構造です*1*2。Appleはこの機能について、ピンニングは必須の対策ではなく、慎重に検討したうえで本当に必要な場合にのみ導入すべきだと明記しています*1。
NSPinnedCAIdentitiesとNSPinnedLeafIdentities
NSPinnedDomains配下では、ドメインごとにNSPinnedCAIdentities(CA証明書・中間CA証明書のピン)またはNSPinnedLeafIdentities(サーバーのリーフ証明書のピン)のいずれか、または両方を指定します*1。公式サンプルは次のような構成です*1。
<key>NSAppTransportSecurity</key>
<dict>
<key>NSPinnedDomains</key>
<dict>
<key>example.org</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSPinnedCAIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=</string>
</dict>
</array>
</dict>
</dict>
</dict>
Androidと同様に、値は証明書のSPKI(Subject Public Key Info)をSHA-256でダイジェスト化しBase64エンコードした文字列(SPKI-SHA256-BASE64)で指定します*1。NSIncludesSubdomainsをtrueにするとサブドメインもピン対象に含められますが、1階層下まで(math.example.orgは対象になっても、その先のadvanced.math.example.orgのような多階層のサブドメインは対象に含まれない)という制約がある点は設計時に押さえておくべきポイントです*1。
CA証明書ピンとリーフ証明書ピンの推奨
Appleはリーフ証明書(サーバー証明書そのもの)をピンする場合、単一の値だけでなく複数の公開鍵をNSPinnedLeafIdentities配下に並べて冗長性を持たせる構成を例示しています*1。一方でCA証明書・中間CA証明書をピンする方式は、同じCAが署名した新しい公開鍵のサーバー証明書に切り替わっても通信を継続できるという運用上の利点があるとされています*1。どちらの方式を採るかは、後述する証明書ピンと公開鍵ピンの選び方とあわせて検討する必要があります。
証明書ピンと公開鍵ピンの違い・選び方
ピンニングの対象は大きく「証明書そのもの」をピンする方式と「証明書に含まれる公開鍵(SPKI)」をピンする方式に分かれます。AndroidのNetwork Security Config、iOSのNSPinnedDomainsはいずれも実装上は公開鍵のSHA-256ダイジェストを指定する形式であり、両OSともデファクトでは公開鍵ピンを採用する仕様になっています*1*3。
証明書ピンの特徴
OWASPのPinning Cheat Sheetは、証明書ピンについて「実装が相対的に単純になり得る」利点を挙げる一方で、「サイト側が証明書を定期的にローテーションする場合、ピンを設定したアプリ側も更新のたびに追随が必要になる」という制約を指摘しています*4。証明書自体には有効期限があり、更新のたびに内容(シリアル番号や有効期間)が変わるため、証明書ピンはサーバー証明書の更新サイクルに強く依存する方式だといえます。
公開鍵ピンの特徴
一方で公開鍵ピンは、証明書からSPKI(公開鍵情報)を抽出してハッシュ化するため、同じ鍵ペアを使い回して証明書だけを再発行するケースでは、ピンの値を変えずに済みます。OWASPは、証明書の有効期間が90日程度に短縮されてきている実情を踏まえ、公開鍵ピンを使えばピンセットの更新間隔を証明書の更新間隔よりも長く保てるとしています*4。ただし公開鍵ピンは、証明書からSPKIを抽出するという追加の算出ステップが必要になる点をトレードオフとして挙げています*4。
選び方の考え方
証明書更新のたびに鍵ペアも変える運用であれば証明書ピン、鍵ペアを維持したまま証明書のみ再発行する運用であれば公開鍵ピンが相性が良いという整理になります。いずれの方式でも、OWASPは単一のピンだけに頼らずバックアップとなるピンを含めることを推奨しており、リーフ証明書のピンにバックアップピンを加えることで、リスクは増えるものの「アプリが通信不能(ブリック状態)になる」事態を避けやすくなるとしています*4。中間CA証明書をバックアップとして持たせておけば、リーフ証明書側に問題が生じた場合のフェイルオーバー手段になり得るとも述べられています*4。
| 観点 | Android(Network Security Config) | iOS(ATS/NSPinnedDomains) |
|---|---|---|
| 設定方式 | res/xml配下のXMLでdomain-config/pin-setを宣言 | Info.plistのNSAppTransportSecurity配下にNSPinnedDomainsを宣言 |
| ピン値の形式 | SPKIのSHA-256ダイジェスト(Base64) | SPKIのSHA-256ダイジェスト(SPKI-SHA256-BASE64) |
| 対象の指定単位 | pin-set配下に複数pinを列挙 | NSPinnedCAIdentities(CA)またはNSPinnedLeafIdentities(リーフ)を配列で指定 |
| 有効期限の扱い | pin-setのexpiration属性で失効日を指定可能。未設定なら無期限 | ドメインごとの設定に有効期限に関する項目が用意されている |
| サブドメイン対応 | domainのincludeSubdomains属性で制御 | NSIncludesSubdomainsで制御。1階層下までが対象 |
| 公式の姿勢 | バックアップピンを含めるよう明記 | ピンは必須でなく慎重な検討のうえ導入すべきと明記 |
ピンのローテーション運用
証明書ピンニングを実装したあとに最も重い運用課題となるのは、ピンの更新(ローテーション)です。サーバー証明書は有効期限があるため定期的に更新され、鍵ペアを新しくする運用であれば、ピンの値もその都度更新が必要になります。ここで問題になるのが、ピンの更新とアプリの更新配信のタイミングのずれです。
バックアップピンの用意が前提
Android・iOSの公式ドキュメントは共通して、単一のピンだけに依存する構成を避けるべきだとしています。Androidの公式ドキュメントは、鍵の切り替えやCA変更を強いられた場合にアプリの接続性を維持できるよう、バックアップとなる鍵を含めるよう明記しており、含めていない場合はアプリの更新を配信しない限り接続性が回復しないと注意喚起しています*3。OWASPも同様に、バックアップピンによって「アプリが通信不能になる」事態を避けやすくなるとしています*4。
ピン失効時にアプリが通信不能になり得るリスク
実運用でとりわけ注意が必要なのは、サーバー側の証明書(または鍵ペア)を切り替えるタイミングで、アプリ側にその新しい値に対応するピンが1つも含まれていない場合です。この状態が発生すると、該当のアプリバージョンを使っている端末はサーバーとの通信が一切成立しなくなる可能性があります。アプリストアでの審査・配信には一定の日数がかかり、かつ利用者全員が即座にアプリを更新するとは限らないため、「ピンを更新したアプリ」が全端末に行き渡るまでの間は、旧バージョンの端末で接続断が起こり得るという前提を運用計画に織り込む必要があります。
OWASPは、証明書やその鍵ペアの切り替え計画を事前に把握できない場合はピンニングの導入自体を避けるべきだとし、頻繁なアプリの再展開が前提になるような運用であればピンニングに慎重になるべきだとも述べています*4。これは、ピンニングが「一度実装すれば終わり」の機能ではなく、サーバー証明書のライフサイクル管理とアプリのリリースサイクルを継続的に同期させる運用体制があって初めて狙い通りに機能する仕組みであることを意味します。
expiration設定はリスクとのトレードオフ
Androidのpin-setに用意されているexpiration属性のように、ピンに有効期限を設けて期限到達後は検証を止めるという設計も選択肢としてあります*3。これは未更新の端末で接続断が続く事態を避ける効果がある一方、期限到達後はピンニングによる保護が働かなくなるため、セキュリティ強度が低下するトレードオフを伴います*3。ピンニングは中間者攻撃を防ぐ効果を狙う一方で、運用の不備によって正規の通信までブロックしてしまうリスクを併せ持つ仕組みだと理解し、証明書の更新計画・アプリのリリース計画・バックアップピンの3つを連動させた運用設計が欠かせません。
外注時に確認すべき実装範囲と引き継ぎの論点
証明書ピンニングの実装をアプリ開発会社に外注する場合、実装そのものよりも、その後のローテーション運用まで含めた体制が用意されているかどうかが実質的な論点になります。
内製に必要なもの
証明書ピンニングを内製で運用するには、Android・iOSそれぞれのプラットフォーム仕様への理解に加え、社内のサーバー証明書の更新計画(証明書の発行元・更新周期・鍵ペアの切り替えタイミング)を継続的に把握し、アプリ側のピン設定に反映し続ける体制が必要です。証明書の更新を担当するインフラ側の部門と、アプリのピン設定を担当する開発側の部門が別チームである場合、両者の間で証明書更新の予定を事前に共有する仕組みがないと、ローテーションの失敗による通信断につながりかねません。
発注先への確認
発注先に証明書ピンニングの実装を依頼する際は、実装方式(証明書ピンか公開鍵ピンか)、バックアップピンを何本用意するか、Android側のexpiration設定の有無と考え方、iOS側のNSPinnedCAIdentitiesとNSPinnedLeafIdentitesのどちらを採用するかという設計方針を具体的に確認する必要があります。あわせて、証明書更新時にどのような手順でピンを更新し、どのタイミングでアプリのアップデート申請を行うかという運用フローの提案があるかどうかも、実装力を見極める材料になります。提案が実装のみにとどまり運用フローに触れていない場合は、その部分を追加で確認しておくべきです。
契約明記の論点
開発会社との契約終了後に別の会社や自社に運用を引き継ぐ可能性がある場合は、ピン設定ファイル(Android側のnetwork_security_config.xml、iOS側のInfo.plist該当部分)の内容、ピンの算出元となった証明書・鍵ペアの情報、証明書更新時の作業手順書を納品物に含めるよう契約段階で明記しておく必要があります。これらの資料がないまま引き継ぐと、後任担当者が既存のピン設定の根拠を1つずつ調査するところから作業を始めることになり、次回の証明書更新までに間に合わないおそれもあります。
まとめ:証明書ピンニングを機能させる3つの判断軸
本稿では、AndroidのNetwork Security ConfigとiOSのApp Transport Security/NSPinnedDomainsによる証明書ピンニングの実装、証明書ピンと公開鍵ピンの違い、ローテーション運用の論点を整理しました。要点を3つに集約すると次の通りです。第一に、証明書ピンニングは中間者攻撃を防ぐための通信路の対策であり、セキュリティ診断や難読化・RASPとは別の論点として切り分けて検討する必要があります。第二に、AndroidとiOSでは設定方式や有効期限の扱いが異なるため、OS別の仕様を踏まえた設計と検証が欠かせません。第三に、ピンにはバックアップの用意とローテーション運用が前提であり、ピンだけを更新してアプリ更新が端末に行き渡っていない状態が生じると通信が成立しなくなり得るという限界を理解したうえで、証明書更新計画とアプリのリリース計画を連動させる体制を整えることが、ピンニングを狙い通りに機能させる条件になります。
よくある質問
証明書ピンニングを実装すればセキュリティ診断は不要になりますか。
不要にはなりません*5。証明書ピンニングは中間者攻撃を防ぐための通信路の対策であり、MASVSベースのセキュリティ診断はアプリ全体の脆弱性を網羅的に確認するための取り組みです。両者は対象範囲が異なるため、ピンニングを実装したうえで別途診断を検討するという整理になります。
AndroidとiOSで実装方法はどれくらい違いますか。
設定の書き方は異なりますが、いずれも証明書のSPKI(公開鍵情報)をSHA-256でダイジェスト化した値を指定する点は共通しています*1*3。AndroidはXML形式のNetwork Security Configでdomain-configとpin-setを宣言し、iOSはInfo.plistのNSPinnedDomains配下にドメインごとの設定を記述します。有効期限の扱いやサブドメインの対象範囲など、細部の仕様はOSごとに確認が必要です。
証明書ピンと公開鍵ピンはどちらを選ぶべきですか。
証明書の更新方針によって変わります*4。証明書の更新のたびに鍵ペアも変える運用であれば証明書ピン、鍵ペアを維持したまま証明書のみ再発行する運用であれば公開鍵ピンとの相性が良いとされています。公開鍵ピンは証明書の更新間隔よりもピンセットの更新間隔を長く保てる利点がありますが、証明書からSPKIを抽出する算出ステップが追加で必要になります。
ピンを更新し忘れるとどうなりますか。
サーバー側の証明書や鍵ペアが切り替わったタイミングで、アプリ側に対応するピンが含まれていないと、該当のアプリバージョンを使っている端末との通信が成立しなくなる可能性があります*3*4。バックアップピンを用意しておくこと、証明書の更新計画とアプリのリリース計画を事前にすり合わせておくことが、このリスクを抑える基本的な対策です。
外注する場合、実装が終わればそれで完了ですか。
実装だけで完了とは考えない方がよいでしょう。証明書ピンニングはサーバー証明書のライフサイクルに合わせてピンを更新し続ける運用が前提の仕組みです。発注時には、証明書更新時のピン更新手順やアプリのリリース計画との連動方法まで含めて確認し、ピン設定ファイルや証明書情報などの引き継ぎ資料を契約に明記しておくことが望ましいでしょう。
著者:テレリモ総研編集部 鈴木 亮佑
ご不明な点はお問い合わせフォームからもご連絡いただけます。
- *1 出典:Apple「Identity Pinning: How to configure server certificates for your app」(Apple Developer)https://developer.apple.com/news/?id=g9ejcf8y
- *2 出典:Apple「NSPinnedDomains」(Apple Developer Documentation)https://developer.apple.com/documentation/bundleresources/information-property-list/nsapptransportsecurity/nspinneddomains
- *3 出典:Android「Network security configuration」(Android Developers)https://developer.android.com/privacy-and-security/security-config
- *4 出典:OWASP「Pinning Cheat Sheet」(OWASP Cheat Sheet Series)https://cheatsheetseries.owasp.org/cheatsheets/Pinning_Cheat_Sheet.html
- *5 出典:OWASP「MASTG-KNOW-0015: Certificate Pinning」(OWASP Mobile Application Security Testing Guide)https://mas.owasp.org/MASTG/knowledge/android/MASVS-NETWORK/MASTG-KNOW-0015/