[ Team LiB ] Previous Section Next Section

8.3 Extending the .NET Framework

In Section 6.3.1.1, we developed the Author evidence class, which identifies the developer of an assembly. In this section, we extend CAS so that you can base policy resolution on the presence of Author evidence.

8.3.1 Custom Membership Conditions Explained

To enable Author evidence to drive policy resolution, first you create a custom membership condition class. Using the techniques described in Section 8.2, you can then use the custom membership condition in code groups to test the values of any Author evidence presented by an assembly or application domain. Here are the key requirements of a custom membership condition class:

Implement IMembershipCondition

Membership condition classes must implement the System.Security.Policy.IMembershipCondition interface, which extends both the System.Security.ISecurityPolicyEncodable and System.Security.ISecurityEncodeable interfaces. We summarize the members of each interface in Table 8-9.

Implement default constructor

Membership condition classes must implement a default (empty) constructor. The runtime uses this constructor when it needs to create a membership condition object from XML contained in the security policy files.

Make serializable

Membership condition classes must be serializable. Because of their simplicity, application of the System.SerializableAttribute is usually sufficient. Those membership condition classes that have more complex serialization requirements must implement the System.Runtime.Serialization.ISerializable interface.

Unless you implement ISerializable, deserialization of an object does not result in the execution of a constructor, and therefore you should not depend on constructor logic to configure or modify the state of the deserialized object.

Table 8-9. Members of the IMembershipCondition, ISecurityPolicyEncodable, and ISecurityEncodeable interfaces

Member

Description

IMembershipCondition interface

 

Check

Determines whether an Evidence collection contains evidence that satisfies the membership criterion

Copy

Returns a copy of the membership condition

Equals

Tests a System.Object against the IMembershipCondition object

ToString

Returns a System.String representation of the membership condition

ISecurityEncodable interface

 

FromXml

Reconstructs the IMembershipCondition object's state from a specified SecurityElement

ToXml

Creates and returns a SecurityElement containing an XML object model representing the IMembershipCondition object's state

ISecurityPolicyEncodable

 

FromXml

The same as ISecurityEncodable.ToXml but handles any policy level-specific state

ToXml

The same as ISecurityEncodable.FromXml but includes any policy level-specific state

In addition to these requirements, many of the guidelines we outlined for evidence in Chapter 6 also hold true for custom membership conditions. These include:

  • Make membership conditions simple. Membership conditions classes have access to the entire Evidence collection of an assembly or application domain in the Check method, and therefore it is possible to create custom membership conditions that test complex membership criteria spanning multiple items of evidence. You must consider who will use your membership condition class and design it appropriately. Creating membership classes that test multiple or complex sets of data can result in security policy that is difficult for security administrators and users to configure and understand.

  • Make membership conditions lightweight. You should consider the amount of memory used by your membership condition classes as well as avoid lengthy processing tasks, such as network, file, and database access during membership evaluation.

  • Make membership conditions noninheritable. It is safest to mark your membership condition classes as sealed in C# and NotInheritable in Visual Basic .NET so that malicious code cannot subclass them in an attempt to change its behavior and subvert CAS, or confuse security administrators.

  • Override Object.ToString. The security administration tools use ToString to display a message that describes the condition and value that a membership is configured to match against. The use of this information necessitates that the output be concise. For example, "Site = *.oreilly.com" or "Author = Peter."

8.3.2 Defining the AuthorMembershipCondition Class

Following the naming pattern used in the .NET class library, we name the membership condition class AuthorMembershipCondition. The AuthorMembershipCondition class is relatively simple; however, there is still a fair amount of code involved because we have to implement the eight members defined in the IMembershipCondition interface. We will break the AuthorMembershipCondition class into manageable sections for explanation.

