7 Customizing Code Access Security
Like most parts of .NET, code access security is customizable. CAS policy is contained in XML configuration files and these files list the classes that provide the policy. You can customize CAS by providing your own classes and mentioning these in the CAS configuration files. In this part of the workshop you will learn how to extend CAS policy through custom evidence.
7.1 Changing CAS Policy
In this example you will extend code groups and permissions. This means that you will change the policy files. It is easy to corrupt the policy files when you do this. The configuration tool will check that values that you supply, but occasionally it will miss an invalid value and this will render the policy file invalid. Typically, the first time you see this will be when you open the configuration tool and you'll find that the policy you have been editing is marked as read-only:

If you get this situation you'll find that you will not be allowed to view
the code groups or permissions below the corrupted policy. The first thing to
try is to reset policy through the configuration tool, right click on
Runtime Security Policy and select Reset All. You'll be presented
with a dialog asking you to confirm your decision. If this is not successful,
then you can try the command line tool, caspol, instead.
I find that caspol is usually more successful at resetting
policy than the configuration tool. Of course, once you have reset policy you
will have to add your custom values again, but this is a good opportunity to
check all of the XML files you use to make sure that you are not adding
invalid values.
It is a good idea before creating custom permissions that you save the security policies that you have so that if you mess up your policy files during your development then you can restore your previous settings. A previous page explains how to do this.
7.2 Custom Evidence
Custom evidence is very poorly documented, you'll not find an explanation of how to do it in the MSDN library and even the comprehensive tome .NET Security Framework by LaMacchia et al (Addison Wesley, 2002, ISBN 0-672-32184-X) does not give all of the steps. I will outline how to do it here, however, as you can see later, it does not help you much.
The first thing I need to mention is that assemblies have two types of
evidence, host evidence and assembly evidence. The host is ASP.NET, IE or the Windows Shell, or it may be a custom process that you have
written to host the runtime. Host evidence is the evidence that have been mentioned so
far, and includes things like Site, URL, Hash etc.
Assembly evidence is evidence that can be found in the assembly. This evidence
is stored in a managed assembly resource called Security.Evidence.
This is just a normal resource, there is nothing special about it. The
assembly linker tool, al.exe even has a switch (/evidence)
that allows you to add an evidence resource to an assembly. However, you do
not need to use this tool, the C# compiler is just as capable.
The assembly evidence is a serialized form of the Evidence
object. Evidence is a sealed class that acts as a container for
host and assembly evidence. You can use any serializable class as the
evidence, and Evidence has a method called AddAssembly to allow
you to add the evidence to the Evidence object (it would have
been better if this method had been called AddAssemblyEvidence). Once you have
created an Evidence object you need to serialize it to a file
using the BinaryFormatter object. You can then add this file as a
resource to the assembly. Microsoft's documentation recommends that you add
the Security.Evidence resource using the assembly linker tool (al.exe). Indeed, this is what
LaMacchia et al recommends:
Compile your .NET Framework code to a module or several modules. For the C# and VB compilers, this is done by using the
/target:moduleoption.Combine the module(s) and the serialized evidence into an assembly using the ALink tool (
al.exe)from the .NET Framework SDK. ALink has a/evidenceoption that is used to point to a serialized evidence file.
All the /evidence switch does is merely adds the information in the
file as an embedded resource called Security.Evidence, the only
unusual aspect of it is that it is a private resource, that is, the
resource can only be accessed by this assembly. The downside of using the
assembly linker tool in this way is that it requires that the code
resides in a separate module (a .netmodule file).
In fact, you do
not need to use the assembly linker tool, the C# compiler is adequate. If you
use the /res:<file>,Security.Evidence switch (where <file>
is the name of the evidence file) then it will add a public embedded
resource with the correct name. The fact that this resource is public is
immaterial. So here are the steps:
- Create a custom evidence class that is serializable
- Create an instance of this class with the serializable data initialized
- Create an
Evidenceobject and add the custom evidence object to it withAddAssembly - Serialize this
Evidenceobject to a file withBinaryFormatter - Compile the assembly that you want to have custom assembly evidence
using the
/res:<file>,Security.Evidenceswitch
Here is a snippet of code that illustrates how to serialize the evidence:
// obj is the evidence object
static void SerializeEvidence(string file, object obj)
{
Evidence ev = new Evidence();
ev.AddAssembly(obj);
BinaryFormatter ser = new BinaryFormatter();
using (Stream stm = File.OpenWrite(file))
{
ser.Serialize(stm, ev);
}
}
The idea is that when the runtime loads your assembly it will gather host
and assembly evidence and then use this to determine the permissions that the
assembly will be granted. You can get the evidence that an assembly has
through the Assembly object:
IEnumerator e = a.Evidence.GetEnumerator();
while(e.MoveNext())
{
Evidence ev = e.Current as Evidence;
Console.WriteLine("Evidence is {0}", ev.GetType().ToString());
}
This code will print out all the evidence for the assembly. The
Evidence class has GetAssemblyEnumerator and
GetHostEnumerator to get the assembly and host evidence, respectively.
Note that for an assembly to be able to get access to its own evidence
the assembly must be granted SecurityPermissionFlag.ControlEvidence.
Code that is granted the LocalIntranet and Internet
permission sets will not have this permission.
If you peruse the MSDN documentation you'll see that for every evidence
type there is also a membership condition type, for example, the Zone
type has an associated ZoneMembershipCondition. The membership
condition class is used to determine whether a particular evidence has a
specific value, and hence indicates that the assembly is a member of a
particular code group. For example, this was taken from security.config:
<IMembershipCondition class="ZoneMembershipCondition" version="1" Zone="Internet"/>
<CodeGroup class="NetCodeGroup" version="1" Name="Internet_Same_Site_Access" Description="...">
<IMembershipCondition class="AllMembershipCondition" version="1"/>
</CodeGroup>
</CodeGroup>
As you can see the XML gives the rules that an assembly has to obey to
be part of the code group and get the specified permission set. So, for the
Internet_Zone a ZoneMembershipCondition object is
created and its Zone property is initialized with "Internet".
When an assembly is assessed, its evidence is passed to this
ZoneMembershipCondition object and the IMembershipCondition.Check
method is called. This method iterates through the Evidence
object and if there is a Zone evidence object the condition
object determines the zone that it refers to and if it is the same as its
Zone property (Internet) the Check
method will
return true.
To try this out, create a file called evidence.cs to contain the classes in
this example, and
make sure that you have access to a public-private key pair. Initially,
the source file looks like this:
using System.Security;
using System.Security.Policy;
using System.Reflection;
[assembly: AssemblyKeyFile("key.snk")]
[assembly: AssemblyVersion("1.0.0.0")]
[Serializable]
public class GuidEvidence
{
Guid guid;
public GuidEvidence(Guid g)
{
guid = g;
}
public Guid Guid
{
get{return guid;}
}
public override string ToString()
{
return guid.ToString();
}
}
0.0.0.0
(the default), because for some reason CAS does not like policy files with
this version (although it is a valid version). To use this class as custom
evidence you must write a tool to create an instance and then serialize it to
a file that you can add as a resource (createEvidence.cs):
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Policy;
class App
{
// Command line: first parameter is the name of the output file,
// the second parameter is optional, and is a GUID
static void Main(string[] args)
{
if (args.Length == 0) return;
if (File.Exists(args[0])) File.Delete(args[0]);
Guid guid;
if (args.Length == 2) guid = new Guid(args[1]);
else
{
guid = Guid.NewGuid();
Console.WriteLine("New GUID
{0}", guid.ToString());
}
GuidEvidence ge = new GuidEvidence(guid);
SerializeEvidence(args[0], ge);
}
static void SerializeEvidence(string file, object obj)
{
Evidence ev = new Evidence();
ev.AddAssembly(obj);
BinaryFormatter ser = new BinaryFormatter();
using (Stream stm = File.OpenWrite(file))
{
ser.Serialize(stm, ev);
}
}
}
csc createEvidence.cs /r:evidence.dll
gacutil /i evidence.dll
Run createEvidence passing lib.evidence as
the parameter, record the GUID that it prints on the command line. Now create
a library (lib.cs) that has a strong name provided through another key
file lib.snk:
using System.Collections;
using System.Reflection;
[assembly: AssemblyKeyFile("lib.snk")]
public class LibraryCode
{
public static string GetData(string str)
{
Assembly a = Assembly.GetExecutingAssembly();
Console.WriteLine("Host evidence:");
IEnumerator e = a.Evidence.GetHostEnumerator();
while(e.MoveNext())
{
Console.WriteLine(e.Current.ToString());
}
Console.WriteLine("Assembly evidence:");
e = a.Evidence.GetAssemblyEnumerator();
while(e.MoveNext())
{
Console.WriteLine(e.Current.ToString());
}
return null;
}
}
For the time being, this method will not return a value, we'll just use it
to dump the evidence that will be used to determine its permissions. For the
first test you need a managed process (app.cs):
class App
{
static void Main(string[] args)
{
if (args.Length > 0)
{
Console.WriteLine("{0}", LibraryCode.GetData(args[0]));
}
}
}
Compile these files:
csc app.cs /r:lib.dll
If you run this process (make sure it has one command line parameter, it does not matter what) you'll see lots of data printed at the command line, here is an edited example:
|
Host evidence: The last item is the <System.Security.Policy.Zone version="1"> <Zone>MyComputer</Zone> </System.Security.Policy.Zone> <System.Security.Policy.Url version="1"> <StrongName version="1" <System.Security.Policy.Hash version="1"> Assembly evidence:
GuidEvidence we added to the assembly. Now let's get the library to
do something. Replace the previous code with
the following (also add a
using statement for System.IO):
public static string GetData(string str) { StreamReader sr = new StreamReader(str); return sr.ReadToEnd(); } |
The idea is that the parameter passed to GetData indicates a
file and this method will read the entire file and return its contents. This
code needs at least a read FileIOPermission, but since the library is installed on this machine it will get full trust.
To reduce the permissions granted to this library we need to have it loaded
from a different location. Create a configuration file for the process (app.exe.config):
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="lib"
publicKeyToken="c7611f8614380ed3"/>
<codeBase version="0.0.0.0"
href="http://127.0.0.1/bin/lib.dll"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
(You will need to replace the publicKeyToken used here with
the public key token in your library, obtained with sn -Tp).
Move the library to the indicated folder (move lib.dll \InetPub\wwwroot\bin).
The site used is in dotted format and so the runtime treats this as being in
the Internet zone, which means that the assembly will get
partial trust.
Now run the application providing the name of a text file, for example,
app app.cs. You'll find that a security exception will be
thrown because the assembly does not have the following permission:
<IPermission class="System.Security.Permissions.FileIOPermission,
mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1"
Read="C:\TestFolder\app.cs"/>
Clearly you need to give the assembly the FileIOPermission,
and in particular any assembly that has the appropriate evidence should be
given this permission. To do this you have to create a
membership condition class that will implement
IMembershipCondition. When you create a code group you will provide
information about the membership condition class and provide a value that, if
met, indicates that the assembly with this evidence will have the permissions
in the code group. The configuration tool only understands standard evidence
and membership condition classes, so to use your custom evidence in a policy
you must provide the information through an XML file in this format:
<!-- one or more attributes with the values that must be met -->
/>
evidence assembly will contain a class called
GuidMembershipCondition, and the XML will look like this:
Culture=neutral, PublicKeyToken=c7611f8614380ed3" version="1"
Guid="dcd89978-2656-4a33-ab79-73423be826c3"
/>
Here, you can see that the XML contains the fully qualified name of the
class, including its full assembly name. The runtime will create an instance
of this class and then initialize the properties of the class that correspond
to the attributes in the XML element. In this case, the
GuidMembershipCondition class will have a string property called
Guid, and the XML file contains the GUID that we added as evidence to
the previous assembly.
The membership condition class looks like this (evidence.cs):
{
Guid guid;
public string Guid
{
get{return guid.ToString();}
set{guid = new Guid(value);}
}
//IMembershipCondition specific methods
public bool Check(Evidence ev);
public IMembershipCondition Copy();
bool IMembershipCondition.Equals(object ev);
string IMembershipCondition.ToString();
// ISecurityEncodable methods
void ISecurityEncodable.FromXml(SecurityElement e);
SecurityElement ISecurityEncodable.ToXml();
// ISecurityPolicyEncodable methods
void ISecurityPolicyEncodable.FromXml(SecurityElement e, PolicyLevel pl);
SecurityElement ISecurityPolicyEncodable.ToXml(PolicyLevel pl);
}
The IMembershipCondition interface derives from
ISecurityEncodable and ISecurityPolicyEncodable. The last
two interfaces have FromXml and ToXml that convert
from a SecurityElement object to a GuidMembershipCondition
object or from a GuidMembershipCondition object to a
SecurityElement object. The SecurityElement class is a
lightweight XML object: it does not understand all the nuances of XML, but
does understand the concepts of elements and attributes. The difference
between these two interfaces is that ISecurityPolicyEncodable
will make the conversion for a specific policy. We don't need policy specific
information in our implementation so the two interfaces can share the same
code. Here's the implementation:
{
string g = e.Attribute("Guid");
if (g != null) guid = new Guid(g);
}
SecurityElement ISecurityEncodable.ToXml()
{
SecurityElement e = new SecurityElement("IMembershipCondition");
e.AddAttribute("class", this.GetType().AssemblyQualifiedName);
e.AddAttribute("version", "1");
e.AddAttribute("Guid", guid.ToString());
return e;
}
void ISecurityPolicyEncodable.FromXml(SecurityElement e, PolicyLevel pl)
{
ISecurityEncodable ise = this as ISecurityEncodable;
ise.FromXml(e);
}
SecurityElement ISecurityPolicyEncodable.ToXml(PolicyLevel pl)
{
ISecurityEncodable ise = this as ISecurityEncodable;
return ise.ToXml();
}
The class has a property called Guid, which
will be initialized from the data in the policy when a comparison with
evidence will be made. The comparison is performed in the Check method:
{
foreach(object o in ev)
{
GuidEvidence g = o as GuidEvidence;
if (g != null)
{
return g.Guid.Equals(guid);
}
}
return false;
}
The method is passed an Evidence object, which as has already
been
mentioned is a collection of evidence objects obtained from the assembly. This
code iterates through the evidence objects and if one of them is a
GuidEvidence object with the same value as the condition in the
GuidMembershipCondition object (which is obtained from policy) then the method returns true.
The final methods in the class provide a clone of the current condition
object, compares two GuidMembershipCondition objects for
equality, and returns a string representation:
{
GuidMembershipCondition cond = new GuidMembershipCondition();
cond.guid = this.guid;
return cond;
}
bool IMembershipCondition.Equals(object ev)
{
GuidEvidence g = ev as GuidEvidence;
if (g != null)
{
return g.Guid.Equals(guid);
}
return false;
}
string IMembershipCondition.ToString()
{
return String.Format(
"<IMembershipCondition class=\"{0}\" version=\"1\" Guid=\"{1}\" />",
this.GetType().AssemblyQualifiedName,
guid.ToString());
}
Now you can compile this assembly. It must be added to the GAC so that the configuration tool can access it:
gacutil /i evidence.dll
Before you can use this new evidence you have to add it as a policy assembly.
|
.NET Version 3.0 Since all assemblies in the GAC have full trust in .NET version 3.0/2.0 you do not have to add the evidence assembly as a policy assembly. If you do this with the configuration tool, you will get the confusing error: Unable to add the selected assembly. The assembly must have a strong name (name, version and public key). caspol is more exact, it tells you that the action
you tried to perform is meaningless. |
There are two ways to do this, either use the configuration tool or caspol. An assembly that contains CAS policy classes should
not be subject to CAS itself otherwise there would be a possibility that there
will be circular references. Thus, the policy assembly should be fully trusted
by the system. To do this with caspol you use the
following:
You have to specify which policy that the assembly will apply to (in this
case the Machine policy), or you could specify -enterprise
or -user.
To do the same thing with the configuration tool, you need to open the appropriate policy, select Policy Assemblies, right click and select Add. This will show you the following dialog:

You will be shown all the assemblies in the GAC and be able to select the appropriate assembly. Again, this will give the assembly full trust.
| It is worth pointing out here that the configuration tool holds onto a copy of the security configuration file. You can only guarantee that it flushes changes to the policy by closing the configuration tool after you have made the policy changes. |
Now that you have a new type of evidence you can create a code group appropriate to this evidence. First, create a permission set that will give assemblies access to files. Select Permission Sets, right click and select New. Give this the name GuidPermissions, click on Next and in the following dialog add the File IO permission to the Assigned permissions:

Click on OK and then Finish. Next, you need to add a code group that grants this permission. Open Code Groups and then select All_Code, right click and select New. Fill in the Name with an appropriate name for the code group (say, My_GUID) and click on Next. In the next dialog select (custom) from the drop down list box and you'll see the following:

The XML box is read only, so you have to import an XML file with the correct details. Open Notepad and type the following:
Culture=neutral, PublicKeyToken=c7611f8614380ed3" version="1"
Guid="dcd89978-2656-4a33-ab79-73423be826c3"
/>
It is important that the value for the class attribute
occupies just one line. In the example above I have had to split it over two
lines, do not do this. The Guid value is the one that you generated with
createEvidence. Note that the PublicKeyToken must correspond to the token in
your assembly. If you do not know what it is type the following at the command
line:
Save the XML file (cust_evidence.xml) to a location where you can find it and close
Notepad.
Move back to the configuration tool, click on Import, navigate to, and
select, the XML file you created. You'll either find that the XML is shown on
the page, or that you'll get an error box indicating that the XML is invalid.
Unfortunately, the error dialog gives no clues about why the XML is
invalid, so if you see this dialog you have to check the XML very carefully.
Here's some things to check:
- Is the
IMembershipConditionelement spelt correctly (check capitalization) - Is it terminated correctly? (ie is there a
/>at the end) - Are attribute values in matching quotes
- Have you given the fully qualified name for the class, that is, the full name (with namespace) and the full name of the assembly?
- Is the full name of the class on one line - in the example above I have split it over two lines.
Once the membership condition has been accepted you can click on Next to get the page to select the permission set. Select the permission set you added earlier (GuidPermissions), click on Next and then on Finished.
|
.NET Version 3.0 The .NET 2.0 configuration tool often creates new code groups with a name prefixed with Copy of, this is benign but you may want to rename the code group to remove this prefix. |
Now run your application again. This time you'll see
that the library assembly is loaded and it has sufficient permissions to be
able to access the file you specify (assuming NTFS gives you access). To
further investigate this, try and evaluate the code groups and permissions for
the library, so right click on Runtime Security Policy and select
Evaluate Assembly and type
http://127.0.0.1/bin/lib.dll as the assembly name. You should
find that the assembly will be a member of the My_GUID code group
under Machine\All_Code and you'll also get the Internet_Zone\Internet_Same_Site_Access
permission.
The framework's membership condition classes also implement IIdentityPermissionFactory.
This interface is used to provide an identity permission
(with the CreateIdentityPermission method) and so code can make
identity demands. However, for security reasons the runtime only calls
CreateIdentityPermission for system classes, so there is no point
implementing this interface on your custom evidence membership condition class. Here's a
posting on a public newsgroup by Ivan Medvedev on this subject:
CreateIdentityPermissionis not called for custom assembly evidence. This is a security measure, done to prevent malicious code from granting itself identity permissions that may allow it to artificially elevate its trust.CreateIdentityPermissionis still invoked for host-supplied custom evidence though. If you need to use this functionality it is relatively easy to create your own host (managed or unmanaged). You can also useAssembly.LoadFromoverload that accepts evidence.
This statement says that custom identity conditions are ignored if you use the
standard .NET hosts (ASP.NET, console and GUI apps, and IE). However, if you
are willing to write your own host to load the assembly, CAS will call your
CreateIdentityPermission method and hence your application can make
custom identity demands.
Finally, clean up this example:
- remove the code group My_GUID and the permission set
GuidPermissions or simply reset the policy by right clicking on
Machine and selecting Reset (or use
caspol -m -rs) - remove full trust from
evidence byselecting the assembly in the Policy Assemblies node, then right click and select Delete (or usecaspol -rf evidence.dll) - remove the
evidenceassembly from the GAC (gacutil -u evidence).
| I hope that you enjoy this tutorial and value the knowledge that you will gain from it. I am always pleased to hear from people who use this tutorial (contact me). If you find this tutorial useful then please also email your comments to mvpga@microsoft.com. |
Errata
If you see an error on this page, please contact me and I will fix the problem.