Luke a Pro

Luke Sun

Developer & Marketer

🇺🇦
EN||

憑證和 PKI:在網際網路上建立信任

| , 9 minutes reading.

1. 為什麼要關心這個問題?

你造訪 https://bank.example.com。你的瀏覽器顯示了一個鎖標誌。它怎麼知道這真的是你的銀行而不是攻擊者?

答案是憑證——將公鑰綁定到身分的數位文件。沒有它們,HTTPS 將毫無意義。

理解 PKI 能幫助你:

  • 偵錯 TLS 憑證錯誤
  • 為你的應用程式設定 HTTPS
  • 理解為什麼「直接點擊忽略警告」是危險的
  • 知道何時以及如何使用用戶端憑證

2. 定義

數位憑證(具體來說是 X.509)綁定了:

  • 公鑰
  • 身分(網域名稱、組織等)
  • 來自可信機構的簽章

公鑰基礎建設 (PKI) 是一個系統,包括:

  • 核發憑證的憑證授權機構 (CA)
  • 核發和撤銷憑證的政策
  • 包含可信根憑證的信任庫
憑證 = 「我,可信 CA,證明
       公鑰 XYZ 屬於 bank.example.com」
       + CA 的簽章

3. 憑證內部有什麼

X.509 憑證結構

Certificate:
    Version: 3
    Serial Number: 04:00:00:00:00:01:2F:4E:E1:5B:3D
    Signature Algorithm: sha256WithRSAEncryption
    Issuer: CN=DigiCert Global Root CA
    Validity:
        Not Before: Mar 08 12:00:00 2023 GMT
        Not After:  Mar 08 12:00:00 2024 GMT
    Subject: CN=www.example.com
    Subject Public Key Info:
        Public Key Algorithm: id-ecPublicKey
        EC Public Key:
            pub: 04:AB:CD:...
            ASN1 OID: prime256v1
    X509v3 Extensions:
        Subject Alternative Name:
            DNS:www.example.com
            DNS:example.com
        Basic Constraints:
            CA:FALSE
        Key Usage:
            Digital Signature, Key Encipherment
        Extended Key Usage:
            TLS Web Server Authentication

關鍵欄位解釋

Subject:憑證是給誰的
  CN=www.example.com(通用名稱)
  O=Example Inc(組織)

Issuer:誰簽署了憑證
  為此身分擔保的 CA

Validity:憑證何時有效
  Not Before / Not After 日期

Subject Alternative Name (SAN):
  涵蓋的其他網域名稱
  現代瀏覽器要求這個

Key Usage:
  金鑰可以用於什麼
  數位簽章、金鑰加密等

Extended Key Usage:
  更具體的用途
  TLS Web 伺服器認證
  程式碼簽章等

4. 信任鏈

信任如何運作

根 CA(自簽章,在瀏覽器信任庫中)

    │ 簽署

中繼 CA 憑證

    │ 簽署

終端實體憑證(你的網站)

瀏覽器驗證:
1. 終端憑證由中繼 CA 簽署
2. 中繼 CA 由根 CA 簽署
3. 根 CA 在信任庫中
4. 所有憑證有效(未過期、未撤銷)

為什麼需要中繼 CA?

安全性:根金鑰極其珍貴
  - 儲存在離線 HSM 中
  - 很少使用(只用於簽署中繼 CA)
  - 如果洩露,整個 PKI 崩潰

靈活性:
  - 如果中繼 CA 洩露可以撤銷
  - 不同中繼 CA 用於不同目的
  - 更短的有效期

縱深防禦:
  - 多一層需要攻破
  - 可以輪換中繼 CA 而不改變根 CA

5. 憑證授權機構 (CA)

CA 做什麼

1. 驗證憑證申請者的身分
   - 網域驗證 (DV):證明你控制網域
   - 組織驗證 (OV):驗證組織存在
   - 延伸驗證 (EV):廣泛的法律驗證

2. 核發憑證
   - 用 CA 的私鑰簽章
   - 包含適當的限制

3. 維護撤銷資訊
   - CRL(憑證撤銷清單)
   - OCSP(線上憑證狀態協定)

4. 保護他們的金鑰
   - 根金鑰在 HSM 中
   - 嚴格的存取控制
   - 定期稽核

主要 CA

商業:
- DigiCert
- Sectigo(前身 Comodo)
- GlobalSign
- Entrust

免費:
- Let's Encrypt(自動化 DV 憑證)
- ZeroSSL

私有:
- 你組織的內部 CA
- 用於內部服務、用戶端憑證

6. 取得憑證

使用 Let’s Encrypt(免費、自動化)

# 安裝 certbot
sudo apt install certbot

# 取得憑證(獨立模式)
sudo certbot certonly --standalone -d example.com -d www.example.com

# 憑證檔案:
# /etc/letsencrypt/live/example.com/fullchain.pem  (憑證 + 中繼憑證)
# /etc/letsencrypt/live/example.com/privkey.pem   (私鑰)

# 自動續期(每天執行兩次)
sudo certbot renew

使用 ACME 協定(程式化方式)

# 使用 acme 函式庫的概念範例
from acme import client, messages
from cryptography.hazmat.primitives.asymmetric import ec

# 產生帳戶金鑰
account_key = ec.generate_private_key(ec.SECP256R1())

# 建立 ACME 用戶端
acme_client = client.ClientV2(
    directory='https://acme-v02.api.letsencrypt.org/directory',
    key=account_key
)

# 請求網域憑證
order = acme_client.new_order(['example.com'])

# 完成驗證(證明網域所有權)
for auth in order.authorizations:
    challenge = get_http_challenge(auth)
    # 部署驗證回應
    # ...
    acme_client.answer_challenge(challenge)

# 完成並取得憑證
certificate = acme_client.finalize_order(order, csr)

7. 憑證驗證

瀏覽器檢查什麼

1. 簽章鏈有效
   └─ 每個憑證由其核發者簽章

2. 根 CA 可信
   └─ 在瀏覽器/作業系統信任庫中

3. 憑證未過期
   └─ 目前時間在有效期內

4. 憑證未被撤銷
   └─ 檢查 CRL 或 OCSP

5. 網域匹配
   └─ Subject 或 SAN 包含該網域

6. 金鑰用途適當
   └─ TLS Web 伺服器認證

7. 憑證政策滿足
   └─ 各種 X.509 限制

驗證程式碼範例

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.x509.oid import ExtensionOID
import ssl
import socket
import datetime

def validate_server_certificate(hostname, port=443):
    """驗證伺服器的憑證"""
    # 取得憑證
    context = ssl.create_default_context()
    with socket.create_connection((hostname, port)) as sock:
        with context.wrap_socket(sock, server_hostname=hostname) as ssock:
            der_cert = ssock.getpeercert(binary_form=True)

    cert = x509.load_der_x509_certificate(der_cert, default_backend())

    # 檢查有效期
    now = datetime.datetime.utcnow()
    if now < cert.not_valid_before or now > cert.not_valid_after:
        return False, "憑證已過期或尚未生效"

    # 檢查主體替代名稱
    try:
        san = cert.extensions.get_extension_for_oid(
            ExtensionOID.SUBJECT_ALTERNATIVE_NAME
        )
        names = san.value.get_values_for_type(x509.DNSName)
        if hostname not in names:
            return False, f"主機名稱 {hostname} 不在 SAN 中"
    except x509.ExtensionNotFound:
        return False, "沒有 SAN 延伸"

    return True, "憑證有效"

# 檢查
valid, message = validate_server_certificate("www.google.com")
print(f"有效: {valid}, 訊息: {message}")

8. 憑證撤銷

為什麼撤銷很重要

需要撤銷的情境:
- 私鑰洩露
- 憑證核發錯誤
- 網域所有權變更
- 組織不再可信

沒有撤銷檢查:
- 持有被盜金鑰的攻擊者可以冒充網站
- 直到憑證自然過期(最長 398 天!)

CRL vs OCSP

CRL(憑證撤銷清單):
┌────────────────────────────────┐
│ 已撤銷序號的大清單              │
│ 定期下載                        │
│ 可能有幾兆位元組                │
│ 更新之間有延遲                  │
└────────────────────────────────┘

OCSP(線上憑證狀態協定):
┌────────────────────────────────┐
│ 查詢:「這個憑證被撤銷了嗎?」   │
│ 回應:「是/否」                 │
│ 即時                            │
│ 隱私問題(CA 看到查詢)          │
└────────────────────────────────┘

OCSP Stapling:
┌────────────────────────────────┐
│ 伺服器取得 OCSP 回應            │
│ 附加到 TLS 握手                 │
│ 用戶端取得新鮮證明              │
│ 沒有隱私洩露給 CA               │
└────────────────────────────────┘

9. 常見憑證問題

憑證錯誤和解決方案

錯誤:NET::ERR_CERT_DATE_INVALID
原因:憑證已過期或尚未生效
修復:續期憑證,檢查伺服器時鐘

錯誤:NET::ERR_CERT_AUTHORITY_INVALID
原因:CA 不可信
修復:使用知名 CA,或安裝根憑證

錯誤:NET::ERR_CERT_COMMON_NAME_INVALID
原因:網域不在憑證中
修復:取得包含正確 SAN 的憑證

錯誤:SSL_ERROR_BAD_CERT_DOMAIN
原因:主機名稱不匹配
修復:使用正確的主機名稱存取

錯誤:憑證鏈不完整
原因:缺少中繼憑證
修復:設定伺服器使用完整鏈

偵錯憑證

# 檢視憑證詳情
openssl s_client -connect example.com:443 -showcerts

# 檢查憑證日期
openssl s_client -connect example.com:443 2>/dev/null | \
  openssl x509 -noout -dates

# 檢查憑證鏈
openssl s_client -connect example.com:443 -showcerts 2>/dev/null | \
  openssl x509 -noout -issuer -subject

# 驗證憑證
openssl verify -CAfile ca-bundle.crt certificate.pem

10. 用戶端憑證

用途

伺服器憑證:伺服器向用戶端證明身分
用戶端憑證:用戶端向伺服器證明身分

用例:
- 服務間認證的雙向 TLS (mTLS)
- 無需密碼的使用者認證
- 物聯網裝置認證
- VPN 認證

設定 mTLS

import ssl
import socket

# 伺服器端
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
server_context.load_cert_chain('server.crt', 'server.key')
server_context.load_verify_locations('client-ca.crt')
server_context.verify_mode = ssl.CERT_REQUIRED  # 要求用戶端憑證

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.bind(('0.0.0.0', 8443))
    sock.listen()
    with server_context.wrap_socket(sock, server_side=True) as ssock:
        conn, addr = ssock.accept()
        # conn.getpeercert() 回傳用戶端憑證

# 用戶端
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
client_context.load_cert_chain('client.crt', 'client.key')
client_context.load_verify_locations('server-ca.crt')

with socket.create_connection(('server.example.com', 8443)) as sock:
    with client_context.wrap_socket(sock, server_hostname='server.example.com') as ssock:
        # 使用雙向認證連線
        pass

11. 自簽憑證

何時使用

適合:
- 開發和測試
- 內部服務(使用私有 CA)
- 學習和實驗

不適合:
- 公開網站
- 被外部用戶端使用的正式環境 API
- 任何信任重要的地方

建立自簽憑證

# 產生私鑰
openssl genrsa -out server.key 2048

# 產生自簽憑證
openssl req -new -x509 -key server.key -out server.crt -days 365 \
  -subj "/CN=localhost"

# 帶 SAN 延伸(現代瀏覽器要求)
openssl req -new -x509 -key server.key -out server.crt -days 365 \
  -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

12. 憑證透明度

它解決什麼問題

如果 CA 為你的網域核發了憑證會怎樣?
- 你可能永遠不知道
- 攻擊者可以攔截流量
- CA 洩露是災難性的

憑證透明度:
- 所有憑證記錄到公開日誌
- 你可以監控你的網域的日誌
- 錯誤核發的憑證可被偵測

如何運作

1. CA 核發憑證
2. CA 提交到 CT 日誌
3. 日誌回傳簽署憑證時間戳記 (SCT)
4. 憑證包含 SCT
5. 瀏覽器驗證 SCT 存在
6. 網域擁有者監控日誌

工具:
- crt.sh(搜尋 CT 日誌)
- Google 憑證透明度
- Cert Spotter(監控)

13. 本章小結

三點要記住:

  1. 憑證將金鑰綁定到身分。 憑證是 CA 的簽章聲明,表明公鑰屬於特定網域或實體。

  2. 信任來自根 CA。 你的瀏覽器信任約 100 個根 CA。那些 CA 簽署中繼 CA,中繼 CA 簽署終端憑證。如果任何環節斷裂,信任就失敗了。

  3. 憑證管理是營運工作。 續期、撤銷和鏈設定是持續的工作。盡可能使用 Let’s Encrypt 自動化。

14. 下一步

我們已經介紹了非對稱密碼學、簽章和憑證。但還有另一個基本原語:確保資料沒有被修改,而不需要證明誰修改了它。

在下一篇文章中:HMAC 和資料完整性——當你已經共享金鑰時,如何用對稱金鑰偵測篡改。