Changing Security Policy by Programming Directly to the Security APIs

for RuBoard

Changing Security Policy by Programming Directly to the Security APIs

Instead of using a particular tool to alter or view security policy, you can also use the Security classes that ship with the .NET Framework base class library to view or change policy directly. In fact, both the Caspol tool as well as the .NET Framework Configuration tool have been implemented using these classes. To solve everyday administrative tasks , it will be very unlikely that you need to make use of these APIs, but the following are some situations in which access to these classes will be necessary:

  • You want to write your own policy administration tool.

  • You want to write a policy analysis tool that checks for policy invariants particular to your business or home situation.

  • You want to write an alternative security scripting system.

  • You want to integrate custom security objects into live policy by creating and setting state on the custom security objects programmatically, as opposed to creating an XML serialization file representing the state you want (the only way the tools currently allow you to add custom security objects).

  • You are simply curious how the security model is implemented.

The rest of this chapter will introduce you to the most relevant security objects and how to use them. You can use this section as a starting point for satisfying any of the previous situations.

NOTE

For more information about the security objects, please consult the .NET Framework SDK. You may also want to check www.gotdotnet.com. This site contains a security section dedicated to the .NET Framework security system, and the .NET Framework security team may have posted helpful samples and papers there by the time this book appears in print.


Overview of the Security Classes Used for Policy Changes

The overall security model that you have learned about in the first section of this book ( especially Chapters in 5 8) is still relevant in discussing the particular classes you can use to change security policy. Indeed, you will see that the class structure maps very well to the underlying security model to which you have been introduced.

The security classes that implement the security model can be found in the following namespaces:

  • System.Security This namespace contains the SecurityManager class from which you can access the policy levels of current security policy. Among other things, the namespace also contains the base classes for permission types, as well as the NamedPermissionSet class. You will access the latter class when viewing or modifying the known named permission sets at a policy level.

  • System.Security.Policy This namespace largely contains the classes implementing the policy system. Specifically, it contains the PolicyLevel class implementing a policy level. From the policy level class, you can access a hierarchy of code groups, a list of named permission sets (see the NamedPermissionSet type in System.Security ), as well as the Policy Assemblies list. This namespace contains the base class for all code groups and some specific code group implementations . Most notably, it contains the UnionCodeGroup class that represents the code group type used to implement most of default policy. The namespace also contains the interface definition of membership conditions ( IMembershipCondition ) and the implementations of the standard membership condition types shipped in the .NET Framework (URL, zone, site, hash, publisher certificate, strong name , application directory and all code).

  • System.Security.Permissions This namespace contains the class implementations of most of the standard permissions shipping with the .NET Framework. This namespace also contains the implementation of the respective security custom attribute classes for using these permissions declaratively .

NOTE

Not all permissions shipping with the .NET Framework are contained in the System.Security.Permissions namespace. This namespace mostly has the implementation of the permissions protecting core runtime resources. Other permissions will be located in the namespace that implements and defines the protected resources. For example, the permissions governing access to networking resources are contained in the System.NET namespace. By searching the SDK for the keyword "permission," you will be able to find out all the permission types shipping in the SDK, as well as other helpful information regarding the implementation of your own permission type.


Examples of Using the Security Classes for Accessing and Changing Policy

This section will show you how to solve some of the standard policy analysis or policy change tasks. You can use the code shown in this section as a starting point. For complete coverage of the types used, please see the SDK reference documentation.

The class from which you will start all your policy change and analysis tasks is the SecurityManager class. Its static methods and properties provide the interface to the current security policy state of your machine. From the SecurityManager , you can get access to policy levels, as well as save policy changes that you have done programmatically.

Resolving Evidence Against Current Security Policy

A common task you may want to do is to find out what permissions the security policy would grant an assembly that had certain forms of evidence. You can simulate this situation by using the SecurityManager . The SecurityManager object has a static member method ( ResolvePolicy(Evidence) ) that allows you to pass in an evidence object and receive back a permission set object representing the policy resolution against the evidence you have provided. In Listing 19.1, the XML serialization of the permission set returned by the policy resolution given your evidence input is printed to the console.

Listing 19.1 Calculating the Permissions Granted by Policy Based on Specific Evidence Set Resolve.cs
 using System; using System.Security; using System.Security.Policy; using System.IO; class Resolve {     static void Main(string[] args)     {         Zone zoneevidence = new Zone(System.Security.SecurityZone.intranet);         Site siteevidence = new Site("www.test.com");         //create evidence object         Evidence evidence = new Evidence();         //setting assembly evidence         evidence.AddHost(zoneevidence);         evidence.AddHost(siteevidence);         PermissionSet result=SecurityManager.ResolvePolicy(evidence);         //printing the XML serialization of the returned permission set         Console.WriteLine(result.ToXml().ToString());         String t = Console.ReadLine();     } } 

NOTE

To compile the listings given in this chapter, save the source code into a filename .cs file. To run the the C# compiler from the command line, type in the following:

 %Windows%\Microsoft.NET\Framework\v[version number]\csc.exe filename.cs 

%Windows% stands for the installation directory of Windows on your machine, version stands for the Framework version installed on your machine, and filename is the name of the file into which you saved the preceding code listing.


Resolving Evidence Against a Specific Policy Level

The SecurityManager class provides an enumeration of the policy levels. This enumeration, of type IEnumerable , is used to access and change a particular policy level. Note that this is why the ICollections namespace is included in this sample. The policy level enumerator returned by SecurityManager stores the policy levels in the following order:

  1. Enterprise policy

  2. Machine policy

  3. User policy

  4. Appdomain policy (if present)

In the following example, we move the enumerator to the machine policy level and call the Resolve(Evidence) method on that level to get the set of permissions that this level would grant an assembly with the provided evidence. Note that we do not get a PermissionSet object back but something called a PolicyStatement . A PolicyStatement is nothing more than a permission set plus a couple of attributes: the level final and exclusive attributes, to be precise.

NOTE

When a level final attribute is encountered, policy evaluation stops at the respective policy level; no policy levels below the one containing the level final attribute are evaluated. In order to capture the information that a level final attribute was encountered during the evaluation of a policy level, the return type of a policy resolution over a policy level needs to return more than a permission set which only consists of a list of permissions. The return type also needs to state whether the level final attribute was encountered . This is the reason why policy levels return a PolicyStatement rather than a simple permission set, which captures exactly that information.


The code in Listing 19.2 demonstrates how to construct an evidence set for which to do policy resolution. It then shows you how to access the machine policy level and resolve against the constructed evidence set.

Listing 19.2 Enumeration of the Policy Levels Resolvelevel.cs
 using System; using System.Security; using System.Security.Policy; using System.Security.Permissions; using System.IO; using System.Collections; class Resolve {     static void Main(string[] args)     {         Zone zoneevidence = new Zone(System.Security.SecurityZone.intranet);         Site siteevidence = new Site("www.microsoft.com");         //create evidence object         Evidence evidence = new Evidence();         evidence.AddHost(zoneevidence);         evidence.AddHost(siteevidence);         //getting policy levels enumerator         IEnumerator policylevels=SecurityManager.PolicyHierarchy();         //moving to machine policy level         policylevels.MoveNext();         policylevels.MoveNext();         PolicyStatement policystatement =           ((PolicyLevel)policylevels.Current).Resolve(evidence);         //Printing machine policy resolution results         Console.WriteLine(policystatement.PermissionSet.ToString());         String t = Console.ReadLine(); } } 
Building and Adding a New Named Permission Set to a Policy Level

To add a new permission set to a policy level, you need to create an instance of the NamedPermissionSet class and add permission instances to it that represent the specific level of trust you want this permission set to express. After you have constructed the permission set, you simply need to call the AddPermissionSet method on a policy level with the newly constructed permission set and save the policy changes. Listing 19.3 demonstrates this for you. A reference to the System.Security.Permission namespace has been added to use the UIPermermission class without having to fully qualify the classname.

NOTE

The constructor of the NamedPermissionSet class requiring only a string as argument will set the permission set to the unrestricted state. To start with a permission set state to which you can still further add permissions, you need to use the overload that takes both the name and a PermissionState argument, where the permission state is set to nothing (Listing 19.3 is an example of using that overload).


Listing 19.3 Adding a New Permission Set AddNewPset.cs
 using System; using System.Security; using System.Security.Policy; using System.Security.Permissions; using System.IO; using System.Collections;     class NewPermissionSet     {         static void Main(string[] args)         {             //create new NamedPermissionSet instance                            NamedPermissionSet pset = new NamedPermissionSet( graphics/ccc.gif "ClipBoardPset",PermissionState.None);             //expressing unrestricted permission to do UI                                           pset.AddPermission(new UIPermission( graphics/ccc.gif UIPermissionClipboard.AllClipboard));             //getting policy levels enumerator             IEnumerator policylevels=SecurityManager.PolicyHierarchy();             //moving to machine policy level             policylevels.MoveNext();             policylevels.MoveNext();             PolicyLevel machinepolicy = (PolicyLevel)(policylevels.Current);             //add the new permission set             machinepolicy.AddNamedPermissionSet(pset);             //Save the changes             SecurityManager.SavePolicy();         }     } 

NOTE

The SecurityManager.SavePolicy() call will fail if the user context in which this line of code is run does not have write access to the respective policy level. If you are not certain that your code will be run on behalf of users that have the appropriate access to the policy levels, you should surround the SavePolicy call with a try-catch exception handling clause.


Removing a Permission Set from a Policy Level

Removing a permission set is simply a matter of supplying the respective permission set name and calling the RemoveNamedPermissionSet method on the policy level object. This is demonstrated in Listing 19.4:

Listing 19.4 Removing a Permission Set Rempset.cs
 using System; using System.Security; using System.Security.Policy; using System.IO; using System.Collections;     class Rempset     {         static void Main(string[] args)         {             IEnumerator policylevels=SecurityManager.PolicyHierarchy();             //moving to machine policy level             policylevels.MoveNext();             policylevels.MoveNext();             PolicyLevel machinepolicy = (PolicyLevel)(policylevels.Current);             //remove permission set             machinepolicy.RemoveNamedPermissionSet("ClipBoardPset");             //Save the changes             SecurityManager.SavePolicy();         }     } 

TIP

To check the state of policy before and after running the sample code, you may want to use the lg , -lp , and ld options of Caspol.


Changing a Permission Set

Permission sets can easily be changed. You only need to create a new permission set and pass it into the ChangeNamedPermissionSet method on the PolicyLevel object. It will return the newly created named permission set, as shown in Listing 19.5.

Listing 19.5 Change a Permission Set ChgPset.cs
 using System; using System.Security; using System.Security.Policy; using System.Security.Permissions; using System.IO; using System.Collections;     class ChangePset     {         static void Main(string[] args)         {             PermissionSet pset = new PermissionSet(PermissionState.None);             pset.AddPermission(new UIPermission(PermissionState.Unrestricted));             IEnumerator policylevels=SecurityManager.PolicyHierarchy();             //moving to machine policy level             policylevels.MoveNext();             policylevels.MoveNext();             PolicyLevel machinepolicy = (PolicyLevel)(policylevels.Current);             //change permission set             NamedPermissionSet namedpset =   machinepolicy.ChangeNamedPermissionSet("UIPermSet",pset);             //Save the changes             SecurityManager.SavePolicy();         }     } 

NOTE

In order for the preceding sample to work, you need to have added a permission set with name UIPermSet to the machine policy. You can do so using the .NET Framework Configuration tool (see Chapter 18 for details on how to use this tool). In the .NET Framework Configuration tool:

  1. Expand the Runtime Security Policy node.

  2. Expand the Machine node.

  3. Right-click on the Permission Sets node and select the New option.

  4. Type in UIPermSet as permission set name.

  5. Set any permission state for the permission set and finish the wizard.

Now you can run the preceding example.


Adding a Code Group to a Policy Level

Listing 19.6 will add an exclusive code group under the root code group of the machine policy level. If this sample is run over default security policy, it will have the effect of disallowing any assemblies from the www.aw.com site to execute and access any protected resources.

Listing 19.6 Add a Code Group addcg.cs
 using System; using System.Security; using System.Security.Policy; using System.Security.Permissions; using System.IO; using System.Collections; class AddCodeGroup {    static void Main(string[] args)    {       //getting the policy level enumerator       IEnumerator policylevels=SecurityManager.PolicyHierarchy();       //moving to machine policy level       policylevels.MoveNext();       policylevels.MoveNext();       PolicyLevel machinepolicy = (PolicyLevel)(policylevels.Current);       //getting the root code group of the code group hierarchy       CodeGroup rootcg = machinepolicy.RootCodeGroup;       //creating a site membership condition for the new code group       SiteMembershipCondition sitemship =          new SiteMembershipCondition("www.aw.com");       //get the intranet permission set from the permission set list of the machine policy       PermissionSet pset =          machinepolicy.GetNamedPermissionSet("Nothing");       //Creating the policy statement, including the Nothing permission set       //making the code group exclusive       PolicyStatement policystatement =          new PolicyStatement(pset,PolicyStatementAttribute.Exclusive);       //create the new code group       //with the new membership condition and policystatement       UnionCodeGroup newcg =          new UnionCodeGroup(sitemship,policystatement);       //add the new code group under the root code group       rootcg.AddChild(newcg);       machinepolicy.RootCodeGroup = rootcg;       //Save the changes       SecurityManager.SavePolicy();    } 
Removing a Code Group from a Policy Level

To remove a child code group from a code group, you need to iterate through the children on that code group until you find the code group you want to delete. You then pass a reference to that code group to the RemoveChild method on the parent code group. Listing 19.7 shows you how to delete a code group under the root code group of the machine policy.

Listing 19.7 Remove a Code Group remcg.cs
 using System; using System.Security; using System.Security.Policy; using System.Security.Permissions; using System.IO; using System.Collections; class RemCodeGroup {     static void Main(string[] args)     {         //getting the policy level enumerator         IEnumerator policylevels=SecurityManager.PolicyHierarchy();         //moving to machine policy level         policylevels.MoveNext();         policylevels.MoveNext();         PolicyLevel machinepolicy = (PolicyLevel)(policylevels.Current);         //getting the root code group of the code group hierarchy         CodeGroup rootcg = machinepolicy.RootCodeGroup;         //getting all the child code groups pf the root code group         IEnumerator rootchildren = (rootcg.Children).GetEnumerator();         //finding and removing the test code group         while(rootchildren.MoveNext())         {             if(((CodeGroup)(rootchildren.Current)).Name=="test")             {                 CodeGroup codegroup = (CodeGroup)rootchildren.Current;                 //remove the code group                 rootcg.RemoveChild(codegroup);                 //save it                 SecurityManager.SavePolicy();             }         }     } } 

The preceding code only iterates through the child code groups under the root code group of the machine policy level. You will need to extend the preceding code if you want to delete a code group at an arbitrary location in the code group tree. In that case, your code should do a recursive tree search for the respective code group. Remember that the code group tree can be irregular. Also, as will be explained in the following section, if you modify any code groups that are not direct children of the root code group, you will need to re-create the affected subsection of the tree (see the following section).

Changing the Properties of a Code Group

There is unfortunately no method you can call that will change the property of a referred code group directly. Instead, you need to perform the following steps:

  1. Iterate through the code group hierarchy until you find the code group you want to alter.

  2. Store a reference to that code group in a local variable.

  3. Delete the code group from the parent's set of code group using the RemoveChildCodeGroup method.

  4. Modify the properties of the locally stored code group (or simply create a new one).

  5. Add the new code group to the parent's list of code groups using the add method.

  6. If the parent code group is not the root code group, you will need to repeat this procedure for the parent code group (save the parent code group locally, remove it from the list of code groups of its parent, and add it back in using the AddChild method). You need to repeat this until you hit the root code group.

The reason for this elaborate set of steps is the fact that except for the root code group, you are never given a direct reference to the actual code groups constituting policy, but rather a copy of the code groups.

NOTE

Note that the previous steps imply that the order of child code groups does not matter. This only holds for code groups whose evaluation semantics does not rely on the order of its child code groups, such as the UnionCodeGroup used throughout default policy. For a more general solution, you need to preserve the order of code groups, so remove and add in all child code groups in the order you found them before modifying one of them.


Listing 19.8 demonstrates how to modify the properties of a child code group of the root of machine policy.

Listing 19.8 Change a Code Group chgcg.cs
 using System; using System.Security; using System.Security.Policy; using System.Security.Permissions; using System.IO; using System.Collections; class ChangeCodeGroup {     static void Main(string[] args)     {         //getting the policy level enumerator         IEnumerator policylevels=SecurityManager.PolicyHierarchy();         //moving to machine policy level         policylevels.MoveNext();         policylevels.MoveNext();         PolicyLevel machinepolicy = (PolicyLevel)(policylevels.Current);         //getting the root code group of the code group hierarchy         CodeGroup rootcg = machinepolicy.RootCodeGroup;         //getting all the child code groups pf the root code group         IEnumerator rootchildren = (rootcg.Children).GetEnumerator();         //finding and changing the internet zone code group         bool found=false;         bool listend=false;         while(rootchildren.MoveNext())         {             if(((CodeGroup)(rootchildren.Current)).Name=="Internet_Zone")             {                 //make a copy of the code group to change                 CodeGroup codegroup = (CodeGroup)rootchildren.Current;                 //delete the code group to be changed from policy                 rootcg.RemoveChild(codegroup);                 //get the new permission set for this code group                 PermissionSet pset =                    machinepolicy.GetNamedPermissionSet("Nothing");                 //wrapped into a policy statement, that would allow for change                 //of code group flags also                 codegroup.PolicyStatement=new PolicyStatement(pset);                 codegroup.Description="Changed code group";                 //add code group back in, this will not preserver cg order!!                 //you will have to remove and add in all children of a cg if you                 //need to preserve code group order                 rootcg.AddChild(codegroup);                 machinepolicy.RootCodeGroup = rootcg;                 //save it                 SecurityManager.SavePolicy();             }         }     } } 

The key steps in Listing 19.8 is the removal of the code group to be changed using the RemoveChild method, the subsequent modification of the code group, and the re-addition of the modified code group to the machine policy.

Adding an Assembly to the Policy Assemblies List

As you will remember from Chapters 8 and 18, the Policy Assemblies list is used to keep a list of all the assemblies required to implement security objects that appear in the code group hierarchy or permission sets of a policy level. All assemblies in this list must be strong named and in the Global Assembly Cache (GAC). Consequently, to add a specific assembly to this cache programmatically, you will need to get the strong name of that assembly. To do so, the following sample in Listing 19.9 uses the AssemblyName 's static GetAssemblyName method that returns an assembly name. The assembly name, in turn , contains the version, name, and strong name public key token of the assembly to be added to the Policy Assemblies list.

Listing 19.9 Add to a Policy Assembly List addtopolylist.cs
 using System; using System.Security; using System.Security.Policy; using System.Security.Permissions; using System.Collections; using System.Reflection; class AddToPolicyAssemblies {     static void Main(string[] args)     {         //getting the policy level enumerator         IEnumerator policylevels=SecurityManager.PolicyHierarchy();         //moving to enterprise policy level         policylevels.MoveNext();         PolicyLevel entpolicy = (PolicyLevel)(policylevels.Current);         //creating the strongname for the assembly to add         AssemblyName aname = AssemblyName.GetAssemblyName("Caspol.exe ");         //by first creating the strong name public key blob type instance         StrongNamePublicKeyBlob spt = new StrongNamePublicKeyBlob(aname.GetPublicKeyToken( graphics/ccc.gif ));         //using that and the assembly name and version         //to create a strong name instance         StrongName sn = new StrongName(spt,aname.Name,aname.Version);         //adding the assembly identified by sn to the policy assemblies list         entpolicy.AddFullTrustAssembly(sn);         //save the changes         SecurityManager.SavePolicy();     } } 

NOTE

Use the RemoveFullTrustAssembly method to remove an assembly from the Policy Assemblies list.


The two key points to remember are to first create an instance of the StrongName class that contains the strong name of the assembly you want to add to the Policy Assemblies list. Subsequently, you need to use the StrongName instance as argument to the AddFullTrustAssembly method, which will add the strong name reference to the policy assemblies list.

Resetting a Policy Level to Its Default State

To reset a policy level, simply call the Reset method on the level you want to bring back to the default security policy state. If you want to go back to the default security policy state of the .NET Framework, simply call the Reset method on all policy levels. After your reset calls, you still need to save the change by calling the SavePolicy method on the SecurityManager class. Listing 19.10 demonstrates how to reset the enterprise policy level to its default policy state.

Listing 19.10 Reset a Policy Level resetpol.cs
 using System; using System.Security; using System.Security.Policy; using System.Collections;     class Resetpol     {         static void Main(string[] args)         {             //getting the policy level enumerator             IEnumerator policylevels=SecurityManager.PolicyHierarchy();             //moving to enterprise policy level             policylevels.MoveNext();             PolicyLevel entpolicy = (PolicyLevel)(policylevels.Current);             //calling the reset method on the PolicyLevel object             //this will cause the policy level to revert its default state             entpolicy.Reset();             //save the changes             SecurityManager.SavePolicy();         }     } 

The main point to remember when doing a reset on a policy level is to not forget to actually save the policy changes by calling the SavePolicy method on the SecurityManager class.

for RuBoard


. NET Framework Security
.NET Framework Security
ISBN: 067232184X
EAN: 2147483647
Year: 2000
Pages: 235

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