[HTTPS][mTLS] Two-way SSL / Mutual TLS – SSL 雙向驗證

Intro

Two-way SSL (Mutual TLS) 即 Server 端也要求 Client 端提供 certificate 做驗證


圖片來源:web-service-principles - HTTP


Client 端憑證

Client 端需配置憑證及對應 Private Key,用於在 CertificateRequest 階段提供終端憑證並生成簽章;
Server 端需配置信任的 CA 列表(包括 intermediate 與 root CA),用於驗證 Client 提供的憑證鏈。

[Server] TLS/SSL憑證(Certificate)常用指令 – 製作CSR


Server 端範例

Server 端需指定信任的 client CA 憑證,若需要支援多個 client 或完整憑證鏈,可將多張 CA 憑證合併至同一個 PEM 檔案。
(需包含信任的 root CA,否則 client 即使帶完整憑證鏈也會驗證失敗)

Nginx 設定

Nginx 可以在 Server block 上啟用驗證 Client 端 certificate:

server {
   listen 443 ssl;

   ssl_certificate /etc/nginx/ssl/public.crt;
   ssl_certificate_key /etc/nginx/ssl/private.rsa;

   # Client certificate verification
   ssl_client_certificate /etc/nginx/ssl/client_ca_fullchain.pem;
   ssl_verify_client on;

   server_name api.my_domain.com;

   location / {
      # Additional configuration here
   }
}

Nginx 在 mTLS 驗證上,不會直接於 TLS handshake 階段拒絕,而是預設回覆 HTTP code 400,描述如:no required SSL certificate was sent)

Apache 設定

<VirtualHost *:443>
    ServerName example.com

    # Enable SSL
    SSLEngine on

    # SSL server settings (for server authentication)
    SSLCertificateFile /path/to/server.crt
    SSLCertificateKeyFile /path/to/server.key

    # Two-way SSL settings (for client authentication)
    SSLCACertificateFile /path/to/combined_ca.crt
    SSLVerifyClient require
    SSLVerifyDepth 2
</VirtualHost>

Client 端範例

CURL

$ curl https://two-way-ssl.local --cert client.crt --key client.key [--pass passPhrase]

# Key也可以bundle至CRT
$ curl https://two-way-ssl.local --cert crt-with-key.crt

# PKCS#12 方法
$ curl https://two-way-ssl.local --cert-type P12 --cert cert.p12:password

PHP Guzzle

$response = $client->get('/', [
        'cert' => '/path/client.crt',
        'ssl_key' => '/path/client.key',     
    ]
);

Pass Phrase情況:

    'cert' => ['/path/client.crt', 'password'],
    'ssl_key' => ['/path/client.key', 'password'],

JAVA (KeyStore)

JAVA 一般使用包含憑證與私鑰的 PKCS#12 格式或者轉成 JKS 載入 java.security.KeyStore

import java.security.KeyStore;
// ...
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(getClass().getResourceAsStream("/path/to/keystore.p12"), "p12_password".toCharArray());

// Build SSL configuration by keyStore

X.509 延伸密鑰用途 (Extended Key Usage, EKU) - 影響 mTLS 驗證

延伸密鑰用途 (Extended Key Usage, 簡稱 EKU) 是 X.509 v3 憑證規範中的一種擴充欄位(Extension),定義於 RFC 5280 Section 4.2.1.12。

EKU 的主要目的在於限定憑證公鑰的可使用場景。即便憑證的基本金鑰用途(Key Usage, 如數位簽章、加密)合規,驗證端(如 Web Server、瀏覽器、API Client)仍會透過 EKU 來確認該憑證是否被授權用於「特定的應用情境」(例如:網頁伺服器身分識別、用戶端身份驗證、程式碼簽署等)。

常見的 EKU 欄位與 OID 對照表

EKU 在憑證內部是以 物件識別碼 (Object Identifier, OID) 的形式儲存:

EKU 名稱 簡稱 / 標識符 OID 作用與用途
TLS Web Server Authentication serverAuth 1.3.6.1.5.5.7.3.1 伺服器驗證:證明自己是合格的 HTTPS 網站/TLS 伺服器。
TLS Web Client Authentication clientAuth 1.3.6.1.5.5.7.3.2 客戶端驗證:用於 mTLS 中,證明 Client (使用者/設備/API) 身分。
Code Signing codeSigning 1.3.6.1.5.5.7.3.3 程式碼簽署:證明軟體或執行檔發行者身分。
Email Protection emailProtection 1.3.6.1.5.5.7.3.4 S/MIME:電子郵件加密與數位簽章。
Time Stamping timeStamping 1.3.6.1.5.5.7.3.8 時間戳記:用於憑證或簽章的時間戳服務 (TSA)。
OCSP Signing ocspSigning 1.3.6.1.5.5.7.3.9 OCSP 簽署:授權用來簽署 OCSP 狀態回應。
Any Extended Key Usage anyExtendedKeyUsage 2.5.29.37.0 任意用途:代表可用於任何延伸密鑰用途。

EKU 資訊在 openssql x509 -noout -text 範例

X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication

當 EKU 為「空值(缺失)」時的行為機制

RFC 5280 規範說明:

"If the extension is present, then the certificate MUST only be used for one of the purposes indicated... If the extension is not present, the certificate may be used for any purpose."

  • 未定義 EKU 欄位(Null / Absent):
    • 理論與預設行為: 視為「不強求/無特別限制」。該憑證技術上可用於 serverAuth、clientAuth 或其他用途。
    • 常見場景: 使用預設指令 openssl req -x509 ... 產生的簡易自簽憑證(Self-Signed Cert),通常不會自動插入 EKU 擴充,因此能同時被 Server 與 Client 驗證接受。
  • 有定義 EKU 欄位(Present):
    • 嚴格限制模式: 憑證只能用於 EKU 欄位中明確列出的用途。
    • 例: 若憑證只宣告了 serverAuth,將其拿去作為 mTLS 的 Client 憑證,驗證端將會拋出錯誤並拒絕連線。

雖然 RFC 允許無 EKU 的憑證萬用,但現代瀏覽器(如 Chrome/Edge)、Go crypto/x509 及部分 Enterprise PKI 框架,在開啟 Strict 模式時,會逐步要求憑證必須顯式包含(Explicit) 對應的 EKU,否則視為不合規。

Web Server 與 TLS 程式庫的驗證流程

當 TLS 握手(Handshake)進行時,底層 TLS 實作(如 OpenSSL、Go crypto/tls)會依據角色執行 EKU 檢查:

[ TLS 握手階段 ]
  1. 驗證憑證鏈 (Certificate Chain) 與簽章是否合法
  2. 檢查憑證有效期限 (Not Before / Not After)
  3. 檢查吊銷狀態 (CRL / OCSP)
  4. 檢查 EKU (Purpose Verification)
     * [ 情況 A ] 憑證無 EKU 欄位 ──> 預設放行 (Pass)
     * [ 情況 B ] 包含目標 EKU (如 clientAuth) ──> 驗證成功 (Pass)
     * [ 情況 C ] 存在 EKU 但缺少目標 EKU ──> 拒絕連線 (Fail)

底層程式庫的具體表現:

OpenSSL (Nginx / Apache / Envoy / HAProxy 底層):

  • 在開啟 Client 驗證 (ssl_verify_client on) 時,OpenSSL 會將 Purpose 設為 X509_PURPOSE_SSL_CLIENT。
  • 若 Client 憑證有 EKU 但缺乏 clientAuth,握手會立即中斷,回傳錯誤代碼:X509_V_ERR_INVALID_PURPOSE (18) / unsupported certificate purpose

References

One thought on “[HTTPS][mTLS] Two-way SSL / Mutual TLS – SSL 雙向驗證

Leave a Reply

Your email address will not be published. Required fields are marked *