171 lines
5.7 KiB
C#
171 lines
5.7 KiB
C#
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(rec =>
|
|
{
|
|
Logger.Information("[DTLS] Outgoing record {N}o head={H}", rec.Length,
|
|
Convert.ToHexString(rec.AsSpan(0, Math.Min(rec.Length, 12))));
|
|
sendRecord(rec);
|
|
});
|
|
SetActive(true);
|
|
Logger.Information("[DTLS] BeginHandshake pour {Identity} — serveur DTLS-PSK démarré", identity);
|
|
|
|
_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)
|
|
{
|
|
Logger.Information("[DTLS] Incoming CALLED active={A} transportNull={T} failed={F} established={E} bitsLeft={B}",
|
|
IsActive(), _transport == null, _failed, _established, packet.GetBitsLeft());
|
|
|
|
if (!IsActive() || _transport == null || _failed)
|
|
{
|
|
return; // passthrough (connexion non chiffrée)
|
|
}
|
|
|
|
var record = ReadAlignedBytes(packet);
|
|
Logger.Information("[DTLS] Incoming nbytes={N} head={H}", record.Length,
|
|
Convert.ToHexString(record.AsSpan(0, Math.Min(record.Length, 12))));
|
|
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;
|
|
}
|
|
}
|