Keep local version of files
This commit is contained in:
@@ -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
|
||||
|
||||
+40
-40
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user