|
|||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Contents
IntroductionOver the past years, I've learned many things from CodeProject ... and now I'm giving back to the CodeProject. Since I didn't find any articles on Code Access Security, here's my one. Enjoy! I'm not going to bore you with theory, but before we wet our feet, there are some concepts, keywords that you should learn. .NET has two kinds of security:
The Common Language Runtime (CLR) allows code to perform only those operations that the code has permission to perform. So CAS is the CLR's security system that enforces security policies by preventing unauthorized access to protected resources and operations. Using the Code Access Security, you can do the following:
We'll be discussing about these things through out this article. Before that, you should get familiar with the jargon. JargonCode access security consists of the following elements:
PermissionsPermissions represent access to a protected resource or the ability to perform a protected operation. The .NET Framework provides several permission classes, like Permission setsA permission set is a collection of permissions. You can put Code groupsCode group is a logical grouping of code that has a specified condition for membership. Code from http://www.somewebsite.com/ can belong to one code group, code containing a specific strong name can belong to another code group and code from a specific assembly can belong to another code group. There are built-in code groups like PolicySecurity policy is the configurable set of rules that the CLR follows when determining the permissions to grant to code. There are four policy levels -
Figure 1 Okay, enough with the theory, it's time to put the theory into practice. Quick ExampleLet's create a new Windows application. Add two buttons to the existing form. We are going to work with the file system, so add the using System.IO;
Figure 2 Write the following code: private void btnWrite_click(object sender, System.EventArgs e)
{
StreamWriter myFile = new StreamWriter("c:\\Security.txt");
myFile.WriteLine("Trust No One");
myFile.Close();
}
private void btnRead_click(object sender, System.EventArgs e)
{
StreamReader myFile = new StreamReader("c:\\Security.txt");
MessageBox.Show(myFile.ReadLine())
myFile.Close()
}
The version number should be intact all the time, for our example to work. Make sure that you set the version number to a fixed value, otherwise it will get incremented every time you compile the code. We're going to sign this assembly with a strong name which is used as evidence to identify our code. That's why you need to set the version number to a fixed value. [assembly: AssemblyVersion("1.0.0.0")]
That's it ... nothing fancy. This will write to a file named Security.txt in C: drive. Now run the code, it should create a file and write the line, everything should be fine ... unless of course you don't have a C: drive. Now what we are going to do is put our assembly into a code group and set some permissions. Don't delete the Security.txt file yet, we are going to need it later. Here we go. .NET Configuration ToolWe can do this in two ways, from the .NET Configuration Tool or from the command prompt using caspol.exe. First we'll do this using the .NET Configuration Tool. Go to Control Panel --> Administrative Tools --> Microsoft .NET Framework Configuration. You can also type "mscorcfg.msc" at the .NET command prompt. You can do cool things with this tool ... but right now we are only interested in setting code access security.
Figure 3 Creating a new permission setExpand the Runtime Security Policy node. You can see the security policy levels -
Figure 4 In the next screen, we can add permissions to our permission set. In the left panel, we can see all the permissions supported by the .NET Framework. Now get the properties of File IO permission. Set the File Path to C:\ and check Read only, don't check others. So we didn't give write permission, we only gave read permission. Please note that there is another option saying "Grant assemblies unrestricted access to the file system." If this is selected, anything can be done without any restrictions for that particular resource, in this case the file system.
Figure 5 Now we have to add two more permissions - Security and User Interface. Just add them and remember to set the "Grant assemblies unrestricted access". I'll explain these properties soon. Without the Security permission, we don't have the right to execute our code, and without the User Interface permission, we won't be able to show a UI. If you're done adding these three permissions, you can see there is a new permission set created, named Creating a new code groupNow we will create a code group and set some conditions, so our assembly will be a member of that code group. Notice that in the code groups node, All_Code is the parent node. Right Click the All_Code node and choose New. You'll be presented with the Create Code Group wizard. I'm going to name it
Figure 6 In the next screen, you have to provide a condition type for the code group. Now these are the evidence that I mentioned earlier. For this example, we are going to use the Strong Name condition type. First, sign your assembly with a strong name and build the project. Now press the Import button and select your assembly. Public Key, Name and Version will be extracted from the assembly, so we don't have to worry about them. Now move on to the next screen. We have to specify a permission set for our code group. Since we have already created one -
Figure 7 Exclusive and LevelFinalIf you haven't messed around with the default .NET configuration security settings, your assembly already belongs to another built-in code group -
Figure 8 I know we have set lots of properties, but it'll all make sense at the end (hopefully). Okay .. it's time to run the code. What we have done so far is, we have put our code into a code group and given permissions only to read from C: drive. Run the code and try both buttons. Read should work fine, but when you press Write, an exception will be thrown because we didn't set permission to write to C: drive. Below is the error message that you get.
Figure 9 So thanks to Code Access Security, this kind of restriction to a resource is possible. There's a whole lot more that you can do with Code Access Security, which we're going to discuss in the rest of this article. Functions of Code Access SecurityAccording to the documentation, Code Access Security performs the following functions: (straight from the documentation)
We have already done the top two, and that is the administrative part. There's a separate namespace that we haven't looked at yet - Security NamespaceThese are the main classes in
These are the main classes in
You can find more permission classes in other namespaces. For example, Next, we'll see how we can use these classes. Declarative vs. ImperativeYou can use two different kinds of syntax when coding, declarative and imperative. Declarative syntaxDeclarative syntax uses attributes to mark the method, class or the assembly with the necessary security information. So when compiled, these are placed in the metadata section of the assembly. [FileIOPermission(SecurityAction.Demand, Unrestricted=true)]
public calss MyClass
{
public MyClass() {...} // all these methods
public void MyMethod_A() {...} // demands unrestricted access to
public void MyMethod_B() {...} // the file system
}
Imperative syntaxImperative syntax uses runtime method calls to create new instances of security classes. public calss MyClass
{
public MyClass() { }
public void Method_A()
{
// Do Something
FileIOPermission myPerm =
new FileIOPermission(PermissionState.Unrestricted);
myPerm.Demand();
// rest of the code won't get executed if this failed
// Do Something
}
// No demands
public void Method_B()
{
// Do Something
}
}
The main difference between these two is, declarative calls are evaluated at compile time while imperative calls are evaluated at runtime. Please note that compile time means during JIT compilation (IL to native). There are several actions that can be taken against permissions.
First, let's see how we can use the declarative syntax. Take the
[UIPermission(SecurityAction.Demand,
Clipboard=UIPermissionClipboard.AllClipboard)]
or with both [UIPermission(SecurityAction.Demand,
Clipboard=UIPermissionClipboard.AllClipboard,
Window=UIPermissionWindow.AllWindows)]
If you want to declare a permission with unrestricted access, you can do it as the following: [UIPermission(SecurityAction.Demand, Unrestricted=true)]
When using imperative syntax, you can use the constructor to pass the values and later call the appropriate action. We'll take the RegistryPermission myRegPerm =
new RegistryPermission(RegistryPermissionAccess.AllAccess,
"HKEY_LOCAL_MACHINE\\Software");
myRegPerm.Demand();
If you want unrestricted access to the resource, you can use RegistryPermission myRegPerm = new
RegistryPermission(PermissionState.Unrestricted);
myRegPerm.Demand();
This is all you need to know to use any permission class in the .NET Framework. Now, we'll discuss about the actions in detail. Security DemandsDemands are used to ensure that every caller who calls your code (directly or indirectly) has been granted the demanded permission. This is accomplished by performing a stack walk. What .. a cat walk? No, that's what your girl friend does. I mean a stack walk. When demanded for a permission, the runtime's security system walks the call stack, comparing the granted permissions of each caller to the permission being demanded. If any caller in the call stack is found without the demanded permission then a Figure 10 Different assemblies as well as different methods in the same assembly are checked by the stack walk. Now back to demands. These are the three types of demands.
DemandTry this sample coding. We didn't use security namespaces before, but we are going to use them now. using System.Security;
using System.Security.Permissions;
Add another button to the existing form. private void btnFileRead_Click(object sender, System.EventArgs e)
{
try
{
InitUI(1);
}
catch (SecurityException err)
{
MessageBox.Show(err.Message,"Security Error");
}
catch (Exception err)
{
MessageBox.Show(err.Message,"Error");
}
}
// Access is denied for this function to read from C: drive
// Note: Using declrative syntax
[FileIOPermission(SecurityAction.Deny,Read="C:\\")]
private void InitUI(int uino)
{
// Do some initializations
ShowUI(uino); // call ShowUI
}
private void ShowUI(int uino)
{
switch (uino)
{
case 1: // That's our FileRead UI
ShowFileReadUI();
break;
case 2:
// Show someother UI
break;
}
}
private void ShowFileReadUI()
{
MessageBox.Show("Before calling demand");
FileIOPermission myPerm = new
FileIOPermission(FileIOPermissionAccess.Read, "C:\\");
myPerm.Demand();
// All callers must have read permission to C: drive
// Note: Using imperative syntax
// code to show UI
MessageBox.Show("Showing FileRead UI");
// This is excuted if only the Demand is successful.
}
I know that this is a silly example, but it's enough to do the job. Now run the code. You should get the "Before calling demand" message, and right after that the custom error message - "Security Error". What went wrong? Look at the following figure: Figure 11 We have denied read permission for the Note that according to the documentation, most classes in .NET Framework already have demands associated with them. For example, take the Link DemandA link demand only checks the immediate caller (direct caller) of your code. That means it doesn't perform a stack walk. Linking occurs when your code is bound to a type reference, including function pointer references and method calls. A link demand can only be applied declaratively. [FileIOPermission(SecurityAction.LinkDemand,Read="C:\\")]
private void MyMethod()
{
// Do Something
}
Inheritance DemandInheritance demands can be applied to classes or methods. If it is applied to a class, then all the classes that derive from this class must have the specified permission. [SecurityPermission(SecurityAction.InheritanceDemand)]
private class MyClass()
{
// what ever
}
If it is applied to a method, then all the classes that derive from this class must have the specified permission to override that method. private class MyClass()
{
public class MyClass() {}
[SecurityPermission(SecurityAction.InheritanceDemand)]
public virtual void MyMethod()
{
// Do something
}
}
Like link demands, inheritance demands are also applied using declarative syntax only. Requesting PermissionsImagine a situation like this. You have given a nice form to the user with 20+ fields to enter and at the end, all the information would be saved to a text file. The user fills all the necessary fields and when he tries to save, he'll get this nice message saying it doesn't have the necessary permission to create a text file! Of course you can try to calm him down explaining all this happened because of a thing called stack walk .. caused by a demand .. and if you are really lucky you can even get away by blaming Microsoft (believe me ... sometimes it works!). Wouldn't it be easier if you can request the permissions prior to loading the assembly? Yes you can. There are three ways to do that in Code Access Security.
Note that these can only be applied using declarative syntax in the assembly level, and not to methods or classes. The best thing in requesting permissions is that the administrator can view the requested permissions after the assembly has been deployed, using the permview.exe (Permission View Tool), so what ever the permissions needed can be granted. RequestMinimumYou can use using System;
using System.Windows.Forms;
using System.IO;
using System.Security;
using System.Security.Permissions;
// placed in assembly level
// using declarative syntax
[assembly:RegistryPermission(SecurityAction.RequestMinimum,
Write="HKEY_LOCAL_MACHINE\\Software")]
namespace SecurityApp
{
// Rest of the implementation
}
RequestOptionalUsing If you use [assembly:FileIOPermission(SecurityAction.RequestMinimum, Read="C:\\")]
[assembly:FileIOPermission(SecurityAction.RequestOptional, Write="C:\\")]
The only permissions that this assembly will have are read and write permissions to the file system. What if it needs to show a UI? Then the assembly still gets loaded but an exception will be thrown when the line that shows the UI is executing, because even though the security policy allows Note that, like RequestRefuseYou can use [assembly:FileIOPermission(SecurityAction.RequestRefuse, Write="C:\\")]
Overriding SecuritySometimes you need to override certain security checks. You can do this by altering the behavior of a permission stack walk using these three methods. They are referred to as stack walk modifiers.
AssertYou can call the private void ShowUI(int uino)
{
// using imperative syntax to create a instance of FileIOPermission
FileIOPermission myPerm = new
FileIOPermission(FileIOPermissionAccess.Read, "C:\\");
myPerm.Assert(); // don't check above stack frames.
switch (uino)
{
case 1: // That's our FileRead UI
ShowFileReadUI();
break;
case 2:
// Show someother UI
break;
}
CodeAccessPermission.RevertAssert(); // cancel assert
}
Make sure that the Figure 12 Even though Note that to use Warning from Microsoft!: If asserts are not handled carefully it may lead into luring attacks where malicious code can call our code through trusted code. DenyWe have used this method already in the previous example. The following code sample shows how to deny permission to connect to a restricted website using imperative syntax: WebPermission myWebPermission =
new WebPermission(NetworkAccess.Connect,
"http://www.somewebsite.com");
myWebPermission.Deny();
// Do some work
CodeAccessPermission.RevertDeny(); // cancel Deny
PermitOnlyYou can use WebPermission myWebPermission =
new WebPermission(NetworkAccess.Connect,
"http://www.somewebsite.com");
myWebPermission.PermitOnly();
// Do some work
CodeAccessPermission.PermitOnly(); // cancel PermitOnly
You can use Calculating PermissionsIn the first example, we configured the machine policy level to set permissions for our code. Now we'll see how those permissions are calculated and granted by the runtime when your code belongs to more than one code group in the same policy level or in different policy levels. The CLR computes the allowed permission set for an assembly in the following way:
So all the permissions associated with matching code groups in one policy level are added together (union) and the result for each policy level is intersected with one another. An intersection is used to ensure that policy lower down in the hierarchy cannot add permissions that were not granted by a higher level. Look at the following figure taken from a MSDN article, to get a better understanding:
Figure 13 Have a quick look at the All_Code code group's associated permission set in
Figure 14 The runtime computes the allowed permission set differently if the Here's what happens if these attributes are set.
Now you should have a clear understanding why we set the Nice Features in .NET Configuration ToolThere are some nice features in .NET Configuration Tool. Just right click the Runtime Security Policy node and you'll see what I'm talking about. Figure 15 Among other options there are two important ones.
ToolsPermissions View Tool - permview.exeThe Permissions View tool is used to view the minimal, optional, and refused permission sets requested by an assembly. Optionally, you can use permview.exe to view all declarative security used by an assembly. Please refer to the MSDN documentation for additional information. Examples:
Code Access Security Policy Tool - caspol.exeThe Code Access Security Policy tool enables users and administrators to modify security policy for the machine policy level, the user policy level and the enterprise policy level. Please refer to the MSDN documentation for additional information. Examples: Here's the output when you run "caspol -listgroups", this will list the code groups that belong to the default policy level - Machine level. Figure 16 Note that label "1." is for All_Code node because it is the parent node. It's child nodes are labeled as "1.x", and their child nodes are labeled as "1.x.x", get the picture?
Summary
That's it. Take a look at the Functions of Code Access Security again. You should have a clear understanding by now than the first time you saw it. There are things like creating custom code access permissions, and best practices when using Code Access Security, which I haven't discussed in this article. If you want more information, you may refer to the following:
I suggest that you go through the whole article one more time, just to make sure you didn't miss anything. If it's still not clear, don't worry, it's not your fault, it's my fault. :) Happy Coding !!! History
| ||||||||||||||||||||||||||||||||||||||||||||||||||