Lập trình · 02/10/2026

HTTP Message Signatures thực chiến: Ký request đúng chuẩn RFC 9421

TLS bảo vệ kết nối hiện tại, nhưng không luôn trả lời được “request này do đúng đối tác tạo, có bị sửa qua nhiều hop và có đang bị phát lại hay không?”. HTTP Message Signatures chuẩn hóa cách ký một tập thành phần của HTTP message: method, target URI, authority, header, digest của body và metadata thời gian.

HTTP Message Signatures thực chiến: Ký request đúng chuẩn RFC 9421

TLS bảo vệ kết nối hiện tại, nhưng không luôn trả lời được “request này do đúng đối tác tạo, có bị sửa qua nhiều hop và có đang bị phát lại hay không?”. HTTP Message Signatures chuẩn hóa cách ký một tập thành phần của HTTP message: method, target URI, authority, header, digest của body và metadata thời gian.

RFC 9421 không phải một header HMAC đơn giản. Nó định nghĩa signature base, derived components, Structured Fields và cách ghép nhiều chữ ký. Body integrity thường kết hợp Content-Digest của RFC 9530. Bài này tập trung vào webhook, service-to-service API và request đi qua gateway/CDN.

1. Message signature bổ sung gì cho TLS?

Cơ chếBảo vệ chínhKhông tự giải quyết
TLSKênh truyền giữa hai peer ở một hopTính xác thực end-to-end qua nhiều terminator, replay ở tầng ứng dụng
Bearer tokenQuyền sở hữu tokenRàng buộc token với method, URI, header và body cụ thể
Content-DigestPhát hiện body khác với digestAi tạo digest; kẻ sửa body có thể tính lại digest nếu digest không được ký
HTTP Message SignatureXác thực các component đã chọnMã hóa nội dung, authorization nghiệp vụ, chống replay nếu thiếu policy

Chữ ký không thay TLS. TLS vẫn bảo vệ confidentiality, chống quan sát/chỉnh sửa trên hop và xác thực server. Message signature tạo thêm bằng chứng mật mã gắn vào semantics của request.

2. Một request đã ký trông như thế nào?

POST /webhooks/orders?region=apac HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Digest: sha-256=:mEkdbO7Srd9LIOegftO0aBX+VPTVz7/CSHes2Z27gc4=:
Signature-Input: sig1=("@method" "@target-uri" "content-type" "content-digest");created=1790907000;expires=1790907300;nonce="8fd2...";keyid="partner-2026-10"
Signature: sig1=:BASE64_SIGNATURE_BYTES:

{"title":"New Title"}

Signature-Input mô tả chữ ký nhãn sig1: danh sách component có thứ tự và tham số. Signature chứa byte chữ ký cùng nhãn. Thứ tự component là một phần của signature base; đổi thứ tự sẽ đổi chữ ký.

Giá trị trên chỉ minh họa hình dạng. Digest và signature thật phải tính trên đúng byte thực tế; không sao chép giá trị mẫu vào integration.

3. Chọn component theo threat model

Một baseline cho state-changing request thường phủ:

  • @method: ngăn biến POST thành GET/DELETE.
  • @target-uri hoặc tập derived component tương đương theo contract: ràng buộc đích.
  • content-type: ràng buộc cách diễn giải body.
  • content-digest: ràng buộc byte body với chữ ký.
  • Header nghiệp vụ quan trọng như tenant, webhook ID hoặc idempotency key khi chúng ảnh hưởng authorization/xử lý.

Không có danh sách đúng cho mọi API. Ký quá ít để lại component quan trọng có thể bị thay; ký mọi header khiến proxy thêm/sửa metadata hợp lệ cũng phá verification. Viết policy cho từng endpoint: component bắt buộc, được phép và bị cấm.

Verifier không được chấp nhận “bất kỳ chữ ký hợp lệ nào”. Nó phải xác nhận chữ ký đã phủ đúng các component policy yêu cầu.

4. Derived component không phải header thường

RFC 9421 định nghĩa các component suy ra như @method, @target-uri, @authority, @scheme, @path, @query và các biến thể query parameter. Chúng được lấy từ HTTP message context theo quy tắc chuẩn, không phải tự tạo header tên tương tự.

Đây là vùng dễ lỗi sau reverse proxy:

  • Client ký URI public HTTPS nhưng backend nhìn thấy HTTP nội bộ.
  • Gateway rewrite path hoặc bỏ prefix.
  • Authority public khác upstream host.
  • Query được parse rồi serialize lại, làm đổi thứ tự hoặc encoding.

Xác định verification diễn ra trước hay sau rewrite. Nếu gateway verify chữ ký biên, nó nên dùng request context client đã ký. Nếu backend verify, gateway cần truyền context gốc qua kênh tin cậy và backend không được tin header forwarded do client tự gửi.

5. Content-Digest ràng buộc body

Message signature không tự động ký body chỉ vì request có body. Cách phổ biến là:

  1. Tính digest trên actual message content bytes.
  2. Đặt kết quả vào Content-Digest theo Structured Fields.
  3. Đưa content-digest vào danh sách component được ký.
  4. Verifier tính lại digest từ raw body bytes và so sánh.
Content-Digest: sha-256=:BASE64_OF_SHA256_BYTES:

Hash trên byte, không trên object JSON đã parse. Hai JSON cùng nghĩa nhưng khác whitespace, thứ tự key hoặc newline có digest khác. Signer phải serialize body một lần, giữ nguyên byte đó để hash và gửi; verifier cần đọc raw body trước middleware biến đổi.

Content-Digest áp dụng cho message content, trong khi Repr-Digest mô tả toàn selected representation. Với request API có body cụ thể, hãy chọn field theo semantics và ghi rõ trong profile, không dùng hai tên thay thế tùy ý.

6. Canonicalization: đừng nối chuỗi thủ công

RFC dùng HTTP Structured Fields và thuật toán xây signature base chính xác. Các khác biệt nhỏ đều làm verification thất bại:

  • Tên field, whitespace và cách kết hợp nhiều field value.
  • Thứ tự component trong Signature-Input.
  • Quoted string, byte sequence và parameter serialization.
  • Percent-encoding, query và authority.
  • Header xuất hiện nhiều lần hoặc bị intermediary hợp nhất.

Ưu tiên thư viện triển khai RFC 9421 và có test vector. Không thiết kế format method + path + body riêng rồi gọi đó là tương thích RFC. Khi dùng nhiều ngôn ngữ, chạy cross-language fixtures để chứng minh cùng request tạo cùng signature base.

7. Quy trình ký an toàn

// Pseudocode: dùng thư viện RFC 9421, không tự canonicalize
bodyBytes = utf8Encode(jsonSerializeOnce(payload))
headers["Content-Type"] = "application/json"
headers["Content-Digest"] = structuredDigest("sha-256", sha256(bodyBytes))

signature = signer.sign({
  method: "POST",
  targetUri: "https://api.example.com/webhooks/orders?region=apac",
  headers,
  body: bodyBytes,
  coveredComponents: [
    "@method", "@target-uri", "content-type", "content-digest"
  ],
  parameters: {
    created: nowUnix(),
    expires: nowUnix() + 300,
    nonce: randomNonce(),
    keyid: "partner-2026-10"
  }
})

sendExactBytes(bodyBytes, headers, signature)

Đừng để HTTP client tự serialize object lần hai sau khi đã tính digest. Nếu library thêm charset, đổi content encoding hoặc redirect sang URI khác, signature có thể không còn đúng. Tắt redirect tự động cho signed state-changing request hoặc ký lại đích mới sau khi policy cho phép.

8. Quy trình verify theo thứ tự fail-closed

  1. Giới hạn kích thước headers/body trước thao tác mật mã tốn CPU.
  2. Parse Signature-Input, Signature và digest bằng parser Structured Fields nghiêm ngặt.
  3. Chọn đúng label/profile; xác nhận đủ component bắt buộc, không chấp nhận downgrade.
  4. Kiểm tra created, expires, clock skew và lifetime tối đa.
  5. Resolve keyid từ registry tin cậy và khóa algorithm theo policy.
  6. Tính lại Content-Digest trên raw bytes.
  7. Xây signature base theo RFC và verify bằng crypto library chuẩn.
  8. Consume nonce/idempotency marker atomically để chặn replay.
  9. Sau cùng mới authorize signer cho tenant, route và operation cụ thể.

Signature hợp lệ chỉ chứng minh holder của private/secret key tạo dữ liệu đã ký. Nó không tự cấp quyền hoàn tiền, sửa tenant khác hay gọi mọi endpoint.

9. Chống replay cần state hoặc ràng buộc mạnh

Kẻ tấn công không cần sửa request nếu có thể gửi lại nguyên request hợp lệ. created và expires thu hẹp cửa sổ nhưng vẫn cho phép replay trong cửa sổ đó. Với thao tác nhạy cảm:

  • Yêu cầu nonce đủ ngẫu nhiên và duy nhất theo signer.
  • Lưu (keyid, nonce) đến hết thời gian chấp nhận bằng atomic insert/set-if-absent.
  • Kết hợp idempotency key nghiệp vụ cho retry hợp lệ.
  • Giới hạn clock skew, signature lifetime và số request theo key.
  • Không đánh dấu nonce đã dùng trước khi signature cơ bản hợp lệ, để tránh attacker làm đầy store bằng dữ liệu rác; vẫn phải rate-limit trước crypto.

Webhook delivery ID có thể vừa hỗ trợ dedupe nghiệp vụ vừa được ký, nhưng không nên thay nonce nếu ID có thể đoán và bị tái sử dụng ngoài contract.

10. keyid, algorithm và rotation

keyid là định danh để tìm key, không phải bí mật và không tự chứng minh key đáng tin. Verifier cần registry ánh xạ partner/tenant → key ID → public key/secret, algorithm cho phép, trạng thái và thời hạn.

  • Không để parameter alg do request gửi tự override policy của key.
  • Dùng algorithm được implementation/profile hỗ trợ; tránh tự tạo algorithm name.
  • Private key nằm trong secret manager/HSM/KMS phù hợp, không trong source hoặc log.
  • Khi rotate, publish key mới trước, signer chuyển sang key mới, verifier chấp nhận overlap có giới hạn rồi thu hồi key cũ.
  • Cache key discovery có TTL và cơ chế refresh khi gặp key mới; chống SSRF nếu key URL đến từ input.

Revocation không thu hồi các request đã chấp nhận. Audit log nên ghi key ID, signature label, created time và kết quả policy mà không ghi private material hay toàn payload nhạy cảm.

11. Intermediary và hop-by-hop transformation

Gateway, CDN và service mesh có thể thay header, giải nén body, normalize path hoặc terminate TLS. Chỉ ký component được bảo toàn qua đường dự kiến hoặc verify trước nơi biến đổi.

Ba mô hình phổ biến:

  • End-to-end: backend verify chữ ký client; mọi intermediary phải giữ nguyên component được ký.
  • Edge verification: gateway verify rồi phát assertion nội bộ được bảo vệ bằng mTLS/chữ ký riêng.
  • Signature chaining: nhiều label/chữ ký cho các trust domain khác nhau; phức tạp hơn và cần profile rõ.

Không xóa chữ ký client rồi chỉ thêm header X-Verified: true trên mạng nội bộ không được bảo vệ; client có thể tự gửi header đó nếu gateway không strip và tái tạo có kiểm soát.

12. Nhiều chữ ký và nhiều label

Signature-Input và Signature là dictionary nên một message có thể mang nhiều signature label. Điều này hữu ích khi client và gateway cùng ký hoặc khi chuyển key/algorithm.

Verifier phải biết label nào đáp ứng profile. Policy “chấp nhận nếu có một label verify thành công” dễ bị downgrade sang chữ ký thử nghiệm phủ ít component. Hãy ràng buộc label/tag, signer role, algorithm và component set mong đợi.

13. Lỗi triển khai thường gặp

  • Ký Content-Digest nhưng không tính lại digest từ body nhận được.
  • Có digest nhưng không đưa field digest vào component được ký.
  • Parse JSON rồi serialize lại trước khi hash.
  • Ký path nội bộ trong khi client dùng public target URI.
  • Cho phép signature quá cũ, không có expires/nonce hoặc replay cache không atomic.
  • Tin algorithm/key URL do request chỉ định mà không đối chiếu registry.
  • Verify thành công rồi bỏ qua bước authorization theo tenant/operation.
  • Log toàn Signature/base/body chứa dữ liệu nhạy cảm.
  • Tự viết canonicalization không có test vector liên ngôn ngữ.

14. Bộ test bắt buộc

  • Golden vectors cho signature base và chữ ký từ implementation độc lập.
  • Đổi method, URI, query, authority, từng header và từng byte body phải fail khi component đó được phủ.
  • Header lặp, whitespace, Unicode, percent-encoding và query parameter trùng.
  • created tương lai/quá cũ, expires hết hạn, lifetime quá dài và clock skew biên.
  • Nonce mới, nonce lặp đồng thời và retry hợp lệ với idempotency key.
  • Unknown/revoked key, algorithm không cho phép và key rotation overlap.
  • Proxy rewrite, forwarded headers giả, content encoding và redirect.
  • Body/header quá lớn, malformed Structured Fields và nhiều signature label nhằm làm cạn CPU.

Test negative quan trọng hơn happy path: mục tiêu là chứng minh mọi thay đổi ngoài policy đều bị từ chối với mã lỗi không làm lộ thông tin key/verification.

15. Checklist rollout production

  • Endpoint có signature profile ghi rõ component, label/tag, algorithm và lifetime.
  • Body raw bytes được giữ nguyên; Content-Digest được tính lại và được ký.
  • Verifier dùng thư viện RFC 9421/Structured Fields đã kiểm thử.
  • Public URI và proxy rewrite có trust boundary rõ.
  • Key registry ràng buộc key ID với signer, tenant, algorithm và trạng thái.
  • Replay cache atomic, có TTL và capacity/rate limit.
  • Verification tách biệt authorization và idempotency nghiệp vụ.
  • Rotation, revocation, clock skew và failure response đã diễn tập.
  • Metrics theo verify result, key ID, replay, clock error và latency; log đã giảm nhạy cảm.
  • TLS/mTLS vẫn bật; signature không được xem là mã hóa.

HTTP Message Signatures đáng tin cậy khi cả hai bên ký cùng một HTTP meaning, không chỉ cùng một chuỗi gần giống nhau. Thành công nằm ở profile chặt chẽ: component nào bắt buộc, raw body nào được hash, URI nào được nhìn thấy, key nào được tin và request nào được coi là replay. Hãy triển khai bằng thư viện chuẩn, test vector liên ngôn ngữ và rollout quan sát được.

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.