Sept. 3, 2010, 9:08 a.m.
posted by vendetta
Socket Permissions
One common feature of network applications is the ability to allow or deny access to the application from specific clients on the network. This feature allows the application administrator to provide a basic level of security to keep unwanted customers out of the application.
You can add this functionality to your C# network applications using the SocketPermission and SocketPermissionAttribute classes, found in the System.Net namespace. These classes provide techniques that allow you to control access to and from your C# network applications based on client network addresses. The two classes implement either declarative or imperative security on the Socket object.
Declarative security Defines security attributes within the application using the SocketPermissionAttribute class. This technique uses metadata within the application code to define security restrictions for the application. Each restriction is defined as an attribute, using the SocketPermissionAttribute class to define the attribute properties.
Imperative security Creates a SocketPermission object within the application to control access. With imperative security, you must create a SocketPermission object and assign a list of security permissions to it.
By far, declarative security is the easiest and most-used technique of the two. Imperative security requires creating a new SocketPermission object, and assigning the proper permissions for the proper objects. This technique is more complex than declarative security, and beyond the scope of this chapter. The following section describes how to easily use declarative security in your network programs.
Implementing Declarative Security
Using declarative security in your network applications is simple. All you need is to add a separate attribute line to your source code for each security attribute you want to include.
The attribute line must be formatted properly for the security attribute. The attribute is placed in the application code using square brackets ([]). This is exactly what you saw in Chapter 16, "Remoting," with the [Serializable] attribute.
The SocketPermissionAttribute class properties are defined within the class constructor:
[SocketPermission(SecurityAction act, Access=acc, Host=host, Port=port, Transport=trans)]
Each property in the constructor defines an element that is checked for the security attribute. All elements in the attribute must be satisfied for the attribute to take effect. Following are definitions for the five parameters in the class:
The SecurityAction object The SecurityAction object defines the specific security action that is performed by the attribute. Figure lists the possible SecurityAction values. The most common values are SecurityAction.Assert, which allows access to the resource, and SecurityAction.Deny, which denies access to the resource.
|
member |
Description |
|---|---|
|
Assert |
The object can access the resource even if calls higher in the attributes deny access. |
|
Demand |
All calls to this class higher in the stack must have granted the permission. |
|
The ability to access the resource specified is denied. |
|
|
InheritanceDemand |
The derived class inheriting this class is required to have been granted permission. |
|
LinkDemand |
The immediate caller is required to have been granted permission. |
|
PermitOnly |
Only the resources specified in this action can be accessed. |
|
RequestMinimum |
The request for the minimum permissions required for the code to run. |
|
RequestOptional |
The request for additional permissions that are optional. |
|
RequestRefuse |
The request that permissions that might be misused will not be granted. |
Access property The Access property defines the network access method allowed by the security attribute. Only two values can be used:
Accept For allowing (or denying) binding to specific sockets
Connect
For allowing (or denying) connecting to specific remote sockets
| Warning |
Be careful of the Accept Access property. The name makes it sound like you can limit incoming connections by remote address. This is not the case. Instead, it specifies a local socket address that will be restricted from binding. |
The Host property The Host property defines the hostname or address in the security attribute. The value is specified as either a hostname string or an IP address octet string. Wildcards can also define more than one address on a network:
Host="192.168.1.*"
The Port property The Port property defines the TCP or UDP port in the security attribute. The value is specified as either an individual port number or the special value All, to represent all ports for the given transport and host.
The Transport property The Transport property defines the type of socket in the security attribute. The value is specified as one of the following string values:
All All transport types
Connectionless Connectionless transports, such as UDP
ConnectionOriented Connection-oriented transports, such as TCP
Tcp The TCP transport
Attributes are listed within the application code in the order in which they should be implemented. Each attribute is tested individually against the object attempting to access the application. As soon as one attribute denies access, the object is denied access to the application.
Using Declarative Security
Using declarative security in network applications is easy—just add the attribute statements within the class file and compile the program as normal. When the application is run, the socket permissions configured in the attribute lines will affect how the program is allowed to connect or bind to local sockets.
A Client Program
Listing 17.1 is the PickyTcpClient.cs program, which modifies the TcpClientSample.cs program from Listing 7.1 to incorporate declarative security restrictions that restrict what remote hosts the client can connect to.
using System;
using System.Net;
using System.Net.Sockets;
using System.Security;
using System.Security.Permissions;
using System.Text;
[SocketPermission(SecurityAction.Deny, Access="Connect", Host="127.0.0.1",
[SocketPermission(SecurityAction.Deny, Access="Connect", Host="192.168.0.2",
Port="All", Transport="All")]
[SocketPermission(SecurityAction.Deny, Access="Connect", Host="192.168.1.100",
Port="80", Transport="All")]
class PickyTcpClient
{
public static void Main()
{
byte[] data = new byte[1024];
string input, stringData;
TcpClient server = null;
Console.Write("Enter a host to connect to: ");
string stringHost = Console.ReadLine();
try
{
server = new TcpClient(stringHost, 9050);
} catch (SocketException)
{
Console.WriteLine("Unable to connect to server");
return;
} catch (SecurityException)
{
Console.WriteLine(
"Sorry, you are restricted from connecting to this server");
return;
}
NetworkStream ns = server.GetStream();
int recv = ns.Read(data, 0, data.Length);
stringData = Encoding.ASCII.GetString(data, 0, recv);
Console.WriteLine(stringData);
while(true)
{
input = Console.ReadLine();
if (input == "exit")
break;
ns.Write(Encoding.ASCII.GetBytes(input), 0, input.Length);
ns.Flush();
data = new byte[1024];
recv = ns.Read(data, 0, data.Length);
stringData = Encoding.ASCII.GetString(data, 0, recv);
Console.WriteLine(stringData);
}
Console.WriteLine("Disconnecting from server...");
ns.Close();
server.Close();
}
}
As you can see, the network program code itself is the same as the original TcpClientSample.cs program—the difference is in the metadata defined at the start of the program. The security attributes specified at the beginning define which sockets are restricted from being accessed by the client. The following is a sample attribute:
[SocketPermission(SecurityAction.Deny, Access="Connect", Host="127.0.0.1", Port="All", Transport="All")]
This attribute restricts the client program from connecting to any port on host 127.0.0.1 (the local machine).
A SecurityException is thrown when a socket permission attribute is triggered. An additional catch statement captures the Exception and produces a customer-friendly error message to the user:
try
{
server = new TcpClient(stringHost, 9050);
} catch (SocketException)
{
Console.WriteLine("Unable to connect to server");
return;
} catch (SecurityException)
{
Console.WriteLine(
"Sorry, you are restricted from connecting to this server");
return;
}
The output from this program should look like:
C:\>PickyTcpCLient Enter a host to connect to: 127.0.0.1 Sorry, you are restricted from connecting to this server C:\>
As expected, the connection attempt was denied, and the SecurityException was thrown.
A Server Program
The declarative security feature also works for servers, but in a slightly different manner. The security attributes defined for the server program restrict access to the local socket. Address and port pairs defined in the security attribute relate to the local address and ports on the server.
If the address and port pair are configured to be restricted, the application program cannot perform a Bind() method on that socket. An example of a server security attribute would be the following:
[SocketPermission(SecurityAction.Deny, Access="Accept", Host="0.0.0.0", Port="9050", Transport="All")]
This security attribute prevents the application from binding to TCP port 9050 on the local machine.
| Note |
Be careful when specifying the host part for Accept security attributes. If you use the machine’s internal loopback IP address, the application may still be able to bind to the external IP address. The special IP address IPAddress.Any (0.0.0.0) can represent all addresses for the interface. |
Listing 17.2 shows the PickyTcpListener.cs program, which uses the original TcpListenerSample.cs program from Listing 7.2 and adds socket permission attributes.
using System; using System.Net; using System.Net.Sockets; using System.Security; using System.Security.Permissions; using System.Text; [SocketPermission(SecurityAction.Deny, Access="Accept", Host="0.0.0.0", [SocketPermission(SecurityAction.Deny, Access="Accept", Host="0.0.0.0", Port="9051", Transport="All")] [SocketPermission(SecurityAction.Deny, Access="Accept", Host="0.0.0.0", Port="9052", Transport="All")] class PickyTcpListener { public static void Main() { int recv; TcpListener newsock = null; byte[] data = new byte[1024]; Console.Write("Enter port number to use: "); string stringPort = Console.ReadLine(); int port = Convert.ToInt32(stringPort); try { newsock = new TcpListener(port); newsock.Start(); } catch (SecurityException) { Console.WriteLine("Sorry, that port is unavailable"); return; } Console.WriteLine("Waiting for a client..."); TcpClient client = newsock.AcceptTcpClient(); NetworkStream ns = client.GetStream(); string welcome = "Welcome to my test server"; data = Encoding.ASCII.GetBytes(welcome); ns.Write(data, 0, data.Length); while(true) { data = new byte[1024]; recv = ns.Read(data, 0, data.Length); if (recv == 0) break; Console.WriteLine( Encoding.ASCII.GetString(data, 0, recv)); ns.Write(data, 0, recv); } ns.Close(); client.Close(); newsock.Stop(); } }
The PickyTcpListener.cs program restricts the application from accessing three separate ports (actually, six, if you count three for the TCP transport and three for the UDP transport). If the application attempts to access one of the restricted ports, a SecurityException is thrown, and an error message is produced.