ADR-0003. Mã hoá payload ECDH + AES-256-GCM bắt buộc trên /stream
| Trường | Giá trị |
|---|---|
| Status | Accepted |
| Date | 2026-03-28 |
| Deciders | signal-team |
| Supersedes | — |
Bối cảnh
- TLS kết thúc tại Traefik; sau nó, traffic băng qua network nội bộ và một bus Redis dùng chung.
- Payload WebSocket mang dữ liệu order/payment không nên đọc được ở bus hay bởi kết nối co-tenant khác.
- Chúng ta muốn cô lập khoá theo từng kết nối và forward secrecy mà không cần một service quản lý khoá.
Quyết định
Chúng ta sẽ yêu cầu mã hoá payload đầu-cuối cho mọi kết nối /stream (requireEncryption: true):
- Trong handshake client gửi
clientPublicKey; server sinh một cặp khoá ECDH P-256 ephemeral, derive một khoá AES-256 qua HKDF-SHA-256, và lưu nó trong một map_clientAesKeysin-memory khoá theo client id. - Server trả
{ serverPublicKey, salt }bên trong sự kiệnconnectedđể client derive cùng khoá. - Mọi message tiếp theo là AES-256-GCM
{ iv, ct }. Chỉconnectedvàerrorgửi plaintext. - Khoá xoá khi disconnect và mất khi restart (client reconnect để re-derive).
Hệ quả
| Ưu | Nhược |
|---|---|
| Payload mờ trên dây, bus, và với kết nối khác | Không lưu — restart ép mọi client reconnect |
| Forward secrecy (khoá ephemeral mỗi kết nối) | clientPublicKey bắt buộc — client không ECDH bị từ chối |
| Không cần KMS bên ngoài | Map khoá in-memory giới hạn một process; dựa vào sticky socket + Redis để scale |
Phương án đã cân nhắc
| Phương án | Ưu | Nhược | Vì sao loại |
|---|---|---|---|
| Chỉ TLS (không mã hoá payload) | Đơn giản nhất | Plaintext sau Traefik + trên bus Redis | Không đủ cho dữ liệu payment |
| Khoá AES tĩnh dùng chung | Không handshake | Không forward secrecy; một rò làm lộ tất cả | Bán kính ảnh hưởng không chấp nhận được |
| mTLS theo từng client | Định danh mạnh | Vòng đời cert nặng trên client POS/Tauri | Vận hành bất khả thi cho đội client |
Tham chiếu
signal/src/components/websocket.component.ts(ECDH, handshake, transformer outbound, handler message)- Encryption
- Operations — bảo mật