mTLS(Mutual TLS)
概要
mTLS(Mutual TLS) は、TLS ハンドシェイクにおいてサーバーとクライアントの双方が証明書を提示し、互いの身元を認証する仕組みです。通常の TLS ではサーバーのみが証明書を提示しますが、mTLS ではクライアントも証明書を提示してサーバーに検証されます。
mTLS の仕様は TLS プロトコル自体に含まれています。TLS 1.2(RFC 5246)と TLS 1.3(RFC 8446)の両方で、サーバーがクライアントに証明書を要求する CertificateRequest メッセージが定義されています。mTLS は独立した RFC を持つ別プロトコルではなく、TLS のクライアント認証機能を使った認証パターンです。
mTLS はゼロトラストアーキテクチャの構成要素として採用が増えています。マイクロサービス間通信、API 認証、IoT デバイス認証、VPN の代替など、ネットワーク境界に依存しないセキュリティモデルで使われます。Google の BeyondCorp、Cloudflare Access、Istio のサービスメッシュなどが mTLS を基盤技術として採用しています。
仕組み
mTLS は TLS ハンドシェイクにクライアント証明書の提示と検証を追加し、プライベート CA から発行した証明書で接続元を認証します。
TLS ハンドシェイクでのクライアント認証
mTLS のハンドシェイク(TLS 1.3)は次の手順で進みます。
- クライアントが ClientHello を送信する(通常の TLS と同じ)
- サーバーが ServerHello、サーバー証明書、
CertificateRequestを送信する - クライアントがサーバー証明書を検証する(通常の TLS と同じ)
- クライアントが自身のクライアント証明書と
CertificateVerify(秘密鍵による署名)を送信する - サーバーがクライアント証明書を検証する。証明書チェーンの検証、有効期限の確認、CN や SAN の確認を行う
- 双方の検証が成功すればハンドシェイクが完了する
CertificateRequest メッセージには、サーバーが受け入れる CA のリスト(certificate_authorities 拡張)が含まれます。クライアントはこのリストに一致する CA から発行された証明書を選択して提示します。
通常の TLS との違い
通常の TLS では、クライアント(ブラウザー)はサーバーの証明書を検証しますが、サーバーはクライアントの身元を検証しません。ユーザー認証はアプリケーション層(パスワード、OAuth トークンなど)に委ねられます。
mTLS では TLS 層でクライアントの身元を確認するため、アプリケーション層の認証とは独立した認証レイヤーとして機能します。トークンの漏洩や総当たり攻撃のリスクがなく、証明書の秘密鍵を持つクライアントだけが接続できます。
クライアント証明書の発行
mTLS 用のクライアント証明書は、組織が運用するプライベート CA から発行するのが一般的です。パブリック CA(Let’s Encrypt など)はクライアント証明書の発行に対応していません。
プライベート CA の構成例は次のとおりです。
プライベートルート CA
└── クライアント証明書 A(サービス A 用)
└── クライアント証明書 B(サービス B 用)
└── クライアント証明書 C(IoT デバイス用)
AWS Private CA、HashiCorp Vault、cfssl、step-ca などのツールがプライベート CA の構築と証明書発行をサポートしています。
設定例
プライベート CA の構築からクライアント証明書の生成、Nginx での検証設定までの一連の手順を示します。
クライアント証明書の生成
cfssl を使ってクライアント証明書を生成する例です。
# CA の秘密鍵と証明書を生成
openssl req -x509 -newkey rsa:4096 -keyout ca.key -out ca.crt -days 365 -nodes -subj "/CN=My Private CA"
# クライアント証明書の CSR を生成
openssl req -newkey rsa:2048 -keyout client.key -out client.csr -nodes -subj "/CN=service-a"
# CA でクライアント証明書に署名
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 90
Nginx でクライアント証明書を検証
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/ssl/server/fullchain.pem;
ssl_certificate_key /etc/ssl/server/privkey.key;
# クライアント証明書の検証を有効化
ssl_client_certificate /etc/ssl/ca/ca.crt;
ssl_verify_client on;
# クライアント証明書の CN をバックエンドに転送
proxy_set_header X-Client-CN $ssl_client_s_dn_cn;
}
ssl_verify_client on を設定すると、クライアント証明書を持たない接続は拒否されます。ssl_verify_client optional にすると、クライアント証明書なしでも接続を許可し、アプリケーション層で追加の判定を行えます。
ssl_client_certificate にはクライアント証明書を発行した CA の証明書を指定します。この CA から発行された証明書を持つクライアントのみが認証を通過します。
curl でクライアント証明書を使った接続
curl --cert client.crt --key client.key --cacert ca.crt https://api.example.com/
--cert でクライアント証明書、--key で秘密鍵、--cacert でサーバー証明書の検証に使う CA 証明書を指定します。
確認方法
openssl でサーバーが mTLS を要求しているか確認するには次のコマンドを使います。
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>&1 | grep -A5 "Acceptable client certificate"
サーバーが CertificateRequest を送信していれば、受け入れる CA の DN が表示されます。
クライアント証明書を提示して接続するには次のコマンドを使います。
openssl s_client -connect api.example.com:443 -servername api.example.com -cert client.crt -key client.key </dev/null
Verify return code: 0 (ok)
Verify return code: 0 (ok) が表示されれば、サーバー証明書の検証とクライアント証明書の認証が成功しています。
外部の視点からも確認したい場合は、Labee Dev Toolbox の SSL Cert API を使うと、外部の視点から TLS ハンドシェイクの成否を確認できます。
curl "https://labee.dev/api/ssl-cert?hostname=api.example.com"
{
"success": true,
"data": {
"hostname": "api.example.com",
"port": 443,
"reachable": true,
"status": 200
},
"error": null,
"meta": { "responseTime": 123 }
}
mTLS が ssl_verify_client on で設定されている場合、クライアント証明書なしの接続は TLS ハンドシェイクの段階で拒否されるため、reachable: false が返ります。ssl_verify_client optional の場合は reachable: true になります。
よくある問題
mTLS の運用では、証明書のライフサイクル管理とロードバランサー構成が主な課題になります。
クライアント証明書の有効期限切れ
クライアント証明書にも有効期限があります。mTLS を使うサービスが増えると、証明書の数も増え、個別の有効期限管理が煩雑になります。HashiCorp Vault や step-ca は短命証明書(有効期間 24 時間〜数日)の自動発行をサポートしており、有効期限管理の負荷を軽減できます。
CA 証明書の更新時にクライアントが接続できなくなる
プライベート CA のルート証明書を更新した場合、サーバー側の ssl_client_certificate を新しい CA 証明書に変更すると、旧 CA で発行されたクライアント証明書が認証を通過しなくなります。移行期間中は新旧両方の CA 証明書をバンドルして ssl_client_certificate に設定します。
ブラウザーでのクライアント証明書選択ダイアログ
ブラウザーが mTLS 対応のサイトに接続すると、クライアント証明書の選択ダイアログが表示されます。ユーザーが正しい証明書を選択しなければ接続に失敗します。ブラウザー向けの mTLS よりも、API やサービス間通信での利用が一般的です。
ロードバランサーでの mTLS 終端
L7 ロードバランサーが TLS を終端する構成では、クライアント証明書の情報をバックエンドに転送する設定が必要です。Nginx では $ssl_client_s_dn 変数でクライアント証明書の Subject を取得し、ヘッダーとしてバックエンドに渡します。ロードバランサーが mTLS 終端をサポートしていない場合、L4(TCP)ロードバランサーを使ってバックエンドまで TLS を透過させます。