chundev
日期:2026-04-29 主題:TLS / PKI / 企業內 CA 部署
TLS 信任是單向的 — 驗證 cert 的是 client,不是 server。Server 即使把 root CA 包進 chain 一起送,client 也會忽略,root 必須是 client 預先就信任的。企業內派送 CA 信任有兩條路:自簽 CA(要自己想辦法把 root 送到每台機器)、公信 CA(OS 廠商已經幫你內建,零派送)。
很常見的場景:企業內架了一個 API 服務,憑證用內網 AD CA 簽。另一台機器要打 API 過來,於是冒出問題:
那張 root 憑證要不要加到「被打的那台」(Server)的 OS 信任清單裡? 如果我把 root 包進 server cert chain 一起送過去,是不是 client 就不用裝了?
兩個問題都對 TLS 信任模型有誤解。下面拆解清楚。
不需要。看 TLS 握手的方向:
Client ────HTTPS 請求────▶ Server
│ 出示 server cert(CA 簽的)
◀───── server cert ────┘
│
└─ 「我憑什麼信你?」用自己的信任清單比對 root
驗證憑證的是收到憑證的那一方,不是出示憑證的那一方。Server 的 cert 是它自己的身分證,它不用驗證自己。
→ 結論:Server 那台 不需要 把 root 加到 OS 信任清單。要加的是「打 API 過來那台 client」。
唯一例外:Server 本身也會主動呼叫其他內部服務(這時它變 Client),或啟用了 mTLS 要驗 client cert,這兩種情況才需要在 Server 端裝 root。
沒用。 這是新手最容易踩的概念坑。
❌ 錯誤心智模型:
Server: "我用這張 cert,順便附上簽我的 root,你信吧"
Client: "好喔" ← 如果是這樣,整個 PKI 崩潰
✅ 實際信任模型:
Server: "我這張 cert 是 X 簽的,X 是 Y 簽的,Y 是 root"
Client: "我先看我自己的信任清單裡有沒有 Y"
├─ 有 → 過
└─ 沒有 → 拒絕(你送過來的 root 我**忽略**)
如果 server 送什麼 root client 就信什麼,那任何攻擊者只要自己生一張 root + 簽張 fake server cert 就能假冒任何網站,HTTPS 形同虛設。
Trust anchor 必須透過 TLS 以外的安全管道事先送到 client(GPO 派送、手動匯入、OS 內建、image baking),靠 TLS channel 本身傳 root 是先有雞還是先有蛋的悖論。
依 RFC 5246 / 8446 的精神,server 送出的 chain 不應該包含 root。常見作法是 fullchain 只含 leaf + intermediate(s),不含 root:
# 在還沒匯入 AD CA root 的機器上
curl -v https://internal-api.example.local/
# 即使 server 把 root 包進 chain 一起送過來,你會看到:
# * SSL certificate problem: unable to get local issuer certificate
# ^^^^^ 注意是 "local"
「local」issuer — 找的是 client 本機的信任清單。
自簽 CA(AD CS / 自建 OpenSSL CA)的痛點:root cert 必須事先透過某種方式送到每一台要驗的機器。常見方式:
GPO 路徑:
Computer Configuration
└─ Policies
└─ Windows Settings
└─ Security Settings
└─ Public Key Policies
├─ Trusted Root Certification Authorities ← root 匯這裡
└─ Intermediate Certification Authorities ← 中間 CA 匯這裡
Enterprise CA 福利:選「Enterprise CA」(AD 整合版)時,root 自動寫入 AD 的 Configuration → Services → Public Key Services → Certification Authorities,所有網域成員開機 / gpupdate 後自動繼承,零手動。
GPO 涵蓋範圍:
| 對象 | GPO 是否吃得到 |
|---|---|
| Domain-joined Windows | ✅ 自動 |
| Domain-joined Mac (進階整合) | ⚠️ 部分,通常要 Jamf 補 |
| Linux server | ❌ 完全不吃 |
| 行動裝置 | ❌ 要 MDM |
| 廠商來的非網域機器 | ❌ 要手動 |
| Container / K8s pod | ❌ 要烤進 image |
Jamf / Intune / Workspace ONE 設 Configuration Profile,把 root cert 包成 .mobileconfig(iOS / Mac)或 SCEP / PKCS#12,員工登錄 MDM 時自動信任。
# Ansible playbook 範例
- name: Trust internal CA root
copy:
src: internal-ca-root.crt
dest: /usr/local/share/ca-certificates/internal-ca-root.crt
- command: update-ca-certificates
也可以用 Puppet / Chef / SaltStack / SCCM。
把 CA HTTP 下載點寫進 cert 的 AIA (Authority Information Access) 與 CDP (CRL Distribution Points) 欄位:
常見錯誤:cert 簽出來但 AIA URL 內網外網不通,導致行動裝置 / 外部廠商驗不過。
COPY internal-ca-root.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates
新機 / 新 container 開出來就已經信任。
# Linux
sudo cp internal-ca-root.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
# Windows
certutil -addstore -f "Root" internal-ca-root.cer
# Java(常見遺漏!)
keytool -importcert -trustcacerts -alias internal-ca \
-file internal-ca-root.crt \
-keystore "$JAVA_HOME/lib/security/cacerts" \
-storepass changeit
# Node.js
export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/internal-ca-root.crt
# Python (requests / httpx)
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
買的公信 CA(DigiCert、TWCA、GlobalSign、Let’s Encrypt 等)的 root,OS 廠商和瀏覽器廠商已經內建:
你買 cert → 公信 CA 簽
↑
這家 CA 的 root 早就被收進:
├─ Microsoft Trusted Root Program (Windows)
├─ Apple Root Program (macOS / iOS)
├─ Mozilla CA Certificate Program (Firefox / 多數 Linux)
├─ Chrome Root Program (Chrome 105+ 自己用)
├─ Android System Trust Store
└─ Java cacerts / Node.js / Python certifi
↓ Windows Update / 系統更新自動同步
你的 client → 早就信任了 → 連線 OK
Chrome 105 起在 macOS 與 Windows 改用自己的 root store,不再讀 OS。後續推到 ChromeOS、Linux、Android(iOS 因 Apple 政策仍用系統 store)。實務影響:
*.example.com 只能匹配一層 subdomain:
api.example.cominternal.example.comapi.dept.example.com ← 兩層 subdomain,不過要多層或多個無關網域,有幾種選擇:
| 類型 | 涵蓋範圍 | 適用場景 |
|---|---|---|
標準 wildcard *.example.com |
單層 subdomain | 簡單統一網域 |
| SAN cert(多列幾個 FQDN) | 明確列出的每個 host | 數量可控、安全度要求高 |
| Multi-Domain Wildcard / SAN Wildcard | 多個 wildcard 共存 | 跨多個網域 |
| ACME 自動化(單 cert / 服務) | 每個服務一張 | DevOps、安全成熟度高 |
一張 wildcard cert 私鑰外洩 = 整個網域所有服務都被冒充。安全成熟度高的組織會避免 wildcard,改用大量 SAN 或自動化簽單 cert(ACME + DNS challenge)。
| 情境 | 自簽 (內網 AD CA) | 公信 CA |
|---|---|---|
| 內網系統,員工都網域成員 | ⭐ GPO 自動派送,最佳解 | 也行但要花錢 |
| 對外服務(公開域名) | ❌ 外部不信你的 root | ⭐ 必選 |
| 開發 / 測試環境 | ⭐ 自簽夠用 | 浪費錢 |
| 給廠商串 API(非網域成員) | ⚠️ 寄 root cert + SOP,麻煩 | ⭐ 對方零配置 |
| 行動裝置 / BYOD | ⚠️ 要 MDM | ⭐ 內建信任 |
| 自動化 / DevOps | ⭐ AD CS auto-enrollment 或內網 ACME | ⭐ ACME (Let’s Encrypt) |
update-ca-certificates 不一定夠,Java 還要 keytool 匯 cacerts,Node 要 NODE_EXTRA_CA_CERTS。