Skip to content

Commit 0782ed8

Browse files
authored
Merge pull request #246 from NLipatov/feature/noise-handshake
Noise IK handshake
2 parents 6fd0e7d + fcbcfd8 commit 0782ed8

File tree

292 files changed

+24274
-6390
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

292 files changed

+24274
-6390
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ on:
1212
- 'src/**' # Only if src directory files changed
1313

1414
env:
15-
GO_VERSION: '1.25.0'
15+
GO_VERSION: '1.26.0'
1616
GOTOOLCHAIN: 'local'
1717

1818
jobs:

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
workflow_dispatch:
55

66
env:
7-
GO_VERSION: '1.25.0'
7+
GO_VERSION: '1.26.0'
88
GOTOOLCHAIN: 'local'
99

1010
jobs:

ReadMe.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
- 🧩 **Easy to deploy & configure**.
1818
- 🚀 **High Performance** — near-zero allocations on the hot path (benchmarked).
1919
- 📦 **Tiny Memory Footprint** — ≈5–15 MB **RSS** under load, ≈5–8 MB idle.
20-
- 🔒 **End-to-End Encryption** — X25519 (Curve25519 ECDH) for key agreement; ChaCha20-Poly1305 (AEAD) for traffic encryption; Ed25519 for authentication.
20+
- 🔒 **End-to-End Encryption**Noise IK handshake for mutual authentication; X25519 (Curve25519 ECDH) for key agreement; ChaCha20-Poly1305 (AEAD) for traffic encryption.
2121
-**Built from Scratch** — no legacy, no bloat. Clean, readable Go code.
2222
- 🌐 **IoT & Embedded Ready** — optimized for small devices and constrained environments.
2323
- 🛡️ **Open Source** — AGPL-3.0-only; commercial licenses available.

