feat(netcode): serveur DTLS-PSK (côté serveur) — franchit le mur du chiffrement
Build Game Server / build (push) Successful in 20s

Implémente le pendant serveur du DTLSHandlerComponent d'UE (la pile client est
[DTLS, Stateless]) : le client exige un DTLS en mode PSK dont l'identité = user_id.

- DtlsPskStore : table user_id -> PSK 32o, chargée depuis l'env (PROSPECT_DTLS_PSKS,
  rendu Vault) — aucune clé en dur ni commitée.
- DtlsPacketTransport : pont entre l'API bloquante DTLS de BouncyCastle et le modèle
  paquet-par-paquet du PacketHandler (handshake sur thread dédié).
- ProspectPskTlsServer : serveur DTLS-PSK BouncyCastle (DTLS 1.2, identité=user_id).
- DTLSHandlerComponent : composant du pipeline (Incoming déchiffre / Outgoing chiffre),
  activé au NMT_Hello portant un EncryptionToken.
- UWorld.NotifyControlMessage : sur NMT_Hello chiffré, si la PSK de l'identité est
  connue -> NMT_EncryptionAck + BeginHandshake ; sinon warning (comme avant).

Compile (0 erreur, BouncyCastle 2.4). ⚠️ Le framing exact DTLS-sur-PacketHandler et
le routage des records de handshake demandent une ITÉRATION EN LIVE contre le vrai
client (invalidable hors client).
This commit is contained in:
2026-07-16 23:06:14 +02:00
parent f9c44a89b3
commit 36122578a3
7 changed files with 423 additions and 3 deletions
+23 -2
View File
@@ -4,6 +4,7 @@ using Prospect.Unreal.Core.Objects;
using Prospect.Unreal.Exceptions;
using Prospect.Unreal.Net;
using Prospect.Unreal.Net.Actors;
using Prospect.Unreal.Net.Dtls;
using Prospect.Unreal.Net.Channels;
using Prospect.Unreal.Net.Packets.Bunch;
using Prospect.Unreal.Net.Packets.Control;
@@ -15,6 +16,9 @@ public abstract partial class UWorld : FNetworkNotify, IAsyncDisposable
{
private static readonly ILogger Logger = Log.ForContext<UWorld>();
// Table user_id -> PSK DTLS, chargée depuis l'env (rendu Vault, jamais commité).
private static readonly Lazy<DtlsPskStore> DtlsPsks = new(() => DtlsPskStore.FromEnvironment());
private UGameInstance? _owningGameInstance;
private AGameModeBase? _authorityGameMode;
@@ -307,8 +311,25 @@ public abstract partial class UWorld : FNetworkNotify, IAsyncDisposable
//
// For now: log and proceed to the challenge so the flow is visible
// in logs; the client will close afterwards.
Logger.Warning("Client requires DTLS-PSK encryption (EncryptionToken={Token}); server-side DTLS not implemented — connection will be dropped by client", encryptionToken);
connection.SendChallengeControlMessage();
// Chiffrement DTLS-PSK requis par le client. Si on connaît la
// PSK de cette identité (user_id), on répond NMT_EncryptionAck et
// on établit le DTLS côté serveur (BouncyCastle). Cf. DTLSHandlerComponent.
var psks = DtlsPsks.Value;
if (psks.Get(encryptionToken) != null && connection.DtlsComponent != null)
{
Logger.Information("DTLS-PSK: identité {Token} connue -> EncryptionAck + handshake", encryptionToken);
NMT_EncryptionAck.Send(connection);
connection.FlushNet();
connection.DtlsComponent.BeginHandshake(
psks, encryptionToken,
rec => connection.LowLevelSend(rec, rec.Length * 8, new FOutPacketTraits()));
connection.SendChallengeControlMessage();
}
else
{
Logger.Warning("DTLS-PSK requis mais aucune PSK pour {Token} (renseigner PROSPECT_DTLS_PSKS) — le client fermera la connexion", encryptionToken);
connection.SendChallengeControlMessage();
}
}
}
break;