The class declaration for AuthorMembershipCondition specifies that it is sealed (C#) and NotInheritable (Visual Basic .NET) to stop malicious code from subclassing it in order to subvert CAS. You should also specify the implementation of IMembershipCondition and annotate AuthorMembershipCondition with the System.Serializable attribute—satisfying the two major requirements of membership condition classes.

We define a single private System.String data member named AuthorName, which will contain the name of the author on which an AuthorMembershipCondition object will base its membership test:

# C#

using System;
using System.Security;
using System.Collections;
using System.Reflection;
using System.Security.Policy;

[assembly:AssemblyKeyFile("Keys.snk")]
[assembly:AssemblyVersion("1.0.0.0")]

namespace ORA.DotNetSecurity.Policy {

    [Serializable]
    public sealed class AuthorMembershipCondition : IMembershipCondition {
    
        private string AuthorName = "";


# Visual Basic .NET

Imports System
Imports System.Security
Imports System.Collections
Imports System.Reflection
Imports System.Security.Policy

<assembly:AssemblyKeyFile("Keys.snk")>
<assembly:AssemblyVersion("1.0.0.0")> 
 
Namespace ORA.DotNetSecurity.Policy
 
    <Serializable> _ 
    Public NotInheritable Class AuthorMembershipCondition
         Implements IMembershipCondition
 
        Private AuthorName As String =  ""

We define two constructors for AuthorMembershipCondition. The first is a default (empty) constructor. The runtime uses this constructor to create AuthorMembershipCondition objects before calling the FromXml method to recreate the state of an AuthorMembershipCondition from XML stored in the policy configuration files. The second constructor is for general use and takes a System.String argument containing the name of the author on which the AuthorMembershipCondition should base its membership test:

# C#

        public AuthorMembershipCondition(  ) {        
        
                this.AuthorName = "";
        }
        
        public AuthorMembershipCondition(string author) {
        
            if (VerifyAuthorName(author)) {            
                this.AuthorName = author;
            }
        }


# Visual Basic.NET

        Public  Sub New(  ) 
                Me.AuthorName = ""
        End Sub
 
        Public  Sub New(ByVal author As String) 
            If VerifyAuthorName(author) Then
                Me.AuthorName = author
            End If
        End Sub

We include a Name property to provide controlled access to the private AuthorName data member and the private VerifyAuthorName utility method, which we use in both the Name property and class constructor to validate the author name provided. To simplify AuthorMembershipCondition you check only if the author name is null (C#) or Nothing (Visual Basic .NET)—in which case, you throw a System.ArgumentNullException. In a production-quality membership condition class, it is good practice to verify the condition data more thoroughly and throw a System.ArgumentException if the data is invalid. For example, it would be a good idea to check for invalid characters and limit the size of the name provided:

  # C#

        // Property to get/set the author name
        public string Name {        
        
            get { 
                return AuthorName;
            }
            
            set {        
                if (VerifyAuthorName(value)) {            
                    this.AuthorName = value;
                }
            }
        }        
        
        // Utility method to verify that the author name
        // is not null.
        private bool VerifyAuthorName(string author) {
        
            if (author == null) {        
                throw new ArgumentNullException("author");
            } else {
                return true;
            }
        }


# Visual Basic .NET

        ' Property to get/set the author name
        Public Property Name(  ) As String
                Get 
                Return AuthorName
                End Get
                Set (ByVal Value As String) 
                If VerifyAuthorName(Value) Then
                    Me.AuthorName = Value
                End If
                End Set
        End Property
 
        ' Utility method to verify that the author name
        ' is not null.
        Private Function VerifyAuthorName(ByVal author As String) _
            As Boolean
 
            If author Is Nothing Then
                Throw New ArgumentNullException("author")
            Else 
                Return True
            End If
        End Function

The real work of the AuthorMembershipCondition class takes place in the Check method. The Check method takes an Evidence collection and enumerates the contained evidence objects to determine if any of them are Author objects. If Check finds an Author object, it compares the Author.Name property with its own private AuthorName data member. If the two names match, Check returns true; otherwise, Check returns false.

For simplicity, we have implemented a straightforward case-sensitive string comparison, but you might also consider support for non-case-sensitive comparisons and wildcard matches. The membership condition logic can be arbitrarily complex, but remember that the runtime may call Check many times during policy resolution, and time-consuming membership condition evaluations will affect application performance:

# C#

        // Determines whether the Evidence meets the membership 
        // condition.
        public bool Check( Evidence evidence ) {
        
                // Return false if the Evidence is null
            if (evidence == null) {
                return false;
            }
        
            Author auth = null;
    
            // Enumerate across the host and assembly evidence
            // collections.
            IEnumerator enumerator = evidence.GetEnumerator(  );
            while (enumerator.MoveNext(  ))
            {            
                auth = enumerator.Current as Author;
                
                // If the evidence is of type Author and the 
                // author names match, the evidence meets the
                // membership condition.
                if (auth != null) {        
                    if (auth.Name == this.AuthorName) {
                        return true;
                    }
                }
            }
            return false;
        }


# Visual Basic .NET

        ' Determines whether the Evidence meets the membership 
        ' condition.
        Public Function Check(ByVal evidence As Evidence) As Boolean _
            Implements IMembershipCondition.Check
 
                ' Return false if the Evidence is Nothing
            If evidence Is Nothing Then
                Return False
            End If
 
            Dim auth As Author =  Nothing 
 
            ' Enumerate across the host and assembly evidence
            ' collections.
            Dim enumerator As IEnumerator =  evidence.GetEnumerator(  ) 
            While enumerator.MoveNext(  )

                auth = CType(enumerator.Current, Author)
 
                ' If the evidence is of type Author and the 
                ' author names match, the evidence meets the
                ' membership condition.
                If Not auth Is Nothing Then
                    If auth.Name = Me.AuthorName Then
                        Return True
                    End If
                End If
            End While

            Return False
        End Function

The remaining members of the IMembershipCondition interface are straightforward. The Copy method returns a clone of the current AuthorMembershipCondition object, and the Equals method compares two AuthorMembershipCondition objects for equality based on the value of their private AuthorName data members instead of comparing object references. Because Object also defines an Equals method, you must override it with the one defined in the IMembershipCondition interface. As a result, you must also override the GetHashCode method:

# C#

        // Creates a copy of the membership condition
        public IMembershipCondition Copy(  ) {
        
            return new AuthorMembershipCondition(this.AuthorName);
        }
    
        // Compares an object for equality based on the author's
        // name, not the object reference.
        public override bool Equals(object obj) {
        
            AuthorMembershipCondition that = 
                (obj as AuthorMembershipCondition);
            
            if (that != null) {                  
                if (this.AuthorName == that.AuthorName) {
                    return true;
                }
            }
            return false;
        }

        // We must override GetHashCode because we override 
        // Object.Equals. Returns a hash code based on the 
        // author's name.
        public override int GetHashCode(  ) {
        
            return this.AuthorName.GetHashCode(  );
        }


# Visual Basic .NET

        ' Creates a copy of the membership condition
        Public Function Copy(  ) As IMembershipCondition _
            Implements IMembershipCondition.Copy
 
            Return New AuthorMembershipCondition(Me.AuthorName)
        End Function
 
        ' Compares an object for equality based on the author's
        ' name, not the object reference.
        Public Overloads Function Equals(ByVal obj As Object) As Boolean _
            Implements IMembershipCondition.Equals

            Dim that As AuthorMembershipCondition = _
                CType(obj,AuthorMembershipCondition)
 
            If Not that Is Nothing Then
                If Me.AuthorName = that.AuthorName Then
                    Return True
                End If
            End If
            Return False
        End Function

        ' We must override GetHashCode because we override 
        ' Object.Equals. Returns a hash code based on the 
        ' author's name.
        Public Overrides Function GetHashCode(  ) As Integer
 
            Return Me.AuthorName.GetHashCode(  )
        End Function

All of the standard membership condition classes override Object.ToString to return a simple human-readable representation of the membership condition they define. The administration tools display this information, and it should be relatively concise. For consistency, we take the same approach:

# C#
    
        // Returns a simple string representation of the 
        // membership condition
        public override string ToString(  ) {
        
                return "Author - " + AuthorName;
        }    


# Visual Basic .NET

        ' Returns a simple string representation of the 
        ' membership condition
        Public Overrides Function ToString(  ) As String _
            Implements IMembershipCondition.ToString

                Return "Author - " & AuthorName
        End Function

Both the ISecurityEncodable and ISecurityPolicyEncodable interfaces define a ToXml method that returns a System.Security.SecurityElement containing an XML object model of the AuthorMembershipCondition object. The AuthorMembershipCondition class does not differentiate between different policy levels and therefore implements both methods the same.

Most importantly, the root XML element must be "IMembershipCondition" or the administrative tools will not be able to import the XML describing the custom membership condition. The XML representation of all standard membership condition classes includes a version attribute that represents the XML format used to represent the membership condition. We have taken the same approach, which gives flexibility should future versions of the AuthorMembershipCondition class require a different structure:

# C#

        // Return a SecurityElement containing an XML object
        // model representing the membership condition.
        public SecurityElement ToXml(  ) {
        
            return this.ToXml(null);
        }
    
        // Return a SecurityElement containing an XML object
        // model representing the membership condition. We have 
        // no need to differentiate between policy levels and so 
        // we ignore the "level" argument.
        public SecurityElement ToXml(PolicyLevel level) {
        
            // Create a new "IMembershipCondition" element
            SecurityElement se =
                new SecurityElement("IMembershipCondition");
        
            // Add fully qualified type name for membership condition
            se.AddAttribute(
                "class", 
                this.GetType(  ).AssemblyQualifiedName
            );
        
            // Add an XML version number of "1"
            se.AddAttribute("version", "1");
        
            // Add the author name
            se.AddAttribute("name", AuthorName);
           
            // Return the new SecurityElement
            return se;
        }


# Visual Basic .NET

        ' Return a SecurityElement containing an XML object
        ' model representing the membership condition.
        Public Function ToXml(  ) As SecurityElement _
            Implements ISecurityEncodable.ToXml

            Return Me.ToXml(Nothing)
        End Function
 
        ' Return a SecurityElement containing an XML object
        ' model representing the membership condition. We have 
        ' no need to differentiate between policy levels and so 
        ' we ignore the "level" argument.
        Public Function ToXml(ByVal level As PolicyLevel) _ 
            As SecurityElement Implements ISecurityPolicyEncodable.ToXml
 
            ' Create a new "IMembershipCondition" element
            Dim se As SecurityElement = _
                New SecurityElement("IMembershipCondition")
 
            ' Add fully qualified type name for membership condition
            se.AddAttribute( _
                "class", _
                Me.GetType(  ).AssemblyQualifiedName _
            )
 
            ' Add an XML version number of "1"
            se.AddAttribute("version", "1")
 
            ' Add the author name
            se.AddAttribute("name", AuthorName)
 
            ' Return the new SecurityElement
            Return se
        End Function

The reverse of the ToXml method is FromXml, which takes a SecuirtyElement and reconstructs the object state. As with ToXml, both the ISecurityEncodable and ISecurityPolicyEncodable interfaces define a FromXml method. Again, you implement both methods the same, because you do not need to perform policy level-specific state configuration:

# C#

        // Reconstruct the state of the membership condition 
        // object from the SecurityElement provided. 
        // fromExtract state from a SecurityElement
        public void FromXml(SecurityElement e) {
        
            this.FromXml(e, null);
        }
    
        // Reconstruct the state of the membership condition 
        // object from the SecurityElement provided. We have 
        // no need to differentiate between policy levels and so 
        // we ignore the "level" argument. 
        public void FromXml(SecurityElement e, PolicyLevel level) {
        
            // Ensure we have a SecurityElement to work with
            if (e == null) throw new ArgumentNullException("e");
            
            // Ensure the SecurityElement is an AuthorMembershipCondition
            if (e.Tag != "IMembershipCondition") {
                throw new ArgumentException
                    ("Element must be IMembershipCondition");
            } else {
                // Extract the author name from the SecurityElement
                this.AuthorName = e.Attribute("name");
            }
        }
    }   
}


# Visual Basic .NET              

        ' Reconstruct the state of the membership condition 
        ' object from the SecurityElement provided. 
        ' fromExtract state from a SecurityElement
        Public  Sub FromXml(ByVal e As SecurityElement) _
            Implements ISecurityEncodable.FromXml
 
            Me.FromXml(e, Nothing)
        End Sub

        ' Reconstruct the state of the membership condition 
        ' object from the SecurityElement provided. We have 
        ' no need to differentiate between policy levels and so 
        ' we ignore the "level" argument. 
        Public  Sub FromXml(ByVal e As SecurityElement, _
            ByVal level As PolicyLevel) _
            Implements ISecurityPolicyEncodable.FromXml
 
            ' Ensure we have a SecurityElement to work with
            If e Is Nothing Then
                     Throw New ArgumentNullException("e")
            End If
 
            ' Ensure the SecurityElement is an AuthorMembershipCondition
            If e.Tag <> "IMembershipCondition" Then
                Throw New ArgumentException _
                    ("Element must be IMembershipCondition")
            Else 
                ' Extract the author name from the SecurityElement
                Me.AuthorName = e.Attribute("name")
            End If
        End Sub
    End Class
End Namespace

8.3.3 Building the AuthorMembershipCondition Assembly

The AuthorMembershipCondition class has a dependency on the Author class you created in the Chapter 7. For convenience, you could build all of the Author-related security classes into a single assembly. This would simplify security administration if you needed to distribute CAS extensions to third parties, but because the Author class is evidence, you would need to add the assembly to the fully trusted assembly list of every policy level. This would grant the assembly full trust at runtime, which many people would find unacceptable.

For the purpose of an example, build AuthorMembershipCondition into its own assembly using the following command. Remember to ensure that you place a copy of the Author.dll assembly from Chapter 7 in the directory where you build AuthorMembershipCondition:

# C#

csc /t:library /reference:Author.dll AuthorMembershipCondition.cs

# Visual Basic .NET

vbc /t:library /reference:Author.dll AuthorMembershipCondition.vb

8.3.4 Using the AuthorMembershipCondition Membership Condition

The Microsoft .NET Framework Configuration tool (Mscorcfg.msc) provides a graphical interface through which you can configure security policy. Mscorcfg.msc implements specific support for the standard membership condition classes and provides user-friendly interfaces through which you can configure their membership condition parameters. However, the support for using custom membership conditions is rudimentary, requiring you to import an XML fragment describing the membership condition to use. This XML is the result of calling ToXml( ).ToString( ) on a custom membership condition object.

In this example, we will use the Code Access Security Policy tool (Caspol.exe) to configure a code group in the user policy level that uses AuthorMembershipCondition to evaluate membership. We will describe the Caspol.exe commands necessary to perform this configuration, but you should look at Chapter 9 for complete details of both the command-line and graphical administration tools.

8.3.4.1 Installing security assemblies

As we discussed in Section 8.1.1.3, assemblies that provide CAS extensions (such as the AuthorMembershipCondition.dll assembly) must be fully trusted by the policy level in which they are used. To make AuthorMembershipCondition.dll fully trusted by the user policy level, execute the following commands from the directory where the AuthorMembershipCondition is located:

gacutil -i AuthorMembershipCondition.dll
caspol -user -addfulltrust AuthorMembershipCondition.dll

The first command installs AuthorMembershipCondition into the global assembly cache (which you must do before you can make it a fully trusted assembly). The second command makes AuthorMembershipCondition a fully trusted assembly in the user policy level.

8.3.4.2 Generating AuthorMembershipCondition XML

You must create an XML representation of the membership condition you want to assign to the code group. Although the required XML is simple enough that you could create it manually, it is much easier and safer to instantiate an AuthorMembershipCondition object and write the contents of the SecurityElement returned by the ToXml to disk.

If you distribute custom membership condition classes to users or customers, we advise you to create a simple utility to perform the creation of the required XML fragments. The CreateAuthorMembership class takes an author name as a command-line argument, instantiates an AuthorMembershipCondition object, and writes an XML representation of it to disk:

# C#

using System;
using System.IO;
using ORA.DotNetSecurity.Policy;

public class CreateAuthorMembership {

    public static void Main(string[] args) {
      
        // Create a new AuthorMembershipCondition based
        // on the name of the author provided
        AuthorMembershipCondition amc = 
            new AuthorMembershipCondition(args[0]);
            
        // Generate the name of the XML output file
        String file = args[0] + ".xml";
 
        // Render the AuthorMembershipCondition to 
        // XML and write it to a file       
        StreamWriter strm = new StreamWriter(file);
        strm.Write(amc.ToXml(  ).ToString(  ));
        strm.Close(  );
        
            // Display result
            Console.WriteLine("Created author membership condition : " + 
                file);        
    }
}

# Visual Basic .NET

Imports System
Imports System.IO
Imports ORA.DotNetSecurity.Policy
 
Public Class CreateAuthorMembership
 
    Public Shared  Sub Main(ByVal args(  ) As String)
 
        ' Create a new AuthorMembershipCondition based
        ' on the name of the author provided
        Dim amc As AuthorMembershipCondition = _
            New AuthorMembershipCondition(args(0))
 
        ' Generate the name of the XML output file
        Dim file As String =  args(0) + ".xml" 
 
        ' Render the AuthorMembershipCondition to 
        ' XML and write it to a file       
        Dim strm As StreamWriter =  New StreamWriter(file) 
        strm.Write(amc.ToXml(  ).ToString(  ))
        strm.Close(  )
 
            ' Display result
            Console.WriteLine("Created author membership condition : " & _ 
                file)
    End Sub
End Class

Build the CreateAuthorMembership utility using the following command, remembering the dependency on the AuthorMembershipCondition.dll library:

# C#

csc /reference:AuthorMembershipCondition.dll CreateAuthorMembership.cs

# Visual Basic .NET

vbc /reference:AuthorMembershipCondition.dll CreateAuthorMembership.vb

Run the command CreateAuthorMembership Peter to create the XML representation of an AuthorMembershipCondition that matches Author evidence for the author "Peter." Here is the content of the Peter.xml file. The PublicKeyToken value will change based on the keys you used to give create a strong name:

<IMembershipCondition class="ORA.DotNetSecurity.Policy.
AuthorMembershipCondition, AuthorMembershipCondition, 
Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc5e18bc387194b3"
                      version="1"
                      name="Peter"/>
8.3.4.3 Configuring security policy

With the Author.dll assembly configured as a fully trusted assembly in the user policy level, you are ready to create a code group whose membership condition is based on Author evidence. Type the following command:

caspol -user -addgroup All_Code -custom Peter.xml FullTrust 
-name "Peter's code" -description "Code group grants all 
code written by Peter full trust"

Here is a summary of the arguments used in this command:

-user

Specifies that the command is to affect the user policy level

-addgroup All_Code

Specifies that you want to add a new child code group to the existing group with the label "All_Code"

-custom Peter.xml FullTrust

Specifies that you are adding a custom membership condition contained in the file Peter.xml and that the permission set granted by the code group is the named permission set "FullTrust"

-name "Peter's code"

Specifies the name of the new code group

-description "Code group grants all code written by Peter full trust"

Specifies a description for the new code group

Depending on how Caspol.exe is configured, it may prompt you to confirm the change your are about to make with the following message:

The operation you are performing will alter security policy.
Are you sure you want to perform this operation? (yes/no)

Entering "yes" updates the user security policy and commits the changes to disk; you will see the following message:

Added union code group with "-custom" membership condition to the User level.
Success

8.3.5 Testing Custom Membership Conditions

The easiest way to test the extensions to the policy resolution process is to use the Caspol.exe tool. In Chapter 6, we embedded Author evidence in an assembly named HelloWorld.exe. We can use Caspol.exe and the HelloWorld.exe assembly to show that the "Peter's code" code group does detect Author evidence and evaluate it correctly. Type the following command from the directory where HelloWorld.exe is located:

caspol -user -resolvegroup HelloWorld.exe

Caspol.exe produces the following output, showing that HelloWorld.exe is a member of the code group "Peter's code":

Microsoft (R) .NET Framework CasPol 1.0.3705.288
Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.

Level = User

Code Groups:

1.  All code: FullTrust
   1.1.  Author - Peter: FullTrust

Success
    [ Team LiB ] Previous Section Next Section