Improved .NET Remoting, Part 2: Secure TCP

.NET remoting enables application communication. It is a generic system that different applications can use to communicate with one another. .NET objects are exposed to remote processes, thus allowing interprocess communication. The applications can be located on the same computer, different computers on the same network, or even computers across separate networks.

Remoting communication is not secure by default, however. The 1.0 and 1.1 versions of the Microsoft Framework offered two options for making it secure:

  1. Use the HttpChannel, host the remoting server inside of IIS, and use SSL to secure communication between the client and server.
  2. Do something similar to this example from Microsoft, which is complicated, not supported, and requires you to create custom code.

The 2.0 version of the Microsoft Framework provides additional security-related functionality within the System.Runtime.Remoting.Channels.Tcp namespace. The TCP communication now can be secured either through code or the application configuration files. The classes support encryption and authentication using the Security Support Provider Interface (SSPI). It relies internally on the NegotiateStream class to secure the communication.

This article shows how to create and consume .NET remoting clients and servers using Visual Studio 2005 and the Microsoft .NET Framework 2.0, focusing on the security enhancements to the TCP channel and how to use them within your applications. It includes the following examples for communicating over secure TCP and securing the communication both in code and through the configuration file:

  1. Create a remotable object.
  2. Create a server to expose the remotable object.
  3. Create a client to connect to the server and consume the object.
  4. Show an example using the configuration file rather than code to control the security.

All you need to follow along is a familiarity with remoting, which you can acquire by referring to a prior article on the topic.

Create a Remotable Object

A remotable object is nothing more than an object that inherits from MarshalByRefObject. The following sample demonstrates a simple class to expose the omnipresent hello world message as an example. This object exposes a single method HelloWorld that will return a string. Anything that is serializable can be returned from the method call.

The first step is to create a new C# class library project. Add a class called SampleObject and put in the following code. Compile the class to make sure you have everything correct.

using System;

namespace CodeGuru.RemotingSample
{
   /// <remarks>
   /// Sample object to demonstrate the use of .NET remoting and IPC.
   /// </remarks>
   public class SampleObject : MarshalByRefObject 
   {
      /// <summary>
      /// Constructor
      /// </summary>
      public SampleObject()
      {
      }

      /// <summary>
      /// Return a hello message
      /// </summary>
      /// <returns>Hello world message</returns>
      public string HelloWorld()
      {
        return "Hello World!";
      }
   }
}

Create a Server to Expose the Remotable Object via TCP

To create a server object that will act as a listener to accepts remote object requests, you create an instance of the channel and then register it for use by clients on a specific port. You can register the service as WellKnownObjectMode.SingleCall, which results in a new instance of the object for each client, or as WellKnownObjectMode.Singleton, which results in one instance of the object used for all clients.

For this example, create a server listener. Because the service needs to be bound to an available port, choose a port that you know to be unused on your computer. (The example code uses 8080.) To see a list of the used ports on your computer, open a command prompt and issue the command "netstat -a". It may produce a long list, so make sure the command prompt buffer sizes are set to allow scrolling. Compile the class to make sure you have everything correct.

Create a new C# console application project. Add a class called SampleRemotingServer and paste in the following code:

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;

namespace CodeGuru.RemotingSample
{
   /// <remarks>
   /// Sample server to demonstrate the use of secure .NET Remoting.
   /// </remarks>
   public class SampleRemotingServer
   {
      public static void Main() 
      {
         // Set up the configuration parameters through a dictionary
         IDictionary properties = new Hashtable();
         properties.Add("port", 8080);
         properties.Add("secure", true);
         properties.Add("impersonate", true);

         // Create an instance of a channel
         TcpServerChannel serverChannel =
            new TcpServerChannel(properties, null);
         ChannelServices.RegisterChannel(serverChannel);

         // Register as an available service with the name HelloWorld
         RemotingConfiguration.RegisterWellKnownServiceType(
            typeof(SampleObject),
            "HelloWorld.rem", 
            WellKnownObjectMode.Singleton );

         Console.WriteLine("Listening on {0}",
                           serverChannel.GetChannelUri());
         Console.WriteLine("Press the enter key to exit...");
         Console.ReadLine();
      }
   }
}

