Code Access Security Using C#
by
Sitaraman Lakshminarayanan
In today’s fast growing Information Technology world, security is a major concern.
Security is important not just to authenticate users but also to authorize their actions.
Common business scenarios where we need more control over security are
1. To restrict the user’s access to the application, based on their identity
2. To restrict the user’s access to protected resources and certain functions of the
application.
3. To verify the identity of the calling program, to check if the caller is trusted or
not.
In this article I will be taking a close look at .NET Security. Code Access Security
addresses these issues. The foundation of CAS is the Permission Object.
Permission:
CLR requires that the code should have required permissions to access the
protected resources. For example if the code has to read from an environment variable or
write information to a file, the code should have enough permission to perform these
operations. Permission Objects are used to enforce these security issues. Lets take a look
at what kind of Permission Objects are available in .Net and how we can use them to
secure our application. There are three different kinds of permission objects, each serving
a specific purpose: Identity Permission, Code Access Permission and Role Based
Security Permission.
Identity Permission:
CLR validates the identity of the assembly provided to it by the loader or host.
The host can be a server host (Asp.Net) or a shell host (command line). The identity,
called the Evidence, can be a strong name (StrongNameIdentityPermission), originating
Zone that is Intranet, Internet or Trusted site (ZoneIdentitypermission), publishers digital
signature (PublisherIdentityPermission), originating URL (URLIdentityPermission) or
originating website (WebSiteIdentityPermission). I will explain in detail with examples
the former two
StrongNameIdentityPermission:
This permission object identifies the caller with a strong name. In order to have a
strong name for your assembly, the code should be signed by a key pair. .Net provides
the utility, sn.exe, to generate a key pair.
sn -k KeyPairFileName.snk
Once the keyfile is generated, you can place the following line of code in
AssemblyInfo.cs
[assembly: AssemblyKeyFile (@"..\\..\\KeyPair.snk")]
The above line will make sure that the assembly is signed with a strong name.
Let us look at an example to understand how we can use StrongNameIdentity
Permission to identify the caller. Create a console application called
TestPermissionObjects.exe. The assembly is signed with a strong name as shown above.
We can demand the strong name identity permission in our component using the public
key counter part of the key pair generated.
public bool TestStrongNameIDentityPermission(string strongname,string version)
{
try
{
byte[] publickey = { 0, 36, 0, 0, 4, 128, 0, 0, 148, 0, 0, 0, 6, 2, 0, 0, 0, 36,
0, 0, 82, 83, 65, 49, 0, 4, 0, 0, 1, 0, 1, 0, 45, 25, 102, 36, 141, 31, 69, 83, 150, 31, 81, 170,
101, 131, 2, 136, 254, 120, 34, 55, 22, 245, 242, 91, 151, 25, 32, 206, 224, 156, 198, 240,
123, 2, 52, 230, 50, 196, 88, 84, 15, 86, 232, 53, 147, 140, 161, 64, 59, 200, 217, 15, 237,
100, 152, 230, 19, 148, 160, 187, 218, 36, 45, 168, 159, 92, 13, 58, 46, 43, 195, 106, 134,
98, 68, 226, 206, 166, 236, 88, 33, 160, 82, 254, 165, 38, 19, 22, 64, 28, 247, 127, 175,
225, 92, 214, 63, 102, 232, 124, 196, 242, 22, 144, 31, 64, 92, 248, 164, 148, 109, 130,
18, 103, 206, 177, 173, 104, 190, 221, 164, 102, 34, 150, 110, 25, 127, 189 } ;
// publickey can be extracted using utility secutil.
// secutil -strongname AssemblyNameWithFulpath
StrongNamePublicKeyBlob publickeyblob = new
StrongNamePublicKeyBlob (publickey);
System.Version ver = new Version(version);
StrongNameIdentityPermission sip = new
StrongNameIdentityPermission(publickeyblob,strongname,ver);
sip.Demand();
return true ;
}
catch(SecurityException se)
{
Console.WriteLine ("Identity Permission Failed -- Strong
Name " + strongname + "--version -- " + version );
return false;
}
}
Any client that is calling the above method of the component should have the
strong name, key pair and correct version otherwise the call to this method will throw a
Security Exception.
ZoneIdentitytPermission:
This permission object identifies the caller with the zone the call originates from.
Defined Security Zones are Internet, Intranet, MyComputer, NoZone, Trusted, and
Untrusted. Code can demand ZoneIdentityPermission to ensure that the caller is from the
specified zone. The following example explains how to use ZoneIdentityPermission
object to ensure that the callers are from the local computer, that is from the MyComputer
zone.
public string TestZoneIdentityPermission()
{
ZoneIDentityPermission zip = new
ZoneIdentityPermission(SecurityZone.MyComputer)
zip.Demand();
//The line below will succeed only if the calling code is from the zone MyComputer.
// If the same component is called from Intranet the call will fail.
StreamWriter sw = new StreamWriter("TestPermission.txt")
sw.write("Sucess ZoneIDentityPermisson");
sw.close();
}
PublisherIDentityPermission:
This permission object identifies the caller with the Digital signature. The caller
should be signed with a Digital Certificate.
URLIdentityPermission:
This permission object identifies the caller with the URL from which the call
originates.
SiteIdentityPermission:
This permission object identifies the caller with the website from which the call
originates.
Code Access Permission:
Identifying the caller using identity permission is not adequate to secure an
application. Various resources like file, environment, variables and message queues
should be protected along with the critical operations that the code performs. These
Security policies can be enforced using CodeAccessPermission. There are various
CodeAcessPermission objects available in .NET such as FileIOPermission,
EnvironmentPermission and EventlogPermission. For a list of CodeAcessPermission
refer to http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/
cpconidentitypermissions.asp
CodeAccessPermission object restricts access to the protected resources,
identifies if all the callers in the call stack have sufficient permission and provides
functionality to create the object from xml. These are basically the implementations of
the interfaces IstackWalk, Ipermission and IsecurityEncodable. IstackInterface
implementation in CodeAcessPermission objects ensures that all callers in the hierarchy
have enough permission to access the protected resources. IPermission interface has the
methods: Demand, Copy, Union, IsSubsetOf and Intersect. Every CodeAccessPermission
object has this interface implemented and the client can call the Demand() method of the
permission object to make certain that the requested permissions are available.
CodeAccessPermission can be implemented in two ways. One is Imperative
syntax and the other is Declarative syntax.
Imperative Syntax:
You can protect access to the resources and functions by creating an instance of
appropriate permission object and demanding the permission. Lets take a look at
FileIOPermissionObject and how we can confine access to files and folders.
public void TestFileIOPermission(string path , string filename)
{
try
{
FileIOPermission fip = new
FileIOPermission(FileIOPermissionAccess.Read , path);
fip.PermitOnly();
StreamWriter sr = new StreamWriter (path +"\\"+
filename);
sr.WriteLine ("FileIOPermision success");
sr.Close();
}
catch(SecurityException se)
{
Console.WriteLine (se.StackTrace);
}
Declarative Syntax:
You can control access to resources and functions by placing a Security Attribute
at Method, Class or Assembly Level. Security information defined using attributes are
placed in the metadata of the code. Each CodeAccessPermission has its corresponding
Security Attribute. Each Security Attribute takes at least SecurityAction as parameter.
SecurityAction enumeration can take values depending on where the security is enforced.
For example, RequestMinimum is supported only at Assembly level. Let us take a look at
the following example, which refuses access to winnt folder.
[assembly: FileIOPermissionAttribute(SecurityAction.RequestRefuse, "c:\\winnt")]
Imperative Syntax won’t allow the user to request permission as opposed to
Declarative syntax where you can request the permission.
PermissionSet:
PermissionSet is a set of Permissions .You can group the permission and add it to
PermissionSet. PermissionSet implements the same Ipermission Interface. Security calls
on a permission set will in turn make calls to permission object of the permission set. For
example making a call to perform Deny() on permission set will deny access to
permission objects that are members of the Permission set. The following example
explains the use of Permission set
public void TestPermissionSet()
{
PermissionSet ps = new PermissionSet(PermissionState.None) ;
ps.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read ,"c:\\C#") );
ps.AddPermission(new
EnvironmentPermission(EnvironmentPermissionAccess.Read ,"TEMP"));
ps.PermitOnly();
Console.WriteLine(Environment.GetEnvironmentVariable("TEMP"));
Console.WriteLine(Environment.GetEnvironmentVariable("PATH"));
}
The above function will throw a Security Exception while trying to access the
environment variable “PATH” which is not granted permission.
You can create your own NamedPermissionSet by grouping the desired
pemissions. These PermissionSets can then be added to codegroup, which will be
interpreted by CLR while computing the permission. (Codegroup -- Each code belongs to
a certain group based on the membership condition. Code that satisfy those conditions
become a member of the group. Each group has a permission set associated with it. .Net
CLR computes permission based on policy, group and associated permission set.). The
following link gives a detailed explanation about computing permission and codegroup
attributes.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/
cpconpermissions.asp
Let us take a look at a pre-defined permission set LocalIntranet, which has the
permission to execute an assembly, read the environment variables TEMP, TMP and
UserName (For other permissions of LocalIntranet please refer to security.config file
under CLR install directory. Or you may use c:\winnt\Microsoft.net\V*\Framework\bin\
msccorfg.msc). The following code, a sample WindowApplication will throw an
exception with the PermissionSet LocalIntranet.
private void TestLocalIntranetZonePermissionSet()
{
try
{
Console.WriteLine("Environment Variable UserName : " +
Environment.GetEnvironmentVariable("USERNAME"));
Console.WriteLine(Environment.GetEnvironmentVariable("PATH"));
}
catch(SecurityException se)
{
Console.WriteLine ("Permission Error -- can't read Environment Variable
PATH");
}
}
Change the permission set associated with zone MyComputer from FullTrust to
LocalIntranet.
caspol -chggroup 1.1 -zone MyComputer LocalIntranet
After performing this change, when you execute the exe file, you will get a
Security Exception.
You may reset the security back to default security policy using caspol -reset. You
can also change the permission set back to Fulltrust using
caspol -chggroup 1.1 -zone MyComputer FullTrust.
CustomPermission:
You can create your custom CodeAccessPermission by inheriting from
CodeAccessPermission class. This in turn has the implementations of Ipermission
Interface and IstackWalk . Since this derives from CodeAccessPermission , your custom
CAP object will also verify that all the callers in hierarchy have enough permission. Once
you have defined your custom CodeAccesPermission class, you can demand permissions
in your code in a manner similar to other CodeAccessPermission objects. Please refer to
the following link for detailed information on creating your own Code Access
Permission.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/
cpconpermissions.asp
Role Based Security:
Application should allow access to the critical information and critical operations
only to authorized users. Users are authorized based on the information provided by the
user. Access to the resources can be restricted to users of any group, only to specific
group or to all the users of a certain group. .Net defines Principal as the combination of
identity and group or role. Identity is nothing but a Windows User (WindowIdentity),
userid (GenericIdentity) or it can be custom (custom identity object that implements
Iidentity Interface). .Net also provides WindowsPrincipal and GenericPrincipal object .
You can define your own Principal object by implementing IPricipal Interface. Role
Based security is described in detail at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/
cpconnamedpermissionsets.asp
The source code along with this article has the implementations of IIdentity and
Iprincipal interfaces to define Custom Identity and Custom Principal.
Note: Custom CodeAccessPermission will check for permissions of all the callers in
hierarchy by default because of the implementation of IStackWalk interface. If you prefer
not to check the permissions of the callers in code stack, you may implement Ipermision
interface to define custom permission class.
Let us define a custom authentication scenario where we can use custom
permission, custom identity and custom principal objects.
a) Gather Evidence ie Userid and password from the user
b) Authenticate against the data store which can be a database, LDAP, etc
c) Create Identity Object using CustomIdentity class (please see the source code attached)
d) Create Principal Object using CustomPrincipal class
e) Set the Principal as Thread.CurrentPrincipal.
By Setting Principal as Thread.CurrentPrincipal, Principal is available at any time
by accessing Thread.CurrentPrincipal property.
Public void TestCustomPermission()
{
CustomIdentity ci = new CustomIdentity(“TestUser” ,”TestRole”);
CustomPrincipal cprincipal
CustomPermission cp
Cp.Demand();
}
The above code demands the identity to be TestUser and Role to be TestRole.
Let us take a look at Demand() method of CustomPermission Object.
public void Demand()
{
if ( ( Thread.CurrentPrincipal.Identity.Name == this.userid ) &
( Thread.CurrentPrincipal.IsInRole(this.role ) ) )
{
Console.WriteLine("CP...Sucess");
}
else
throw new SecurityException("Custom Permission
Denied");
Demand() method will fail if the provided credentials are not sufficient.
Note: Attached Sample Code has implementations of Ipermission and IIdentity and
Iprincipal.
Conclusion:
.Net Security has pre defined permission objects which are highly flexible to use and can
be extended to address custom permissions. Have fun in securing your application!