Featured image of post Chứng chỉ API & Chuỗi niềm tin (Chain of Trust): Mô hình tư duy chuẩn chỉ

Chứng chỉ API & Chuỗi niềm tin (Chain of Trust): Mô hình tư duy chuẩn chỉ

Dừng việc đoán mò với lỗi SSL. Hướng dẫn cấp độ chuyên gia về Chain of Trust, debug bằng openssl và cách chứng minh lỗi thuộc về ai.

Chẳng có gì làm cụt hứng anh em dev nhanh bằng cái lỗi SSLError.

Đang hì hục gọi API, bỗng dưng Python hét lên: [SSL: CERTIFICATE_VERIFY_FAILED]. Phản xạ đầu tiên thường là Google, tìm một câu trên StackOverflow và thêm ngay verify=False vào code.

Đừng làm thế. Bạn vừa mở toang cửa nhà cho trộm vào đấy.

Đây là hướng dẫn cấp độ chuyên gia (mastery guide) về xử lý chứng chỉ. Chúng ta sẽ không chỉ dừng ở việc “làm cho nó chạy”, mà sẽ hiểu sâu tại sao nó lỗi, cách debug bằng công cụ chuyên nghiệp, và làm sao để chứng minh với client rằng lỗi nằm ở server của họ chứ không phải code của bạn.


Phần 1: Nền tảng (Mô hình tư duy)

Trước khi debug, phải thống nhất cách hệ thống vận hành.

Chuỗi niềm tin (Chain of Trust)

Chứng chỉ TLS hoạt động theo hệ thống phân cấp sự tin tưởng:

1
2
3
4
5
6
7
ROOT CA TIN CẬY (Tòa án Tối cao)
INTERMEDIATE CA (Tòa án Địa phương)
LEAF CERTIFICATE (Căn cước công dân của API)

Máy tính chỉ tin ông Root CA. Nó tin ông Leaf vì ông Intermediate đã bảo lãnh, và ông Root đã bảo lãnh cho ông Intermediate.

Hãy tưởng tượng việc công chứng:

  • Root CA (Chính phủ): Hệ điều hành có sẵn danh sách các “ông lớn” (DigiCert, Let’s Encrypt). Đây là nguồn gốc niềm tin.
  • Intermediate CA (Văn phòng công chứng): Root CA quá lớn để đi ký cho từng web nhỏ. Họ ủy quyền cho Intermediate. Nếu Văn phòng có dấu của Chính phủ, bạn tin Văn phòng.
  • Leaf Certificate (Sổ đỏ): Chứng chỉ thực sự trên api.company.com. Chỉ có giá trị nếu được Văn phòng uy tín ký.
  • Custom CA (Thẻ nhân viên): Các công ty lớn tự tạo Root CA nội bộ. Vì không phải “hàng Chính phủ” (OS không biết), Python sẽ từ chối mặc định.

Quy tắc vàng: Khi server gửi chứng chỉ, nó phải gửi cả Leaf + Intermediate (gọi là “full chain”). Nếu chỉ gửi Leaf, chuỗi bị đứt gãy. Đây là nguyên nhân số 1 gây lỗi API.

Hậu cần: Key & Định dạng

Mấy file trong thư mục certs/ là cái gì?

Public Key vs. Private Key

  • Certificate (.crt/.pem): Cái Ổ khóa. Public. Đưa cho ai cũng được.
  • Private Key (.key): Cái Chìa khóa. Private. Tuyệt đối giữ kín. Mất chìa là vứt ổ khóa.

Mê hồn trận định dạng

  • .pem: Chuẩn phổ biến nhất. Văn bản mã hóa Base64. Bắt đầu bằng -----BEGIN CERTIFICATE-----.
  • .crt / .cer: Thường chỉ là file .pem đổi đuôi.
  • .der: Phiên bản nhị phân của .pem.
  • .p12 / .pfx: Cái “Vali”. Chứa cả Cert và Key, có mật khẩu. Dân Java/Windows hay dùng.

Chuyển đổi nhanh

1
2
3
# Tách cert & key từ file .p12 ra định dạng Python hiểu (.pem)
openssl pkcs12 -in bundle.p12 -clcerts -nokeys -out cert.pem
openssl pkcs12 -in bundle.p12 -nocerts -nodes -out key.pem

Phần 2: Điều tra (Công cụ tác nghiệp)

Đừng đoán mò. Khi API lỗi, hãy dùng những “kính lúp” này để soi “nội tạng” hệ thống.

1. “Cây búa vàng”: openssl s_client

Lệnh quan trọng nhất cuộc đời làm API. Nó cho bạn xem cuộc bắt tay (handshake) trực tiếp của server.

1
openssl s_client -connect api.yourcompany.com:443 -showcerts 2>/dev/null

Cách đọc kết quả:

1
2
3
4
5
6
7
8
9
Certificate chain
 0 s:CN = api.yourcompany.com                      # <-- Tầng 0: Leaf (Server)
   i:CN = DigiCert SHA2 Secure CA                  #     Được ký bởi Intermediate
 1 s:CN = DigiCert SHA2 Secure CA                  # <-- Tầng 1: Intermediate
   i:CN = DigiCert Global Root G2                  #     Được ký bởi Root
---
Server certificate
-----BEGIN CERTIFICATE-----
...

Phán quyết (Tìm dòng này ở cuối):

  • Verify return code: 0 (ok): ✅ Chuẩn.
  • Verify return code: 21 (unable to verify the first certificate): ❌ Đứt chuỗi (Broken Chain). Server gửi Leaf nhưng quên Intermediate.
  • Verify return code: 19 (self-signed certificate in certificate chain): ❌ CA lạ. Server dùng Custom CA mà máy bạn không tin.

2. “Kính chiếu yêu”: Soi file local

Có file .pem trong tay? Đừng cat nó. Hãy đọc cấu trúc nó.

1
openssl x509 -in certificate.pem -text -noout

Cần soi gì:

  • Validity: Kiểm tra Not Before / Not After. Hết hạn chưa?
  • Subject vs Issuer:
    • Nếu Subject == Issuer: Nó là Root CA (Tự ký).
    • Nếu Subject != Issuer: Nó là Intermediate hoặc Leaf.
  • SAN (Subject Alternative Name): Header này có chứa chính xác domain bạn đang gọi không? api.com khác với www.api.com nhé.

3. “Thanh tra tự động”: Script thu thập bằng chứng

Lưu file này tên ssl_audit.sh. Chạy nó để lấy bằng chứng đập vào mặt… à nhầm, gửi cho đối tác.

1
2
3
4
5
6
7
8
9
#!/bin/bash
HOST=$1
PORT=${2:-443}

echo "=== SSL Audit: $HOST:$PORT ==="
echo "1. VERIFICATION: $(openssl s_client -connect $HOST:$PORT 2>/dev/null | grep "Verify return code")"
echo "2. EXPIRY:       $(echo | openssl s_client -connect $HOST:$PORT 2>/dev/null | openssl x509 -noout -enddate)"
echo "3. CERT CHAIN:"
openssl s_client -connect $HOST:$PORT -showcerts 2>/dev/null | grep -E "s:|i:"

Phần 3: Chẩn đoán (Tại sao lỗi & Cách sửa)

Bí ẩn “Chạy trên máy tôi”

“Trên Chrome chạy ngon mà Python lỗi!”

  • Nguyên nhân: Trình duyệt có tính năng AIA Chasing. Nếu server thiếu Intermediate cert, Chrome tự đi tải về giúp. Python/Postman/cURL thì không.
  • Cách sửa: Bắt server cấu hình gửi fullchain.pem.

“Trên Ubuntu chạy ngon, lên Docker (Alpine) thì tạch!”

  • Nguyên nhân: Alpine Linux tối giản, thường không cài sẵn gói ca-certificates.
  • Cách sửa:
    1
    
    RUN apk add --no-cache ca-certificates && update-ca-certificates
    

Giải mã mã lỗi (Error Decoder)

LỗiChẩn đoánLỗi của ai?Hành động
CERTIFICATE_VERIFY_FAILEDChuỗi thiếu hoặc Root lạServer hoặc ClientCheck s_client. Nếu chain đủ -> Update Trust Store client.
certificate has expiredDate > Not AfterServerYêu cầu họ renew.
hostname mismatchDomain không nằm trong SANServerHọ cài nhầm cert của web khác.
unable to get local issuer certificateMáy thiếu Root CAClientpip install --upgrade certifi

Test nhanh: Tại họ hay tại mình? (3 Bước)

  1. curl có gọi được không? (Dùng Trust Store hệ điều hành)
    • curl -v https://api.client.com
  2. Python có gọi được không? (Dùng Trust Store của certifi)
    • python -c "import requests; print(requests.get('https://api.client.com'))"
  3. Có gọi được Google không? (Test mạng)
    • Nếu Google ngon mà Python lỗi khi gọi API kia => 99% lỗi do API bên kia.

Phần 4: Giải pháp (Hành động)

1. Email “Chuyên nghiệp”

Nếu Script ở Bước 2 báo lỗi đứt chuỗi (Verify return code: 21), hãy gửi email này:

“Dear Team, Chúng tôi gặp lỗi SSL verification khi kết nối api.yours.com. Diagnostic Output: Verify return code: 21 (unable to verify the first certificate)

Phân tích: Server đang gửi Leaf certificate nhưng thiếu Intermediate certificate. Các strict client (Python/Java) sẽ chặn kết nối này vì chuỗi niềm tin không toàn vẹn (broken chain).

Yêu cầu: Vui lòng cấu hình web server để trả về gói fullchain.pem (Leaf + Intermediate).”

2. “Sách nấu ăn” Python

Nếu lỗi do server dùng Private/Custom CA (hợp lệ), bạn cần dạy Python tin nó.

Cách A: Hardcode (Tường minh)

1
2
3
4
5
6
import requests

# Link tới file CA nội bộ
custom_ca_path = "/path/to/my_company_root_ca.pem"

requests.get("https://internal-api.com", verify=custom_ca_path)

Cách B: Biến môi trường (Global) Tuyệt vời cho Docker, không cần sửa code.

1
export REQUESTS_CA_BUNDLE="/path/to/my_company_root_ca.pem"

Cách C: mTLS (Cần chứng minh BẠN là ai)

1
2
3
# verify="ca.pem" -> Tin server
# cert=("me.crt", "me.key") -> Chứng minh mình
requests.get("https://mtls-api.com", verify="ca.pem", cert=("me.crt", "me.key"))

Tổng kết

  • Internet công cộng: Phải “tự chạy”. Nếu lỗi -> Server bên kia bị Broken Chain.
  • API nội bộ: Thường cần Custom CA (verify=/path/to/ca.pem).
  • Debug: Luôn bắt đầu bằng openssl s_client. Đừng đoán.
  • Trình duyệt biết tuốt: Đừng bao giờ lấy “Chrome chạy được” làm thước đo cho API.
Được tạo với sự lười biếng tình yêu 🦥

Subscribe to My Newsletter