From c4f2fe35cb01bb528cd92d383e9e9c01044188a4 Mon Sep 17 00:00:00 2001 From: firealarmss Date: Sun, 28 Jul 2024 02:59:31 -0500 Subject: [PATCH] Fix AES wrapped UDP to work properly with dvmfne --- FnePeer.cs | 8 ++++-- FneUtils.cs | 57 +++++++++++++++++++++++++++++++++++++ Udp.cs | 81 +++++++++++++++++++++-------------------------------- 3 files changed, 95 insertions(+), 51 deletions(-) diff --git a/FnePeer.cs b/FnePeer.cs index 0787a70..bff5c91 100644 --- a/FnePeer.cs +++ b/FnePeer.cs @@ -8,6 +8,7 @@ * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0) * * Copyright (C) 2022-2023 Bryan Biedenkapp, N2PLL +* Copyright (C) 2024 Caleb, KO4UYJ * */ @@ -117,7 +118,7 @@ namespace fnecore /// /// /// - public FnePeer(string systemName, uint peerId, string address, int port) : this(systemName, peerId, new IPEndPoint(IPAddress.Parse(address), port)) + public FnePeer(string systemName, uint peerId, string address, int port, string PresharedKey = null) : this(systemName, peerId, new IPEndPoint(IPAddress.Parse(address), port), PresharedKey) { /* stub */ } @@ -128,11 +129,14 @@ namespace fnecore /// /// /// - public FnePeer(string systemName, uint peerId, IPEndPoint endpoint) : base(systemName, peerId) + public FnePeer(string systemName, uint peerId, IPEndPoint endpoint, string PresharedKey = null) : base(systemName, peerId) { masterEndpoint = endpoint; client = new UdpReceiver(); + if (PresharedKey != null) + client.SetPresharedKey(FneUtils.ConvertHexStringToPresharedKey(PresharedKey)); + info = new PeerInformation(); info.PeerID = peerId; info.Connection = false; diff --git a/FneUtils.cs b/FneUtils.cs index 0e15e58..82b0e73 100644 --- a/FneUtils.cs +++ b/FneUtils.cs @@ -8,6 +8,7 @@ * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0) * * Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL +* Copyright (C) 2024 Caleb, KO4UYJ * */ @@ -619,6 +620,62 @@ namespace fnecore return new string(result); } + /// + /// Converts a hexadecimal string to a byte array. + /// + /// The hexadecimal string. + /// The byte array. + public static byte[] HexStringToByteArray(string hexString) + { + // Remove any spaces or non-hex characters if necessary + hexString = hexString.Replace(" ", "").Replace("-", ""); + + // Ensure the string length is even + if (hexString.Length % 2 != 0) + throw new ArgumentException("Invalid hex string length."); + + byte[] byteArray = new byte[hexString.Length / 2]; + + for (int i = 0; i < hexString.Length; i += 2) + { + byteArray[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16); + } + + return byteArray; + } + + /// + /// Converts a hexadecimal string to a byte array for preshared key. + /// + /// The hexadecimal string. + /// The expected key length in bytes. + /// The byte array. + public static byte[] ConvertHexStringToPresharedKey(string hexString) + { + if (hexString.Length == 32) + { + // Double the key if it's 32 characters (16 hex pairs) + hexString += hexString; + Console.WriteLine("Half-length network preshared encryption key detected, doubling key on itself."); + } + + if (hexString.Length == 64) + { + if (hexString.All(c => "0123456789abcdefABCDEF".Contains(c))) + { + return HexStringToByteArray(hexString); + } + else + { + throw new ArgumentException("Invalid characters in the network preshared encryption key."); + } + } + else + { + throw new ArgumentException("Invalid network preshared encryption key length, key should be 32 hex pairs, or 64 characters."); + } + } + /// /// Helper to display the ASCII representation of a hex dump. /// diff --git a/Udp.cs b/Udp.cs index e776445..1b4f9e7 100644 --- a/Udp.cs +++ b/Udp.cs @@ -87,7 +87,6 @@ namespace fnecore public async Task Receive() { UdpReceiveResult res = await client.ReceiveAsync(); - byte[] buffer = res.Buffer; // are we crypto wrapped? @@ -100,32 +99,27 @@ namespace fnecore 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); - - // 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(res.Buffer, 0, cryptoBuffer, 0, res.Buffer.Length - 2); - } + int cryptedLen = res.Buffer.Length - 2; + byte[] cryptoBuffer = new byte[cryptedLen]; + Buffer.BlockCopy(res.Buffer, 2, cryptoBuffer, 0, cryptedLen); // decrypt byte[] decrypted = Decrypt(cryptoBuffer, presharedKey); - // finalize, cleanup buffers and replace with new + // 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 + } } return new UdpFrame() @@ -174,7 +168,6 @@ namespace fnecore /// protected static byte[] Decrypt(byte[] buffer, byte[] key) { - byte[] decrypted = null; using (AesManaged aes = new AesManaged() { KeySize = 256, @@ -186,20 +179,14 @@ namespace fnecore }) { ICryptoTransform decryptor = aes.CreateDecryptor(); - using (MemoryStream ms = new MemoryStream()) + using (MemoryStream ms = new MemoryStream(buffer)) using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) + using (MemoryStream resultStream = new MemoryStream()) { - 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); - } + cs.CopyTo(resultStream); + return resultStream.ToArray(); } } - - return decrypted; } } // public abstract class UDPBase @@ -289,36 +276,32 @@ namespace fnecore 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); + // calculate the length of the encrypted data + int cryptedLen = buffer.Length; - // do we need to pad the original buffer to be block aligned? - if (cryptedLen % AES_BLOCK_SIZE != 0) + // calculate the padding needed to make the buffer a multiple of AES_BLOCK_SIZE + int paddingLen = AES_BLOCK_SIZE - (cryptedLen % AES_BLOCK_SIZE); + if (paddingLen == AES_BLOCK_SIZE) { - 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); + paddingLen = 0; // no padding needed if already a multiple of AES_BLOCK_SIZE } - // encrypt + byte[] cryptoBuffer = new byte[cryptedLen + paddingLen]; + Buffer.BlockCopy(buffer, 0, cryptoBuffer, 0, buffer.Length); + + // encrypt the buffer 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; + // create the final buffer with the magic number and encrypted data + buffer = new byte[crypted.Length + 2]; + Buffer.BlockCopy(crypted, 0, buffer, 2, crypted.Length); + FneUtils.WriteBytes(AES_WRAPPED_PCKT_MAGIC, ref buffer, 0); + + // set the length to the actual length of the buffer to be sent + frame.Message = buffer; } - client.Send(buffer, frame.Message.Length); + client.Send(frame.Message, frame.Message.Length); } } // public class UdpReceiver : UdpBase } // namespace fnecore