Code Access Security in the Cocoon Host


In Chapter 8, I wrote a CLR host called runcocoon.exe that enabled a new deployment model called a cocoon. Recall that the cocoon deployment model allowed you to package all the files in your application (minus the .NET Framework assemblies) into a single OLE-structured storage file. Runcocoon.exe customized how the CLR loads assemblies by providing an assembly loading manager using the CLR hosting interfaces to run the applications contained in cocoon files. In this section, I extend the host built in Chapter 8 to use the CAS system to restrict the set of operations that can be performed by applications contained in cocoons.

Before I get into the implementation details of how to customize CAS, allow me to establish some security requirements for runcocoon.exe. In particular, the enhancements I make in this chapter will enforce that code running as part of a cocoon application has the following characteristics:

  • It can reference only other assemblies contained in the cocoon and the .NET Framework assemblies. No other assemblies will be granted the permission to execute. A SecurityException will be raised if an attempt is made to load such an assembly.

  • It will have permission only to execute, display user interface, and read and write files to a temporary scratch space referred to as isolated storage. (I describe a bit more about isolated storage later in the chapter when I build the policy tree that grants the appropriate permission.)

I use the infrastructure provided by a HostSecurityManager to enforce these requirements. In particular, I supply an application domain CAS policy tree that grants the assemblies in the cocoon the permission only to execute, display user interface, and manipulate isolated storage. The assemblies in the cocoon will be identified to the CAS system using custom evidence and a custom membership condition. Modifying runcocoon.exe to incorporate CAS requires the following steps:

1.

Create an initial implementation of a class derived from HostSecurityManager.

2.

Create custom evidence used to authenticate the assemblies in the cocoon.

3.

Create a membership condition that can recognize the custom evidence.

4.

Create an application-domain-level CAS policy tree that grants the appropriate permissions.

5.

Assign the custom evidence to the assemblies in the cocoon.

The following sections describe these steps in detail.

Step 1: Provide an Initial Implementation of HostSecurityManager

The customizations I'd like to achieve are centered around the implementation of a host security manager. Plugging the initial implementation of a host security manager into the CAS system requires three initial steps: (1) derive a class from the HostSecurityManager base class, (2) implement the Flags property to tell the CLR which customizations I am interested in participating in, and (3) return an instance of the host security manager from the application domain manager.

The host security manager will participate in two of the customizations offered by HostSecurityManager: it provides custom evidence for assemblies and an application-domain-level CAS policy tree. So I must return the HostSecurityManagerFlags.HostAssemblyEvidence and HostSecurityManagerFlags.HostPolicy-level flags from the host security manager's implementation of the Flags property. The initial implementation looks like this:

  public class CocoonSecurityManager : HostSecurityManager   {      public override HostSecurityManagerFlags Flags      {         get         {            return (HostSecurityManagerFlags.HostAssemblyEvidence |               HostSecurityManagerFlags.HostPolicyLevel);         }      }   }

Providing the CLR with an instance of the host security manager is just a matter of creating a new instance of CocoonSecurityManager and returning it from the HostSecurityManager property of the application domain manager. The following snippet shows the application domain manager class implemented in Chapter 8 with an implementation of the HostSecurityManager property added.

public class CocoonDomainManager : AppDomainManager, ICocoonDomainManager {    public override HostSecurityManager HostSecurityManager    {       get       {          // Return a new instance of the security manager.          return new CocoonSecurityManager();       }    }    // The rest of the class omitted... }

Now that I've got the basic infrastructure in place, I will go ahead and fill in the details of the implementation.

Step 2: Create Custom Evidence

Recall that the CLR associates various types of evidence, including evidence describing the assembly's origin and any signatures it might have, with an assembly automatically as the assembly is loaded. However, none of the evidence the CLR assigns by default is sufficient to tell you that an assembly was loaded from a cocoon file, so I implement my own evidence for this purpose.

Implementing the custom evidence is very straightforward because any managed class can be used to represent evidence associated with an assembly. The custom evidence I implement in the runcocoon.exe host is a simple managed type called EvCocoon:

   public class EvCocoon    {    };

The real work comes in implementing a membership condition to recognize this evidence and in constructing a code group that uses that membership condition to assign permissions, as you'll see in the next few sections.

Step 3: Create a Custom Membership Condition

Classes that represent custom evidence aren't generally useful without a corresponding membership condition that recognizes the evidence during policy evaluation. Earlier in the chapter, I discussed how the .NET Framework provides membership conditions that recognize the various types of evidence that the CLR natively understands, such as strong names, URLs, and so on. In this section, I do the same by providing a membership condition called CocoonMembershipCondition that recognizes the EvCocoon custom evidence object.

All membership conditions must implement the IMembershipCondition interface. IMembershipCondition derives from two other interfaces: ISecurityEncodable and ISecurityPolicyEncodable. The members on IMembershipCondition are shown in Table 10-2.

Table 10-2. The Methods on IMembershipCondition

Method

Description

Check

Given a collection of evidence, Check looks to see whether the collection contains the evidence the membership condition is looking for. In the sample implementation, Check will look for evidence of type EvCocoon.

Copy

Returns an exact copy of the instance of the membership condition on which it is called.

ToXml from ISecurityEncodable

Provides an XML representation of the membership condition.

FromXml from ISecurityEncodable

Creates the membership condition from the XML representation.

ToXml(policyLevel) from ISecurityPolicyEncodable

Provides an XML representation of the membership condition specific to the requested policy level.

ToXml(policyLevel) from ISecurityPolicyEncodable

Creates the membership condition from the XML representation for a specific policy level.


The Check method is the heart of a membership condition. The CLR calls Check during policy resolution to determine whether the set of evidence associated with an assembly satisfies the criteria required by the membership condition. If Check returns true, the CLR adds the permissions granted by the code group containing the membership condition. The implementation of Check in CocoonMembershipCondition enumerates the collection of evidence passed in looking for an instance of EvCocoon. The implementation of CocoonMembershipCondition is shown in the following listing:

public class CocoonMembershipCondition : IMembershipCondition    {       public bool Check(Evidence evidence)       {          if (evidence == null)             return false;          // Loop through the evidence looking for an instance of          // EvCocoon.          IEnumerator enumerator = evidence.GetHostEnumerator();          while (enumerator.MoveNext())          {             Object obj = enumerator.Current;             if (obj is EvCocoon)             {                // We've found cocoon evidence!                return true;             }          }          return false;       }       public IMembershipCondition Copy()       {           return new CocoonMembershipCondition();       }       public override bool Equals(Object o)       {          CocoonMembershipCondition that = (o as CocoonMembershipCondition);          if (that != null)          {              return true          }          return false;       }       // The Cocoon membership condition cannot be specified in       // security XML configuration files.       public SecurityElement ToXml()          { throw new NotSupportedException(); }       public void FromXml(SecurityElement e)          { throw new NotSupportedException(); }       public SecurityElement ToXml(PolicyLevel level)          { throw new NotSupportedException(); }       public void FromXml(SecurityElement e, PolicyLevel level)          { throw new NotSupportedException(); }    };

The ToXml and FromXml methods are called by the CLR during policy administration to translate the membership condition to and from an XML representation. You must implement these methods if you'd like your membership condition to be included in the enterprise, machine, and user policy levels because the definition of those policy levels is persisted to XML files stored on disk. In the sample case, however, the CocoonMembershipCondition will appear only in the custom application-domain-level policy tree. So I've chosen not to implement the methods required to translate a CocoonMembershipCondition to and from XML.

Step 4: Create an Application-Domain-Level Policy Tree

The custom evidence and custom membership condition I've built in the last few sections provide all the pieces I need to create a CAS policy tree that grants the appropriate permissions to assemblies loaded out of a cocoon.

A central element of the policy tree is a code group that grants the appropriate permissions to all assemblies that pass the membership condition implemented by CocoonMembershipCondition. Recall that I want assemblies in cocoon files to be able to execute, display user interface, and store files using isolated storage. These three permissions are represented, respectively, by the SecurityPermission, UIPermission, and IsolatedStorageFilePermission permissions from the System.Security.Permissions namespace. These permissions are grouped together into a PermissionSet object that is passed, along with an instance of the CocoonMembershipCondition class, to the constructor of the System.Security.Policy.UnionCodeGroup as shown in the following snippet:

// Create the permission set granted to assemblies that satisfy // the custom membership condition. Grant the permission to execute, // display UI, and access IsolatedStorage. PermissionSet pSet = new PermissionSet(PermissionState.None); // Add permission to execute. pSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution)); // Add permission to display UI. pSet.AddPermission(new UIPermission(PermissionState.Unrestricted)); // Add permission to store 10k of data in isolated storage. IsolatedStorageFilePermission isoStorePermission = new  IsolatedStorageFilePermission(PermissionState.None); isoStorePermission.UsageAllowed =  IsolatedStorageContainment.DomainIsolationByUser; isoStorePermission.UserQuota = 10000; pSet.AddPermission(isoStorePermission); // Create a code group with the custom membership condition and grant set. UnionCodeGroup cocoonCG = new UnionCodeGroup(    new CocoonMembershipCondition(), new PolicyStatement(pSet));

The policy tree needs more than just this one code group, however. Recall that the set of permissions granted to an assembly is the intersection of the permission grants from each of the four policy levels, so I must grant a set of permissions to all assemblies allowed to run in the application domain. Although the code group I've just built covers the assemblies in the cocoon, I must also add code groups that grant permissions to the .NET Framework assemblies. Without such a code group, the .NET Framework assemblies wouldn't be granted any permissions and, hence, wouldn't be allowed to run, even though the default security policy has granted them full trust! In addition, the host runtime assembly (CocoonHostRuntime) must also have permission to run in the application domain. Given the need to grant permissions to these other assemblies, the final policy will have several other code groups, as shown in Figure 10-8.

Figure 10-8. The application domain policy level for the runcocoon.exe host


As you can see in the figure, the policy tree contains code groups that grant full trust to the .NET Framework and CocoonHostRuntime assemblies based on the public key pair that was used to generate their strong names. This is accomplished in code using instances of System.Security.Policy.StrongNameMembershipCondition. Creating an instance of StrongNameMembershipCondition requires the public key used when the assembly was signed. The sn.exe utility from the .NET Framework SDK has options that enable you to extract the public key from a strong-named assembly and format that key in a way that makes it easy to paste into source code as an array of bytes. For example, to obtain the public key used for CocoonHostRuntime, I'd use the -e option:

C:\sn e CocoonHostRuntime.dll CocoonPublicKey.snk

Now, given the key contained in cocoonpublickey.snk, I can use the -o option to translate that key into a form that's easy to use in source code:

C:\sn o CocoonPublicKey.snk CocoonPublicKey.csv

Finally, I can define the public key in source code using the contents of cocoonpublickey.csv like this:

private static byte[] s_CocoonHostRuntimePublicKey = {     0,  36,   0,   0,   4, 128,   0,   0, 148,   0,   0,   0,     6,   2,   0,   0,   0,  36,   0,   0,  82,  83,  65,  49,     0,   4,   0,   0,   1,   0,   1,   0, 241, 255, 223,  68,     103,  53,  57, 194,  68, 246,  41,  44, 219, 236, 159,  34,     224, 176, 134, 172, 137,  77,  26, 145, 228, 143, 130,  16,     75,  36, 135,  78, 188, 240,  60, 158, 191,  99, 180,  73,     195, 154,  43,  24, 231, 230,  59,  49, 123, 233,  45, 148,     56,   6, 192,  62, 100, 214,  15, 121,   2, 187, 167,  54,     124,  15, 222,  25, 189, 129, 195,  28, 141, 227, 254, 209,     189, 241,  48, 114, 192, 210, 132, 218,  80,  70, 248, 240,     163,  79, 121, 196,  44,  83,  64, 217,  55,  19,  31, 204,     104, 138,  91,  82, 208,  10,  72, 112, 214,  44, 127,  47,     186,  72,  80, 101, 227, 240, 184,  27, 181,  50, 137, 147,     173, 222, 101, 231 };

Given the definitions of the necessary public keys, all that's left is to construct the final policy tree and return it from the host security manager's implementation of DomainPolicy as shown in the following listing:

   public class CocoonSecurityManager : HostSecurityManager    {       private static byte[] s_msPublicKey =       {          0,   36,   0,   0,   4, 128,   0,   0, 148,   0,   0,   0,   6,   2,          0,    0,   0,  36,   0,   0,  82,  83,  65,  49,   0,   4,   0,   0,          1,    0,   1,   0,   7, 209, 250,  87, 196, 174, 217, 240, 163,  46,          132,  170,  15, 174, 253,  13, 233, 232, 253, 106, 236, 143, 135,          251,    3, 118, 108, 131,  76, 153, 146,  30, 178,  59, 231, 154,          217,  213, 220, 193, 221, 154, 210,  54,  19,  33,   2, 144,  11,          114,   60, 249, 128, 149, 127, 196, 225, 119,  16, 143, 198,   7,          119,   79,  41, 232,  50,  14, 146, 234,   5, 236, 228, 232,  33,          192,  165, 239, 232, 241, 100,  92,  76,  12, 147, 193, 171, 153,          40,   93,  98,  44, 170, 101,  44,  29, 250, 214,  61, 116,  93,          111,   45, 229, 241, 126,  94, 175,  15, 196, 150,  61,  38,  28,          138,   18,  67, 101,  24,  32, 109, 192, 147,  52,  77,  90, 210,          147       };       private static byte[] s_ecmaPublicKey =       {          0,   0,   0,   0,   0,   0,   0,   0,   4,   0,   0,   0,          0,   0,   0,   0       };       private static byte[] s_CocoonHostRuntimePublicKey =       {          0,  36,   0,   0,   4, 128,   0,   0, 148,   0,   0,   0,          6,   2,   0,   0,   0,  36,   0,   0,  82,  83,  65,  49,          0,   4,   0,   0,   1,   0,   1,   0, 241, 255, 223,  68,          103,  53,  57, 194,  68, 246,  41,  44, 219, 236, 159,  34,          224, 176, 134, 172, 137,  77,  26, 145, 228, 143, 130,  16,          75,  36, 135,  78, 188, 240,  60, 158, 191,  99, 180,  73,          195, 154,  43,  24, 231, 230,  59,  49, 123, 233,  45, 148,          56,   6, 192,  62, 100, 214,  15, 121,   2, 187, 167,  54,          124,  15, 222,  25, 189, 129, 195, 28,  141, 227, 254, 209,          189, 241,  48, 114, 192, 210, 132, 218,  80,  70, 248, 240,          163,  79, 121, 196,  44,  83,  64, 217,  55,  19,  31, 204,          104, 138,  91,  82, 208,  10,  72, 112, 214,  44, 127,  47,          186,  72,  80, 101, 227, 240, 184,  27, 181,  50, 137, 147,          173, 222, 101, 231       };       public override PolicyLevel DomainPolicy       {          get          {             PolicyLevel pol = PolicyLevel.CreateAppDomainLevel();             pol.RootCodeGroup.PolicyStatement = new PolicyStatement(                new PermissionSet(PermissionState.None));             // Create membership condition for the MS platform key.             UnionCodeGroup msKeyCG =                new UnionCodeGroup (                   new StrongNameMembershipCondition(new                      StrongNamePublicKeyBlob(s_msPublicKey),                      null, null),                      new PolicyStatement(                         new PermissionSet(PermissionState.Unrestricted)));             // Add this code group as a child of the root.             pol.RootCodeGroup.AddChild(msKeyCG);             // Create membership condition for the ECMA key.             UnionCodeGroup ecmaKeyCG =                new UnionCodeGroup(                   new StrongNameMembershipCondition(new                      StrongNamePublicKeyBlob(s_ecmaPublicKey), null, null),                      new PolicyStatement(                         new PermissionSet(PermissionState.Unrestricted)));             // Add this code group as a child of the root.             pol.RootCodeGroup.AddChild(ecmaKeyCG);             // Create membership condition for the key that             // signed CocoonHostRuntime.             UnionCodeGroup hostKeyCG =                new UnionCodeGroup(                   new StrongNameMembershipCondition(                       new StrongNamePublicKeyBlob(                          s_CocoonHostRuntimePublicKey),                          null, null),                          new PolicyStatement(                             new PermissionSet(PermissionState.Unrestricted)));             // Add this code group as a child of the root.             pol.RootCodeGroup.AddChild(hostKeyCG);             // Create the permission set I'll grant to assemblies             // that satisfy the custom membership condition. Grant the             // permission to execute, display UI, and access             // IsolatedStorage.             PermissionSet pSet = new PermissionSet(PermissionState.None);             // Add permission to execute.             pSet.AddPermission(                new SecurityPermission(SecurityPermissionFlag.Execution));             // Add permission to display UI.             pSet.AddPermission(                new UIPermission(PermissionState.Unrestricted));             // Add permission to store 10k of data in isolated storage.             IsolatedStorageFilePermission isoStorePermission =                new IsolatedStorageFilePermission(PermissionState.None);                isoStorePermission.UsageAllowed =                   IsolatedStorageContainment.DomainIsolationByUser;                isoStorePermission.UserQuota = 10000;                pSet.AddPermission(isoStorePermission);             // Create a code group with the custom membership             // condition and grant set.             UnionCodeGroup cocoonCG =                new UnionCodeGroup(new CocoonMembershipCondition(),                new PolicyStatement(pSet));             // Add this code group as a child of the root.             pol.RootCodeGroup.AddChild(cocoonCG);             return pol;          }       }    }

Now that I've built the policy tree, let's revisit the initial security requirements to see how they are satisfied. There were two requirements. The assemblies in a cocoon file (1) can reference only other assemblies contained in the cocoon and the .NET Framework assemblies, and (2) will have the permission only to execute, display user interface, and store files in isolated storage. The first requirement is satisfied because the policy tree does not have a code group that grants any permissions to assemblies other than the assemblies in the cocoon, the .NET Framework assemblies, and the CocoonHostRuntime. If an attempt is made to load any other assembly into the application domain, the policy tree will grant it no permissions. Because the final grant set for an assembly is determined by intersecting the permission grants from each of the four policy levels, the fact that I've granted no permissions will cause the assembly's final grant set to be empty. Without at least the permission to execute, the assembly won't run in the application domain. The second requirement is satisfied by the code group that I specifically built to grant permissions to assemblies in cocoons. That code group grants only the permissions I want. Again, because the overall grant set is determined by intersection, I am guaranteed that no other policy level can grant more permissions than I'd like.

Note

System.AppDomain has a method called SetAppDomainPolicy that can also be used to associate a CAS policy tree with an application domain. SetAppDomainPolicy is now deprecated in favor of using the PolicyLevel property introduced in .NET Framework 2.0. However, if your extensible application must run on versions of the .NET Framework earlier than .NET Framework 2.0, you'll need to use SetAppDomainPolicy instead.


Step 5: Assign Custom Evidence to Assemblies in the Cocoon

The final step in adding CAS support to runcocoon.exe is to associate the custom evidence type (EvCocoon) with all assemblies that are loaded out of cocoon files. I do this using the ProvideAssemblyEvidence method on the host security manager. Because I returned the value HostSecurityManagerFlags.HostAssemblyEvidence from the Flags property of CocoonHostSecurityManager, the CLR will call ProvideAssemblyEvidence each time an assembly is about to be loaded into an application domain. ProvideAssemblyEvidence takes as input an instance of System.Reflection.Assembly identifying the assembly about to be loaded and an instance of System.Security.Policy.Evidence representing the collection of evidence that has been assigned to the assembly so far. That is, the set of evidence that the CLR has automatically assigned as part of loading the assembly. The implementation of ProvideAssemblyEvidence is free to modify this evidence in any way. The modified evidence is then returned from the method. The definition of ProvideAssemblyEvidence is as follows:

public class HostSecurityManager {    // The rest of the class definition omitted...    public override Evidence ProvideAssemblyEvidence(       Assembly loadedAssembly,       Evidence evidence); }

Evidence has several methods that enable you to manipulate the contents of an evidence collection. One of these methods, called AddHost, is specifically designed to allow extensible applications to add evidence to an existing evidence collection. I use AddHost to add an instance of EvCocoon to the collection of evidence passed to ProvideAssemblyEvidence. I then return the modified collection.

Before I can finish the implementation of ProvideAssemblyEvidence, however, I must consider one more important design point. The implementation of ProvideAssemblyEvidence must add an instance of the EvCocoon evidence only to those assemblies that are loaded from the cocoon. However, given that ProvideAssemblyEvidence will be called for every assembly that is loaded into the application domain, how can I determine which calls to ProvideAssemblyEvidence are for assemblies contained in cocoons? The only thing I have to work with is the instance of the Assembly class passed to ProvideAssemblyEvidence. Given that, I must be able to determine whether an assembly comes from a cocoon through some property or method on Assembly.

The best way to solve this problem requires revisiting how assemblies are loaded by runcocoon.exe. Recall from Chapter 8 that runcocoon.exe uses the CLR hosting interfaces to implement an assembly loading manager whose sole purpose is to load assemblies from the cocoon. It is the assembly loading manager, specifically the implementation of IHostAssemblyStore::ProvideAssembly, that knows when an assembly is being loaded from a cocoon. What I need is some mechanism for ProvideAssembly (in the unmanaged portion of the host) to communicate the fact that an assembly was loaded from a cocoon to the implementation of ProvideAssemblyEvidence (in the managed portion of the host). This mechanism exists in the form of the pContext parameter to ProvideAssembly and the HostContext property on Assembly. Here's the definition of ProvideAssembly from mscoree.idl:

interface IHostAssemblyStore: IUnknown {     HRESULT ProvideAssembly             (             [in] AssemblyBindInfo *pBindInfo,             [out] UINT64          *pAssemblyId,             [out] UINT64          *pContext,             [out] IStream        **ppStmAssemblyImage,             [out] IStream        **ppStmPDB);     // Rest of the interface definition omitted... }

To pass context information to managed code, the implementation of ProvideAssembly will set a specific value into *pContext each time an assembly is loaded from a cocoon. This value can then be retrieved in ProvideAssemblyEvidence using the HostContext property on Assembly. Here are the relevant parts of the implementation of ProvideAssembly from Chapter 8:

static const int CocoonAssemblyHostContext = 5; HRESULT STDMETHODCALLTYPE CCocoonAssemblyStore::ProvideAssembly(                      AssemblyBindInfo *pBindInfo,                      UINT64           *pAssemblyId,                      UINT64           *pContext,                      IStream          **ppStmAssemblyImage,                      IStream          **ppStmPDB) {    // Portions of the implementation omitted...    *pContext = 0;    // Try to load the assembly from the cocoon. If the assembly is    // contained in the cocoon, S_OK will be returned.    HRESULT hr = m_pStreamIndex->GetStreamForBindingIdentity(       pBindInfo->lpPostPolicyIdentity,       pAssemblyId,       ppStmAssemblyImage);    if (SUCCEEDED(hr))    {       // Set the host context to indicate this assembly was loaded       // from a cocoon. This data will be used in the host security manager's       // implementation of ProvideAssemblyEvidence to associate the custom       // evidence with the assembly.       *pContext = CocoonAssemblyHostContext;    }     return hr; }

Now that I can easily determine which assemblies came from cocoons, the implementation of ProvideAssemblyEvidence is straightforward:

public class CocoonSecurityManager : HostSecurityManager {    // Portions of the class implementation omitted...    static int CocoonAssemblyHostContext = 5;    public override Evidence ProvideAssemblyEvidence(Assembly loadedAssembly,    Evidence evidence)    {       if (loadedAssembly.HostContext == CocoonAssemblyHostContext)       {          // Add an instance of the cocoon evidence class to the list of host          // evidence. This evidence will cause the check in          // CocoonMembershipCondition to pass during policy evaluation,          // thereby granting the permissions specified in the app-domain-          // level policy.          evidence.AddHost(new EvCocoon());          // Add evidence that identifies this assembly as coming from          // the MyComputer zone. Without this evidence, the assembly would          // get no permissions at the machine level, so the grant based on          // cocoon evidence would get canceled out. In essence, providing          // this evidence causes the assembly to get "FullTrust" (through          // default policy), which I then lock back down through Cocoon          // evidence.          evidence.AddHost(new Zone(SecurityZone.MyComputer));       }       return evidence;    } }

There is one subtlety in the preceding code that I haven't explained. Notice that in addition to associating EvCocoon evidence with assemblies that come from cocoons, I also associate evidence representing the MyComputer zone. The reason I must do this is as follows. Assemblies loaded from an assembly loading manager (IStream*), such as those contained in cocoons, don't get much of the default evidence that the CLR typically associates with assemblies as they are loaded. Because the CLR doesn't know where the contents of an assembly represented by an IStream* originated, it cannot assign evidence describing the URL, zone, and so on, so these assemblies have no evidence that would cause the enterprise, machine, and user policy levels to grant them any permissions whatsoever (through default policy). Given this, the permissions I grant through the application-domain-level CAS policy don't appear in the final grant set; they are intersected out of the final result because the other policy levels have granted nothing.

I can solve this in a few ways. First, I could modify enterprise, machine, or user policy to grant permissions to assemblies based on EvCocoon evidence. Although this approach would work, it's cumbersome because it requires the default CAS policy files to be edited on every machine on which I want to run an application contained in a cocoon. The other approach is to assign additional evidence to cocoon assemblies that will cause them to be granted permissions by the enterprise, machine, or user level. That is the approach I've taken in the preceding code. By granting evidence representing the MyComputer zone, I cause the assemblies in the cocoon to be granted full trust by the default CAS policy at the machine level. Then, when full trust is intersected with the grants I supply at the application domain policy level, the grants form the final set of permissions granted to the assembly.

At first glance it might seem dangerous to associate evidence that will cause a cocoon assembly to be granted full trust at the machine level. However, because the assembly's final grant set is determined through an intersection of the grant sets from each level, you can effectively narrow down the grant set from full trust to only those permissions specified at the application domain level.

Assigning Evidence Using the Assembly Loading APIs

Assigning evidence using ProvideAssemblyEvidence is convenient because it enables you to assign evidence to all assemblies from a single location. In this way, you can be sure that no assemblies will be loaded into the domain without you having a chance to customize their evidence. However, you can also assign evidence to an assembly at the time you load it using one of the assembly loading APIs. Recall from Chapter 7 that the assembly loading APIs, such as Assembly.Load and AppDomain.Load, all have a parameter that enables you to specify evidence to associate with the assembly. It's generally more convenient to assign evidence using a host security manager as described. However, if you have a simple scenario in which you don't need a host security manager (or application domain manager) for any other reason, or if you have context at the point the assembly is loaded that is required to generate the proper evidence, supplying evidence using one of the assembly loading APIs is a perfectly viable solution.

As you'd expect, the parameter that enables you to supply evidence using one of the assembly loading APIs is of type System.Security.Policy.Evidence as shown in the following definition of Assembly.Load:

static public Assembly Load(AssemblyName assemblyRef,                             Evidence assemblySecurity)

Listing 10-2 creates a new evidence collection, adds an instance of EvCocoon to the collection, and passes it to Assembly.Load.

Listing 10-2. Evidenceload.cs
using System; using System.Reflection; using System.Security; using System.Security.Policy; using System.Collections; namespace EvidenceDisplay {    [Serializable]    class EvCocoon    {    }    class Program    {       static void Main(string[] args)       {          Evidence cocoonEvidence = new Evidence();          cocoonEvidence.AddHost(new EvCocoon());          cocoonEvidence.AddHost(new Zone(SecurityZone.Intranet));          // Load an assembly.          Assembly a = Assembly.Load("Utilities",cocoonEvidence);          // Assembly a is now loaded and ready to use...       }    } }

The evidence you pass to the assembly loading APIs is merged with the evidence supplied by the CLR in the following ways:

  • All types of evidence you pass that are not also supplied by the CLR are simply added to the evidence collection. Thus, the final collection contains both the evidence you supplied along with that provided by the CLR.

  • If you supply a type of evidence that is also supplied by the CLR, your evidence will supersede the evidence already in the collection. For example, when you load an assembly from the local machine, the CLR will assign it evidence of type System.Security.Policy.Zone representing the MyComputer zone. If you supply zone evidence in your call to the assembly loading APIs, representing Intranet, for example, the final collection of evidence associated with the assembly will have Zone evidence representing Intranet.

Remember, too, that if you are using an assembly loading API that loads an assembly into an application domain other than the one in which you're currently running, the CLR must be able to serialize your custom evidence type across the application domain boundary. This is why the EvCocoon type in Listing 10-2 is marked with the [Serializable] custom attribute.

Putting It All Together

Now that I've implemented all the pieces required to incorporate CAS into the runcocoon.exe host, let's take a step back and see how all these pieces fit together. Figure 10-9 shows the sequence of steps that occur at run time to grant a set of permissions to the assemblies in a cocoon application.

Figure 10-9. Code Access Security in the runcocoon.exe host


The following points explain these steps in greater detail:

1.

An application domain is created in which to run the application contained in the cocoon. When the application domain is created, the CLR accesses the DomainPolicy property on the host security manager (implemented by CocoonHostSecurityManager) to access the CAS policy tree associated with the new domain.

2.

As the application is running, the assembly loading manager gets called to load assemblies from the cocoon.

3.

The implementation of IHostAssemblyStore::ProvideAssembly sets a special value into the host context parameter (*pContext) to indicate that a particular assembly was loaded from the cocoon.

4.

The implementation of ProvideAssemblyEvidence in CocoonHostSecurityManager accesses the HostContext property on the instance of Assembly it is passed to determine whether a given assembly is being loaded from a cocoon. I add an instance of EvCocoon to the evidence collection for all assemblies coming from cocoon files.

5.

As the assembly is loaded, the CLR evaluates CAS policy with that assembly's evidence at the enterprise, machine, user, and application domain levels. The grant sets from each level are intersected to determine the final set of permissions granted to the assembly.



    Customizing the Microsoft  .NET Framework Common Language Runtime
    Customizing the Microsoft .NET Framework Common Language Runtime
    ISBN: 735619883
    EAN: N/A
    Year: 2005
    Pages: 119

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net