mTLS 雙向 TLS 認證實作

Mutual TLS Authentication Implementation

mTLS 概述與運作原理

mTLS(Mutual TLS)又稱為雙向 TLS 認證,是 TLS 協定的延伸應用。在標準的 TLS 連線中,只有伺服器需要提供憑證來證明身份;而在 mTLS 中,客戶端也必須提供憑證,讓雙方都能驗證對方的身份。

mTLS 握手流程

  1. 客戶端發起連線請求
  2. 伺服器回傳伺服器憑證
  3. 客戶端驗證伺服器憑證
  4. 伺服器要求客戶端憑證
  5. 客戶端回傳客戶端憑證
  6. 伺服器驗證客戶端憑證
  7. 雙方建立加密連線

單向 TLS vs 雙向 TLS

特性單向 TLS雙向 TLS (mTLS)
伺服器憑證必要必要
客戶端憑證不需要必要
身份驗證僅驗證伺服器雙向驗證
安全性中等
適用場景一般網站API、微服務、零信任架構

使用 OpenSSL 建立 CA

首先,我們需要建立自己的憑證授權中心(CA)來簽發伺服器和客戶端憑證。

建立 CA 私鑰

1
2
3
4
5
6
# 建立工作目錄
mkdir -p ~/mtls-demo/{ca,server,client}
cd ~/mtls-demo

# 產生 CA 私鑰(4096 位元 RSA)
openssl genrsa -out ca/ca.key 4096

建立 CA 憑證

1
2
3
# 產生 CA 自簽憑證(有效期 10 年)
openssl req -new -x509 -days 3650 -key ca/ca.key -out ca/ca.crt \
    -subj "/C=TW/ST=Taiwan/L=Taipei/O=MyOrg/OU=IT/CN=MyCA"

驗證 CA 憑證

1
2
# 檢視 CA 憑證資訊
openssl x509 -in ca/ca.crt -text -noout

產生伺服器憑證

建立伺服器私鑰與 CSR

1
2
3
4
5
6
# 產生伺服器私鑰
openssl genrsa -out server/server.key 2048

# 建立伺服器憑證簽署請求(CSR)
openssl req -new -key server/server.key -out server/server.csr \
    -subj "/C=TW/ST=Taiwan/L=Taipei/O=MyOrg/OU=Server/CN=localhost"

建立 SAN 擴展設定檔

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 建立 server.ext 設定檔,加入 SAN(Subject Alternative Name)
cat > server/server.ext << EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
DNS.2 = *.example.com
IP.1 = 127.0.0.1
EOF

使用 CA 簽發伺服器憑證

1
2
3
4
# 簽發伺服器憑證(有效期 1 年)
openssl x509 -req -in server/server.csr -CA ca/ca.crt -CAkey ca/ca.key \
    -CAcreateserial -out server/server.crt -days 365 \
    -extfile server/server.ext

產生客戶端憑證

建立客戶端私鑰與 CSR

1
2
3
4
5
6
# 產生客戶端私鑰
openssl genrsa -out client/client.key 2048

# 建立客戶端 CSR
openssl req -new -key client/client.key -out client/client.csr \
    -subj "/C=TW/ST=Taiwan/L=Taipei/O=MyOrg/OU=Client/CN=client01"

建立客戶端擴展設定檔

1
2
3
4
5
6
7
# 建立 client.ext 設定檔
cat > client/client.ext << EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
EOF

使用 CA 簽發客戶端憑證

1
2
3
4
# 簽發客戶端憑證
openssl x509 -req -in client/client.csr -CA ca/ca.crt -CAkey ca/ca.key \
    -CAcreateserial -out client/client.crt -days 365 \
    -extfile client/client.ext

Nginx mTLS 設定

基本 mTLS 設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
server {
    listen 443 ssl;
    server_name localhost;

    # 伺服器憑證
    ssl_certificate /path/to/server/server.crt;
    ssl_certificate_key /path/to/server/server.key;

    # CA 憑證(用於驗證客戶端)
    ssl_client_certificate /path/to/ca/ca.crt;

    # 啟用客戶端憑證驗證
    ssl_verify_client on;
    ssl_verify_depth 2;

    # TLS 協定與加密套件
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers on;

    location / {
        # 傳遞客戶端憑證資訊到後端
        proxy_set_header X-SSL-Client-CN $ssl_client_s_dn_cn;
        proxy_set_header X-SSL-Client-Verify $ssl_client_verify;

        return 200 "mTLS 驗證成功!客戶端 CN: $ssl_client_s_dn_cn\n";
        add_header Content-Type text/plain;
    }
}

選擇性驗證設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 若要讓客戶端憑證為選擇性(非強制)
ssl_verify_client optional;

# 在 location 中檢查驗證結果
location /protected {
    if ($ssl_client_verify != SUCCESS) {
        return 403 "需要有效的客戶端憑證\n";
    }
    # ...其他設定
}

curl 測試 mTLS 連線

基本測試

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 使用客戶端憑證連線
curl -v --cacert ca/ca.crt \
    --cert client/client.crt \
    --key client/client.key \
    https://localhost/

# 簡化輸出
curl -s --cacert ca/ca.crt \
    --cert client/client.crt \
    --key client/client.key \
    https://localhost/

測試無憑證連線(預期失敗)

1
2
3
# 不提供客戶端憑證(應該被拒絕)
curl -v --cacert ca/ca.crt https://localhost/
# 預期錯誤:SSL peer certificate or SSH remote key was not OK

使用 PKCS#12 格式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 將憑證和私鑰合併為 PKCS#12 格式
openssl pkcs12 -export -out client/client.p12 \
    -inkey client/client.key \
    -in client/client.crt \
    -certfile ca/ca.crt \
    -passout pass:mypassword

# 使用 PKCS#12 檔案測試
curl --cacert ca/ca.crt \
    --cert-type P12 \
    --cert client/client.p12:mypassword \
    https://localhost/

常見錯誤排解

憑證驗證失敗

錯誤訊息: SSL certificate problem: unable to get local issuer certificate

解決方案:

1
2
3
4
5
6
# 確認 CA 憑證路徑正確
curl --cacert /correct/path/to/ca.crt ...

# 或將 CA 加入系統信任
sudo cp ca/ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

憑證過期

錯誤訊息: SSL certificate problem: certificate has expired

解決方案:

1
2
3
4
5
6
# 檢查憑證有效期限
openssl x509 -in client/client.crt -noout -dates

# 重新簽發憑證
openssl x509 -req -in client/client.csr -CA ca/ca.crt -CAkey ca/ca.key \
    -CAcreateserial -out client/client.crt -days 365 -extfile client/client.ext

私鑰不匹配

錯誤訊息: SSL: error:0B080074:x509 certificate routines:X509_check_private_key:key values mismatch

解決方案:

1
2
3
4
# 驗證憑證與私鑰是否匹配
openssl x509 -noout -modulus -in client/client.crt | openssl md5
openssl rsa -noout -modulus -in client/client.key | openssl md5
# 兩個 MD5 值應該相同

CN 或 SAN 不匹配

錯誤訊息: SSL: certificate subject name does not match target host name

解決方案:

1
2
3
# 檢查憑證的 CN 和 SAN
openssl x509 -in server/server.crt -noout -text | grep -A1 "Subject:"
openssl x509 -in server/server.crt -noout -text | grep -A1 "Subject Alternative Name"

使用場景

微服務間通訊

在 Kubernetes 或微服務架構中,mTLS 可確保服務之間的通訊安全,防止未授權的服務存取。

API 安全

對於高安全性要求的 API,mTLS 提供比 API Key 或 JWT 更強的身份驗證。

零信任網路

在零信任架構中,mTLS 是實現「永不信任,始終驗證」原則的重要技術。

IoT 裝置認證

物聯網裝置可使用客戶端憑證來證明身份,確保只有授權裝置能連接到後端服務。

參考資料

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy