Chapter 15. Code Refinement, Security, and Deployment

Topics in This Chapter

  • Code Refinement: .NET provides a tool, FxCop, which analyzes code by checking it against a set of best practice rules and recommendations. This tool is designed for building components, but most applications can benefit from it as way to amend and refine how code is implemented. An example demonstrates how to analyze code using this tool's command-line interface.

  • Strongly Named Assemblies: One aspect of code security is being able to verify an application's origin and version. .NET provides a way to mark an assembly with a key that identifies it, and supports an assembly versioning scheme that distinguishes between code versions—allowing multiple versions of a component to coexist.

  • Application Security: The .NET Code Access Security model is based on a simple principle: allow code to access system resources and perform operations only when it has permission to do so. Before an assembly can access resources such as files, sockets, or the registry, it is checked for evidence to determine the permissions that it can be given. This chapter explains the overall security model and looks at how it is applied administratively and within code.

  • Application Deployment: One of the touted benefits of .NET is the ability to install an application using XCOPY deployment—simple file copying. However, many applications require a more sophisticated approach that takes into account security policies and resource management. This chapter presents a checklist of issues to be considered.

In the earliest days of programming, computers were used primarily to perform calculations and tedious tabulations. The measure of a program's correctness was whether it produced accurate results for a given set of input values. Modern software development now relies more on component-based solutions. The components often come from multiple sources, and it's not always possible to know the origin or trustworthiness of the components. As a result, code security and the ease of deploying and updating an application are now important metrics against which an application's success is judged.

This chapter looks at the issues and steps involved in producing a deliverable .NET software product. It breaks the process down into the three categories shown in Figure 15-1: code refinement, which looks at how code is tested against best practice rules; code security, which ensures that code is accessed only by other code that has permission to do so; and code deployment, which looks at how an application or component is packaged and made available for deployment.

Deliverable software should meet coding standards, be secure, and be easily deployed

Figure 15-1. Deliverable software should meet coding standards, be secure, and be easily deployed

The first section shows how to use FxCop as a tool to analyze an assembly and generate code change recommendations based on a predefined set of coding standards. The second section looks at the details of how to create a strongly named assembly and the security benefits that accrue from doing so.

The next section—which forms the heart of the chapter—explores the topic of Code Access Security (CAS). It explains how an administrator uses .NET tools to define a multi-level security policy for a computing environment and how security features are embedded in code. It also stresses understanding the interrelated security roles of evidence, policy, and permissions.

The chapter concludes with a look at the issues to be considered in deploying an application to users or customers. The advantages and disadvantages of using XCOPY or an installer to physically distribute an application are discussed.

Following .NET Code Design Guidelines

The developers of .NET included a set of rules [1] that are intended to serve as a guide for writing code that runs on the .NET platform. Although the rules are written with component developers in mind (Microsoft uses them for its managed code libraries), any application can benefit from them.

To help a developer incorporate these rules and best practices in their code, .NET includes the FxCop tool that analyzes code against these rules. Some of the rules may be stricter than your development environment requires; or they may simply conflict with your own standards—you may prefer non-Microsoft naming conventions, for instance. To accommodate your own coding standards, the tool permits you to disable rules and add custom ones. Figure 15-2 shows how rules are displayed with check boxes that make it easy to enable or disable them.

FxCop allows rules to be enabled or disabled by clicking a check box

Figure 15-2. FxCop allows rules to be enabled or disabled by clicking a check box

FxCop is available as both a GUI (fxcop.exe) and command line (fxcopcmd.exe) application. Downloads of the latest version and documentation are free and available at several Web locations. The download also includes an SDK that can be used to create custom rules—a topic beyond the scope of this section.

Using FxCop

The purpose of FxCop is to analyze an assembly and produce output that pinpoints code features that violate the set of recommended best practices. To illustrate how it works, let's create an assembly from the code in Listing 15-1 and run it through FxCop. The program is a simple console application that accepts an input string, reverses it, and prints it. A simple menu permits the user to specify whether she wants to reverse a string or quit.

To test with FxCop, compile the program and pass the assembly to FxCop:

C:/> csc fxtest.cs
C:/> fxcopcmd  /file:fxtest.exe  /out:fxoutput.txt

Due to the length of the output, it's better to direct it to a file rather than display it.

Example 15-1. Code to Be Analyzed by FxCop

//fxtest.cs
using System;
using System.Text;
namespace FxTesting
{
   public class TestApp 
   {
      static void Main()
      {
         string msg;
         string oper="";
         while(oper !="Q")
         {
            Console.WriteLine("(R)everse, (Q)uit");
            oper= Console.ReadLine();
            oper = oper.ToUpper();
            if(oper=="R"){
               Console.WriteLine("Enter phrase to reverse:");
               msg= Console.ReadLine();
               if(msg.Length>1) msg= Reverse(msg);
               Console.WriteLine(msg);
         }
      }
   }
   // Function to reverse a string
   public static String Reverse(String stringParameter)
   {
      if(stringParameter.Length==1)
      {
         return stringParameter;
      }
         else
         {
            return Reverse(stringParameter.Substring(1)) + 
                           stringParameter.Substring(0,1);
         }
      }
   }
}

The output is serialized as XML that contains Message tags, describing each occurrence where the code does not conform to the recommended practices. Here is an example of the raw code comprising one message:

<Message TypeName="AssembliesShouldHaveValidStrongNames" 
   Category="Microsoft.Design" CheckId="CA2210" Status="Active" 
   Created="2005-01-12 02:41:07Z" FixCategory="NonBreaking"> 
   <Issue Name="NoStrongName" Certainty="95"  
      Level="CriticalError">Sign 'fxtest' with a strong name key.
   </Issue>
</Message>

Let's look at the analysis FxCop produces for the fxtest assembly. For brevity, only the TypeName values from the XML are listed. Beneath each is a description of the code changes that will eliminate the message:

(1)

"AssembliesShouldDeclareMinimumSecurity"

Requires adding a permission attribute that specifies the permissions required by this assembly. This is explained in Section 15.3 of this chapter.

(2)

"AssembliesShouldHaveValidStrongNames"

Assembly should be assigned a strong name, which is a key that identifies the assembly. Assigning strong names is described in Section 15.2 of this chapter.

(3)

"MarkAssembliesWithAssemblyVersion"

Add version attribute: [assembly: AssemblyVersion("1.0.0.0")].

(4)

"MarkAssembliesWithClsCompliant"

Add compliant attribute: [System.CLSCompliant(true)]

(5)

"MarkAssembliesWithComVisible"

Add ComVisible attribute: [ComVisible(true)].

This exposes public managed assemblies and types to COM. By default, they are not visible to COM.

(6)

"StaticHolderTypesShouldNotHaveConstructors"

Because the class TestApp has only static members, it should not have a public constructor. In this case, the public constructor is the default parameterless constructor. To override this, add: private TestApp() {}.

(7)

"AvoidUnnecessaryStringCreation"

To avoid allocating memory for strings, string operations should be avoided. The solution is to eliminate oper = oper.ToUpper(); and to use case- insensitive comparisons in the code, such as if(string.Compare(oper, "R", true)==0).

(8)

"AvoidTypeNamesInParameters"

Naming rules recommend that type names not be included as part of a parameter name. In this case, it objects to the parameter name stringParameter.

A review of the output shows that the assembly recommendations (1 through 5) are oriented toward components that will be stored in libraries and used by other code. As one would expect, the emphasis is on how component code interacts with other code. In general, security and type visibility is of more importance here than in general application development. Recommendations 6 and 7 promote more efficient code, whereas rule 8 is a subjective naming convention.

Many of the rules are overly restrictive for most programming environments, and you'll want to disable them. However, it is beneficial to be aware of them. They represent a set of best practices that have evolved with .NET. Even if you do not incorporate them in your code, understanding the reasoning behind them will deepen your knowledge of .NET.

Strongly Named Assemblies

The software development process relies increasingly on integrating code with components from multiple vendors. The vendor may be well known and trusted, or the component may be Internet freeware from an unknown developer. In both cases, security concerns demand a way to identify and authenticate the software. The most reliable solution is to sign the software with some unique digital signature that guarantees its origin and trustworthiness. The certificates that identify the publisher of downloadable software on the Internet are an example of this.

One of the integral parts of .NET security is the use of signing to create a uniquely identifiable assembly. .NET refers to such an assembly as strongly named. A strongly named assembly uses four attributes to uniquely identify itself: its file or simple name, a version number, a culture identity, and a public key token (discussed later). Together, these four are referred to as the “name” of the assembly. In addition, the assembly has the digital signature created by signing it. All of this is stored in the assembly's manifest. [2]

Although an assembly does need to be strongly named, doing so offers several advantages:

  • It enables version control. Although you can add a version number to an assembly, the Common Language Runtime (CLR) ignores the version information unless the assembly is strongly named. As we will see later, having versioning in effect permits multiple versions of a component to run and ensures compatibility between assemblies sharing a version number.

  • It permits an assembly to be placed in the Global Assembly Cache (GAC) where it can be shared among calling assemblies.

  • The .NET Code Access Policy can be used to grant or restrict permissions to assemblies based on their strong name. In addition, the strong name can also be used programmatically to control access to resources.

  • It allows assemblies to have the same name, because the assemblies are identified by their unique information—not their name.

Creating a Strongly Named Assembly

The first step in creating a strong name is to create a pair of public and private encryption keys. During compilation, the private key is used to encrypt the hashed contents of the files contained in the assembly. The encrypted string that is produced is the digital signature that “signs” the assembly. It is stored in the manifest, along with the public key. Here are the steps to create and use a strongly named assembly:

  1. A strong name is generated using asymmetric public key cryptography. This scheme relies on a private key that is used for encryption and a public key that decrypts. To create a file containing this key pair, use the Strong Name command-line utility as shown here:

    SN –k <keyfilename>
    SN –k  KeyLib.snk
    
  2. Because the public key that is created is so large, .NET creates a more manageable public key token that is a 64-bit hash of the public key. When a client assembly is built that references a strongly named assembly, the public key token of the referenced assembly is stored in the manifest of the client assembly as a way to reference the target assembly.

  3. After you have the file containing the public/private key pair, creating the strongly named assembly is simple: just place the AssemblyKeyFile attribute (located in the System.Refection namespace) in your code at the assembly level:

    [assembly: AssemblyKeyFile("KeyLib.snk")]
    
  4. This statement causes the compiler to extract the private key from the specified file and use it to sign the assembly. At the same time, the public key is placed in the assembly's manifest. Note that if you invoke the C# compiler from the command line, you can leave out the AssemblyKeyFile attribute and use the /keyfile flag to specify the path to the .snk file.

  5. When the runtime loads this assembly, it essentially reverses the signing process: It decrypts the signature using the public key found in the manifest. Then, it performs a hash of the assembly's contents and compares this with the decrypted hash. If they do not match, the assembly is not allowed to run.

Figure 15-3 summarizes the overall process. Note how decryption works. The decrypted signature yields a hash that should match the output when a new hash is performed on the assembly. If the two match, you can be sure that the private key associated with the public key was used for the original signing, and that the assembly has not been tampered with—changing even one bit will result in a different hash.

Using private and public keys to sign and verify a strong assembly

Figure 15-3. Using private and public keys to sign and verify a strong assembly

Core Note

Core Note

A digital signature is not the same thing as a digital certificate. The digital signature in a strongly named assembly tells you nothing about who created the assembly, whereas a certificate contains the identity information that is used to authenticate the certificate publisher. Refer to documentation on Authenticode to learn how to obtain and use a certificate with an assembly.

Delayed Signing

It is imperative that the private key generated using Sn.exe (or some other process) not be compromised, because it is how an organization uniquely signs its software. If another party has access to the key, consumers cannot trust the ownership of the assembly.

One measure to secure the key is to limit its availability to developers, or withhold it altogether. During the development stage, developers are given only the public key. Use of the private key is delayed until it is necessary to sign the final software version. Delayed signing requires different steps than are used for creating a strongly named assembly:

  1. Sn.exe is used to create a file containing the public/private key pair. This should be the task of the security administrator.

  2. Sn.exe is run again to extract the public key into a separate file. The command uses the –p switch, and specifies the original file and the file to contain the public key:

    SN –p  KeyLib.snk  PubKeyLib.snk
    
  3. The public key file is distributed to developers who must add two attributes to their assemblies to perform partial signing and have the public key stored in the assembly's manifest.

    [assembly: AssemblyKeyFile("PubKeyLib.snk")]
    [assembly: AssemblyDelaySign(true)]
    
  4. If you follow these preceding steps to create an assembly, you'll find that an exception occurs (“strong name validation failed”) when you try to load it. This is because the assembly does not have a valid signature. To instruct the runtime to skip signature verification, run Sn.exe with the –Vr switch and specify the delay-signed assembly:

    SN –Vr <delay-signed assembly>
    
  5. Prior to deployment, the assembly should be officially signed. Use SN with the –R switch to sign it:

    SN –R <delay-signed assembly> 
    

    To reinstate signature verification for the assembly, run

    SN –Vu <assembly>
    

Because an assembly references another strongly named assembly using its public key, there is no need to rebuild assemblies dependent on this one.

Global Assembly Cache (GAC)

The Global Assembly Cache is a special directory set aside where strongly named assemblies can be stored and located by the CLR. As part of resolving references at load time, the CLR automatically looks in the GAC for the requested assembly. The obvious advantage of storing assemblies in the GAC is that they are located in a central, well known location where they can be located and shared by multiple applications. A less obvious advantage is that the strong name signatures for assemblies in the GAC are verified only when installed in the GAC. This improves the performance of applications referencing these assemblies, because no verification is required when loading the assembly.

Physically, the GAC is a Microsoft Windows directory located on the following path:

C:winntassembly

You can view its contents using a shell extension (ShFusion.dll) that is added to Windows Explorer as part of the .NET Framework installation. Each entry displays an assembly's name, type, version number, and public key token. By clicking an assembly entry, you can bring up a context menu that permits you to display the assembly's properties or delete it.

The easiest way to install a strongly named assembly into the GAC (or uninstall one) is to use GACUtil.exe, a command-line utility that ships with .NET SDK. Here is the syntax for performing selected operations:

>gacutil /i <assembly>

Installs assembly in GAC.

>gacutil /u <assembly>

Uninstalls an assembly from the GAC.

>gacutil /if <assembly>

Installs assembly in GAC; if an assembly with that name already exists, it is overwritten.

>gacutil /l

Lists contents of GAC.

There are a couple of drawbacks to storing an assembly in the GAC. First, it is difficult to reference during compilation due to the verbose GAC subdirectory naming conventions. An alternative is to compile referencing a local copy of the assembly. Then, remove the local assembly after compilation is completed.

Another possible drawback stems from the fact that an assembly cannot be copied into the GAC. If your application requires an assembly in the GAC, it eliminates deploying an application by simply copying files to a client's machine. Deployment issues are discussed in the last section of this chapter.

Versioning

A major benefit of using strongly named assemblies is that the CLR uses the assembly's version information to bind assemblies that are dependent on each other. When such an assembly is loaded, the CLR checks the version number of referenced assemblies to ensure they have the same version numbers as recorded in the calling assembly's manifest. If the version fails to match (usually because a new version has been created), an exception is thrown.

Assigning a Version Number to an Assembly

Every assembly has a version number. A default value is used if one is not explicitly defined. The version can be assigned using the Assembly Linker (Al.exe) tool, but is usually declared within the code using an AssemblyVersion attribute:

[assembly: AssemblyVersion("1.0.0.0")]

The version number has four parts:

<major version>.<minor version>.<build number>.<revision>

You can specify all the values or you can accept the default build number, revision number, or both by using an asterisk (*). For example:

[assembly:AssemblyVersion("2.3.")]     yields 2.3.0.0
[assembly:AssemblyVersion("2.3.*")]    yields 2.3.1830,4000

When an asterisk (*) is specified for the build number, a default build number is calculated by taking the number of days since January 1, 2000. The default revision number is the number of seconds past midnight divided by two.

You can use reflection to view an assembly's version along with the other parts of its identity. To illustrate, add the following attributes to the code in Listing 15-1 to create a custom version number and strong name:

[assembly: AssemblyKeyFile("Keylb.snk")]
[assembly: AssemblyVersion("1.0.*")]

Compile the code and use the Assembly.GetName method to display the assembly's identification.

Console.WriteLine(Assembly.GetExecutingAssembly().GetName());

This method returns an instance of the AssemblyName class that contains the assembly's simple name, culture, public key or public key token, and version.

fxtest, Version=1.0.1839.24546, Culture=neutral, 
   PublicKeyToken=1f081c4ba0eeb6db

Security

The centerpiece of .NET security is the Code Access Security model. As the name implies, it is based on code access—not user access. Conceptually, the model is quite simple. Before an assembly or component within an assembly may access system resources (files, the registry, event log, and others), the CLR checks to ensure that it has permission to do so. It does this by collecting evidence about the assembly—where is it located and its content. Based on this evidence, it grants the assembly certain permissions to access resources and perform operations. Figure 15-4 illustrates the key elements in this process and introduces the terms that you must know in order to administer security.

An assembly is matched with code groups whose evidence it satisfies

Figure 15-4. An assembly is matched with code groups whose evidence it satisfies

When an assembly is loaded, the CLR gathers its evidence and attempts to match it with code groups whose evidence it satisfies. A code group is a binding between a set of permissions and a single type of evidence. For example, a code group may be defined so that only assemblies from a particular application directory are allowed to have Web access. If an assembly's site evidence indicates it is from that directory, it is part of the code group and has Web access. An assembly can be a member of multiple code groups, and, consequently, can have multiple permissions.

.NET provides predefined evidence, permissions, code groups, and security policies—a collection of code groups. Although code can be used to hook into and modify some aspects of security, an administrator performs the bulk of security configuration and management using .NET tools. In most cases, the predefined elements are all that an administrator needs. However, the security model is flexible, and permits an administrator to create security policies from custom evidence, permissions, and code groups.

This abstract representation of .NET security shown in Figure 15-4 is implemented in concrete types: The Evidence class is a collection that holds evidence objects; permission classes grant access to a resource or the right to perform some action; and a PermissionSet is a collection class that groups permissions and contains methods to manipulate them. The following sections take a close look at evidence, permissions, and how they are related. You'll then see how to implement a security policy, both as an administrator and by accessing the permission classes through code.

Permissions and Permission Sets

A permission is the right to access a resource or perform some action. An assembly is assigned a permission when its evidence matches the evidence requirements for a permission set. Figure 15-5 illustrates how permissions come into play when an assembly attempts to access a resource. In this case, assembly A calls assembly B, which creates a FileStream object and attempts to use its Write method. The code in the .NET Framework Class Library that implements this method demands that the calling assembly have permission to perform file I/O. Moreover, it requires that all assemblies further up the call stack also have this permission. This check—known as walking the call stack—ensures that an assembly that does not have a required permission cannot use one that does to illegally perform an operation.

An assembly must have permission to perform file I/O

Figure 15-5. An assembly must have permission to perform file I/O

Because both Assembly B and Assembly A possess the FileIO permission, the write operation is permitted. An exception of type System.Security.SecurityException is thrown if either assembly does not have the requisite permission.

Permissions are often interrelated: for example, the permission to write to a file requires an accompanying permission to access the directory containing the file. To avoid having to grant and deny all permissions on an individual basis, .NET includes a PermissionSet class that allows a collection of permissions to be treated as a single entity for the purpose of denying or granting permissions.

As we shall see, permission sets can be created and applied programmatically by creating and adding individual permission objects to a PermissionSet collection. An alternate approach is to use the .NET Configuration tool to create permission sets and assign them to code groups. To encourage this approach, .NET includes predefined permission sets.

Named Permission Sets

.NET provides seven built-in or named permission sets. They range from the most restrictive Nothing, which prevents an assembly from loading, to the least restrictive Full-Trust, which permits unrestricted access to all of the permissions listed in Table 15-1. Here they are in ascending order of trust:

  • NothingPrevents an assembly from being loaded. This is used primarily when the code is deemed untrustworthy because its origin cannot be determined.

  • ExecutionPermits code to load and run, but little else. It cannot access external resources. Such code is useful only for making calculations.

  • InternetAllows code to display and implement a user interface. By default, .NET grants this permission to all code coming from the Internet. Its permissions include the ability to open a file, perform safe printing, create safe top-level and subwindows, and connect to the originating site using HTTP or HTTPS.

  • LocalIntranetSpecifies the default permissions for code originating in the local intranet. In addition to the permissions granted the Internet set, its permissions include unrestricted access to FileDialogs, default printing privileges, and unrestricted user interface access.

  • EverythingGrants all permissions except permission to skip verification, because this is needed to verify the code is type-safe.

  • FullTrustGrants access to all built-in permissions. All code running on the local machine is given this by default.

Table 15-1. Built-in Permission Classes

Namespace

Class Name

Controls Permission To:

System.Security.Permissions

Environment

Access user and OS environment variables

 

FileDialog

Access files or folders using a file dialog box.

 

FileIO

Access files or folders.

 

IsolatedStorage

Configure isolated storage and set quota on size of user's store.

 

KeyContainer

Access key containers. A key container is an area within a key database that contains key pairs used for cryptography.

 

Reflection

Access metadata using Reflection commands.

 

Registry

Access system registry.

 

Security

Access security permissions.

 

Store

Stores containing X.509 certificates. X.509 is a standard for public key certificates. Most commonly used for securing transmission of data over Internet.

 

UI

User interfaces and the clipboard.

System.Net

Dns

Domain name servers.

 

Socket

Connect or accept connects at a specified socket. A socket is an address designated by a port # and IP address.

 

Web

Connect to or from a Web host.

System.Drawing

Printing

Access printers.

System.Data.Common

DBData

Access data using a data provider.

System.Data.SqlClient

SqlClient

Use SQL Server data provider.

System

EventLog

Write to or browse a machine's event log.

System.ServiceProcess

ServiceController

Access to control or browse a machine's services.

System.Messaging

MessageQueue

Access the message queue.

Figure 15-6 lists the most interesting permission sets along with the individual permissions they contain. These sets cannot be modified; however, they can be copied and used as a base set for creating a custom permission set.

Permissions associated with selected named permission sets

Figure 15-6. Permissions associated with selected named permission sets

Permission Set Attributes

The built-in permission sets provide a convenient way to request permission for more than one permission type at a time. This code segment shows the syntax to request permission for the LocalIntranet permission set. It attaches a PermissionSetAttribute with a Name value set to the name of the desired permission set:

[PermissionSet(SecurityAction.Demand,Name="LocalIntranet")]
public string GetTitle()  // Requires LocalIntranet
{}

Note that all named permission sets except Everything can be applied as an attribute.

.NET Built-in Security Permissions

The individual permissions shown in Figure 15-5 are implemented in .NET as built-in permission classes. In addition to being accessed by the security configuration tools, they can also be accessed by code to implement a finer-grained security than can be configured using administrative tools.

Table 15-1 summarizes the more important built-in permission classes.

All permission classes inherit and implement the interfaces shown in Figure 15-7. Of these, IPermission and IStackWalk are the most useful. IPermission defines a Demand method that triggers the stack walk mentioned earlier; IStackWalk contains methods that permit a program to modify how the stack walk is performed. This proves to be a handy way to ensure that a called component does not perform an action outside of those that are requested. We'll look at these interfaces in more detail in the discussion of programmatic security.

Interfaces inherited by permission classes

Figure 15-7. Interfaces inherited by permission classes

Identity Permissions

Recall that when the CLR loads an assembly, it matches the assembly's evidence against that required by code groups and grants permissions from the code groups whose criteria it meets. These code group derived permissions are either custom permissions or built-in permissions as described in Table 15-1.

The CLR also grants another set of permissions that correspond directly to the identity evidence provided by the assembly. For example, there are ZoneIdentityPermission and StrongNamedIdentityPermission classes that demand an assembly originate from a specific zone or have a specific strong name identity. Table 15-2 lists the origin-based identity classes.

Table 15-2. Identity Permission Classes

Class

Identity Represented

PublisherIdentityPermission

The digital signature of the assembly's publisher.

SiteIdentityPermission

Web site where the code comes from.

StrongNamedIdentityPermission

Strong name of the assembly.

URLIdentityPermission

URL where the code comes from. This includes the protocols HTTP, HTTPS, and FTP.

ZoneIdentityPermission

Zone where the code originates: Internet, Intranet, MyComputer, NoZone, Trusted, Untrusted.

Unlike the built-in permissions described earlier, these classes cannot be administered using configuration tools. Instead, a program creates an instance of an identity permission class and uses its methods to demand that an assembly provide a specified identity to perform some action. This programmatic use of permission classes is referred to as imperative security.

Permission Attributes

All security permission classes have a corresponding attribute class that can be applied as an attribute to an assembly, class, and method to specify security equivalent to that provided by the permission class. This is referred to as declarative security, and serves two useful purposes: When applied at the assembly level, a permission attribute informs the runtime which permissions the assembly requires, and enables the runtime to throw an exception if it cannot grant these permissions; when applied to classes and methods within the code, the attribute specifies which permissions any calling assemblies must have to use this assembly. Examples using declarative security are provided later in the chapter.

Evidence

To qualify as a member of a code group and assume its privileges, an assembly must provide evidence that matches the evidence membership requirements of the code group. This evidence is based on either the assembly's origin or its signature. The origin identification includes Site, Url, and Zone evidence; the signature refers to an assembly's strong name, its digitally signed certificate (such as X.509), or a hash of the assembly's content. The Common Language Runtime provides seven predefined types of evidence. They are referred to by names used in the security administrative tools:

  • Strong NameAn assembly with a Strong Name has a public key that can be used to identify the assembly. A class or method can be configured to accept calls only from an assembly having a specified public key value. The most common use for this is to identify third-party components that share the same public key. A Strong Name has two other properties, Version and Name, that also can be required as evidence by a host assembly.

  • PublisherThis evidence indicates that an assembly has been digitally signed with a certificate such as X.509. Certificates are provided by a trusted certificate authority and are most commonly used for secure Internet transactions. When a signed assembly is loaded, the CLR recognizes the certificate and adds a Publisher object to the assembly.

  • HashBy applying a computational algorithm to an assembly, a unique identifier known as a hash is created. This hash evidence is automatically added to each assembly and serves to identify particular builds of the assembly. Any change in the compiled code yields a different hash value—even if the version is unchanged.

  • Application DirectoryThis evidence is used to grant a permission set to all assemblies that are located in a specified directory or in a subdirectory of the running application.

  • SiteSite evidence is the top-level portion of a URL that excludes the format and any subdirectory identifiers. For example, www.corecsharp.net is extracted as site evidence from http://www.corecsharp.net/code.

  • URLThis evidence consists of the entire URL identifying where an assembly comes from. In the preceding example, http://www.corecsharp.net/code is provided as URL evidence.

  • ZoneThe System.Security.SecurityZone enumeration defines five security zones: MyComputer, Intranet, Internet, Trusted, and Untrusted. An assembly's zone evidence is the zone from which it comes.

    • MyComputerCode coming from the local machine.

    • IntranetCode coming from computers on the same local area network.

    • InternetCode coming from the Internet that is identified by an HTTP or IP address. If the local machine is identified as http://localhost/, it is part of the Internet zone.

    • TrustedIdentifies Internet sites that are trusted. These sites are specified using Microsoft Internet Explorer (IE).

    • UntrustedSites specified in IE as being malicious or untrustworthy.

In addition to these, there is also a blank evidence known as All Code evidence that is used by an administrator to create a code group that matches all assemblies.

The CLR maintains evidence in an instance of the Evidence collection class. This object contains two evidence collections: one for the built-in host evidence and another for user-defined evidence. This evidence is made available to security policy, which then determines the permissions available to the assembly. You can use reflection to view evidence programmatically. In this example, we view the evidence for an assembly, movieclient.exe, which is located on the local machine (the assembly's source code is presented later in this section):

using System;
using System.Reflection;
using System.Security.Policy;
class ClassEvidence
{
   public static void Main()
   {
      Assembly ClientAssembly;
      // (1)Load object to reference movieclient assembly
      ClientAssembly = Assembly.Load("movieclient");
      // (2) Evidence is available through Evidence property
      Evidence ev = ClientAssembly.Evidence;
      // (3) Display each evidence object
      foreach (object ob in ev) 
      {
         Console.WriteLine(ob.ToString());
      }
   }
}

Output from the program reveals the Zone, Url, Strong Name, and Hash evidence associated with the assembly. No Site evidence is present because the assembly's Url origin is defined by a file:// rather than an http:// format. Application Directory evidence is also missing because it comes from the host application, not the assembly's metadata.

<System.Security.Policy.Zone version="1">
   <Zone>MyComputer</Zone>
</System.Security.Policy.Zone>

<System.Security.Policy.Url version="1">
   <Url>file://C:/movieclient.EXE</Url>
</System.Security.Policy.Url>

<StrongName version="1"
            Key="002400... 8D2"
            Name="movieclient"
            Version="0.0.0.0"/>

<System.Security.Policy.Hash version="1">
   <RawData>4D5A90000300000004000000FFFF0000B80
   </RawData>
</System.Security.Policy.Hash>

Security Policies

A .NET security policy defines how assembly evidence is evaluated to determine the permissions that are granted to the assembly. .NET recognizes four policy levels: Enterprise, Machine, User, and Application Domain. The policy-level names describe their recommended usage. Enterprise is intended to define security policy across all machines in the enterprise; Machine defines security for a single machine; User defines security policy for individual users; and Application Domain security is applied to code running in a specific AppDomain. Enterprise, Machine, and User policies are configured by an administrator. AppDomain policy, which is implemented only programmatically and used for special cases, is not discussed.

Despite their names, policies can be configured in any way an administrator chooses. The User policy could be set up to define enterprise security and the Machine policy to define user security. However, an administrator should take advantage of the names and use them to apply security to their intended target. As you will see in the discussion of the .NET Framework Configuration Tool (see “The .NET Framework Configuration Tool” on page 704), the security policy is granular enough to allow custom security policies on individual machines and users.

How .NET Applies Security Policies

Each security policy level is made up of one or more code sets. Each code set, in turn, contains a set of permissions that are mapped to a specific evidence type. Figure 15-8 illustrates how code sets and policy levels are combined to yield a permission set for an assembly.

A permission set is created from the intersection of policy level permissions

Figure 15-8. A permission set is created from the intersection of policy level permissions

The .NET security manager is responsible for evaluating evidence and policy to determine the permissions granted. It begins at the enterprise level and determines the permissions in it that can be granted to the assembly. In this example, enterprise contains three code groups—two of which the assembly's evidence satisfies. The logical union of these permissions produces the permission set at this level. The other two policy levels are evaluated in the same way, yielding their associated permission set. The logical intersection of the three permission sets produces the permission set that is assigned to the assembly. In this case, the final set consists of permissions 2 and 5—the only permissions present on each level.

Configuring Security Policy

Physically, each policy level is stored as a configurable XML file that defines a hierarchy of code groups for the policy. The Enterprise policy file, enterprisec.config , and the Machine policy file, security.config[3], are stored in the same folder on a Microsoft Windows system:

<Windows Directory>Microsoft.NETFramework<Version>config

The User policy file is named security.config and is located on the path

<Documents and Settings><User Name>Application DataMicrosoft
      CLR Security Config<Version>

Listing 15-2 contains an extract from the Machine policy file that illustrates the file layout. It comprises four major sections:

<SecurityClasses>

Defines all individual permissions.

<NamedPermissionSets>

Defines named permission sets.

<CodeGroup>

Provides a name and permission set for code group.

<FullTrustAssemblies>

List of trusted assemblies.

The code group section is structured as a multi-level hierarchy, with the conditions for granting permissions becoming more restrictive at each lower level.

Example 15-2. Extracts from the Machine Policy File Security.Config

<SecurityClasses>
   <SecurityClass Name="WebPermission"
         Description="System.Net.WebPermission, System, 
         Version=2.0.3600.0, Culture=neutral, 
         PublicKeyToken=b77a5c561934e089"/>
   <SecurityClass Name="EventLogPermission" 
         Description="System.Diagnostics.EventLogPermission, 
         System, Version=2.0.3600.0, Culture=neutral, 
         PublicKeyToken=b77a5c561934e089"/>
   ...
</SecurityClasses>
<NamedPermissionSets>
   <PermissionSet class="NamedPermissionSet" 
         version="1"
         Name="LocalIntranet"
         Description="Default rights given to applications on 
         the local intranet">
      <IPermission class="EnvironmentPermission"
         version="1" Read="USERNAME"/>
      <IPermission class="FileDialogPermission"
         version="1" Unrestricted="true"/>
   ...
</PermissionSet>
...
</NamedPermissionSets>
<CodeGroup class="UnionCodeGroup"
         version="1"
         PermissionSetName="Nothing"
         Name="All_Code"
         Description="Code group grants no ...">
       <IMembershipCondition class="AllMembershipCondition"
         version="1"/>
   <CodeGroup class="UnionCodeGroup"
         version="1"
         PermissionSetName="FullTrust"
         Name="My_Computer_Zone"
         Description="Code group grants ...">
         ...
   </CodeGroup>
   ... additional code groups here
</CodeGroup>
<FullTrustAssemblies>
   <IMembershipCondition class="StrongNameMembershipCondition"
         version="1"
         PublicKeyBlob="00000000000000000400000000000000"
         Name="mscorlib.resources"
         AssemblyVersion="Version=2.0.3600.0"/>
   <IMembershipCondition class="StrongNameMembershipCondition"
         version="1"  
         PublicKeyBlob="00000000000000000400000000000000"
         Name="System"
         AssemblyVersion="Version=2.0.3600.0"/>
   ...
</FullTrustAssemblies>

.NET provides two ways to work with these policy files: a command-line Code Access Security Policy (caspol) and a graphical Configuration tool (MSCorCfg.msc). Both can be used to modify the default configuration, create custom code groups, create custom permission sets, and export policies to be deployed with an application. We'll look at both tools in this section, with an emphasis on the Configuration tool because its visual interface makes it more popular and easier to learn than the command-line approach.

The .NET Framework Configuration Tool

On a Microsoft Windows system, you start the Configuration tool by selecting Microsoft .NET Framework Configuration from the Administrative Tools folder or by selecting Run and typing MSCORCFG.MSC. The program interface consists of a window divided into two panes. As shown in Figure 15-9, the left side contains a tree structure comprising multiple folders. Of these, the Runtime Security Policy folder expands to display a hierarchy of security information.

Interface for the .NET Framework Configuration tool

Figure 15-9. Interface for the .NET Framework Configuration tool

At the top level of the hierarchy are the three folders representing the Enterprise, Machine, and User policies. Beneath each of these are folders that contain code groups, permission sets, and policy assemblies. This hierarchy is, of course, simply a visual representation of the underlying XML policy files. You can observe this by comparing the raw XML tags in Listing 15-2 with the items displayed under the Machine policy.

The Default Configuration Policies

The Enterprise and User policies contain a single default code group named All_Code. If you click it, you'll see this description in the right panel:

“Code group grants all code full trust and forms the root of the code group tree.”

Specifically, this code group binds All Code evidence with the FullTrust permission set. Recall that all assemblies qualify as members of code groups that use All Code evidence and that the FullTrust permission set offers unrestricted permissions. The net effect of binding these two is to create Enterprise and User policies that offer unlimited permissions to all assemblies. In other words, these two policies offer no security at all by default.

Core Note

Core Note

The code groups provided by .NET are named after the evidence they represent. Because no two code groups may have the same name, custom code groups must use a modified naming convention.

The Machine security policy is far more interesting and instructive. At its root is the All_Code code group. Unlike the other two policies, it binds All Code evidence to the Nothing permission set, which means the code group grants no permissions. To find the permissions, you must look to the code groups nested beneath this root. The first level contains six groups: My_Computer_Zone, LocalIntranet_ Zone, Internet_Zone, Restricted_Zone, Trusted_Zone, and Application_ Security_Manager. All except Restricted_Zone have one or more child code groups. Let's look at how default permissions are granted for two of these: My_Computer_Zone and LocalIntranet_Zone. To view details about the other code groups, simply right-click their name to bring up a properties window.

My_Computer_Zone Code Group

This code group grants the FullTrust permission set to assemblies that satisfy the My Computer zone evidence. Its two child code groups grant FullTrust to assemblies that have Strong Name evidence containing either the Microsoft public key or the ECMA public key, also known as the Standard Public Key.[4] It is important to understand that an assembly can satisfy the Strong Name evidence without satisfying the My Computer zone evidence. This is because .NET evaluates child code groups even if the conditions for the parent group are not satisfied. Conversely, a code group's properties can be set to instruct .NET not to evaluate child groups if the conditions of the parent code group are satisfied.

LocalIntranet_Zone Code Group

This code group grants the LocalIntranet permission set to assemblies that satisfy the Local Intranet zone evidence—any computers on the same local area network as the machine on which the host code runs. This code group has two child code groups: Intranet_Same_Site_Access and Intranet_Same_Directory_Access. The former permits code to access the site of its origin; the latter permits code to access its original install directory. The practical effect of these permissions is to permit code to perform I/O on its local storage.

Configuring Code Access Security with the Configuration Tool—An Example

To illustrate how to use the Configuration tool, we'll create a new permission set and assign it the Reflection and Execution permissions. Next, we'll create a code group that maps the new permission set to Url evidence that specifies a directory on the local machine. The effect is to grant the Reflection permission to any assembly that runs from this directory.

For testing, we'll create a simple application that uses reflection to access a private field in this assembly's class:

// (movieclass.dll) Will use reflection to access this class
using System;
public class Movies
{
   private int ID;
   private string Director;
   public string title;
   public string year;
}

Listing 15-3 contains the code that accesses the private Director field. This is done by calling the Type.GetField method and passing as arguments the field name and flags that request access to a private field in a class instance.

Example 15-3. Assembly Requiring Reflection Permission

// configtest.cs
using System;
using System.Reflection;
class ClassEvidence
{
   public static void Main()
   {
      Assembly ClientAssembly;
      ClientAssembly = Assembly.Load("movieclass");
      // Get the desired Type in the Assembly
      Type myType = ClientAssembly.GetType("Movies");
      // Get the FieldInfo for private field "Director".
      // Specify nonpublic field and instance class.
      // Accessing private members requires Reflection 
      // Permission.
      FieldInfo myFieldInfo = myType.GetField("Director", 
            BindingFlags.NonPublic | BindingFlags.Instance);
      if (myFieldInfo !=null) 
         Console.WriteLine("Field: {0} Type: {1}", 
                           myFieldInfo.Name, 
                           myFieldInfo.FieldType);
      {
         // output: Field: Director  Type: System.String
      } else {
         Console.WriteLine("Could not access field.");
      }
   }
}

Creating a Permission Set

Follow these steps to create the permission set:

  1. Right-click the Permission Sets folder under Machine policy and select New.

  2. Enter Reflection as the name of the set and provide a description.

  3. On the next screen, select Security from the Available Permissions. Check Enable Assembly Execution and Allow Evidence Control from the permission settings window.

  4. Select Reflection from the Available Permissions. Check Grant Assemblies Unrestricted Permission to Discover Information About Other Assemblies.

  5. Click Finish and the permission set named Reflection appears in the left pane.

You now have a permission set that allows an assembly the rights associated with the Reflection permission.

Creating a Code Group

Follow these steps to create the code group:

  1. Right-click the All_Code node—under Machine-Code Groups—and select New.

  2. Enter My_Computer_Url as the name of the group and provide a description.

  3. For the Condition Type, choose URL.

  4. For the URL, enter file://c:/cas/*. This specifies the folder from which an assembly must originate in order to satisfy the Url evidence.

  5. Next, assign the new Reflection permission set to the code group and click Finish.

  6. Click the new code group in the left pane and select Edit Code Group Properties. Check the option This Policy Level Will Only Have the Permissions from the Permission Set Associated with the Code Group.

The final step is necessary to make this example work. Setting this Exclusive option tells .NET to assign only the permissions of the new code group to any code found in the specified directory path. If this option is not set, the code is evaluated against all Machine level code groups and receives permissions from all whose evidence it matches. Also, note that code not located in the specified subdirectory is unaffected by the new code group and receives the default Machine policy permissions.

Testing the New Code Group

To demonstrate the effects of this new code group, compile the program and store a copy of configtest.exe in C: and C:CAS. Run both copies from the command line, and they should succeed. Now, use the Configuration tool to change the permission set for the My_Computer_Url code group from Reflection to Internet. When you now run the program from the CAS subdirectory, it fails because the Internet permission set does not include the Reflection permission. The program still runs fine from C:, because its permissions come from the other code groups in the Machine policy.

This is a simple example, but it illustrates the core principle behind Code Access Security of assigning permissions to an assembly based on an analysis of its evidence. In this case, identical assemblies receive different permissions based on the directory in which the code resides. The evidence is a Url specified directory, but could just as easily be an Internet site, the assembly's Strong Name, or its security zone.

Determining the Permissions Granted to an Assembly

It is not easy to empirically determine the permissions that are granted to a given assembly. You must gather the evidence for the assembly, determine the security policies in effect from the XML policy files, and evaluate the evidence in light of the policies. Fortunately, the .NET Configuration tool and Caspol utility can perform this evaluation for you.

To use the Configuration tool, right-click the Runtime Security Policy folder and select Evaluate Assembly from the context menu. Use the Browse button to locate the assembly in the file directory. Select View Permissions Granted to Assembly, and click the Next button to display the individual permissions. For c:casconfigtest.exe from the preceding example, Security and Reflection are listed. For c:configtest.exe, Unrestricted is listed.

Caspol is run from the command line. Among its numerous options is –rsp, which resolves permissions for an assembly. To determine permissions for the configtest assembly, enter this command:

C:>caspol –rsp c:casconfigtest.exe

Output is in an XML format and correctly includes Security and Reflection, as well as two identity permissions: UrlIdentity and ZoneIdentity (MyComputer). The identity permissions can be ignored because specific permissions are provided.

You can also use caspol to understand why an assembly qualifies for its permissions by identifying the code groups that the assembly belongs to. Enter the previous command, except replace the –rsp option with the –rsg (resolve code group) option:

C:>caspol –rsg c:casconfigtest.exe

This output shows that a code group uses Url evidence to grant its permission set (Reflection) exclusively to any assembly in the specified directory. Because our assembly is in that directory, the Security and Reflection permissions must come from this code group:

Level = Enterprise
Code Groups:
1.  All code: FullTrust

Level = Machine
Code Groups:
1.  All code: Nothing
   1.1.  Zone - MyComputer: FullTrust
   1.6.  Url - file://C:/cas/*: Reflection (Exclusive)

Level = User
Code Groups:
1.  All code: FullTrust

Note that you can view the contents of the Reflection permission set by executing

C:>caspol –m -lp

The serialized XML output lists the permissions associated with all permission sets at the machine policy level—including the Reflection permission set.

Determining the Permissions Required by an Assembly

One of the objectives of designing an effective security policy is to grant an assembly only those permissions it requires, and no more. The ease of using predefined permission sets should be resisted when it allows an assembly to access resources that it does not require. In the preceding example, the configtest assembly required only the capability to execute and access members of the Reflection namespace. To satisfy these narrow requirements, we created a custom permission set containing the Security and Reflection permissions. This custom permission set was clearly a better choice than the unrestricted permissions offered by Everything and FullTrust—the only predefined permission sets granting the Reflection permission.

Given the obvious advantage of using customized permission sets, the question becomes how an administrator identifies the minimum permissions required by an assembly. .NET offers two utilities for this purpose: PermView and PermCalc. PermView, which is discussed in the next section, displays the permissions explicitly requested—using attributes—by an application; PermCalc evaluates an assembly and produces serialized XML output of the required permission classes. It is run from the command line, as shown here:

C:>permcalc c:casconfigtest.exe

Evaluating an assembly to determine its required permissions is a difficult task and not always guaranteed to provide accurate results. In fact, when the utility is run against configtest, it displays FileIOPermission—but not Reflection—as a required permission. However, if we add an attribute (described next) to the code requesting the Reflection permission, the utility correctly displays this as a required permission. As a rule, the only way to be certain which permissions an assembly requires is to declare them in the code—our next topic.

Requesting Permissions for an Assembly

Including permission attributes in code is referred to as declarative security, and it serves two primary purposes: When applied at the assembly level, the attribute serves to inform the runtime which permissions the assembly requires—or does not require—to function; when applied at the class or method levels, it protects resources by demanding that callers possess specific permissions to access a resource through the current assembly. Attributes used for this latter purpose trigger or modify a stack walk that verifies all assemblies in the call chain can access the called assembly. We'll examine this in “Programmatic Security” on page 715. For now, the focus is on how to use assembly-level attributes to adjust security within a program.

Although the term request is broadly used to describe the use of permission attributes, it's better described as a way for an assembly to publish (in metadata) its permission requirements. The effectiveness of including permission attributes in code is governed by three rules:

  • An assembly cannot receive any more permissions than are defined by the security policy rules—no matter what it “requests.”

  • Although requests cannot cause code to receive extra permissions to which it is not entitled, it can influence the runtime to deny permissions.

  • An assembly receives permissions whether it requests them or not. The example shown in Listing 15-3 does not include a permission attribute, but it receives all the permissions that it qualifies for based in the evidence it provides the runtime.

Despite the fact that you do not have to include permission requests in your code, there are important reasons for doing so:

  • It allows PermView to display the permissions required by an assembly and also improves the reliability of PermCalc. This provides a quick way for administrators to collect assembly requirements and design an appropriate security policy.

  • Permission attribute information is stored in the assembly's manifest where it is evaluated by the CLR when loading the assembly. This permits the runtime to prevent an assembly from executing if it does not have the requested permissions. In most cases, this is preferable to having the program begin execution and then shut down unexpectedly because it has inadequate permissions.

  • It enables code to specify only those permissions it needs. So why are extra permissions a problem if you don't use them? Because the code may have a bug, or exploitable feature, that malicious calling code can use to take advantage of the “extra” permissions.

For a full trust environment where code has unrestricted permissions—typically in-house applications—security is not a significant factor, and it may not be necessary to apply security attributes. However, if you operate in a security-conscious environment or are creating components for use by other software, you should include security attributes as a means of providing self-documenting security.

How to Apply a Permission Attribute

Because the permission attribute is to have assembly scope, it is declared in the first line of code following the using statement(s):

[assembly : PermissionAttribute(
               SecurityAction.membername, 
               PermissionAttribute property)
] 

Let's examine its construction:

Assembly

Indicates the attribute has assembly scope.

PermissionAttribute

The permission class being requested.

SecurityAction

An enumeration describing the type of permission request. Three values can be assigned to the assembly scope:

RequestMinimum—. The minimum permissions required to run.

RequestOptional—. Permissions the code can use but does not require. This implicitly refuses all other permissions not requested. Be careful with this, because it is not obvious that a request for one permission causes all other non-requested permissions to be denied.

RequestRefuse—. Permissions that should not be assigned to the assembly even if it qualifies for them.

The final argument to the constructor sets a property of the permission attribute class to a value that describes the specific permission requested. The following example should clarify this.

Testing a Permission Attribute

To demonstrate the effects of applying a permission attribute, let's add this statement to the source for configtest, shown in Listing 15-3:

//place on line preceding: class ClassEvidence 
 [assembly : ReflectionPermission(
              SecurityAction.RequestMinimum, 
              Flags=ReflectionPermissionFlag.TypeInformation)
]

The ReflectionPermission attribute class is used to request the minimum permissions required to run. The second parameter, TypeInformation, is an enumeration property of the class that permits reflection on nonvisible members of a class. (Recall that the sole purpose of this code is to use reflection in order to access a private field on a class.)

Compile and run the code from the C: root directory. Because applications run on the local machine have unrestricted permissions, the program runs successfully. Let's see what happens if the second parameter does not specify a permission that enables access to a private field. To test this, change the enumeration value to ReflectionEmit—an arbitrary property that only permits the assembly to emit metacode. When run with this parameter, the assembly again succeeds because it continues to receive unrestricted permissions on the local machine. As a final test, change the first parameter to SecurityAction.RequestOptional. This causes the assembly to fail, because only the requested ReflectionEmit permission is granted to it. Table 15-3 summarizes how content of the permission attribute affects the assembly's operation.

Table 15-3. Effect of Changing Permission Attribute on Assembly

Attribute Parameters

Result

Explanation

SecurityAction.RequestMinimum

Flags = ReflectionPermission-
Flag.TypeInformation

Succeeds

RequestMinimum ensures that assembly is granted all permissions determined by security policy. Includes Reflection.

TypeInformation permits reflection on members that are not visible.

SecurityAction.RequestMinimum

Flags = ReflectionPermission-
Flag.ReflectionEmit

Succeeds

RequestMinimum ensures that assembly is granted all permissions determined by security policy. Includes Reflection.

ReflectionEmit parameter does not specify the permission required by the assembly.

SecurityActionRequestOptional

Flags = ReflectionPermission-
Flag.ReflectionEmit

Fails

RequestOptional causes all non-requested permissions to be denied.

Because ReflectionEmit does not provide permission to access a non-public field, the assembly fails.

Programmatic Security

The .NET Configuration tool provides a broad stroke approach to defining security for a computing environment. In most cases, this is satisfactory. If two assemblies come from the same security zone, it usually makes sense to grant them the same permissions. However, suppose you are developing components for clients with an unknown security policy, or you are using third-party components whose trustworthiness is unknown. In both cases, programmatic security offers a way to enforce security specifically for these components.

Programmatic security is implemented by using .NET permission classes to control and enforce security on a calling assembly or a called assembly. This is an important point: The calling assembly can override permissions granted by the CAS and prevent the assembly from accessing a resource; conversely, a called assembly can refuse to perform an operation unless the calling assembly—or assemblies—has permissions it requires. The key to implementing programmatic security is a mechanism known as a stack walk.

Stack Walk

As mentioned earlier in the chapter, a stack walk refers to steps the CLR follows to verify that all methods in a call stack have permission to perform an operation or access a system resource. This ensures that an immediate client with the proper permissions is not called by malicious code further up the stack that does not have permission.

As shown in Figure 15-10, a stack walk is triggered when code invokes a permission's Demand method. This method is inherited from the IPermission interface (refer to Figure 15-7), which includes other methods such as Intersect and Union that allow permission objects to be logically combined.

Invoking the Permission.Demand method triggers a stack walk

Figure 15-10. Invoking the Permission.Demand method triggers a stack walk

The Demand method is used extensively throughout the .NET Framework to ensure that applications requesting operations such as file I/O or database access have the proper permissions. Although most calls to the Framework Class Library (FCL) are protected in this way, it can still be useful to demand a stack walk within your code. You may want to apply even stricter permission requirements than the FCL demands, or you may want to ensure that a lengthy operation has all the required permissions before it is launched.

To provide a simple illustration, let's create a component that returns information about a requested movie from our Films database (see Chapter 11). Listings 15-4 and 15-5 contain the component and client code, respectively. For brevity, the ADO.NET code is excluded, but is available as downloadable code.

Because the SqlClient permission is required to access a SQL Server database, the component includes the following code to ensure that the calling assembly has this permission:

SqlClientPermission sqlPerm= 
      new SqlClientPermission(PermissionState.Unrestricted);
sqlPerm.Demand();  // Trigger stack walk

The code simply creates an instance of the desired permission class and calls its Demand method. There is no limit on the number of permission objects that can be created and used to demand a stack walk. However, it is an expensive process and should be used judiciously.

Example 15-4. Component to Illustrate Code Access Security

// filmcomponent.cs  (.dll)
using System.Data.SqlClient;
using System;
namespace moviecomponents
{
   public class MovieData
   {
      // Return MovieProfile object
      public static MovieProfile GetMovie(string movietitle)
      {
         // Return null if movie not found
         MovieProfile mp = null; 
         // Demand SqlClient permission from methods on call stack
         SqlClientPermission sqlPerm= new 
               SqlClientPermission(PermissionState.Unrestricted);
         sqlPerm.Demand();
         //*** Code here to query database for movie information
         //*** Requires SqlClient permission
         return mp;
      }
   }
   public  class MovieProfile
   {
      private string pTitle;
      private int pYear;
      private int pRank;
      private string pOscar;
      public MovieProfile(string title, int year, 
                          int afiRank, string bestFilm)
      {
         pTitle= title;
         pYear = year;
         pRank = afiRank;
         pOscar = bestFilm;
      }
      // Readonly properties
      public string Title
      {
         get{return pTitle;}
      }
      public int Year
      {
         get{return pYear;}
      }
      public int Ranking
      {
         get{return pRank;}
      }
      public string BestPicture
      {
         get{return pOscar;}
      }
   }     // class
}       // namespace

Example 15-5. Assembly to Access Film Component

// movieclient.cs  (.exe)
using System;
using moviecomponents;
namespace MovieClient
{
   class MovieMgr
   {
      static void Main(string[] args)
      {
         string myMovie;
         if(args.Length>0) 
         {
            myMovie= args[0];
            // Call component to fetch movie data
            MovieProfile mp = MovieData.GetMovie(myMovie);
            if(mp==null)
            {
               Console.WriteLine("Movie not found");
            }
            else
            {
               Console.WriteLine("Year:{0} AFI Rank: {1}",
                                 mp.Year, mp.Ranking);
            }
         }
      }
   }    // class
 }     // namespace

This code can be tested from the command line. Compile both the component and client. Then, run the client by passing it the movie name as a parameter:

C:>csc /t:library filmcomponent.cs
C:>csc /r:filmcomponent.dll movieclient.cs
C:>movieclient casablanca
Year: 1942  AFI Rank:2

Because code running on a local machine has unrestricted permissions (by default), the stack walk verifies that movieclient has the necessary permission. To make the example more interesting, let's run the client when it does not have the SqlClient permission. The easiest way to remove this permission is by adding an assembly-level permission attribute that explicitly refuses the permission. You do this by passing the SecurityAction.RequestRefuse enumeration to the constructor of the permission attribute you do not want granted to the assembly. To refuse the SqlClient permission, place this statement before the namespace statement in the client code:

[assembly : SqlClientPermission(SecurityAction.RequestRefuse)]

Building and running the modified SqlClient results in a security exception being thrown.

Stack Walk Modifiers

Just as a called assembly has the rights to initiate a stack walk, the objects on the call stack have the right to modify the behavior of the stack walk. For example, they can cause the walk to fail, or stop the walk at the current object so that it does not check objects further up the stack. These capabilities have significant security implications: inducing a stack walk failure is a way for a client to restrict what actions a called assembly may perform; and terminating a stack early can improve performance by eliminating unnecessary steps in a repeated stack walk.

The ability to modify a stack walk is provided by methods in the IStackWalk interface (refer to Figure 15-7) that all permissions must implement. This interface defines the Demand method that initiates a stack walk, and three other methods, Assert, Deny, and PermitOnly, that modify a stack walk's normal operation. Assert stops the stack walk at the current stack frame; Deny causes the walk to fail; and PermitOnly specifies the only permission that an object will allow a stack walk to verify.

To demonstrate these methods, let's modify the code in Listing 15-5 to implement each method. First, we add namespaces so that we can access IStackWalk and the permission:

using System.Security;
using System.Data.SqlClient;
using System.Security.Permissions;

Then, the code is changed to create an IStackWalk object that is set to the permission being checked by the stack walk—in this case, SqlClientPermission:

myMovie= args[0];
//
IStackWalk stackWalker;
stackWalker = new 
      SqlClientPermission(PermissionState.Unrestricted);
stackWalker.Deny();      // Deny use of SqlClient by GetMovie
// Call component to fetch movie data
try{ 
   MovieProfile mp = MovieData.GetMovie(myMovie);

In our first example, we call the permission's Deny() method that causes the stack walk's verification of the SqlClient permission to fail. Filmcomponent cannot access the database and throws an exception.

Now, replace the call to Deny with a call to Assert. Assert prevents the stack walk from continuing further up the code stack—unnecessary in this case because there is no other object on the stack. The stack walk succeeds because movieclient has the required permission, and database access is permitted.

Core Note

Core Note

On the surface, the use of Assert seems to undermine the reason for a stack walk—to ensure that all objects on the call stack have a required permission. To make sure the method is not used to hide potentially malicious code on the call chain, .NET requires that the asserting code have a special security permission before it can assert; then, as added protection, it triggers a stack walk when code makes an assertion that verifies that all code above it has the asserted permission.

The final way to modify the stack walk is by using the PermitOnly method to indicate specific permissions that the code is willing to let the called assembly use. In the current example, the calling assembly has unrestricted permissions; the component filmcomponent has need for only the SqlClient permission. By using PermitOnly to specify this permission, movieclient thwarts any stack walks that attempt to verify other permissions.

An assembly may be required to call multiple components, each with its own permission requirements. You may need to permit SQL access for one and file I/O for another. If you call PermitOnly a second time to override the first call, an exception is thrown. Instead, you must clear the effects of the first call by calling CodeAccessPermission's static RevertPeritOnly method; then, you call PermitOnly with a new permission. The following segment could be added to our code to remove SqlClient as the only allowed permission, and replace it with the Reflection permission.

// Remove effects of previous PermitOnly call
CodeAccessPermission.RevertPermitOnly();
stackWalker = new ReflectionPermission(
      ReflectionPermissionFlag.TypeInformation);
// Allow stack walk to verify Reflection permission only
stackWalker.PermitOnly();
// Now call a method that requires Reflection

Declarative Security Using Permission Attributes

The security technique just described that explicitly calls a permission's Demand method to invoke a stack walk is referred to as imperative security. An alternative form of security that uses attributes to achieve the same effect is known as declarative security. We looked at one version of this in the earlier discussion of how to use permission attributes to request permissions. In this section, we'll see how the other form of declarative security is used to attach attributes to classes or methods to verify that clients have the necessary permissions to use current code.

The easiest way to explain declarative security is to compare it with its imperative counterpart. Here are the statements from Listing 15-4 that trigger a stack walk. Below it is an equivalent attribute declaration that results in the same stack walk.

// Imperative Security
SqlClientPermission sqlPerm= new 
      SqlClientPermission(PermissionState.Unrestricted);
sqlPerm.Demand();  

// Declarative Security 
[SqlClientPermission(SecurityAction.Demand)]
public static MovieProfile GetMovie(string movietitle){

The syntax for the attribute constructor is straightforward. It consists of the permission attribute class name with a parameter specifying a SecurityAction enumeration member. Depending on the permission type, there may also be a second parameter that specifies a property value for the permission class.

The most interesting feature of the attribute declaration is its SecurityAction parameter that specifies the action caused by the attribute. Its three most important members and their uses are the following:

  • DemandTriggers a stack walk at runtime that requires all callers on the call stack to have a specified permission or identity.

  • LinkDemandOnly the immediate caller is required to have a specified permission or identity. This check occurs during loading and eliminates the performance penalty of using a stack walk. However, because all objects on the call stack are not checked, this should only be used when you are certain the call stack is secure.

  • InheritanceDemandRequires that any subclass inheriting from the current code has required security permissions. Without this check, malicious code could use inheritance or the capability of overriding protected methods, for its own purposes. This check occurs during loading.

The final two enumeration members result in checks that occur during loading and have no comparable statements that can be executed at runtime. However, the use of demand security to trigger a stack walk is common to both and leaves the developer with the decision of which to use. In where the component knows which permissions it needs at compile time, declarative security is recommended. Its syntax is simpler, and it provides a form of self-documentation. Also, declarative security information is placed in an assembly's manifest where it is available to the CLR during loading. As a rule, the sooner the CLR has information about code, the more efficiently it can operate.

Imperative security is recommended when variables that affect security are unknown at compile time. It's also easier to use when more granular security is required: attributes usually apply to a class or method, whereas demand statements can be placed anywhere in code.

Application Deployment Considerations

The purpose of this section is to take a broad look at the issues that should be considered when deciding on a strategy for installing your application. Much of the discussion centers on practical issues such as how deployment is affected by the use of public or private assemblies, how configuration files are used to tell the CLR where to search for an assembly, and how configuration files ease the problems of distributing new versions of an assembly.

Microsoft Windows Deployment: XCOPY Deployment Versus the Windows Installer

Almost always included in the list of the top .NET features is the promise of using XCOPY deployment to install your application on client machines. The idea is to create a simple script that uses the XCOPY utility to copy your assemblies and resource files to the specified directories on the client's machine. It is a terrific idea and works—if you have an application with only a few assembly files that can be stored in a local directory. However, there are a lot of installation scenarios where XCOPY does not work: It cannot be used to register a COM component in your application, and it cannot be used to store an assembly in the GAC. In addition, for a professional application, XCOPY does not provide the well-designed interface that customers expect.

The most popular alternative to XCOPY is the Microsoft Windows Installer that ships with most of its operating systems and is also available as a separate download. The installer is an installation service that processes files having a special format (.msi) and performs install operations based on their content. An .msi file is referred to as a Windows Installer Package, and an install can contain more than one of these files.

The easiest way to create an MSI file for your application is using Visual Studio .NET. It has a wizard that steps you through the process. If your application requires special Code Access Security policies, use the Configuration tool to create an install package. To do so, right-click the Runtime Security Policy node and select Create a Deployment Package. The ensuing wizard steps you through the process. You can also create a custom installer by creating a class that inherits from the System.Configuration.Install.Installer class and overrides its Install and Uninstall methods.

Core Note

Core Note

.NET 2.0 and Visual Studio 2005 introduce a ClickOnce technology that is designed to simplify the installation and update of Windows applications. The idea is to create the application and use VS.NET to “publish” it to a File or Web Server. The location is provided as a URL to users who link to a Web page that provides install instructions. Included with the application is a manifest that describes the assembly and files that comprise the application. This information is used to determine whether an update is available for the application. The update process can be set up to automatically check for updates each time the application is run, or only run when requested.

ClickOnce works for straightforward installations that do not need to access the registry or install assemblies in the GAC. It is also targeted for the Microsoft Windows environment, as it contains shortcuts and an uninstall feature used by Windows. Although VS.NET is the easiest way to publish an application for installation, it can be done manually.

Deploying Assemblies in the Global Assembly Cache

.NET supports two assembly deployment models: private and shared. A private assembly is identified by its file name—without the extension—and is stored locally with other assemblies that it references or that reference it. Shared assemblies are stored in the Global Assembly Cache. Although this is a convenient way to make an assembly available to multiple applications, it does present an installation problem. Recall from the earlier discussion that an assembly cannot be copied into the GAC; instead, a special tool is required to install or “register” it. During code development, the GACUtil.exe utility is used for this purpose. However, this tool is not included with the end-user version of the .NET Framework redistributable package, so there is no assurance that it will exist on the user's machine. Instead, use the Windows Installer.

Deploying Private Assemblies

The easiest way to deploy an application with multiple private assemblies is to place all of the assemblies in the same directory. This home directory, referred to as the application's base directory, is the first place the CLR looks when trying to locate an assembly. However, using a single directory prevents assemblies from being grouped into folders that logically describe their purpose. For example, an application may have a set of assemblies dedicated to graphics and another dedicated to database access. Placing these in Graphics and Data subfolders represents a more logical and easier-to-maintain code deployment.

To see how to set up a meaningful directory structure for an application, let's first look at how the CLR locates private assemblies. This discovery process, called probing, begins with the application's base directory. If the assembly is not located there, it searches for an immediate subdirectory having the same name as the target assembly. For example, if the assembly is myClass, it looks first for the directory myClass.dll and then myClass.exe. The CLR loads the assembly from the first directory in which it locates it.

In our case, we want to use a directory structure that is not based on the name of the assemblies. To force the CLR to extend its probe into other folders, we must add a special <probing> element to the application's configuration file. For an application named myApp.exe, we create a configuration file named myApp.exe.config and store it in the application's base directory. Included in the file is a <probing> tag with a privatePath attribute that specifies the subdirectories (below the base directory) for the CLR to search.

<configuration>
   <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
         <probing privatePath="graphics;data" />
      </assemblyBinding>
   </runtime>
</configuration>

By default, the specified search paths are relative to the application's base directory. You can also specify an absolute path, but the path must specify a folder below the application's base directory.

The application configuration file in this example is rather simple, and you may choose to build it manually. However, because the file often contains other configuration elements, manual manipulation can quickly become an unwieldy task. As an alternative, you can use the same Configuration tool described in the CAS discussion. It has an Applications folder that can be opened and will lead you through the steps to create a configuration file for a selected assembly. One of its more useful and instructive features is an option to view a list of other assemblies that the selected assembly depends on.

Using CodeBase Configuration

A <codebase> element provides another way to specify the location of an assembly. It differs from the <probing> element in that it can specify any directory on the machine, as well as a location on a file or Web server across a network. The CLR uses the URI information provided by this element to download an assembly the first time it is requested by an application. Notice how <codebase> is used in the following sample configuration file.

The <codebase> element, along with a companion <assemblyIdentity> element, is placed inside the <dependentAssembly> block. The <assemblyIdentity> element provides information about the assembly the CLR is attempting to locate: its simple name, public key token, and culture. The <codebase> element provides the assembly's version number and location where it can be found. If the assembly is not strongly named, the version information is ignored.

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
     <dependentAssembly>
        <assemblyIdentity name="movieclass"
           publicKeyToken="1F081C4BA0EEB6DB"
               culture="neutral" />
        <codeBase version="1.0.0.0"  
                   href="http://localhost/scp/movieclass.dll" />
     </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

The href can also refer to the local file system:

<codeBase version="1.0.0.0" 
                  href="file:///e:movieclass.dll"  />

Using a Configuration File to Manage Multiple Versions of an Assembly

One of the advantages of using a strongly named assembly is the version binding feature it offers. When Assembly A is compiled against Assembly B, A is bound to the specific version of B used during the compilation. If Assembly B is replaced with a new version, the CLR recognizes this at load time and throws an exception. It is a good security feature, but can make updates to B more time-consuming because all assemblies dependent on it must be recompiled.

A configuration file can be used to override version binding by instructing the runtime to load a different version of the assembly than was originally bound to the calling assembly. This redirection is achieved by adding a <bindingRedirect> element that specifies the original version and the new version to be used in place of it. Here is how the previous configuration file is altered to have a newer version of the movieclass assembly loaded:

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly> 
        <assemblyIdentity name="movieclass"
           publicKeyToken="1F081C4BA0EEB6DB"
               culture="neutral" />
        <bindingRedirect oldVersion="1.0.0.0" 
                         newVersion="2.0.0.0"/>

        <codeBase version="2.0.0.0"  
                   href="http://localhost/scp/movieclass.dll" />

      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

Use of the <bindingRedirect> element inside an application configuration file offers a flexible and practical approach to matching applications with new or older versions of a component. For example, both versions of the component can run side by side on the same machine. One application can continue to use the older version, whereas the configuration file of a second application is modified to direct it to the newer component. Moreover, if the second application encounters problems with the new component, the <bindingRedirect> element can be removed and the application will revert to using the original component.

Assembly Version and Product Information

As a final note on deployment, it is good practice to include assembly-level attributes to your assembly to provide useful information to clients that want to examine the properties of your .exe or .dll file. As an example, the attributes in the following code yield the properties shown in Figure 15-11:

using System;
using System.Reflection;
[assembly:AssemblyVersion("2.0.0.0")] 
[assembly:AssemblyCompany("Acme Software")]
[assembly:AssemblyCopyright("Copyright (c) 2005 Stephen Perry")]
[assembly:AssemblyTrademark("")]
// Set the version ProductName & ProductVersion fields
[assembly:AssemblyProduct("Core C# Examples")]
[assembly:AssemblyInformationalVersion("2.0.0.0")]
[assembly:AssemblyTitle("Core C# movieclass.dll")]
[assembly:AssemblyDescription("This is a sample C# class")]
//
public class Movies
{
// ... Remainder of code
Attributes provide information identifying an assembly

Figure 15-11. Attributes provide information identifying an assembly

Summary

There is more to developing an application or component than simply implementing code to handle a specific task or set of tasks. Ancillary issues such as code correctness, adherence to code standards, code efficiency, security, and code deployment must be addressed. This chapter has presented an overview of tools and approaches available to handle these issues in .NET.

A useful tool for determining how your code meets .NET coding standards is FxCop. It's included with the .NET SDK and evaluates an assembly against a set of rules that define good coding practice. You can add custom rules and disable rules that don't apply.

To secure an application, .NET employs a concept known as Code Access Security. Unlike role- or user-based security—which .NET also supports—CAS restricts what resources code can access. When an assembly loads, .NET gathers information about its identity known as evidence. It evaluates the evidence against security policies and maps it into a set of permissions that are granted to the assembly. A security administrator typically configures the security policy using the .NET Configuration tool, although a command-line utility is also available. Security can also be implemented in code. Permissions are nothing more than classes, and can be used by developers to request permissions for an assembly and demand that calling assemblies have the necessary permissions to access a secure resource.

The final step in developing an application is settling on a deployment strategy. In .NET, this can be as easy as creating an install based on copying files to a machine (XCOPY deployment); alternatively, MSI files can be created for use with the Microsoft Windows Installer. One of the key install decisions is how to deploy assemblies. Private assemblies are placed together in a common directory path; shared assemblies are stored in the Global Assembly Cache. In addition, a configuration file can be set up to instruct the runtime to search for assemblies in directories on the local computer or a remote Web server located across a network.

Test Your Understanding

1:

Indicate whether the following are true or false:

  1. A strongly named assembly must be stored in the GAC.

  2. When compiling an application, the CLR automatically searches the GAC for a referenced assembly.

  3. When loading an assembly, the CLR automatically searches the GAC for a referenced assembly.

  4. A public key token must exist in the manifest of an assembly referencing a strongly named assembly.

2:

You are developing a component for a class library and want to follow good coding practices. Evaluate the assembly with FxCop. The output includes the following message. What is missing from your code?

TypeName= "AssembliesShouldDeclareMinimumSecurity" 

3:

What are the four parts of an assembly's strong name?

4:

How do you assign a version number to an assembly? How are default values for the build number and revision determined?

5:

What three methods are used to modify a stack walk? Describe the role of each.

6:

What is delayed signing, and why is it used?

7:

Identify each of the following: predefined permission, named permission set, and security zone.

8:

Indicate whether the following statements about assembly deployment are true or false:

  1. Only a strongly named assembly can be placed in the GAC.

  2. The <probing> element must specify a directory below the application's base directory.

  3. The <codeBase> element is used to redirect the CLR to use a newer version of an assembly.

  4. XCOPY deployment can be used to install shared assemblies.

9:

What element or elements need to be added to this application configuration file so that the application will access the new version 2.0.0.0 of this component, rather than the previous 1.0.0.0 version? Both the application and component reside in c:data.

<dependentAssembly>
   <assemblyIdentity name="movieclass"
      publicKeyToken="1F081C4BA0EEB6DB"
               culture="neutral" />   

   ---> Insert code here
</dependentAssembly>


[2] The manifest is a set of tables containing metadata that describes the files in the assembly.

[3] Not to be confused with the machine.config file that holds machine-wide configuration data.

[4] Assemblies based on the European Computer Manufacturers Association (ECMA) specifications for the Common Language Infrastructure (CLI) contain an ECMA public key. These include system.dll and mscorlib.dll. The key is actually a placeholder that is mapped to a key pair provided by the particular CLR installation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset