Two Different, Yet Similar Security Models

The .NET Framework provides two basic models for security: code access based security and role-based security. Code access security describes what the code has permission to do and to which resources it has access. Role-based security controls who has permission to run the code and what they are allowed to do. The CLR supports both of these models using a similar infrastructure. By understanding some of the concepts that are common to both models, you should be able to understand the models better individually.

Permissions

Three types of permissions exist:

  • Code access permission

  • Identity permission

  • Role-based security permission

Code Access Permission

Code access permission refers to a class derived from System.Security.CodeAccessPermission. This means that each permission class has a common set of methods: Demand, Assert, Deny, PermitOnly, IsSubsetOf, Intersect, and Union.

Each permission class designates a right to either access a particular resource or to perform a particular operation. All permissions are requested or demanded by the code; ultimately, the permission that is granted to the code is up to the CLR to decide. The CLR makes the decision to grant a particular permission based on evidence (which is assigned to the code when it enters the runtime environment) and policy (which is dictated by the user, machine, or enterprise policy files). Table 16.1 lists the code access permission classes that are available as part of the .NET Framework SDK.

Note

Evidence and policy are discussed later in this chapter.


Table 16.1. Code Access Permissions
Permission Class Name Namespace Right Represented
DirectoryServicesPermission .DirectoryServices Code access to DirectoryServices classes
DnsPermission .Net Domain Name System (DNS) Server access
EnvironmentPermission .Security.Permissions Environment variable access
EventLogPermission .Diagnostics Event log services access
FileDialogPermission .Security.Permissions File or directory access through a File dialog box
FileIOPermission .Security.Permissions File or directory access permissions
IsolatedStorageFilePermission .Security.Permissions Private virtual file system access and usage
MessageQueuePermission .Messaging Messaging access
OleDbPermission .Data.OleDb Database access using OLE DB
PerformanceCounterPermission .Diagnostics Performance counter access
PrintingPermission .Drawing.Printing Printer access
ReflectionPermission .Security.Permissions Metadata access through Reflection APIs
RegistryPermission .Security.Permissions Registry access
SecurityPermission .Security.Permissions SecurityPermissionFlag access
ServiceControllerPermission .ServiceProcess Service controller access
SocketPermission .Net Connection and Transport address access
SqlClientPermission .Data.SqlClient SQL database access
UIPermission .Security.Permissions User interface and Clipboard access
WebPermission .Net HTTP Internet resource access

Identity Permission

These permissions represent a means of identifying code by where it came from, who wrote it, and how much it is trusted. Identity permissions, such as code access permissions, are derived from System.Security.CodeAccessPermission. Table 16.2 lists the identity permissions that are part of the .NET Framework.

Table 16.2. Identity Permissions
Permission Class Name Namespace Identity Description
PublisherIdentityPermission .Security.Permissions Permission to act as the software publisher through a certificate.
SiteIdentityPermission .Security.Permissions Permission for the Web site from which the code originates.
StrongNameIdentityPermission .Security.Permissions Permission to access code with a strong name.
UrlIdentityPermission .Security.Permissions Permission for the URL from which the code originates.
ZoneIdentityPermission .Security.Permissions Permission for the zone from which the code originates. The zone is based on IE options.

Role-Based Permission

Role-based permission is based on a single class, PrincipalPermission. This class can be used to determine if a user has a specified identity or is a member of a specified role. A role can be part of the operating system (a member of a group for example), it can be completely specified by the application, or it can be a role that is integrated with COM+.

Type Safety

Type safety is not typically thought of as a security issue. However, type safety plays an important role in runtime security. Type-safe code accesses memory in a well-defined and allowable fashion. If the code can be verified as type safe, then it can be safely isolated from other code in the system or from other code in the same process. When code is verifiably type safe, it is guaranteed not to stray from its allocated memory space. To be backward compatible with legacy C code, C++ allows references to memory (pointers) to take on any value. With verifiably type-safe code, all memory access is tightly controlled and cannot cause other code in the system to crash or malfunction. PEVerify (in Program FilesMicrosoft Visual Studio .NETFrameworkSDKin) is a tool that verifies code for type safety. Ideally, you want to run this tool on your code and receive the following output:

All Classes and Methods in propertiescs.exe Verified

However, when PEVerify is run against code marked as unsafe as shown in Listing 16.1 the output of Listing 16.2 results.

Listing 16.1. Unsafe C# Code
unsafe static void StringAddress(string s)
{
    fixed(char *p = s)
    {
        Console.WriteLine("0x{0:X8} ", (uint)p);
    }
}

This simple code takes in a string and prints the address that is associated with the string. PEVerify finds numerous errors in this code. It reports numerous errors, most of which are in Listing 16.2.

Listing 16.2. Errors from PEVerify Against Unsafe Code
. . .
[IL]: Error: [unsafe.exe : CLRUnleashed.Unsafe::StringAddress] [local variable
 #0x00000000] ELEMENT_TYPE_PTR cannot be verified.
[IL]: Error: [unsafe.exe : CLRUnleashed.Unsafe::StringAddress] [offset 0x00000002] [opcode
 ldloc.1] initlocals must be set for verifiable methods with one or more local variables.
[IL]: Error: [unsafe.exe : CLRUnleashed.Unsafe::StringAddress] [offset 0x00000003] [opcode
 conv.i] [found objref 'System.String'] Expected numeric type on the stack.
[IL]: Error: [unsafe.exe : CLRUnleashed.Unsafe::StringAddress] [offset 0x0000000A] [opcode
 stloc.0] [found Int32] [expected address of Int16] Unexpected type on the stack.
. . .
[IL]: Error: [unsafe.exe : CLRUnleashed.Unsafe::StringAddress] [offset 0x00000011] [opcode
 conv.u4] [found address of Int16] Expected numeric type on the stack.
16 Errors Verifying unsafe.exe

To make sense of these errors, you need to look at the IL code that the C# compiler generated. This is because PEVerify refers addresses and terms that are more readily apparent in from the IL code. ILDasm can provide the output shown in Listing 16.3.

Listing 16.3. ILDasm Listing for the Unsafe C# Code in Listing 16.1
.method private hidebysig static void  StringAddress(string s) cil managed
{
  // Code size       31 (0x1f)
  .maxstack  2
  .locals ([0] char* p,
           [1] string pinned CS$00000520$00000000)
  IL_0000:  ldarg.0
  IL_0001:  stloc.1
  IL_0002:  ldloc.1
  IL_0003:  conv.i
  IL_0004:  call       int32 [mscorlib]System.Runtime.CompilerServices. RuntimeHelpers:
:get_OffsetToStringData()
  IL_0009:  add
  IL_000a:  stloc.0
  IL_000b:  ldstr      "0x{0:X8} "
  IL_0010:  ldloc.0
  IL_0011:  conv.u4
  IL_0012:  box        [mscorlib]System.UInt32
  IL_0017:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)
  IL_001c:  ldnull
  IL_001d:  stloc.1
  IL_001e:  ret
}  // end of method Unsafe::StringAddress

Now you can see what PEVerify is complaining about:

  • ELEMENT_TYPE_PTR cannot be verified. This is in reference to the char* p local variable. As stated earlier, these pointers can take on any value; therefore, they cannot be verified.

  • Initlocals must be set for verifiable methods with one or more local variables. When a method is marked as unsafe, one of the side effects is that the local variables are not initialized. Because these variables can randomly take on any value, this cannot be verified. Even though this is in reference to ldloc.1, which is initialized with the previous two instructions, PEVerify flags this as an uninitialized variable.

  • Expected numeric type on the stack. Here an attempt is made to convert the string on the stack to an integer. This is a correct function, but it is not type safe.

  • Expected numeric type on the stack. This time the verification detects the address of an Int16 (char *p) on the stack rather than a simple numeric type that it was expecting. The tool is simply reporting the type mismatch.

PEVerify does a thorough job of verifying code even for this simple code example. If you are concerned about type safety, run your code against PEVerify. Note that VC++ with managed extensions is not verifiable; as such, PEVerify simply reports that it is a bad PE header.

If you stay away from unsafe code or at least isolate unsafe code, your code is verifiably type safe, which goes a long way toward making your code secure.

Note

All of the verification that PEVerify provides happens automatically as the JIT compiler converts your IL code to native code. You would normally only concern yourself with PEVerify if you were writing a compiler or if you just wanted to see if your code was verifiably type-safe.


Security Policy

Security policy is a set of rules governing the permissions that code is allowed to have. The administrator sets the policy and the runtime enforces it. All code is given evidence by the runtime host. This evidence, along with policy from the enterprise, machine, and user levels, determines what permissions are granted to code. Remember the following formula:

Evidence + Policy + Assembly Permission Requests = Permission Granted to Code

Security Policy Levels

Three levels of security policy are specified in XML configuration files. In order, they are as follows:

  • Enterprise—The security policy at this level is described by an XML file at %runtime install path%ConfigEnterprisesec.config, where %runtime install path% is where you have installed the runtime. Typically, this is something like WindowsMicrosoft.NetFrameworkv1.0.xxxx. This file contains a list of code groups, permission sets, and policy assemblies. When managed code is started, the runtime uses the policy that is described at this level as the starting point for deciding what permissions the code should be granted. A domain controller distributes this XML file throughout a domain of computers. A domain administrator then dictates the policy described in this file.

  • Machine—The security policy at this level is described by an XML file at %runtime install path%Configsecurity.config. The machine level is the second level of the policy. It is only possible for the machine policy to restrict the permission set that would be granted managed code after evaluating the policy at the enterprise level. The format of this file is identical to the format of the file that describes the enterprise policy.

  • User—An XML file describes the security policy at this level at %USERPROFILE%Application DataMicrosoftCLR SecurityConfigv1.0.xxxxSecurity.config. %USERPROFILE% points to Documents and Settingsyour name. It is only possible for the user policy to restrict the permission set that would be granted managed code after evaluating the policy at the machine and enterprise levels. The format of this file is identical to the format of the files that describe the machine and enterprise policies.

A fourth security policy level is set by the developer in the code. This is the AppDomain security policy level and it cannot be changed through any of the configuration files. After the assembly is built, the policy is fixed and users or administrators cannot directly change it.

Security Policy Administration Tools

Although each level of the policy is an editable XML file and it is possible to manually edit any of the files, it is strongly recommended that these files only be modified with either the .NET Framework Configuration tool (mscorcfg.msc) or the Code Access Security Policy tool (caspol.exe). Your first choice should be the .NET Framework Configuration tool, which is shown in Figure 16.1.

Figure 16.1. NET Framework Configuration tool.


If you frequently administer the security for .NET applications, place the .NET Framework Configuration tool on your desktop as a shortcut, as shown in Chapter 6, Figure 6.5. Look at the tree on the left pane of Figure 16.1. To illustrate using this tool to administer runtime security, the Runtime Security Policy node has been expanded. If you have the configuration tool up and running, you can expand the Runtime Security Policy node. You can see three nodes for Enterprise, Machine, and User policy. You can expand each of these nodes and modify the corresponding policy file in a consistent way that is much less likely to inadvertently corrupt a security policy file. The top-level node (Enterprise, Machine, and User) gives you a brief help description of that policy level as well as the full path to the policy file that will be modified by changing or adding information below that node.

If the .NET Framework Configuration tool does not provide you with enough flexibility, then use the Code Access Security Policy tool (caspol.exe). Caspol is a command-line utility that is primarily intended for scripting .NET security policy. Invoking caspol with no arguments provides a usage list that should give you an idea of the available options. Listing 16.4 shows some of the options that are available for caspol.

Listing 16.4. Caspol Usage
Usage: caspol <option> <args> ...

caspol -m[achine]
    Modifier that makes additional commands act on the machine level

caspol -u[ser]
    Modifier that makes additional commands act on the user level

caspol -en[terprise]
    Modifier that makes additional commands act on the enterprise level

caspol -cu
caspol -customuser <path>
    Modifier that makes additional commands act on the custom user level

caspol -a[ll]
    Set all policy levels as the active levels

caspol -ca
caspol -customall <path>
    Modifier that makes additional commands act on all levels as a custom user

caspol -l[ist]
    List code groups & permission sets

caspol -lg
caspol -listgroups
    List code groups
caspol -lp
caspol -listpset
    List permission sets

caspol -lf
caspol -listfulltrust
    List full trust assemblies

caspol -ld
caspol -listdescription
    List code group names and descriptions

caspol -ap
caspol -addpset { <named_xml_file> | <xml_file> <name> }
    Add named permission set to policy level

caspol -cp
caspol -chgpset <xml_file> <pset_name>
    Change named permission set in active level

caspol -rp
caspol -rempset <pset_name>
    Remove a named permission set from the policy level

caspol -af
caspol -addfulltrust <assembly_name>
    Add full trust assembly to policy level

caspol -rf
caspol -remfulltrust <assembly_name>
    Remove a full trust assembly from the policy level

caspol -rg
caspol -remgroup <label|name>
    Remove code group at <label|name>

caspol -cg
caspol -chggroup <label|name> {<mship>|<pset_name>|<flag>} +
    Change code group at <label|name> to given membership,
    permission set, or flags

caspol -ag
caspol -addgroup <parent_label|name> <mship> <pset_name> <flag>
    Add code group to <parent_label|name> with given membership,
    permission set, and flags

caspol -rsg
caspol -resolvegroup <assembly_name>
    List code groups this file belongs to
caspol -rsp
caspol -resolveperm <assembly_name>
    List permissions granted to this file

caspol -s[ecurity] { on | off }
    Turn security on or off

caspol -e[xecution] { on | off }
    Enable/Disable checking for "right-to-run" on code execution start-up

caspol -pp
caspol -polchgprompt { on | off }
    Enable/Disable policy change prompt

caspol -r[ecover]
    Recover the most recently saved version of a level

caspol -rs
caspol -reset
    Reset a level to its default state

caspol -f[orce]
    Enable forcing save that will disable caspol functionality

caspol -b[uildcache]
    Build the security policy cache file.

caspol -?
caspol /?
caspol -h[elp]
    Displays this screen


where "<mship>" can be:
  -allcode                 All code
  -appdir                  Application directory
  -custom <xml_file>       Custom membership condition
  -hash <hashAlg> {-hex <hashValue>|-file <assembly_name>}
                           Assembly hash
  -pub {-cert <cert_file_name> | -file <signed_file_name> | -hex <hex_string>}
                           Software publisher
  -site <website>          Site
  -strong -file <assemblyfile_name> {<name> | -noname}
          {<version> | -noversion}
                           Strong name
  -url <url>               URL
  -zone <zone_name>        Zone, where zone can be:
                                 MyComputer
                                 Intranet
                                 Trusted
                                 Internet
                                 Untrusted

where "<flag>" can be any combination of:
  -exclusive {on|off}
                           Set the policy statement Exclusive flag
  -levelfinal {on|off}
                           Set the policy statement LevelFinal flag
  -n[ame] <name>
                           Code group name
  -d[escription] <desc>
                           Code group description

Details of the command-line arguments for caspol can be found at ms-help://MS.VSCC/MS.MSDNVS/cptools/html/cpgrfcodeaccesssecuritypolicyutilitycaspolexe.htm.

Modifying Security Policy Evaluation

You can restrict policy as you move from the highest level (enterprise) to the lowest level (user). For most cases, this is true, although in two cases the preceding level has the ultimate say in the policy. This is if a LevelFinal or Exclusive attribute is attached to a code group.

The LevelFinal attribute excludes any policy from being evaluated below the current level. For example, if this attribute was applied to a code group at the machine level, then the user policy would have no effect on the code that is a member of the code group marked with LevelFinal. By marking a code group with LevelFinal, you guarantee that any assembly in this code group will never have less than a certain set of permissions. This attribute is put in place to ensure that lower levels will not be able to restrict code permissions to the level that it no longer runs properly.

Note

Application domain security policy is always evaluated regardless of the LevelFinal attribute.


The Exclusive attribute marks a code group as the sole determining factor for policy at the current level. Normally, if an assembly could belong to several code groups, the intersection of the permission sets determines policy. If a code group is marked as Exclusive, none of the other code group matches are considered for the current policy level. This attribute modifies the normal policy evaluation only for the current level. Policy is still evaluated as normal below the current level. If an assembly matches more than one code group that is marked Exclusive, the assembly will not be allowed to execute.

If you are using the .NET Framework Configuration tool to administer the security policy, you will not see the terms LevelFinal or Exclusive. Rather, you will see two check boxes with a textual description of these attributes. Figure 16.2 shows what the check boxes look like with the Configuration tool.

Figure 16.2. A code group viewed with the .NET Framework Configuration tool.


The check box labeled Policy Levels Below This Level Will Not Be Evaluated corresponds to the LevelFinal attribute. The other check box (This Policy Level Will Only Have the Permissions from the Permission Set Associated with This Code Group) in the group corresponds to the Exclusive attribute. To see the dialog box shown in Figure 16.2, select one of the code groups, right-click on the code group, and select Properties.

Code Groups

When the host of the runtime loads code, the host collects evidence about the code and presents this evidence to the runtime with the code to be executed. A code group is a set of code that has a membership criteria based on certain kinds of evidence. Code can be classified based on the following kinds of evidence:

  • Application Directory—The application's installation directory.

  • Hash—The cryptographic hash of an assembly. The hash uniquely identifies a particular assembly.

  • Publisher—The Authenticode signature of the signer of the code.

  • Site—The site of origin of the code, such as www.microsoft.com.

  • Strong Name—The complete strong name of the assembly. This includes the public key token, the version, and so forth. See Chapter 6 for a detailed description of strong names. Technically, the Strong Name condition includes the public key, and optionally the name and version portions of the assembly name. You cannot specify culture as part of the Strong Name membership condition.

  • URL—The URL of the origin of the code.

  • Zone—The zone of the origin of the code based on the zones defined by Microsoft Internet Explorer.

You can see what the membership criteria for a particular code group is by using the .NET Framework Configuration tool and selecting the properties of an existing code group. Doing so gives you an idea of how a code group is defined and how you can define your own code group. Currently, the membership criteria for the Restricted_Zone Code Group is based on zone evidence. You can view the other possible membership criteria by selecting the drop-down list, as shown in Figure 16.3.

Figure 16.3. Code group membership condition.


From this drop-down list, you can see the evidence that was presented earlier. You can start to imagine how to create a code group that is specific to your application or applications with membership criteria from the evidence shown in Figure 16.3.

After a code group has a membership criteria defined, a permission set can be assigned to that code group. The .NET Framework installs with the following permission sets:

  • FullTrust—This permission set is almost equivalent to turning security off. It allows unrestricted access to any of your computer's resources. Ensure that only assemblies that you fully trust receive this permission set.

  • SkipVerification—This permission set grants the right for software to bypass verification. As discussed earlier, type safety plays a critical role in the overall security of code. By running tools such as PEVerify, you can get an idea of what kinds of type safety problems your code could have. If you have already run PEVerify on your code, you might want to allow the code to skip the equivalent verification that happens at JIT time to allow your code slightly faster startup times.

  • Execution—This permission set includes the SecurityPermission with the SecurityPermissionFlag set to Execution.

  • Nothing—This permission set denies permission to any resource, including the permission to execute.

  • LocalIntranet—This permission set details a set of permissions that is appropriate for an application running from an intranet. More permission is given to this permission set because it is assumed that the intranet is more secure than the Internet.

Note

The default permissions are detailed in Table 16.3.


  • Internet—This permission set is more restrictive than the LocalIntranet permission set because the Internet is inherently insecure.

  • Everything—This permission set is much like the FullTrust permission set. This permission set includes all of the built-in permissions and gives unrestricted access to each. It does not include the SkipVerification flag as part of the Security permission.

Again using the drop-down list to see the possible permission set assignments, you can see a list that looks like Figure 16.4.

Figure 16.4. Code group permission set.


You can see the possible permission sets that are available by expanding the Permission Sets node of the tree in the left pane of the .NET Framework Configuration tool.

Note that you can define your own permission set—you are not restricted to the default permissions. Using the configuration tool, it is easy to create a new permission set. Select the top level Permission Sets node and click the Create New Permission Set hyperlink in the right pane of the tool and follow the instructions. As you start to define a new permission set, you will be asked to give the permission set a name and a description, as shown in Figure 16.5.

Figure 16.5. Start to Define a New Permission Set.


Notice that if you have another means to create the permission set, you can import the permission set description from an XML file.

After giving the permission set a name and description, you are prompted to add built-in permissions to the permission set, as shown in Figure 16.6.

Figure 16.6. Adding new permissions to a permission set.


Each permission that is added to the permission set has a different set of criteria. Figure 16.7 shows how adding a SecurityPermission presents you with a dialog box listing the possible SecurityPermissionFlags.

Figure 16.7. Adding a SecurityPermission to a new permission set.


On the other hand, adding a FileIOPermission prompts you for a list of files or directories and the associated access permission. Figure 16.8 shows the dialog box for adding a FileIOPermission.

Figure 16.8. Adding a FileIOPermission to a new permission set.


As shown in Figure 16.8, you must specify FileIOPermission by indicating that you only want read access to the D:Temp directory. By specifying read access to the directory, you are implicitly granting read access to all of the files contained within that directory.

Using the .NET Framework Configuration tool, you can select the Internet Zone code group and see that it has the Internet Permission Set assigned to it. (Select Properties by right-clicking on the Internet Zone code group or select the Internet Zone code group and click the Edit Code Properties hyperlink in the right pane.) Figure 16.9 shows what this looks like.

Figure 16.9. Internet Zone code group permission set properties.


You can select any of the permissions and look at the permission that is added to the permission set. For example, you can look at the Isolated Storage File permission by selecting this permission and then selecting the View Permission button. Figure 16.10 shows what the default Isolated Storage File permission looks like.

Figure 16.10. Internet Zone code group permission for Isolated Storage File.


Looking at the Internet Zone code group, you would expect the code to originate from the Internet. You can verify this by selecting the Membership Condition tab for the Internet Zone code group. You then see what is shown in Figure 16.11.

Figure 16.11. Internet Zone code group membership condition.


From this part of the properties of the Internet Zone code group, you can see that the membership condition is based on the zone evidence, and the particular zone that it comes from is the Internet. You just verified what you thought to be true all along. If you repeat this process of looking at each code group and then looking at the permissions that are part of the permission set assigned to that code group, Table 16.3 is the result.

Table 16.3. Default Permissions
Permission Local Intranet Permission Set Local Intranet Zone Code Group Internet Permission Set Internet Code Group and Trusted Zone Code Group
DnsPermission Unrestricted
EventLogPermission Instrument
FileDialogPermission Unrestricted Open (read-only access to a file)
FileIOPermission Read (custom code group to directory share of origin)
IsolatedStoragePermission AssemblyIsolationByUser (unrestricted quota) DomainIsolationByUser (10240 bytes)
PrintingPermission DefaultPrinting SafePrinting
ReflectionPermission ReflectionEmit, No Member Access, No Type Access
SecurityPermission Execution, Assertion Execution
UIPermission Unrestricted SafeTopLevelWindows, OwnClipboard
WebPermission Connect (custom code group to site of origin) Connect (custom code group to site of origin)

The My Computer Zone code group, the Microsoft Strong Name code group, and the ECMA Strong Name code group are not mentioned because they have the Full Trust permission set; therefore, they have unrestricted access to everything. Also not listed are the All Code code group or the Restricted Zone code group; these code groups are assigned the Nothing permission set and have no permission to access resources or perform operations.

Policy Assemblies

The security system uses policy assemblies during evaluation of a policy level. A policy assembly defines a custom security object such as a custom permission or a custom membership condition. You have already seen a couple custom code groups included under the Local Internet code group, the Internet Zone code group, and the Trusted Zone code group. These three code groups allow connection or read directory access to the site where the code originated. These custom code groups require special algorithms to determine membership. They are special or custom because they do not rely on the specific evaluation of a predetermined set of conditions for membership.

You can see the policy assemblies that are currently installed with the .NET Framework Configuration tool. By clicking on the Policy Assemblies node, you are presented with a screen that looks like Figure 16.12.

Figure 16.12. Policy assemblies from the .NET Framework Configuration tool.


Clicking on the View Policy Assemblies hyperlink or selecting the Properties menu item to view the policy assemblies (as opposed to using the Help screen) results in a listing of all policy assemblies that are installed on the computer. One such listing of policy assemblies is shown in Figure 16.13.

Figure 16.13. A list of the policy assemblies.


These policy assemblies will become important as you learn about defining custom permissions.

Principal

A principal represents the identity of a user who is logged into the system. Within the .NET Framework, three kinds of principals exist:

  • GenericPrincipal— This class of users can stand for any user/role mapping.

  • WindowsPrincipal— This class represents the Windows user.

  • Custom Principal—This class extends the notion of user and role by inheriting from IPrincipal.

Authentication

Authentication is the process of verifying the identity of a principal by looking at some credentials and validating them against a known authority. Many algorithms are available to authenticate a principal: basic, digest, Passport, NTLM, or Kerberos. One of the properties of a WindowsPrincipal is the type of authentication that is used and the principal's authentication status. After a principal is authenticated, the .NET Framework can reliably use the information that the Principal object provides to make decisions (role-based security).

Authorization

After a principal has been authenticated, authorization takes over and determines what this particular principal is authorized to do. This is the task of role-based security.

Role-Based Security

You can litter your code with hard-coded if-then statements for each possible user or group of users in your system and respond to users differently in the code. However, when you add or remove a user, you have to rewrite the code. This might be a good quick solution for a small system, but for any practical application, you need to find a different way. The .NET Framework abstracts the username and password (authentication) along with the context in which the user is running into a Principal and an Identity object.

Principal and Identity Objects

A Principal object in the .NET Framework implements the IPrincipal interface. Two classes in the .NET Framework implement the IPrincipal interface: the WindowsPrincipal class and the GenericPrincipal class. Both classes contain a property value that returns an Identity object (an object that implements the IIdentity interface). The WindowsPrincipal class returns a WindowsIdentity object from the Identity property. The GenericPrincipal class returns a GenericIdentity object from its Identity property. Principal and Identity objects can be likened to user and group concepts, where an Identity is more closely associated with the concept of a user. A Principal object encapsulates both an Identity and a role. The .NET Framework grants rights to a Principal based on its Identity or role.

At a minimum, an Identity object encapsulates a name and an authentication type. You will rarely see an Identity without an associated Principal; the two objects usually occur in pairs. Identity is either associated with a Windows user account (WindowsIdentity) or it is not (GenericIdentity and custom Identity objects).

Creating WindowsPrincipal and WindowsIdentity Objects

