An important goal of the .NET Framework is to facilitate the development of highly distributed, component-based systems. With the .NET Framework, you can easily create applications that utilize code from different publishers, dynamically loading assemblies from different locations as required. Internet Explorer is an early example of this flexible architecture: downloading and executing .NET controls on demand, and giving the user a rich interface to the Web. Over the coming years, you can expect to see many more .NET applications componentized and delivered "on demand."
Traditionally, code executes using the identity, roles, and permissions of the user that runs it. As we discussed in the previous section, role-based security is important for ensuring system security. However, in a connected world where distributed systems and highly mobile code are the order of the day, it is insufficient to base system security decisions solely on the permission granted to the user running an application. Faulty code can damage files to which the user has access, or, worse, malicious code can take advantage of the user's authority to perform all kinds of mischief. A highly trusted user (with extensive system permissions) cannot freely download and run an application from an untrusted source for fear of what the code may do to his system and other systems on his network.
Many companies tackle the problem of code security by "locking down" their users' desktops, restricting the applications that people can install and run. This heavy-handed approach requires extensive engineering and support resources to maintain and reduces the utility and flexibility of each user's computer.
The .NET Framework includes an important new feature known as code-access security (CAS), which provides fine-grained control over the operations and resources to which managed code has access. With CAS, access is based on the identity of the code, not the user running it. Some examples of operations protected by CAS include:
Creating, deleting, and modifying files and directories
Reading from and writing to the Windows registry
Using sockets to accept or initiate a network connection
Creating new application domains
Running native (unmanaged) code
Other pre-.NET technologies have offered code-level security by differentiating between the identity of code from the user running it. These technologies include Java with its "sandbox" approach and Windows Internet Zones, which restrict the functionality of downloaded controls. However, few of these technologies provide the level of control, flexibility, and extensibility of CAS.
The three key elements of code-access security are evidence, security policy, and permissions. When the runtime loads an assembly, it inspects various characteristics of the assembly (evidence) to determine an identity for the code. Based on a configurable set of rules (security policy), the runtime uses the assembly's evidence as input to a process named policy resolution. The result of policy resolution is a set of protected operations and resources to which the code within the assembly has access (permissions).
In the course of execution, code attempts to perform a variety of operations. If the code attempts to perform a protected operation, then the runtime will look at the permissions granted to the assembly containing the code and determine if they include the permission necessary to allow the action to go ahead. Figure 5-2 illustrates the relationship between evidence, security policy, and permissions during policy resolution. Here is a detailed account of each element:
Evidence includes the characteristics of an assembly used by the runtime to establish an identity for the assembly. Establishing an identity for each assembly allows the runtime to apply security policy in order to determine the set of permissions to grant to the assembly. Evidence includes characteristics, such as the strong name of an assembly, the web site from where an assembly is loaded, or the publisher certificate used to sign an assembly. We discuss evidence and code identity in Chapter 6.
Security policy is a configurable set of rules that the runtime uses during policy resolution to determine which permissions to grant to an assembly by evaluating the assembly's evidence. For example, security policy could dictate that all code downloaded from the web site http://www.oreilly.com has permission to write to the C:\Test.txt file, or that only code signed with Microsoft's publisher certificate can run unmanaged code. We discuss security policy in Chapter 8.
Permissions represent the authority of an assembly's code to access protected operations and resources. Permissions can be very specific, such as permission to open a network connection to the web site http://www.oreilly.com, or very broad, such as permission to read and write anywhere on the hard disk. We discuss permissions in Chapter 7.
CAS does not replace or circumvent the security provided by the Windows operating system. As shown in Figure 5-3, CAS is effectively another layer of security that managed code must pass through before it can interact with protected system resources, such as the hard disk and the Windows registry. The important difference between CAS and Windows security is that CAS bases security decisions on the identity of the code performing an action, whereas Windows bases security decisions on the identity of the user on whose behalf the code is running.
For example, if a managed application tries to write a file to the hard disk, CAS first determines if the code has the necessary authority to write to the file by evaluating the permissions granted to the assembly when it was loaded. If the code does not have the necessary authority, then the write operation fails and the runtime throws a security exception. On the other hand, if the code does have permission, the runtime interacts with the operating system on behalf of the current user to access the file. If Windows file permissions do not allow the user to write to the specified file, then access is denied and the runtime throws an exception. If the user has write permission, then the file is written.
As Figure 5-3 also shows, CAS controls the ability of managed code to call unmanaged (native) code, such as the Win32 APIs. This is important, because when a managed application is allowed to call into unmanaged code, there is no CAS layer to restrict what the unmanaged code can do on behalf of the managed code. Instead of having permissions based on code identity, you are back to the situation where only the permissions of the current user restrict the code's actions. As we discussed at the beginning of this section, this is not sufficient when dealing with mobile code from potentially unknown and untrusted sources.
Finally, CAS also controls access to functionality that is internal to the CLR and does not affect operating system resources, such as the ability to create an application domain. We discuss CAS in greater detail in Chapter 6 through Chapter 9.