docs/TunGo/docs/2. introduction to TunGo.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ sidebar_position: 1
1414
- 🧩 **Easy to deploy & configure**.
1515
- 🚀 **High Performance** — near-zero allocations on the hot path (benchmarked).
1616
- 📦 **Tiny Memory Footprint** — ≈5–15 MB **RSS** under load, ≈5–8 MB idle.
17-
- 🔒 **End-to-End Encryption** — X25519 (Curve25519 ECDH) for key agreement; ChaCha20-Poly1305 (AEAD) for traffic encryption; Ed25519 for authentication.
17+
- 🔒 **End-to-End Encryption**Noise IK handshake for mutual authentication; X25519 (Curve25519 ECDH) for key agreement; ChaCha20-Poly1305 (AEAD) for traffic encryption.
1818
-**Built from Scratch** — no legacy, no bloat. Clean, readable Go code.
1919
- 🌐 **IoT & Embedded Ready** — optimized for small devices and constrained environments.
2020
- 🛡️ **Open Source** — AGPL-3.0-only; commercial licenses available.
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
# Handshake and Rekeying Protocol
2+
3+
**Status:** Living document
4+
**Last updated:** 2026-02-17
5+
6+
## Overview
7+
8+
TunGo uses a **Noise IK** handshake for mutual authentication and key agreement, followed by periodic **X25519 + HKDF-SHA256** rekeying. Transport encryption uses **ChaCha20-Poly1305** AEAD with epoch-based nonce management.
9+
10+
**Cipher suite:** X25519 / ChaChaPoly / SHA-256
11+
**Protocol ID:** `"TunGo"`, version `0x01`
12+
13+
---
14+
15+
## 1. Handshake (Noise IK)
16+
17+
Noise IK assumes the initiator (client) already knows the responder's (server) static public key.
18+
19+
### 1.1 Message Flow
20+
21+
```
22+
Client Server
23+
│ │
24+
│─── MSG1: (e, es, s, ss) + MAC1 + MAC2 ───────>│
25+
│ │
26+
│<── COOKIE REPLY (optional, under load) ───────│
27+
│ │
28+
│─── MSG1 (retry with cookie) ─────────────────>│
29+
│ │
30+
│<── MSG2: (e, ee, se) ─────────────────────────│
31+
│ │
32+
├═══ Transport keys established ════════════════╡
33+
```
34+
35+
### 1.2 MSG1 (Client -> Server)
36+
37+
Wire format:
38+
39+
```
40+
[1B version] [>=80B noise_payload] [16B MAC1] [16B MAC2]
41+
```
42+
43+
- **version:** `0x01`
44+
- **noise_payload:** Noise IK first message — client ephemeral public (32B, plaintext) + encrypted client static (48B)
45+
- **MAC1:** Stateless authentication (always verified)
46+
- **MAC2:** Cookie-based authentication (verified only under load)
47+
48+
Minimum size: 113 bytes.
49+
50+
### 1.3 MSG2 (Server -> Client)
51+
52+
Noise IK second message. No MACs — bidirectional authentication is implicit after Noise completes.
53+
54+
After MSG2, both sides derive:
55+
- `c2sKey` (32 bytes) — client-to-server transport key
56+
- `s2cKey` (32 bytes) — server-to-client transport key
57+
- `sessionId` (32 bytes) — from Noise channel binding
58+
59+
### 1.4 Server Verification Order
60+
61+
```
62+
1. CheckVersion() — reject unknown protocol versions
63+
2. VerifyMAC1() — stateless, before any DH or allocation
64+
3. VerifyMAC2() — only under load (LoadMonitor)
65+
4. Noise handshake — DH computations, peer lookup
66+
5. Peer ACL check — AllowedPeers / PeerDisabled
67+
```
68+
69+
All failures return a uniform `ErrHandshakeFailed` to prevent information leakage.
70+
71+
---
72+
73+
## 2. DoS Protection
74+
75+
### 2.1 MAC1 (Stateless, Always Required)
76+
77+
```
78+
key = BLAKE2s-256("mac1" || "TunGo" || 0x01 || server_pubkey)
79+
MAC1 = BLAKE2s-128(key, noise_msg1)
80+
```
81+
82+
Verified before any state allocation or DH computation.
83+
84+
### 2.2 MAC2 (Stateful, Under Load)
85+
86+
```
87+
key = BLAKE2s-256("mac2" || "TunGo" || 0x01 || cookie_value)
88+
MAC2 = BLAKE2s-128(key, noise_msg1 || MAC1)
89+
```
90+
91+
Checked only when `LoadMonitor` detects pressure.
92+
93+
### 2.3 Cookie Mechanism
94+
95+
**Cookie value** (IP-bound, time-bucketed):
96+
```
97+
bucket = unix_seconds / 120
98+
cookie = BLAKE2s-128(server_secret[32], client_ip[16] || bucket[2])
99+
```
100+
101+
Valid for current and previous bucket (handles transitions).
102+
103+
**Cookie reply** (encrypted, 56 bytes):
104+
```
105+
[24B nonce] [16B encrypted_cookie] [16B poly1305_tag]
106+
```
107+
108+
Encryption:
109+
```
110+
key = BLAKE2s-256("cookie" || "TunGo" || 0x01 || server_pubkey || client_ephemeral)
111+
ciphertext = XChaCha20-Poly1305.Seal(key, nonce, cookie, aad=client_ephemeral)
112+
```
113+
114+
---
115+
116+
## 3. Transport Encryption
117+
118+
### 3.1 AEAD
119+
120+
ChaCha20-Poly1305 with 60-byte AAD:
121+
122+
```
123+
AAD [60 bytes]:
124+
[ 0..31] sessionId (32 bytes)
125+
[32..47] direction (16 bytes: "client-to-server" or "server-to-client")
126+
[48..59] nonce (12 bytes)
127+
```
128+
129+
SessionId and direction are pre-filled at session creation. Only the nonce is updated per packet.
130+
131+
### 3.2 Nonce Structure (12 bytes)
132+
133+
```
134+
[0..7] counterLow (uint64, big-endian)
135+
[8..9] counterHigh (uint16, big-endian)
136+
[10..11] epoch (uint16, big-endian)
137+
```
138+
139+
- **Counter:** 80-bit monotonic (2^80 messages per epoch). Overflow returns error.
140+
- **Epoch:** Immutable per session, identifies rekeying generation.
141+
142+
### 3.3 TCP Transport
143+
144+
```
145+
Wire frame: [2B epoch] [ciphertext + 16B tag]
146+
```
147+
148+
- Dual-epoch: current + previous session coexist during rekey.
149+
- Auto-cleanup: previous session zeroed on first current-epoch decryption (TCP ordering guarantee).
150+
- No replay protection (TCP provides ordering).
151+
152+
### 3.4 UDP Transport
153+
154+
```
155+
Wire frame: [8B route-id] [12B nonce] [ciphertext + 16B tag]
156+
```
157+
158+
- Route-id is derived from `sessionId` (first 8 bytes, big-endian) and enables O(1) session lookup.
159+
- Epoch embedded in nonce bytes 10..11.
160+
- **Replay protection:** 1024-bit sliding window bitmap per epoch.
161+
- Tentative check before decryption (Check).
162+
- Committed only after AEAD authentication succeeds (Accept).
163+
- Prevents window poisoning by invalid packets.
164+
- **Epoch ring:** Fixed-capacity FIFO of sessions. Evicted sessions are zeroed.
165+
166+
---
167+
168+
## 4. Rekeying
169+
170+
### 4.1 Key Derivation
171+
172+
Both sides perform X25519 ECDH, then derive new transport keys via HKDF-SHA256:
173+
174+
```
175+
shared = X25519(local_private, remote_public)
176+
newC2S = HKDF-SHA256(ikm=shared, salt=currentC2S, info="tungo-rekey-c2s")
177+
newS2C = HKDF-SHA256(ikm=shared, salt=currentS2C, info="tungo-rekey-s2c")
178+
```
179+
180+
Current keys serve as HKDF salt, providing forward secrecy chaining.
181+
182+
### 4.2 Control Plane Packets
183+
184+
```
185+
RekeyInit: [0xFF] [0x01] [0x02] [32B X25519 public key] (35 bytes)
186+
RekeyAck: [0xFF] [0x01] [0x03] [32B X25519 public key] (35 bytes)
187+
```
188+
189+
### 4.3 Rekey FSM
190+
191+
```
192+
StartRekey installPending
193+
Stable ──────────> Rekeying ──────────────> Pending
194+
^ │
195+
│ ActivateSendEpoch │
196+
└───────────────────────────────────────────┘
197+
^ │
198+
│ AbortPendingIfExpired (5s) │
199+
└───────────────────────────────────────────┘
200+
```
201+
202+
| State | Description |
203+
|-------|-------------|
204+
| **Stable** | Normal operation. One active send epoch. |
205+
| **Rekeying** | StartRekey called, new keys computed, new epoch installed for receive. |
206+
| **Pending** | Awaiting peer confirmation (first successful decryption with new epoch). |
207+
208+
### 4.4 Rekey Flow
209+
210+
```
211+
Client Server
212+
│ │
213+
│── RekeyInit (client X25519 pub) ────────>│
214+
│ │ derive newC2S, newS2C
215+
│ │ install new epoch (recv)
216+
│<── RekeyAck (server X25519 pub) ─────────│
217+
│ │
218+
│ derive newC2S, newS2C │
219+
│ install new epoch (recv + send) │
220+
│ │
221+
│── first packet with new epoch ──────────>│
222+
│ │ peer confirmed → activate send
223+
│<── first packet with new epoch ──────────│
224+
│ │
225+
├═══ Both sides on new epoch ══════════════╡
226+
```
227+
228+
### 4.5 Safety Invariants
229+
230+
- Only one in-flight rekey at a time.
231+
- Epochs monotonically increase. Max safe epoch: 65000 (of 65535). Beyond this, `ErrEpochExhausted` forces re-handshake.
232+
- Send epoch never decreases.
233+
- Pending keys never overwrite active keys until peer proves possession (via successful decryption).
234+
- Pending rekey auto-aborts after 5 seconds if no peer confirmation.
235+
- Default rekey interval: 120 seconds.
236+
237+
---
238+
239+
## 5. Key Zeroization
240+
241+
| Material | When Zeroed |
242+
|----------|-------------|
243+
| Ephemeral DH private keys | Immediately after DH computation (`defer mem.ZeroBytes`) |
244+
| Shared secrets (rekey) | Immediately after key derivation (`defer mem.ZeroBytes`) |
245+
| Pending rekey keys (FSM) | On abort or promotion to active |
246+
| Previous session keys | On first current-epoch decryption (TCP) or epoch eviction (UDP) |
247+
| Nonce replay window | On session teardown (`SlidingWindow.Zeroize`) |
248+
| AAD buffers | On session teardown (`DefaultUdpSession.Zeroize`) |
249+
250+
**Limitation:** Go GC may copy heap objects before zeroing. `mem.ZeroBytes` is best-effort defense against memory forensics, verified by compiler output analysis to not be optimized away (Go 1.26.x, all target platforms).
251+
252+
---
253+
254+
## 6. Constants
255+
256+
| Constant | Value | Purpose |
257+
|----------|-------|---------|
258+
| Protocol version | `0x01` | Wire format versioning |
259+
| MAC1 / MAC2 size | 16 bytes | BLAKE2s-128 output |
260+
| Cookie bucket | 120 seconds | IP-bound cookie validity window |
261+
| Cookie reply size | 56 bytes | nonce (24) + encrypted cookie (16) + tag (16) |
262+
| AAD length | 60 bytes | sessionId (32) + direction (16) + nonce (12) |
263+
| UDP route-id | 8 bytes | session identifier prefix for O(1) peer lookup |
264+
| Nonce counter | 80 bits | Messages per epoch before overflow |
265+
| Replay window | 1024 bits | UDP out-of-order tolerance |
266+
| Epoch capacity | uint16 | 65535 values, safe threshold 65000 |
267+
| Rekey interval | 120 seconds | Default periodic rekey trigger |
268+
| Pending timeout | 5 seconds | Auto-abort unconfirmed rekey |

docs/TunGo/i18n/ar/code.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"message": "آمن"
2525
},
2626
"feature.secure.description": {
27-
"message": "نفق من طرف إلى طرف مع تشفير ChaCha20"
27+
"message": "مصافحة Noise IK، اتفاق مفاتيح X25519، تشفير AEAD ChaCha20-Poly1305"
2828
},
2929
"feature.multiTransport.title": {
3030
"message": "دعم النقل المتعدد"

docs/TunGo/i18n/ar/docusaurus-plugin-content-docs/current/2. introduction to TunGo.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ sidebar_position: 1
1414
- 🧩 **سهل النشر والإعداد**.
1515
- 🚀 **أداء عالي** — تخصيصات ذاكرة شبه معدومة في المسار الساخن (تم قياسها).
1616
- 📦 **بصمة ذاكرة صغيرة** — ≈5–15 ميجابايت **RSS** تحت الحمل، ≈5–8 ميجابايت في وضع الخمول.
17-
- 🔒 **تشفير من طرف إلى طرف** — X25519 (Curve25519 ECDH) لاتفاق المفاتيح؛ ChaCha20-Poly1305 (AEAD) لتشفير حركة المرور؛ Ed25519 للمصادقة.
17+
- 🔒 **تشفير من طرف إلى طرف**مصافحة Noise IK للمصادقة المتبادلة؛ X25519 (Curve25519 ECDH) لاتفاق المفاتيح؛ ChaCha20-Poly1305 (AEAD) لتشفير حركة المرور.
1818
-**مبني من الصفر** — بدون كود قديم، بدون تضخم. كود Go نظيف وقابل للقراءة.
1919
- 🌐 **جاهز لإنترنت الأشياء والأنظمة المدمجة** — محسّن للأجهزة الصغيرة والبيئات ذات الموارد المحدودة.
2020
- 🛡️ **مفتوح المصدر** — AGPL-3.0-only؛ تراخيص تجارية متاحة.

0 commit comments

Comments
 (0)