From 12fc111c4db92b23dc8f2704213eb068fdb73c89 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Tue, 16 Jul 2024 22:19:00 -0400 Subject: [PATCH] add low-level support for AES wrapping; --- Udp.cs | 209 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 161 insertions(+), 48 deletions(-) diff --git a/Udp.cs b/Udp.cs index e0a4a84..e776445 100644 --- a/Udp.cs +++ b/Udp.cs @@ -7,13 +7,16 @@ * @package DVM / Fixed Network Equipment Core Library * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0) * -* Copyright (C) 2022 Bryan Biedenkapp, N2PLL +* Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL * */ using System; +using System.IO; using System.Net; using System.Net.Sockets; +using System.Runtime.Intrinsics.X86; +using System.Security.Cryptography; using System.Threading.Tasks; namespace fnecore @@ -39,8 +42,14 @@ namespace fnecore /// public abstract class UdpBase { + protected const ushort AES_WRAPPED_PCKT_MAGIC = 0xC0FE; + protected const int AES_BLOCK_SIZE = 128; + protected UdpClient client; + protected bool isCryptoWrapped; + protected byte[] presharedKey; + /* ** Methods */ @@ -51,6 +60,24 @@ namespace fnecore protected UdpBase() { client = new UdpClient(); + + isCryptoWrapped = false; + presharedKey = null; + } + + /// + /// Sets the preshared encryption key. + /// + /// + public void SetPresharedKey(byte[] presharedKey) + { + if (presharedKey != null) { + this.presharedKey = presharedKey; + isCryptoWrapped = true; + } else { + this.presharedKey = null; + isCryptoWrapped = false; + } } /// @@ -60,72 +87,121 @@ namespace fnecore public async Task Receive() { UdpReceiveResult res = await client.ReceiveAsync(); - return new UdpFrame() + + byte[] buffer = res.Buffer; + + // are we crypto wrapped? + if (isCryptoWrapped) { - Message = res.Buffer, - Endpoint = res.RemoteEndPoint - }; - } - } // public abstract class UDPBase + if (presharedKey == null) + throw new InvalidOperationException("tried to read datagram encrypted with no key? this shouldn't happen BUGBUG"); - /// - /// Class implementing a UDP listener (server). - /// - public class UdpListener : UdpBase - { - private IPEndPoint listen; + // does the network packet contain the appropriate magic leader? + ushort magic = FneUtils.ToUInt16(res.Buffer, 0); + if (magic == AES_WRAPPED_PCKT_MAGIC) + { + int cryptedLen = (res.Buffer.Length - 2) * sizeof(byte); + byte[] cryptoBuffer = new byte[res.Buffer.Length - 2]; + Buffer.BlockCopy(res.Buffer, 0, cryptoBuffer, 0, res.Buffer.Length - 2); - /* - ** Properties - */ + // do we need to pad the original buffer to be block aligned? + if (cryptedLen % AES_BLOCK_SIZE != 0) + { + int alignment = AES_BLOCK_SIZE - (cryptedLen % AES_BLOCK_SIZE); + cryptedLen += alignment; - /// - /// Gets the for this . - /// - public IPEndPoint EndPoint => listen; + // reallocate buffer and copy + cryptoBuffer = new byte[cryptedLen]; + Buffer.BlockCopy(res.Buffer, 0, cryptoBuffer, 0, res.Buffer.Length - 2); + } - /* - ** Methods - */ + // decrypt + byte[] decrypted = Decrypt(cryptoBuffer, presharedKey); - /// - /// Initializes a new instance of the class. - /// - /// - /// - public UdpListener(string address, int port) : this(new IPEndPoint(IPAddress.Parse(address), port)) - { - /* stub */ - } + // finalize, cleanup buffers and replace with new + if (decrypted != null) + buffer = decrypted; + else + buffer = new byte[0]; + } + else + buffer = new byte[0]; // this will effectively discard packets without the packet magic + } - /// - /// Initializes a new instance of the class. - /// - /// - public UdpListener(IPEndPoint endpoint) - { - listen = endpoint; - client = new UdpClient(listen); + return new UdpFrame() + { + Message = buffer, + Endpoint = res.RemoteEndPoint + }; } /// /// /// - /// - public void Send(UdpFrame frame) + /// + /// + /// + protected static byte[] Encrypt(byte[] buffer, byte[] key) { - client.Send(frame.Message, frame.Message.Length, frame.Endpoint); + byte[] encrypted = null; + using (AesManaged aes = new AesManaged() + { + KeySize = 256, + Key = key, + BlockSize = AES_BLOCK_SIZE, + Mode = CipherMode.ECB, + Padding = PaddingMode.Zeros, + IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } + }) + { + ICryptoTransform encryptor = aes.CreateEncryptor(); + using (MemoryStream ms = new MemoryStream()) + using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) + { + cs.Write(buffer, 0, buffer.Length); + encrypted = ms.ToArray(); + } + } + + return encrypted; } /// /// /// - /// - public async Task SendAsync(UdpFrame frame) + /// + /// + /// + protected static byte[] Decrypt(byte[] buffer, byte[] key) { - return await client.SendAsync(frame.Message, frame.Message.Length, frame.Endpoint); + byte[] decrypted = null; + using (AesManaged aes = new AesManaged() + { + KeySize = 256, + Key = key, + BlockSize = 128, + Mode = CipherMode.ECB, + Padding = PaddingMode.Zeros, + IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } + }) + { + ICryptoTransform decryptor = aes.CreateDecryptor(); + using (MemoryStream ms = new MemoryStream()) + using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) + { + byte[] outBuf = new byte[16384]; + int len = cs.Read(outBuf, 0, outBuf.Length); + if (len > 0) + { + decrypted = new byte[len]; + Buffer.BlockCopy(outBuf, 0, decrypted, 0, len); + } + } + } + + return decrypted; } - } // public class UdpListener : UdpBase + } // public abstract class UDPBase /// /// @@ -205,7 +281,44 @@ namespace fnecore /// public void Send(UdpFrame frame) { - client.Send(frame.Message, frame.Message.Length); + byte[] buffer = frame.Message; + + // are we crypto wrapped? + if (isCryptoWrapped) + { + if (presharedKey == null) + throw new InvalidOperationException("tried to read datagram encrypted with no key? this shouldn't happen BUGBUG"); + + int cryptedLen = buffer.Length * sizeof(byte); + byte[] cryptoBuffer = new byte[buffer.Length]; + Buffer.BlockCopy(buffer, 0, cryptoBuffer, 0, buffer.Length); + + // do we need to pad the original buffer to be block aligned? + if (cryptedLen % AES_BLOCK_SIZE != 0) + { + int alignment = AES_BLOCK_SIZE - (cryptedLen % AES_BLOCK_SIZE); + cryptedLen += alignment; + + // reallocate buffer and copy + cryptoBuffer = new byte[cryptedLen]; + Buffer.BlockCopy(buffer, 0, cryptoBuffer, 0, buffer.Length); + } + + // encrypt + byte[] crypted = Encrypt(cryptoBuffer, presharedKey); + + // finalize, cleanup buffers and replace with new + buffer = new byte[cryptedLen + 2]; + if (crypted != null) + { + Buffer.BlockCopy(crypted, 0, buffer, 2, cryptedLen); + FneUtils.WriteBytes(AES_WRAPPED_PCKT_MAGIC, ref buffer, 0); + } + else + return; + } + + client.Send(buffer, frame.Message.Length); } } // public class UdpReceiver : UdpBase } // namespace fnecore