TLS và HTTPS: ổ khóa trên thanh địa chỉ thực sự làm gì

TLS 1.3 ghép hàm băm, mã hóa đối xứng, thỏa thuận khóa và chữ ký thành một lần bắt tay. Đọc chuỗi chứng chỉ thật của viet.sarl, Let's Encrypt và ACME, Certificate Transparency, forward secrecy, HSTS, và hai đoạn TLS phía sau Cloudflare.

Bốn bài đầu của seri là từng viên gạch: số ngẫu nhiên, hàm băm, mật khẩu, và khóa công khai. Bài này ghép chúng thành giao thức được dùng nhiều nhất thế giới: TLS, chữ "S" trong HTTPS.

Bài về mạng của seri máy tính đã đo thời gian: bắt tay TLS tới viet.sarl xong sau khoảng 145 ms, tức thêm khoảng một RTT sau TCP. Bài này không đo lại thời gian mà mở bên trong lần bắt tay đó: ai gửi gì, vì sao kẻ đứng giữa không làm được gì, và chứng chỉ đến từ đâu.

Lộ trình: TLS bảo vệ cái gì, bắt tay TLS 1.3 từng bước, chứng chỉ và chuỗi tin cậy (đọc chứng chỉ thật của viet.sarl), Let's Encrypt và ACME, Certificate Transparency, forward secrecy, HSTS, SNI và ECH, hai đoạn TLS phía sau Cloudflare, rồi những gì TLS không bảo vệ. Lệnh dùng là openssl s_client (OpenSSL 3.6) chạy trên máy thật.

TLS bảo vệ cái gì

Giữa trình duyệt và máy chủ có Wi-Fi quán cà phê, nhà mạng, các điểm trung chuyển Internet. Ai ở giữa cũng đọc và sửa được gói tin. TLS đảm bảo ba điều:

Nghĩa là Công cụ
Xác thực máy chủ Đang nói chuyện đúng với viet.sarl, không phải kẻ giả mạo Chứng chỉ + chữ ký (bài 4)
Bí mật Người ở giữa không đọc được nội dung Thỏa thuận khóa (bài 4) + mã hóa đối xứng
Toàn vẹn Người ở giữa không sửa được mà không bị phát hiện AEAD (thẻ xác thực trên từng bản ghi)

Bắt tay TLS 1.3 từng bước

TLS 1.3 (2018) gọn hơn hẳn TLS 1.2: chỉ một lượt đi và về trước khi gửi được dữ liệu, và bỏ hết các thuật toán cũ.

Trình duyệt                                          Máy chủ
  ClientHello  ───────────────────────────────────►
    các bộ mã hỗ trợ, nonce ngẫu nhiên,
    tên miền (SNI), khóa tạm X25519 + ML-KEM
                ◄───────────────────────────────────  ServerHello
                                                        chọn bộ mã, khóa tạm của máy chủ
                     ── từ đây trở đi đã mã hóa ──
                ◄───────────────────────────────────  Certificate (chuỗi chứng chỉ)
                ◄───────────────────────────────────  CertificateVerify (chữ ký)
                ◄───────────────────────────────────  Finished (HMAC của cả cuộc bắt tay)
  Finished  ──────────────────────────────────────►
  Yêu cầu HTTP (đã mã hóa)  ──────────────────────►

Từng bước dùng một viên gạch của seri:

  1. Thỏa thuận khóa. Trình duyệt gửi luôn khóa công khai tạm của mình trong lời chào đầu tiên, máy chủ gửi lại khóa của nó. Hai bên tính ra cùng một bí mật (bài 4). Với viet.sarl, đó là X25519 cộng ML-KEM-768.
  2. Dẫn xuất khóa. Từ bí mật chung, cả hai chạy HKDF, một hàm dựa trên HMAC (bài 2), để sinh ra nhiều khóa riêng: khóa cho từng chiều, khóa cho phần bắt tay, khóa cho dữ liệu.
  3. Chứng chỉ. Máy chủ gửi chứng chỉ có khóa công khai lâu dài của nó, được một CA ký.
  4. CertificateVerify. Máy chủ ký bản băm của toàn bộ cuộc bắt tay tới lúc đó bằng khóa bí mật lâu dài. Đây là bước chặn kẻ đứng giữa. Kẻ đó có thể chuyển tiếp chứng chỉ thật, nhưng không có khóa bí mật để ký các khóa tạm do chính mình chèn vào.
  5. Finished. Mỗi bên gửi một HMAC của cả cuộc bắt tay. Nếu ai ở giữa sửa bất kỳ byte nào, chẳng hạn hạ cấp xuống thuật toán yếu hơn, HMAC sẽ không khớp.
  6. Dữ liệu đi qua một AEAD: AES-GCM hoặc ChaCha20-Poly1305.

