[ Team LiB ] Previous Section Next Section

8.2 Programming Security Policy

The .NET security administration tools (discussed in Chapter 9) are sufficient for most users and security administrators to configure their security policy, but to have complete control over all security policy features, you must do so programmatically. As with evidence and permissions, .NET represents each of the security policy elements with classes, meaning that you can manipulate them in your own code very easily.

In the following sections, we provide explanations of how to program the key components of security policy, starting with code groups and membership conditions, then moving on to policy levels, and finally the security manager. We do not discuss individual permissions or permission sets, because we covered them in Chapter 7. We conclude with an example that brings together all elements of security policy programming; we demonstrate how to manipulate the policy of an application domain to control the policy resolution process applied to the assemblies loaded into it.

8.2.1 Programming Code Groups

The abstract System.Security.Policy.CodeGroup class provides the base representation of a code group and defines the functionality that lies at the heart of the policy resolution process. Four noninheritable subclasses extend CodeGroup to provide concrete implementations that you can use in security policy programming; these are shown in Figure 8-8.

Figure 8-8. Concrete subclasses of CodeGroup
figs/pdns_0808.gif

Figure 8-9 illustrates the structure of the CodeGroup class, showing that CodeGroup is a container for all of the elements we discussed in Section 8.1.1.1. We summarize how CodeGroup implements each of these component elements here and provide additional details on how to use them later in this section.

Membership condition

An object that implements the System.Security.Policy.IMembershipCondition interface provides the functionality to determine whether an assembly or application domain qualifies for membership to the code group. You must specify the membership condition at CodeGroup instantiation but can change it later using the MembershipCondition property.

Policy statement

The policy statement contains values that specify the effect the CodeGroup has on assemblies and application domains that are members of the group. The System.Security.Policy.PolicyStatement class represents a code group's policy statement. It contains a value from the System.Security.Policy.PolicyStatementAttribute enumeration to represent the code group's attributes, and a System.Security.PermissionSet that specifies the set of permissions granted by the code group to its members.

Children

Each CodeGroup contains an ordered list of child CodeGroup objects. In turn, each child CodeGroup can have its own children, creating a tree structure. The Children property gets or sets the code group's children using a System.Collections.IList containing the set of child CodeGroup objects.

Figure 8-9. Structure of the CodeGroup class
figs/pdns_0809.gif

The most important method of the CodeGroup class is Resolve, which takes an Evidence collection as an argument. Policy resolution of an assembly (or application domain) consists of the runtime calling the Resolve method on the root CodeGroup in each policy level and passing it the assembly's Evidence collection. In the Resolve method, the CodeGroup is responsible for determining if the assembly's evidence qualifies it for membership, how to apply any attributes, and how or if the CodeGroup should use its children to continue the policy resolution process. Resolve returns a PolicyStatement that represents the net effect of all code groups in the tree to which the assembly qualified for membership. The key difference between each of the CodeGroup subclasses is how they process the Resolve method:

UnionCodeGroup

This is the most commonly used code group type, which implements policy resolution, as we explained in Section 8.1.2. Members are tested against all child code groups for membership. The resulting PolicyStatement contains the union of the code group's permission set and the permission sets of each child of which the assembly is also a member. If a code group has the Exclusive attribute, the PolicyStatement will include only those permissions granted by that group, as we described in Section 8.1.2.1.

FileCodeGroup

This operates the same as UnionCodeGroup with respect to matching members against its children and how the resulting PolicyStatement is calculated. However, FileCodeGroup does not support attributes and does not contain a statically defined permission set. With each call to Resolve, if the evidence of the assembly contains "file://"-based Url evidence, the FileCodeGroup dynamically generates a permission set that contains a System.Security.Permissions.FileIOPermission granting access to the directory specified in the Url evidence. We discussed Url evidence in Chapter 6 and FileIOPermission in Chapter 7.

NetCodeGroup

This operates the same as FileCodeGroup, but with each call to Resolve, if the evidence of the assembly contains "http://" or "https://" Url (or Site) evidence, the NetCodeGroup dynamically generates a permission set that contains a System.Security.Permissions.WebPermission granting connect access to the specified web site. We discussed WebPermission in Chapter 7.

FirstMatchCodeGroup

This operates the same UnionCodeGroup but evaluates members against its children only until it finds the first matching child group. The resulting PolicyStatement contains the permission granted by the FirstMatchCodeGroup and at most one of its child groups; this makes the order of the child elements important.

As important as Resolve is to the runtime, both Resolve and the similar ResolveMatchingCodeGroups method provide minimal value when used directly. You cannot use them to drive the policy resolution process manually, and therefore they are useful only for testing and debugging. Table 8-2 summarizes the properties and methods of CodeGroup and highlights any special behavior implemented by the subclasses. No specific code-access permissions are required to use the members of the code group classes.

Table 8-2. Members of the code group classes

Member

Description

Properties

 

AttributeString

Returns a String representing the code group's attributes. FileCodeGroup and NetCodeGroup do not use attributes and always return null (C#) or Nothing (Visual Basic .NET).

Children

Gets or sets the code group's child code groups. The child CodeGroup elements are contained in an IList. For FirstMatchCodeGroup groups, the order of the children in the IList is important.

Description

Gets or sets the description of the code group as a String.

MembershipCondition

Gets or sets the membership condition for the code group using a System.Security.Policy.IMembershipCondition instance.

MergeLogic

Returns a String that describes the logic used to merge the permissions of the code group and its children; returns "First Match" for FirstMatchCodeGroup and "Union" for the other three types of code groups.

Name

Gets or sets the name of the code group using a String.

PermissionSetName

Gets a String containing the name of the code group's permission set. This is null (C#) or Nothing (Visual Basic .NET) if the permission set does not have a name. The NetCodeGroup class always returns the name "Same site Web" and the FileCodeGroup always returns the name "Same directory FileIO-" appended with the type of file access granted.

PolicyStatement

Gets or sets the PolicyStatement of the code group.

Methods

 

AddChild

Creates a copy of the specified CodeGroup and adds it to the end of the current list of children.

Copy

Creates a deep copy of the CodeGroup, including all child code groups.

Equals

Basic equality is determined by comparing the Name, Description, and MembershipCndition of two CodeGroup objects. It is also possible to force a comparison that includes all child code groups.

FromXml

Reconstructs a CodeGroup from XML previously generated using the ToXml method.

RemoveChild

Removes the specified CodeGroup from the list of immediate children.

Resolve

Returns a PolicyStatement containing the permissions granted by the CodeGroup based on a specified Evidence collection. As discussed earlier, the type of code group determines how the permissions of child code groups affect the final PolicyStatement.

ResolveMatchingCodeGroups

Returns a CodeGroup tree containing all of the child code groups to which a specified Evidence collection qualifies for membership. The type of code group determines the set of child code groups contained in the final collection.

ToXml

Returns a SecurityElement containing the XML object model for the CodeGroup. ToXml is useful for writing the contents of a code group to an XML file so that you can import it when using the administrative tools. See Chapter 9 for further details.

Before demonstrating the creation and manipulation of code groups, we provide details of membership conditions and policy statements.

8.2.1.1 Programming membership conditions

Membership conditions are classes that implement the IMembershipCondition interface. All four types of code group contain a single IMembershipCondition instance that you must specify as an argument to the code group's constructor; you can get and set the IMembershipCondition through the CodeGroup.MembershipCondition property after construction.

The IMembershipCondition interface defines a method named Check, which takes an Evidence collection argument and returns a Boolean value indicating whether the values of the contained evidence objects satisfy a configurable condition:

# C#

bool Check(Evidence evidence);

# Visual Basic .NET

Function Check(ByVal evidence As Evidence) As Boolean

A CodeGroup calls the Check method of its IMembershipCondtion during policy resolution to evaluate whether an assembly or application domain qualifies for membership to the code group.

The .NET Framework includes the eight standard membership condition classes listed in Table 8-3; all are members of the System.Security.Policy namespace. Seven of these classes contain the logic necessary to test the values of standard evidence classes, which we discussed in Chapter 6. One additional membership condition class, named AllMembershipCondition always returns true when Check is called, regardless of the evidence provided. This means a code group using AllMembershipCondition as its membership condition will contain all assemblies and application domains tested against it.

Table 8-3. Standard membership condition classes

Membership class

Membership condition

AllMembershipCondition

All code irrespective of evidence.

ApplicationDirectoryMembershipCondition

Evidence collection contains both ApplicationDirectory and Url evidence. The Url evidence represents a location that is a child of that represented by the ApplicationDirectory evidence.

HashMembershipCondition

Evidence collection contains a Hash class with the specified hash value.

PublisherMembershipCondition

Evidence collection contains a Publisher class with the specified publisher certificate.

SiteMembershipCondition

Evidence collection contains a Site class with the specified site name.

StrongNameMembershipCondition

Evidence collection contains a StrongName class with the specified hash value.

UrlMembershipCondition

Evidence collection contains a Url class with the specified URL location.

ZoneMembershipCondition

Evidence collection contains a Zone class with the specified security zone.

The constructors for each type of membership condition vary depending on the evidence types they evaluate, but all of them are relatively straightforward to use if you understand the standard evidence types. You should consult the .NET Framework SDK documentation for complete details, but here are some examples of how to create membership conditions:

# C#

// Create a membership condition to match all code.
IMembershipCondition m1 = new AllMembershipCondition(  );

// Create a membership condition to match all code with
// Internet Zone evidence.
IMembershipCondition m2 = 
    new ZoneMembershipCondition(SecurityZone.Internet);

// Create a membership condition to match all code from
// all "oreilly.com" Sites.
IMembershipCondition m3 = 
    new SiteMembershipCondition("*.oreilly.com");

// Create a membership condition to match all code with
// the same Publisher certificate as was used to sign
// the SomeFile.exe assembly.
IMembershipCondition m4 = 
    new PublisherMembershipCondition(
        X509Certificate.CreateFromSignedFile("SomeFile.exe")
    );

# Visual Basic .NET

' Create a membership condition to match all code.
Dim m1 As IMembershipCondition =  New AllMembershipCondition(  ) 
 
' Create a membership condition to match all code with
' Internet Zone evidence.
Dim m2 As IMembershipCondition =  _
New ZoneMembershipCondition(SecurityZone.Internet) 
 
' Create a membership condition to match all code from
' all "oreilly.com" Sites.
Dim m3 As IMembershipCondition = _
New SiteMembershipCondition("*.oreilly.com")

' Create a membership condition to match all code with
' the same Publisher certificate as was used to sign
' the SomeFile.exe assembly.
Dim m4 As IMembershipCondition =  _
New PublisherMembershipCondition( _
X509Certificate.CreateFromSignedFile("SomeFile.exe"))
8.2.1.2 Programming policy statements

For the UnionCodeGroup and FirstMatchCodeGroup classes, a PolicyStatement class represents the effect the code group has on its members, including the code group's attributes and its permission set. The FileCodeGroup and NetCodeGroup classes do not require you to set a policy statement, because they generate their permission sets dynamically and do not support attributes.

Provide a PolicyStatement as an argument to the UnionCodeGroup and FirstMatchCodeGroup constructors, and you can get and set the PolicyStatement after construction through the CodeGroup.PolicyStatement property.

The PolicyStatement class provides two constructors. The first takes a System.Security.PermissionSet argument specifying the permissions a code group grants to its members. The second constructor takes both a PermissionSet and a member of the System.Security.Policy.PolicyStatementAttribute enumeration, representing the code group's set of attributes. We list the possible values of PolicyStatementAttribute in Table 8-4.

Table 8-4. Members of the PolicyStatementAttribute enumeration

Member

Description

All

The code group has both the Exclusive and LevelFinal attributes

Exclusive

The code group has only the Exclusive attribute. See "Code Group Attributes" for details

LevelFinal

The code group has only the LevelFinal attribute. See "Code Group Attributes" for details

Nothing

The code group has no attributes

The properties listed in Table 8-5 provide access to the content of the PolicyStatement after instantiation.

Table 8-5. Properties of PolicyStatement

Property

Description

Attributes

Gets or sets the attributes contained in the policy statement as a member of the PolicyStatementAttribute enumeration.

AttributeString

Gets a human-readable String representing the attributes contained in the PolicyStatement.

PermissionSet

Gets or sets the permission contained in the PolicyStatement as a PermissionSet.

The following examples demonstrate how to create PolicyStatement objects. See Chapter 7 for a description of how to create PermissionSet objects:

# C#

// Create a PolicyStatement that grants Unrestricted access 
// to everything
PolicyStatement p1 = new PolicyStatement(
    new PermissionSet(PermissionState.Unrestricted)
);

// Create a PolicyStatement that grants read access to the 
// file "C:\File.txt" and specifies the LevelFinal attribute.
PermissionSet pset = new PermissionSet(
    new FileIOPermission(FileIOPermissionAccess.Read,@"C:\File.txt"));
PolicyStatement p2 = new PolicyStatement(
    pset, PolicyStatementAttribute.LevelFinal);
        
# Visual Basic .NET

' Create a PolicyStatement that grants Unrestricted access 
' to everything
Dim p1 As PolicyStatement =  New PolicyStatement( _
New PermissionSet(PermissionState.Unrestricted)) 
 
' Create a PolicyStatement that grants read access to the 
' file "C:\File.txt" and specifies the LevelFinal attribute.
Dim pset As PermissionSet = New PermissionSet( _
New FileIOPermission(FileIOPermissionAccess.Read,"C:\File.txt"))  

Dim p2 As PolicyStatement = New PolicyStatement _
(pset,PolicyStatementAttribute.LevelFinal)
8.2.1.3 Creating code groups

With the knowledge of how to create policy statements and membership conditions, creating code groups is straightforward. You create UnionCodeGroup and FirstMatchCodeGroup objects by providing an IMembershipCondition and a PolicyStatement as constructor arguments. The following example creates a UnionCodeGroup with the Exclusive attribute that matches all code downloaded from any web site in the oreilly.com domain and grants it unrestricted access to the filesystem:

# C# 

// Create the permission set and add unrestricted file access.
PermissionSet pset = new PermissionSet(PermissionState.None);
pset.AddPermission(new FileIOPermission(PermissionState.Unrestricted));

// Create the policy statement and set the Exclusive attribute.
PolicyStatement pstate = 
    new PolicyStatement(pset, PolicyStatementAttribute.Exclusive);

// Create the membership condition to match all "*.oreilly.com" sites.
IMembershipCondition mcon = 
    new SiteMembershipCondition("*.oreilly.com");

// Create the UnionCodeGroup
UnionCodeGroup cg = new UnionCodeGroup(mcon, pstate);

# Visual Basic .NET

' Create the permission set and add unrestricted file access.
Dim pset As PermissionSet = New PermissionSet(PermissionState.None) 
pset.AddPermission(New FileIOPermission(PermissionState.Unrestricted))
 
' Create the policy statement and set the Exclusive attribute.
Dim pstate As PolicyStatement = _
New PolicyStatement(pset, PolicyStatementAttribute.Exclusive) 
 
' Create the membership condition to match all "*.oreilly.com" sites.
Dim mcon As IMembershipCondition = _
New SiteMembershipCondition("*.oreilly.com") 
 
' Create the UnionCodeGroup
Dim cg As UnionCodeGroup = New UnionCodeGroup(mcon,pstate)

The NetCodeGroup class constructor takes only an IMembershipCondition, because it calculates dynamically its permission set and does not support attributes; therefore, there is no need for a PolicyStatement. The following example creates a NetCodeGroup that allows all code signed with the publisher certificate contained in the Publisher.cer file to connect back to the web site from where it is loaded. Details of the WebPermission class are in Chapter 7:

# C# 

NetCodeGroup cg = new NetCodeGroup(
    new PublisherMembershipCondition(
        X509Certificate.CreateFromCerFile("Publisher.cer")
    ));

# Visual Basic .NET

Dim cg As NetCodeGroup = New NetCodeGroup( _
New PublisherMembershipCondition( _
X509Certificate.CreateFromCerFile("Publisher.cer")))

The FileCodeGroup constructor takes an IMembershipCondition and a System.Security.Permissions.FileIOPermissionAccess argument. The FileIOPermissionAccess specifies the type of file access permission the FileCodeGroup should include in its dynamically generated permission set; see Table 8-6 for possible values. Details of the FileIOPermission class are in Chapter 7.

