Introduction
Here we have a simple C# WPF secure messenger. The application is designed to demonstrate methods of secure communication with Microsoft .NET 4.5, as well as the MVVM programming pattern often used with the WPF platform.
Background
This application started out as a demo I created for an interview in 2010. It was originally a Winforms application, and I thought it could benefit from some improvements.
So I decided to re-write it as a WPF application, with use of the MVVM and Command programming patterns.
Using the code
The solution consists of three projects, a WPF host project, a shared class library, and a server class library. As shown below:
The WPF host project has both server and client components. The client component runs on the main or UI thread, and the server component runs on a background thread.
Firstly I will explain the View Model and Command classes as they define the programming pattern used. Below is a copy of the MainViewModel.cs class:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows;
using Secure.Messenger.Server;
using Secure.Messenger.Shared;
namespace Secure.Messenger.WpfHost
{
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
SendData = new Command(SendDataExecute, SendDataCanExecute);
ConnectToServer = new Command(ConnectToServerExecute, ConnectToServerCanExecute);
RemoteIPAddress = LocalIPAddress().ToString();
SendMessage = "Hello";
if (!DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
StartServerThread();
}
}
#region Networking
CryptoServer _server;
TcpClient _client;
NetworkStream _strm;
public AutoResetEvent _serverStarted = new AutoResetEvent(false);
Thread ServerThread;
Int32 _port = 9050;
private void StartServerThread()
{
try
{
ServerThread = new Thread(new ThreadStart(StartServer));
ServerThread.IsBackground = true;
ServerThread.Priority = ThreadPriority.Normal;
ServerThread.Start();
_serverStarted.WaitOne();
StatusMessages.Add("Local Server Started Successfully at : " + _remoteIPAddress);
}
catch (Exception ex)
{
StatusMessages.Add("Problem Starting The Local Server : " + ex.Message);
return;
}
}
private void ConnectingToServer()
{
try
{
MessageData mes = new MessageData("Connection Message 123");
_client = new TcpClient(_remoteIPAddress, _port);
_strm = _client.GetStream();
if (!CryptoHelper.SendData(_strm, mes))
throw new Exception("Send data failed");
_strm.Close();
_client.Close();
StatusMessages.Add("Remote Server Connection Successfull to : " + _remoteIPAddress);
NotConnectedVisibility = Visibility.Hidden;
}
catch (Exception ex)
{
StatusMessages.Add("Problem Connecting to Remote Server at : " + _remoteIPAddress);
StatusMessages.Add("Message : " + ex.Message);
return;
}
}
private void SendTextData()
{
MessageData mes = new MessageData(SendMessage);
_client = new TcpClient(_remoteIPAddress.ToString(), _port);
_strm = _client.GetStream();
CryptoHelper.SendData(_strm, mes);
_strm.Close();
_client.Close();
SentMessages.Add("Sent to " + _remoteIPAddress.ToString() + " > " + SendMessage);
}
void StartServer()
{
_server = new CryptoServer(_serverStarted);
_server.ReceivedData += server_ReceivedData;
_server.StartServer(LocalIPAddress(), _port);
}
void server_ReceivedData(object sender, EventArgs<string> e)
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
ReceivedMessages.Add("Received from " + _remoteIPAddress + " > " + e.Value);
}));
}
private IPAddress LocalIPAddress()
{
if (!System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
{
return null;
}
IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
return host
.AddressList
.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork);
}
#endregion
#region Visibility
private Visibility _notConnectedVisibility = Visibility.Visible;
public Visibility NotConnectedVisibility
{
get
{
return _notConnectedVisibility;
}
set
{
_notConnectedVisibility = value;
RaisePropertyChanged("NotConnectedVisibility");
}
}
#endregion
#region Commands
public Command SendData { get; set; }
public Boolean SendDataCanExecute(Object parameter)
{
return true;
}
public void SendDataExecute(Object parameter)
{
SendTextData();
}
public Command ConnectToServer { get; set; }
public Boolean ConnectToServerCanExecute(Object parameter)
{
return true;
}
public void ConnectToServerExecute(Object parameter)
{
ConnectingToServer();
}
#endregion
#region TextData
private String _remoteIPAddress = String.Empty;
public String RemoteIPAddress
{
get
{
return _remoteIPAddress;
}
set
{
_remoteIPAddress = value;
RaisePropertyChanged("RemoteIPAddress");
}
}
private String _sendMessage = String.Empty;
public String SendMessage
{
get
{
return _sendMessage;
}
set
{
_sendMessage = value;
RaisePropertyChanged("SendMessage");
}
}
#endregion
#region Observable Collections
private ObservableCollection<String> _receivedMessages = new ObservableCollection<string>();
public ObservableCollection<String> ReceivedMessages
{
get
{
return _receivedMessages;
}
set
{
_receivedMessages = value;
RaisePropertyChanged("ReceivedMessages");
}
}
private ObservableCollection<String> _sentMessages = new ObservableCollection<string>();
public ObservableCollection<String> SentMessages
{
get
{
return _sentMessages;
}
set
{
_sentMessages = value;
RaisePropertyChanged("SentMessages");
}
}
private ObservableCollection<String> _statusMessages = new ObservableCollection<string>();
public ObservableCollection<String> StatusMessages
{
get
{
return _statusMessages;
}
set
{
_statusMessages = value;
RaisePropertyChanged("StatusMessages");
}
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName.ToString()));
}
}
#endregion
}
}
The class inherits from INotifyPropertyChanged the members of this interface are used to update the view. In this case MainWindow .axml.
The implimentation of the INotifyPropertyChanged members in this case is:
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName.ToString()));
}
}
The RaisePropertyChanged method is called when one of the properties of the View Model is updated, as shown:
private String _sendMessage = String.Empty;
public String SendMessage
{
get
{
return _sendMessage;
}
set
{
_sendMessage = value;
RaisePropertyChanged("SendMessage");
}
}
The MainViewModel.cs is connected to the MainWindow.xaml inside the axml code like this:
<Window x:Class="Secure.Messenger.WpfHost.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Secure.Messenger.WpfHost"
Title="Secure Messenger Demo" Height="730" Width="705"
ResizeMode="NoResize" Icon="Images/lock.ico">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
With the namespace Secure.Messenger.WpfHost
being declared in the Window tag as local
, then the Window.Datacontext
being declared as a class called MainViewModel
inside that namespace.
The controls in the view can then be bound to the properties of the View Model, like this:
<TextBox Grid.Column="1" Grid.Row="2" Width="150" FontWeight="Medium" HorizontalAlignment="Left" Height="25" VerticalAlignment="Top"
Text="{Binding RemoteIPAddress, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
With the Text
property of the textbox being bound to the RemoteIPAddress
property of the View Model.
The next aspect to explain is the Command.cs
class, shown below:
using System;
using System.Windows.Input;
namespace Secure.Messenger.WpfHost
{
public class Command : ICommand
{
public delegate void CommandOnExecute(object parameter);
public delegate bool CommandOnCanExecute(object parameter);
private CommandOnExecute _execute;
private CommandOnCanExecute _canExecute;
public Command(CommandOnExecute onExecuteMethod, CommandOnCanExecute onCanExecuteMethod)
{
_execute = onExecuteMethod;
_canExecute = onCanExecuteMethod;
}
#region ICommand Members
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute.Invoke(parameter);
}
public void Execute(object parameter)
{
_execute.Invoke(parameter);
}
#endregion
}
}
This class is used inside the MainViewModel.cs
class as a way of wiring the Commands paths of the controls to executable code, without resorting to using the code behind file. The implementation of the ICommand
interface ensure the commands get executed, and can execute.
The Commands are declared in the View Model like this:
public Command SendData { get; set; }
public Boolean SendDataCanExecute(Object parameter)
{
return true;
}
public void SendDataExecute(Object parameter)
{
SendTextData();
}
Instanciated like this:
SendData = new Command(SendDataExecute, SendDataCanExecute);
In the View Model's constructor. In the View the Commands are defined like this:
<Button Grid.Row="8" Grid.Column="1" Width="100" HorizontalAlignment="Right" Content="Send Message"
Margin="0,0,210,0" Command="{Binding SendData}"/>
With the Command
property of the Button control being bound to the SendData
property of the View Model.
The View Model also contains the code to perform the network operations, with some of the methods being farmed out to a help class called CryptoHelper
in the Secure.Messenger.Server
project. I will explain the network operations in more detail later on.
Next, here we have a screen shot of the MainWindow at start-up:
The application finds the IP address of the machine it's running on. The application then starts the local server instance at that IP address, and populates the 'IP Address of Target' Textbox with this same IP address, thus setting the application up in local loop-back mode.
==============
PLEASE NOTE: This application will only run if the machine's IP address is in the expected range of Local Area Networks. They are:
IP range: 10.0.0.0 – 10.255.255.255 subnet mask:255.0.0.0
IP range: 172.16.0.0 – 172.31.255.255 subnet mask: 255.240.0.0
IP range: 192.168.0.0 – 192.168.255.255 subnet mask: 255.255.0.0
This limitation of due to the use of a TCPListener
class in the Server, which can only be used in this range of addresses. I will explain the reason for this later.
So if your machine is on a LAN (I'm sure it is), then this application should start with no issues part from your firewall may ask you for permission to allow the use of the required port. In this case port 9050
.
===============
Once the application has started, the Server component is created and started on a separate thread like so:
private void StartServerThread()
{
try
{
ServerThread = new Thread(new ThreadStart(StartServer));
ServerThread.IsBackground = true;
ServerThread.Priority = ThreadPriority.Normal;
ServerThread.Start();
_serverStarted.WaitOne();
StatusMessages.Add("Local Server Started Successfully at : " + _remoteIPAddress);
}
catch (Exception ex)
{
StatusMessages.Add("Problem Starting The Local Server : " + ex.Message);
return;
}
}
This method is called in the constructor of the View Model like this:
public MainViewModel()
{
SendData = new Command(SendDataExecute, SendDataCanExecute);
ConnectToServer = new Command(ConnectToServerExecute, ConnectToServerCanExecute);
RemoteIPAddress = LocalIPAddress().ToString();
SendMessage = "Hello";
if (!DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
StartServerThread();
}
}
With a test to see whether we are in design mode. This makes sure the server thread isn't started in design mode. This caused my version of Visual Studio to crash whilst I was developing this application.
The start server method is shown below:
void StartServer()
{
_server = new CryptoServer(_serverStarted);
_server.ReceivedData += server_ReceivedData;
_server.StartServer(LocalIPAddress(), _port);
}
private IPAddress LocalIPAddress()
{
if (!System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
{
return null;
}
IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
return host
.AddressList
.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork);
}
The CryptoServer class is shown below:
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Secure.Messenger.Shared;
namespace Secure.Messenger.Server
{
public class CryptoServer
{
public event EventHandler<EventArgs<String>> ReceivedData;
AutoResetEvent _serverStarted;
public CryptoServer(AutoResetEvent serverStarted)
{
_serverStarted = serverStarted;
}
public void StartServer(IPAddress localIPAddress, int localPort)
{
TcpListener listener = null;
try
{
listener = new TcpListener(localIPAddress, localPort);
listener.Start();
_serverStarted.Set(); }
catch (Exception ex)
{
ReceivedData.SafeInvoke(this, new EventArgs<string>("From Server : Network Error : " + ex.Message));
return;
}
NetworkStream strm = null;
while (!listener.Pending())
{
TcpClient client = listener.AcceptTcpClient();
strm = client.GetStream();
MessageData mes = CryptoHelper.ReceiveData(strm);
if (mes != null)
{
if (mes.MessageBody != "Connection Message 123")
{
ReceivedData.SafeInvoke(this, new EventArgs<string>(mes.MessageBody));
}
}
else
ReceivedData.SafeInvoke(this, new EventArgs<string>("From Server : Deserialisation Error"));
}
}
}
}
As can be seen an AutoResetEvent
is passed into the server class, this is then used to signal be to the main thread that the server has started successfully.
A while loop is used to detect when a data has arrived in the TCPListener
, this may seem poor programming, but as there are no events to subscribe to when using a TCPListener
this is the only technique available.
The data is sent back from the server using a thread safe event invoking technique shown below. This event is then raised inside the View Model class, like so:
void server_ReceivedData(object sender, EventArgs<string> e)
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
ReceivedMessages.Add("Received from " + _remoteIPAddress + " > " + e.Value);
}));
}
The receiving method then has to use the Dispatcher.Invoke
to access the ReceivedMessages collection property as it can only be accessed on the UI thread. The Dispatcher.Invoke
acheives this requirement.
As can be seen two extension methods are used in this class, they are shown below:
namespace System
{
public class EventArgs<T> : EventArgs
{
public EventArgs(T value)
{
_value = value;
}
private T _value;
public T Value
{
get { return _value; }
}
}
public static class Extensions
{
public static void SafeInvoke<T>(this EventHandler<T> eventToRaise, object sender, T e) where T : EventArgs
{
EventHandler<T> handler = eventToRaise;
if (handler != null)
{
handler(sender, e);
}
}
}
}
The SafeInvoke<T> method is used as a thread safe way of invoking an event, and the EventArgs<T> provides a generic typed EventArgs class for sending data back to the main thread.
So one the server has been started and successfully connected to the local client (loop-back configuration), or to another instance of the application's client component, it looks like this:
Messages can then be sent securely, and then UI looks like this:
I tested the application using a VM, and then took a screen shot to prove the application can be used across a LAN. Shown below:
Secure Messenging Programming Logic
The secure messages are acheived with the use of a TripleDESCryptoServiceProvider
class, and a CryptoStream
class.
The CryptoStream
class is then transported across the network using a MemoryStream
class.
The methods used for sending and receiving the data are shown below:
public class CryptoHelper
{
public static Boolean SendData(NetworkStream strm, MessageData mes)
{
IFormatter formatter = new SoapFormatter();
MemoryStream memstrm = new MemoryStream();
TripleDESCryptoServiceProvider tdes = null;
CryptoStream csw = null;
try
{
byte[] Key = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
byte[] IV = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
tdes = new TripleDESCryptoServiceProvider();
csw = new CryptoStream(memstrm, tdes.CreateEncryptor(Key, IV), CryptoStreamMode.Write);
formatter.Serialize(csw, mes);
csw.FlushFinalBlock();
byte[] data = memstrm.GetBuffer();
int memsize = (int)memstrm.Length;
byte[] size = BitConverter.GetBytes(memsize);
strm.Write(size, 0, 4);
strm.Write(data, 0, (int)memsize);
return true;
}
catch (Exception)
{
return false;
}
finally
{
strm.Flush();
csw.Close();
memstrm.Close();
}
}
public static MessageData ReceiveData(NetworkStream strm)
{
MemoryStream memstrm = new MemoryStream();
byte[] Key = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16};
byte[] IV = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16};
TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
CryptoStream csw = new CryptoStream(memstrm, tdes.CreateDecryptor(Key, IV),
CryptoStreamMode.Write);
byte[] data = new byte[2048];
int recv = strm.Read(data, 0, 4);
int size = BitConverter.ToInt32(data, 0);
int offset = 0;
while (size > 0)
{
recv = strm.Read(data, 0, size);
csw.Write(data, offset, recv);
offset += recv;
size -= recv;
}
csw.FlushFinalBlock();
IFormatter formatter = new SoapFormatter();
memstrm.Position = 0;
MessageData mes = new MessageData(string.Empty);
try
{
mes = (MessageData)formatter.Deserialize(memstrm);
}
catch (SerializationException ex)
{
return null;
}
memstrm.Close();
return mes;
}
}
The programming logic is very low level, I could explain the encryption mechanisms more closely, but this article is quite long already, and I'm sure you can figure it out for yourselves by debugging the application.
As can be seen the encryption keys used are just test keys, and if this application was reverse engineered as all .NET assemblies can be, the keys would be found. So to make this application more secure the keys would have to be stored in the Windows registry, in the user hive. Then somehow the registry keys should be added at install.
Finally
I'm sure some of you may ask why this application does not WCF architecture, and you would be right. It should use WCF message or transport security, but I still think it is a good example of WPF and direct encryption techniques together.
It is my plan to write a second version of this application, again using WPF, but with WCF architecture and a supervisory client server element as well (for user management), so watch this space for that version.
Also, this is the reason why I used a simple TCPListener
class, as I saw this just as a demo version.
Points of Interest
Whilst developing this application I did find I had to use Parallel Watch windows to pick up some of the breakpoints in the server thread.
History
Version 1.0