
Messagerie chiffrée pair-à-pair en Rust avec Noise IK et UDP NAT hole punching
Chat pair-à-pair chiffré de bout en bout par UDP. Pas de comptes, pas de serveur central relayant/stocker les messages, pas d'intermédiaire. Juste deux pairs, une connexion directe et un chiffrement via le protocole Noise.
https://github.com/user-attachments/assets/939e96d3-45e3-4484-9a27-28c3a0457b05
Deux personnes exécutent punchline connect <pair> sur leurs machines. Punchline traverse leurs NAT, effectue une poignée de main chiffrée et les plonge dans un chat privé – le tout en quelques millisecondes. Les serveurs STUN et de signal inclus gèrent la découverte, puis s'effacent.

cargo build --release
Démarrer les serveurs (sur une machine que les deux pairs peuvent atteindre), ou utiliser ceux que j'héberge publiquement à 64.225.107.28 (STUN : port 3478, signal : port 8743) :
punchline-stund # Serveur STUN - indique aux pairs leur IP publique
punchline-signald # Serveur de signal - associe les pairs qui veulent communiquer
Sur chaque machine des pairs :
# Générer votre identité (paire de clés X25519)
punchline keygen
# Partager votre clé publique avec votre pair
punchline pubkey
# Enregistrer sa clé
punchline peers add alice a1b2c3d4...64_hex_chars
# Se connecter (les deux pairs exécutent cette commande, ciblant l'autre)
punchline connect alice --stun <server>:3478 --signal <server>:8743
Le TUI se lance avec une vue en direct de la progression de la connexion :
Découverte STUN – résolution de votre adresse externe via punchline-stund
Serveur de signal – connexion à punchline-signald
Attente du pair – le serveur de signaux associe les deux pairs
Trou de serrure – établissement du chemin UDP direct
Poignée de main Noise – échange de clés chiffré
Une fois terminé, vous êtes dans le chat. Tapez et appuyez sur Entrée. Appuyez sur Échap pour quitter.
L'ensemble du système est composé de trois binaires, tous inclus dans ce dépôt :
| Binaire | Rôle | Quand utilisé |
|---|
Une fois la configuration initiale effectuée, les serveurs STUN et de signal ne sont plus contactés. Tout circule directement de pair à pair.
punchlineDrapeaux globaux :
| Drapeau | Description |
|---|---|
-v | Augmente la verbosité des logs (-v = debug, -vv = trace). |
-q, --quiet | Supprime toute sortie de log. |
punchline-stundpunchline-signaldAu lieu de passer --stun et --signal à chaque fois, créez ~/.config/punchline/config.toml :
stun_server = "203.0.113.10:3478"
signal_server = "203.0.113.10:8743"
punchline peers # lister tous
punchline peers add alice a1b2c3d4... # ajouter
punchline peers remove alice # supprimer
Les alias sont stockés dans ~/.punchline/known_peers.toml. Vous pouvez aussi vous connecter directement avec une clé hexadécimale brute de 64 caractères.
punchline status
Affiche votre identité, configuration, accessibilité des serveurs (envoie une véritable sonde STUN et une connexion TCP) et le nombre de pairs.
Les deux serveurs supportent -v (debug), -vv (trace), -q (silencieux), --address et --port :
punchline-stund -v --port 3478
punchline-signald -v --port 8743
Personnalisez le TUI via ~/.config/punchline/style.toml
Styles utilisés dans la vidéo :
[colors]
my_text = "#ebdbb2"
peer_text = "#bdae93"
input_text = "#ebdbb2"
border = "#ebdbb2"
sidebar_key = "#ebdbb2"
sidebar_value = "#bdae93"
[padding]
chat_horizontal = 2
chat_vertical = 1
Toutes les couleurs sont en hexadécimal RVB. Si le fichier est absent, les couleurs par défaut du terminal sont utilisées.
punchline completions bash > ~/.local/share/bash-completion/completions/punchline
punchline completions zsh > ~/.zfunc/_punchline
punchline completions fish > ~/.config/fish/completions/punchline.fish
Nom complet du protocole : Noise_IK_25519_ChaChaPoly_SHA256
| Composant | Rôle |
|---|
Le modèle IK signifie que l'initiateur connaît la clé publique statique du répondant avant le début de la poignée de main. Les deux pairs possèdent déjà les clés l'un de l'autre (échangées hors bande ou via le registre de pairs), donc aucune confiance à la première utilisation n'est requise.
Punchline sélectionne déterministiquement l'initiateur en comparant les 8 premiers octets de la clé publique de chaque pair sous forme de u64 big-endian. Le pair avec la valeur la plus petite devient l'initiateur. Les deux côtés calculent cela indépendamment.
L'identité est une clé secrète X25519 de 32 octets dans ~/.punchline/id_x25519 avec permissions Unix 0600. La clé publique est dérivée au chargement. La génération de clés utilise x25519-dalek avec OsRng.
Le premier octet de chaque paquet UDP identifie son type :
Les deux pairs exécutent simultanément le même algorithme :
PROBE (0x00) toutes les 200 ms à l'adresse externe du pair.PROBE, passe à l'envoi d'ACK (0x01).ACK, envoie un dernier ACK et déclare le succès.Les messages (0x02) transportent des charge utiles UTF-8 chiffrées par Noise. Les keepalives (0x03) sont des charge utiles vides chiffrées envoyées toutes les 10 secondes pour maintenir la synchronisation des compteurs de nonce de chiffrement. 30 secondes sans paquet déclenchent la déconnexion.
JSON sur WebSocket :
// PairRequest (client -> serveur)
{ "external_addr": "203.0.113.5:48291", "public_key": "a1b2...", "target_public_key": "d4e5..." }
// PairResponse (serveur -> client)
{ "target_external_addr": "198.51.100.7:51003", "target_public_key": "d4e5..." }
Suit RFC 5389 (simplifié) : requête/réponse de liaison avec XOR-MAPPED-ADDRESS. IPv4 uniquement.
Espace de travail Cargo avec quatre crates :
crates/
├── proto/ # Bibliothèque partagée : crypto, STUN, types de signal, trait de transport
├── client/ # Client P2P : CLI, TUI, logique de connexion, gestion des pairs
├── signald/ # Serveur de signal : appariement des pairs via WebSocket
└── stund/ # Serveur STUN : découverte d'adresse externe
cargo install punchline # Client TUI
cargo install punchline-signald # Serveur de signal
cargo install punchline-stund # Serveur STUN
Prérequis : Édition Rust 2024 (rustc 1.85+)
git clone https://github.com/michal-pielka/punchline.git
cd punchline
cargo build --release
Les binaires sont placés dans target/release/ :
punchlinepunchline-signaldpunchline-stundcargo test
Les tests couvrent les opérations cryptographiques, l'encodage/décodage STUN, la sérialisation du protocole de signal, l'analyse de configuration, la gestion des pairs, les thèmes de style et la poignée de main Noise IK.
MIT - voir LICENSE.
punchline-stund | Serveur STUN (UDP) – répond avec l'IP:port externe du client | Uniquement pendant la configuration |
punchline-signald | Serveur de signal (WebSocket) – associe les pairs et échange les adresses | Uniquement pendant la configuration |
punchline | Le messager lui-même – CLI, TUI, crypto, trou de serrure | Toujours |
| Commande | Description |
|---|
keygen [--force] [-i chemin] | Génère une nouvelle paire de clés d'identité X25519. Utilisez --force pour écraser sans confirmation. Utilisez -i pour spécifier le chemin de sortie. |
pubkey [-i chemin] | Affiche votre clé publique (64 caractères hexadécimaux). Utilisez -i pour dériver d'un fichier de clés spécifique. |
connect <pair> [-i chemin] [--stun adresse] [--signal adresse] | Se connecte à un pair par alias ou par clé hexadécimale brute. Utilisez -i pour spécifier la clé d'identité. Lance le TUI. |
peers | Liste tous les pairs connus. |
peers add <nom> <clé> | Enregistre la clé publique d'un pair sous un surnom. |
peers remove <nom> | Supprime un pair par son surnom. |
config path | Affiche le chemin du fichier de configuration. |
config show | Affiche les valeurs actuelles de la configuration. |
status | Affiche l'identité, la configuration, l'accessibilité des serveurs et le nombre de pairs. |
completions <shell> | Génère les complétions shell (bash, zsh ou fish). |
| Drapeau | Description |
|---|
--address <adr> | Adresse de liaison (par défaut : 0.0.0.0). |
--port <port> | Port de liaison (par défaut : 3478). |
-v / -vv | Logs de debug / trace. |
-q | Mode silencieux. |
| Drapeau | Description |
|---|
--address <adr> | Adresse de liaison (par défaut : 0.0.0.0). |
--port <port> | Port de liaison (par défaut : 8743). |
-v / -vv | Logs de debug / trace. |
-q | Mode silencieux. |
| Noise IK | Modèle de poignée de main – l'initiateur connaît la clé publique du répondant. Se termine en 2 messages. |
| X25519 | Échange de clés Diffie-Hellman sur courbe elliptique (RFC 7748). Sécurité 128 bits, temps constant. |
| ChaCha20-Poly1305 | Chiffrement AEAD pour les messages (RFC 8439). Même chiffrement que TLS 1.3 et WireGuard. |
| SHA-256 | Utilisé en interne par Noise pour la dérivation de clés et le hachage de la poignée de main. |
| Préfixe | Type | Phase | Description |
|---|
0x00 | PROBE | Trou de serrure | Envoyé toutes les 200 ms pour ouvrir une brèche NAT |
0x01 | ACK | Trou de serrure | Confirme la réception d'un PROBE |
| (aucun) | Poignée de main | Poignée de main | Charge utile de poignée de main chiffrée brute Noise |
0x02 | Message | Transport | Message de chat chiffré |
0x03 | Keepalive | Transport | Charge utile chiffrée vide (battement de cœur) |
| Crate | Objectif |
|---|
snow | Framework du protocole Noise (poignée de main + chiffrement de transport) |
x25519-dalek | Génération et dérivation de clés X25519 |
ratatui | Framework d'interface utilisateur en terminal |
crossterm | Gestion des événements de terminal |
clap | Analyse des arguments CLI + complétions shell |
tungstenite | Client/serveur WebSocket |
tracing | Journalisation structurée |