---
title: mTLS と双方向認証
url: https://doc.liz6.com/ja/networking/05-tls-and-pki/04-mtls-and-mutual-authentication
locale: ja
area: networking
tags:
- networking
- tls-and-pki
date: 2026-06-30
modified: 2026-07-16
description: サーバーが「自分自身」を証明するには TLS 証明書を使用し、クライアントが「自分自身」を証明するには mTLS を使用します。双方が証明書を表示し、検証し合います。ゼロトラストアーキテクチャにおいて、mTLS はサービス間通信のアイデンティティの基盤ですが、証明書の配布とローテーションの運用コストが主な障壁となっています。
---

# mTLS と双方向認証

> サーバーが「自分自身」を証明するには TLS 証明書を使用し、クライアントが「自分自身」を証明するには mTLS を使用します。双方が証明書を表示し、検証し合います。ゼロトラストアーキテクチャにおいて、mTLS はサービス間通信のアイデンティティの基盤ですが、証明書の配布とローテーションの運用コストが主な障壁となっています。

## 概要

標準的な TLS はサーバーのアイデンティティのみを検証します。mTLS（mutual TLS）はクライアント証明書の検証を追加します。サーバーはハンドシェイク中に `CertificateRequest` を送信し、クライアントは証明書を提示する必要があります。これにより、IP やネットワーク位置に依存せずに双方向でアイデンティティを証明する「ゼロトラスト」ネットワークの基盤が実現します。SPIFFE はサービスアイデンティティを標準化し、Istio/Linkerd は mTLS を使用して service mesh 内の各 pod 間の通信を自動的に暗号化します。独自 CA の構築は mTLS の必須条件です。証明書の発行と失効を完全に制御する必要があります。

## TLS vs mTLS: ハンドシェイクの違い

<svg viewBox="0 0 720 400" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="TLS 与 mTLS 握手差异对比">
  <defs><marker id="mtls-arr1" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="400" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">TLS vs mTLS: 単方向の信頼から双方向の相互検証へ</text>

  <text x="44" y="70" font-size="12" font-weight="700" fill="#64748b">TLS</text>
  <rect x="80" y="48" width="150" height="36" rx="6" fill="#e2e8f0"/>
  <text x="155" y="70" text-anchor="middle" font-size="11" fill="#334155">サーバーが証明書を提供</text>
  <line x1="230" y1="66" x2="266" y2="66" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr1)"/>
  <rect x="270" y="48" width="160" height="36" rx="6" fill="#e2e8f0"/>
  <text x="350" y="70" text-anchor="middle" font-size="11" fill="#334155">クライアントがサーバーを検証</text>
  <line x1="430" y1="66" x2="466" y2="66" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr1)"/>
  <text x="474" y="70" font-size="12" font-weight="700" fill="#475569">→ 単方向の信頼</text>

  <text x="44" y="122" font-size="12" font-weight="700" fill="#3730a3">mTLS</text>
  <rect x="80" y="100" width="225" height="36" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="192" y="122" text-anchor="middle" font-size="10.5" fill="#3730a3">サーバー証明書 + CertificateRequest</text>
  <line x1="305" y1="118" x2="341" y2="118" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr1)"/>
  <rect x="345" y="100" width="175" height="36" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="432" y="122" text-anchor="middle" font-size="11" fill="#3730a3">クライアントも証明書を提供する必要あり</text>
  <line x1="520" y1="118" x2="556" y2="118" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr1)"/>
  <text x="564" y="122" font-size="12" font-weight="700" fill="#0f766e">→ 双方向の相互検証</text>

  <text x="360" y="170" text-anchor="middle" font-size="13" font-weight="700" fill="#1f2933">TLS 1.3 mTLS 簡略化ハンドシェイク</text>

  <rect x="60" y="182" width="600" height="32" rx="6" fill="#f1f5f9"/>
  <text x="360" y="203" text-anchor="middle" font-size="11.5" fill="#334155">Client → Server: ClientHello + key_share</text>
  <line x1="360" y1="214" x2="360" y2="228" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr1)"/>

  <rect x="60" y="230" width="600" height="36" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="248" text-anchor="middle" font-size="11" fill="#3730a3">Server → Client: ServerHello + key_share +</text>
  <text x="360" y="262" text-anchor="middle" font-size="11" fill="#3730a3">{EncryptedExtensions + CertificateRequest + ...}</text>
  <line x1="360" y1="268" x2="360" y2="282" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr1)"/>

  <rect x="60" y="284" width="600" height="36" rx="6" fill="#ccfbf1" stroke="#99f6e4"/>
  <text x="360" y="306" text-anchor="middle" font-size="11.5" font-weight="700" fill="#115e59">Client → Server: {Certificate + CertificateVerify + Finished}</text>

  <text x="360" y="332" text-anchor="middle" font-size="11" fill="#0f766e">↑ 標準的な TLS 1.3 に加え、Certificate + CertificateVerify（クライアント提供）が追加される</text>

  <rect x="40" y="338" width="640" height="46" rx="7" fill="#eef2ff"/>
  <text x="360" y="357" text-anchor="middle" font-size="11" fill="#3730a3">CertificateRequest に含まれる内容: certificate_authorities（空=任意の CA）</text>
  <text x="360" y="373" text-anchor="middle" font-size="11" fill="#3730a3">signature_algorithms · certificate_extensions（例: SPIFFE OID）</text>
</svg>

## プライベート CA の管理

mTLS をデプロイする最初のステップ: プライベート CA を構築する

```bash
# 1. ルート CA の作成（オフライン、ハードウェアセキュリティモジュールまたはエアギャップ環境のマシン）:
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ca.key
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
  -subj "/CN=Private CA" -extensions v3_ca

# 2. サーバー/クライアント証明書の発行:
openssl genpkey -algorithm EC -out server.key
openssl req -new -key server.key -out server.csr \
  -subj "/CN=metrics-agent-001"
openssl x509 -req -days 90 -in server.csr -CA ca.crt -CAkey ca.key \
  -out server.crt -set_serial 0x01

# 3. 失効（侵害された証明書がある場合）:
openssl ca -revoke server.crt -keyfile ca.key -cert ca.crt
```

## SPIFFE

mTLS の証明書 CN/SAN に SPIFFE ID を含めるための標準化されたサービスアイデンティティフレームワーク:

```
SPIFFE ID: spiffe://trust-domain/path
  trust-domain: 組織/クラスターの識別子
  path: サービス/インスタンスの識別子

例: spiffe://liz6.com/metrics-agent/li-cn-node-2

X.509 証明書内:
  SAN URI: spiffe://liz6.com/metrics-agent/li-cn-node-2
  → URI SAN によって検証可能（CN のみではない）

SVID (SPIFFE Verifiable Identity Document):
  = X.509 証明書（短期間: デフォルト 1 時間）+ 秘密鍵
  → SPIRE agent によって自動的にローテーションされる
```

## Service Mesh (Istio/Linkerd)

<svg viewBox="0 0 720 270" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Service Mesh 中 sidecar 自动完成 mTLS">
  <defs><marker id="mtls-arr2" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="270" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Service Mesh における mTLS: アプリケーションコードは意識しない</text>

  <rect x="220" y="50" width="280" height="34" rx="6" fill="#4f46e5"/>
  <text x="360" y="71" text-anchor="middle" font-size="10.5" font-weight="700" fill="#ffffff">control plane (istiod / linkerd-identity)</text>
  <line x1="290" y1="84" x2="210" y2="132" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr2)"/>
  <line x1="430" y1="84" x2="470" y2="132" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr2)"/>
  <text x="360" y="108" text-anchor="middle" font-size="10" fill="#64748b">短期証明書の発行 · 自動ローテーション</text>

  <rect x="55" y="138" width="80" height="40" rx="6" fill="#e2e8f0"/>
  <text x="95" y="162" text-anchor="middle" font-size="12" font-weight="700" fill="#334155">Pod A</text>
  <line x1="135" y1="158" x2="153" y2="158" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr2)"/>

  <rect x="155" y="134" width="150" height="48" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="230" y="154" text-anchor="middle" font-size="11" font-weight="700" fill="#3730a3">sidecar proxy</text>
  <text x="230" y="169" text-anchor="middle" font-size="10" fill="#4f46e5">Envoy / linkerd-proxy</text>
  <line x1="305" y1="158" x2="323" y2="158" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr2)"/>

  <rect x="325" y="143" width="70" height="30" rx="15" fill="#0d9488"/>
  <text x="360" y="163" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">mTLS</text>
  <line x1="395" y1="158" x2="413" y2="158" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr2)"/>

  <rect x="415" y="134" width="150" height="48" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="490" y="154" text-anchor="middle" font-size="11" font-weight="700" fill="#3730a3">sidecar proxy</text>
  <text x="490" y="169" text-anchor="middle" font-size="10" fill="#4f46e5">Envoy / linkerd-proxy</text>
  <line x1="565" y1="158" x2="583" y2="158" stroke="#475569" stroke-width="1.6" marker-end="url(#mtls-arr2)"/>

  <rect x="585" y="138" width="80" height="40" rx="6" fill="#e2e8f0"/>
  <text x="625" y="162" text-anchor="middle" font-size="12" font-weight="700" fill="#334155">Pod B</text>

  <rect x="40" y="200" width="640" height="50" rx="8" fill="#f0fdfa"/>
  <text x="360" y="220" text-anchor="middle" font-size="12" font-weight="700" fill="#115e59">アプリケーションコードは mTLS の存在を知る必要がない</text>
  <text x="360" y="238" text-anchor="middle" font-size="11" fill="#0f766e">各 sidecar は control plane から短期証明書を取得し、自動ローテーション、自動 mTLS を実行する</text>
</svg>

## 比較

| | mTLS | API Key | JWT (OAuth2) |
|---|---|---|---|
| トランスポート層 | L4 (TLS ハンドシェイク段階) | L7 (HTTP ヘッダー) | L7 (HTTP ヘッダー) |
| アイデンティティの紐付け | 証明書 (CN/SAN + 有効期限) | key 文字列 | claims (sub, exp, aud) |
| ローテーション | CA による再発行 | 新規 key の生成 | 短命 TTL + refresh |
| 失効 | CRL / OCSP | 失効 DB による失効 | 短命 TTL（自然失効） |
| ミドルウェアのオーバーヘッド | 低い (TLS そのもの) | 中程度 (key の検証) | 中程度 (JWT の検証) |
| ゼロトラスト互換性 | ✓ (SPIFFE) | ✗ | △ (トークン署名に PKI が必要) |

## 参考

- **RFC**: 8446, 8705
- **SPIFFE**: spiffe.io, github.com/spiffe/spire
- **Istio**: istio.io/latest/docs/concepts/security/#mutual-tls-authentication

*Keywords: mTLS, client certificate, SPIFFE, SPIRE, service mesh, private CA, certificate revocation, SVID*
