Description: This document is a tutorial in a series of
tutorials for programmers learning about the .NET Framework development
environment. What you will learn is how
the security features of the .NET Framework behave, and how you can take
advantage of them in your software.
Requirements: You should be familiar with at least one programming language, such as C++, Pascal, PERL, Java or Visual Basic. This tutorial assumes familiarity with the Common Language Runtime and the .NET Framework in general. Introductory tutorials on both of these topics have already been released in this tutorial series. To do the exercises and run the examples you need a PC running Windows with the .NET Framework SDK installed.
1. Security
and the .NET Framework
1.2. Security
Features of the Host OS
1.3. Security
Goals of the .NET Framework
2. Introductory
Code Access Security
3. Practical
Security Programming
4. Advanced
Code Access Security
4.1. Policy
and administration
4.6. Defining
Secured Components
Figure
2‑3 Partial List of Standard
Permissions
Figure
3‑1 GracefulDisplayFile.cs
Figure
4‑1 Stack Modifications
Exercise
2‑1 Testing DisplayFile.cs
Exercise
2‑2 Testing CAS with
DisplayFile.cs Part 1
Exercise
2‑3 Testing CAS with
DisplayFile.cs Part 2
Exercise
2‑4 Testing DisplayFile.cs using
Zoner.cs
Exercise 3‑1 Zone testing Pad.cs
Computer security is technology designed to protect resources. The resources being protected vary widely, as does the technology and nature of the protection. In fact, computer security is such a diverse field that it is often inappropriate to use a term as general as security to discuss the topic.
To provide some perspective, lets take a short look at the types of resources that are traditionally protected via computer security.
· The file system
· System memory
· Communication data
· Identification data
· Bandwidth (CPU, network, etc.)
· Screen and GUI data
This list is by no stretch of the mind complete, but it does show some otherwise unrelated resources in a system that require protection. Security technologies themselves also come in a variety of forms.
· Authentication and user management
· Authorization and access control
· Encryption and key management
· Address space and process boundaries
· User Logon-session boundaries
· Key exchange and communication protocols
Many books have been written on each of the preceding topics individually, and still each of these topics can be subdivided further.
When designing the .NET Framework, Microsoft had to decide how the platform was going to address security. Was the .NET Framework going to be a comprehensive secure solution, including user databases, logons, authentication, and the works? Or was the .NET Framework going to rely on underlying technologies, such as services provided by the host operating system? The answer is that the .NET Framework does both.
The .NET Framework itself actually implements only a single comprehensive form of security known as Code Access Security. Code access security (or CAS) is a security subsystem built into the .NET Framework that is both reliant on other features of the framework (such as JIT compilation and garbage collection), as well as relied upon by the rest of the platform. CAS is a part of the .NET Framework that affects almost every other part of the .NET Framework, and is largely the focus of this tutorial.
However, before jumping into the details of Code Access Security, it can be helpful to touch upon the parts of security that the .NET Framework borrows from its host OS. Lets take, for example, encryption.
General encryption is not a feature implemented by the .NET Framework per-se. However your managed code has access to rich encryption ability. This is because the .NET Framework currently runs on Windows, and Windows Crypto-API is a very powerful encryption engine. Rather than rewrite these encryption providers, the .NET Framework Class Library (or FCL) provides an extensive set of types (found in the System.Security.Cryptography namespace) that wrap the API of the host OS.
If the .NET Framework were to run on a less capable OS, however, it would be entirely possible to move the encryption implementation into the FCL itself. This, in fact, is likely to happen as the .NET Framework platform moves to lighter weight devices such as PDAs. In this way, the .NET Framework does not provide a security service, but rather a wrapper of one that may at one time become a full-fledged implementation.
In contrast, there are some security features that the .NET Framework is not going to attempt to provide, unless it is provided by the host OS. In these cases, the overall security story will differ depending on the host OS. An example of this is user authentication and account management. The .NET Framework does not provide these features, nor are there any plans for a future implementation. This simply is not part of the goals of the .NET Framework.
What this means is that if the .NET Framework is installed on a system that implements user authentication and account management, then the code that runs on the .NET Framework will be subject to the user-based security of the host. However, if the .NET Framework is installed on an OS with no user-based security, then managed code in that environment simply wont have or be subject to the missing feature.
In more concrete terms, when managed code is running on Windows 2000 or Windows XP, it benefits from the rich access control of the file system, protected process address space, user authentication, etc. However, when managed code runs on Windows 98 or Windows ME, these security features do not exist, or exist in a much more limited fashion.
Knowing the goals of the .NET Framework will help you to grasp the resulting technologies. Here are some goals.
· Perform authentication of code (rather than user authentication).
· Potentially limit access to system resources through authorization based on code-based authentication.
· Reduce the granularity of security to units of executing code smaller than a single process or application.
· Provide a security mechanism to ensure a users ability to safely execute code that originated from a wide variety of sources (including the Internet).
These goals are pretty specific, but there are actually a
couple of important code scenarios that they promote. These are the
In the mobile code scenario, code exists on some remote location such as an Internet server, or a share in an enterprise. The user initiates the downloading and local execution of this code. This can be in the form of a control in a browser, or as a free-standing executable downloaded from the network and run locally. Either way, mobile code is code that meets two criteria: it executes locally and it was potentially developed by someone that you do not know or trust.
The idea behind mobile code is that great majority of developers that you do not know or trust have no ill-intent towards you or your system, and develop usable or entertaining software. Meanwhile, if that software runs locally on your system, you as the user gain the advantage of performance and rich features over a server-side implementation.
The mobile code scenario is important. It is a goal of the .NET Framework to alleviate the potential problems with mobile code such as worms, Trojans and viruses.
Lets move on to the ISP scenario. ISP stands for Internet Service Provider. Most ISPs provide some means of web-hosting for their clients. This can range from a few megabytes of disk space for static content to full fledged hosted machines or networks of machines.
The problem with the ISP scenario really lies with the smaller hosts. These are the small business or personal websites that are likely to share a single machine or group of machines with other clients of the ISP. In these cases, there are some real security problems associated with executing code to produce dynamic web-pages. This includes servlets, ISAPI DLLs, CGI scripts and executables, ASP and a host of other technologies.
The problem is that running code on the server side is a security risk. One client may purposefully or inadvertently cause harm to another clients data or page. In fact, it is feasible that a single client could bring down the entire server machine.
Currently the solution to this problem is to keep server-side technologies limited to scripts or interpreted code. This causes performance to be bad, and often the technologies must also have severely limited access to the API of the system. Many ISPs simply deny their clients the ability to host anything other than a static web-site unless they pay the price for a dedicated machine.
It is a goal of the .NET Framework to allow an ISP to run server-side code in the native machine language of the system. Meanwhile the code runs in the same process with other server-side code, but the .NET Framework provides sufficient isolation to keep all clients and the system itself safe.
We have talked about the technical goals of the .NET Framework, and we have discussed a couple of scenarios that the .NET Framework seeks to enable. This should give you sufficient background to begin looking at Code Access Security.
The basic operation of CAS is actually pretty simple.
· When the Common Language Runtime (CLR) loads code, the code is authenticated. What this means is that the CLR uses knowledge, known as evidence, that it has about the code (such as its origin) to establish a group that the code belongs to.
· Once a code group is established, permissions are assigned to the code, and then the code is allowed to execute.
· While executing, the codes permissions are checked at appropriate times to restrict the actions that the code can take. If the code attempts a restricted action, a SecurityException is thrown.
Lets look at a concrete example.
using System;
using System.IO;
using System.Security;
class App{
public static void
StreamReader reader = new StreamReader(args[0]);
Console.WriteLine(reader.ReadToEnd());
}
}
Figure 2‑1 DisplayFile.cs
The code in Figure 2‑1 is a very simple application that will display any text file to the console window. In fact, does not directly address security in any way. However, the code does access a potentially protected resource, and that is the file that it reads before it writes the contents to the console window.
This file access is actually performed by the implementation of the StreamReader class in the example. Depending on the results of authentication by the CLR, this code will either execute or throw a SecurityException when it attempts to access the general file system.
Note: The code shown in Figure 2‑1 could execute inside of a try-catch(SecurityException) block. This would allow the code to gracefully recover from its inability to read the file. In fact, this kind of exception handling is a key factor in addressing security with your managed code.
If you are interested in seeing CAS in action, compile the code in Figure 2‑1 into an executable. Run the executable from your local file system and display a text file on your local file system. All should work fine. Now copy your executable to a network share. Execute the file from the share, but display the same file from your local file system. You should see the CLR halt execution of DisplayFile.exe by throwing a SecurityException.
Exercise 2‑1 Testing DisplayFile.cs
Exercise 2‑2 Testing CAS with DisplayFile.cs Part 1
Exercise 2‑3 Testing CAS with DisplayFile.cs Part 2
So far in our discussion of code access security, the definition for the term code has been unclear. It is important to define this term literally as well as in our minds. Code, when referring to security on the .NET Framework, refers to a single assembly. (If you are not familiar with the concept of assemblies in the .NET Framework, see the tutorial in this series titles Introducing the Common Language Runtime).
All of the code in a single assembly will share the same security identity, as well as the same permissions at runtime. For example, the code in Figure 2‑1 is a very simple executable file. When built, all of the code in the example exists in a single managed assembly. So all of the code in Figure 2‑1 will be subject to the same permissions and will have the same security identity.
It is important to recognize the assembly as a unit of security, because most managed applications execute code from many assemblies. In fact, even the DisplayFile.cs example makes use of more than one assembly, because it uses types defined by the FCL. The FCL is published as a collection of assemblies.
Since CAS deals with code in terms of assemblies, it is possible for code running in the same application to have different security permissions. These permissions would differ based on the assemblies that the code lives in, as well as where the assembly was loaded from, etc.
Note: The FCL is the class
library that ships with the .NET Framework.
The assemblies in the FCL are granted very high privileges in terms of
CAS. In fact, the most important
permission that the FCL has been granted is the ability to interoperate with
the operating system by calling into unmanaged
code that makes up the system API. Other
code, such as an assembly downloaded from the Internet, would not be granted
high permissions, and could only call into system API through a more trusted
assembly such as an FCL assembly.
The cornerstone of code access security is a process called a demand. Trusted code (and the CLR itself) performs a demand before it access a protected resource on behalf of calling code.
The demanding code is demanding a permission or set of
permissions of the calling code. In
fact, by default when a demand is performed, every caller up the call-stack all
the way to
For example, suppose that Code A uses a component in Code B that uses a FileStream object (implemented in Code C) that accesses the file system. The implementation of the FileStream object would demand permission to access the file system, before doing the actual work of accessing a file. The CLR will perform the demand by checking all callers in the call-stack for the permission before returning control to the code in the FileStream object.

Figure 2‑2 Demand
If all of the code in the call chain has the permission then code continues to execute past the demand. Typically, after a demand, some access to a protected resource is made. However, if any of the calling code does not have the demanded permission, then a SecurityException is thrown from the point of demand.
This has the effect of halting the code at that point, and causing the protected access to not be performed. Code earlier in the chain is free to catch the SecurityException, but regardless CAS has done its job by not allowing the code that accesses the resource to execute.
This is really the basic infrastructure upon which the rest of CAS is built.
Note: A demand would not
be possible (or at least it would not be enforceable) if managed code were not
JIT compiled, and direct access to application memory restricted. This begins to give you an idea of how all of
the infrastructure that the CLR provides works together to create a platform.
Permissions are actually class objects that can describe a logical ability. Permission objects are demanded, and then it is the permission itself that compares itself with the permissions held by the assemblies in the call chain. There are a number of standard permissions defined by the FCL, and these are the ones with which you will be most commonly concerned.
For example, the FileIOPermission is the permission that the FileStream class demands before it performs file access. The UIPermission is the permission class that is demanded by the Form class before it allows code to display a form.
Component library developers typically decide which of the types that they are defining represent resources that must be securable. Then, they define permissions to match the secured portions of their class library. Finally, it is the responsibility of the classes themselves to demand the appropriate permissions before performing an action. Many of the types in the FCL have permissions associated with them that effect whether their functionality is available to code at runtime.
|
Permission |
Description |
|
FileIOPermission |
Permission to access the file system. This permission object is very granular and
can refer to a single file, a directory tree, or an entire drive. |
|
FileDialogPermission |
Permission to use the common file dialog boxes to initiate
file IO from the user. |
|
IsolatedStoragePermission |
Permission to use an isolated portion of the local file
system. Isolated storage is a
convenient feature for mobile code that does not have general file system
access. |
|
UIPermission |
Permission to present GUI to the user. The UIPermission is granular and can be
granted to varying degrees. |
|
PrintingPermission |
Permission to print to a local printer. The PrintingPermission can be granted to
varying degrees. |
|
WebPermission |
Permission to access the web via protocols such as
HTTP. The WebPermission is very
granular. Code can be granted access
to the web in general, or it can be granted on a per-URL/protocol basis. |
|
SocketPermission |
Permission to use sockets for communication. |
|
SecurityPermission |
The SecurityPermission is a meta permission that
encapsulates the ability to perform a number of security related tasks. These include calling into unmanaged code,
and performing security asserts. |
Figure 2‑3 Partial List of Standard Permissions
The preceding figure shows a partial list of permission
objects defined by the FCL. This table
should give you an idea of some permissions that exist, and the kinds of
resources that they represent.
When the CLR loads an assembly and prepares to execute the code in the assembly, it must first authenticate the code. In the process of authenticating code, the CLR gathers evidence about the assembly. Evidence is a general term for information about the assembly, and I will discuss evidence in general, later in this document.
An important piece of evidence that the CLR gathers for an assembly is the zone from which the assembly originated. For example, code launched from your local file system is in the MyComputer zone, while code downloaded from the Internet is in the Internet zone. This establishment of evidence (and particularly zone-evidence), allows the CAS system to authenticate the code and assign it to an appropriate code-group. The zone effects the permissions that are granted to the assembly.
Zones are not the only kind of evidence supported by CAS. But for application developers, zones can have a profound effect on code. For example, DisplayFile example in Figure 2‑1 works just fine, or throws an exception depending on the zone that it is authenticated to by the CLR.
You should be familiar with the standard zones, because they will effect whether or not your code works when run from the Internet or an network share in your enterprise.
|
Zone |
Description |
|
Local |
Code executed from the local system. Code in this zone has full trust. |
|
Intranet |
Code executed from a share or URL on the enterprise
network. Limited access to local
resources. |
|
Internet |
Code downloaded from the Internet. Minimal access to local resources. |
|
Restricted |
Code in the restricted zone is not allowed to execute. |
Figure 2‑4 Zones
The code in the following figure is a utility called Zoner.exe that allows you to execute another managed application, arbitrarily selecting the security zone that the new assembly runs in.
using System;
using System.Reflection;
using System.Security;
using System.Security.Policy;
class App{
public static void
try{
SecurityZone zone = SecurityZone.Internet;
String url = "http://www.IBuySpy.com";
String exe;
String[] newArgs = null;
for(Int32 index = 0;; index++){
if(args[index][0] != '-' && args[index][0] != '/'){
exe = args[index];
newArgs = new String[args.Length-(index+1)];
Array.Copy(args, index+1, newArgs, 0, newArgs.Length);
break;
}
String setting = args[index].Substring(3);
switch(args[index].ToUpper()[1]){
case 'Z': // Set zone
zone = (SecurityZone)
Enum.Parse(typeof(SecurityZone), setting, true);
break;
case 'U': // Set url
url = setting;
break;
default:
throw(new ApplicationException());
}
}
StartApp(exe, newArgs, zone, url);
}catch(ArgumentException){ Usage();
}catch(IndexOutOfRangeException){ Usage();
}catch(ApplicationException){ Usage();}
}
static void Usage(){
Console.WriteLine(
"Usage: zoner /u:[url] /z:[zone] [Executable File]\n"+
"[url]\tUrl indicating the source of this .exe");
Console.WriteLine();
Console.Write("[zone]");
String[] zones = Enum.GetNames(typeof(SecurityZone));
foreach(String zone in zones){
Console.WriteLine("\t{0}",
zone);
}
Console.WriteLine(
"\nDefault: zoner /u:http://www.IBuySpy.com
/z:Internet");
}
static void StartApp(String exe,
String[] newArgs, SecurityZone zone, String url){
Evidence evidence = new Evidence();
evidence.AddHost(new Zone(zone));
evidence.AddHost(new Url(url));
AppDomain app = AppDomain.CreateDomain(exe, evidence);
app.ExecuteAssembly(exe, evidence, newArgs);
}
}
When passing the name of an executable to Zoner, you must
qualify the executable with its full name including .exe. Otherwise zoner.exe will not be able to find
the executable to run.
Note: The concepts used by the Zoner.cs utility are fairly
advanced, including advanced CAS and CLR reflection. You can use the sources as a study device if
you want to, but do not feel like you need to understand every aspect of the
source code. And of course you can just
use the utility as a helpful tool to become comfortable with the abilities of
code running from different security zones.
Exercise 2‑4 Testing DisplayFile.cs using Zoner.cs
If you are developing a code library that is going to be widely reused then you will most likely have to learn quite a few of the ins and outs of code access security. However, when writing managed applications, and lightly reused components, you can typically get by with a general understanding of CAS, and some best practices.
This brief section is here simply to introduce you to some of the details related to security that you will encounter when writing managed code.
When writing managed code, you have to be aware that FCL components may throw the SecurityException. Any code that accesses resources on behalf of your application may find that you do not have permission to make the access, and throw the exception. This does not mean, however, that your application should just blow up in the users face. That is not good design.
As a result, almost all developers of managed code will have to throw in the occasional try-catch block to gracefully recover from security exceptions thrown by library components. The following code makes an adjustment to the DisplayFile.cs code in Figure 2‑1.
using System;
using System.IO;
using System.Security;
class App{
public static void
try{
StreamReader reader = new StreamReader(args[0]);
Console.WriteLine(reader.ReadToEnd());
}catch(SecurityException){
Console.WriteLine("Insufficient file permission.");
}
}
}
Figure 3‑1 GracefulDisplayFile.cs
The code in the preceding figure adds an exception handler shown in red. This handler catches the potential SecurityException, and tells the user that they do not have permission. This is preferable to an unhandled exception, which halts the application immediately.
Zone testing is something that you should perform on any managed application that you write. You should minimally test your application in the MyComputer and Intranet zones, since your users are likely to launch your application from either zone.
Zone testing is as simple as running your application from multiple zones, testing its features, and noting the features that do not work due to CAS. Some hot-spots to look for are features like file access, registry access, UI, printing, and network communication.
Note: Many developers new
to the .NET Framework neglect the testing of their applications from various
zones, and then are surprised to hear users reporting application failure when
running the application from a network share.
Once you have found the portions of your application that do not work when run in different zones, you should add exception handling blocks, and other work-around code so that your application performs well under all circumstances. Even an application that gracefully informs the user that it cannot perform its duties due to security restrictions is much better than an application that gives the user no idea why the application is crashing.
Experience is the best mentor, so I have included two versions of a text editor written in C# with this tutorial. The first version is the text editor written without any consideration for security. The second is a revised version with additional code that addresses the possible security limitations of the application. The projects are called Pad.exe and SecurePad.exe respectively.
Exercise 3‑1 Zone
testing Pad.cs
What you have learned so far will get you a long way with managed code and the features of Code Access Security. However, I would like to make you aware of the infrastructure behind the behavior of CAS so that you are prepared for further study on the topic as your needs dictate.
In this section, I will briefly define certain concepts that
come up when dealing with CAS and documentation related to CAS.
Many of the features of code access security are collectively referred to as Policy. In particular, evidence, code groups, and permission sets (each defined shortly) are all part of policy
Your managed code is subject to CAS policy, and system administrators will adjust policy to meet the needs of their systems. The .NET Framework ships with a tool called CasPol.exe that allows you to view and administer any aspect of policy. It is not vitally important that you become comfortable with CasPol.exe, but you should be aware of its existence as a developer of managed code. Developers will, from time to time, have to tweak security policy to test or work with their code.
Note: The .NET Framework also installs a snap-in for the MMC
console that allows you to manage .NET Security policy. The snap-in is meant to be a GUI replacement
for CasPol.exe.
However, the snap-in only supports a subset of the features of CasPol.exe.
A typical use of CasPol.exe, for example, would be to grant a single strongly named component assembly full trust. This way whether the assembly is installed locally, or downloaded from across the network, it would not suffer the security limitations of other code run from the same zone.
Administrators may chose to bestow this trust on certain
components, and you as a developer should be aware of this ability for your own
component code.
Evidence is a general term describing information gathered about an assembly at assembly-load time. Evidence includes zone of origin, strong name public key, Authenticode signatures, URL, and other information.
In fact, evidence is an extensible feature of code access security. What this means is that it is possible to define custom evidence objects, and then use CasPol.exe to add these evidence objects to a system. Then, as code is loaded, the custom evidence can be used to authenticate the code.
You should be aware of evidence for two reasons. First, if you plan to delve deeply in to .NET
Framework Security, evidence becomes an important factor. Second, zones are a common form of evidence,
and often documentation about evidence in general will apply to zone evidence.
Code groups are a hierarchy of nodes maintained in the policy data for a system. Each code group is associated with some form of identification and a permission set. When an assembly is loaded, the system uses evidence to decide to which code groups the assembly belongs.
Once an assemblys code groups have been established the various permission sets for the code groups are concatenated, and these become the permissions for your assembly at runtime.
Permission sets are actually a very simple portion of policy. They are simply a named set of permissions that can be associated with one or more code groups.
Now that you are familiar with some of the more advanced
terms associated with code access security, we can go through the security
steps that the CLR takes when loading an
assembly for execution. The following
list summarizes these steps.
·
Assembly
Loads CLR gathers evidence for the assembly.
·
Code
groups assigned Based on evidence, one or more code groups are assigned to
the assembly.
·
Grant
applied Based on the code groups for the assembly, a grant is applied to the
assembly in memory. The grant is the
collection of all of the permission sets associated with the code groups.
·
Execution
The CLR is now ready to JIT compile and execute the code for the assembly.
Normally you will not have to consider the steps that the
CLR and CAS take when loading an assembly.
But this information can be helpful in helping to complete the picture
that CAS and policy make with managed code.
If you are defining reusable components that allow access to a resource that should be securable, then you have some considerations to make. First, you must consider how your assembly will be administered to have sufficient permissions to access the resource. Second, you must consider how you expose access to the resource to the assemblies consuming your assembly.
It is this second detail that requires you to become more familiar than an application developer must be with CAS. You must decide which permissions will be used to protect the resource that your class exposes (or whether you need to define custom permissions). Then, the methods that access or expose the resource must demand the appropriate permissions before touching the resource.
You will find that you need to directly work with permissions and CAS most often when you write code that interoperates with the operating system by calling system APIs. This is because your code may be granted the ability to call into unmanaged code, but your callers may not. You need a mechanism by which you can demand some permission from the callers, without requiring that they have the complete permissions enjoyed by your assembly.
As a reader new to the .NET Framework, the best thing to
take away from this section is that the CAS system is very flexible, and as the
security requirements of your components grow, you can learn about and use more
of the advanced features of CAS.
Code access security defines a security stack that is used
in the process of demanding permissions.
CAS also supports the ability to mark the security stack at a certain
point in code execution to effect any demands made by code in method calls
deeper on the stack. Using these
features is a very advanced aspect of CAS and is not at all necessary to
understand as a beginning developer with the .NET Framework. However, the following table lists the three
security stack modifications that are available to managed code, for your
future reference.
|
Stack Modification |
Description |
|
Assert |
Marks the stack with the asserted permission. A demand for this permission will halt at
this point in the stack. This feature
is used to pre-empt a demand and re-demand a different permission,
effectively translating one permission requirement for another. Code must have a permission to assert. |
|
Deny |
Code can chose to deny itself a permission or permission
set for the life of a method call.
This is a way to be certain that code deeper in the call stack is not
performing an action that you have not sanctioned. |
|
Permit-only |
Permit-only works much like deny, only rather than deny a
permission or set of permissions, permit only denies all permissions that are
not in the permit-only set. |
Figure 4‑1 Stack Modifications
As you can see, the code security mechanism of the .NET Framework is feature-rich and flexible. You can get by with paying minimal attention to security (only catching the occasional security exception), or you can dig deeply into CAS, and create your own permissions and secured components.
The effect of code access security is that users will be able to run more code, safely. So long as the code a user is running is managed, they will no longer have to worry about viruses and other malicious or buggy code from the Internet.
As developers, CAS gives us freedom to exploit features of
the system, without making users afraid to execute our code. Understanding how to exploit managed code and
code access security will help you to write mobile code and other applications
that are more useable for your users.
Enjoy the .NET Framework!