Listing 16.5 shows how to get the Identity associated with the current logged on user or the user who is executing the program that contains these APIs. The complete source for this file is in the Principal directory.

Listing 16.5. Printing WindowsIdentity Information
WindowsIdentity wi = WindowsIdentity.GetCurrent();
Console.WriteLine("My name is: {0} ", wi.Name);
Console.WriteLine("Authentication type: {0} ", wi.AuthenticationType);
Console.WriteLine("Anonymous: {0} ", wi.IsAnonymous);
Console.WriteLine("Authenticated: {0} ", wi.IsAuthenticated);
Console.WriteLine("Guest: {0} ", wi.IsGuest);
Console.WriteLine("System: {0} ", wi.IsSystem);
Console.WriteLine("My token is: {0} ", wi.Token);

The WindowsIdentity contains a static method that returns the current user. After you obtain the Identity object, you can obtain the name of the user and the authentication that were used to verify that the user really is who he says he is. You can see from the other properties whether the user is indeed authenticated, and in a general way, you can determine who the user is (System, Guest, and so on). This Identity is, as the name implies, based on the Windows OS idea of the user, and it relies on Windows to perform the authentication. By using WindowsIdentity, you can assume that the token is a Windows Security token. You can use this token to get even more information about the logged on user. Keith Brown wrote a wonderful COM component that dumps token information in the form of HTML. Using COM Interop (see Chapter 8, “Using COM/COM+ from Managed Code”), you can use this COM component to dump the token information. Listing 16.6 shows the lines of code used to call this COM component. This component writes information about the token in the form of HTML, which can be viewed with Internet Explorer.

Listing 16.6. Dumping Token Information
Console.WriteLine("My token is: {0} ", wi.Token);
// Dump the token information
ITokDump2 itd = new TokDumpClass();
StreamWriter file = new StreamWriter("tokendump.htm");
file.Write(itd.DumpThisToken((int)wi.Token, 0x177));
file.Close();
// Display the token info
Process.Start("tokendump.htm");

The HTML output looks something like Figure 16.14.

Figure 16.14. Windows token dump.


The converted TokenDump program is included in the TokenDump directory. The token is only a valid Windows NT Security token when you are assured that the Identity is a WindowsIdentity. For a GenericIdentity or for a custom identity, the token's meaning is up to the implementation.

After you dump the token information, look to see if a particular user is a member of a role. You can determine if a user is in a role by calling the IsInRole method that is part of the IPrincipal interface. The IsInRole method of IPrincipal takes a string that describes the particular role. The WindowsPrincipal class implements two overloaded methods. One of the overloaded methods takes an integer role identifier. The other takes one of the enumerated values for a typical role in a Windows system, WindowsBuiltInRole.

Impersonating

Using WindowsIdentity, you can impersonate another user on the system (assuming that you know that person's password). An overloaded constructor for WindowsIdentity takes a token as an argument. This token corresponds to the Windows NT Security Token—the same token that you learned how to pull information from in Listing 16.6 and Figure 16.14. No wrapper exists in the .NET Framework to obtain a Windows NT Security Token that corresponds to another user, so you have to revert to using the unmanaged calls. Use the Win32 function LogonUser. Listing 16.7 shows how to use this call to log in as another user. The complete source associated with this listing can be found in the WindowsImpersonation directory.

Listing 16.7. Log On As Another User
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool LogonUser(string lpszUsername,
                                    string lpszDomain,
                                    string lpszPassword,
                                    int dwLogonType,
                                    int dwLogonProvider,
                                    out IntPtr phToken);
. . .
// The Windows NT user token.
IntPtr token;
// Get the user token for the specified user, machine, and password using the unmanaged
 LogonUser method.
bool loggedOn = NativeMethods.LogonUser(
    // Username.
    impersonateUser.Text,
    // Computer name.
    impersonateDomain.Text,
    // Password.
    impersonatePassword.Text,
    // Logon type = LOGON32_LOGON_NETWORK_CLEARTEXT.
    (int)LogonType.LOGON32_LOGON_NETWORK_CLEARTEXT,
    // Logon provider = LOGON32_PROVIDER_DEFAULT.
    (int)LogonProvider.LOGON32_PROVIDER_DEFAULT,
    // The user token for the specified user is returned here.
    out token);

The output from a successful LogonUser attempt is the token of the logged on user. You can take this token and create a WindowsIdentity class to impersonate the user. Listing 16.8 shows how to do this.

Listing 16.8. Impersonating a User
private WindowsImpersonationContext impersonationContext;
. . .
Debug.WriteLine("New identity created:");
WindowsIdentity loggedonUser = new WindowsIdentity(userToken);
Debug.WriteLine(loggedonUser.Name);
Debug.WriteLine(string.Format("0x{0:X8} ", loggedonUser.Token));

// Impersonate the user.
impersonationContext = loggedonUser.Impersonate();

The WindowsImpersonationContext saves the current user context. That way, when you need to return to yourself, you just need to call the Undo method on the WindowsInpersonationContext class object.

A simple application has been put together that allows you to impersonate other users for whom you know the password. The complete source is in the WindowsImpersonate directory. Initially, characteristics of the user who started the application will be displayed on the left portion of the application. These characteristics include the username, the token associated with this user, the authentication used, and whether the user is an administrator. After you have filled in the form on the right portion of the application (machine, username, and password), you can try to log in. If you have successfully logged in, the button will change its label to Impersonate. Clicking on the Impersonate button causes you to impersonate the user who was logged on. After a successful impersonation, the button changes to Revert. When you click Revert, the screen is restored to the original user. The initial screen should look something like Figure 16.15.

Figure 16.15. Windows impersonation.


Creating GenericPrincipal and GenericIdentity Objects

Using WindowsIdentity and WindowsPrincipal is great for an all-Windows environment. The authentication is handled for you, you can take advantage of the detailed security structure that is available on a Windows platform, and the tools to administer the users are already in place. The problem is that in a highly distributed environment, depending on Windows security might be impractical. Building tools to administer the users of your system who are integrated with the Windows security could be a daunting task. These are some of the reasons that GenericPrincipal and GenericIdentity objects were created. A GenericIdentity is not tied to Windows security. It is only associated with a username that is user definable. You can construct a GenericIdentity for any valid string. Creating a GenericPrincipal is similar in that it takes a GenericIdentity; however, it also takes an array of strings that are completely arbitrary, describing the roles to be associated with this identity. First, you construct the GenericIdentity:

GenericIdentity identity = new GenericIdentity("Kevin");

Next, construct a GenericPrincipal object to be associated with the Identity that was just created, along with an array that describes the roles to be associated with the identity.

string [] roles = new string[]{"President", "Manager", "Worker"} ;
GenericPrincipal principal = new GenericPrincipal(identity, roles);

Using GenericIdentity and GenericPrincipal, you can build any authentication that you need to. You can read the usernames from a database and have a table that maps roles to users. The role that is associated with a particular user becomes dynamic. At the same time, you still have the encapsulation that you desire for each user and the security context in which the user should operate.

Replacing a Principal Object

Every Thread that is running under the CLR has various properties. Two properties describe the culture to be associated with a thread: CurrentCulture and CurrentUICulture. These properties will be very essential in building an internationalized application, as detailed in Chapter 18, “Globalization/Localization.” Another property of threads that is specifically used for security is the CurrentPrincipal property. If you have SecurityPermission with the ControlPrincipal set, then you can modify the Thread's Principal. The Principal with which you replace the Thread's CurrentPrincipal will be the Principal against which PrincipalPermision security checks will be carried out.

If you want to make security decisions based on Windows accounts, the WindowsPrincipal is already defined when you log in. You might need to set the Principal policy so that you have access to the current user information as follows:

// Set principal policy so that you have permission
// to get current user information.
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
// Get the current principal and put it into a
// principal object.
WindowsPrincipal MyPrincipal = (Thread.CurrentPrincipal as WindowsPrincipal);

Source for code that performs much the same function as above can be found in the Program FilesMicrosoft Visual Studio .NETFrameworkSDKSamplesQuickStarthowtosamplessecuritywindowsidentitycheckcs directory as part of the SDK samples.

In this case, WindowsPrincipal is a bit different from GenericPrincipal, where you do not explicitly set a policy.

PrincipalPermission is the only permission object that is involved in role-based security. Typically, you construct this permission specifying a username and a role (both strings). If the CurrentPrincipal (as specified by the static Thread.CurrentPrincipal property) matches the identity and role that the permission specifies, then Demanding the permission will succeed. If the Identity (user) or the role is not part of the CurrentPrincipal, then a security exception is thrown and the permission is Demanded. Listing 16.9 shows an example of constructing and using the PrincipalPermission class. Source corresponding to this listing can be found in the GenericPrincipal directory.

Listing 16.9. Constructing and Using the PrincipalPermission Class
PrincipalPermission perm = new PrincipalPermission(name, role);
. . .
private bool CheckAuthorization(PrincipalPermission perm)
{
    bool result = false;
    try
    {
        perm.Demand();
        result = true;
    }
    catch(Exception)
    {
        result = false;
    }
    return result;
}

The permission is constructed, a routine is called with the permission as an argument, and the Demand method is called on the permission. Demand tells the runtime that to continue, the permission must be associated with the permission object (in this case, PrincipalPermission). One of two things can happen when a Demand is made on a permission. First, the permission can be granted, in which case the call just falls to the next statement. Second, if the permission has not been granted, then a security exception is thrown.

This programmatic access to the security permissions is called imperative security. With imperative security, the programmer creates the permission and calls methods on the created permission to determine whether the permission has been granted. Because of the dynamic nature of this application, imperative security seems to be a better fit than its companion declarative security. Declarative security will be discussed later in this chapter in the section “Code Access Security.”

As a final example for this section, a sample application has been built that demonstrates many of the principles explored in this section. The application is in the GenericPrincipal directory. When it first comes up, it looks like Figure 16.16.

Figure 16.16. GenericPrincipal application.


You will have to use some imagination when you run this application. The application is meant to illustrate what in a real application would be two separate tasks and applications. The side to the left the vertical black bar is the user database. Here, an administrator builds a list of users and a list of roles. A new user is created by clicking on the New User button, and a new role is created by clicking on the New Role button. The association between a user and a role is done by dragging a role from the right list box and dropping it on one of the users in the left tree view. So far, no security objects are involved. The basics are in place to illustrate how to add users and roles. Security objects are not used.

The right side is meant to simulate a user logging into your system. When the user logs in, the username and password are compared against the database to make sure this is a valid user. This is authentication. You can make the authentication much more secure and robust, but the idea is that you have a hook on which to put your favorite authentication scheme. Again, security objects are not used. If the user database is persistent and encrypted, you might need to use some of the cryptography classes.

Next, if the username and password check out and the user is authenticated, a GenericIdentity is constructed based on the logged on user. In addition, a GenericPrincipal is constructed based on the GenericIdentity and the roles that are associated with this user. Then, the IPrincipal interface that is associated with the GenericPrincipal just constructed is assigned to the CurrentPrincipal of the current Thread.

At this point, the Authorization View button should appear enabled. Clicking this button causes a PrincipalPermission to be constructed for each of the roles in the database, and a Demand is made to see if the current Thread's Principal meets the criteria in the constructed permission. If the permission has been granted, the role is added to the tree on the right. If the permission has not been granted, no action is taken. The end result is that the tree on the right should match the corresponding user tree node on the left. There has been no prevision made in this code to allow for multiple users to log on.

Code Access Security

Code access security is based on the grant of the permissions that are listed in Tables 16.1 and 16.2. Through an established policy and evidence that is associated with code, a set of permissions is established that could be granted to the code. The code either attempts an operation or requests that a permission be granted. If the permission is granted, you can assume that an access or use of a resource will succeed, at least with respect to security. Each of the permissions that is listed in Tables 16.1 and 16.2 has the following methods inherited from CodeAccessPermission:

  • Assert— The CLR does a stack walk to determine whether every caller in the chain has the appropriate permission. This prevents the scenario of less privileged code calling more privileged code and indirectly having access to resources that it would not otherwise have access to. Using less trusted code to call highly trusted code to perform what otherwise would be an unauthorized action is known as a luring attack. The CLR prevents luring attacks by walking the stack to make sure that all callers of this method have the requisite permission. It can be costly to walk the stack to evaluate a permission. Assert tells the CLR to stop the stack walk at the point in the call stack that the Assert was issued. The primary reason that you would want to use Assert is performance. By using Assert, you avoid the costly stack walk. The problem is that this potentially opens a security hole in your application, so use Assert carefully. After Assert is issued, you open your application to a luring attack. You can issue a Demand for the permission, which ensures that your code has the permission (or an exception is thrown), followed by an Assert indicating that a stack walk is no longer necessary. (Demand makes sure your application has the required permission.) If you combine the use of Demand followed by Assert, you can have the benefits of the Assert without the associated security hole. Because of the potential security hole, a permission is associated with using Assert. If you cannot Assert, it is possible that you are not being granted SecurityPermission with the SecurityPermissionFlag Assertion set.

  • Demand— This call forces a SecurityException if permission has not been granted to the resource that the permission protects. Calling this method means that you are about to access a resource that the permission is supposed to protect. If you use the System.IO.File class to access a file, this class will Demand FileIOPermission before accessing the file system. If you access the file system via P/Invoke (P/Invoke is covered in Chapter 7), you should specifically Demand FileIOPermission because the unmanaged code will not. Another scenario in which you would use Demand is coupled with Assert, as described earlier. If the Demand for a particular resource succeeds, you can call Assert to avoid repeated security checks on callers of a method.

  • Deny— Sometimes an assembly might want to have less permission than it has been granted. For example, your code might need to take certain actions to limit liability. This is the purpose of the Deny method. Calling Deny on a permission explicitly denies access to the resource that the permission describes. Note that Deny is only applicable to the stack frame that has called Deny and below (assuming that the stack grows downward). For example, the following illustrates the automatic reverting of Deny:

    AttemptToUseResource()
    {
        // Use resource
    }
    DenyResource()
    {
        perm.Deny();
        AttemptToUseResource();
    }
    UseResource()
    {
        DenyResource();
        AttemptToUseResource();
    }
    

    The call to AttemptToUseResource that occurs from DenyResource fails because of the Deny call. The call to AttemptToUseResource that occurs after the call to DenyResource succeeds because the stack frame has popped off the Deny.

  • Intersect— This creates and returns a permission that is the intersection of the current permission and the permission that is passed as an argument.

  • PermitOnly— This method is similar to Deny. Deny causes a stack walk to fail for access to a particular resource. PermitOnly specifies the only condition under which a stack walk succeeds; it fails for any other case.

  • RevertAll— This static call explicitly reverts code permissions back to their original state. It has companion static methods that revert only specific cases: RevertAssert, RevertDeny, and RevertPermitOnly.

  • Union— This allows the union of one or more permissions. This call creates a union of the current permission and the permission that is passed as an argument. You can create a union of more than two permissions by repeatedly calling Union.

Imperative Security

As was done with PrincipalPermission, each CodeAccessPermission derived permission class can be instantiated and programmatically manipulated. Listing 16.10 illustrates using imperative security when calling unmanaged code, first denying the permission to call unmanaged code. The complete source for this sample is in the CodeAccessSecuritySecurity1 directory. For code that is running on the local machine, imperative security is not required to use P/Invoke. This is simply an illustration of how to control and use a specific permission.

Listing 16.10. Calling Unmanaged Code by Using Imperative Security to Deny Permission
private static void CallUnmanagedCodeWithoutPermission()
{
    // Create a security permission object to describe the
    // UnmanagedCode permission:
    SecurityPermission perm =
       new SecurityPermission(SecurityPermissionFlag.UnmanagedCode);

    // Deny the UnmanagedCode from the current set of permissions.
    // Any method that is called on this thread until this method
    // returns will be denied access to unmanaged code.
    perm.Deny();

    try
    {
        Console.WriteLine("Attempting to call unmanaged code without permission.");
        NativeMethods.MessageBox(0, "Hello World!", "", 0);
        Console.WriteLine("Called unmanaged code without permission. Whoops!");
    }
    catch (SecurityException)
    {
        Console.WriteLine("Caught Security Exception attempting to call unmanaged code.");
    }
}

Listing 16.11 shows how to call unmanaged code by asserting that the permission is granted. The complete source for this sample is in the CodeAccessSecuritySecurity1 directory.

Listing 16.11. Calling Unmanaged Code Using Imperative Security to Assert Permission
private static void CallUnmanagedCodeWithPermission()
{
    // Create a security permission object to describe the
    // UnmanagedCode permission:
    SecurityPermission perm =
       new SecurityPermission(SecurityPermissionFlag.UnmanagedCode);

    // Verify that you can be granted all the permissions that you will need.
    perm.Demand();

    // Check that you have permission to access unmanaged code.
    // If you don't have permission to access unmanaged code, then
    // this call will throw a SecurityException
    perm.Assert();

    try
    {
        Console.WriteLine("Attempting to call unmanaged code with permission.");
        NativeMethods.MessageBox(0, "Hello World!", "", 0);
        Console.WriteLine("Called unmanaged code with permission.");
    }
    catch (SecurityException)
    {
        Console.WriteLine("Caught Security Exception attempting to call unmanaged code.
 Whoops!");
    }
    // Technically this call is not required but it is good practice
    // to keep an Assert alive on as long as is needed.
    CodeAccessPermission.RevertAssert();
}

Both samples in Listings 16.10 and 16.11 use imperative security. The next section illustrates how to do the same with declarative security.

Declarative Security

Declarative security uses attributes that are typically attached to methods, classes, and assemblies. Use declarative security when it is known ahead of time what permission is needed or can be denied. Because a runtime cost of constructing the permission objects doesn't exist, declarative security is in most cases faster than imperative security. Listing 16.12 shows how to create a method to deny access permission to unmanaged code. Compare this with the imperative security version in Listing 16.10. The complete source for this sample is in the CodeAccessSecuritySecurity2 directory.

Listing 16.12. Calling Unmanaged Code Using Declarative Security to Deny Permission
// The security permission attached to this method will deny the
// UnmanagedCode permission from the current set of permissions for
// the duration of the call to this method:
[SecurityPermission(SecurityAction.Deny, Flags =
   SecurityPermissionFlag.UnmanagedCode)]
private static void CallUnmanagedCodeWithoutPermission()
{
    try
    {
        Console.WriteLine("Attempting to call unmanaged code without permission.");
        NativeMethods.MessageBox(0, "Hello World!", "", 0);
        Console.WriteLine("Called unmanaged code without permission.  Whoops!");
    }
    catch (SecurityException)
    {
        Console.WriteLine("Caught Security Exception attempting to call unmanaged code.");
    }
}

Listing 16.13 shows how to create a method to Assert access permission to unmanaged code. Compare this with the imperative security version in Listing 16.11. The complete source for this sample is in the CodeAccessSecuritySecurity2 directory.

Listing 16.13. Calling Unmanaged Code Using Declarative Security to Assert Permission
// The security permission attached to this method will force a
// runtime check for the unmanaged code permission whenever
// this method is called. If the caller does not have unmanaged code
// permission, then the call will generate a Security Exception.
[SecurityPermission(SecurityAction.Demand, Flags =
     SecurityPermissionFlag.UnmanagedCode)]
 [SecurityPermission(SecurityAction.Assert, Flags =
   SecurityPermissionFlag.UnmanagedCode)]
private static void CallUnmanagedCodeWithPermission()
{
    try
    {
        Console.WriteLine("Attempting to call unmanaged code with permission.");
        NativeMethods.MessageBox(0, "Hello World!", "", 0);
        Console.WriteLine("Called unmanaged code with permission.");
    }
    catch (SecurityException)
    {
        Console.WriteLine("Caught Security Exception attempting to call unmanaged code.
 Whoops!");
    }
}

Notice that the code for declarative security is easier to read and has somewhat fewer lines of code involved.

Creating Your Own Code Access Permissions

To implement your own custom permission, you need to perform the following steps:

1.
Design your custom permission. Don't just gloss over this step. Ensure that this permission does not overlap another permission. Consider performance implications of your custom permission. What about dependencies that your permission introduces? What is the overhead of the permission? Have you implemented declarative security?

2.
Implement your design. You can create a custom permission in two ways. The first way is to derive your class from CodeAccessPermission. This allows you to leverage the code in CodeAccessPermission, which implements many of the methods of IPermission. If you go this route, you need to derive your class from CodeAccessPermission and IUnrestrictedPermission. Implementing IUnrestrictedPermission is relatively easy because it only has one method to implement: IsUnrestricted. This method simply returns a value that indicates whether unrestricted access to the resource is allowed. Deriving from CodeAccessPermission, you need to override the following methods:

Copy— Creates a duplicate of the current permission object.

Intersect— Returns an intersection with the passed permission.

IsSubsetOf— Returns true if passed permission includes everything allowed. This is indirectly called by demand and essentially grants or denies access to the resource.

FromXml— Builds a permission object from XML.

ToXml— Converts the permission to XML.

The other method to build your own permission class is to derive directly from IPermission and IUnrestrictedPermission. This is the hard way, but it probably will result in the lowest overhead for your permission. By directly deriving from IPermission, you can avoid the stack walk that occurs in the CodeAccessPermission implementation that might not be appropriate with certain permissions.

3.
Make policy aware of your custom permission.

4.
Add the assembly to the list of trusted assemblies.

Tell security policy what code should be granted to your custom permission.

Implementing the Design of a Custom Permission

Rather than going into a detailed explanation of what is involved in building a custom permission, a sample custom permission has been put together. The implementation is in CustomPermissionDayPermission directory. This custom permission protects a resource by time. The permission can be created so that it is valid for a number of days in the week. For example, this custom permission can be created so that it is only valid on Monday and Wednesday.

Note

This section describes the process that is involved in creating a custom permission. The process of creating a custom permission is valid regardless of the algorithm used to determine the permission. However, I would not recommend creating and using permissions that depend on the current time. Many optimizations occur in the .NET Framework that make the performance of security features (primarily stack walks) acceptable. Because of these optimizations, it is not guaranteed that permissions based on time will always function as expected.


Two things have to occur for the permission to allow access to the resource. First, the code that is accessing the resource needs to have been granted the permission for the days required. Second, the current day of the week needs to match that in the permission. If either of these two conditions is false, then a Demand throws a SecurityException.

Listing 16.14 shows some of the constructors that are required to implement this permission.

Listing 16.14. Constructors in a Custom Permission
private bool unrestricted = false;
private ArrayList dayList = new ArrayList();

public DayPermission(PermissionState state)
{
    unrestricted = (state == PermissionState.Unrestricted);
}

public DayPermission(DayOfWeek day)
{
    dayList.Add(day);
}

public DayPermission(ArrayList days)
{
    foreach(DayOfWeek day in days)
        dayList.Add(day);
}

Implementing ToXml and FromXml is relatively straightforward because the SecurityElement class takes care of most of the hard work. Copy performs a deep copy of the elements in the permission. IsSubsetOf makes a comparison with a target permission and returns true whether they are the same or not (at least one completely contains the other). The Intersect and Union methods are the reasons for using an ArrayList rather than just a value for a single day. If one permission object specifies Monday and the other specifies Tuesday, what is the intersection of the two? The intersection is the null set, which cannot be represented unless you implement a representation for the null set. The same logic holds for the Union method.

Making Security Policy Aware of Your Custom Permission

If you make a mistake and corrupt the security policy file, you can usually restore it to the default state with caspol -user -rs or caspol -rs (to restore the machine-level security policy file).

To make the security policy aware of a custom permission, you need to create an XML file that describes your permission. When creating a custom permission, make it serializable so that this support is already in your custom permission assembly. Listing 16.15 shows some simple code to write out the serialized XML description of a custom permission class.

Listing 16.15. Code to Output the XML Description of a Custom Permission
using System.IO;
using System.Security.Permissions;

class CustomPermission
{
   public static void Main()
   {
       CustomPermission perm =
           new CustomPermission(PermissionState.Unrestricted);
   NamedPermissionSet pset =
       new NamedPermissionSet("MyPermissionSet", PermissionState.None);
   pset.Description = "Permission set containing my custom permission";
   pset.AddPermission(perm);
   StreamWriter file = new StreamWriter("mypermissionset.xml");
   file.Write(pset.ToXml());
   file.Close();
   }
}

When code shown in Listing 16.15 is run, it outputs an XML file (mypermisionset.xml) that looks like Listing 16.16.

Listing 16.16. XML Description of a Custom Permission Set
<PermissionSet class="System.Security.NamedPermissionSet"
               version="1"
               Name="MyPermissionSet"
               Description="Permission set containing my custom permission">
   <IPermission class="CLRUnleashed.CustomPermission,
                CustomPermission,
                Version=1.0.0.0,
                Culture=neutral,
                PublicKeyToken=03c1ed2f02a88ea9"
                version="1"
                Unrestricted="True"/>
</PermissionSet>

Now you can take the XML file shown in Listing 16.6 and import it into a security policy using the following command-line utility:

caspol -user -addpset mypermissionset.xml

This command adds the permission set to the user level. You could substitute -machine or -enterprise depending on for which level you will be using the new permission.

Notice that this command adds a full named permission set. You can also add just the permission by extracting the XML portion of the tag <IPermission . . . /> and manually inserting that into an existing named permission set. Of course, this involves directly editing the XML file, but it avoids creating a new permission set.

Adding Your Custom Permission Assembly to the List of Assemblies That Are Fully Trusted

You need to add the assembly that implements the custom permission to the list of assemblies that have full trust. You can see the list using the command line:

caspol -listfulltrust

To add the assembly that implements the custom permission, use the following command line:

caspol -addfulltrust mypermissionset.dll

Note

Before this is done, it is a good idea to add the affected assemblies to the GAC. The security policy system needs to be able to locate the policy assemblies at runtime.


Now you can view the list of fully trusted assemblies using caspol -listfulltrust. You should see your assembly on the list.

Note

If your custom permission assembly A uses classes from assemblies B and C, then B and C need to be added to the policy assembly list. In addition, any assemblies used by B and C need to be added to the list (and so on). Especially note that for permissions written in VB, the VB runtime assemblies need to be added to the policy assembly list.


Adding a Code Group or Modifying a Code Group to Grant the Newly Formed Permission

Adding a code group is probably easier using the .NET Framework Configuration Tool because you can browse for the code for which you want to have the additional permission. Following is the caspol command line to add a new code group that uses the new permission set:

caspol -addgroup -user -allcode -strong <strong_file_path> MyPermissionSet -name Test_Code
 -Description "Test security code group"

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

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