One of the great benefits of .NET is that you can easily download and execute code on multiple workstations to enable the creation of smart client applications. Although this convenient feature can make your life easier, it can also increase security problems. However, the .NET Framework offers a solution in code access security (CAS). CAS enables the .NET Framework to go beyond simple identity-based security and allows administrators to configure different levels of trust based on attributes of the code referred to as evidence. These attributes can include digital certificates, the application directory, the hash value of the assembly, the originating site or URL, the zone (Internet, Local Intranet, Trusted Sites, etc.), and the strong name of the assembly. The breadth of this evidence makes CAS very granular. For a more detailed explanation of CAS’s general makeup, check out my earlier article. Here, we'll focus on how managed code can interact with CAS and how administrators can configure CAS.
CAS in your code
When the common language runtime (CLR) determines which permissions your code will have, it computes an allowed permission set by consulting the four policy levels (enterprise, machine, user, and the optional application domain configured by the hosting application). At each level, new permissions are intersected with existing permissions. As a result, permissions are granted only if all the policy levels allow it.
Developers can also place both declarative and imperative security requests in their code to notify the CLR that it will require specific permissions. Although specifically requesting permissions is not required, not doing so means that your code must be ready to handle exceptions that result from insufficient permissions. In addition, requesting specific permissions helps ensure that your code is used only for the purposes it was intended and that it cannot be used maliciously. Finally, requesting only the minimum permissions your code needs to run increases the probability that the code will be allowed to run.
Keep in mind that the classes of the .NET Framework are secure and make permission requests when called from your code. So even if your code does not request permissions explicitly, if it calls, for example, the classes in the System.IO namespace to write to the file system, the FileStream class requests the appropriate permissions. Therefore, it is unnecessary and slows performance to request the same permission in your code as the .NET Framework class you are calling. See the .NET Framework documentation for what permissions are requested by each method.
If your code does not need to access protected resources (the file system, the registry, etc.), you needn’t worry about requesting permissions. Requesting permissions also adds a bit of complexity to your code, so it's best used when the code is deployed across an intranet or the Internet and will perform functions or access resources that may be restricted.
Using declarative security
Declarative security uses the attributes mentioned in my previous article to embed security information at the assembly, class, or member (method) level in managed code. These attributes indicate both the permission requested (demanded) and how it will interact with the code.
The constructor of each attribute accepts one of the nine members of the SecurityAction enumeration that indicates how the permission will be used. For example, the declaration of the following method uses the PrintingPermission attribute to ensure that the method is executed only if the assembly has the capability to print from a restricted dialog box using the Demand member:
<PrintingPermission(SecurityAction.Demand, Level:= PrintingPermissionLevel.SafePrinting)> _
Public Sub PrintResults()
' Print results here
In this case, if the calculated permissions do not include the capability to print, a SecurityException is thrown when this method is executed. Asking for needed permissions ahead of time allows exceptions to be caught early (before the method code begins executing) if the assembly has not been granted the appropriate permission. To catch the exception even earlier, the demand can be placed at the class level instead.
In addition, when a permission is demanded as shown above, the CLR walks the call stack to make sure that all code (actually each assembly) executed leading up to loading the assembly also has the required permission. If not, the code is not loaded and an exception is thrown. Although this is expensive in terms of performance, it is required to ensure that code without permissions cannot sneak in the back door by executing code that has a larger allowed permission set, often referred to as a luring attack.
The previous snippet used the Demand member of SecurityAction to request a permission, but your code can make two other types of security demands as well: link demands and inheritance demands. Simply put, a link demand is made with the LinkDemand member of SecurityAction. It is checked during just-in-time compilation, and it checks only the immediate caller rather than performing a stack walk. Although this is good for performance, it can open your code to luring attacks and should be used only if you can ensure that your code cannot be used maliciously.
An inheritance demand is likewise specified with the InheritanceDemand member, is placed at the class level, and is checked at load time to ensure that only code with the specified permission can inherit from the class.
An assembly can request minimum and optional permissions in addition to refusing permissions. For example, the following declaration requests the minimum file I/O permissions needed to write to the file myfile.txt.
<Assembly: FileIOPermissionAttribute(SecurityAction.RequestMinimum, Write:= "c:\myfile.txt")>
Requesting only the minimum permissions required helps ensure that your code will actually be able to run. This is the case because the FileStream class also makes a demand for permissions that may not be granted by the policy and that you can therefore restrict.
In addition to requesting specific permissions for the file system, registry, or message queue, you can request permissions defined in one of the predefined permissions sets, such as Nothing, Execution, and FullTrust. This is accomplished by using the PermissionSet attribute and setting its Name property like so:
<Assembly: PermissionSetAttribute(SecurityAction.RequestMinimum, Name:= "FullTrust")>
It is important to remember that demanding permission does not mean that the code demanding it will be granted the permission. Demanding the permission is simply a means of telling the CLR that the assembly, class, or method needs the permission to function correctly. If the permission is not in the calculated set or is not granted to all callers in the call stack, an exception is thrown.
You can, however, bypass the walk of the call stack by asserting the permission. To assert a permission, you use the Assert member of SecurityAction in the attribute. In that case, the permission that is asserted will be added to the set without checking all the calling assemblies. Obviously, this can lead to security holes, but it can be useful in situations where your assembly performs some action on a resource that its callers do not know about and would not normally perform. For example, you could create a class that must access a particular registry key for configuration information but does not present this fact to the user. Your code could then assert the permission using the RegistryPermission attribute so that its caller would not require the permission.
Of course, the biggest risk in asserting permissions is introduced if your code allows the caller to specify the registry key to manipulate, as in the previous example, and your code then asserts the permission to access it.
To be able to assert permissions, your code requires the SecurityPermission attribute with the SecurityPermissionFlag set to Assertion, which is included by default in the FullTrust, LocalIntranet, and Everything permission sets.
Finally, .NET supports five additional permissions that map to the evidence used to determine membership in code access groups. For instance, the following code could be used to ensure that the assembly is loaded only from the Atomic Web site:
<Assembly: UrlIdentityPermissionAttribute(SecurityAction.RequestMinimum, _
Collectively, these permissions are referred to as identity permissions and also include PublisherIdentityPermission, SiteIdentityPermission, StrongNameIdentityPermission, and ZoneIdentityPermission.
Using imperative security
While declarative security is specified through attributes, imperative security is handled through a set of classes derived from CodeAccessPermission. The difference is that a permission class is invoked at runtime from within a method, so it can dynamically demand and assert permissions. For example, rather than use the FileIOPermission attribute to request read access to a static directory, the FileIOPermission class can be instantiated and configured with the appropriate path, as shown below, where dataFile is a file path calculated from user input or read from persistent storage, such as the registry or a database:
Dim oIO As New FileIOPermission(FileIOPermissionAccess.Read, dataFile)
To request the permission, the appropriate method (Demand, Assert, DemandImmediate, RevertAssert, and Deny, among others) must be used. As a result, the code can demand the permission by calling the Demand method of the oIO object to initiate a stack walk:
Catch e As SecurityException
' Catch the error here
Using imperative security requires more error handling, but it's more flexible because it can be used when the resource your code needs to access is known only at runtime and when permissions are used only for a short time. For example, using imperative security code in a method can assert a permission to write to the registry and then call the RevertAssert method when finished to ensure that callers cannot use the permission to write malicious data to the registry.
As I mentioned in my previous article, an administrator can configure a security policy on a machine running the .NET Framework by using the Microsoft .NET Configuration Tool installed in the Administrative Tools group. To configure a policy on a group of machines, an administrator can create a security deployment package using the tool. The resulting Windows Installer package can then be deployed across a network by making the .msi file available on an intranet site or by using a Group Policy. For more information about configuring and deploying a policy, see the .NET Framework Enterprise Security Policy Administration and Deployment FAQ.
Levels of trust
CAS provides granularity to security for Framework applications by introducing the concept of levels of trust wedded to specific permissions. Using CAS in your smart client applications can make your code more secure and allow it to execute with the appropriate permissions.