Securing Stream Data






Securing Stream Data

Problem

You want to use the TCP server in Recipe 16.1 to communicate with the TCP client in Recipe 16.2. However, you need the communication to be secure.

Solution

Replace the NetworkStream class with the more secure SslStream class on both the client and the server. The code for the more secure TCP client, TCPClient_SSL, is shown in Figure (changes are highlighted).

TCPClient_SSL class

class TCPClient_SSL
{
    private TcpClient _client = null;
    private IPAddress _address = IPAddress.Parse("127.0.0.1");
    private int _port = 5;
    private IPEndPoint _endPoint = null;

    public TCPClient_SSL(string address, string port)
    {
        _address = IPAddress.Parse(address);
        _port = Convert.ToInt32(port);
        _endPoint = new IPEndPoint(_address, _port);
    }

    public void ConnectToServer(string msg)
    {
        try
        {
            using (client = new TcpClient())
            {
                client.Connect(_endPoint);

               
                using (SslStream sslStream = new SslStream(_client.GetStream(),
                                false, new RemoteCertificateValidationCallback(
                                    CertificateValidationCallback)))
                {
                    sslStream.AuthenticateAsClient("MyTestCert2");

                    // Get the bytes to send for the message.
                    byte[] bytes = Encoding.ASCII.GetBytes(msg);

                    // Send message.
                    Console.WriteLine("Sending message to server: " + msg);
                    
                    sslStream.Write(bytes, 0, bytes.Length);

                    // Get the response.
                    // Buffer to store the response bytes.
                    bytes = new byte[1024];

                    // Display the response.
                    
                    int bytesRead = sslStream.Read(bytes, 0, bytes.Length);
                    string serverResponse = Encoding.ASCII.GetString(bytes, 0,
                          bytesRead);
                    Console.WriteLine("Server said: " + serverResponse);
                }
            }
        }
        catch (SocketException e)
        {
            Console.WriteLine("There was an error talking to the server: {0}",
            e.ToString());
        }
    }

    
    private bool CertificateValidationCallback(object sender,
                                               X509Certificate certificate,
                                               X509Chain chain,
                                               SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors == SslPolicyErrors.None)
        {
            return true;
        }
        else
        {
            if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors)
            {
                Console.WriteLine("The X509Chain.ChainStatus returned an array " +
                   "of X509ChainStatus objects containing error information.");
            }
            else if (sslPolicyErrors ==
                     SslPolicyErrors.RemoteCertificateNameMismatch)
            {
                 Console.WriteLine("There was a mismatch of the name " +
                   "on a certificate.");
            }
            else if (sslPolicyErrors ==
                     SslPolicyErrors.RemoteCertificateNotAvailable)
            {
                 Console.WriteLine("No certificate was available.");
            }
            else
            {
                Console.WriteLine("SSL Certificate Validation Error!");
            }
        }

        Console.WriteLine(Environment.NewLine +
                          "SSL Certificate Validation Error!");
        Console.WriteLine(sslPolicyErrors.ToString());

        return false;
    }
}

The new code for the more secure TCP server, TCPServer_SSL, is shown in Figure (changes are highlighted).

TCPServer_SSL class

class TCPServer_SSL
{
    private TcpListener _listener = null;
    private IPAddress _address = IPAddress.Parse("127.0.0.1");
    private int _port = 55555;

    #region CTORs
    public TCPServer_SSL()
    {
    }

    public TCPServer_SSL(string address, string port)
    {
        _port = Convert.ToInt32(port);
        _address = IPAddress.Parse(address);
    }
    #endregion // CTORs

    #region Properties
    public IPAddress Address
    {
        get { return _address; }
        set { _address = value; }
    }

    public int Port
    {
        get { return _port; }
        set { _port = value; }
    }
    #endregion

    public void Listen()
    {
        try
        {
           _using_(listener = new TcpListener(_address, _port))
            {
                // Fire up the server.
                listener.Start();

                // Enter the listening loop.
                while (true)
                {
                    Console.Write("Looking for someone to talk to… ");

                    // Wait for connection.
                    TcpClient newClient = _listener.AcceptTcpClient();
                    Console.WriteLine("Connected to new client");

                    // Spin a thread to take care of the client.
                    ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessClient),
                                             newClient);
                }
            }
        }
        catch (SocketException e)
        {
            Console.WriteLine("SocketException: {0}", e);
        }
        finally
        {
            // Shut it down.
            _listener.Stop();
        }

        Console.WriteLine("\nHit any key (where is ANYKEY?) to continue…");
        Console.Read();
    }

    private void ProcessClient(object client)
    {
        using (TcpClient newClient = (TcpClient)client)
        {
            // Buffer for reading data.
            byte[] bytes = new byte[1024];
            string clientData = null;

            
            using (SslStream sslStream = new SslStream(newClient.GetStream()))
            {
               sslStream.AuthenticateAsServer(GetServerCert("MyTestCert2"), false,
                                       SslProtocols.Default, true);

               // Loop to receive all the data sent by the client.
               int bytesRead = 0;
               
               while ((bytesRead = sslStream.Read(bytes, 0, bytes.Length)) != 0)
               {
                   // Translate data bytes to an ASCII string.
                   clientData = Encoding.ASCII.GetString(bytes, 0, bytesRead);
                   Console.WriteLine("Client says: {0}", clientData);

                   // Thank them for their input.
                   bytes = Encoding.ASCII.GetBytes("Thanks call again!");

                   // Send back a response.
                   
                   sslStream.Write(bytes, 0, bytes.Length);
                }
            }
        }
    }

    
    private static X509Certificate GetServerCert(string subjectName)
    {
        X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        store.Open(OpenFlags.ReadOnly);
        X509CertificateCollection certificate =
                store.Certificates.Find(X509FindType.FindBySubjectName,
                                        subjectName, true);

        if (certificate.Count > 0)
            return (certificate[0]);
        else
            return (null);
    }
}

Discussion

For more information about the inner workings of the TCP server and client and how to run these applications, see Recipes 16.1 and 16.2. In this recipe, you will cover only the changes needed to convert the TCP server and client to use the SslStream object for secure communication.

The SslStream object uses the SSL protocol to provide a secure encrypted channel on which to send data. However, encryption is just one of the security features built into the SslStream object. Another feature of SslStream is that it prevents malicious or even accidental modification to the data. Even though the data is encrypted, it may become modified during transit. To determine if this has occurred, the data is signed with a hash before it is sent. When it is received, the data is rehashed and the two hashes are compared. If both hashes are equivalent, the message arrived intact; if the hashes are not equivalent, then something modified the data during transit.

The SslStream object also has the ability to use client and/or server certificates to authenticate the client and/or the server. These certificates are used to prove the identity of the issuer. For example, if a client attaches to a server using SSL, the server must provide a certificate to the client that is used to prove that the server is who it says it is. The SslStream object also allows the client to pass a certificate to the server if the client also needs to prove who it is to the server.

To allow the TCP server and client to communicate successfully, you need to set up an X.509 certificate that will be used to authenticate the TCP server. To do this, you set up a test certificate using the makecert.exe utility. This utility can be found in the <drive>:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin directory. The syntax for creating a simple certificate is as follows:

	makecert -r -pe -n "CN=MyTestCert2" -e 01/01/2036
	        -sr localMachine c:\MyAppTestCert.cer

The options are defined as follows:


-r

The certificate will be self-signed.


-pe

The certificate's private key will be exportable so that it can be included in the certificate.


-n "CN=MyTestCert2"

The publisher's certificate name. The name follows the "CN=" text.


-e 01/01/2036

The date at which this certificate expires.


-sr localMachine

The store where this certificate will be located. In this case, it is localMachine. However, you can also specify currentUser (which is the default if this switch is

omitted).

The final argument to the makecert.exe utility is the output filename, in this case c:\MyAppTestCert.cer. This will create the certificate in the c:\MyAppTestCert.cer file on the hard drive.

The next step involves opening Windows Explorer and right-clicking on the c:\MyAppTestCert.cer file. This will display a pop-up menu with the Install Certificate menu item. Click this menu item and a wizard will be started to allow you to import this .cer file into the certificate store. The first dialog box of the wizard is shown in Figure. Click the Next button to go to the next step in the wizard.

The first step of the Certificate Import Wizard


The next step in the wizard allows you to choose the certificate store in which you want to install your certificate. This dialog is shown in Figure. Keep the defaults and click the Next button.

The final step in the wizard is shown in Figure. On this dialog, click the Finish button.

Specifying a certificate store in the Certificate Import Wizard


The last step of the Certificate Import Wizard


After you click the Finish button, the message box shown in Figure is displayed, warning you to verify the certificate that you wish to install. Click the Yes button to install the certificate.

The security warning


Finally, the message box in Figure is displayed, indicating that the import was successful.

The import successful message


At this point you can run the TCP server and client and they should communicate successfully.

To use the SslStream in the TCP server project, you need to create a new SslStream object to wrap the TcpClient object:

	SslStream sslStream = new SslStream(newClient.GetStream());

Before you can use this new stream object, you must authenticate the server using the following line of code:

	sslStream.AuthenticateAsServer(GetServerCert("MyTestCert2"),
                                   false, SslProtocols.Default, true);

The GetServerCert method finds the server certificate used to authenticate the server. Notice the name passed in to this method; it is the same as the publisher's certificate name switch used with the makecert.exe utility (see the n switch). This certificate is returned from the GetServerCert method as an X509Certificate object. The next argument to the AuthenticateAsServer method is false, indicating that a client certificate is not required. The SslProtocols.Default argument indicates that the authentication mechanism (SSL 2.0, SSL 3.0, TLS 1.0, or PCT 1.0) is chosen based on what is available to the client and server. The final argument indicates that the certificate will be checked to see whether it has been revoked.

To use the SslStream in the TCP client project, you create a new SslStream object, a bit differently from how it was created in the TCP server project:

	SslStream sslStream = new SslStream(_client.GetStream(), false,
	      new RemoteCertificateValidationCallback(CertificateValidationCallback));

This constructor accepts a stream from the _client field, a false indicating that the stream associated with the _client field will be closed when the Close method of the SslStream object is called, and a delegate that validates the server certificate. The CertificateValidationCallback method is called whenever a server certificate needs to be validated. The server certificate is checked and any errors are passed into this delegate method to allow you to handle them as you wish.

The AuthenticateAsClient method is called next to authenticate the server:

	sslStream.AuthenticateAsClient("MyTestCert2");

As you can see, with a little extra work, you can replace the current stream type you are using with the SslStream to gain the benefits of the SSL protocol.

See Also

See the "SslStream Class" topic in the MSDN documentation.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows