証明書ピンニングとは何か?
証明書ピンニング(Certificate Pinning)とは、特定のSSL/TLS証明書や公開鍵をアプリケーション側で固定し、信頼できるサーバーとだけ安全に通信を行うための技術です。これにより、中間者攻撃(MITM攻撃)を防ぎ、偽の証明書を使ったなりすましを防ぐことができます。
通常、TLS/SSL通信では、認証局(CA)によって発行された証明書をサーバーが提示し、クライアント(ブラウザやアプリ)がその証明書を信頼できるかどうかを確認します。しかし、万が一、悪意のある第三者が不正な証明書を取得してしまった場合、ユーザーは攻撃者のサーバーと通信してしまう可能性があります。
証明書ピンニングは、このリスクを低減するために、あらかじめ特定の証明書または公開鍵をアプリケーションに組み込んでおき、それ以外の証明書を拒否する仕組みです。
証明書ピンニングの仕組み
証明書ピンニングの基本的な仕組みは以下のようになっています。
- 公開鍵または証明書をアプリに埋め込む
- アプリ開発時に、特定のサーバー証明書の公開鍵または証明書自体をアプリのコードにハードコードしておく。
- サーバーとの通信時に証明書をチェック
- アプリがサーバーに接続すると、サーバーはTLSハンドシェイクの際に証明書を提示する。
- アプリは提示された証明書の公開鍵やハッシュ値を、事前に埋め込まれた情報と比較する。
- 一致すれば接続を継続、不一致なら通信を遮断
- 一致しない場合、アプリはそのサーバーとの通信を拒否することで、不正な中間者攻撃を防ぐ。
証明書ピンニングの実装方法(やり方)
証明書ピンニングを実装する方法はいくつかありますが、代表的な手法を紹介します。
Androidアプリでの証明書ピンニング
Androidでは、Network Security Config
を使用して証明書ピンニングを実装できます。
手順
1. res/xml/network_security_config.xml
を作成し、ピンニングする証明書情報を記述します。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">example.com</domain>
<pin-set expiration="2025-01-01">
<pin digest="SHA-256">base64_encoded_hash_of_public_key</pin>
</pin-set>
</domain-config>
</network-security-config>
2. AndroidManifest.xml
に networkSecurityConfig
を指定します。
<application
android:networkSecurityConfig="@xml/network_security_config">
</application>
これにより、指定された公開鍵のハッシュが一致しない証明書を持つサーバーへの通信がブロックされます。
iOSアプリでの証明書ピンニング
iOSでは、NSURLSession
の URLSessionDelegate
を使用してピンニングを実装できます。
手順
class PinningURLSessionDelegate: NSObject, URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if let serverTrust = challenge.protectionSpace.serverTrust {
let cert = SecTrustGetCertificateAtIndex(serverTrust, 0)
let certData = SecCertificateCopyData(cert!) as Data
let localCertData = NSDataAsset(name: "server_cert")!.data
if certData == localCertData {
completionHandler(.useCredential, URLCredential(trust: serverTrust))
return
}
}
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
ここでは、アプリに埋め込んだ証明書(server_cert
)と、サーバーから受信した証明書を比較し、一致しない場合は接続を拒否します。
Webサーバーでの公開鍵ピンニング(HPKP)
かつては、HPKP(HTTP Public Key Pinning)というHTTPヘッダーを使用したピンニングもありましたが、設定ミスのリスクが高いため、現在は推奨されていません。
Public-Key-Pins: pin-sha256="base64_encoded_hash"; max-age=5184000; includeSubDomains
現在では、代わりに 証明書透明性(Certificate Transparency, CT) や、HSTS(HTTP Strict Transport Security)を組み合わせることが一般的です。
証明書ピンニングの注意点(デメリット)
証明書ピンニングは強力なセキュリティ対策ですが、適用にはいくつかの注意点があります。
- 証明書の更新が困難
- ピンニングした証明書が期限切れになったり、更新されるとアプリの通信がすべて失敗する可能性があります。
- これを防ぐために、複数のバックアップ鍵をピンニングしておくのが望ましい。
- 緊急対応が難しい
- 万が一、証明書が危殆化(漏洩)した場合、アプリのアップデートなしに修正する手段がない。
- そのため、証明書の失効や緊急交換が必要な場合は、バックアップの公開鍵を用意する。
- 運用コストが増加
- ピンニングを導入することで、開発と運用のコストが増えるため、適用する範囲を慎重に決める必要がある。
まとめ
証明書ピンニングは、サーバー証明書を特定のものに固定し、中間者攻撃やなりすましを防ぐ強力な手法です。しかし、証明書の更新や管理が難しくなるため、慎重に運用する必要があります。
推奨する対策:
- 証明書透明性(CT)とHSTSを併用
- 複数のバックアップ鍵を登録
- 定期的な証明書更新の計画を立てる
- 証明書ピンニングの必要性を十分に検討