From fb8c6ce601cb1d7d8a6faf5fba8060cbe755acad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Fri, 9 Apr 2021 15:57:06 +0200 Subject: [PATCH 1/7] feat: add ChaCha20 crypto transport --- .../CHANGELOG.md | 5 + .../CHANGELOG.md.meta | 7 + .../LICENSE.md | 9 + .../LICENSE.md.meta | 7 + .../README.md | 1 + .../README.md.meta | 7 + .../Runtime.meta | 8 + .../Runtime/ChaCha20.meta | 3 + .../Runtime/ChaCha20/ChaCha20Cipher.cs | 366 ++++++++++++ .../Runtime/ChaCha20/ChaCha20Cipher.cs.meta | 3 + .../Runtime/ChaCha20/Util.cs | 125 ++++ .../Runtime/ChaCha20/Util.cs.meta | 3 + .../Runtime/CryptographyTransportAdapter.cs | 562 ++++++++++++++++++ .../CryptographyTransportAdapter.cs.meta | 3 + .../Runtime/MLAPI.Cryptography.dll | Bin 0 -> 30720 bytes .../Runtime/MLAPI.Cryptography.dll.meta | 33 + .../Runtime/MLAPI.Cryptography.pdb.meta | 7 + ...om.mlapi.contrib.transport.chacha20.asmdef | 16 + ...api.contrib.transport.chacha20.asmdef.meta | 7 + .../package.json | 11 + .../package.json.meta | 7 + 21 files changed, 1190 insertions(+) create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/CHANGELOG.md create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/CHANGELOG.md.meta create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/LICENSE.md create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/LICENSE.md.meta create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/README.md create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/README.md.meta create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/Runtime.meta create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20.meta create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/ChaCha20Cipher.cs create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/ChaCha20Cipher.cs.meta create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/Util.cs create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/Util.cs.meta create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs.meta create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/Runtime/MLAPI.Cryptography.dll create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/Runtime/MLAPI.Cryptography.dll.meta create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/Runtime/MLAPI.Cryptography.pdb.meta create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/Runtime/com.mlapi.contrib.transport.chacha20.asmdef create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/Runtime/com.mlapi.contrib.transport.chacha20.asmdef.meta create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/package.json create mode 100644 Transports/com.mlapi.contrib.transport.chacha20/package.json.meta diff --git a/Transports/com.mlapi.contrib.transport.chacha20/CHANGELOG.md b/Transports/com.mlapi.contrib.transport.chacha20/CHANGELOG.md new file mode 100644 index 00000000..32bde89d --- /dev/null +++ b/Transports/com.mlapi.contrib.transport.chacha20/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog +All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) + +## 1.0.0 +First version of the ChaCha20 transport as a Unity package. \ No newline at end of file diff --git a/Transports/com.mlapi.contrib.transport.chacha20/CHANGELOG.md.meta b/Transports/com.mlapi.contrib.transport.chacha20/CHANGELOG.md.meta new file mode 100644 index 00000000..cedb450f --- /dev/null +++ b/Transports/com.mlapi.contrib.transport.chacha20/CHANGELOG.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0a024bb1894e44e4b84f176b0b386f24 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Transports/com.mlapi.contrib.transport.chacha20/LICENSE.md b/Transports/com.mlapi.contrib.transport.chacha20/LICENSE.md new file mode 100644 index 00000000..ea4b0085 --- /dev/null +++ b/Transports/com.mlapi.contrib.transport.chacha20/LICENSE.md @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2021 Unity Technologies + +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, sublicense, 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. \ No newline at end of file diff --git a/Transports/com.mlapi.contrib.transport.chacha20/LICENSE.md.meta b/Transports/com.mlapi.contrib.transport.chacha20/LICENSE.md.meta new file mode 100644 index 00000000..0c9c2353 --- /dev/null +++ b/Transports/com.mlapi.contrib.transport.chacha20/LICENSE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 56fa6d8e6a036b74eaef27818e3698c4 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Transports/com.mlapi.contrib.transport.chacha20/README.md b/Transports/com.mlapi.contrib.transport.chacha20/README.md new file mode 100644 index 00000000..180c7365 --- /dev/null +++ b/Transports/com.mlapi.contrib.transport.chacha20/README.md @@ -0,0 +1 @@ +ChaCha20 transport for MLAPI \ No newline at end of file diff --git a/Transports/com.mlapi.contrib.transport.chacha20/README.md.meta b/Transports/com.mlapi.contrib.transport.chacha20/README.md.meta new file mode 100644 index 00000000..99224e0f --- /dev/null +++ b/Transports/com.mlapi.contrib.transport.chacha20/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8ffe30828b308c245b69db7e4a4787d8 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime.meta b/Transports/com.mlapi.contrib.transport.chacha20/Runtime.meta new file mode 100644 index 00000000..2ab461dd --- /dev/null +++ b/Transports/com.mlapi.contrib.transport.chacha20/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d8bfbe123de7f4140b6b2071b1a7cbfa +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20.meta b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20.meta new file mode 100644 index 00000000..19313ca0 --- /dev/null +++ b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ccce8b0bffa74671925ae87d227fae1e +timeCreated: 1617966330 \ No newline at end of file diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/ChaCha20Cipher.cs b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/ChaCha20Cipher.cs new file mode 100644 index 00000000..c892dfb6 --- /dev/null +++ b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/ChaCha20Cipher.cs @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2015, 2018 Scott Bennett + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +// Modified by Albin Corén, https://github.com/twotenpvp (@Unity Technologies) + +using System; +using System.Text; + +namespace MLAPI.Transport.ChaCha20.ChaCha20 +{ + public sealed class ChaCha20Cipher : IDisposable + { + /// + /// The ChaCha20 state (aka "context") + /// + private uint[] m_State; + + /// + /// Determines if the objects in this class have been disposed of. Set to + /// true by the Dispose() method. + /// + private bool m_IsDisposed; + + /// + /// Set up a new ChaCha20 state. The lengths of the given parameters are + /// checked before encryption happens. + /// + /// + /// See ChaCha20 Spec Section 2.4 + /// for a detailed description of the inputs. + /// + /// + /// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit + /// little-endian integers + /// + /// + /// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit + /// little-endian integers + /// + /// + /// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian integer + /// + public ChaCha20Cipher(byte[] key, byte[] nonce, uint counter) + { + m_State = new uint[16]; + m_IsDisposed = false; + + KeySetup(key); + IvSetup(nonce, counter); + } + + /// + /// The ChaCha20 state (aka "context"). Read-Only. + /// + public uint[] State => m_State; + + /// + /// Set up the ChaCha state with the given key. A 32-byte key is required + /// and enforced. + /// + /// + /// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit + /// little-endian integers + /// + private void KeySetup(byte[] key) + { + if (key == null) + { + throw new ArgumentNullException("Key is null"); + } + + if (key.Length != 32) + { + throw new ArgumentException( + $"Key length must be 32. Actual: {key.Length}" + ); + } + + // These are the same constants defined in the reference implementation. + // http://cr.yp.to/streamciphers/timings/estreambench/submissions/salsa20/chacha8/ref/chacha.c + byte[] sigma = Encoding.ASCII.GetBytes("expand 32-byte k"); + byte[] tau = Encoding.ASCII.GetBytes("expand 16-byte k"); + + m_State[4] = Util.U8To32Little(key, 0); + m_State[5] = Util.U8To32Little(key, 4); + m_State[6] = Util.U8To32Little(key, 8); + m_State[7] = Util.U8To32Little(key, 12); + + byte[] constants = (key.Length == 32) ? sigma : tau; + int keyIndex = key.Length - 16; + + m_State[8] = Util.U8To32Little(key, keyIndex + 0); + m_State[9] = Util.U8To32Little(key, keyIndex + 4); + m_State[10] = Util.U8To32Little(key, keyIndex + 8); + m_State[11] = Util.U8To32Little(key, keyIndex + 12); + + m_State[0] = Util.U8To32Little(constants, 0); + m_State[1] = Util.U8To32Little(constants, 4); + m_State[2] = Util.U8To32Little(constants, 8); + m_State[3] = Util.U8To32Little(constants, 12); + } + + /// + /// Set up the ChaCha state with the given nonce (aka Initialization Vector + /// or IV) and block counter. A 12-byte nonce and a 4-byte counter are + /// required. + /// + /// + /// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit + /// little-endian integers + /// + /// + /// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian integer + /// + private void IvSetup(byte[] nonce, uint counter) + { + if (nonce == null) + { + // There has already been some state set up. Clear it before exiting. + Dispose(); + throw new ArgumentNullException("Nonce is null"); + } + + if (nonce.Length != 12) + { + // There has already been some state set up. Clear it before exiting. + Dispose(); + throw new ArgumentException( + $"Nonce length must be 12. Actual: {nonce.Length}" + ); + } + + m_State[12] = counter; + m_State[13] = Util.U8To32Little(nonce, 0); + m_State[14] = Util.U8To32Little(nonce, 4); + m_State[15] = Util.U8To32Little(nonce, 8); + } + + /// + /// Encrypt an arbitrary-length plaintext message (input), writing the + /// resulting ciphertext to the output buffer. The number of bytes to read + /// from the input buffer is determined by numBytes. + /// + /// + /// + /// + public void ProcessBytes(byte[] input, int inputOffset, byte[] output, int outputOffset, int count) + { + if (m_IsDisposed) + { + throw new ObjectDisposedException("state", "The ChaCha state has been disposed"); + } + + if (count < 0 || count > input.Length - inputOffset) + { + throw new ArgumentOutOfRangeException("count", "The number of bytes to read must be between [0..input.Length]"); + } + + uint[] x = new uint[16]; // Working buffer + byte[] tmp = new byte[64]; // Temporary buffer + int outputPosition = 0; + int inputPosition = 0; + + while (count > 0) + { + for (int i = 16; i-- > 0;) + { + x[i] = m_State[i]; + } + + for (int i = 20; i > 0; i -= 2) + { + QuarterRound(x, 0, 4, 8, 12); + QuarterRound(x, 1, 5, 9, 13); + QuarterRound(x, 2, 6, 10, 14); + QuarterRound(x, 3, 7, 11, 15); + + QuarterRound(x, 0, 5, 10, 15); + QuarterRound(x, 1, 6, 11, 12); + QuarterRound(x, 2, 7, 8, 13); + QuarterRound(x, 3, 4, 9, 14); + } + + for (int i = 16; i-- > 0;) + { + Util.ToBytes(tmp, Util.Add(x[i], m_State[i]), 4 * i); + } + + m_State[12] = Util.AddOne(m_State[12]); + if (m_State[12] <= 0) + { + /* Stopping at 2^70 bytes per nonce is the user's responsibility */ + m_State[13] = Util.AddOne(m_State[13]); + } + + if (count <= 64) + { + for (int i = count; i-- > 0;) + { + output[i + outputPosition + outputOffset] = (byte) (input[i + inputPosition + inputOffset] ^ tmp[i]); + } + + return; + } + + for (int i = 64; i-- > 0;) + { + output[i + outputPosition + outputOffset] = (byte) (input[i + inputPosition + inputOffset] ^ tmp[i]); + } + + count -= 64; + outputPosition += 64; + inputPosition += 64; + } + } + + /// + /// The ChaCha Quarter Round operation. It operates on four 32-bit unsigned + /// integers within the given buffer at indices a, b, c, and d. + /// + /// + /// The ChaCha state does not have four integer numbers: it has 16. So + /// the quarter-round operation works on only four of them -- hence the + /// name. Each quarter round operates on four predetermined numbers in + /// the ChaCha state. + /// See ChaCha20 Spec Sections 2.1 - 2.2. + /// + /// A ChaCha state (vector). Must contain 16 elements. + /// Index of the first number + /// Index of the second number + /// Index of the third number + /// Index of the fourth number + public static void QuarterRound(uint[] x, uint a, uint b, uint c, uint d) + { + if (x == null) + { + throw new ArgumentNullException("Input buffer is null"); + } + + if (x.Length != 16) + { + throw new ArgumentException(); + } + + x[a] = Util.Add(x[a], x[b]); + x[d] = Util.Rotate(Util.XOr(x[d], x[a]), 16); + x[c] = Util.Add(x[c], x[d]); + x[b] = Util.Rotate(Util.XOr(x[b], x[c]), 12); + x[a] = Util.Add(x[a], x[b]); + x[d] = Util.Rotate(Util.XOr(x[d], x[a]), 8); + x[c] = Util.Add(x[c], x[d]); + x[b] = Util.Rotate(Util.XOr(x[b], x[c]), 7); + } + + /// + /// Currently not used. + /// + /// + /// + public static void ChaCha20BlockFunction(byte[] output, uint[] input) + { + if (input == null || output == null) + { + throw new ArgumentNullException(); + } + + if (input.Length != 16 || output.Length != 64) + { + throw new ArgumentException(); + } + + uint[] x = new uint[16]; // Working buffer + + for (int i = 16; i-- > 0;) + { + x[i] = input[i]; + } + + for (int i = 20; i > 0; i -= 2) + { + QuarterRound(x, 0, 4, 8, 12); + QuarterRound(x, 1, 5, 9, 13); + QuarterRound(x, 2, 6, 10, 14); + QuarterRound(x, 3, 7, 11, 15); + + QuarterRound(x, 0, 5, 10, 15); + QuarterRound(x, 1, 6, 11, 12); + QuarterRound(x, 2, 7, 8, 13); + QuarterRound(x, 3, 4, 9, 14); + } + + for (int i = 16; i-- > 0;) + { + Util.ToBytes(output, Util.Add(x[i], input[i]), 4 * i); + } + } + + #region Destructor and Disposer + + /// + /// Clear and dispose of the internal state. The finalizer is only called + /// if Dispose() was never called on this cipher. + /// + ~ChaCha20Cipher() + { + Dispose(false); + } + + /// + /// Clear and dispose of the internal state. Also request the GC not to + /// call the finalizer, because all cleanup has been taken care of. + /// + public void Dispose() + { + Dispose(true); + /* + * The Garbage Collector does not need to invoke the finalizer because + * Dispose(bool) has already done all the cleanup needed. + */ + GC.SuppressFinalize(this); + } + + /// + /// This method should only be invoked from Dispose() or the finalizer. + /// This handles the actual cleanup of the resources. + /// + /// + /// Should be true if called by Dispose(); false if called by the finalizer + /// + private void Dispose(bool disposing) + { + if (!m_IsDisposed) + { + if (disposing) + { + /* Cleanup managed objects by calling their Dispose() methods */ + } + + /* Cleanup any unmanaged objects here */ + if (m_State != null) + { + Array.Clear(m_State, 0, m_State.Length); + } + + m_State = null; + } + + m_IsDisposed = true; + } + + #endregion + } +} diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/ChaCha20Cipher.cs.meta b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/ChaCha20Cipher.cs.meta new file mode 100644 index 00000000..43e4ff72 --- /dev/null +++ b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/ChaCha20Cipher.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 314b25bc369a4358bcc275b5b4eda2ba +timeCreated: 1617966199 \ No newline at end of file diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/Util.cs b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/Util.cs new file mode 100644 index 00000000..c98a85da --- /dev/null +++ b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/Util.cs @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2015, 2018 Scott Bennett + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +// Modified by Albin Corén, https://github.com/twotenpvp (@Unity Technologies) + +using System; + +namespace MLAPI.Transport.ChaCha20.ChaCha20 +{ + public class Util + { + /// + /// n-bit left rotation operation (towards the high bits) for 32-bit + /// integers. + /// + /// + /// + /// The result of (v LEFTSHIFT c) + public static uint Rotate(uint v, int c) + { + unchecked + { + return (v << c) | (v >> (32 - c)); + } + } + + /// + /// Unchecked integer exclusive or (XOR) operation. + /// + /// + /// + /// The result of (v XOR w) + public static uint XOr(uint v, uint w) + { + return unchecked(v ^ w); + } + + /// + /// Unchecked integer addition. The ChaCha spec defines certain operations + /// to use 32-bit unsigned integer addition modulo 2^32. + /// + /// + /// + /// See ChaCha20 Spec Section 2.1. + /// + /// + /// + /// + /// The result of (v + w) modulo 2^32 + public static uint Add(uint v, uint w) + { + return unchecked(v + w); + } + + /// + /// Add 1 to the input parameter using unchecked integer addition. The + /// ChaCha spec defines certain operations to use 32-bit unsigned integer + /// addition modulo 2^32. + /// + /// + /// See ChaCha20 Spec Section 2.1. + /// + /// + /// The result of (v + 1) modulo 2^32 + public static uint AddOne(uint v) + { + return unchecked(v + 1); + } + + /// + /// Convert four bytes of the input buffer into an unsigned + /// 32-bit integer, beginning at the inputOffset. + /// + /// + /// + /// An unsigned 32-bit integer + public static uint U8To32Little(byte[] p, int inputOffset) + { + unchecked + { + return ((uint) p[inputOffset] + | ((uint) p[inputOffset + 1] << 8) + | ((uint) p[inputOffset + 2] << 16) + | ((uint) p[inputOffset + 3] << 24)); + } + } + + /// + /// Serialize the input integer into the output buffer. The input integer + /// will be split into 4 bytes and put into four sequential places in the + /// output buffer, starting at the outputOffset. + /// + /// + /// + /// + public static void ToBytes(byte[] output, uint input, int outputOffset) + { + if (outputOffset < 0) + { + throw new ArgumentOutOfRangeException("outputOffset", "The buffer offset cannot be negative"); + } + + unchecked + { + output[outputOffset] = (byte) input; + output[outputOffset + 1] = (byte) (input >> 8); + output[outputOffset + 2] = (byte) (input >> 16); + output[outputOffset + 3] = (byte) (input >> 24); + } + } + } +} diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/Util.cs.meta b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/Util.cs.meta new file mode 100644 index 00000000..9d2a2290 --- /dev/null +++ b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/Util.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d6bf41c930544459a55c60278cb25a87 +timeCreated: 1617966350 \ No newline at end of file diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs new file mode 100644 index 00000000..c624a83a --- /dev/null +++ b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs @@ -0,0 +1,562 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using MLAPI.Cryptography.KeyExchanges; +using MLAPI.Logging; +using MLAPI.Serialization; +using MLAPI.Serialization.Pooled; +using MLAPI.Transport.ChaCha20.ChaCha20; +using MLAPI.Transports.Tasks; +using UnityEngine; + +namespace MLAPI.Transport.ChaCha20 +{ + public class CryptographyTransportAdapter : NetworkTransport + { + public override ulong ServerClientId => Transport.ServerClientId; + + public NetworkTransport Transport; + + public bool SignKeyExchange; + + [TextArea] + public string ServerBase64PFX; + + private X509Certificate2 m_ServerCertificate; + + private byte[] m_ServerCertificateBytes + { + get + { + if (m_ServerCertificatesByteBacking == null) + { + m_ServerCertificatesByteBacking = m_ServerCertificate.Export(X509ContentType.Cert); + } + + return m_ServerCertificatesByteBacking; + } + } + + private byte[] m_ServerCertificatesByteBacking; + + // State + private bool m_IsServer; + + // Used by client + private ECDiffieHellmanRSA m_ServerSignedKeyExchange; + private ECDiffieHellman m_ServerKeyExchange; + + // Used by server + private readonly Dictionary m_ClientSignedKeyExchanges = new Dictionary(); + private readonly Dictionary m_ClientKeyExchanges = new Dictionary(); + + public byte[] ServerKey { get; private set; } + public readonly Dictionary ClientKeys = new Dictionary(); + + private readonly Dictionary m_ClientCiphers = new Dictionary(); + private ChaCha20Cipher m_ServerCipher; + + private readonly Dictionary m_ClientStates = new Dictionary(); + + private enum ClientState : byte + { + WaitingForHailResponse, + Connected + } + + // Max message size + private byte[] m_CryptoBuffer = new byte[1024 * 8]; + + private enum MessageType : byte + { + Hail, // Server->Client + HailResponse, // Client->Server + Ready, // Server->Client + Internal // MLAPI Message + } + + public override void Send(ulong clientId, ArraySegment data, NetworkChannel networkChannel) + { + using (PooledNetworkBuffer buffer = PooledNetworkBuffer.Get()) + using (PooledNetworkWriter writer = PooledNetworkWriter.Get(buffer)) + { + // Write message type + writer.WriteBits((byte)MessageType.Internal, 2); + + // Align bits + writer.WritePadBits(); + + // Get the ChaCha20 cipher + ChaCha20Cipher cipher = clientId == ServerClientId ? m_ServerCipher : m_ClientCiphers[clientId]; + + // Store position (length messes with it) + long position = buffer.Position; + + // Expand buffer with data count + buffer.SetLength(buffer.Length + data.Count); + + // Restore position + buffer.Position = position; + + // Encrypt with ChaCha + cipher.ProcessBytes(data.Array, data.Offset, buffer.GetBuffer(), (int)buffer.Position, data.Count); + + + // Send the encrypted format + Transport.Send(clientId, new ArraySegment(buffer.GetBuffer(), 0, (int)buffer.Length), networkChannel); + } + } + + private readonly NetworkBuffer m_DataBuffer = new NetworkBuffer(); + public override NetworkEvent PollEvent(out ulong clientId, out NetworkChannel networkChannel, out ArraySegment payload, out float receiveTime) + { + NetworkEvent @event = Transport.PollEvent(out ulong internalClientId, out NetworkChannel internalNetworkChannel, out ArraySegment internalPayload, out float internalReceiveTime); + + if (@event == NetworkEvent.Connect && m_IsServer && !m_ClientStates.ContainsKey(internalClientId)) + { + // Send server a handshake + using (PooledNetworkBuffer hailBuffer = PooledNetworkBuffer.Get()) + using (PooledNetworkWriter hailWriter = PooledNetworkWriter.Get(hailBuffer)) + { + // Write message type + hailWriter.WriteBits((byte)MessageType.Hail, 2); + + // Write if key exchange should be signed + hailWriter.WriteBit(SignKeyExchange); + + // Pad bits + hailWriter.WritePadBits(); + + if (SignKeyExchange) + { + // Create handshake parameters + ECDiffieHellmanRSA keyExchange = new ECDiffieHellmanRSA(m_ServerCertificate); + m_ClientSignedKeyExchanges.Add(internalClientId, keyExchange); + + // Write public part of RSA key + hailWriter.WriteByteArray(m_ServerCertificateBytes); + + // Write key exchange public part + hailWriter.WriteByteArray(keyExchange.GetSecurePublicPart()); + } + else + { + // Create handshake parameters + ECDiffieHellman keyExchange = new ECDiffieHellman(); + m_ClientKeyExchanges.Add(internalClientId, keyExchange); + + // Write key exchange public part + hailWriter.WriteByteArray(keyExchange.GetPublicKey()); + } + + // Send hail + Transport.Send(internalClientId, new ArraySegment(hailBuffer.GetBuffer(), 0, (int)hailBuffer.Length), NetworkChannel.Internal); + } + + // Add them to client state + m_ClientStates.Add(internalClientId, ClientState.WaitingForHailResponse); + + clientId = internalClientId; + networkChannel = NetworkChannel.Internal; + payload = new ArraySegment(); + receiveTime = internalReceiveTime; + return NetworkEvent.Nothing; + } + else if (@event == NetworkEvent.Data) + { + // Set the data to the buffer + m_DataBuffer.SetTarget(internalPayload.Array); + m_DataBuffer.SetLength(internalPayload.Count + internalPayload.Offset); + m_DataBuffer.Position = internalPayload.Offset; + + using (PooledNetworkReader dataReader = PooledNetworkReader.Get(m_DataBuffer)) + { + MessageType messageType = (MessageType)dataReader.ReadByteBits(2); + + if (messageType == MessageType.Hail && !m_IsServer) + { + // Server sent us a hail + + // Read if the data was signed + bool sign = dataReader.ReadBit(); + + if (sign != SignKeyExchange) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Error) + { + NetworkLog.LogError("Mismatch between " + nameof(SignKeyExchange)); + } + + clientId = internalClientId; + networkChannel = NetworkChannel.Internal; + payload = new ArraySegment(); + receiveTime = internalReceiveTime; + return NetworkEvent.Nothing; + } + + // Align bits + dataReader.SkipPadBits(); + + if (SignKeyExchange) + { + // Read certificate + m_ServerCertificate = new X509Certificate2(dataReader.ReadByteArray()); + + // TODO: IMPORTANT!!! VERIFY CERTIFICATE!!!!!!! + + // Create key exchange + m_ServerSignedKeyExchange = new ECDiffieHellmanRSA(m_ServerCertificate); + + // Read servers public part + byte[] serverPublicPart = dataReader.ReadByteArray(); + + // Get shared + byte[] key = m_ServerSignedKeyExchange.GetVerifiedSharedPart(serverPublicPart); + + // Do key stretching with PBKDF2-HMAC-SHA1 with Application.productName as salt + using (Rfc2898DeriveBytes pbdkf = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(Application.productName), 10_000)) + { + // Add raw key for external use + ServerKey = key; + + // ChaCha wants 48 bytes + byte[] chaChaData = pbdkf.GetBytes(48); + + // Get key part + byte[] chaChaKey = new byte[32]; + Buffer.BlockCopy(chaChaData, 0, chaChaKey, 0, 32); + + // Get nonce part + byte[] chaChaNonce = new byte[12]; + Buffer.BlockCopy(chaChaData, 32, chaChaNonce, 0, 12); + + // Create cipher + m_ServerCipher = new ChaCha20Cipher(chaChaKey, chaChaNonce, BitConverter.ToUInt32(chaChaData, 32 + 12)); + } + } + else + { + // Create key exchange + m_ServerKeyExchange = new ECDiffieHellman(); + + // Read servers public part + byte[] serverPublicPart = dataReader.ReadByteArray(); + + // Get shared + byte[] key = m_ServerKeyExchange.GetSharedSecretRaw(serverPublicPart); + + // Do key stretching with PBKDF2-HMAC-SHA1 with Application.productName as salt + using (Rfc2898DeriveBytes pbdkf = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(Application.productName), 10_000)) + { + // Add raw key for external use + ServerKey = key; + + // ChaCha wants 48 bytes + byte[] chaChaData = pbdkf.GetBytes(48); + + // Get key part + byte[] chaChaKey = new byte[32]; + Buffer.BlockCopy(chaChaData, 0, chaChaKey, 0, 32); + + // Get nonce part + byte[] chaChaNonce = new byte[12]; + Buffer.BlockCopy(chaChaData, 32, chaChaNonce, 0, 12); + + // Create cipher + m_ServerCipher = new ChaCha20Cipher(chaChaKey, chaChaNonce, BitConverter.ToUInt32(chaChaData, 32 + 12)); + } + } + + // Respond with hail response + using (PooledNetworkBuffer hailResponseBuffer = PooledNetworkBuffer.Get()) + using (PooledNetworkWriter hailResponseWriter = PooledNetworkWriter.Get(hailResponseBuffer)) + { + // Write message type + hailResponseWriter.WriteBits((byte)MessageType.HailResponse, 2); + + // Align bits + hailResponseWriter.WritePadBits(); + + if (SignKeyExchange) + { + // Write public part + hailResponseWriter.WriteByteArray(m_ServerSignedKeyExchange.GetSecurePublicPart()); + } + else + { + // Write public part + hailResponseWriter.WriteByteArray(m_ServerKeyExchange.GetPublicKey()); + } + + // Send hail response + Transport.Send(internalClientId, new ArraySegment(hailResponseBuffer.GetBuffer(), 0, (int)hailResponseBuffer.Length), NetworkChannel.Internal); + } + + clientId = internalClientId; + networkChannel = NetworkChannel.Internal; + payload = new ArraySegment(); + receiveTime = internalReceiveTime; + return NetworkEvent.Nothing; + } + else if (messageType == MessageType.HailResponse && m_IsServer && m_ClientStates.ContainsKey(internalClientId) && m_ClientStates[internalClientId] == ClientState.WaitingForHailResponse) + { + // Client sent us a hail response + + // Align bits + dataReader.SkipPadBits(); + + // Read clients public part + byte[] clientPublicPart = dataReader.ReadByteArray(); + + if (SignKeyExchange) + { + // Get key + byte[] key = m_ClientSignedKeyExchanges[internalClientId].GetVerifiedSharedPart(clientPublicPart); + + // Do key stretching with PBKDF2-HMAC-SHA1 with Application.productName as salt + using (Rfc2898DeriveBytes pbdkf = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(Application.productName), 10_000)) + { + // Add raw key for external use + ClientKeys.Add(internalClientId, key); + + // ChaCha wants 48 bytes + byte[] chaChaData = pbdkf.GetBytes(48); + + // Get key part + byte[] chaChaKey = new byte[32]; + Buffer.BlockCopy(chaChaData, 0, chaChaKey, 0, 32); + + // Get nonce part + byte[] chaChaNonce = new byte[12]; + Buffer.BlockCopy(chaChaData, 32, chaChaNonce, 0, 12); + + // Create cipher + m_ClientCiphers.Add(internalClientId, new ChaCha20Cipher(chaChaKey, chaChaNonce, BitConverter.ToUInt32(chaChaData, 32 + 12))); + } + + // Cleanup + m_ClientSignedKeyExchanges.Remove(internalClientId); + } + else + { + // Get key + byte[] key = m_ClientKeyExchanges[internalClientId].GetSharedSecretRaw(clientPublicPart); + + // Do key stretching with PBKDF2-HMAC-SHA1 with Application.productName as salt + using (Rfc2898DeriveBytes pbdkf = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(Application.productName), 10_000)) + { + // Add raw key for external use + ClientKeys.Add(internalClientId, key); + + // ChaCha wants 48 bytes + byte[] chaChaData = pbdkf.GetBytes(48); + + // Get key part + byte[] chaChaKey = new byte[32]; + Buffer.BlockCopy(chaChaData, 0, chaChaKey, 0, 32); + + // Get nonce part + byte[] chaChaNonce = new byte[12]; + Buffer.BlockCopy(chaChaData, 32, chaChaNonce, 0, 12); + + // Create cipher + m_ClientCiphers.Add(internalClientId, new ChaCha20Cipher(chaChaKey, chaChaNonce, BitConverter.ToUInt32(chaChaData, 32 + 12))); + } + + //Cleanup + m_ClientKeyExchanges.Remove(internalClientId); + } + + // Respond with ready response + using (PooledNetworkBuffer readyResponseBuffer = PooledNetworkBuffer.Get()) + using (PooledNetworkWriter readyResponseWriter = PooledNetworkWriter.Get(readyResponseBuffer)) + { + // Write message type + readyResponseWriter.WriteBits((byte)MessageType.Ready, 2); + + // Align bits + readyResponseWriter.WritePadBits(); + + // Send ready message + Transport.Send(internalClientId, new ArraySegment(readyResponseBuffer.GetBuffer(), 0, (int)readyResponseBuffer.Length), NetworkChannel.Internal); + } + + // Elevate to connected + m_ClientStates[internalClientId] = ClientState.Connected; + + clientId = internalClientId; + networkChannel = internalNetworkChannel; + payload = new ArraySegment(); + receiveTime = internalReceiveTime; + return NetworkEvent.Connect; + } + else if (messageType == MessageType.Ready && !m_IsServer) + { + // Server is ready for us! + // Let the MLAPI know we are connected + clientId = internalClientId; + networkChannel = internalNetworkChannel; + payload = new ArraySegment(); + receiveTime = internalReceiveTime; + return NetworkEvent.Connect; + } + else if (messageType == MessageType.Internal && (!m_IsServer || (m_ClientStates.ContainsKey(internalClientId) && m_ClientStates[internalClientId] == ClientState.Connected))) + { + // Decrypt and pass message to the MLAPI + + // Align bits + dataReader.SkipPadBits(); + + // Get the correct cipher + ChaCha20Cipher cipher = m_IsServer ? m_ClientCiphers[internalClientId] : m_ServerCipher; + + // Decrypt bytes + cipher.ProcessBytes(m_DataBuffer.GetBuffer(), (int)m_DataBuffer.Position, m_CryptoBuffer, 0, (int)(m_DataBuffer.Length - m_DataBuffer.Position)); + + clientId = internalClientId; + networkChannel = internalNetworkChannel; + payload = new ArraySegment(m_CryptoBuffer, 0, (int)(m_DataBuffer.Length - m_DataBuffer.Position)); + receiveTime = internalReceiveTime; + return NetworkEvent.Data; + } + } + } + else if (@event == NetworkEvent.Disconnect) + { + // Cleanup + + if (m_IsServer) + { + if (SignKeyExchange) + { + if (m_ClientSignedKeyExchanges.ContainsKey(internalClientId)) + { + m_ClientSignedKeyExchanges.Remove(internalClientId); + } + } + else + { + if (m_ClientKeyExchanges.ContainsKey(internalClientId)) + { + m_ClientKeyExchanges.Remove(internalClientId); + } + } + + if (ClientKeys.ContainsKey(internalClientId)) + { + ClientKeys.Remove(internalClientId); + } + + if (m_ClientCiphers.ContainsKey(internalClientId)) + { + m_ClientCiphers[internalClientId].Dispose(); + m_ClientCiphers.Remove(internalClientId); + } + + if (m_ClientStates.ContainsKey(internalClientId)) + { + m_ClientStates.Remove(internalClientId); + } + } + else + { + m_ServerSignedKeyExchange = null; + m_ServerKeyExchange = null; + ServerKey = null; + } + + clientId = internalClientId; + networkChannel = internalNetworkChannel; + payload = new ArraySegment(); + receiveTime = internalReceiveTime; + return NetworkEvent.Disconnect; + } + + clientId = internalClientId; + networkChannel = internalNetworkChannel; + payload = new ArraySegment(); + receiveTime = 0; + return NetworkEvent.Nothing; + } + + public override SocketTasks StartClient() + { + m_IsServer = false; + return Transport.StartClient(); + } + + public override SocketTasks StartServer() + { + m_IsServer = true; + ParsePFX(); + return Transport.StartServer(); + } + + public override void DisconnectRemoteClient(ulong clientId) + { + Transport.DisconnectRemoteClient(clientId); + } + + public override void DisconnectLocalClient() + { + Transport.DisconnectLocalClient(); + } + + public override ulong GetCurrentRtt(ulong clientId) + { + return Transport.GetCurrentRtt(clientId); + } + + public override void Shutdown() + { + Transport.Shutdown(); + } + + public override void Init() + { + Transport.Init(); + } + + private void ParsePFX() + { + try + { + string pfx = ServerBase64PFX.Trim(); + + try + { + if (m_IsServer && SignKeyExchange && !string.IsNullOrWhiteSpace(pfx)) + { + byte[] decodedPfx = Convert.FromBase64String(ServerBase64PFX); + + m_ServerCertificate = new X509Certificate2(decodedPfx); + + if (!m_ServerCertificate.HasPrivateKey) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning("The imported PFX file did not have a private key"); + } + } + } + } + catch (FormatException e) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Error) + { + NetworkLog.LogError("Parsing PFX failed: " + e); + } + } + } + catch (CryptographicException e) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Error) + { + NetworkLog.LogError("Importing of certificate failed: " + e); + } + } + } + } +} diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs.meta b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs.meta new file mode 100644 index 00000000..d7435c0a --- /dev/null +++ b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d1a5905f8f5d420c8806675d3843373a +timeCreated: 1617961447 \ No newline at end of file diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/MLAPI.Cryptography.dll b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/MLAPI.Cryptography.dll new file mode 100644 index 0000000000000000000000000000000000000000..e2cfc9e3933249c5036a1ff9a0ae1c2c360f5b4d GIT binary patch literal 30720 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 Date: Fri, 29 Oct 2021 13:52:18 +0200 Subject: [PATCH 2/7] Updated for NetCode for GameObjects v1.0 --- .../Runtime/CryptographyTransportAdapter.cs | 534 +++++++++--------- .../package.json | 1 - 2 files changed, 265 insertions(+), 270 deletions(-) diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs index c624a83a..b4f21b64 100644 --- a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs +++ b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs @@ -1,15 +1,14 @@ using System; using System.Collections.Generic; +using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using MLAPI.Cryptography.KeyExchanges; -using MLAPI.Logging; -using MLAPI.Serialization; -using MLAPI.Serialization.Pooled; using MLAPI.Transport.ChaCha20.ChaCha20; -using MLAPI.Transports.Tasks; +using Unity.Netcode; using UnityEngine; +using UnityEngine.Assertions; namespace MLAPI.Transport.ChaCha20 { @@ -67,7 +66,8 @@ private enum ClientState : byte } // Max message size - private byte[] m_CryptoBuffer = new byte[1024 * 8]; + private readonly byte[] m_CryptoBuffer = new byte[1024 * 8]; + private readonly byte[] m_WriteBuffer = new byte[1024 * 8]; private enum MessageType : byte { @@ -77,350 +77,348 @@ private enum MessageType : byte Internal // MLAPI Message } - public override void Send(ulong clientId, ArraySegment data, NetworkChannel networkChannel) + public override void Send(ulong clientId, ArraySegment data, NetworkDelivery networkDelivery) { - using (PooledNetworkBuffer buffer = PooledNetworkBuffer.Get()) - using (PooledNetworkWriter writer = PooledNetworkWriter.Get(buffer)) - { - // Write message type - writer.WriteBits((byte)MessageType.Internal, 2); - - // Align bits - writer.WritePadBits(); - - // Get the ChaCha20 cipher - ChaCha20Cipher cipher = clientId == ServerClientId ? m_ServerCipher : m_ClientCiphers[clientId]; - - // Store position (length messes with it) - long position = buffer.Position; - - // Expand buffer with data count - buffer.SetLength(buffer.Length + data.Count); - - // Restore position - buffer.Position = position; + // Write message type + m_WriteBuffer[0] = (byte) MessageType.Internal; - // Encrypt with ChaCha - cipher.ProcessBytes(data.Array, data.Offset, buffer.GetBuffer(), (int)buffer.Position, data.Count); + // Get the ChaCha20 cipher + ChaCha20Cipher cipher = clientId == ServerClientId ? m_ServerCipher : m_ClientCiphers[clientId]; + // Encrypt with ChaCha + cipher.ProcessBytes(data.Array, data.Offset, m_WriteBuffer, 1, data.Count); - // Send the encrypted format - Transport.Send(clientId, new ArraySegment(buffer.GetBuffer(), 0, (int)buffer.Length), networkChannel); - } + // Send the encrypted format + Transport.Send(clientId, new ArraySegment(m_WriteBuffer, 0, 1 + data.Count), networkDelivery); } - private readonly NetworkBuffer m_DataBuffer = new NetworkBuffer(); - public override NetworkEvent PollEvent(out ulong clientId, out NetworkChannel networkChannel, out ArraySegment payload, out float receiveTime) + public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment payload, out float receiveTime) { - NetworkEvent @event = Transport.PollEvent(out ulong internalClientId, out NetworkChannel internalNetworkChannel, out ArraySegment internalPayload, out float internalReceiveTime); + NetworkEvent @event = Transport.PollEvent(out ulong internalClientId, out ArraySegment internalPayload, out float internalReceiveTime); if (@event == NetworkEvent.Connect && m_IsServer && !m_ClientStates.ContainsKey(internalClientId)) { - // Send server a handshake - using (PooledNetworkBuffer hailBuffer = PooledNetworkBuffer.Get()) - using (PooledNetworkWriter hailWriter = PooledNetworkWriter.Get(hailBuffer)) + // Write message type + m_WriteBuffer[0] = (byte)((byte)MessageType.Hail | (SignKeyExchange ? (byte)1 : (byte)0) << 7); + + int length = 0; + + if (SignKeyExchange) { - // Write message type - hailWriter.WriteBits((byte)MessageType.Hail, 2); + // Create handshake parameters + ECDiffieHellmanRSA keyExchange = new ECDiffieHellmanRSA(m_ServerCertificate); + m_ClientSignedKeyExchanges.Add(internalClientId, keyExchange); - // Write if key exchange should be signed - hailWriter.WriteBit(SignKeyExchange); + // Write public part length + m_WriteBuffer[1] = (byte) m_ServerCertificateBytes.Length; + m_WriteBuffer[2] = (byte) (m_ServerCertificateBytes.Length >> 8); - // Pad bits - hailWriter.WritePadBits(); + // Write public part of RSA key + Buffer.BlockCopy(m_ServerCertificateBytes, 0, m_WriteBuffer, 3, m_ServerCertificateBytes.Length); - if (SignKeyExchange) - { - // Create handshake parameters - ECDiffieHellmanRSA keyExchange = new ECDiffieHellmanRSA(m_ServerCertificate); - m_ClientSignedKeyExchanges.Add(internalClientId, keyExchange); + // Get the secure public part (semi heavy) + byte[] securePublic = keyExchange.GetSecurePublicPart(); - // Write public part of RSA key - hailWriter.WriteByteArray(m_ServerCertificateBytes); + // Write public part length + m_WriteBuffer[3 + m_ServerCertificateBytes.Length] = (byte) securePublic.Length; + m_WriteBuffer[4 + m_ServerCertificateBytes.Length] = (byte) (securePublic.Length >> 8); - // Write key exchange public part - hailWriter.WriteByteArray(keyExchange.GetSecurePublicPart()); - } - else - { - // Create handshake parameters - ECDiffieHellman keyExchange = new ECDiffieHellman(); - m_ClientKeyExchanges.Add(internalClientId, keyExchange); + // Write key exchange public part + Buffer.BlockCopy(securePublic, 0, m_WriteBuffer, 5 + m_ServerCertificateBytes.Length, securePublic.Length); - // Write key exchange public part - hailWriter.WriteByteArray(keyExchange.GetPublicKey()); - } + // Set length + length = 5 + m_ServerCertificateBytes.Length + securePublic.Length; + } + else + { + // Create handshake parameters + ECDiffieHellman keyExchange = new ECDiffieHellman(); + m_ClientKeyExchanges.Add(internalClientId, keyExchange); - // Send hail - Transport.Send(internalClientId, new ArraySegment(hailBuffer.GetBuffer(), 0, (int)hailBuffer.Length), NetworkChannel.Internal); + // Get the secure public part (semi heavy) + byte[] publicKey = keyExchange.GetPublicKey(); + + // Write public part length + m_WriteBuffer[1] = (byte) publicKey.Length; + m_WriteBuffer[2] = (byte) (publicKey.Length >> 8); + + // Write key exchange public part + Buffer.BlockCopy(publicKey, 0, m_WriteBuffer, 3, publicKey.Length); + + // Set length + length = 3 + publicKey.Length; } + // Ensure length is set + Assert.IsTrue(length != 0); + + // Send hail + Transport.Send(internalClientId, new ArraySegment(m_WriteBuffer, 0, length), NetworkDelivery.ReliableSequenced); + // Add them to client state m_ClientStates.Add(internalClientId, ClientState.WaitingForHailResponse); clientId = internalClientId; - networkChannel = NetworkChannel.Internal; payload = new ArraySegment(); receiveTime = internalReceiveTime; return NetworkEvent.Nothing; } else if (@event == NetworkEvent.Data) { - // Set the data to the buffer - m_DataBuffer.SetTarget(internalPayload.Array); - m_DataBuffer.SetLength(internalPayload.Count + internalPayload.Offset); - m_DataBuffer.Position = internalPayload.Offset; + // TODO: IGNORE SIGN BIT - using (PooledNetworkReader dataReader = PooledNetworkReader.Get(m_DataBuffer)) - { - MessageType messageType = (MessageType)dataReader.ReadByteBits(2); + // Keep track of a read head + int position = internalPayload.Offset; + int start = position; - if (messageType == MessageType.Hail && !m_IsServer) - { - // Server sent us a hail + MessageType messageType = (MessageType) (internalPayload.Array[position++] & 0x7F); - // Read if the data was signed - bool sign = dataReader.ReadBit(); + if (messageType == MessageType.Hail && !m_IsServer) + { + // Server sent us a hail - if (sign != SignKeyExchange) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Error) - { - NetworkLog.LogError("Mismatch between " + nameof(SignKeyExchange)); - } + // Read if the data was signed + bool sign = ((internalPayload.Array[start] & 0x80) >> 7) == 1; - clientId = internalClientId; - networkChannel = NetworkChannel.Internal; - payload = new ArraySegment(); - receiveTime = internalReceiveTime; - return NetworkEvent.Nothing; + if (sign != SignKeyExchange) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Error) + { + NetworkLog.LogErrorServer("Mismatch between " + nameof(SignKeyExchange)); } - // Align bits - dataReader.SkipPadBits(); + clientId = internalClientId; + payload = new ArraySegment(); + receiveTime = internalReceiveTime; + return NetworkEvent.Nothing; + } - if (SignKeyExchange) - { - // Read certificate - m_ServerCertificate = new X509Certificate2(dataReader.ReadByteArray()); + if (SignKeyExchange) + { + // Read certificate length + ushort certLength = (ushort)(internalPayload.Array[position++] | (ushort)(internalPayload.Array[position++] << 8)); - // TODO: IMPORTANT!!! VERIFY CERTIFICATE!!!!!!! + // Alloc cert + // Cert needs exact buffer size so we cannot reuse + byte[] cert = new byte[certLength]; - // Create key exchange - m_ServerSignedKeyExchange = new ECDiffieHellmanRSA(m_ServerCertificate); + // Copy cert into cert buffer + Buffer.BlockCopy(internalPayload.Array, position += certLength, cert, 0, certLength); - // Read servers public part - byte[] serverPublicPart = dataReader.ReadByteArray(); + // Create cert + m_ServerCertificate = new X509Certificate2(cert); - // Get shared - byte[] key = m_ServerSignedKeyExchange.GetVerifiedSharedPart(serverPublicPart); + // TODO: IMPORTANT!!! VERIFY CERTIFICATE!!!!!!! - // Do key stretching with PBKDF2-HMAC-SHA1 with Application.productName as salt - using (Rfc2898DeriveBytes pbdkf = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(Application.productName), 10_000)) - { - // Add raw key for external use - ServerKey = key; + // Create key exchange + m_ServerSignedKeyExchange = new ECDiffieHellmanRSA(m_ServerCertificate); - // ChaCha wants 48 bytes - byte[] chaChaData = pbdkf.GetBytes(48); + // Read public part length + ushort publicPartLength = (ushort)(internalPayload.Array[position++] | (ushort)(internalPayload.Array[position++] << 8)); - // Get key part - byte[] chaChaKey = new byte[32]; - Buffer.BlockCopy(chaChaData, 0, chaChaKey, 0, 32); + // Read servers public part length + byte[] serverPublicPart = new byte[publicPartLength]; - // Get nonce part - byte[] chaChaNonce = new byte[12]; - Buffer.BlockCopy(chaChaData, 32, chaChaNonce, 0, 12); + // Copy public part + Buffer.BlockCopy(internalPayload.Array, position += publicPartLength, serverPublicPart, 0, publicPartLength); - // Create cipher - m_ServerCipher = new ChaCha20Cipher(chaChaKey, chaChaNonce, BitConverter.ToUInt32(chaChaData, 32 + 12)); - } - } - else + // Get shared + byte[] key = m_ServerSignedKeyExchange.GetVerifiedSharedPart(serverPublicPart); + + // Do key stretching with PBKDF2-HMAC-SHA1 with Application.productName as salt + using (Rfc2898DeriveBytes pbdkf = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(Application.productName), 10_000)) { - // Create key exchange - m_ServerKeyExchange = new ECDiffieHellman(); + // Add raw key for external use + ServerKey = key; - // Read servers public part - byte[] serverPublicPart = dataReader.ReadByteArray(); + // ChaCha wants 48 bytes + byte[] chaChaData = pbdkf.GetBytes(48); - // Get shared - byte[] key = m_ServerKeyExchange.GetSharedSecretRaw(serverPublicPart); + // Get key part + byte[] chaChaKey = new byte[32]; + Buffer.BlockCopy(chaChaData, 0, chaChaKey, 0, 32); - // Do key stretching with PBKDF2-HMAC-SHA1 with Application.productName as salt - using (Rfc2898DeriveBytes pbdkf = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(Application.productName), 10_000)) - { - // Add raw key for external use - ServerKey = key; + // Get nonce part + byte[] chaChaNonce = new byte[12]; + Buffer.BlockCopy(chaChaData, 32, chaChaNonce, 0, 12); - // ChaCha wants 48 bytes - byte[] chaChaData = pbdkf.GetBytes(48); + // Create cipher + m_ServerCipher = new ChaCha20Cipher(chaChaKey, chaChaNonce, BitConverter.ToUInt32(chaChaData, 32 + 12)); + } + } + else + { + // Create key exchange + m_ServerKeyExchange = new ECDiffieHellman(); - // Get key part - byte[] chaChaKey = new byte[32]; - Buffer.BlockCopy(chaChaData, 0, chaChaKey, 0, 32); + // Read public part length + ushort publicPartLength = (ushort)(internalPayload.Array[position++] | (ushort)(internalPayload.Array[position++] << 8)); - // Get nonce part - byte[] chaChaNonce = new byte[12]; - Buffer.BlockCopy(chaChaData, 32, chaChaNonce, 0, 12); + // Read servers public part length + byte[] serverPublicPart = new byte[publicPartLength]; - // Create cipher - m_ServerCipher = new ChaCha20Cipher(chaChaKey, chaChaNonce, BitConverter.ToUInt32(chaChaData, 32 + 12)); - } - } + // Copy buffer + Buffer.BlockCopy(internalPayload.Array, position, serverPublicPart, 0, publicPartLength); + + // Get shared + byte[] key = m_ServerKeyExchange.GetSharedSecretRaw(serverPublicPart); - // Respond with hail response - using (PooledNetworkBuffer hailResponseBuffer = PooledNetworkBuffer.Get()) - using (PooledNetworkWriter hailResponseWriter = PooledNetworkWriter.Get(hailResponseBuffer)) + // Do key stretching with PBKDF2-HMAC-SHA1 with Application.productName as salt + using (Rfc2898DeriveBytes pbdkf = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(Application.productName), 10_000)) { - // Write message type - hailResponseWriter.WriteBits((byte)MessageType.HailResponse, 2); + // Add raw key for external use + ServerKey = key; - // Align bits - hailResponseWriter.WritePadBits(); + // ChaCha wants 48 bytes + byte[] chaChaData = pbdkf.GetBytes(48); - if (SignKeyExchange) - { - // Write public part - hailResponseWriter.WriteByteArray(m_ServerSignedKeyExchange.GetSecurePublicPart()); - } - else - { - // Write public part - hailResponseWriter.WriteByteArray(m_ServerKeyExchange.GetPublicKey()); - } + // Get key part + byte[] chaChaKey = new byte[32]; + Buffer.BlockCopy(chaChaData, 0, chaChaKey, 0, 32); - // Send hail response - Transport.Send(internalClientId, new ArraySegment(hailResponseBuffer.GetBuffer(), 0, (int)hailResponseBuffer.Length), NetworkChannel.Internal); - } + // Get nonce part + byte[] chaChaNonce = new byte[12]; + Buffer.BlockCopy(chaChaData, 32, chaChaNonce, 0, 12); - clientId = internalClientId; - networkChannel = NetworkChannel.Internal; - payload = new ArraySegment(); - receiveTime = internalReceiveTime; - return NetworkEvent.Nothing; + // Create cipher + m_ServerCipher = new ChaCha20Cipher(chaChaKey, chaChaNonce, BitConverter.ToUInt32(chaChaData, 32 + 12)); + } } - else if (messageType == MessageType.HailResponse && m_IsServer && m_ClientStates.ContainsKey(internalClientId) && m_ClientStates[internalClientId] == ClientState.WaitingForHailResponse) - { - // Client sent us a hail response - // Align bits - dataReader.SkipPadBits(); + /* Respond with hail response */ - // Read clients public part - byte[] clientPublicPart = dataReader.ReadByteArray(); + // Write message type + m_WriteBuffer[0] = (byte) MessageType.HailResponse; - if (SignKeyExchange) - { - // Get key - byte[] key = m_ClientSignedKeyExchanges[internalClientId].GetVerifiedSharedPart(clientPublicPart); + // Get public part to write + byte[] publicPart = SignKeyExchange ? m_ServerSignedKeyExchange.GetSecurePublicPart() : m_ServerKeyExchange.GetPublicKey(); - // Do key stretching with PBKDF2-HMAC-SHA1 with Application.productName as salt - using (Rfc2898DeriveBytes pbdkf = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(Application.productName), 10_000)) - { - // Add raw key for external use - ClientKeys.Add(internalClientId, key); + // Write public part length + m_WriteBuffer[1] = (byte) publicPart.Length; + m_WriteBuffer[2] = (byte) (publicPart.Length >> 8); - // ChaCha wants 48 bytes - byte[] chaChaData = pbdkf.GetBytes(48); + // Write public part + Buffer.BlockCopy(publicPart, 0, m_WriteBuffer, 3, publicPart.Length); - // Get key part - byte[] chaChaKey = new byte[32]; - Buffer.BlockCopy(chaChaData, 0, chaChaKey, 0, 32); + // Send hail response + Transport.Send(internalClientId, new ArraySegment(m_WriteBuffer, 0, 3 + publicPart.Length), NetworkDelivery.ReliableSequenced); - // Get nonce part - byte[] chaChaNonce = new byte[12]; - Buffer.BlockCopy(chaChaData, 32, chaChaNonce, 0, 12); + clientId = internalClientId; + payload = new ArraySegment(); + receiveTime = internalReceiveTime; + return NetworkEvent.Nothing; + } + else if (messageType == MessageType.HailResponse && m_IsServer && m_ClientStates.ContainsKey(internalClientId) && m_ClientStates[internalClientId] == ClientState.WaitingForHailResponse) + { + // Client sent us a hail response - // Create cipher - m_ClientCiphers.Add(internalClientId, new ChaCha20Cipher(chaChaKey, chaChaNonce, BitConverter.ToUInt32(chaChaData, 32 + 12))); - } + // Read clients public part + ushort clientPublicPartLength = (ushort)(internalPayload.Array[position++] | (ushort)(internalPayload.Array[position++] << 8)); - // Cleanup - m_ClientSignedKeyExchanges.Remove(internalClientId); - } - else - { - // Get key - byte[] key = m_ClientKeyExchanges[internalClientId].GetSharedSecretRaw(clientPublicPart); + // Alloc public part + byte[] clientPublicPart = new byte[clientPublicPartLength]; - // Do key stretching with PBKDF2-HMAC-SHA1 with Application.productName as salt - using (Rfc2898DeriveBytes pbdkf = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(Application.productName), 10_000)) - { - // Add raw key for external use - ClientKeys.Add(internalClientId, key); + // Copy public part + Buffer.BlockCopy(internalPayload.Array, position, clientPublicPart, 0, clientPublicPartLength); - // ChaCha wants 48 bytes - byte[] chaChaData = pbdkf.GetBytes(48); + if (SignKeyExchange) + { + // Get key + byte[] key = m_ClientSignedKeyExchanges[internalClientId].GetVerifiedSharedPart(clientPublicPart); - // Get key part - byte[] chaChaKey = new byte[32]; - Buffer.BlockCopy(chaChaData, 0, chaChaKey, 0, 32); + // Do key stretching with PBKDF2-HMAC-SHA1 with Application.productName as salt + using (Rfc2898DeriveBytes pbdkf = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(Application.productName), 10_000)) + { + // Add raw key for external use + ClientKeys.Add(internalClientId, key); - // Get nonce part - byte[] chaChaNonce = new byte[12]; - Buffer.BlockCopy(chaChaData, 32, chaChaNonce, 0, 12); + // ChaCha wants 48 bytes + byte[] chaChaData = pbdkf.GetBytes(48); - // Create cipher - m_ClientCiphers.Add(internalClientId, new ChaCha20Cipher(chaChaKey, chaChaNonce, BitConverter.ToUInt32(chaChaData, 32 + 12))); - } + // Get key part + byte[] chaChaKey = new byte[32]; + Buffer.BlockCopy(chaChaData, 0, chaChaKey, 0, 32); - //Cleanup - m_ClientKeyExchanges.Remove(internalClientId); + // Get nonce part + byte[] chaChaNonce = new byte[12]; + Buffer.BlockCopy(chaChaData, 32, chaChaNonce, 0, 12); + + // Create cipher + m_ClientCiphers.Add(internalClientId, new ChaCha20Cipher(chaChaKey, chaChaNonce, BitConverter.ToUInt32(chaChaData, 32 + 12))); } - // Respond with ready response - using (PooledNetworkBuffer readyResponseBuffer = PooledNetworkBuffer.Get()) - using (PooledNetworkWriter readyResponseWriter = PooledNetworkWriter.Get(readyResponseBuffer)) + // Cleanup + m_ClientSignedKeyExchanges.Remove(internalClientId); + } + else + { + // Get key + byte[] key = m_ClientKeyExchanges[internalClientId].GetSharedSecretRaw(clientPublicPart); + + // Do key stretching with PBKDF2-HMAC-SHA1 with Application.productName as salt + using (Rfc2898DeriveBytes pbdkf = new Rfc2898DeriveBytes(key, Encoding.UTF8.GetBytes(Application.productName), 10_000)) { - // Write message type - readyResponseWriter.WriteBits((byte)MessageType.Ready, 2); + // Add raw key for external use + ClientKeys.Add(internalClientId, key); - // Align bits - readyResponseWriter.WritePadBits(); + // ChaCha wants 48 bytes + byte[] chaChaData = pbdkf.GetBytes(48); - // Send ready message - Transport.Send(internalClientId, new ArraySegment(readyResponseBuffer.GetBuffer(), 0, (int)readyResponseBuffer.Length), NetworkChannel.Internal); - } + // Get key part + byte[] chaChaKey = new byte[32]; + Buffer.BlockCopy(chaChaData, 0, chaChaKey, 0, 32); - // Elevate to connected - m_ClientStates[internalClientId] = ClientState.Connected; + // Get nonce part + byte[] chaChaNonce = new byte[12]; + Buffer.BlockCopy(chaChaData, 32, chaChaNonce, 0, 12); - clientId = internalClientId; - networkChannel = internalNetworkChannel; - payload = new ArraySegment(); - receiveTime = internalReceiveTime; - return NetworkEvent.Connect; - } - else if (messageType == MessageType.Ready && !m_IsServer) - { - // Server is ready for us! - // Let the MLAPI know we are connected - clientId = internalClientId; - networkChannel = internalNetworkChannel; - payload = new ArraySegment(); - receiveTime = internalReceiveTime; - return NetworkEvent.Connect; + // Create cipher + m_ClientCiphers.Add(internalClientId, new ChaCha20Cipher(chaChaKey, chaChaNonce, BitConverter.ToUInt32(chaChaData, 32 + 12))); + } + + //Cleanup + m_ClientKeyExchanges.Remove(internalClientId); } - else if (messageType == MessageType.Internal && (!m_IsServer || (m_ClientStates.ContainsKey(internalClientId) && m_ClientStates[internalClientId] == ClientState.Connected))) - { - // Decrypt and pass message to the MLAPI - // Align bits - dataReader.SkipPadBits(); + /* Respond with ready response */ - // Get the correct cipher - ChaCha20Cipher cipher = m_IsServer ? m_ClientCiphers[internalClientId] : m_ServerCipher; + // Write message type + m_WriteBuffer[0] = (byte) MessageType.Ready; - // Decrypt bytes - cipher.ProcessBytes(m_DataBuffer.GetBuffer(), (int)m_DataBuffer.Position, m_CryptoBuffer, 0, (int)(m_DataBuffer.Length - m_DataBuffer.Position)); + // Send ready message + Transport.Send(internalClientId, new ArraySegment(m_WriteBuffer, 0, 1), NetworkDelivery.ReliableSequenced); - clientId = internalClientId; - networkChannel = internalNetworkChannel; - payload = new ArraySegment(m_CryptoBuffer, 0, (int)(m_DataBuffer.Length - m_DataBuffer.Position)); - receiveTime = internalReceiveTime; - return NetworkEvent.Data; - } + // Elevate to connected + m_ClientStates[internalClientId] = ClientState.Connected; + + clientId = internalClientId; + payload = new ArraySegment(); + receiveTime = internalReceiveTime; + return NetworkEvent.Connect; + } + else if (messageType == MessageType.Ready && !m_IsServer) + { + // Server is ready for us! + // Let the MLAPI know we are connected + clientId = internalClientId; + payload = new ArraySegment(); + receiveTime = internalReceiveTime; + return NetworkEvent.Connect; + } + else if (messageType == MessageType.Internal && (!m_IsServer || (m_ClientStates.ContainsKey(internalClientId) && m_ClientStates[internalClientId] == ClientState.Connected))) + { + // Decrypt and pass message to the MLAPI + + // Get the correct cipher + ChaCha20Cipher cipher = m_IsServer ? m_ClientCiphers[internalClientId] : m_ServerCipher; + + // Decrypt bytes + cipher.ProcessBytes(internalPayload.Array, position, m_CryptoBuffer, 0, internalPayload.Count - position); + + clientId = internalClientId; + payload = new ArraySegment(m_CryptoBuffer, 0, internalPayload.Count - position); + receiveTime = internalReceiveTime; + return NetworkEvent.Data; } } else if (@event == NetworkEvent.Disconnect) @@ -468,26 +466,24 @@ public override NetworkEvent PollEvent(out ulong clientId, out NetworkChannel ne } clientId = internalClientId; - networkChannel = internalNetworkChannel; payload = new ArraySegment(); receiveTime = internalReceiveTime; return NetworkEvent.Disconnect; } clientId = internalClientId; - networkChannel = internalNetworkChannel; payload = new ArraySegment(); receiveTime = 0; return NetworkEvent.Nothing; } - public override SocketTasks StartClient() + public override bool StartClient() { m_IsServer = false; return Transport.StartClient(); } - public override SocketTasks StartServer() + public override bool StartServer() { m_IsServer = true; ParsePFX(); @@ -514,9 +510,9 @@ public override void Shutdown() Transport.Shutdown(); } - public override void Init() + public override void Initialize() { - Transport.Init(); + Transport.Initialize(); } private void ParsePFX() @@ -537,7 +533,7 @@ private void ParsePFX() { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning("The imported PFX file did not have a private key"); + NetworkLog.LogWarningServer("The imported PFX file did not have a private key"); } } } @@ -546,7 +542,7 @@ private void ParsePFX() { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { - NetworkLog.LogError("Parsing PFX failed: " + e); + NetworkLog.LogErrorServer("Parsing PFX failed: " + e); } } } @@ -554,7 +550,7 @@ private void ParsePFX() { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { - NetworkLog.LogError("Importing of certificate failed: " + e); + NetworkLog.LogErrorServer("Importing of certificate failed: " + e); } } } diff --git a/Transports/com.mlapi.contrib.transport.chacha20/package.json b/Transports/com.mlapi.contrib.transport.chacha20/package.json index 38260079..7b5520ff 100644 --- a/Transports/com.mlapi.contrib.transport.chacha20/package.json +++ b/Transports/com.mlapi.contrib.transport.chacha20/package.json @@ -6,6 +6,5 @@ "description": "ChaCha20 Transport for MLAPI", "author": "Albin Corén", "dependencies": { - "com.unity.multiplayer.mlapi": "0.0.1-preview.1" } } From 6327cbdaba253f7e28d3fe2de20312f49c2c30f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Fri, 29 Oct 2021 14:40:39 +0200 Subject: [PATCH 3/7] Added cert validation handler --- .../Runtime/CryptographyTransportAdapter.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs index b4f21b64..052674d3 100644 --- a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs +++ b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs @@ -12,7 +12,7 @@ namespace MLAPI.Transport.ChaCha20 { - public class CryptographyTransportAdapter : NetworkTransport + public class CryptographyTransportAdpater : NetworkTransport { public override ulong ServerClientId => Transport.ServerClientId; @@ -23,6 +23,8 @@ public class CryptographyTransportAdapter : NetworkTransport [TextArea] public string ServerBase64PFX; + public Func ValidateCertificate; + private X509Certificate2 m_ServerCertificate; private byte[] m_ServerCertificateBytes @@ -207,8 +209,22 @@ public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment(); + receiveTime = internalReceiveTime; + return NetworkEvent.Nothing; + } // Create key exchange m_ServerSignedKeyExchange = new ECDiffieHellmanRSA(m_ServerCertificate); From 31d1bbaaf29b265ffc2da264be95bc79200bfe7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Fri, 29 Oct 2021 14:41:28 +0200 Subject: [PATCH 4/7] Remove old comment --- .../Runtime/CryptographyTransportAdapter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs index 052674d3..4513c02f 100644 --- a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs +++ b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs @@ -167,8 +167,6 @@ public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment Date: Fri, 29 Oct 2021 20:22:23 +0200 Subject: [PATCH 5/7] Added counter resyncronization --- .../Runtime/ChaCha20/ChaCha20Cipher.cs | 65 ++---- .../Runtime/CryptographyTransportAdapter.cs | 219 ++++++++++-------- 2 files changed, 146 insertions(+), 138 deletions(-) diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/ChaCha20Cipher.cs b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/ChaCha20Cipher.cs index c892dfb6..c1b6a963 100644 --- a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/ChaCha20Cipher.cs +++ b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/ChaCha20/ChaCha20Cipher.cs @@ -34,6 +34,10 @@ public sealed class ChaCha20Cipher : IDisposable /// private bool m_IsDisposed; + private readonly byte[] m_Key; + private readonly byte[] m_Nonce; + private uint m_Counter; + /// /// Set up a new ChaCha20 state. The lengths of the given parameters are /// checked before encryption happens. @@ -58,6 +62,9 @@ public ChaCha20Cipher(byte[] key, byte[] nonce, uint counter) m_State = new uint[16]; m_IsDisposed = false; + m_Key = key; + m_Nonce = nonce; + KeySetup(key); IvSetup(nonce, counter); } @@ -67,6 +74,17 @@ public ChaCha20Cipher(byte[] key, byte[] nonce, uint counter) /// public uint[] State => m_State; + public uint Counter + { + get => m_Counter; + set => IvSetup(m_Nonce, value); + } + + public void SetCounter(uint counter) + { + IvSetup(m_Nonce, counter); + } + /// /// Set up the ChaCha state with the given key. A 32-byte key is required /// and enforced. @@ -147,6 +165,8 @@ private void IvSetup(byte[] nonce, uint counter) m_State[13] = Util.U8To32Little(nonce, 0); m_State[14] = Util.U8To32Little(nonce, 4); m_State[15] = Util.U8To32Little(nonce, 8); + + m_Counter = counter; } /// @@ -206,6 +226,8 @@ public void ProcessBytes(byte[] input, int inputOffset, byte[] output, int outpu m_State[13] = Util.AddOne(m_State[13]); } + m_Counter++; + if (count <= 64) { for (int i = count; i-- > 0;) @@ -265,49 +287,6 @@ public static void QuarterRound(uint[] x, uint a, uint b, uint c, uint d) x[b] = Util.Rotate(Util.XOr(x[b], x[c]), 7); } - /// - /// Currently not used. - /// - /// - /// - public static void ChaCha20BlockFunction(byte[] output, uint[] input) - { - if (input == null || output == null) - { - throw new ArgumentNullException(); - } - - if (input.Length != 16 || output.Length != 64) - { - throw new ArgumentException(); - } - - uint[] x = new uint[16]; // Working buffer - - for (int i = 16; i-- > 0;) - { - x[i] = input[i]; - } - - for (int i = 20; i > 0; i -= 2) - { - QuarterRound(x, 0, 4, 8, 12); - QuarterRound(x, 1, 5, 9, 13); - QuarterRound(x, 2, 6, 10, 14); - QuarterRound(x, 3, 7, 11, 15); - - QuarterRound(x, 0, 5, 10, 15); - QuarterRound(x, 1, 6, 11, 12); - QuarterRound(x, 2, 7, 8, 13); - QuarterRound(x, 3, 4, 9, 14); - } - - for (int i = 16; i-- > 0;) - { - Util.ToBytes(output, Util.Add(x[i], input[i]), 4 * i); - } - } - #region Destructor and Disposer /// diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs index 4513c02f..a0975606 100644 --- a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs +++ b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs @@ -12,7 +12,7 @@ namespace MLAPI.Transport.ChaCha20 { - public class CryptographyTransportAdpater : NetworkTransport + public class CryptographyTransportAdapter : NetworkTransport { public override ulong ServerClientId => Transport.ServerClientId; @@ -56,8 +56,10 @@ private byte[] m_ServerCertificateBytes public byte[] ServerKey { get; private set; } public readonly Dictionary ClientKeys = new Dictionary(); - private readonly Dictionary m_ClientCiphers = new Dictionary(); - private ChaCha20Cipher m_ServerCipher; + private readonly Dictionary m_ClientSendCiphers = new Dictionary(); + private readonly Dictionary m_ClientReceiveCiphers = new Dictionary(); + private ChaCha20Cipher m_ServerSendCipher; + private ChaCha20Cipher m_ServerReceiveCipher; private readonly Dictionary m_ClientStates = new Dictionary(); @@ -85,13 +87,18 @@ public override void Send(ulong clientId, ArraySegment data, NetworkDelive m_WriteBuffer[0] = (byte) MessageType.Internal; // Get the ChaCha20 cipher - ChaCha20Cipher cipher = clientId == ServerClientId ? m_ServerCipher : m_ClientCiphers[clientId]; + ChaCha20Cipher cipher = clientId == ServerClientId ? m_ServerSendCipher : m_ClientSendCiphers[clientId]; + + // Write least significant bits of counter + m_WriteBuffer[1] = (byte) cipher.Counter; + + Debug.LogError("SEND: " + cipher.Counter); // Encrypt with ChaCha - cipher.ProcessBytes(data.Array, data.Offset, m_WriteBuffer, 1, data.Count); + cipher.ProcessBytes(data.Array, data.Offset, m_WriteBuffer, 2, data.Count); // Send the encrypted format - Transport.Send(clientId, new ArraySegment(m_WriteBuffer, 0, 1 + data.Count), networkDelivery); + Transport.Send(clientId, new ArraySegment(m_WriteBuffer, 0, 2 + data.Count), networkDelivery); } public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment payload, out float receiveTime) @@ -103,7 +110,7 @@ public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment byte.MaxValue - 64 && leastSignificantBitsCounter < 64) + { + // The counter was on the upper half of the bits and the message we got was rolled over + + // Roll over the counter then set the 8 least significant bits to the senders + counter = ((counter + ((uint)byte.MaxValue - ((byte) counter)) + 1) & 0xFFFFFF00) | leastSignificantBitsCounter; + } + else if ((counter & 0xFF) < 64 && leastSignificantBitsCounter > byte.MaxValue - 64) + { + // The counter was on the lower half of the bits and the message we got was in the past + + // Roll the counter back to make the least significant bits 0xFF. + + // TODO: Optimize with something closer to real bitmath + // Eg. https://bisqwit.iki.fi/story/howto/bitmath/ + while ((counter & 0xFF) != byte.MaxValue) + { + counter--; + } + + // Counter was rolled back to where the bits were on the upper half then set to the least significant bits of the sender + counter = (counter & 0xFFFFFF00) | leastSignificantBitsCounter; + } + else + { + // We didn't detect a integer rollover from the 8 least significant bits. We simply set the last 8 to the senders 8 lsb + counter = (counter & 0xFFFFFF00) | leastSignificantBitsCounter; + } + + // Reset the cipher nonce and IV to the predicted value + cipher.SetCounter(counter); + } // Decrypt bytes cipher.ProcessBytes(internalPayload.Array, position, m_CryptoBuffer, 0, internalPayload.Count - position); @@ -461,10 +484,16 @@ public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment Date: Fri, 29 Oct 2021 21:03:31 +0200 Subject: [PATCH 6/7] Remove old log --- .../Runtime/CryptographyTransportAdapter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs index a0975606..1c469a4f 100644 --- a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs +++ b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs @@ -92,8 +92,6 @@ public override void Send(ulong clientId, ArraySegment data, NetworkDelive // Write least significant bits of counter m_WriteBuffer[1] = (byte) cipher.Counter; - Debug.LogError("SEND: " + cipher.Counter); - // Encrypt with ChaCha cipher.ProcessBytes(data.Array, data.Offset, m_WriteBuffer, 2, data.Count); From fb75a28d781cd926d814a98310a4d2d113f5f818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albin=20Cor=C3=A9n?= Date: Fri, 29 Oct 2021 22:18:40 +0200 Subject: [PATCH 7/7] Add HMAC support --- .../Runtime/CryptographyTransportAdapter.cs | 111 ++++++++++++++++-- 1 file changed, 101 insertions(+), 10 deletions(-) diff --git a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs index 1c469a4f..23826e4d 100644 --- a/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs +++ b/Transports/com.mlapi.contrib.transport.chacha20/Runtime/CryptographyTransportAdapter.cs @@ -5,6 +5,7 @@ using System.Security.Cryptography.X509Certificates; using System.Text; using MLAPI.Cryptography.KeyExchanges; +using MLAPI.Cryptography.Utils; using MLAPI.Transport.ChaCha20.ChaCha20; using Unity.Netcode; using UnityEngine; @@ -23,6 +24,9 @@ public class CryptographyTransportAdapter : NetworkTransport [TextArea] public string ServerBase64PFX; + [Tooltip("This adds significant CPU overhead and a 32 byte overhead on each message")] + public bool SignEveryMessage = false; + public Func ValidateCertificate; private X509Certificate2 m_ServerCertificate; @@ -53,14 +57,22 @@ private byte[] m_ServerCertificateBytes private readonly Dictionary m_ClientSignedKeyExchanges = new Dictionary(); private readonly Dictionary m_ClientKeyExchanges = new Dictionary(); + // Public keys for external use public byte[] ServerKey { get; private set; } public readonly Dictionary ClientKeys = new Dictionary(); + // Ciphers private readonly Dictionary m_ClientSendCiphers = new Dictionary(); private readonly Dictionary m_ClientReceiveCiphers = new Dictionary(); + private ChaCha20Cipher m_ServerSendCipher; private ChaCha20Cipher m_ServerReceiveCipher; + // HMACs + private readonly Dictionary m_ClientAuthenticators = new Dictionary(); + private HMACSHA256 m_ServerAuthenticator; + + // States private readonly Dictionary m_ClientStates = new Dictionary(); private enum ClientState : byte @@ -73,6 +85,8 @@ private enum ClientState : byte private readonly byte[] m_CryptoBuffer = new byte[1024 * 8]; private readonly byte[] m_WriteBuffer = new byte[1024 * 8]; + private readonly byte[] m_AuthenticationBuffer = new byte[32]; + private enum MessageType : byte { Hail, // Server->Client @@ -95,8 +109,20 @@ public override void Send(ulong clientId, ArraySegment data, NetworkDelive // Encrypt with ChaCha cipher.ProcessBytes(data.Array, data.Offset, m_WriteBuffer, 2, data.Count); + if (SignEveryMessage) + { + // Get authenticator + HMACSHA256 authenticator = clientId == ServerClientId ? m_ServerAuthenticator : m_ClientAuthenticators[clientId]; + + // Calculate signature + byte[] signature = authenticator.ComputeHash(m_WriteBuffer, 0, 2 + data.Count); + + // Copy signature + Buffer.BlockCopy(signature, 0, m_WriteBuffer, 2 + data.Count, signature.Length); + } + // Send the encrypted format - Transport.Send(clientId, new ArraySegment(m_WriteBuffer, 0, 2 + data.Count), networkDelivery); + Transport.Send(clientId, new ArraySegment(m_WriteBuffer, 0, 2 + data.Count + (SignEveryMessage ? 32 : 0)), networkDelivery); } public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment payload, out float receiveTime) @@ -106,7 +132,7 @@ public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment> 7) == 1; + bool signEveryMessage = ((internalPayload.Array[start] & 0x40) >> 6) == 1; - if (sign != SignKeyExchange) + if (sign != SignKeyExchange || signEveryMessage != SignEveryMessage) { - if (NetworkLog.CurrentLogLevel <= LogLevel.Error) + if (sign != SignKeyExchange) { - NetworkLog.LogErrorServer("Mismatch between " + nameof(SignKeyExchange)); + if (NetworkLog.CurrentLogLevel <= LogLevel.Error) + { + NetworkLog.LogErrorServer("Mismatch between " + nameof(SignKeyExchange)); + } + } + + if (signEveryMessage != SignEveryMessage) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Error) + { + NetworkLog.LogErrorServer("Mismatch between " + nameof(SignEveryMessage)); + } } clientId = internalClientId; @@ -272,7 +310,7 @@ public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment(); + receiveTime = internalReceiveTime; + return NetworkEvent.Nothing; + } + } + // Get the least significant bits of the counter byte leastSignificantBitsCounter = internalPayload.Array[position++]; @@ -448,10 +533,10 @@ public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment(m_CryptoBuffer, 0, internalPayload.Count - position); + payload = new ArraySegment(m_CryptoBuffer, 0, (internalPayload.Count - position) - (SignEveryMessage ? 32 : 0)); receiveTime = internalReceiveTime; return NetworkEvent.Data; } @@ -498,6 +583,12 @@ public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment