|
|
|
|
@ -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
|
|
|
|
|
/// </summary>
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sets the preshared encryption key.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="presharedKey"></param>
|
|
|
|
|
public void SetPresharedKey(byte[] presharedKey)
|
|
|
|
|
{
|
|
|
|
|
if (presharedKey != null) {
|
|
|
|
|
this.presharedKey = presharedKey;
|
|
|
|
|
isCryptoWrapped = true;
|
|
|
|
|
} else {
|
|
|
|
|
this.presharedKey = null;
|
|
|
|
|
isCryptoWrapped = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
@ -60,72 +87,121 @@ namespace fnecore
|
|
|
|
|
public async Task<UdpFrame> 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");
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Class implementing a UDP listener (server).
|
|
|
|
|
/// </summary>
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the <see cref="IPEndPoint"/> for this <see cref="UdpListener"/>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initializes a new instance of the <see cref="UdpListener"/> class.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="address"></param>
|
|
|
|
|
/// <param name="port"></param>
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initializes a new instance of the <see cref="UdpListener"/> class.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="endpoint"></param>
|
|
|
|
|
public UdpListener(IPEndPoint endpoint)
|
|
|
|
|
{
|
|
|
|
|
listen = endpoint;
|
|
|
|
|
client = new UdpClient(listen);
|
|
|
|
|
return new UdpFrame()
|
|
|
|
|
{
|
|
|
|
|
Message = buffer,
|
|
|
|
|
Endpoint = res.RemoteEndPoint
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
///
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="frame"></param>
|
|
|
|
|
public void Send(UdpFrame frame)
|
|
|
|
|
/// <param name="buffer"></param>
|
|
|
|
|
/// <param name="key"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
///
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="frame"></param>
|
|
|
|
|
public async Task<int> SendAsync(UdpFrame frame)
|
|
|
|
|
/// <param name="buffer"></param>
|
|
|
|
|
/// <param name="key"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
///
|
|
|
|
|
@ -205,7 +281,44 @@ namespace fnecore
|
|
|
|
|
/// <param name="frame"></param>
|
|
|
|
|
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
|
|
|
|
|
|