Certificate & CA — vì sao trình duyệt tin một server
Certificate X.509 chứa gì, chain of trust từ root CA đến leaf, tại sao cần intermediate CA, Let's Encrypt cấp cert tự động ra sao, và những lỗi cert thường gặp nhất trong production.
TL;DR: Trình duyệt tin một server vì server cầm trong tay một certificate — tài liệu điện tử ghi "domain này thuộc về public key này, được ký bởi tổ chức X". Nhưng ai tin tổ chức X? Câu trả lời là một chuỗi tin cậy (chain of trust): cert của server được ký bởi intermediate CA, intermediate CA được ký bởi root CA, và root CA đã được pre-install vào OS/trình duyệt từ trước. Bạn không cần tin server — bạn tin root CA đã được bạch hóa. Let's Encrypt tự động hoá toàn bộ quy trình này miễn phí. Lỗi cert kinh điển nhất là thiếu intermediate cert — browser cache nó nên không thấy, nhưng curl và mobile fail.
1. Analogy — Chứng chỉ hành nghề có công chứng
Hình dung bạn đến gặp một bác sĩ lần đầu. Bạn không biết họ có đủ năng lực không. Họ đưa cho bạn:
- Bằng y khoa (leaf certificate) — ghi tên họ, chuyên khoa, ngày cấp, hạn dùng.
- Dấu công chứng của Bộ Y tế trên bằng đó (chữ ký của intermediate CA).
- Bộ Y tế được Nhà nước công nhận (root CA — bạn tin vì Nhà nước đã được xác thực từ trước).
Bạn không cần biết bác sĩ này là ai — bạn tin Nhà nước, Nhà nước tin Bộ Y tế, Bộ Y tế ký bằng. Chuỗi tin cậy đóng vòng.
| Analogy chứng chỉ | TLS Certificate |
|---|---|
| Bằng y khoa (tên + chuyên khoa + hạn) | Leaf cert (domain + public key + hạn) |
| Dấu công chứng Bộ Y tế | Chữ ký của intermediate CA |
| Bộ Y tế được Nhà nước công nhận | Intermediate CA được root CA ký |
| Nhà nước — pre-trusted | Root CA — đã có trong trust store OS/browser |
| Bằng tự làm không có công chứng | Self-signed certificate |
Tin cậy chỉ đi theo một chiều: từ root xuống leaf. Root CA ký intermediate, intermediate ký leaf. Trình duyệt đi ngược từ leaf lên để verify — kiểm tra từng chữ ký trong chuỗi. Chuỗi gãy ở đâu (thiếu link, cert hết hạn, chữ ký sai) thì lỗi ở đó.
2. Certificate X.509 chứa gì
Một certificate (theo chuẩn X.509, RFC 5280) là một tài liệu có cấu trúc chứa các trường sau:
SAN (Subject Alternative Names) là trường quan trọng nhất khi debug. Trình duyệt hiện đại kiểm tra SAN, không phải CN (Common Name) — cert phải có domain bạn đang truy cập trong SAN, kể cả subdomain. Một cert cho example.com không cover www.example.com trừ khi SAN có cả hai hoặc dùng wildcard *.example.com.
Xem cert thật bằng openssl:
openssl s_client -connect github.com:443 -showcerts </dev/null 2>/dev/null \
| openssl x509 -noout -text \
| grep -A 5 "Subject Alternative Name"
# X509v3 Subject Alternative Name:
# DNS:github.com, DNS:www.github.com
3. Chain of trust — root → intermediate → leaf
Thực tế không bao giờ root CA ký trực tiếp leaf cert. Chuỗi luôn có ít nhất một intermediate CA ở giữa:
flowchart TD
R["Root CA<br/>(ISRG Root X1)<br/>Tu ky - pre-installed OS"] --> I["Intermediate CA<br/>(Let's Encrypt R11)<br/>Duoc Root CA ky"]
I --> L["Leaf Certificate<br/>(example.com)<br/>Duoc Intermediate ky"]
L --> S["Server example.com<br/>Giu private key tuong ung"]Tại sao cần intermediate CA? Root CA là "vương miện" — private key của nó cực kỳ quý giá và nguy hiểm nếu bị lộ. Root CA phải được giữ offline trong két sắt vật lý (air-gapped HSM). Nếu root CA ký trực tiếp từng cert, nó phải online liên tục — rủi ro cao. Giải pháp: root CA ký một vài intermediate CA (có thể online), intermediate CA ký cert thường ngày. Nếu intermediate bị tấn công, revoke nó là xong — root vẫn an toàn.
Trust store của OS (Windows Certificate Store, macOS Keychain, Linux /etc/ssl/certs/) và trình duyệt chứa danh sách root CA pre-trusted. Khi trình duyệt nhận leaf cert, nó đi theo chuỗi lên root để verify — nếu root có trong trust store, chuỗi được chấp nhận.
4. Self-signed vs CA-signed
Self-signed certificate là cert không có Issuer nào khác — server tự ký cert của chính nó bằng private key của nó. Kỹ thuật không khác gì cert thường, chỉ là không ai đảm bảo danh tính ngoài bản thân server.
| Self-signed | CA-signed | |
|---|---|---|
| Chi phí | Miễn phí, tạo ngay | Miễn phí (Let's Encrypt) đến vài trăm USD/năm |
| Trình duyệt | Cảnh báo đỏ | Tin cậy mặc định |
| Dùng cho | Dev local, nội bộ | Production, public-facing |
| Revocation | Không có CRL/OCSP | Có thể revoke khi bị lộ |
Self-signed cert không chứng minh được danh tính server. Người dùng bỏ qua cảnh báo là đã train não "bấm qua cảnh báo cert là bình thường" — kẻ tấn công MITM với cert giả cũng bị bỏ qua luôn. Dùng Let's Encrypt — miễn phí và tự động hoàn toàn.
5. Let's Encrypt & ACME — cấp cert miễn phí tự động
Let's Encrypt là CA phi lợi nhuận cấp cert DV (Domain Validation) miễn phí thông qua giao thức ACME (Automatic Certificate Management Environment, RFC 8555). Cert có hạn 90 ngày và tự động gia hạn.
ACME hoạt động thế nào:
sequenceDiagram
participant C as certbot (client)
participant A as ACME Server (Let's Encrypt)
participant D as DNS / Web server
C->>A: Toi muon cert cho example.com
A-->>C: Challenge: dat file nay tai /.well-known/acme-challenge/<token>
C->>D: Tao file challenge
A->>D: GET /.well-known/acme-challenge/<token>
D-->>A: OK (content dung)
A-->>C: Domain validated — cert issued
C->>D: Cai cert moiBước quan trọng: Let's Encrypt yêu cầu bạn chứng minh quyền kiểm soát domain qua challenge. Hai loại phổ biến:
- HTTP-01 challenge: đặt một file tại
http://example.com/.well-known/acme-challenge/<token>— Let's Encrypt fetch để verify. - DNS-01 challenge: tạo TXT record
_acme-challenge.example.com— dùng khi server không public (staging server, wildcard cert).
Tự động gia hạn với certbot renew hoặc systemd timer — không cần làm gì thủ công sau khi setup.
# Cai certbot (Ubuntu)
sudo apt install certbot python3-certbot-nginx
# Lay cert cho Nginx (HTTP-01 challenge tu dong)
sudo certbot --nginx -d example.com -d www.example.com
# Test gia han (dry run, khong thuc su gia han)
sudo certbot renew --dry-run
6. Lỗi cert thường gặp và cách đọc
| Lỗi | Nguyên nhân | Debug |
|---|---|---|
CERTIFICATE_EXPIRED | Cert hết hạn (90 ngày Let's Encrypt) | Chạy certbot renew hoặc xem ngày hết hạn: openssl s_client -connect host:443 |
ERR_CERT_COMMON_NAME_INVALID | Domain không có trong SAN | Xem SAN: openssl x509 -noout -text |
ERR_CERT_AUTHORITY_INVALID | Thiếu intermediate cert, hoặc self-signed | Xem full chain: openssl s_client -showcerts |
SSL_ERROR_RX_RECORD_TOO_LONG | Port 443 nhưng server trả HTTP (không phải TLS) | Kiểm tra Nginx/Apache config — thường do bind sai port |
REVOKED | Cert bị revoke (private key bị lộ) | CA thu hồi qua CRL/OCSP — phải issue cert mới |
Lỗi kinh điển nhất: thiếu intermediate cert. Đây là lỗi hay gặp nhất trong production:
# Kiem tra chain day du
openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null | grep "s:"
# Phai thay it nhat 2 dong:
# s:CN = example.com (leaf cert)
# s:CN = Let's Encrypt R11 (intermediate CA)
# s:CN = ISRG Root X1 (root CA — co the khong co vi browser biet san)
Desktop browser thường cache intermediate CA từ các lần truy cập trước — nên bạn test trên Chrome không thấy lỗi. Nhưng curl, Postman, mobile app mới cài, hay khi clear cache đều fail với lỗi "certificate verify failed". Luôn deploy đủ chain: leaf + intermediate. Với Nginx: dùng ssl_certificate fullchain.pem (không phải cert.pem). Let's Encrypt certbot tạo sẵn fullchain.pem đã bao gồm intermediate.
7. Kiểm tra cert bằng openssl s_client
openssl s_client là tool debug cert mạnh nhất trên CLI:
# Ket noi va xem cert chain
openssl s_client -connect github.com:443 -showcerts </dev/null
# Output quan trong:
# Certificate chain
# 0 s:CN = github.com (leaf)
# i:CN = Let's Encrypt R11 (issuer = intermediate)
# 1 s:CN = Let's Encrypt R11 (intermediate)
# i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
# Xem ngay het han
openssl s_client -connect github.com:443 </dev/null 2>/dev/null \
| openssl x509 -noout -dates
# notBefore=Jan 5 00:00:00 2025 GMT
# notAfter=Apr 5 23:59:59 2025 GMT
# Kiem tra SAN
openssl s_client -connect github.com:443 </dev/null 2>/dev/null \
| openssl x509 -noout -text | grep -A 2 "Subject Alternative"
8. Pitfall — hiểu nhầm thường gặp
❌ Nhầm 1: "Thiếu intermediate cert thì mọi client đều báo lỗi."
✅ Không phải. Desktop browser (Chrome, Firefox) cache intermediate CA từ các phiên trước — bạn test thấy ổn. curl, wget, mobile app mới cài, và server-to-server call (Webhook, API client) sẽ fail. Luôn dùng fullchain.pem, không phải cert.pem.
❌ Nhầm 2: "Self-signed cert đủ cho staging."
✅ Self-signed cert khiến mọi HTTP client từ chối kết nối trừ khi bỏ qua verify (curl -k, --insecure). Môi trường staging nên dùng cert thật từ Let's Encrypt với staging ACME server (miễn phí, không rate limit) — giống production, không gây lỗi sai.
❌ Nhầm 3: "Wildcard cert (*.example.com) cover mọi subdomain."
✅ Wildcard chỉ cover một cấp: *.example.com cover api.example.com, www.example.com — nhưng không cover api.v2.example.com hay example.com (không có subdomain). Cần SAN riêng cho domain gốc.
9. Liên hệ các bài khác
- Bài 01 — TLS/HTTPS handshake: Certificate xuất hiện trong bước ServerHello của TLS handshake — bài đó giải thích cơ chế kỹ thuật, bài này giải thích nội dung cert và tại sao trình duyệt tin.
- Bài 03 — Bộ công cụ debug:
openssl s_clientvàcurl -vđể xem cert thật — bài đó liệt kê cú pháp đầy đủ với output mẫu. - Bài 06 — Mini-challenge reverse proxy TLS: Bạn sẽ tự issue và cài cert vào Nginx — ứng dụng trực tiếp của bài này.
- Khoá Foundations — Điều gì xảy ra khi gõ URL: TLS/cert là chặng 3 trong bản đồ 5 chặng — bài đó nhắc "xác thực server qua certificate" nhưng không đào sâu.
10. 📚 Deep Dive — tài liệu gốc
Đọc khi muốn đi tới tận gốc:
- RFC 5280 — X.509 PKI Certificate Profile: Định nghĩa đầy đủ cấu trúc certificate X.509 — Subject, SAN, chain validation, revocation (CRL/OCSP).
- RFC 8555 — ACME Protocol: Giao thức Let's Encrypt dùng để tự động hoá cấp cert — HTTP-01, DNS-01, TLS-ALPN-01 challenge.
- Let's Encrypt Documentation: Hướng dẫn thực hành certbot + Nginx/Apache. "Certificate Compatibility" list những client nào trust ISRG Root X1.
- RFC 6960 — OCSP (Online Certificate Status Protocol): Cách trình duyệt kiểm tra cert có bị revoke chưa (thay thế CRL — danh sách đen offline).
11. Tóm tắt
- Certificate X.509 chứa: domain (Subject/SAN), public key, Issuer CA, hạn dùng, chữ ký số của CA.
- Chain of trust: root CA (pre-installed) ký intermediate CA, intermediate ký leaf cert. Trình duyệt verify từ leaf lên root — chuỗi gãy ở đâu là lỗi ở đó.
- Intermediate CA tồn tại để giữ root CA offline an toàn — nếu intermediate bị tấn công, revoke nó mà không ảnh hưởng root.
- SAN (không phải CN) là trường trình duyệt dùng để verify domain — wildcard
*.example.comchỉ cover một cấp subdomain. - Self-signed cert không có bên thứ ba bảo chứng — mọi client từ chối trừ khi bỏ qua verify (
-k). Không dùng production. - Let's Encrypt cấp cert DV miễn phí qua ACME — HTTP-01 hoặc DNS-01 challenge để chứng minh sở hữu domain, gia hạn tự động mỗi 90 ngày.
- Lỗi kinh điển: thiếu intermediate cert — browser cache nên không thấy lỗi, nhưng curl/mobile/API client fail. Luôn dùng
fullchain.pem. - Debug bằng
openssl s_client -connect host:443 -showcertsđể xem full chain, hạn cert, và SAN.
12. Tự kiểm tra
Q1Certificate X.509 chứa những trường quan trọng nào? Trường nào trình duyệt dùng để kiểm tra domain?▸
example.com phải có example.com trong SAN. Wildcard *.example.com cover một cấp subdomain nhưng không cover domain gốc hay sub-sub-domain.Q2Tại sao chain of trust cần intermediate CA? Tại sao không để root CA ký trực tiếp mọi leaf cert?▸
Q3Let's Encrypt dùng cơ chế gì để xác minh bạn thật sự sở hữu domain trước khi cấp cert?▸
http://yourdomain.com/.well-known/acme-challenge/<token> — Let's Encrypt fetch URL đó để verify bạn kiểm soát web server. (2) DNS-01: Let's Encrypt yêu cầu bạn tạo TXT record _acme-challenge.yourdomain.com — dùng khi server không public hoặc cần wildcard cert. Sau khi challenge pass, cert được issue trong vài giây và có hạn 90 ngày với tự động gia hạn.Q4Tại sao thiếu intermediate cert lại là lỗi nguy hiểm mà khó phát hiện trong development?▸
fullchain.pem (leaf + intermediate), không phải cert.pem (chỉ leaf).Q5Sự khác biệt thực tế giữa self-signed cert và cert được CA ký là gì? Khi nào dùng self-signed hợp lý?▸
Q6Bạn deploy Nginx với cert Let's Encrypt nhưng curl từ máy khác báo 'certificate verify failed'. Desktop browser của bạn lại thấy ổn. Nguyên nhân khả năng cao nhất là gì và cách fix?▸
openssl s_client -connect yourdomain.com:443 -showcerts và đếm số cert: phải thấy ít nhất 2 (leaf + intermediate). Fix: trong Nginx thay ssl_certificate cert.pem bằng ssl_certificate fullchain.pem — file fullchain.pem mà certbot tạo đã bao gồm cả intermediate. Reload Nginx và test lại bằng curl.Bài tiếp theo: Bộ công cụ debug mạng — dig, curl, nc, ss, tcpdump
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên