Aller au contenu principal

Protocole de Handshake et de Renouvellement des Cles

Statut : Document evolutif Derniere mise a jour : 2026-02-17

Apercu

TunGo utilise un handshake Noise IK pour l'authentification mutuelle et l'accord de cles, suivi d'un renouvellement periodique des cles via X25519 + HKDF-SHA256. Le chiffrement de transport utilise ChaCha20-Poly1305 AEAD avec une gestion des nonces basee sur les epoques.

Suite cryptographique : X25519 / ChaChaPoly / SHA-256 Identifiant du protocole : "TunGo", version 0x01


1. Handshake (Noise IK)

Noise IK suppose que l'initiateur (client) connait deja la cle publique statique du repondeur (serveur).

1.1 Flux de messages

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 (Client -> Serveur)

Format sur le reseau :

[1B version] [>=80B noise_payload] [16B MAC1] [16B MAC2]
  • version : 0x01
  • noise_payload : Premier message Noise IK — cle publique ephemere du client (32B, en clair) + cle statique chiffree du client (48B)
  • MAC1 : Authentification sans etat (toujours verifiee)
  • MAC2 : Authentification basee sur cookie (verifiee uniquement sous charge)

Taille minimale : 113 octets.

1.3 MSG2 (Serveur -> Client)

Deuxieme message Noise IK. Sans MACs — l'authentification bidirectionnelle est implicite apres l'achevement de Noise.

Apres MSG2, les deux parties derivent :

  • c2sKey (32 octets) — cle de transport client-vers-serveur
  • s2cKey (32 octets) — cle de transport serveur-vers-client
  • sessionId (32 octets) — a partir de la liaison de canal Noise

1.4 Ordre de verification du serveur

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

Tous les echecs renvoient un ErrHandshakeFailed uniforme pour empecher la fuite d'informations.


2. Protection contre les DoS

2.1 MAC1 (Sans etat, toujours requis)

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

Verifie avant toute allocation d'etat ou calcul DH.

2.2 MAC2 (Avec etat, sous charge)

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

Verifie uniquement lorsque LoadMonitor detecte une pression.

Valeur du cookie (liee a l'IP, decoupee en intervalles de temps) :

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

Valide pour l'intervalle actuel et le precedent (gestion des transitions).

Reponse cookie (chiffree, 56 octets) :

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

Chiffrement :

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

3. Chiffrement de transport

3.1 AEAD

ChaCha20-Poly1305 avec AAD de 60 octets :

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 et direction sont preremplis a la creation de la session. Seul le nonce est mis a jour par paquet.

3.2 Structure du Nonce (12 octets)

[0..7]   counterLow   (uint64, big-endian)
[8..9] counterHigh (uint16, big-endian)
[10..11] epoch (uint16, big-endian)
  • Compteur : 80 bits monotone (2^80 messages par epoque). Le depassement renvoie une erreur.
  • Epoque : Immuable par session, identifie la generation de renouvellement des cles.

3.3 Transport TCP

Wire frame: [2B epoch] [ciphertext + 16B tag]
  • Double epoque : la session actuelle et la precedente coexistent pendant le renouvellement des cles.
  • Nettoyage automatique : la session precedente est mise a zero lors du premier dechiffrement avec l'epoque actuelle (garantie d'ordonnancement TCP).
  • Pas de protection contre la repetition (TCP assure l'ordonnancement).

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.

  • Epoque integree dans les octets 10..11 du nonce.

  • Protection contre la repetition : Fenetre glissante de 1024 bits (bitmap) par epoque.

    • Verification provisoire avant le dechiffrement (Check).
    • Confirmation uniquement apres la reussite de l'authentification AEAD (Accept).
    • Empeche l'empoisonnement de la fenetre par des paquets invalides.
  • Anneau d'epoques : FIFO a capacite fixe pour les sessions. Les sessions evincees sont mises a zero.


4. Renouvellement des cles

4.1 Derivation des cles

Les deux parties effectuent un X25519 ECDH, puis derivent de nouvelles cles de transport via 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")

Les cles actuelles servent de sel HKDF, assurant le chainage de la confidentialite persistante.

4.2 Paquets du plan de controle

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

4.3 Machine a etats du renouvellement des cles

         StartRekey            installPending
Stable ──────────> Rekeying ──────────────> Pending
^ │
│ ActivateSendEpoch │
└───────────────────────────────────────────┘
^ │
│ AbortPendingIfExpired (5s) │
└───────────────────────────────────────────┘
EtatDescription
StableFonctionnement normal. Une seule epoque d'envoi active.
RekeyingStartRekey appele, nouvelles cles calculees, nouvelle epoque installee pour la reception.
PendingEn attente de la confirmation du pair (premier dechiffrement reussi avec la nouvelle epoque).

4.4 Flux de renouvellement des cles

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 Invariants de securite

  • Un seul renouvellement de cles en cours a la fois.
  • Les epoques augmentent de maniere monotone. Epoque sure maximale : 65000 (sur 65535). Au-dela, ErrEpochExhausted force un nouveau handshake.
  • L'epoque d'envoi ne diminue jamais.
  • Les cles en attente ne remplacent jamais les cles actives tant que le pair n'a pas prouve la possession (via un dechiffrement reussi).
  • Le renouvellement en attente s'annule automatiquement apres 5 secondes sans confirmation du pair.
  • Intervalle de renouvellement des cles par defaut : 120 secondes.

5. Mise a zero des cles

MateriauMoment de la mise a zero
Cles privees DH ephemeresImmediatement apres le calcul DH (defer mem.ZeroBytes)
Secrets partages (renouvellement des cles)Immediatement apres la derivation des cles (defer mem.ZeroBytes)
Cles en attente de renouvellement (machine a etats)A l'abandon ou a la promotion en cles actives
Cles de la session precedenteAu premier dechiffrement avec l'epoque actuelle (TCP) ou a l'eviction de l'epoque (UDP)
Fenetre de repetition des noncesA la fermeture de la session (SlidingWindow.Zeroize)
Tampons AADA la fermeture de la session (DefaultUdpSession.Zeroize)

Limitation : Le ramasse-miettes de Go peut copier des objets du tas avant la mise a zero. mem.ZeroBytes est une defense au mieux contre l'analyse forensique de la memoire, verifiee par l'analyse de la sortie du compilateur pour confirmer l'absence d'optimisation (Go 1.26.x, toutes les plateformes cibles).


6. Constantes

ConstanteValeurObjectif
Version du protocole0x01Gestion des versions du format reseau
Taille de MAC1 / MAC216 octetsSortie BLAKE2s-128
Intervalle de cookie120 secondesFenetre de validite du cookie lie a l'IP
Taille de la reponse cookie56 octetsnonce (24) + cookie chiffre (16) + tag (16)
Longueur de AAD60 octetssessionId (32) + direction (16) + nonce (12)
UDP route-id8 bytessession identifier prefix for O(1) peer lookup
Compteur de nonce80 bitsMessages par epoque avant depassement
Fenetre de repetition1024 bitsTolerance au desordre UDP
Capacite des epoquesuint1665535 valeurs, seuil de securite 65000
Intervalle de renouvellement des cles120 secondesDeclencheur periodique de renouvellement par defaut
Delai d'attente5 secondesAbandon automatique du renouvellement non confirme