Bộ mã của TLS 1.3 chỉ còn 5, so với hàng trăm tổ hợp của TLS 1.2. Không còn RSA trao khóa, CBC, RC4, SHA-1 hay nén dữ liệu. Mỗi thứ bị bỏ đều từng gây ra ít nhất một lỗ hổng có tên riêng.

Đọc kết nối thật tới viet.sarl

$ openssl s_client -connect viet.sarl:443 -servername viet.sarl
Protocol: TLSv1.3
Cipher is TLS_AES_256_GCM_SHA384
Negotiated TLS1.3 group: X25519MLKEM768
Peer signature type: ecdsa_secp256r1_sha256
Verification: OK

Đọc từng dòng:

  • TLS_AES_256_GCM_SHA384: dữ liệu mã hóa bằng AES-256-GCM, còn HKDF và Finished dùng SHA-384.
  • X25519MLKEM768: thỏa thuận khóa kiểu lai, chống được máy tính lượng tử (bài 4).
  • ecdsa_secp256r1_sha256: máy chủ ký CertificateVerify bằng khóa ECDSA P-256.

Thử ép phiên bản cũ hơn:

Ép phiên bản Kết quả
TLS 1.1 Không bắt tay được
TLS 1.2 Được, với ECDHE-ECDSA-CHACHA20-POLY1305
TLS 1.3, chỉ X25519 Được, không có phần hậu lượng tử

TLS 1.2 vẫn được nhận cho các thiết bị cũ, nhưng chỉ với bộ mã hiện đại (ECDHE và AEAD). TLS 1.0 và 1.1 đã bị các trình duyệt bỏ từ năm 2020.

Chứng chỉ và chuỗi tin cậy

Chứng chỉ là một tài liệu ghi "khóa công khai này thuộc về tên miền này, có hiệu lực từ ngày A tới ngày B", rồi được một CA (certificate authority) ký. Trình duyệt không biết viet.sarl, nhưng biết khoảng 150 CA gốc có sẵn trong hệ điều hành. Nếu lần theo được chữ ký từ chứng chỉ của trang lên tới một CA gốc, trình duyệt tin chứng chỉ đó.

Chuỗi thật của viet.sarl:

0 CN=viet.sarl                 khóa EC P-256      ký bởi YE2 (ecdsa-with-SHA384)
                               hiệu lực Oct 14 2026 → Jan 12 2027, tên: viet.sarl, *.viet.sarl
1 Let's Encrypt YE2            khóa EC P-384      ký bởi Root YE
2 ISRG Root YE                 khóa EC P-384      ký bởi ISRG Root X2
3 ISRG Root X2                 khóa EC P-384      ký bởi ISRG Root X1 (RSA)

Vài điều đáng chú ý:

  • Chứng chỉ chỉ có hiệu lực 90 ngày. Chứng chỉ ngắn hạn làm giảm thiệt hại khi khóa bị lộ, vì việc thu hồi chứng chỉ trong thực tế hoạt động kém. Ngành đang tiến tới giới hạn 47 ngày vào năm 2029.
  • Mọi khóa trong chuỗi đều là đường cong elliptic, trừ chữ ký ở mắt cuối: ISRG Root X2 được "ký chéo" bởi Root X1 (RSA). Root X1 có mặt trên nhiều thiết bị cũ hơn, nên chữ ký chéo giúp thiết bị chưa có Root X2 vẫn tin được chuỗi này.
  • Chứng chỉ có cả *.viet.sarl, và đây là chứng chỉ Cloudflare cấp cho máy chủ biên của nó (phần dưới giải thích).

Mô hình CA có điểm yếu lớn: bất kỳ CA nào trong danh sách cũng ký được chứng chỉ cho bất kỳ tên miền nào. Năm 2011, CA DigiNotar của Hà Lan bị xâm nhập và cấp chứng chỉ giả cho *.google.com, được dùng để nghe lén người dùng ở Iran. DigiNotar bị gỡ khỏi mọi trình duyệt và phá sản ngay trong năm đó.

Let's Encrypt và ACME

Trước năm 2015, chứng chỉ tốn tiền, phải gia hạn tay và hay bị quên. Let's Encrypt cấp chứng chỉ miễn phí và tự động qua giao thức ACME:

  1. Máy chủ tạo một cặp khóa và xin chứng chỉ cho viet.sarl.
  2. Let's Encrypt đưa ra một thử thách để chứng minh bạn kiểm soát tên miền. Có thể là đặt một file ở http://viet.sarl/.well-known/acme-challenge/... (HTTP-01), trả lời trên cổng 443 (TLS-ALPN-01), hoặc thêm một bản ghi DNS (DNS-01).
  3. Let's Encrypt kiểm tra từ nhiều địa điểm khác nhau trên Internet, rồi ký chứng chỉ.

Caddy làm toàn bộ việc này mà không cần cấu hình. Caddyfile của trang này chỉ có {$DOMAIN} { ... }, và Caddy tự xin, tự lưu và tự gia hạn chứng chỉ trước khi hết hạn. Nhờ tự động hóa kiểu này, HTTPS đã đi từ khoảng 40% số trang được tải năm 2015 lên hơn 90% hiện nay.

Certificate Transparency: ai cũng thấy ai được cấp gì

Để chống chuyện như DigiNotar, mọi chứng chỉ công khai giờ đều phải được ghi vào các nhật ký Certificate Transparency (CT). Đây là các nhật ký công khai, chỉ ghi thêm được, cấu trúc bằng cây Merkle (bài 2) để không ai sửa lén được. Trình duyệt từ chối chứng chỉ không có bằng chứng đã được ghi vào nhật ký.

Hệ quả: chủ một tên miền có thể tra mọi chứng chỉ từng được cấp cho tên miền của mình trên các trang như crt.sh, và đặt cảnh báo khi có chứng chỉ lạ. Hệ quả phụ: mọi tên miền con có chứng chỉ đều lộ ra công khai. Đừng đặt admin-noi-bo.viet.sarl rồi nghĩ không ai biết.

Forward secrecy

Nếu một ngày khóa bí mật lâu dài của máy chủ bị lộ, lưu lượng đã ghi lại từ trước có bị giải mã không?

  • TLS 1.2 với RSA trao khóa (đã bị bỏ): có. Trình duyệt mã hóa bí mật phiên bằng khóa công khai RSA của máy chủ. Lộ khóa đó là giải mã được mọi phiên cũ.
  • TLS 1.3: không. Bí mật phiên đến từ các khóa tạm, tạo mới cho từng kết nối rồi xóa ngay. Khóa lâu dài chỉ dùng để ký, không dùng để mã hóa. Lộ nó thì kẻ gian giả mạo được máy chủ từ lúc đó trở đi, nhưng không đọc được gì của quá khứ.

Tính chất này gọi là forward secrecy. Nó là lý do bài trước nhấn mạnh việc tạo khóa đường cong elliptic chỉ mất vài micro giây: mỗi kết nối đều cần một cặp mới.

HSTS: đừng bao giờ thử HTTP

Gõ viet.sarl vào thanh địa chỉ, trình duyệt có thể thử http:// trước. Trang này chuyển hướng ngay:

$ curl -sI http://viet.sarl/
HTTP/1.1 308 Permanent Redirect
Location: https://viet.sarl/

Nhưng chính yêu cầu HTTP đầu tiên đó không được mã hóa. Kẻ đứng giữa có thể chặn nó và không bao giờ chuyển sang HTTPS (tấn công "SSL stripping"). Header HSTS (Strict-Transport-Security: max-age=31536000) bảo trình duyệt: "trong một năm tới, chỉ dùng HTTPS với tên miền này, kể cả khi người dùng gõ http". Danh sách HSTS preload còn được cài sẵn trong trình duyệt, nên có hiệu lực ngay từ lần truy cập đầu tiên.

Kiểm tra trang này: header trả về chưa có Strict-Transport-Security. Đây là một mục cần làm, và bài 9 sẽ ghi lại cùng các header bảo mật khác. Một lưu ý trước khi bật: HSTS rất khó gỡ. Nếu sau này một tên miền con không có HTTPS, trình duyệt sẽ từ chối vào nó cho tới khi max-age hết hạn. Nên bắt đầu với max-age ngắn rồi tăng dần.

SNI và ECH: tên trang vẫn lộ

Một địa chỉ IP của Cloudflare phục vụ hàng triệu trang. Máy chủ cần biết trình duyệt muốn trang nào để đưa đúng chứng chỉ, nên ClientHello mang tên miền ở dạng chưa mã hóa. Đó là SNI. Người ở giữa không thấy bạn đọc bài nào trên viet.sarl, nhưng thấy bạn đang vào viet.sarl. Truy vấn DNS cũng để lộ tên miền, trừ khi dùng DNS qua HTTPS.

ECH (Encrypted Client Hello) mã hóa luôn phần đó bằng một khóa công khai lấy từ DNS. Cloudflare và các trình duyệt lớn đã hỗ trợ, nhưng chưa phổ biến ở mọi nơi.

Hai đoạn TLS phía sau Cloudflare

Ổ khóa trên trình duyệt chỉ nói về đoạn đầu tiên. Với trang này có hai đoạn:

Trình duyệt ──TLS 1.3 (chứng chỉ Cloudflare)──► Cloudflare ──TLS (chứng chỉ Caddy xin từ Let's Encrypt)──► Caddy ──HTTP──► app
  • Đoạn 1: trình duyệt tới Cloudflare. Đây là chứng chỉ đọc ở trên, Cloudflare tự xin cho máy chủ biên.
  • Đoạn 2: Cloudflare tới máy chủ gốc. Caddy có chứng chỉ riêng. Cloudflare giải mã ở giữa, nên Cloudflare đọc được toàn bộ lưu lượng. Đó là cái giá của cache và chống tấn công từ chối dịch vụ: bạn tin Cloudflare như tin chính máy chủ của mình.
  • Đoạn 3: Caddy tới app, bên trong mạng Docker, là HTTP thường. Đoạn này chấp nhận được vì nằm trên cùng một máy.

Cấu hình đoạn 2 trên Cloudflare quan trọng hơn người ta nghĩ. Chế độ "Flexible" dùng HTTP thường từ Cloudflare về máy chủ gốc, nên trình duyệt hiện ổ khóa trong khi nửa đường còn lại không mã hóa. Chế độ "Full (strict)" yêu cầu máy chủ gốc có chứng chỉ hợp lệ và kiểm tra nó. Caddy của trang này có chứng chỉ thật từ Let's Encrypt, nên dùng được chế độ "Full (strict)". Đó là chế độ nên chọn, và nên kiểm tra lại trong bảng điều khiển của Cloudflare chứ đừng mặc định là đã đúng.

TLS không bảo vệ cái gì

  • Dữ liệu trên máy chủ: TLS kết thúc ở Cloudflare và Caddy. Database, log và bản sao lưu là việc của bài 6.
  • Một trang lừa đảo có HTTPS: viet-sarl-login.com cũng xin được chứng chỉ miễn phí. Ổ khóa nghĩa là "kết nối được mã hóa tới đúng tên miền đang hiện", chứ không có nghĩa "trang này đáng tin".
  • Siêu dữ liệu: tên miền (SNI, DNS), địa chỉ IP, thời điểm và kích thước các gói tin.
  • Lỗi của ứng dụng: XSS, SQL injection hay phiên đăng nhập yếu đi qua kênh mã hóa vẫn y nguyên là lỗi.

Tóm lại

  • TLS cho xác thực, bí mật và toàn vẹn, bằng cách ghép thỏa thuận khóa, HKDF, chứng chỉ có chữ ký và AEAD.
  • TLS 1.3: một RTT, 5 bộ mã, không còn thuật toán cũ. viet.sarl bắt tay bằng TLS 1.3, AES-256-GCM, X25519MLKEM768 và chữ ký ECDSA P-256. TLS 1.1 bị từ chối.
  • Chuỗi chứng chỉ: trang → Let's Encrypt YE2 → Root YE → ISRG Root X2 (ký chéo bởi X1). Toàn đường cong elliptic, hiệu lực 90 ngày.
  • ACME tự động hóa chứng chỉ, Caddy làm hết. CT công khai mọi chứng chỉ được cấp.
  • Forward secrecy: khóa tạm cho từng kết nối, nên lộ khóa lâu dài không lộ dữ liệu quá khứ.
  • Trang này còn thiếu HSTS. Sau Cloudflare có hai đoạn TLS, và Cloudflare đọc được nội dung.

Bài tiếp theo chuyển từ dữ liệu đang đi sang dữ liệu đang nằm yên: mã hóa ổ đĩa, database và từng cột, envelope encryption với KMS, tìm kiếm trên dữ liệu đã mã hóa, và những gì mã hóa lúc lưu không chống được.