diff options
Diffstat (limited to 'docs/research')
| -rw-r--r-- | docs/research/088-sealed-sessions.md | 83 |
1 files changed, 83 insertions, 0 deletions
diff --git a/docs/research/088-sealed-sessions.md b/docs/research/088-sealed-sessions.md new file mode 100644 index 0000000..4f9bda2 --- /dev/null +++ b/docs/research/088-sealed-sessions.md @@ -0,0 +1,83 @@ +# 088 — 봉인한 세션과 보낸 이: 키 합의는 세션에 한 번, 출처는 키에 묶어 + +2026-09-27. [086](086-sealed-sends.md)의 "다음" 중 둘. 봉인한 전달이 봉인하지 않은 전달의 절반 속도였던 것(요청마다 X25519 +세 번)과, base 모드라서 `X-Pure-Source`가 인증되지 않던 것. + +착수 기준: + +- 바꿀 결정: 요청마다 캡슐화할지, 전달선마다 세션을 하나 두고 그 안에서 번호로 봉인할지(RFC 9180의 여러 번 봉인). 보낸 이는 + HPKE auth 모드로 인증할지(보내는 노드의 키가 봉인에 들어간다), 서명을 따로 붙일지. +- 최소 확인: 받는 쪽이 다시 시작해도 따로 묻지 않고 세션을 여는지, 번호나 보낸 이 키를 바꾸면 열리지 않는지, 이름 붙은 보낸 이의 + 이름을 다른 키로 쓰는 노드가 거절되는지 검사로 지킨다. 전달 속도를 요청마다 캡슐화하던 빌드와 번갈아 잰다. +- 중단 기준: 봉인하지 않은 전달이 느려지거나, 한 번 봉인한 것이 다른 번호로 열리면 되돌린다. + +## 한 것 + +**세션.** 보내는 쪽은 전달선마다 HPKE 문맥을 하나 두고, 첫 봉인 때 한 번 캡슐화한다. 그 뒤 요청은 문맥의 번호로 봉인한다(넌스는 +기본 넌스와 번호의 XOR). 번호는 OpenSSL의 보내는 쪽 문맥이 스스로 올리므로 같은 넌스가 두 번 쓰이지 않는다. 봉인이 실패하면 +다음 시도는 새 세션을 만든다. 프로세스가 다시 시작하면 새 세션이다. 헤더가 세션과 번호를 싣는다. + +``` +X-Pure-Seal: x25519-sha256-aes128gcm <캡슐화한 키 64 hex> <번호> [<보낸 이의 공개키 64 hex>] +``` + +본문은 이제 암호문과 태그 16 B뿐이다(캡슐화한 키는 헤더로 갔다). 받는 쪽의 연결 스레드는 연 세션을 16개까지 기억하고, 가장 덜 +쓴 것부터 바꾼다. 세션마다 캡슐화를 한 번 풀고(X25519 한두 번), 요청마다 번호를 정해(`OSSL_HPKE_CTX_set_seq`, 받는 쪽만 할 +수 있다) 연다. 캡슐화한 키가 요청마다 오므로 받는 쪽이 다시 시작하거나 요청이 다른 스레드로 가도 묻지 않고 연다. 봉인은 여전히 +요청의 출처·첫 번호·개수에 묶인다(AAD). + +**보낸 이.** 보내는 노드에 `--key`가 있으면 auth 모드로 봉인한다. 그 노드의 키가 키 합의에 들어가므로, 헤더에 적은 보낸 이의 +공개키에 맞는 개인키를 가진 쪽만 받는 쪽이 여는 봉인을 만들 수 있다. 받는 노드는 `--sender NAME=KEY`로 보낸 이를 이름과 키로 +적을 수 있다. 적으면 그 노드는 적힌 보낸 이의 전달만 받는다. 출처가 적힌 이름이고, auth 모드이며, 헤더의 키가 적힌 키와 같아야 +한다. 아니면 403 `sender`다. 헤더의 키가 거짓이면 봉인이 열리지 않는다(400 `seal`). + +| 요청이 | 답 | +|---|---| +| 봉인이 열린다 | 200, 번호마다 한 번 적용 | +| 한 바이트, 번호, 보낸 이의 키, 출처·번호·개수 중 무엇이든 바뀌었다 | 400 `seal`, 적용 없음 | +| 봉인이 없다(`--sealed-only`나 `--sender`인 노드) | 403 `unsealed` | +| 적힌 보낸 이가 아니거나 그 키가 아니다(`--sender`인 노드) | 403 `sender` | + +**검사(`server_sealed`).** 086의 것에 더해 이것들을 본다. + +- 보내는 쪽이 제 키로 봉인한다. 한 전달선의 요청(53–57개)이 모두 한 세션이고, 번호가 0부터 하나씩 오른다. +- 번호를 바꾼 요청과 보낸 이의 키를 바꾼 요청은 400 `seal`이다. +- 키 없이(base 모드) 다른 노드의 키로 봉인한 요청은 400 `seal`이다. +- 받는 노드를 `--sender 출처=보낸이키`로 다시 띄우면, 보내는 쪽의 다음 주문이 같은 세션으로 가서 열리고 적용된다. +- 같은 모듈이라 같은 출처 이름을 쓰는 다른 노드가 제 키로 봉인하면 403 `sender`이고 아무것도 적용되지 않는다. + +여섯 번 연달아, 그리고 `make check` 전체에서 통과한다. + +## 장치가 말한 것 + +086과 같은 방법이다. 받는 서버를 멈춘 채 보내기 20,000개를 쌓고 푼다. 세 라운드이고 변형의 순서는 돌린다. 봉인은 보내는 쪽도 +제 키로 한다(auth 모드). 기준은 요청마다 캡슐화하던 빌드(`bdb5f10`)의 봉인이다 +([원시 결과](../../research/experiments/014-sealed-sends/2026-09-27-sessions-linux-aarch64.jsonl)). + +| | 보내기/s | +|---|---:| +| 봉인 없음 | 462.2–470.2k | +| 봉인, 세션과 보낸 이 인증(지금) | **461.2–463.7k** | +| 봉인, 요청마다 캡슐화(전) | 207.3–233.7k | + +봉인의 비용이 절반에서 1% 안쪽이 됐다. 요청마다 들던 X25519 세 번(약 0.5 ms)이 세션에 한 번(auth 모드는 보내는 쪽 셋, 받는 +쪽 둘)이 되고, 요청마다 남은 것은 AES-GCM이다(약 10 KiB 묶음에 양쪽 합쳐 약 9 µs). + +## 성질과 한계 + +- **넌스.** 보내는 쪽 문맥의 번호는 OpenSSL이 올리고 되돌릴 수 없다. 세션은 프로세스 안에서만 살고, 실패하면 버린다. 그래서 + 한 키와 넌스로 두 평문을 봉인하는 일이 없다. +- **재전송.** 기록한 요청을 다시 보내면 열린다(같은 세션, 같은 번호). 그러나 출처와 번호로 한 번만 적용된다(083의 성질). + 다른 번호나 다른 출처로 옮기면 열리지 않는다. +- **보낸 이 인증의 한계.** auth 모드는 키 누설 사칭(KCI)에 약하다(RFC 9180 9.1.1). 받는 노드의 개인키를 훔친 쪽은 그 노드에게 + 아무 보낸 이로나 봉인할 수 있다. 받는 노드의 키는 그 노드가 받는 모든 것을 여는 키이기도 하므로, 그것을 지키는 것이 먼저다. +- **전방 비밀성.** 아직 없다. 받는 노드의 키를 훔치면 기록해 둔 세션이 열린다. 받는 키를 돌려 쓰는 것(서명한 사전 키)이나 + 메신저의 래칫이 다음이다. +- **비용 공격.** 모르는 캡슐화 키를 단 요청은 열 때마다 캡슐화를 한 번 푼다(X25519, 약 0.16 ms). 열리지 않는 세션은 기억하지 + 않는다. 출처별 속도 제한은 남은 일이다. +- **메타데이터.** 출처, 번호, 개수, 경로, 크기, 그리고 이제 보낸 이의 공개키가 보인다. 중계는 누가 누구에게 보내는지 안다. + +## 다음 + +- 받는 키 돌리기(서명한 사전 키)로 전방 비밀성을 세션 단위로. +- 메신저: 사용자마다 한 노드, 연락처는 `--sender` 목록, 화면은 Vega. |