Table 8-6. Members of the FileIOPermissionAccess enumeration

Property

Description

AllAccess

All access

Append

Access to append content to an existing file or directory

NoAccess

No access

PathDiscovery

Access to the information contained in the path of a directory or file

Read

Access to read from a file or directory

Write

Access to write to a file or directory, including overwriting and deleting existing files and directories

The following code creates a FileCodeGroup that allows all code signed with the publisher certificate contained in the Publisher.cer file to write to the directory from which it was loaded:

# C# 

FileCodeGroup cg = new FileCodeGroup(
    new PublisherMembershipCondition(
        X509Certificate.CreateFromCertFile("Publisher.cer")),
    FileIOPermissionAccess.Write
);

# Visual Basic .NET

Dim cg As FileCodeGroup = New FileCodeGroup( _
New PublisherMembershipCondition( _
X509Certificate.CreateFromCertFile("Publisher.cer")),
FileIOPermissionAccess.Write)

8.2.2 Programming Policy Levels

The .NET class library contains the System.Security.Policy.PolicyLevel class to represent all security policy levels: enterprise, machine, user, and application domain. The PolicyLevel class is a container for the component elements we described in "Security Policy Levels": fully trusted assemblies, named permission sets, and code groups.

You cannot create new PolicyLevel objects using constructors. To manipulate the enterprise, machine, or user policy levels, you must obtain a reference to the desired level using the static methods of the System.Security.SecurityManager class, which we discuss in the next section. To create a new application domain policy level, use the static PolicyLevel.CreateAppDomainLevel factory method, which we demonstrate in Section 8.2.4.

Table 8-7 summarizes the members of the PolicyLevel class. None of the members is protected with code-access permissions. .NET protects the methods you use to obtain and update policy levels, not the methods that manipulate them once you have a reference.

Table 8-7. Members of the PolicyLevel class

Member

Description

Properties

 

FullTrustAssemblies

Gets an IList of StrongNameMembershipCondition objects representing the fully trusted assemblies of the policy level.

Label

Gets a String containing a description of the PolicyLevel.

NamedPermissionSets

Gets an IList of NamedPermissionSet objects representing the fully trusted assemblies of the policy level.

RootCodeGroup

Gets or sets the root CodeGroup of the policy levels code group tree.

StoreLocation

Gets a String containing the path of the file in which the PolicyLevel is stored. Returns null (C#) or Nothing (Visual Basic .NET) if the PolicyLevel does not have a storage location.

Methods

 

AddFullTrustAssembly

Adds a StrongNamedMembershipCondition to the fully trusted assembly list.

AddNamedPermissionSet

Adds a NamedPermissionSet to the policy level's set of named permission sets.

ChangeNamedPermissionSet

Replaces the PermissionSet of the specified NamedPermissionSet.

CreateAppDomainLevel

Returns a PolicyLevel configured for use as an application domain policy level.

FromXml

Reconstructs a PolicyLevel from correctly formatted XML, which is normally generated using the ToXml method.

GetNamedPermissionSet

Returns a NamedPermissionSet with the specified name.

Recover

Reverts the file where the policy level is stored to the previously stored version.

RemoveFullTrustAssembly

Removes the specified fully trusted assembly.

RemoveNamedPermissionSet

Removes the specified named permission set.

Reset

Returns the policy level to its default state. See Chapter 9 for details on the default state of the various policy levels.

Resolve

Returns a PolicyStatement containing the permissions granted by the PolicyLevel based on a specified Evidence collection. This is the same as calling Resolve on the CodeGroup contained in the policy levels RootCodeGroup property.

ResolveMatchingCodeGroups

Returns a CodeGroup tree containing all of the child code groups to which a specified Evidence collection qualifies for membership. This is the same as calling ResolveMatchingCodeGroups on the CodeGroup contained in the policy levels RootCodeGroup property.

ToXml

Returns a SecurityElement containing an XML object model of the PolicyLevel and its contents.

8.2.2.1 Managing fully trusted assemblies

The PolicyLevel class represents fully trusted assemblies by maintaining a list of StrongNameMembershipCondition objects configured to match the strong names of the trusted assembly. You can manage the fully trusted assembly list by providing StrongName or StrongNameMembershipCondition objects to the AddFullTrustAssembly and RemoveFullTrustAssembly methods. The read-only FullTrustAssemblies property gets a System.Collections.IList containing the list of fully trusted assemblies.

Example 8-1 creates a StrongNameMembershipCondition object to add an entry to the fully trusted assembly list for the HelloWorld assembly.

Example 8-1. Managing a fully trusted assembly
# C#

// Create a byte array containing the strong name public key 
// data.
byte[] publickey = { 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, 169, 206, 164, 8, 66, 197, 231, 138, 148, 74,
    99, 125, 171, 203, 120, 143, 240, 155, 104, 138, 4, 123, 15, 
    55, 85, 255, 183, 20, 111, 10, 217, 58, 127, 15, 236, 86, 16, 
    121, 222, 35, 161, 14, 122, 246, 85, 226, 162, 221, 46, 215, 
    161, 151, 183, 38, 31, 150, 198, 119, 109, 94, 11, 65, 208, 
    33, 122, 172, 106, 62, 192, 4, 35, 255, 220, 10, 43, 90, 92, 
    183, 29, 136, 57, 235, 30, 5, 127, 72, 210, 108, 215, 226, 65, 
    197, 184, 28, 129, 184, 191, 211, 159, 69, 8, 84, 116, 65, 186, 
    179, 35, 116, 174, 223, 167, 217, 116, 8, 178, 232, 213, 155, 
    172, 87, 181, 187, 61, 43, 133, 105, 10, 187 };

// Create a StrongNamePublicKeyBlob object from the 
// public key byte array.
StrongNamePublicKeyBlob blob = new StrongNamePublicKeyBlob(publickey); 
            
// Create a Version object based on the assembly version 
// number
Version version = new Version("1.1578.0.0");
    
// Create the new StrongNameMembershipCondition 
StrongNameMembershipCondition mc = 
    new StrongNameMembershipCondition (blob, "HelloWorld", version);

// Create a new application domain policy level
PolicyLevel p = PolicyLevel.CreateAppDomainLevel(  );

// Add the StrongNameMembershipCondition to the fully trusted 
// assembly list
p.AddFullTrustAssembly(mc);

# Visual Basic .NET

' Create a byte array containing the strong name public key 
' data.
Dim publickey(  ) As Byte = { 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, 169, 206, 164, 8, 66, 197, 231, 138, 148, 74, _
    99, 125, 171, 203, 120, 143, 240, 155, 104, 138, 4, 123, 15, _
    55, 85, 255, 183, 20, 111, 10, 217, 58, 127, 15, 236, 86, 16, _
    121, 222, 35, 161, 14, 122, 246, 85, 226, 162, 221, 46, 215, _
    161, 151, 183, 38, 31, 150, 198, 119, 109, 94, 11, 65, 208, _
    33, 122, 172, 106, 62, 192, 4, 35, 255, 220, 10, 43, 90, 92, _
    183, 29, 136, 57, 235, 30, 5, 127, 72, 210, 108, 215, 226, 65, _ 
    197, 184, 28, 129, 184, 191, 211, 159, 69, 8, 84, 116, 65, 186, _
    179, 35, 116, 174, 223, 167, 217, 116, 8, 178, 232, 213, 155, _
    172, 87, 181, 187, 61, 43, 133, 105, 10, 187} 

' Create a StrongNamePublicKeyBlob object from the 
' publickey byte array.
Dim blob As StrongNamePublicKeyBlob = New StrongNamePublicKeyBlob(publickey) 
 
' Create a Version object based on the assembly version 
' number
Dim version As Version = New Version("1.1578.0.0") 
 
' Create the new StrongNameMembershipCondition 
Dim mc As StrongNameMembershipCondition = _
New StrongNameMembershipCondition(blob,"HelloWorld",version) 
 
' Create a new application domain policy level
Dim p As PolicyLevel = PolicyLevel.CreateAppDomainLevel(  ) 
 
' Add the StrongNameMembershipCondition to the fully trusted 
' assembly list
p.AddFullTrustAssembly(mc)
8.2.2.2 Managing named permission sets

As described in Section 8.1.1, each policy level contains a default set of named permissions sets. These default sets cannot be deleted, and, apart from the Everything group, you cannot edit them.

To manage a policy level's named permission sets use the AddNamedPermissionSet and RemoveNamedPermissionSet methods. AddNamedPermissionSet takes a NamedPermissionSet argument, whereas RemoveNamedPermissionSet can take either a NamedPermissionSet or a String containing the name of the NamedPermissionSet to remove. You can also change the permission set of an existing NamedPermissionSet without having to add and remove it by calling the ChangeNamedPermissionSet method and passing it the name of the NamedPermissionSet to change, and a PermissionSet containing the new set of permissions.

The GetNamedPermissionSet method returns a NamedPermissionSet with the specified name, and the NamedPermissionSets property gets an IList containing the set of NamedPermissionSet objects. Example 8-2 demonstrates the manipulation of named permission sets.

Example 8-2. Manipulating named permission sets
# C#
// Create a new application domain policy level
PolicyLevel p = PolicyLevel.CreateAppDomainLevel(  );

// Get a copy of the default permission set named "Internet" and
// call it "NewPermissionSet" 
NamedPermissionSet ps = 
    p.GetNamedPermissionSet("Internet").Copy("NewPermissionSet");

// Add the new permission set
p.AddNamedPermissionSet(ps);

// Modify the permission set "NewPermissionSet" to grant unrestricted 
// access
p.ChangeNamedPermissionSet("NewPermissionSet", 
    new PermissionSet(PermissionState.Unrestricted));

// Remove the NewPermissionSet permission set
p.RemoveNamedPermissionSet("NewPermissionSet");

# Visual Basic .NET

' Create a new application domain policy level
Dim p As PolicyLevel = PolicyLevel.CreateAppDomainLevel(  ) 
 
' Get a copy of the default permission set named "Internet" and
' call it "NewPermissionSet" 
Dim ps As NamedPermissionSet = _
p.GetNamedPermissionSet("Internet").Copy("NewPermissionSet") 
 
' Add the new permission set
p.AddNamedPermissionSet(ps)
 
' Modify the permission set "NewPermissionSet" to grant unrestricted 
' access
p.ChangeNamedPermissionSet("NewPermissionSet", _
New PermissionSet(PermissionState.Unrestricted))
 
Console.WriteLine(p.ToXml(  ))
' Remove the NewPermissionSet permission set
p.RemoveNamedPermissionSet("NewPermissionSet")
8.2.2.3 Managing the code group tree

You get and set the root code group of the policy level's code group tree using the RootCodeGroup property (see Example 8-3). You must then use the methods and properties of CodeGroup, which we discussed in Section 8.2.1, to build and configure the tree hierarchy. The following code creates the policy level we used in Section 8.1.2 to demonstrate the policy resolution process (see Figure 8-4).

The difficulty in building large code trees is that the CodeGroup.AddChild method and the PolicyLevel.RootCodeGroup property both create a copy of the CodeGroup argument you pass to them. Therefore, either you must build your tree from the bottom up, or reacquire the reference to the CodeGroup you have just added before adding children to it.

Example 8-3. Manipulating the code group tree of a policy level
# C#

// Create a new application domain policy level.
PolicyLevel p = PolicyLevel.CreateAppDomainLevel(  );

// Create the MyCompany named permission set as a copy of 
// the default LocalIntranet named permission set
p.AddNamedPermissionSet(
    p.GetNamedPermissionSet("LocalIntranet").Copy("MyCompany")
);

// Create the My_Site code group that matches all code
// run from the "www.mysite.com" Site and grants it FullTrust.
UnionCodeGroup MySite = new UnionCodeGroup(
    new SiteMembershipCondition("www.mysite.com"),
    new PolicyStatement(p.GetNamedPermissionSet("FullTrust"))
);
MySite.Name = "My_Site";

// Create the Work_Site code group that matches all code
// run from the "www.company.com" Site and grants it the 
// MyCompany, permission set. 
UnionCodeGroup WorkSite = new UnionCodeGroup(
    new SiteMembershipCondition("www.company.com"),
    new PolicyStatement(p.GetNamedPermissionSet("MyCompany"))
);
WorkSite.Name = "Work_Site";

// Create the Internet_Code code group that matches all code
// run from the Internet Zone and grants it Interent permissions.
UnionCodeGroup Internet = new UnionCodeGroup(
    new ZoneMembershipCondition(SecurityZone.Internet),
    new PolicyStatement(p.GetNamedPermissionSet("Internet"))
);
Internet.Name = "Internet_Code";

// Add the My_Site and Work_Site code groups as children of the 
// Internet code group
Internet.AddChild(MySite);
Internet.AddChild(WorkSite);

// Create the My_Code code group that matches all code
// run from the My_Computer Zone and grants it FullTrust.
UnionCodeGroup MyCode = new UnionCodeGroup(
    new ZoneMembershipCondition(SecurityZone.MyComputer),
    new PolicyStatement(p.GetNamedPermissionSet("FullTrust"))
);
MyCode.Name = "My_Code";

// Create the root UnionCodeGroup that matches all code,
// but grants no permissions.
UnionCodeGroup Root = new UnionCodeGroup(
    new AllMembershipCondition(  ),
    new PolicyStatement(p.GetNamedPermissionSet("Nothing"))
);
Root.Name = "All_Code";

// Add the My_Code and Internet_Code groups as children of the 
// Root code group
Root.AddChild(MyCode);
Root.AddChild(Internet);

// Assign the code group tree to the PolicyLevel
p.RootCodeGroup = Root;

# Visual Basic .NET

' Create a new application domain policy level.
Dim p As PolicyLevel =  PolicyLevel.CreateAppDomainLevel(  ) 
 
' Create the MyCompany named permission set as a copy of 
' the default LocalIntranet named permission set
p.AddNamedPermissionSet( _
p.GetNamedPermissionSet("LocalIntranet").Copy("MyCompany"))
 
' Create the My_Site code group that matches all code
' run from the "www.mysite.com" Site and grants it FullTrust.
Dim MySite As UnionCodeGroup = New UnionCodeGroup( _
New SiteMembershipCondition("www.mysite.com"), _
New PolicyStatement(p.GetNamedPermissionSet("FullTrust"))) 
MySite.Name = "My_Site"
 
' Create the Work_Site code group that matches all code
' run from the "www.company.com" Site and grants it the 
' MyCompany, permission set. 
Dim WorkSite As UnionCodeGroup =  New UnionCodeGroup( _
New SiteMembershipCondition("www.company.com"), _
New PolicyStatement(p.GetNamedPermissionSet("MyCompany"))) 
WorkSite.Name = "Work_Site"
 
' Create the Internet_Code code group that matches all code
' run from the Internet Zone and grants it Interent permissions.
Dim Internet As UnionCodeGroup =  New UnionCodeGroup( _
New ZoneMembershipCondition(SecurityZone.Internet), _
New PolicyStatement(p.GetNamedPermissionSet("Internet"))) 
Internet.Name = "Internet_Code"
 
' Add the My_Site and Work_Site code groups as children of the 
' Internet code group
Internet.AddChild(MySite)
Internet.AddChild(WorkSite)
 
' Create the My_Code code group that matches all code
' run from the My_Computer Zone and grants it FullTrust.
Dim MyCode As UnionCodeGroup =  New UnionCodeGroup( _
New ZoneMembershipCondition(SecurityZone.MyComputer), _
New PolicyStatement(p.GetNamedPermissionSet("FullTrust"))) 
MyCode.Name = "My_Code"
 
' Create the root UnionCodeGroup that matches all code,
' but grants no permissions.
Dim Root As UnionCodeGroup =  New UnionCodeGroup( _
New AllMembershipCondition(  ), _
New PolicyStatement(p.GetNamedPermissionSet("Nothing"))) 
Root.Name = "All_Code"
 
' Add the My_Code and Internet_Code groups as children of the 
' Root code group
Root.AddChild(MyCode)
Root.AddChild(Internet)
 
' Assign the code group tree to the PolicyLevel
p.RootCodeGroup = Root

8.2.3 Programming the Security Manager

The System.Security.SecurityManager class contains a set of static members that provide access to critical security system functionality and data. Most members of SecurityManager require the caller to have the ControlPolicy permission; ControlPolicy is an element of System.Security.Permissions.SecurityPermission, which we discussed in Chapter 7. The SecurityManager functionality protected by ControlPolicy means that ControlPolicy is one of the highest trust permissions you can grant to code; you must have absolute confidence in the source and integrity of the code to which you grant the ControlPolicy permission.

Table 8-8 summarizes the members of the SecurityManager class. The far right column titled "C" identifies those members that require the ControlPolicy permission.

Table 8-8. Members of the SecurityManager class

Member

Description

C

Properties

 

CheckExecutionRights

Controls whether the runtime enforces the Execution permission.

SecurityEnabled

Controls whether the runtime enforces code-access security.

Methods

 

IsGranted

Checks if the calling code has a specified permission.

 

LoadPolicyLevelFromFile

Replaces the specified policy level with a policy level loaded from a file; the file must contain an XML representation of the policy level. Use a member of the PolicyLevelType enumeration to identify the current policy level to replace.

LoadPolicyLevelFromString

Like LoadPolicyLevelFromFile but reads the XML definition of the new policy level from a String.

PolicyHierarchy

Provides access to the currently active policy levels via an IEnumerator.

ResolvePolicy

Returns a PermissionSet containing all of the permissions the policy resolution process would grant based on a specified Evidence collection.

 

ResolvePolicyGroup

Returns an IEnumerator containing a set of CodeGroup objects representing the code groups to which a specified Evidence collection would grant membership.

 

SavePolicy

Saves the security configuration, including all policy levels.

SavePolicyLevel

Saves the specified policy level to the same location from which it was loaded.

The SecurityEnabled property is critical to the overall operation of the security system. SecurityEnabled is the master switch for code access security. Setting SecurityEnabled to false causes all demands for code-access or identity permissions to succeed; role-based security is not affected. This is effectively the same as giving all code FullTrust but has less overhead, because the runtime bypasses the security system when code makes security demands.

SecurityManager implements one of the most important members of the whole security system. The SecurityEnabled property allows you to turn off all code-access security, meaning that any code can perform any operation regardless of evidence and permissions.

The CheckExecutionRights property controls whether the runtime enforces the Execution permission, which is also an element of the SecurityPermission class. When CheckExecutionRights is false, any code can run regardless of whether it has the Execution permission.

The runtime will not store changes to the SecurityEnabled and CheckExecutionRights properties until you call the SavePolicy method. Even after calling SavePolicy, the effect on the current process is unpredictable, and only new processes will work reliably with the new settings:

# C#

// Turn on security, turn off execution checking, and save the settings
SecurityManager.SecurityEnabled = true;
SecurityManager.CheckExecutionRights = false;
SecurityManager.SavePolicy(  );

# Visual Basic.NET

' Turn on security, turn off execution checking, and save the settings
SecurityManager.SecurityEnabled = True
SecurityManager.CheckExecutionRights = False
SecurityManager.SavePolicy(  )

The reason you are most likely to be tempted to use both SecurityEnabled and CheckExecutionRights is performance. In an environment where security is not required, it makes sense to remove the overhead of runtime security checks, but the performance gains will vary greatly depending on the nature of the application. In addition, you should consider thoroughly what other factors are affecting application performance before you decide to turn off code-access security.

The IsGranted, ResolvePolicy, and ResolvePolicyGroup members are useful for testing and debugging but are not as useful as they might initially seem. IsGranted only determines if the grant set of the calling code contains a specified permission—you should not use it as a substitute for the normal security control statements we discussed in Chapter 7. ResolvePolicy and ResolvePolicyGroup both take an Evidence collection and run it through the policy resolution process. ResolvePolicy returns a grant set based on the evidence provided, and ResolvePolicyGroup returns the code groups to which the evidence qualifies for membership.

Of more use is the PolicyHierarchy method that returns a System.Collections.IEnumerator containing PolicyLevel objects for each of the active policy levels. Stepping through the enumerator, you can obtain a PolicyLevel object representing one of the active policy levels, allowing you to edit its content. Use the PolicyLevel.Label property to identify the desired PolicyLevel:

# C#

// Get the enumeration of policy levels.
IEnumerator e = SecurityManager.PolicyHierarchy(  );

// Step through the PolicyLevel objects, find the User policy
// and display it to the console.
while(e.MoveNext(  )) {

    PolicyLevel p = e.Current as PolicyLevel;

    if (p.Label == "User") {
        Console.WriteLine(p.ToXml(  ).ToString(  ));
    }
}

# Visual Basic .NET

' Get the enumeration of policy levels.
Dim e As IEnumerator =  SecurityManager.PolicyHierarchy(  ) 
 
' Step through the PolicyLevel objects, find the User policy
' and display it to the console.
While e.MoveNext(  )
 
    Dim p As PolicyLevel =  CType(e.Current,PolicyLevel)
 
    If p.Label = "User" Then
        Console.WriteLine(p.ToXml(  ).ToString(  ))
    End If
End While

After editing an active PolicyLevel, you must call SavePolicy or SavePolicyLevel method if you want to save the changes to disk so that they affect future processes. SavePolicy saves all policy levels and policy settings, but SavePolicyLevel saves the configuration of a specified PolicyLevel. SavePolicyLevel saves the specified PolicyLevel back to its source location, which is contained in the PolicyLevel.StoreLocation property.

8.2.4 Programming Application Domain Policy

Application domain policy is the final layer of security policy evaluated when determining the code-access permissions to grant an assembly. Regardless of the permissions granted to an assembly based on the enterprise, machine, and user policies, you can further lock down its permissions using application domain policy. Application domain policy is resolved even if the assembly is a member of a LevelFinal code group in a higher policy level.

You configure application domain policy by calling the AppDomain.SetAppDomainPolicy method and passing a configured PolicyLevel object. Application domain policy can be set only once per application domain—setting it a second time results in a System.Security.Policy.PolicyException. Your code must have the ControlDomainPolicy permission to call the SetAppDomainPolicy method. ControlDomainPolicy is an element of SecurityPermission, which we discussed in Chapter 7:

# C#

public void SetAppDomainPolicy(
    PolicyLevel domainPolicy
);

# Visual Basic .NET

NotOverridable Public Sub SetAppDomainPolicy( _
    ByVal domainPolicy As PolicyLevel
)

Any assemblies loaded into an application domain before you configure the application domain policy are subject to the standard policy resolution results from the enterprise, machine, and user levels. Application domain policy affects only those assemblies loaded after it's set. This allows you to load assemblies that require higher trust, before locking down the application domain through the additional layer of policy.

The need to implement application domain policy will not be an everyday occurrence, but when writing applications that are responsible for loading other code, the capability is invaluable.

Example 8-4 uses the SetDomainPolicy method to create a policy level with the code group structure shown in Figure 8-10 and assign it to a specified application domain. The root code group matches all code but grants the Nothing permission set. The second layer of code groups determines the permissions to grant based on Url evidence of a loaded assembly. You grant assemblies loaded from the Trusted folder the FullTrust permission set, assemblies loaded from the Untrusted folder the Internet permission set, and assemblies loaded from the Demo the Execution permission set. You grant all other assemblies no permissions. Because we expect an assembly to have only one piece of Url evidence, we make the root code group of type FirstMatchCodeGroup, saving the runtime from performing unnecessary policy resolution processing.

Figure 8-10. Sample application domain policy
figs/pdns_0810.gif
Example 8-4. Creating a policy level and assigning it to an application domain
# C#

private static void SetDomainPolicy(AppDomain domain) {
        
        // Create a new PolicyLevel for the application domain
    PolicyLevel policy = PolicyLevel.CreateAppDomainLevel(  );

    // Create a new FirstMatchCodeGroup that matches all code and
    // grants the named permission set "Nothing".
    policy.RootCodeGroup = new FirstMatchCodeGroup(
        new AllMembershipCondition(  ),
        new PolicyStatement(policy.GetNamedPermissionSet("Nothing"))
    );
        
    // Determine the base path name for the URL evidence
    String basePath = "file://" + AppDomain.CurrentDomain.BaseDirectory;

    // Create the code groups that apply to the assemblies loaded 
    // from specific folders. Make each of the the code groups
    // Exclusive so that they set the absolut maximum permisisons a
    // plugin can have.
    policy.RootCodeGroup.AddChild(new UnionCodeGroup(
        new UrlMembershipCondition(basePath + "Trusted/*"),
        new PolicyStatement(policy.GetNamedPermissionSet("FullTrust"),
        PolicyStatementAttribute.Exclusive)
    ));

    policy.RootCodeGroup.AddChild(new UnionCodeGroup(
        new UrlMembershipCondition(basePath + "Untrusted/*"),
        new PolicyStatement(policy.GetNamedPermissionSet("Internet"),
        PolicyStatementAttribute.Exclusive)
    ));

    policy.RootCodeGroup.AddChild(new UnionCodeGroup(
        new UrlMembershipCondition(basePath + "Demo/*"),
        new PolicyStatement(policy.GetNamedPermissionSet("Execution"),
        PolicyStatementAttribute.Exclusive)
    ));

    // Assign the policy to the specified application domain.
    domain.SetAppDomainPolicy(policy);
}

# Visual Basic .NET

Private Shared Sub SetDomainPolicy(ByVal domain As AppDomain)
 
        ' Create a new PolicyLevel for the application domain
    Dim policy As PolicyLevel = _
    PolicyLevel.CreateAppDomainLevel(  ) 
 
    ' Create a new FirstMatchCodeGroup that matches all code and
    ' grants the named permission set "Nothing".
    policy.RootCodeGroup = New FirstMatchCodeGroup( _
    New AllMembershipCondition(  ), _
    New PolicyStatement(policy.GetNamedPermissionSet("Nothing")))
 
    ' Determine the base path name for the URL evidence
    Dim basePath As String = _
    "file://" & AppDomain.CurrentDomain.BaseDirectory;

    ' Create the code groups that apply to the assemblies loaded 
    ' from specific folders. Make each of the the code groups
    ' Exclusive so that they set the absolut maximum permisisons a
    ' plugin can have.
    policy.RootCodeGroup.AddChild(New UnionCodeGroup( _
    New UrlMembershipCondition(basePath & "Trusted/*"), _
    New PolicyStatement(policy.GetNamedPermissionSet("FullTrust"), _
    PolicyStatementAttribute.Exclusive)))
 
    policy.RootCodeGroup.AddChild(New UnionCodeGroup( _
    New UrlMembershipCondition(basePath & "Untrusted/*"), _
    New PolicyStatement(policy.GetNamedPermissionSet("Internet"), _
    PolicyStatementAttribute.Exclusive)))
 
    policy.RootCodeGroup.AddChild(New UnionCodeGroup( _
    New UrlMembershipCondition(basePath & "Demo/*"), _
    New PolicyStatement(policy.GetNamedPermissionSet("Execution"), _
    PolicyStatementAttribute.Exclusive)))

 
    ' Assign the policy to the specified application domain.
    domain.SetAppDomainPolicy(policy)
End Sub
    [ Team LiB ] Previous Section Next Section