feat(netcode): serveur DTLS-PSK (côté serveur) — franchit le mur du chiffrement
Build Game Server / build (push) Successful in 20s
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:
@@ -0,0 +1,159 @@
|
||||
using Org.BouncyCastle.Tls;
|
||||
using Prospect.Unreal.Net.Dtls;
|
||||
using Prospect.Unreal.Serialization;
|
||||
using Serilog;
|
||||
|
||||
namespace Prospect.Unreal.Net;
|
||||
|
||||
/// <summary>
|
||||
/// Composant de chiffrement DTLS-PSK côté serveur, pendant du DTLSHandlerComponent
|
||||
/// d'UE (la pile client est [DTLSHandlerComponent, StatelessConnectHandlerComponent]).
|
||||
///
|
||||
/// Le client exige le chiffrement : il annonce son identité PSK (= user_id) dans
|
||||
/// NMT_Hello, et on répond NMT_EncryptionAck puis on établit un DTLS-PSK. Ici on
|
||||
/// implémente le côté serveur (BouncyCastle) ; la clé provient du DtlsPskStore.
|
||||
///
|
||||
/// ⚠️ Première implémentation — le framing exact DTLS-sur-PacketHandler d'UE et
|
||||
/// l'injection des records de handshake dans la voie d'émission demandent une
|
||||
/// itération EN LIVE contre le vrai client (invalidable hors client).
|
||||
/// </summary>
|
||||
public sealed class DTLSHandlerComponent : HandlerComponent
|
||||
{
|
||||
private static readonly ILogger Logger = Log.ForContext<DTLSHandlerComponent>();
|
||||
|
||||
private DtlsPacketTransport? _transport;
|
||||
private DtlsTransport? _dtls; // non-null une fois le handshake terminé
|
||||
private Thread? _handshakeThread;
|
||||
private volatile bool _established;
|
||||
private volatile bool _failed;
|
||||
|
||||
public DTLSHandlerComponent(PacketHandler handler) : base(handler, nameof(DTLSHandlerComponent))
|
||||
{
|
||||
RequiresHandshake = true;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
// Inactif tant qu'on n'a pas la PSK (via BeginHandshake) : les connexions
|
||||
// sans EncryptionToken passent en clair.
|
||||
SetActive(false);
|
||||
Initialized();
|
||||
}
|
||||
|
||||
public override bool IsValid() => true;
|
||||
|
||||
/// <summary>
|
||||
/// Démarre le serveur DTLS-PSK pour une identité (user_id) donnée. <paramref name="sendRecord"/>
|
||||
/// émet un record DTLS vers le client (voie bas-niveau, hors chiffrement).
|
||||
/// </summary>
|
||||
public void BeginHandshake(DtlsPskStore store, string identity, Action<byte[]> sendRecord)
|
||||
{
|
||||
if (store.Get(identity) == null)
|
||||
{
|
||||
Logger.Warning("Pas de PSK pour l'identité {Identity} — DTLS impossible (fournir PROSPECT_DTLS_PSKS)", identity);
|
||||
_failed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_transport = new DtlsPacketTransport(sendRecord);
|
||||
SetActive(true);
|
||||
|
||||
_handshakeThread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var server = new ProspectPskTlsServer(store);
|
||||
_dtls = new DtlsServerProtocol().Accept(server, _transport);
|
||||
_established = true;
|
||||
Logger.Information("Handshake DTLS-PSK établi pour {Identity}", identity);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_failed = true;
|
||||
Logger.Error(ex, "Handshake DTLS-PSK échoué pour {Identity}", identity);
|
||||
}
|
||||
})
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = "dtls-handshake",
|
||||
};
|
||||
_handshakeThread.Start();
|
||||
}
|
||||
|
||||
public override void Incoming(FBitReader packet)
|
||||
{
|
||||
if (!IsActive() || _transport == null || _failed)
|
||||
{
|
||||
return; // passthrough (connexion non chiffrée)
|
||||
}
|
||||
|
||||
var record = ReadAlignedBytes(packet);
|
||||
if (record.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Alimente la pile DTLS (handshake OU données applicatives).
|
||||
_transport.Feed(record);
|
||||
|
||||
if (!_established || _dtls == null)
|
||||
{
|
||||
// Record de handshake : consommé par le thread, rien à remonter au pipeline.
|
||||
packet.SetData(Array.Empty<byte>(), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Données applicatives : déchiffrer et remplacer le contenu du paquet.
|
||||
var plain = new byte[_dtls.GetReceiveLimit()];
|
||||
var n = _dtls.Receive(plain, 0, plain.Length, 0);
|
||||
if (n > 0)
|
||||
{
|
||||
packet.SetData(plain[..n], (long)n * 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
packet.SetData(Array.Empty<byte>(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Outgoing(ref FBitWriter packet, FOutPacketTraits traits)
|
||||
{
|
||||
if (!IsActive() || !_established || _dtls == null)
|
||||
{
|
||||
return; // avant établissement : le handshake gère ses propres records
|
||||
}
|
||||
|
||||
var plain = packet.GetData();
|
||||
var nbytes = (int)packet.GetNumBytes();
|
||||
_dtls.Send(plain, 0, nbytes); // le record chiffré part via le callback du transport
|
||||
}
|
||||
|
||||
public override int GetReservedPacketBits()
|
||||
{
|
||||
// Marge d'en-tête DTLS (record 13o + AEAD ~40o) — valeur à affiner en live.
|
||||
return 64 * 8;
|
||||
}
|
||||
|
||||
public bool IsEstablished => _established;
|
||||
|
||||
public bool HasFailed => _failed;
|
||||
|
||||
private static byte[] ReadAlignedBytes(FBitReader packet)
|
||||
{
|
||||
var bytes = packet.GetBytesLeft();
|
||||
if (bytes <= 0)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
var buf = new byte[bytes];
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* p = buf)
|
||||
{
|
||||
packet.Serialize(p, bytes);
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user