Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF Secure Messenger

0.00/5 (No votes)
22 Nov 2013 1  
WPF Secure Messenger

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:

Solution

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(); //Wait Here Until Server Has Started

                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);
            //SendMessage = string.Empty;        
        }
 
        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:

Not Connected

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(); //Wait Here Until Server Has Started

                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(); // Signals the main thread to say local server thread has been started
            }
            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:

Connected

Messages can then be sent securely, and then UI looks like this:

Message Sent

 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:

With VM

 

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here