Keep local version of files

This commit is contained in:
2026-10-08 00:49:10 +05:30
parent 7fa0621ff8
commit 053665c690
7343 changed files with 5470578 additions and 5470528 deletions
@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 0d3cd9d7d6e84a578f7e4b384ff813f1
timeCreated: 1708793986
fileFormatVersion: 2
guid: 0d3cd9d7d6e84a578f7e4b384ff813f1
timeCreated: 1708793986
@@ -1,18 +1,18 @@
{
"name": "EncryptionTransportEditor",
"rootNamespace": "",
"references": [
"GUID:627104647b9c04b4ebb8978a92ecac63"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
{
"name": "EncryptionTransportEditor",
"rootNamespace": "",
"references": [
"GUID:627104647b9c04b4ebb8978a92ecac63"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -1,14 +1,14 @@
fileFormatVersion: 2
guid: 4c9c7b0ef83e6e945b276d644816a489
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/Editor/EncryptionTransportEditor.asmdef
uploadId: 736421
fileFormatVersion: 2
guid: 4c9c7b0ef83e6e945b276d644816a489
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/Editor/EncryptionTransportEditor.asmdef
uploadId: 736421
@@ -1,90 +1,90 @@
using UnityEditor;
using UnityEngine;
namespace Mirror.Transports.Encryption
{
[CustomEditor(typeof(EncryptionTransport), true)]
public class EncryptionTransportInspector : UnityEditor.Editor
{
SerializedProperty innerProperty;
SerializedProperty clientValidatesServerPubKeyProperty;
SerializedProperty clientTrustedPubKeySignaturesProperty;
SerializedProperty serverKeypairPathProperty;
SerializedProperty serverLoadKeyPairFromFileProperty;
// Assuming proper SerializedProperty definitions for properties
// Add more SerializedProperty fields related to different modes as needed
void OnEnable()
{
innerProperty = serializedObject.FindProperty("Inner");
clientValidatesServerPubKeyProperty = serializedObject.FindProperty("ClientValidateServerPubKey");
clientTrustedPubKeySignaturesProperty = serializedObject.FindProperty("ClientTrustedPubKeySignatures");
serverKeypairPathProperty = serializedObject.FindProperty("ServerKeypairPath");
serverLoadKeyPairFromFileProperty = serializedObject.FindProperty("ServerLoadKeyPairFromFile");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// Draw default inspector for the parent class
DrawDefaultInspector();
EditorGUILayout.LabelField("Encryption Settings", EditorStyles.boldLabel);
if (innerProperty != null)
{
EditorGUILayout.LabelField("Common", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(innerProperty);
EditorGUILayout.Separator();
}
// Client Section
EditorGUILayout.LabelField("Client", EditorStyles.boldLabel);
EditorGUILayout.HelpBox("Validating the servers public key is essential for complete man-in-the-middle (MITM) safety, but might not be feasible for all modes of hosting.", MessageType.Info);
EditorGUILayout.PropertyField(clientValidatesServerPubKeyProperty, new GUIContent("Validate Server Public Key"));
EncryptionTransport.ValidationMode validationMode = (EncryptionTransport.ValidationMode)clientValidatesServerPubKeyProperty.enumValueIndex;
switch (validationMode)
{
case EncryptionTransport.ValidationMode.List:
EditorGUILayout.PropertyField(clientTrustedPubKeySignaturesProperty);
break;
case EncryptionTransport.ValidationMode.Callback:
EditorGUILayout.HelpBox("Please set the EncryptionTransport.onClientValidateServerPubKey at runtime.", MessageType.Info);
break;
}
EditorGUILayout.Separator();
// Server Section
EditorGUILayout.LabelField("Server", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(serverLoadKeyPairFromFileProperty, new GUIContent("Load Keypair From File"));
if (serverLoadKeyPairFromFileProperty.boolValue)
{
EditorGUILayout.PropertyField(serverKeypairPathProperty, new GUIContent("Keypair File Path"));
}
if(GUILayout.Button("Generate Key Pair"))
{
EncryptionCredentials keyPair = EncryptionCredentials.Generate();
string path = EditorUtility.SaveFilePanel("Select where to save the keypair", "", "server-keys.json", "json");
if (!string.IsNullOrEmpty(path))
{
keyPair.SaveToFile(path);
EditorUtility.DisplayDialog("KeyPair Saved", $"Successfully saved the keypair.\nThe fingerprint is {keyPair.PublicKeyFingerprint}, you can also retrieve it from the saved json file at any point.", "Ok");
if (validationMode == EncryptionTransport.ValidationMode.List)
{
if (EditorUtility.DisplayDialog("Add key to trusted list?", "Do you also want to add the generated key to the trusted list?", "Yes", "No"))
{
clientTrustedPubKeySignaturesProperty.arraySize++;
clientTrustedPubKeySignaturesProperty.GetArrayElementAtIndex(clientTrustedPubKeySignaturesProperty.arraySize - 1).stringValue = keyPair.PublicKeyFingerprint;
}
}
}
}
serializedObject.ApplyModifiedProperties();
}
[CustomEditor(typeof(ThreadedEncryptionKcpTransport), true)]
class EncryptionThreadedTransportInspector : EncryptionTransportInspector {}
}
}
using UnityEditor;
using UnityEngine;
namespace Mirror.Transports.Encryption
{
[CustomEditor(typeof(EncryptionTransport), true)]
public class EncryptionTransportInspector : UnityEditor.Editor
{
SerializedProperty innerProperty;
SerializedProperty clientValidatesServerPubKeyProperty;
SerializedProperty clientTrustedPubKeySignaturesProperty;
SerializedProperty serverKeypairPathProperty;
SerializedProperty serverLoadKeyPairFromFileProperty;
// Assuming proper SerializedProperty definitions for properties
// Add more SerializedProperty fields related to different modes as needed
void OnEnable()
{
innerProperty = serializedObject.FindProperty("Inner");
clientValidatesServerPubKeyProperty = serializedObject.FindProperty("ClientValidateServerPubKey");
clientTrustedPubKeySignaturesProperty = serializedObject.FindProperty("ClientTrustedPubKeySignatures");
serverKeypairPathProperty = serializedObject.FindProperty("ServerKeypairPath");
serverLoadKeyPairFromFileProperty = serializedObject.FindProperty("ServerLoadKeyPairFromFile");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// Draw default inspector for the parent class
DrawDefaultInspector();
EditorGUILayout.LabelField("Encryption Settings", EditorStyles.boldLabel);
if (innerProperty != null)
{
EditorGUILayout.LabelField("Common", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(innerProperty);
EditorGUILayout.Separator();
}
// Client Section
EditorGUILayout.LabelField("Client", EditorStyles.boldLabel);
EditorGUILayout.HelpBox("Validating the servers public key is essential for complete man-in-the-middle (MITM) safety, but might not be feasible for all modes of hosting.", MessageType.Info);
EditorGUILayout.PropertyField(clientValidatesServerPubKeyProperty, new GUIContent("Validate Server Public Key"));
EncryptionTransport.ValidationMode validationMode = (EncryptionTransport.ValidationMode)clientValidatesServerPubKeyProperty.enumValueIndex;
switch (validationMode)
{
case EncryptionTransport.ValidationMode.List:
EditorGUILayout.PropertyField(clientTrustedPubKeySignaturesProperty);
break;
case EncryptionTransport.ValidationMode.Callback:
EditorGUILayout.HelpBox("Please set the EncryptionTransport.onClientValidateServerPubKey at runtime.", MessageType.Info);
break;
}
EditorGUILayout.Separator();
// Server Section
EditorGUILayout.LabelField("Server", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(serverLoadKeyPairFromFileProperty, new GUIContent("Load Keypair From File"));
if (serverLoadKeyPairFromFileProperty.boolValue)
{
EditorGUILayout.PropertyField(serverKeypairPathProperty, new GUIContent("Keypair File Path"));
}
if(GUILayout.Button("Generate Key Pair"))
{
EncryptionCredentials keyPair = EncryptionCredentials.Generate();
string path = EditorUtility.SaveFilePanel("Select where to save the keypair", "", "server-keys.json", "json");
if (!string.IsNullOrEmpty(path))
{
keyPair.SaveToFile(path);
EditorUtility.DisplayDialog("KeyPair Saved", $"Successfully saved the keypair.\nThe fingerprint is {keyPair.PublicKeyFingerprint}, you can also retrieve it from the saved json file at any point.", "Ok");
if (validationMode == EncryptionTransport.ValidationMode.List)
{
if (EditorUtility.DisplayDialog("Add key to trusted list?", "Do you also want to add the generated key to the trusted list?", "Yes", "No"))
{
clientTrustedPubKeySignaturesProperty.arraySize++;
clientTrustedPubKeySignaturesProperty.GetArrayElementAtIndex(clientTrustedPubKeySignaturesProperty.arraySize - 1).stringValue = keyPair.PublicKeyFingerprint;
}
}
}
}
serializedObject.ApplyModifiedProperties();
}
[CustomEditor(typeof(ThreadedEncryptionKcpTransport), true)]
class EncryptionThreadedTransportInspector : EncryptionTransportInspector {}
}
}
@@ -1,10 +1,10 @@
fileFormatVersion: 2
guid: 871580d2094a46139279d651cec92b5d
timeCreated: 1708794004
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/Editor/EncryptionTransportInspector.cs
uploadId: 736421
fileFormatVersion: 2
guid: 871580d2094a46139279d651cec92b5d
timeCreated: 1708794004
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/Editor/EncryptionTransportInspector.cs
uploadId: 736421
File diff suppressed because it is too large Load Diff
@@ -1,10 +1,10 @@
fileFormatVersion: 2
guid: 28f3ac4ff1d346a895d0b4ff714fb57b
timeCreated: 1708111337
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/EncryptedConnection.cs
uploadId: 736421
fileFormatVersion: 2
guid: 28f3ac4ff1d346a895d0b4ff714fb57b
timeCreated: 1708111337
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/EncryptedConnection.cs
uploadId: 736421
@@ -1,119 +1,119 @@
using System;
using System.IO;
using Mirror.BouncyCastle.Asn1.Pkcs;
using Mirror.BouncyCastle.Asn1.X509;
using Mirror.BouncyCastle.Crypto;
using Mirror.BouncyCastle.Crypto.Digests;
using Mirror.BouncyCastle.Crypto.Generators;
using Mirror.BouncyCastle.X509;
using Mirror.BouncyCastle.Crypto.Parameters;
using Mirror.BouncyCastle.Pkcs;
using Mirror.BouncyCastle.Security;
using UnityEngine;
namespace Mirror.Transports.Encryption
{
public class EncryptionCredentials
{
const int PrivateKeyBits = 256;
// don't actually need to store this currently
// but we'll need to for loading/saving from file maybe?
// public ECPublicKeyParameters PublicKey;
// The serialized public key, in DER format
public byte[] PublicKeySerialized;
public ECPrivateKeyParameters PrivateKey;
public string PublicKeyFingerprint;
EncryptionCredentials() {}
// TODO: load from file
public static EncryptionCredentials Generate()
{
var generator = new ECKeyPairGenerator();
generator.Init(new KeyGenerationParameters(new SecureRandom(), PrivateKeyBits));
AsymmetricCipherKeyPair keyPair = generator.GenerateKeyPair();
var serialized = SerializePublicKey((ECPublicKeyParameters)keyPair.Public);
return new EncryptionCredentials
{
// see fields above
// PublicKey = (ECPublicKeyParameters)keyPair.Public,
PublicKeySerialized = serialized,
PublicKeyFingerprint = PubKeyFingerprint(new ArraySegment<byte>(serialized)),
PrivateKey = (ECPrivateKeyParameters)keyPair.Private
};
}
public static byte[] SerializePublicKey(AsymmetricKeyParameter publicKey)
{
// apparently the best way to transmit this public key over the network is to serialize it as a DER
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKey);
return publicKeyInfo.ToAsn1Object().GetDerEncoded();
}
public static AsymmetricKeyParameter DeserializePublicKey(ArraySegment<byte> pubKey) =>
// And then we do this to deserialize from the DER (from above)
// the "new MemoryStream" actually saves an allocation, since otherwise the ArraySegment would be converted
// to a byte[] first and then shoved through a MemoryStream
PublicKeyFactory.CreateKey(new MemoryStream(pubKey.Array, pubKey.Offset, pubKey.Count, false));
public static byte[] SerializePrivateKey(AsymmetricKeyParameter privateKey)
{
// Serialize privateKey as a DER
PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKey);
return privateKeyInfo.ToAsn1Object().GetDerEncoded();
}
public static AsymmetricKeyParameter DeserializePrivateKey(ArraySegment<byte> privateKey) =>
// And then we do this to deserialize from the DER (from above)
// the "new MemoryStream" actually saves an allocation, since otherwise the ArraySegment would be converted
// to a byte[] first and then shoved through a MemoryStream
PrivateKeyFactory.CreateKey(new MemoryStream(privateKey.Array, privateKey.Offset, privateKey.Count, false));
public static string PubKeyFingerprint(ArraySegment<byte> publicKeyBytes)
{
Sha256Digest digest = new Sha256Digest();
byte[] hash = new byte[digest.GetDigestSize()];
digest.BlockUpdate(publicKeyBytes.Array, publicKeyBytes.Offset, publicKeyBytes.Count);
digest.DoFinal(hash, 0);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
public void SaveToFile(string path)
{
string json = JsonUtility.ToJson(new SerializedPair
{
PublicKeyFingerprint = PublicKeyFingerprint,
PublicKey = Convert.ToBase64String(PublicKeySerialized),
PrivateKey= Convert.ToBase64String(SerializePrivateKey(PrivateKey))
});
File.WriteAllText(path, json);
}
public static EncryptionCredentials LoadFromFile(string path)
{
string json = File.ReadAllText(path);
SerializedPair serializedPair = JsonUtility.FromJson<SerializedPair>(json);
byte[] publicKeyBytes = Convert.FromBase64String(serializedPair.PublicKey);
byte[] privateKeyBytes = Convert.FromBase64String(serializedPair.PrivateKey);
if (serializedPair.PublicKeyFingerprint != PubKeyFingerprint(new ArraySegment<byte>(publicKeyBytes)))
throw new Exception("Saved public key fingerprint does not match public key.");
return new EncryptionCredentials
{
PublicKeySerialized = publicKeyBytes,
PublicKeyFingerprint = serializedPair.PublicKeyFingerprint,
PrivateKey = (ECPrivateKeyParameters) DeserializePrivateKey(new ArraySegment<byte>(privateKeyBytes))
};
}
class SerializedPair
{
public string PublicKeyFingerprint;
public string PublicKey;
public string PrivateKey;
}
}
}
using System;
using System.IO;
using Mirror.BouncyCastle.Asn1.Pkcs;
using Mirror.BouncyCastle.Asn1.X509;
using Mirror.BouncyCastle.Crypto;
using Mirror.BouncyCastle.Crypto.Digests;
using Mirror.BouncyCastle.Crypto.Generators;
using Mirror.BouncyCastle.X509;
using Mirror.BouncyCastle.Crypto.Parameters;
using Mirror.BouncyCastle.Pkcs;
using Mirror.BouncyCastle.Security;
using UnityEngine;
namespace Mirror.Transports.Encryption
{
public class EncryptionCredentials
{
const int PrivateKeyBits = 256;
// don't actually need to store this currently
// but we'll need to for loading/saving from file maybe?
// public ECPublicKeyParameters PublicKey;
// The serialized public key, in DER format
public byte[] PublicKeySerialized;
public ECPrivateKeyParameters PrivateKey;
public string PublicKeyFingerprint;
EncryptionCredentials() {}
// TODO: load from file
public static EncryptionCredentials Generate()
{
var generator = new ECKeyPairGenerator();
generator.Init(new KeyGenerationParameters(new SecureRandom(), PrivateKeyBits));
AsymmetricCipherKeyPair keyPair = generator.GenerateKeyPair();
var serialized = SerializePublicKey((ECPublicKeyParameters)keyPair.Public);
return new EncryptionCredentials
{
// see fields above
// PublicKey = (ECPublicKeyParameters)keyPair.Public,
PublicKeySerialized = serialized,
PublicKeyFingerprint = PubKeyFingerprint(new ArraySegment<byte>(serialized)),
PrivateKey = (ECPrivateKeyParameters)keyPair.Private
};
}
public static byte[] SerializePublicKey(AsymmetricKeyParameter publicKey)
{
// apparently the best way to transmit this public key over the network is to serialize it as a DER
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKey);
return publicKeyInfo.ToAsn1Object().GetDerEncoded();
}
public static AsymmetricKeyParameter DeserializePublicKey(ArraySegment<byte> pubKey) =>
// And then we do this to deserialize from the DER (from above)
// the "new MemoryStream" actually saves an allocation, since otherwise the ArraySegment would be converted
// to a byte[] first and then shoved through a MemoryStream
PublicKeyFactory.CreateKey(new MemoryStream(pubKey.Array, pubKey.Offset, pubKey.Count, false));
public static byte[] SerializePrivateKey(AsymmetricKeyParameter privateKey)
{
// Serialize privateKey as a DER
PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKey);
return privateKeyInfo.ToAsn1Object().GetDerEncoded();
}
public static AsymmetricKeyParameter DeserializePrivateKey(ArraySegment<byte> privateKey) =>
// And then we do this to deserialize from the DER (from above)
// the "new MemoryStream" actually saves an allocation, since otherwise the ArraySegment would be converted
// to a byte[] first and then shoved through a MemoryStream
PrivateKeyFactory.CreateKey(new MemoryStream(privateKey.Array, privateKey.Offset, privateKey.Count, false));
public static string PubKeyFingerprint(ArraySegment<byte> publicKeyBytes)
{
Sha256Digest digest = new Sha256Digest();
byte[] hash = new byte[digest.GetDigestSize()];
digest.BlockUpdate(publicKeyBytes.Array, publicKeyBytes.Offset, publicKeyBytes.Count);
digest.DoFinal(hash, 0);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
public void SaveToFile(string path)
{
string json = JsonUtility.ToJson(new SerializedPair
{
PublicKeyFingerprint = PublicKeyFingerprint,
PublicKey = Convert.ToBase64String(PublicKeySerialized),
PrivateKey= Convert.ToBase64String(SerializePrivateKey(PrivateKey))
});
File.WriteAllText(path, json);
}
public static EncryptionCredentials LoadFromFile(string path)
{
string json = File.ReadAllText(path);
SerializedPair serializedPair = JsonUtility.FromJson<SerializedPair>(json);
byte[] publicKeyBytes = Convert.FromBase64String(serializedPair.PublicKey);
byte[] privateKeyBytes = Convert.FromBase64String(serializedPair.PrivateKey);
if (serializedPair.PublicKeyFingerprint != PubKeyFingerprint(new ArraySegment<byte>(publicKeyBytes)))
throw new Exception("Saved public key fingerprint does not match public key.");
return new EncryptionCredentials
{
PublicKeySerialized = publicKeyBytes,
PublicKeyFingerprint = serializedPair.PublicKeyFingerprint,
PrivateKey = (ECPrivateKeyParameters) DeserializePrivateKey(new ArraySegment<byte>(privateKeyBytes))
};
}
class SerializedPair
{
public string PublicKeyFingerprint;
public string PublicKey;
public string PrivateKey;
}
}
}
@@ -1,10 +1,10 @@
fileFormatVersion: 2
guid: af6ae5f74f9548588cba5731643fabaf
timeCreated: 1708139579
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/EncryptionCredentials.cs
uploadId: 736421
fileFormatVersion: 2
guid: af6ae5f74f9548588cba5731643fabaf
timeCreated: 1708139579
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/EncryptionCredentials.cs
uploadId: 736421
@@ -1,289 +1,289 @@
using System;
using System.Collections.Generic;
using Mirror.BouncyCastle.Crypto;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Serialization;
namespace Mirror.Transports.Encryption
{
[HelpURL("https://mirror-networking.gitbook.io/docs/manual/transports/encryption-transport")]
public class EncryptionTransport : Transport, PortTransport
{
public override bool IsEncrypted => true;
public override string EncryptionCipher => "AES256-GCM";
[FormerlySerializedAs("inner")]
[HideInInspector]
public Transport Inner;
public ushort Port
{
get
{
if (Inner is PortTransport portTransport)
return portTransport.Port;
Debug.LogError($"EncryptionTransport can't get Port because {Inner} is not a PortTransport");
return 0;
}
set
{
if (Inner is PortTransport portTransport)
{
portTransport.Port = value;
return;
}
Debug.LogError($"EncryptionTransport can't set Port because {Inner} is not a PortTransport");
}
}
public enum ValidationMode
{
Off,
List,
Callback
}
[FormerlySerializedAs("clientValidateServerPubKey")]
[HideInInspector]
public ValidationMode ClientValidateServerPubKey;
[FormerlySerializedAs("clientTrustedPubKeySignatures")]
[HideInInspector]
[Tooltip("List of public key fingerprints the client will accept")]
public string[] ClientTrustedPubKeySignatures;
public Func<PubKeyInfo, bool> OnClientValidateServerPubKey;
[FormerlySerializedAs("serverLoadKeyPairFromFile")]
[HideInInspector]
public bool ServerLoadKeyPairFromFile;
[FormerlySerializedAs("serverKeypairPath")]
[HideInInspector]
public string ServerKeypairPath = "./server-keys.json";
EncryptedConnection client;
readonly Dictionary<int, EncryptedConnection> serverConnections = new Dictionary<int, EncryptedConnection>();
readonly List<EncryptedConnection> serverPendingConnections =
new List<EncryptedConnection>();
EncryptionCredentials credentials;
public string EncryptionPublicKeyFingerprint => credentials?.PublicKeyFingerprint;
public byte[] EncryptionPublicKey => credentials?.PublicKeySerialized;
void ServerRemoveFromPending(EncryptedConnection con)
{
for (int i = 0; i < serverPendingConnections.Count; i++)
if (serverPendingConnections[i] == con)
{
// remove by swapping with last
int lastIndex = serverPendingConnections.Count - 1;
serverPendingConnections[i] = serverPendingConnections[lastIndex];
serverPendingConnections.RemoveAt(lastIndex);
break;
}
}
void HandleInnerServerDisconnected(int connId)
{
if (serverConnections.TryGetValue(connId, out EncryptedConnection con))
{
ServerRemoveFromPending(con);
serverConnections.Remove(connId);
}
OnServerDisconnected?.Invoke(connId);
}
void HandleInnerServerError(int connId, TransportError type, string msg) => OnServerError?.Invoke(connId, type, $"inner: {msg}");
void HandleInnerServerDataReceived(int connId, ArraySegment<byte> data, int channel)
{
if (serverConnections.TryGetValue(connId, out EncryptedConnection c))
c.OnReceiveRaw(data, channel);
}
void HandleInnerServerConnected(int connId) => HandleInnerServerConnected(connId, Inner.ServerGetClientAddress(connId));
void HandleInnerServerConnected(int connId, string clientRemoteAddress)
{
Debug.Log($"[EncryptionTransport] New connection #{connId} from {clientRemoteAddress}");
EncryptedConnection ec = null;
ec = new EncryptedConnection(
credentials,
false,
(segment, channel) => Inner.ServerSend(connId, segment, channel),
(segment, channel) => OnServerDataReceived?.Invoke(connId, segment, channel),
() =>
{
Debug.Log($"[EncryptionTransport] Connection #{connId} is ready");
// ReSharper disable once AccessToModifiedClosure
ServerRemoveFromPending(ec);
OnServerConnectedWithAddress?.Invoke(connId, clientRemoteAddress);
},
(type, msg) =>
{
OnServerError?.Invoke(connId, type, msg);
ServerDisconnect(connId);
});
serverConnections.Add(connId, ec);
serverPendingConnections.Add(ec);
}
void HandleInnerClientDisconnected()
{
client = null;
OnClientDisconnected?.Invoke();
}
void HandleInnerClientError(TransportError arg1, string arg2) => OnClientError?.Invoke(arg1, $"inner: {arg2}");
void HandleInnerClientDataReceived(ArraySegment<byte> data, int channel) => client?.OnReceiveRaw(data, channel);
void HandleInnerClientConnected() =>
client = new EncryptedConnection(
credentials,
true,
(segment, channel) => Inner.ClientSend(segment, channel),
(segment, channel) => OnClientDataReceived?.Invoke(segment, channel),
() =>
{
OnClientConnected?.Invoke();
},
(type, msg) =>
{
OnClientError?.Invoke(type, msg);
ClientDisconnect();
},
HandleClientValidateServerPubKey);
bool HandleClientValidateServerPubKey(PubKeyInfo pubKeyInfo)
{
switch (ClientValidateServerPubKey)
{
case ValidationMode.Off:
return true;
case ValidationMode.List:
return Array.IndexOf(ClientTrustedPubKeySignatures, pubKeyInfo.Fingerprint) >= 0;
case ValidationMode.Callback:
return OnClientValidateServerPubKey(pubKeyInfo);
default:
throw new ArgumentOutOfRangeException();
}
}
void Awake() =>
// check if encryption via hardware acceleration is supported.
// this can be useful to know for low end devices.
//
// hardware acceleration requires netcoreapp3.0 or later:
// https://github.com/bcgit/bc-csharp/blob/449940429c57686a6fcf6bfbb4d368dec19d906e/crypto/src/crypto/AesUtilities.cs#L18
// because AesEngine_x86 requires System.Runtime.Intrinsics.X86:
// https://github.com/bcgit/bc-csharp/blob/449940429c57686a6fcf6bfbb4d368dec19d906e/crypto/src/crypto/engines/AesEngine_X86.cs
// which Unity does not support yet.
Debug.Log($"EncryptionTransport: IsHardwareAccelerated={AesUtilities.IsHardwareAccelerated}");
public override bool Available() => Inner.Available();
public override bool ClientConnected() => client != null && client.IsReady;
public override void ClientConnect(string address)
{
switch (ClientValidateServerPubKey)
{
case ValidationMode.Off:
break;
case ValidationMode.List:
if (ClientTrustedPubKeySignatures == null || ClientTrustedPubKeySignatures.Length == 0)
{
OnClientError?.Invoke(TransportError.Unexpected, "Validate Server Public Key is set to List, but the clientTrustedPubKeySignatures list is empty.");
return;
}
break;
case ValidationMode.Callback:
if (OnClientValidateServerPubKey == null)
{
OnClientError?.Invoke(TransportError.Unexpected, "Validate Server Public Key is set to Callback, but the onClientValidateServerPubKey handler is not set");
return;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
credentials = EncryptionCredentials.Generate();
Inner.OnClientConnected = HandleInnerClientConnected;
Inner.OnClientDataReceived = HandleInnerClientDataReceived;
Inner.OnClientDataSent = (bytes, channel) => OnClientDataSent?.Invoke(bytes, channel);
Inner.OnClientError = HandleInnerClientError;
Inner.OnClientDisconnected = HandleInnerClientDisconnected;
Inner.ClientConnect(address);
}
public override void ClientSend(ArraySegment<byte> segment, int channelId = Channels.Reliable) =>
client?.Send(segment, channelId);
public override void ClientDisconnect() => Inner.ClientDisconnect();
public override Uri ServerUri() => Inner.ServerUri();
public override bool ServerActive() => Inner.ServerActive();
public override void ServerStart()
{
if (ServerLoadKeyPairFromFile)
credentials = EncryptionCredentials.LoadFromFile(ServerKeypairPath);
else
credentials = EncryptionCredentials.Generate();
#pragma warning disable CS0618 // Type or member is obsolete
Inner.OnServerConnected = HandleInnerServerConnected;
#pragma warning restore CS0618 // Type or member is obsolete
Inner.OnServerConnectedWithAddress = HandleInnerServerConnected;
Inner.OnServerDataReceived = HandleInnerServerDataReceived;
Inner.OnServerDataSent = (connId, bytes, channel) => OnServerDataSent?.Invoke(connId, bytes, channel);
Inner.OnServerError = HandleInnerServerError;
Inner.OnServerDisconnected = HandleInnerServerDisconnected;
Inner.ServerStart();
}
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId = Channels.Reliable)
{
if (serverConnections.TryGetValue(connectionId, out EncryptedConnection connection) && connection.IsReady)
connection.Send(segment, channelId);
}
public override void ServerDisconnect(int connectionId) =>
// cleanup is done via inners disconnect event
Inner.ServerDisconnect(connectionId);
public override string ServerGetClientAddress(int connectionId) => Inner.ServerGetClientAddress(connectionId);
public override void ServerStop() => Inner.ServerStop();
public override int GetMaxPacketSize(int channelId = Channels.Reliable) =>
Inner.GetMaxPacketSize(channelId) - EncryptedConnection.Overhead;
public override int GetBatchThreshold(int channelId = Channels.Reliable) => Inner.GetBatchThreshold(channelId) - EncryptedConnection.Overhead;
public override void Shutdown() => Inner.Shutdown();
public override void ClientEarlyUpdate() => Inner.ClientEarlyUpdate();
public override void ClientLateUpdate()
{
Inner.ClientLateUpdate();
Profiler.BeginSample("EncryptionTransport.ServerLateUpdate");
client?.TickNonReady(NetworkTime.localTime);
Profiler.EndSample();
}
public override void ServerEarlyUpdate() => Inner.ServerEarlyUpdate();
public override void ServerLateUpdate()
{
Inner.ServerLateUpdate();
Profiler.BeginSample("EncryptionTransport.ServerLateUpdate");
// Reverse iteration as entries can be removed while updating
for (int i = serverPendingConnections.Count - 1; i >= 0; i--)
serverPendingConnections[i].TickNonReady(NetworkTime.time);
Profiler.EndSample();
}
}
}
using System;
using System.Collections.Generic;
using Mirror.BouncyCastle.Crypto;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Serialization;
namespace Mirror.Transports.Encryption
{
[HelpURL("https://mirror-networking.gitbook.io/docs/manual/transports/encryption-transport")]
public class EncryptionTransport : Transport, PortTransport
{
public override bool IsEncrypted => true;
public override string EncryptionCipher => "AES256-GCM";
[FormerlySerializedAs("inner")]
[HideInInspector]
public Transport Inner;
public ushort Port
{
get
{
if (Inner is PortTransport portTransport)
return portTransport.Port;
Debug.LogError($"EncryptionTransport can't get Port because {Inner} is not a PortTransport");
return 0;
}
set
{
if (Inner is PortTransport portTransport)
{
portTransport.Port = value;
return;
}
Debug.LogError($"EncryptionTransport can't set Port because {Inner} is not a PortTransport");
}
}
public enum ValidationMode
{
Off,
List,
Callback
}
[FormerlySerializedAs("clientValidateServerPubKey")]
[HideInInspector]
public ValidationMode ClientValidateServerPubKey;
[FormerlySerializedAs("clientTrustedPubKeySignatures")]
[HideInInspector]
[Tooltip("List of public key fingerprints the client will accept")]
public string[] ClientTrustedPubKeySignatures;
public Func<PubKeyInfo, bool> OnClientValidateServerPubKey;
[FormerlySerializedAs("serverLoadKeyPairFromFile")]
[HideInInspector]
public bool ServerLoadKeyPairFromFile;
[FormerlySerializedAs("serverKeypairPath")]
[HideInInspector]
public string ServerKeypairPath = "./server-keys.json";
EncryptedConnection client;
readonly Dictionary<int, EncryptedConnection> serverConnections = new Dictionary<int, EncryptedConnection>();
readonly List<EncryptedConnection> serverPendingConnections =
new List<EncryptedConnection>();
EncryptionCredentials credentials;
public string EncryptionPublicKeyFingerprint => credentials?.PublicKeyFingerprint;
public byte[] EncryptionPublicKey => credentials?.PublicKeySerialized;
void ServerRemoveFromPending(EncryptedConnection con)
{
for (int i = 0; i < serverPendingConnections.Count; i++)
if (serverPendingConnections[i] == con)
{
// remove by swapping with last
int lastIndex = serverPendingConnections.Count - 1;
serverPendingConnections[i] = serverPendingConnections[lastIndex];
serverPendingConnections.RemoveAt(lastIndex);
break;
}
}
void HandleInnerServerDisconnected(int connId)
{
if (serverConnections.TryGetValue(connId, out EncryptedConnection con))
{
ServerRemoveFromPending(con);
serverConnections.Remove(connId);
}
OnServerDisconnected?.Invoke(connId);
}
void HandleInnerServerError(int connId, TransportError type, string msg) => OnServerError?.Invoke(connId, type, $"inner: {msg}");
void HandleInnerServerDataReceived(int connId, ArraySegment<byte> data, int channel)
{
if (serverConnections.TryGetValue(connId, out EncryptedConnection c))
c.OnReceiveRaw(data, channel);
}
void HandleInnerServerConnected(int connId) => HandleInnerServerConnected(connId, Inner.ServerGetClientAddress(connId));
void HandleInnerServerConnected(int connId, string clientRemoteAddress)
{
Debug.Log($"[EncryptionTransport] New connection #{connId} from {clientRemoteAddress}");
EncryptedConnection ec = null;
ec = new EncryptedConnection(
credentials,
false,
(segment, channel) => Inner.ServerSend(connId, segment, channel),
(segment, channel) => OnServerDataReceived?.Invoke(connId, segment, channel),
() =>
{
Debug.Log($"[EncryptionTransport] Connection #{connId} is ready");
// ReSharper disable once AccessToModifiedClosure
ServerRemoveFromPending(ec);
OnServerConnectedWithAddress?.Invoke(connId, clientRemoteAddress);
},
(type, msg) =>
{
OnServerError?.Invoke(connId, type, msg);
ServerDisconnect(connId);
});
serverConnections.Add(connId, ec);
serverPendingConnections.Add(ec);
}
void HandleInnerClientDisconnected()
{
client = null;
OnClientDisconnected?.Invoke();
}
void HandleInnerClientError(TransportError arg1, string arg2) => OnClientError?.Invoke(arg1, $"inner: {arg2}");
void HandleInnerClientDataReceived(ArraySegment<byte> data, int channel) => client?.OnReceiveRaw(data, channel);
void HandleInnerClientConnected() =>
client = new EncryptedConnection(
credentials,
true,
(segment, channel) => Inner.ClientSend(segment, channel),
(segment, channel) => OnClientDataReceived?.Invoke(segment, channel),
() =>
{
OnClientConnected?.Invoke();
},
(type, msg) =>
{
OnClientError?.Invoke(type, msg);
ClientDisconnect();
},
HandleClientValidateServerPubKey);
bool HandleClientValidateServerPubKey(PubKeyInfo pubKeyInfo)
{
switch (ClientValidateServerPubKey)
{
case ValidationMode.Off:
return true;
case ValidationMode.List:
return Array.IndexOf(ClientTrustedPubKeySignatures, pubKeyInfo.Fingerprint) >= 0;
case ValidationMode.Callback:
return OnClientValidateServerPubKey(pubKeyInfo);
default:
throw new ArgumentOutOfRangeException();
}
}
void Awake() =>
// check if encryption via hardware acceleration is supported.
// this can be useful to know for low end devices.
//
// hardware acceleration requires netcoreapp3.0 or later:
// https://github.com/bcgit/bc-csharp/blob/449940429c57686a6fcf6bfbb4d368dec19d906e/crypto/src/crypto/AesUtilities.cs#L18
// because AesEngine_x86 requires System.Runtime.Intrinsics.X86:
// https://github.com/bcgit/bc-csharp/blob/449940429c57686a6fcf6bfbb4d368dec19d906e/crypto/src/crypto/engines/AesEngine_X86.cs
// which Unity does not support yet.
Debug.Log($"EncryptionTransport: IsHardwareAccelerated={AesUtilities.IsHardwareAccelerated}");
public override bool Available() => Inner.Available();
public override bool ClientConnected() => client != null && client.IsReady;
public override void ClientConnect(string address)
{
switch (ClientValidateServerPubKey)
{
case ValidationMode.Off:
break;
case ValidationMode.List:
if (ClientTrustedPubKeySignatures == null || ClientTrustedPubKeySignatures.Length == 0)
{
OnClientError?.Invoke(TransportError.Unexpected, "Validate Server Public Key is set to List, but the clientTrustedPubKeySignatures list is empty.");
return;
}
break;
case ValidationMode.Callback:
if (OnClientValidateServerPubKey == null)
{
OnClientError?.Invoke(TransportError.Unexpected, "Validate Server Public Key is set to Callback, but the onClientValidateServerPubKey handler is not set");
return;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
credentials = EncryptionCredentials.Generate();
Inner.OnClientConnected = HandleInnerClientConnected;
Inner.OnClientDataReceived = HandleInnerClientDataReceived;
Inner.OnClientDataSent = (bytes, channel) => OnClientDataSent?.Invoke(bytes, channel);
Inner.OnClientError = HandleInnerClientError;
Inner.OnClientDisconnected = HandleInnerClientDisconnected;
Inner.ClientConnect(address);
}
public override void ClientSend(ArraySegment<byte> segment, int channelId = Channels.Reliable) =>
client?.Send(segment, channelId);
public override void ClientDisconnect() => Inner.ClientDisconnect();
public override Uri ServerUri() => Inner.ServerUri();
public override bool ServerActive() => Inner.ServerActive();
public override void ServerStart()
{
if (ServerLoadKeyPairFromFile)
credentials = EncryptionCredentials.LoadFromFile(ServerKeypairPath);
else
credentials = EncryptionCredentials.Generate();
#pragma warning disable CS0618 // Type or member is obsolete
Inner.OnServerConnected = HandleInnerServerConnected;
#pragma warning restore CS0618 // Type or member is obsolete
Inner.OnServerConnectedWithAddress = HandleInnerServerConnected;
Inner.OnServerDataReceived = HandleInnerServerDataReceived;
Inner.OnServerDataSent = (connId, bytes, channel) => OnServerDataSent?.Invoke(connId, bytes, channel);
Inner.OnServerError = HandleInnerServerError;
Inner.OnServerDisconnected = HandleInnerServerDisconnected;
Inner.ServerStart();
}
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId = Channels.Reliable)
{
if (serverConnections.TryGetValue(connectionId, out EncryptedConnection connection) && connection.IsReady)
connection.Send(segment, channelId);
}
public override void ServerDisconnect(int connectionId) =>
// cleanup is done via inners disconnect event
Inner.ServerDisconnect(connectionId);
public override string ServerGetClientAddress(int connectionId) => Inner.ServerGetClientAddress(connectionId);
public override void ServerStop() => Inner.ServerStop();
public override int GetMaxPacketSize(int channelId = Channels.Reliable) =>
Inner.GetMaxPacketSize(channelId) - EncryptedConnection.Overhead;
public override int GetBatchThreshold(int channelId = Channels.Reliable) => Inner.GetBatchThreshold(channelId) - EncryptedConnection.Overhead;
public override void Shutdown() => Inner.Shutdown();
public override void ClientEarlyUpdate() => Inner.ClientEarlyUpdate();
public override void ClientLateUpdate()
{
Inner.ClientLateUpdate();
Profiler.BeginSample("EncryptionTransport.ServerLateUpdate");
client?.TickNonReady(NetworkTime.localTime);
Profiler.EndSample();
}
public override void ServerEarlyUpdate() => Inner.ServerEarlyUpdate();
public override void ServerLateUpdate()
{
Inner.ServerLateUpdate();
Profiler.BeginSample("EncryptionTransport.ServerLateUpdate");
// Reverse iteration as entries can be removed while updating
for (int i = serverPendingConnections.Count - 1; i >= 0; i--)
serverPendingConnections[i].TickNonReady(NetworkTime.time);
Profiler.EndSample();
}
}
}
@@ -1,18 +1,18 @@
fileFormatVersion: 2
guid: 0aa135acc32a4383ae9a5817f018cb06
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/EncryptionTransport.cs
uploadId: 736421
fileFormatVersion: 2
guid: 0aa135acc32a4383ae9a5817f018cb06
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/EncryptionTransport.cs
uploadId: 736421
@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 4048f5ff245dfa34abec0a401364e7c0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 4048f5ff245dfa34abec0a401364e7c0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 31ff83bf6d2e72542adcbe2c21383f4a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 31ff83bf6d2e72542adcbe2c21383f4a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,15 +1,15 @@
Version with renamed namespaces to avoid conflicts lives here: https://github.com/MirrorNetworking/bc-csharp
Copyright (c) 2000-2024 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org).
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sub license, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: The above copyright notice and this
permission notice shall be included in all copies or substantial portions of the Software.
**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.**
Version with renamed namespaces to avoid conflicts lives here: https://github.com/MirrorNetworking/bc-csharp
Copyright (c) 2000-2024 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org).
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sub license, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: The above copyright notice and this
permission notice shall be included in all copies or substantial portions of the Software.
**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.**
@@ -1,14 +1,14 @@
fileFormatVersion: 2
guid: 2b45a99b5583cda419e1f1ec943fec4b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/Plugins/BouncyCastle/LICENSE.md
uploadId: 736421
fileFormatVersion: 2
guid: 2b45a99b5583cda419e1f1ec943fec4b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/Plugins/BouncyCastle/LICENSE.md
uploadId: 736421
@@ -1,40 +1,40 @@
fileFormatVersion: 2
guid: 03a89f29994a3b44cb3015b3c5ece010
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/Plugins/BouncyCastle/Mirror.BouncyCastle.Cryptography.dll
uploadId: 736421
fileFormatVersion: 2
guid: 03a89f29994a3b44cb3015b3c5ece010
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/Plugins/BouncyCastle/Mirror.BouncyCastle.Cryptography.dll
uploadId: 736421
@@ -1,12 +1,12 @@
using System;
using Mirror.BouncyCastle.Crypto;
namespace Mirror.Transports.Encryption
{
public struct PubKeyInfo
{
public string Fingerprint;
public ArraySegment<byte> Serialized;
public AsymmetricKeyParameter Key;
}
}
using System;
using Mirror.BouncyCastle.Crypto;
namespace Mirror.Transports.Encryption
{
public struct PubKeyInfo
{
public string Fingerprint;
public ArraySegment<byte> Serialized;
public AsymmetricKeyParameter Key;
}
}
@@ -1,10 +1,10 @@
fileFormatVersion: 2
guid: e1e3744418024c02acf39f44c1d1bd20
timeCreated: 1708874062
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/PubKeyInfo.cs
uploadId: 736421
fileFormatVersion: 2
guid: e1e3744418024c02acf39f44c1d1bd20
timeCreated: 1708874062
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/PubKeyInfo.cs
uploadId: 736421
@@ -1,281 +1,281 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using kcp2k;
using Mirror.BouncyCastle.Crypto;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Serialization;
using Debug = UnityEngine.Debug;
namespace Mirror.Transports.Encryption
{
[HelpURL("https://mirror-networking.gitbook.io/docs/manual/transports/encryption-transport")]
public class ThreadedEncryptionKcpTransport : ThreadedKcpTransport
{
public override bool IsEncrypted => true;
public override string EncryptionCipher => "AES256-GCM";
public override string ToString() => $"Encrypted {base.ToString()}";
public enum ValidationMode
{
Off,
List,
Callback
}
[HideInInspector]
public ValidationMode ClientValidateServerPubKey;
[Tooltip("List of public key fingerprints the client will accept")]
[HideInInspector]
public string[] ClientTrustedPubKeySignatures;
/// <summary>
/// Called when a client connects to a server
/// ATTENTION: NOT THREAD SAFE.
/// This will be called on the worker thread.
/// </summary>
public Func<PubKeyInfo, bool> OnClientValidateServerPubKey;
[HideInInspector]
[FormerlySerializedAs("serverLoadKeyPairFromFile")]
public bool ServerLoadKeyPairFromFile;
[HideInInspector]
[FormerlySerializedAs("serverKeypairPath")]
public string ServerKeypairPath = "./server-keys.json";
EncryptedConnection encryptedClient;
readonly Dictionary<int, EncryptedConnection> serverConnections = new Dictionary<int, EncryptedConnection>();
readonly List<EncryptedConnection> serverPendingConnections =
new List<EncryptedConnection>();
EncryptionCredentials credentials;
public string EncryptionPublicKeyFingerprint => credentials?.PublicKeyFingerprint;
public byte[] EncryptionPublicKey => credentials?.PublicKeySerialized;
// Used for threaded time keeping as unitys Time.time is not thread safe
Stopwatch stopwatch = Stopwatch.StartNew();
void ServerRemoveFromPending(EncryptedConnection con)
{
for (int i = 0; i < serverPendingConnections.Count; i++)
if (serverPendingConnections[i] == con)
{
// remove by swapping with last
int lastIndex = serverPendingConnections.Count - 1;
serverPendingConnections[i] = serverPendingConnections[lastIndex];
serverPendingConnections.RemoveAt(lastIndex);
break;
}
}
void HandleInnerServerDisconnected(int connId)
{
if (serverConnections.TryGetValue(connId, out EncryptedConnection con))
{
ServerRemoveFromPending(con);
serverConnections.Remove(connId);
}
OnThreadedServerDisconnected(connId);
}
void HandleInnerServerDataReceived(int connId, ArraySegment<byte> data, int channel)
{
if (serverConnections.TryGetValue(connId, out EncryptedConnection c))
c.OnReceiveRaw(data, channel);
}
void HandleInnerServerConnected(int connId, IPEndPoint clientRemoteAddress)
{
Debug.Log($"[ThreadedEncryptionKcpTransport] New connection #{connId} from {clientRemoteAddress}");
EncryptedConnection ec = null;
ec = new EncryptedConnection(
credentials,
false,
(segment, channel) =>
{
server.Send(connId, segment, KcpTransport.ToKcpChannel(channel));
OnThreadedServerSend(connId, segment,channel);
},
(segment, channel) => OnThreadedServerReceive(connId, segment, channel),
() =>
{
Debug.Log($"[ThreadedEncryptionKcpTransport] Connection #{connId} is ready");
// ReSharper disable once AccessToModifiedClosure
ServerRemoveFromPending(ec);
OnThreadedServerConnected(connId, clientRemoteAddress);
},
(type, msg) =>
{
OnThreadedServerError(connId, type, msg);
ServerDisconnect(connId);
});
serverConnections.Add(connId, ec);
serverPendingConnections.Add(ec);
}
void HandleInnerClientDisconnected()
{
encryptedClient = null;
OnThreadedClientDisconnected();
}
void HandleInnerClientDataReceived(ArraySegment<byte> data, int channel) => encryptedClient?.OnReceiveRaw(data, channel);
void HandleInnerClientConnected() =>
encryptedClient = new EncryptedConnection(
credentials,
true,
(segment, channel) =>
{
client.Send(segment, KcpTransport.ToKcpChannel(channel));
OnThreadedClientSend(segment, channel);
},
(segment, channel) => OnThreadedClientReceive(segment, channel),
() =>
{
OnThreadedClientConnected();
},
(type, msg) =>
{
OnThreadedClientError(type, msg);
ClientDisconnect();
},
HandleClientValidateServerPubKey);
bool HandleClientValidateServerPubKey(PubKeyInfo pubKeyInfo)
{
switch (ClientValidateServerPubKey)
{
case ValidationMode.Off:
return true;
case ValidationMode.List:
return Array.IndexOf(ClientTrustedPubKeySignatures, pubKeyInfo.Fingerprint) >= 0;
case ValidationMode.Callback:
return OnClientValidateServerPubKey(pubKeyInfo);
default:
throw new ArgumentOutOfRangeException();
}
}
protected override void Awake()
{
base.Awake();
// client (NonAlloc version is not necessary anymore)
client = new KcpClient(
HandleInnerClientConnected,
(message, channel) => HandleInnerClientDataReceived(message, KcpTransport.FromKcpChannel(channel)),
HandleInnerClientDisconnected,
(error, reason) => OnThreadedClientError(KcpTransport.ToTransportError(error), reason),
config
);
// server
server = new KcpServer(
HandleInnerServerConnected,
(connectionId, message, channel) => HandleInnerServerDataReceived(connectionId, message, KcpTransport.FromKcpChannel(channel)),
HandleInnerServerDisconnected,
(connectionId, error, reason) => OnThreadedServerError(connectionId, KcpTransport.ToTransportError(error), reason),
config
);
// check if encryption via hardware acceleration is supported.
// this can be useful to know for low end devices.
//
// hardware acceleration requires netcoreapp3.0 or later:
// https://github.com/bcgit/bc-csharp/blob/449940429c57686a6fcf6bfbb4d368dec19d906e/crypto/src/crypto/AesUtilities.cs#L18
// because AesEngine_x86 requires System.Runtime.Intrinsics.X86:
// https://github.com/bcgit/bc-csharp/blob/449940429c57686a6fcf6bfbb4d368dec19d906e/crypto/src/crypto/engines/AesEngine_X86.cs
// which Unity does not support yet.
Debug.Log($"ThreadedEncryptionKcpTransport: IsHardwareAccelerated={AesUtilities.IsHardwareAccelerated}");
}
protected override void ThreadedClientConnect(string address)
{
if (!SetupEncryptionForClient())
return;
base.ThreadedClientConnect(address);
}
bool SetupEncryptionForClient()
{
switch (ClientValidateServerPubKey)
{
case ValidationMode.Off:
break;
case ValidationMode.List:
if (ClientTrustedPubKeySignatures == null || ClientTrustedPubKeySignatures.Length == 0)
{
OnThreadedClientError(TransportError.Unexpected, "Validate Server Public Key is set to List, but the clientTrustedPubKeySignatures list is empty.");
return false;
}
break;
case ValidationMode.Callback:
if (OnClientValidateServerPubKey == null)
{
OnThreadedClientError(TransportError.Unexpected, "Validate Server Public Key is set to Callback, but the onClientValidateServerPubKey handler is not set");
return false;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
credentials = EncryptionCredentials.Generate();
return true;
}
protected override void ThreadedClientConnect(Uri address)
{
if (!SetupEncryptionForClient())
return;
base.ThreadedClientConnect(address);
}
protected override void ThreadedClientSend(ArraySegment<byte> segment, int channelId)
{
encryptedClient?.Send(segment, channelId);
}
protected override void ThreadedServerStart()
{
if (ServerLoadKeyPairFromFile)
credentials = EncryptionCredentials.LoadFromFile(ServerKeypairPath);
else
credentials = EncryptionCredentials.Generate();
base.ThreadedServerStart();
}
protected override void ThreadedServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
{
if (serverConnections.TryGetValue(connectionId, out EncryptedConnection connection) && connection.IsReady)
connection.Send(segment, channelId);
}
public override int GetMaxPacketSize(int channelId = Channels.Reliable) => base.GetMaxPacketSize(channelId) - EncryptedConnection.Overhead;
public override int GetBatchThreshold(int channelId) => base.GetBatchThreshold(channelId) - EncryptedConnection.Overhead;
protected override void ThreadedClientLateUpdate()
{
base.ThreadedClientLateUpdate();
Profiler.BeginSample("ThreadedEncryptionKcpTransport.ServerLateUpdate");
encryptedClient?.TickNonReady(stopwatch.Elapsed.TotalSeconds);
Profiler.EndSample();
}
protected override void ThreadedServerLateUpdate()
{
base.ThreadedServerLateUpdate();
Profiler.BeginSample("ThreadedEncryptionKcpTransport.ServerLateUpdate");
// Reverse iteration as entries can be removed while updating
for (int i = serverPendingConnections.Count - 1; i >= 0; i--)
serverPendingConnections[i].TickNonReady(stopwatch.Elapsed.TotalSeconds);
Profiler.EndSample();
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using kcp2k;
using Mirror.BouncyCastle.Crypto;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Serialization;
using Debug = UnityEngine.Debug;
namespace Mirror.Transports.Encryption
{
[HelpURL("https://mirror-networking.gitbook.io/docs/manual/transports/encryption-transport")]
public class ThreadedEncryptionKcpTransport : ThreadedKcpTransport
{
public override bool IsEncrypted => true;
public override string EncryptionCipher => "AES256-GCM";
public override string ToString() => $"Encrypted {base.ToString()}";
public enum ValidationMode
{
Off,
List,
Callback
}
[HideInInspector]
public ValidationMode ClientValidateServerPubKey;
[Tooltip("List of public key fingerprints the client will accept")]
[HideInInspector]
public string[] ClientTrustedPubKeySignatures;
/// <summary>
/// Called when a client connects to a server
/// ATTENTION: NOT THREAD SAFE.
/// This will be called on the worker thread.
/// </summary>
public Func<PubKeyInfo, bool> OnClientValidateServerPubKey;
[HideInInspector]
[FormerlySerializedAs("serverLoadKeyPairFromFile")]
public bool ServerLoadKeyPairFromFile;
[HideInInspector]
[FormerlySerializedAs("serverKeypairPath")]
public string ServerKeypairPath = "./server-keys.json";
EncryptedConnection encryptedClient;
readonly Dictionary<int, EncryptedConnection> serverConnections = new Dictionary<int, EncryptedConnection>();
readonly List<EncryptedConnection> serverPendingConnections =
new List<EncryptedConnection>();
EncryptionCredentials credentials;
public string EncryptionPublicKeyFingerprint => credentials?.PublicKeyFingerprint;
public byte[] EncryptionPublicKey => credentials?.PublicKeySerialized;
// Used for threaded time keeping as unitys Time.time is not thread safe
Stopwatch stopwatch = Stopwatch.StartNew();
void ServerRemoveFromPending(EncryptedConnection con)
{
for (int i = 0; i < serverPendingConnections.Count; i++)
if (serverPendingConnections[i] == con)
{
// remove by swapping with last
int lastIndex = serverPendingConnections.Count - 1;
serverPendingConnections[i] = serverPendingConnections[lastIndex];
serverPendingConnections.RemoveAt(lastIndex);
break;
}
}
void HandleInnerServerDisconnected(int connId)
{
if (serverConnections.TryGetValue(connId, out EncryptedConnection con))
{
ServerRemoveFromPending(con);
serverConnections.Remove(connId);
}
OnThreadedServerDisconnected(connId);
}
void HandleInnerServerDataReceived(int connId, ArraySegment<byte> data, int channel)
{
if (serverConnections.TryGetValue(connId, out EncryptedConnection c))
c.OnReceiveRaw(data, channel);
}
void HandleInnerServerConnected(int connId, IPEndPoint clientRemoteAddress)
{
Debug.Log($"[ThreadedEncryptionKcpTransport] New connection #{connId} from {clientRemoteAddress}");
EncryptedConnection ec = null;
ec = new EncryptedConnection(
credentials,
false,
(segment, channel) =>
{
server.Send(connId, segment, KcpTransport.ToKcpChannel(channel));
OnThreadedServerSend(connId, segment,channel);
},
(segment, channel) => OnThreadedServerReceive(connId, segment, channel),
() =>
{
Debug.Log($"[ThreadedEncryptionKcpTransport] Connection #{connId} is ready");
// ReSharper disable once AccessToModifiedClosure
ServerRemoveFromPending(ec);
OnThreadedServerConnected(connId, clientRemoteAddress);
},
(type, msg) =>
{
OnThreadedServerError(connId, type, msg);
ServerDisconnect(connId);
});
serverConnections.Add(connId, ec);
serverPendingConnections.Add(ec);
}
void HandleInnerClientDisconnected()
{
encryptedClient = null;
OnThreadedClientDisconnected();
}
void HandleInnerClientDataReceived(ArraySegment<byte> data, int channel) => encryptedClient?.OnReceiveRaw(data, channel);
void HandleInnerClientConnected() =>
encryptedClient = new EncryptedConnection(
credentials,
true,
(segment, channel) =>
{
client.Send(segment, KcpTransport.ToKcpChannel(channel));
OnThreadedClientSend(segment, channel);
},
(segment, channel) => OnThreadedClientReceive(segment, channel),
() =>
{
OnThreadedClientConnected();
},
(type, msg) =>
{
OnThreadedClientError(type, msg);
ClientDisconnect();
},
HandleClientValidateServerPubKey);
bool HandleClientValidateServerPubKey(PubKeyInfo pubKeyInfo)
{
switch (ClientValidateServerPubKey)
{
case ValidationMode.Off:
return true;
case ValidationMode.List:
return Array.IndexOf(ClientTrustedPubKeySignatures, pubKeyInfo.Fingerprint) >= 0;
case ValidationMode.Callback:
return OnClientValidateServerPubKey(pubKeyInfo);
default:
throw new ArgumentOutOfRangeException();
}
}
protected override void Awake()
{
base.Awake();
// client (NonAlloc version is not necessary anymore)
client = new KcpClient(
HandleInnerClientConnected,
(message, channel) => HandleInnerClientDataReceived(message, KcpTransport.FromKcpChannel(channel)),
HandleInnerClientDisconnected,
(error, reason) => OnThreadedClientError(KcpTransport.ToTransportError(error), reason),
config
);
// server
server = new KcpServer(
HandleInnerServerConnected,
(connectionId, message, channel) => HandleInnerServerDataReceived(connectionId, message, KcpTransport.FromKcpChannel(channel)),
HandleInnerServerDisconnected,
(connectionId, error, reason) => OnThreadedServerError(connectionId, KcpTransport.ToTransportError(error), reason),
config
);
// check if encryption via hardware acceleration is supported.
// this can be useful to know for low end devices.
//
// hardware acceleration requires netcoreapp3.0 or later:
// https://github.com/bcgit/bc-csharp/blob/449940429c57686a6fcf6bfbb4d368dec19d906e/crypto/src/crypto/AesUtilities.cs#L18
// because AesEngine_x86 requires System.Runtime.Intrinsics.X86:
// https://github.com/bcgit/bc-csharp/blob/449940429c57686a6fcf6bfbb4d368dec19d906e/crypto/src/crypto/engines/AesEngine_X86.cs
// which Unity does not support yet.
Debug.Log($"ThreadedEncryptionKcpTransport: IsHardwareAccelerated={AesUtilities.IsHardwareAccelerated}");
}
protected override void ThreadedClientConnect(string address)
{
if (!SetupEncryptionForClient())
return;
base.ThreadedClientConnect(address);
}
bool SetupEncryptionForClient()
{
switch (ClientValidateServerPubKey)
{
case ValidationMode.Off:
break;
case ValidationMode.List:
if (ClientTrustedPubKeySignatures == null || ClientTrustedPubKeySignatures.Length == 0)
{
OnThreadedClientError(TransportError.Unexpected, "Validate Server Public Key is set to List, but the clientTrustedPubKeySignatures list is empty.");
return false;
}
break;
case ValidationMode.Callback:
if (OnClientValidateServerPubKey == null)
{
OnThreadedClientError(TransportError.Unexpected, "Validate Server Public Key is set to Callback, but the onClientValidateServerPubKey handler is not set");
return false;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
credentials = EncryptionCredentials.Generate();
return true;
}
protected override void ThreadedClientConnect(Uri address)
{
if (!SetupEncryptionForClient())
return;
base.ThreadedClientConnect(address);
}
protected override void ThreadedClientSend(ArraySegment<byte> segment, int channelId)
{
encryptedClient?.Send(segment, channelId);
}
protected override void ThreadedServerStart()
{
if (ServerLoadKeyPairFromFile)
credentials = EncryptionCredentials.LoadFromFile(ServerKeypairPath);
else
credentials = EncryptionCredentials.Generate();
base.ThreadedServerStart();
}
protected override void ThreadedServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
{
if (serverConnections.TryGetValue(connectionId, out EncryptedConnection connection) && connection.IsReady)
connection.Send(segment, channelId);
}
public override int GetMaxPacketSize(int channelId = Channels.Reliable) => base.GetMaxPacketSize(channelId) - EncryptedConnection.Overhead;
public override int GetBatchThreshold(int channelId) => base.GetBatchThreshold(channelId) - EncryptedConnection.Overhead;
protected override void ThreadedClientLateUpdate()
{
base.ThreadedClientLateUpdate();
Profiler.BeginSample("ThreadedEncryptionKcpTransport.ServerLateUpdate");
encryptedClient?.TickNonReady(stopwatch.Elapsed.TotalSeconds);
Profiler.EndSample();
}
protected override void ThreadedServerLateUpdate()
{
base.ThreadedServerLateUpdate();
Profiler.BeginSample("ThreadedEncryptionKcpTransport.ServerLateUpdate");
// Reverse iteration as entries can be removed while updating
for (int i = serverPendingConnections.Count - 1; i >= 0; i--)
serverPendingConnections[i].TickNonReady(stopwatch.Elapsed.TotalSeconds);
Profiler.EndSample();
}
}
}
@@ -1,18 +1,18 @@
fileFormatVersion: 2
guid: 5d3e310924fb49c195391b9699f20809
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/ThreadedEncryptionKcpTransport.cs
uploadId: 736421
fileFormatVersion: 2
guid: 5d3e310924fb49c195391b9699f20809
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Encryption/ThreadedEncryptionKcpTransport.cs
uploadId: 736421