Lewati ke konten utama

Protokol Handshake dan Rekeying

Status: Dokumen yang terus diperbarui Terakhir diperbarui: 2026-02-17

Ringkasan

TunGo menggunakan handshake Noise IK untuk autentikasi timbal balik dan kesepakatan kunci, diikuti oleh rekeying berkala menggunakan X25519 + HKDF-SHA256. Enkripsi transport menggunakan AEAD ChaCha20-Poly1305 dengan manajemen nonce berbasis epoch.

Cipher suite: X25519 / ChaChaPoly / SHA-256 ID Protokol: "TunGo", versi 0x01


1. Handshake (Noise IK)

Noise IK mengasumsikan bahwa inisiator (klien) sudah mengetahui kunci publik statis dari responder (server).

1.1 Alur Pesan

Client                                          Server
│ │
│─── MSG1: (e, es, s, ss) + MAC1 + MAC2 ───────>│
│ │
│<── COOKIE REPLY (optional, under load) ───────│
│ │
│─── MSG1 (retry with cookie) ─────────────────>│
│ │
│<── MSG2: (e, ee, se) ─────────────────────────│
│ │
├═══ Transport keys established ════════════════╡

1.2 MSG1 (Klien -> Server)

Format kawat:

[1B version] [>=80B noise_payload] [16B MAC1] [16B MAC2]
  • version: 0x01
  • noise_payload: Pesan pertama Noise IK — kunci publik efemeral klien (32B, teks terang) + kunci statis klien terenkripsi (48B)
  • MAC1: Autentikasi tanpa status (selalu diverifikasi)
  • MAC2: Autentikasi berbasis Cookie (diverifikasi hanya saat beban tinggi)

Ukuran minimum: 113 byte.

1.3 MSG2 (Server -> Klien)

Pesan kedua Noise IK. Tanpa MAC — autentikasi dua arah bersifat implisit setelah Noise selesai.

Setelah MSG2, kedua sisi menurunkan:

  • c2sKey (32 byte) — kunci transport klien-ke-server
  • s2cKey (32 byte) — kunci transport server-ke-klien
  • sessionId (32 byte) — dari pengikatan kanal Noise

1.4 Urutan Verifikasi Server

1. CheckVersion()         — reject unknown protocol versions
2. VerifyMAC1() — stateless, before any DH or allocation
3. VerifyMAC2() — only under load (LoadMonitor)
4. Noise handshake — DH computations, peer lookup
5. Peer ACL check — AllowedPeers / PeerDisabled

Semua kegagalan mengembalikan ErrHandshakeFailed yang seragam untuk mencegah kebocoran informasi.


2. Perlindungan DoS

2.1 MAC1 (Tanpa Status, Selalu Diperlukan)

key  = BLAKE2s-256("mac1" || "TunGo" || 0x01 || server_pubkey)
MAC1 = BLAKE2s-128(key, noise_msg1)

Diverifikasi sebelum alokasi status atau komputasi DH apa pun.

2.2 MAC2 (Berisi Status, Saat Beban Tinggi)

key  = BLAKE2s-256("mac2" || "TunGo" || 0x01 || cookie_value)
MAC2 = BLAKE2s-128(key, noise_msg1 || MAC1)

Diperiksa hanya ketika LoadMonitor mendeteksi tekanan.

Nilai Cookie (terikat IP, dibagi berdasarkan waktu):

bucket = unix_seconds / 120
cookie = BLAKE2s-128(server_secret[32], client_ip[16] || bucket[2])

Berlaku untuk bucket saat ini dan sebelumnya (menangani transisi).

Balasan Cookie (terenkripsi, 56 byte):

[24B nonce] [16B encrypted_cookie] [16B poly1305_tag]

Enkripsi:

key = BLAKE2s-256("cookie" || "TunGo" || 0x01 || server_pubkey || client_ephemeral)
ciphertext = XChaCha20-Poly1305.Seal(key, nonce, cookie, aad=client_ephemeral)

3. Enkripsi Transport

3.1 AEAD

ChaCha20-Poly1305 dengan AAD 60 byte:

AAD [60 bytes]:
[ 0..31] sessionId (32 bytes)
[32..47] direction (16 bytes: "client-to-server" or "server-to-client")
[48..59] nonce (12 bytes)

SessionId dan direction diisi terlebih dahulu saat pembuatan sesi. Hanya nonce yang diperbarui per paket.

3.2 Struktur Nonce (12 byte)

[0..7]   counterLow   (uint64, big-endian)
[8..9] counterHigh (uint16, big-endian)
[10..11] epoch (uint16, big-endian)
  • Counter: 80-bit monotonik (2^80 pesan per epoch). Overflow menghasilkan error.
  • Epoch: Tidak dapat diubah per sesi, mengidentifikasi generasi rekeying.

3.3 Transport TCP

Wire frame: [2B epoch] [ciphertext + 16B tag]
  • Epoch ganda: sesi saat ini dan sebelumnya hidup berdampingan selama rekey.
  • Pembersihan otomatis: sesi sebelumnya di-nol-kan pada dekripsi pertama epoch saat ini (jaminan pengurutan TCP).
  • Tanpa perlindungan replay (TCP menyediakan pengurutan).

3.4 Transport UDP

Wire frame: [8B route-id] [12B nonce] [ciphertext + 16B tag]
  • Route-id is derived from sessionId (first 8 bytes, big-endian) and enables O(1) session lookup.

  • Epoch tertanam dalam byte nonce 10..11.

  • Perlindungan replay: Bitmap jendela geser 1024-bit per epoch.

    • Pemeriksaan tentatif sebelum dekripsi (Check).
    • Dikonfirmasi hanya setelah autentikasi AEAD berhasil (Accept).
    • Mencegah pencemaran jendela oleh paket tidak valid.
  • Epoch ring: FIFO berkapasitas tetap untuk sesi. Sesi yang dikeluarkan akan di-nol-kan.


4. Rekeying

4.1 Penurunan Kunci

Kedua sisi melakukan X25519 ECDH, kemudian menurunkan kunci transport baru melalui HKDF-SHA256:

shared  = X25519(local_private, remote_public)
newC2S = HKDF-SHA256(ikm=shared, salt=currentC2S, info="tungo-rekey-c2s")
newS2C = HKDF-SHA256(ikm=shared, salt=currentS2C, info="tungo-rekey-s2c")

Kunci saat ini berfungsi sebagai salt HKDF, memberikan rantai kerahasiaan maju (forward secrecy).

4.2 Paket Control Plane

RekeyInit:  [0xFF] [0x01] [0x02] [32B X25519 public key]   (35 bytes)
RekeyAck: [0xFF] [0x01] [0x03] [32B X25519 public key] (35 bytes)

4.3 FSM Rekey

         StartRekey            installPending
Stable ──────────> Rekeying ──────────────> Pending
^ │
│ ActivateSendEpoch │
└───────────────────────────────────────────┘
^ │
│ AbortPendingIfExpired (5s) │
└───────────────────────────────────────────┘
StatusDeskripsi
StableOperasi normal. Satu epoch pengiriman aktif.
RekeyingStartRekey dipanggil, kunci baru dihitung, epoch baru dipasang untuk penerimaan.
PendingMenunggu konfirmasi peer (dekripsi pertama yang berhasil dengan epoch baru).

4.4 Alur Rekey

Client                                     Server
│ │
│── RekeyInit (client X25519 pub) ────────>│
│ │ derive newC2S, newS2C
│ │ install new epoch (recv)
│<── RekeyAck (server X25519 pub) ─────────│
│ │
│ derive newC2S, newS2C │
│ install new epoch (recv + send) │
│ │
│── first packet with new epoch ──────────>│
│ │ peer confirmed → activate send
│<── first packet with new epoch ──────────│
│ │
├═══ Both sides on new epoch ══════════════╡

4.5 Invarian Keamanan

  • Hanya satu rekey yang sedang berlangsung pada satu waktu.
  • Epoch meningkat secara monotonik. Epoch aman maksimum: 65000 (dari 65535). Melampaui batas ini, ErrEpochExhausted memaksa handshake ulang.
  • Epoch pengiriman tidak pernah menurun.
  • Kunci yang menunggu konfirmasi tidak pernah menimpa kunci aktif sampai peer membuktikan kepemilikan (melalui dekripsi yang berhasil).
  • Rekey yang menunggu konfirmasi dibatalkan secara otomatis setelah 5 detik jika tidak ada konfirmasi peer.
  • Interval rekey default: 120 detik.

5. Penghapusan Kunci (Zeroization)

MaterialKapan Di-nol-kan
Kunci privat DH efemeralSegera setelah komputasi DH (defer mem.ZeroBytes)
Rahasia bersama (rekey)Segera setelah penurunan kunci (defer mem.ZeroBytes)
Kunci rekey yang menunggu konfirmasi (FSM)Saat dibatalkan atau dipromosikan menjadi aktif
Kunci sesi sebelumnyaPada dekripsi pertama epoch saat ini (TCP) atau pengusiran epoch (UDP)
Jendela replay NonceSaat pembongkaran sesi (SlidingWindow.Zeroize)
Buffer AADSaat pembongkaran sesi (DefaultUdpSession.Zeroize)

Keterbatasan: GC Go mungkin menyalin objek heap sebelum penghapusan. mem.ZeroBytes adalah pertahanan terbaik yang mungkin terhadap forensik memori, diverifikasi melalui analisis output compiler untuk memastikan tidak dioptimalkan (Go 1.26.x, semua platform target).


6. Konstanta

KonstantaNilaiTujuan
Protocol version0x01Pemversian format kawat
MAC1 / MAC2 size16 byteOutput BLAKE2s-128
Cookie bucket120 detikJendela validitas cookie terikat IP
Cookie reply size56 bytenonce (24) + encrypted cookie (16) + tag (16)
AAD length60 bytesessionId (32) + direction (16) + nonce (12)
UDP route-id8 bytessession identifier prefix for O(1) peer lookup
Nonce counter80 bitPesan per epoch sebelum overflow
Replay window1024 bitToleransi ketidakterurutan UDP
Epoch capacityuint1665535 nilai, ambang aman 65000
Rekey interval120 detikPemicu rekey berkala default
Pending timeout5 detikPembatalan otomatis rekey yang belum dikonfirmasi