The Microsoft Windows .NET Framework has raised the bar for development in the era of smart client and XML Web services applications. The inclusion of a robust and object-oriented runtime engine, the Common Language Runtime (CLR), coupled with a comprehensive set of class libraries, allows developers to more easily create smart client applications that download and execute locally on a user’s PC but that are “no-touch” in terms of deployment, installation, and updates. Many of these concepts have recently been rolled into a sample application called TaskVision, available on the Windows Forms Web site.
This more connected way of developing software, while a boon for both developers and users, introduces a new set of security concerns because code is downloaded and executed on a variety of PCs. Fortunately, the CLR also includes a new way of handling security for Framework applications, known as code access security (CAS). In this article, I’ll walk you through the basics of CAS so you’ll be ready to use it as you develop smart client applications.
A new approach
In the Windows environment, the privileges associated with code executing on a PC generally derive directly from the identity of the user account under which the code is running. For example, if I am running a CRM application, and it attempts to write a file to a specific directory, the operating system will check to ensure that my user account token has permissions to the directory before allowing the application to complete the action. The same is true of other resources and rights on the machine, such as accessing the system registry, creating logged events, changing the system time, and shutting down the machine.
The problem with this system is that it is not very granular, since it doesn’t allow you to set up permissions based on any attributes of the code itself. If a hacker can persuade or trick you into downloading a piece of code that you execute under your highly privileged account, your operating system is essentially wide open for all sorts of mischief. This situation is what leads to the well-known dictum “run with least privilege” that everyone should certainly take to heart.
However, the .NET Framework goes a step further than simple identity-based security. The CAS mechanism enforced by the CLR allows code (actually assemblies, the unit of deployment and security in the .NET Framework) to be assigned different levels of trust based on attributes of the code, such as its digital certificate or originating site or URL. In this way, coherent policies can be constructed that allow only code originating from known and trusted sources to execute on a PC with the .NET Framework.
To get your mind around a new concept, it's always necessary to have a vocabulary for expression. CAS in the Framework uses a core set of terminology that you should get familiar with and that I’ll use to help explain how the system is set up and administered.
At the lowest level, permissions define the actions your code can perform. In the Framework, these permissions are defined as classes that derive from the System.Security.CodeAccessPermission class. There are 22 such derived classes in the v1.1 of the Framework that define rights such as reading and writing files (FileIOPermission), printing (PrintingPermission), and using reflection (ReflectionPermission). Developers can also create custom permissions through inheritance. Essentially, the CLR associates instances of these classes with your code implicitly at runtime if required to do so. Your code can also ask for specific permissions using these classes, as you’ll see in the next article.
To make the permissions easier to manage, the Framework ships with seven permission sets that aggregate various permissions. These permission sets are:
You can also create and manage custom permission sets using the Microsoft .NET Configuration tool installed in the Administrative Tools group. You can think of permission sets as fully defined levels of trust.
When the CLR is deciding whether to grant a particular permission to your code, it consults some of your code's properties. These properties, collectively referred to as evidence, are what make CAS so granular. Evidence can include (but is not limited to):
- The digital certificate of your assembly
- The application directory
- The hash value of the assembly
- The originating site or URL
- The zone (Internet, Local Intranet, Trusted Sites, etc.)
- The strong name of the assembly
Code groups define the intersection of evidence with permission sets. For example, the Framework automatically defines a Local_Intranet_Zone code group that grants the LocalIntranet permission set to any code originating in the Local Intranet zone. The LocalIntranet permission set then allows the code the right to use isolated storage, full UI access, some capability to do reflection, and limited access to environment variables.
At the highest level, the Framework defines four security policies in a hierarchy:
- Application domain
Each policy includes a set of code groups and permission sets. When the CLR is determining which permissions your code will have, it starts with highest level policy (enterprise) and works its way down, computing an allowed permission set. As each policy is consulted, new permissions are intersected with existing permissions. The result is that permissions are granted only if all the policy levels allow it, ensuring that lower level policies like user can't increase the permission level restricted at a higher level like machine. Default security policies are preconfigured by the Framework, but you can modify them using the Microsoft .NET Configuration tool.
As I just mentioned, the CLR constructs the allowed permission set based on the security policies in place. However, you can both declaratively (to be checked at load time) and imperatively (to be checked at runtime) notify the runtime that it will require a certain permission. This demand forces the runtime to do a stack-walk, whereby all callers (other assemblies) in the call stack are checked to determine whether they also have the permission. If so, the permission is granted; if not, an exception is thrown. In the next article, I’ll discuss how to demand permissions.
Sometimes, code you write may need to have a certain permission that its callers probably won't have. In these cases, your code can declaratively or imperatively assert the permission, thereby allowing your code to perform the action if it has been granted permission even though all its callers have not. This can be dangerous in certain situations, as I’ll discuss in the next article, and requires that your code be granted a special assert permission first.
A CAS scenario
Figure A walks through an example of how CAS is applied when some code is downloaded to a client PC from an intranet site.
A user clicks a hyperlink (1) on a Web page to download and execute (2) a managed application from an intranet Web server. As the assembly is loaded by the CLR on the host machine (3), the evidence is collected (4) and compared against the various code groups in the descending levels of security policy (5). Once the permissions have been granted, the application can begin to execute.
However, in this case, the application then attempts to read from the system registry (6). Since the registry permission was not among those added to the allowed permission set, a security exception is thrown (7) by the CLR, and the registry access is disallowed. Note that in this scenario, it doesn’t matter which user account the user is logged on under. The attributes of the code itself, its evidence, in conjunction with policies, determined its behavior.
Now you should be able to put your mind around the reason for CAS and better understand how it is implemented in the Framework. In the next article, I’ll look at configuring CAS in more detail, using CAS programmatically both declaratively and imperatively, and give you a few guidelines to help you as you develop great Framework applications.