Add a reference in your project to System.Runtime.Remoting; otherwise, the TcpChannel and other related classes will not be found. In addition, add a reference to the project containing the SampleObject. Otherwise, the code will not compile because it won't know how to find a reference to SampleObject.

To secure the communication programmatically, you need to configure the server connection. Notice how the example code passes parameter information in through a Dictionary. You can configure a number of additional parameters (Find more parameters in the VS 2005 Beta Docs on MSDN.) The parameters you will use on the server side for this example are as follows:

  • port: Specifies the port number on which the channel will listen.
  • secure: Indicates whether or not the channel is to be secured. (When secured, the channel can require user credentials, as well as encrypt and sign the communication. If the TcpServerChannel is secure, the TcpClientChannel must also be secure. Otherwise, no connection is made.)
  • impersonate: Indicates whether or not the server should impersonate the client.

Create a TCP Client to Use the Remotable Object

Now that you have your remotable object and a server object to listen for requests, create a client to consume it. The client will be very simple. It will connect to the server, create an instance of the object from the server, and then execute the HelloWorld method.

Create a new C# console application project. Add a class called SampleRemotingClient and paste in the following code:

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;

namespace CodeGuru.RemotingSample
{
   /// <remarks>
   /// Sample client to demonstrate the use of secure .NET Remoting.
   /// </remarks>
   public class SampleRemotingClient
   {
      public static void Main()
      {
         // Set up the configuration parameters through a dictionary
         IDictionary properties = new Hashtable();
         properties.Add("secure", true);
         properties.Add("connectionTimeout", 5000);
         properties.Add("tokenImpersonationLevel", "Impersonation");

         // Create a channel for communicating w/ the remote object
         IpcClientChannel clientChannel = 
         new IcpClientChannel(properties, null);
         ChannelServices.RegisterChannel(clientChannel);

         // Create an instance of the remote object
         RemotingConfiguration.RegisterWellKnownClientType(
            typeof(SampleObject),
            "ipc://localhost:8080/HelloWorld.rem");

         SampleObject sample = new SampleObject();
         Console.WriteLine("{0}", sample.HelloWorld());

         Console.WriteLine("Press the Enter key to exit...");
        Console.ReadLine();
      }
   }
}

Add a reference to System.Runtime.Remoting in the project; otherwise, the TcpChannel and other related classes will not be found. In addition, add a reference to the project containing the SampleObject. Otherwise, the code will not compile because it won't know how to find a reference to SampleObject. You could use the Activator object to create a remote instance, but this example simply uses the RemotingConfiguration object to register the SampleObject to be created from the remote location.

You also need to pass parameters to the client channel, which you'll do the same way as the server parameters in the previous section. The parameters you'll use on the client side are as follows:

  • secure: Indicates whether or not the channel is to be secured. (When secured, the channel can require user credentials, as well as encrypt and sign the communication. If the TcpServerChannel is secure, the TcpClientChannel must also be secure. Otherwise, no connection is made.)
  • connectionTimeout: Dictates number of milliseconds to wait for a successful connection. (The default value is to wait indefinitely, which you'll change for this example.)
  • tokenImpersonationLevel: Indicates how the client is to be authenticated with the server.

Improved .NET Remoting, Part 2: Secure TCP

Putting It All Together to Test the IPC Remoting Sample

Once you have created the projects and successfully compiled each of them, you are ready to try it out. Start the server by running the compiled executable. After the server successfully starts, the console window will display with the message "Press the enter key to exit..." (see Figure 1).

[RemotingServer.jpg]

Figure 1. The Server Successfully Starts

The server is listening, so now you are ready to run the client. Executing the client, whether by running the executable or debugging in Visual Studio, should result in "Hello World!" being displayed in a separate console window (see Figure 2). The window will pause until you press the Enter key to allow you time to see the resulting output message.

[RemotingClient.jpg]

Figure 1.The Client Successfully Executes

You should change some of the sample code. For example, if you change the client tokenImpersonationLevel to "Identification" and run the code, you'll receive an exception because the authentication configuration of the client and server do not match.

Using Application Configuration Files

Setting up the security in the application code is all well and good. However, I personally would prefer not to have to change code to make modifications to the way the security behaves in communication. This is where the application configuration files come into the picture. You can accomplish the same thing you did in the parameter setup of both the client and the server through the use of configuration files. You can set up and control the entire connection through configuration files. Just replace the code for configuring and setting up the channel and move it into the configuration files.

RemotingServerSample Configuration File

Add an application configuration file to your server. The following is the App.config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <system.runtime.remoting>
      <application>
         <service>
            <wellknown mode="Singleton"
                       type="CodeGuru.RemotingSample.SampleObject,
                             RemotingSample"
                       objectUri="HelloWorld.rem" />
         </service>
            <channels>
               <channel ref="tcp" secure="true" port="8080"
                        impersonate="true" />
            </channels>
      </application>
   </system.runtime.remoting>
</configuration>

The wellknown element specifies your mode, the object to create, the assembly location, and the URI to assign it. The channel element specifies that you want secure communication, to use port 8080, and to impersonate the user.

The server code becomes much simpler because the channel setup is now pushed into the configuration file:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;

namespace CodeGuru.RemotingSample
{
   /// <remarks>
   /// Sample server to demonstrate the use of secure .NET Remoting.
   /// </remarks>
   public class SampleRemotingServer
   {
      public static void Main() 
      {
         // Set the configuration according to your config file
         RemotingConfiguration.Configure(
            "RemotingClientSample.exe.config");

         Console.WriteLine("Listening on {0}", 
         ChannelServices.RegisteredChannels[0].ChannelName);
         Console.WriteLine("Press the enter key to exit...");
         Console.ReadLine();
      }
   }
}

RemotingClientSample Configuration File

Add an application configuration file to your client. The App.config file should contain the following:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <system.runtime.remoting>
      <application>
         <channels>
        <channel ref="tcp" secure="true"
                 tokenImpersonationLevel="impersonation" />
         </channels>
      </application>
   </system.runtime.remoting>
</configuration>

The client code becomes much simpler because the channel setup is now pushed into the configuration file:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;

namespace CodeGuru.RemotingSample
{
   /// <remarks>
   /// Sample client to demonstrate the use of secure .NET Remoting.
   /// </remarks>
   public class SampleRemotingClient
   {
      public static void Main()
      {
         // Set the configuration according to your config file
         RemotingConfiguration.Configure(
            "RemotingClientSample.exe.config");

         // Create an instance of the remote object
         RemotingConfiguration.RegisterWellKnownClientType(
            typeof(SampleObject),
            "ipc://localhost:8080/HelloWorld.rem");
         SampleObject sample = new SampleObject();

         Console.WriteLine("{0}", sample.HelloWorld());
         Console.WriteLine("Press the enter key to exit...");
         Console.ReadLine();
      }
   }
}

A Step Towards Secure Computing

.NET remoting is a powerful way to enable interprocess communication. The security enhancements to the Runtime.Remoting.Channels.Tcp namespace are a welcome step towards secure computing. It helps to ensure that objects flowing between your applications are secure during transport.

Future Columns

The topic of the next column is yet to be determined. If you have something in particular that you would like to see explained here, contact me at mstrawmyer@crowechizek.com.



About the Author

Mark Strawmyer

Mark Strawmyer is a Senior Architect of .NET applications for large and mid-size organizations. He specializes in architecture, design and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C# for the fifth year in a row. You can reach Mark at mark.strawmyer@crowehorwath.com.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • With 81% of employees using their phones at work, companies have stopped asking: "Is corporate data leaking from personal devices?" and started asking: "How do we effectively prevent corporate data from leaking from personal devices?" The answer has not been simple. ZixOne raises the bar on BYOD security by not allowing email data to reside on the device. In addition, Zix allows employees to maintain complete control of their personal device, therefore satisfying privacy demands of valued employees and the …

  • Hybrid cloud platforms need to think in terms of sweet spots when it comes to application platform interface (API) integration. Cloud Velocity has taken a unique approach to tight integration with the API sweet spot; enough to support the agility of physical and virtual apps, including multi-tier environments and databases, while reducing capital and operating costs. Read this case study to learn how a global-level Fortune 1000 company was able to deploy an entire 6+ TB Oracle eCommerce stack in Amazon Web …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds