Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DTLS for the entity system #107

Merged
merged 9 commits into from
Jan 19, 2025
6 changes: 4 additions & 2 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}

- name: Download dependencies
run: wget https://files.catbox.moe/r5p6rr.gpg -O deps.zip.gpg
run: wget https://files.catbox.moe/t156ho.gpg -O deps.zip.gpg

- name: Decrypt dependencies
run: gpg --quiet --batch --yes --decrypt --passphrase="${{ secrets.DEPENDENCIES_ZIP_PASSPHRASE }}" --output deps.zip deps.zip.gpg
Expand All @@ -44,7 +44,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.0.x
dotnet-version: 8.0.x

- name: Restore dependencies
run: dotnet restore ${{ github.workspace }}
Expand All @@ -60,6 +60,8 @@ jobs:
${{ github.workspace }}/HKMP/bin/Release/net472/HKMP.dll
${{ github.workspace }}/HKMP/bin/Release/net472/HKMP.xml
${{ github.workspace }}/HKMP/bin/Release/net472/HKMP.pdb
${{ github.workspace }}/HKMP/bin/Release/net472/BouncyCastle.Cryptography.dll
${{ github.workspace }}/HKMP/bin/Release/net472/BouncyCastle.Cryptography.xml

- name: Upload HKMPServer artifact
uses: actions/upload-artifact@v4
Expand Down
5 changes: 4 additions & 1 deletion HKMP/HKMP.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,15 @@
<HintPath>$(References)\MonoMod.Utils.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="BouncyCastle.Cryptography">
<HintPath>$(References)\BouncyCastle.Cryptography.dll</HintPath>
</Reference>
</ItemGroup>

<!-- Create an item group for the source and destination files of the copy target. This way we can specify
multiple paths that we want the assembly to be copied to. -->
<ItemGroup>
<SourceFiles Include="$(TargetDir)$(AssemblyName).dll;$(TargetDir)$(AssemblyName).pdb;$(TargetDir)$(AssemblyName).xml" />
<SourceFiles Include="$(TargetDir)*.dll;$(TargetDir)*.pdb;$(TargetDir)*.xml" />
<Dest Include="$(OutputDirectory)" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
Expand Down
87 changes: 87 additions & 0 deletions HKMP/Networking/Client/ClientDatagramTransport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System.Net.Sockets;
using Hkmp.Logging;
using Org.BouncyCastle.Tls;

namespace Hkmp.Networking.Client;

/// <summary>
/// Class that implements the DatagramTransport interface from DTLS. This class simply sends and receives data using
/// a UDP socket directly.
/// </summary>
internal class ClientDatagramTransport : DatagramTransport {
/// <summary>
/// The socket with which to send and over which to receive data.
/// </summary>
private readonly Socket _socket;

public ClientDatagramTransport(Socket socket) {
_socket = socket;
}

/// <summary>
/// The maximum number of bytes to receive in a single call to <see cref="Receive"/>.
/// </summary>
/// <returns>The maximum number of bytes that can be received.</returns>
public int GetReceiveLimit() {
return DtlsClient.MaxPacketSize;
}

/// <summary>
/// The maximum number of bytes to send in a single call to <see cref="Send"/>.
/// </summary>
/// <returns>The maximum number of bytes that can be sent.</returns>
public int GetSendLimit() {
return DtlsClient.MaxPacketSize;
}

/// <summary>
/// This method is called whenever the corresponding DtlsTransport's Receive is called. The implementation
/// receives data from the network and store it in the given buffer. If no data is received within the given
/// <paramref name="waitMillis"/>, the method returns -1.
/// </summary>
/// <param name="buf">Byte array to store the received data.</param>
/// <param name="off">The offset at which to begin storing the bytes.</param>
/// <param name="len">The number of bytes that can be stored in the buffer.</param>
/// <param name="waitMillis">The number of milliseconds to wait for data to receive.</param>
/// <returns>The number of bytes that were received, or -1 if no bytes were received in the given time.</returns>
public int Receive(byte[] buf, int off, int len, int waitMillis) {
try {
_socket.ReceiveTimeout = waitMillis;
var numReceived = _socket.Receive(
buf,
off,
len,
SocketFlags.None,
out var socketError
);

if (socketError == SocketError.Success) {
return numReceived;
}

Logger.Error($"UDP Socket Error on receive: {socketError}");
} catch (SocketException e) {
Logger.Error($"UDP Socket exception, ErrorCode: {e.ErrorCode}, Socket ErrorCode: {e.SocketErrorCode}, Exception:\n{e}");
}

return -1;
}

/// <summary>
/// This method is called whenever the corresponding DtlsTransport's Send is called. The implementation simply
/// sends the data in the buffer over the network.
/// </summary>
/// <param name="buf">Byte array containing the bytes to send.</param>
/// <param name="off">The offset in the buffer at which to start sending bytes.</param>
/// <param name="len">The number of bytes to send.</param>
public void Send(byte[] buf, int off, int len) {
_socket.Send(buf, off, len, SocketFlags.None);
}

/// <summary>
/// Cleanup login for when this transport channel should be closed.
/// Since we handle socket closing in another class (<seealso cref="DtlsClient"/>), there is nothing here.
/// </summary>
public void Close() {
}
}
110 changes: 110 additions & 0 deletions HKMP/Networking/Client/ClientTlsClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System.Text;
using Hkmp.Logging;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Tls;
using Org.BouncyCastle.Tls.Crypto;
using Org.BouncyCastle.Utilities.Encoders;

namespace Hkmp.Networking.Client;

/// <summary>
/// Client-side TLS client implementation that handles reporting supported cipher suites, provides client
/// authentication and checks server certificate.
/// </summary>
/// <param name="crypto">TlsCrypto instance for handling low-level cryptography.</param>
internal class ClientTlsClient(TlsCrypto crypto) : AbstractTlsClient(crypto) {
/// <summary>
/// List of supported cipher suites on the client-side.
/// </summary>
private static readonly int[] SupportedCipherSuites = [
CipherSuite.TLS_AES_128_GCM_SHA256,
CipherSuite.TLS_AES_256_GCM_SHA384,
CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
];

/// <inheritdoc />
protected override ProtocolVersion[] GetSupportedVersions() {
return ProtocolVersion.DTLSv12.Only();
}

/// <summary>
/// Get the supported cipher suites for this TLS client.
/// </summary>
/// <returns>An int array representing the cipher suites.</returns>
protected override int[] GetSupportedCipherSuites() {
return SupportedCipherSuites;
}

/// <inheritdoc />
/// <summary>
/// Get the authentication implementation for this TLS client that handles providing client credentials and
/// checking server certificates.
/// </summary>
/// <returns>The TlsAuthentication instance for this TLS client.</returns>
public override TlsAuthentication GetAuthentication() {
return new TlsAuthenticationImpl();
}

/// <summary>
/// Implementation for TLS authentication that handles providing client credentials and checking server
/// certificates.
/// </summary>
private class TlsAuthenticationImpl : TlsAuthentication {
/// <summary>
/// Notify the TLS client of the server certificate that the server has sent. This method checks whether to
/// trust the server based on this certificate or not. If not, the method will throw an exception which will
/// subsequently abort the connection.
/// In the current implementation, we only log the fingerprints of the certificates in the chain from the
/// server.
/// </summary>
/// <inheritdoc />
/// <param name="serverCertificate">The server certificate instance.</param>
public void NotifyServerCertificate(TlsServerCertificate serverCertificate) {
if (serverCertificate?.Certificate == null || serverCertificate.Certificate.IsEmpty) {
throw new TlsFatalAlert(AlertDescription.bad_certificate);
}

var chain = serverCertificate.Certificate.GetCertificateList();

Logger.Info("Server certificate fingerprint(s):");
for (var i = 0; i < chain.Length; i++) {
var entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
Logger.Info($" fingerprint:SHA256 {Fingerprint(entry)} ({entry.Subject})");
}
}

/// <summary>
/// Get the credentials of the client so the server can verify who we are. Currently, we have no way to
/// provide client-side credentials, so we return null.
/// </summary>
/// <inheritdoc />
public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest) {
// TODO: provide means for a client to have certificate and return it in this method
return null;
}

/// <summary>
/// Return a fingerprint for the given X509 certificate.
/// </summary>
/// <param name="c">The 509 certificate to fingerprint.</param>
/// <returns>The fingerprint as a string.</returns>
private static string Fingerprint(X509CertificateStructure c) {
var der = c.GetEncoded();
var hash = DigestUtilities.CalculateDigest("SHA256", der);
var hexBytes = Hex.Encode(hash);
var hex = Encoding.ASCII.GetString(hexBytes).ToUpperInvariant();

var fp = new StringBuilder();
var i = 0;
fp.Append(hex.Substring(i, 2));
while ((i += 2) < hex.Length) {
fp.Append(':');
fp.Append(hex.Substring(i, 2));
}
return fp.ToString();
}
}
}
19 changes: 4 additions & 15 deletions HKMP/Networking/Client/ClientUpdateManager.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using Hkmp.Animation;
using Hkmp.Game;
using Hkmp.Game.Client.Entity;
using Hkmp.Math;
using Hkmp.Networking.Packet;
using Hkmp.Networking.Packet.Data;
using Org.BouncyCastle.Tls;

namespace Hkmp.Networking.Client;

Expand All @@ -15,19 +13,10 @@ namespace Hkmp.Networking.Client;
/// </summary>
internal class ClientUpdateManager : UdpUpdateManager<ServerUpdatePacket, ServerPacketId> {
/// <summary>
/// Construct the update manager with a UDP net client.
/// Construct the update manager a DTLS transport instance.
/// </summary>
/// <param name="udpSocket">The UDP socket for the local client.</param>
public ClientUpdateManager(Socket udpSocket) : base(udpSocket) {
}

/// <inheritdoc />
protected override void SendPacket(Packet.Packet packet) {
if (!UdpSocket.Connected) {
return;
}

UdpSocket?.SendAsync(new ArraySegment<byte>(packet.ToArray()), SocketFlags.None);
/// <param name="dtlsTransport">The DTLS transport instance for sending data.</param>
public ClientUpdateManager(DtlsTransport dtlsTransport) : base(dtlsTransport) {
}

/// <inheritdoc />
Expand Down
Loading
Loading