TCP, HTTP & Web cho Backend/Certificate & CA — vì sao trình duyệt tin một server
24/29
Bài 24 / 29~22 phútTLS & Công cụ DebugMiễn phí lượt xem

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:

  1. Bằng y khoa (leaf certificate) — ghi tên họ, chuyên khoa, ngày cấp, hạn dùng.
  2. Dấu công chứng của Bộ Y tế trên bằng đó (chữ ký của intermediate CA).
  3. 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ậnIntermediate CA được root CA ký
Nhà nước — pre-trustedRoot CA — đã có trong trust store OS/browser
Bằng tự làm không có công chứngSelf-signed certificate
💡 Cách nhớ chain of trust

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:

Subject— ai sở hữu cert này: CN=example.com, O=Acme Corp
Subject Alternative Names (SAN)— danh sách domain/IP được bao phủ: *.example.com, example.com
Public Key— khoá công khai của server (ECDSA hoặc RSA)
Issuer— CA nào ký cert này: Let's Encrypt Authority R11
Valid From / Valid To— thời hạn: 2025-01-01 đến 2025-04-01 (Let's Encrypt: 90 ngày)
Signature— chữ ký số của Issuer CA trên toàn bộ nội dung trên

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-signedCA-signed
Chi phíMiễn phí, tạo ngayMiễn phí (Let's Encrypt) đến vài trăm USD/năm
Trình duyệtCảnh báo đỏTin cậy mặc định
Dùng choDev local, nội bộProduction, public-facing
RevocationKhông có CRL/OCSPCó thể revoke khi bị lộ
⚠️ Self-signed trong production là lỗi bảo mật

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 moi

Bướ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ỗiNguyên nhânDebug
CERTIFICATE_EXPIREDCert 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_INVALIDDomain không có trong SANXem SAN: openssl x509 -noout -text
ERR_CERT_AUTHORITY_INVALIDThiếu intermediate cert, hoặc self-signedXem full chain: openssl s_client -showcerts
SSL_ERROR_RX_RECORD_TOO_LONGPort 443 nhưng server trả HTTP (không phải TLS)Kiểm tra Nginx/Apache config — thường do bind sai port
REVOKEDCert 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)
⚠️ Thiếu intermediate — browser OK nhưng curl và mobile fail

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

10. 📚 Deep Dive — tài liệu gốc

📚 RFC & tài liệu chính thức

Đọc khi muốn đi tới tận gốc:

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.com chỉ 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

Tự kiểm tra
Q1
Certificate 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?
Certificate X.509 chứa: Subject (danh tính chủ sở hữu), Subject Alternative Names (SAN) (danh sách domain/IP được bao phủ), Public Key (khoá công khai của server), Issuer (CA nào ký), Valid From/To (thời hạn), và Signature (chữ ký số của Issuer CA). Trình duyệt hiện đại dùng SAN để kiểm tra domain — không phải CN. Cert cho 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.
Q2
Tạ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?
Root CA là "vương miện" của hệ thống PKI — private key của nó cực kỳ quý giá. Nếu root CA private key bị lộ, toàn bộ hệ thống tin cậy sụp đổ và phải phân phối lại trust store cho mọi thiết bị trên thế giới. Vì vậy root CA phải offline hoàn toàn trong két an toàn vật lý (air-gapped HSM). Intermediate CA đứng giữa: được root ký một lần khi tạo, rồi online để ký cert thường ngày. Nếu intermediate bị tấn công, revoke intermediate là xong — root vẫn an toàn, tạo intermediate mới là tiếp tục hoạt động.
Q3
Let'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?
Let's Encrypt dùng giao thức ACME (RFC 8555) với hai loại challenge phổ biến: (1) HTTP-01: Let's Encrypt yêu cầu bạn đặt một file đặc biệt tại 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.
Q4
Tại sao thiếu intermediate cert lại là lỗi nguy hiểm mà khó phát hiện trong development?
Lỗi thiếu intermediate cert không hiện ra trên desktop browser vì browser (Chrome, Firefox) tích cực cache intermediate CA từ các lần truy cập trước — cache đó đủ để hoàn thành chain. Khi bạn test trên browser của mình đã dùng nhiều lần, mọi thứ có vẻ ổn. Nhưng curl, Postman, mobile app mới cài, webhook endpoint, và server-to-server call không có cache đó — chúng nhận chain không đầy đủ và từ chối. Hậu quả: deployment lên production thấy ổn, nhưng API client của đối tác, mobile app user mới, hay integration bên thứ ba đều fail. Fix: luôn dùng fullchain.pem (leaf + intermediate), không phải cert.pem (chỉ leaf).
Q5
Sự 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ý?
Self-signed cert không có bên thứ ba bảo chứng danh tính — server tự ký cert của mình. Mọi HTTP client (curl, browser, mobile app) từ chối bởi mặc định vì không verify được chain. Dùng hợp lý cho: development local (localhost HTTPS), mạng nội bộ với client đã cài thủ công CA nội bộ, hoặc service-to-service khi cả hai đầu trong cùng hệ thống kiểm soát được trust store. Không bao giờ dùng self-signed cho public-facing production — dùng Let's Encrypt miễn phí, tự động, và được trust mặc định bởi mọi thiết bị hiện đại.
Q6
Bạ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?
Nguyên nhân khả năng cao nhất là thiếu intermediate cert trong cấu hình Nginx. Desktop browser cache intermediate CA nên tự hoàn thành chain và không báo lỗi. curl gọi thẳng, không cache, nhận chain không đầy đủ và từ chối. Kiểm tra bằng 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

Đặt 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

Bài tiếp theo

Bộ công cụ debug mạng — dig, curl, nc, ss, tcpdump