To learn about imperative CAS, and particularly about evidence-based security, let's begin by looking at the Evidence class. We will then see how to obtain an Evidence class object of the currently running application domain. Finally, we will see how to enumerate its contents and then make imperative programming decisions based on the discovered evidence. The Evidence ClassBefore we look at the ImperativeCASComponent example, we should have a good understanding of the Evidence class itself, since it is the crucial ingredient in that code example. The Evidence class, found in the System.Security.Policy namespace, encapsulates the set of evidence information that can be used to enforce security policy decisions.
For security reasons, the Evidence class is sealed, meaning that it cannot be used as the superclass for any new derived classes. Just imagine the skullduggery you could unleash if you were able to trick the CLR into substituting your own derived class in place of the highly trusted Evidence class! The types of evidence that can be obtained via an Evidence object may include digital signature, point of origin, or even custom evidence information that may be useful in explicitly making imperative CAS decisions. The Evidence class implements the two interfaces ICollection and IEnumerable . As we will see shortly, the ICollection interface specifies members for containing a collection of objects, and the IEnumerable interface provides access to those objects via the IEnumerator interface. In the case of the Evidence class, the contained objects represent distinct pieces of host evidence and assembly evidence. Here is the declaration for the Evidence class. public sealed class Evidence : ICollection, IEnumerable
EVIDENCE CONSTRUCTORSThere are three constructors in the Evidence class. The Evidence constructor, which takes no parameters, initializes a new empty instance of the Evidence class. Of course, an Evidence object that contains no evidence information is not too useful. However, we shall see that the Evidence class provides methods for adding evidence. The constructor that takes a single Evidence parameter provides a shallow copy. The third constructor takes two Object array parameters, which are used to initialize the new Evidence instance with two arrays of host and assembly evidence objects. public Evidence(); //initialize a new empty instance public Evidence( Evidence evidence //shallow copy ); public Evidence( object[] hostEvidence, //host evidence array object[] assemblyEvidence //assembly evidence array ); EVIDENCE PROPERTIESIn the Evidence class, there are five public properties named Count, Locked, IsSynchronized, SyncRoot , and IsReadOnly . Strangely, only the two properties Count and Locked are of any actual use. As we previously mentioned, the Evidence class implements the ICollection interface, which specifies the public properties Count, IsSynchronized , and SyncRoot , as well as the public method CopyTo . Since the Evidence class encapsulates a set of evidence information objects, it makes sense that Count is a read-only property that represents the number of these pieces of contained evidence. The IsSynchronized method is normally used on collections to determine if the collection is thread-safe. [20] In the case of the Evidence class, IsSynchronized always returns false, since thread-safe evidence sets are not supported. This actually makes sense, since it would be quite difficult to imagine why threads would be messing around with sensitive, security- related operations at the same time using the same evidence. In any case the IsSynchronized property is effectively useless and therefore is not used.
The SyncRoot property normally provides an object on which you can synchronize access to a collection. However, since synchronization of evidence collections is not supported, SyncRoot only returns this . Therefore, the SyncRoot property is also of no practical use and is not used. In spite of being useless, both IsSynchronized and SyncRoot are defined by the ICollection , and therefore the Evidence class must expose them even if they serve no useful purpose! The IsReadOnly property always returns false , because read-only evidence sets are not supported. Since this return value is a forgone conclusion, IsReadOnly is also a useless property, and it is therefore not used. The Locked property gets or sets a true or false value indicating whether the evidence is currently locked. If this property is set to false, then the contained evidence can be modified by calling the AddHost or Merge methods, which we discuss shortly. If the Locked property is set to true, however, then a SecurityException exception is thrown by the Merge or AddHost [21] methods, unless the code has been granted the ControlEvidence security permission. In fact, it turns out that you need the ControlEvidence security permission to set the Locked property to a new value in the first place. The default value for the Locked property is false. If you are not planning to add or merge evidence information to an Evidence object, then you can simply ignore the Locked property altogether.
The following list briefly describes each of these Evidence properties. Again, note that three of them are of no use because they return hardwired results, and also note that Count is read-only, but Locked is read/write.
EVIDENCE METHODSIn the Evidence class there are seven [22] public methods: GetEnumerator, CopyTo, AddAssembly, AddHost, GetAssemblyEnumerator, GetHostEnumerator , and Merge . The basic operations that these methods support are enumerating and copying, as well as adding and merging evidence information.
One of these methods, GetEnumerator , originates in the IEnumerable interface. The GetEnumerator method simply returns an IEnumerator interface that can be used to walk through the evidence collection via its Current property and the MoveNext and Reset methods. Another method, CopyTo , originates in the ICollection interface. The CopyTo method simply copies the elements of the evidence collection to an Object array, starting at a particular index position. The remaining six methods are specific to the Evidence class itself.
Obtaining the Current Application Domain EvidenceAlthough the Evidence class does provide constructors, you typically want to obtain a ready-made Evidence object that reflects the current runtime situation. The way to get such an Evidence object is to access the Evidence property of the current application domain object. The AppDomain.CurrentDomain static property provides the current application domain for the current thread. This is shown in the following code. Evidence evidence = AppDomain.CurrentDomain.Evidence ; Enumerating EvidenceYou can walk through the available evidence by enumerating the contents of the current application domain Evidence object. If you do that, you will find security zone and URL information, which is determined by the physical origin of the assembly. You will also find hash code information, which provides identity evidence relating to the binary contents of the assembly itself. If, and only if, the assembly has been digitally signed, you will also find cryptographically strong name evidence. A strong name allows the signer to be mathematically verified and ensures that the assembly cannot be forged, tampered with, or repudiated. To be effective, these guarantees depend on a trusted certificate authority that vouches for the digital signature. These types of evidence are represented by the following classes, all of which are defined in the System.Security.Policy namespace:
Once you have the current application domain's Evidence object, you can obtain an IEnumerator interface on it and walk through the evidence information, making any appropriate security decisions that you deem necessary. To do this, simply call on the Evidence object's GetEnumerator method and enter a while loop that calls MoveNext in each iteration. //walking thru the evidence IEnumerator enumerator = evidence.GetEnumerator(); while ( enumerator.MoveNext() ) { object item = enumerator.Current ; //make decisions based on evidence item if (...) { ... } } The WalkingThruEvidence ExampleLet's look at the WalkingThruEvidence example to see how this can be done in practice. This program obtains the current Evidence object and then displays the evidence information that it contains. In this example we have a digitally signed assembly, so we can see the additional StrongName evidence that is produced by the presence of a digital signature.
The WalkingThruEvidence example first obtains the current Evidence object, and then it loops through all the evidence that it contains. Then, for each piece of evidence, it displays the details found. Here is the source code. //obtain appdomain security evidence Evidence evidence = AppDomain.CurrentDomain.Evidence; //obtain evidence enumerator IEnumerator enumerator = evidence.GetEnumerator(); //walk thru evidence while (enumerator.MoveNext()) { object item = enumerator.Current; //display the evidence Type type = item.GetType(); Console.WriteLine(type.Name + ": "); if (type == typeof(Url)) { Console.WriteLine( " Value: " + ((Url)item).Value); } if (type == typeof(Zone)) { Console.WriteLine( " SecurityZone: " + ((Zone)item).SecurityZone); } if (type == typeof(Hash)) { Console.WriteLine( " MD5: " + BitConverter.ToString(((Hash)item).MD5)); Console.WriteLine( " " + "SHA1: " + BitConverter.ToString(((Hash)item).SHA1)); } if (type == typeof(StrongName)) { Console.WriteLine( " Name: " + ((StrongName)item).Name); Console.WriteLine( " Version: " + ((StrongName)item).Version); Console.WriteLine( " PublicKey: " + ((StrongName)item).PublicKey); } if (type == typeof(Site)) { Console.WriteLine( " Name: " + ((Site)item).Name); } } Here is the output of the WalkingThruEvidence example that results from running it directly from the local file system. Output lines that are too long to fit on the printed page have been shortened where necessary. As you can see, the zone is the local computer, and the URL specifies the file protocol followed by the file path where the assembly is located. Because the assembly was digitally signed, you can see the strong name evidence, including the public key. Finally, the MD5 and SHA-1 hash information is present. If you were to rebuild the assembly without the digital signature, then the strong name evidence would be missing. Since this was run directly from the local file system, and not via Internet Explorer, the Web site evidence is absent. Zone: SecurityZone: MyComputer Url: Value: file://C:/.../WalkingThruEvidence.exe StrongName: Name: WalkingThruEvidence Version: 1.0.1010.20177 PublicKey: 00240000048000009400000006020000002400005253... 5C5703B8AEEA06C1CFD72327CD0F35FD650345ACA6806F7 Hash: MD5: A6-AB-D6-AD-42-41-38-67-BF-57-32-4C-55-A4-6C-A4 SHA1: F6-E1-17-1A-4B-6C-BE-DB-4B-ED-...-E4-E2-C3-37 Accessing the WalkingThruEvidence Example Via IISLet's try something slightly different now by publishing the WalkingThruEvidence assembly on the local IIS Web site and then executing it from within Internet Explorer via http. To publish a file on an IIS Web site, you simply copy the file to the \inetpub\ wwwroot directory. Then, to access it in Internet Explorer, you enter the URL in the form of http:// servername /filename . To do this locally, you can specify localhost as your server name. The URL that you will then enter in Internet Explorer will therefore be http://localhost/WalkingThruEvidence.exe . Assuming that you have deployed the assembly to the IIS root directory, if you try to run this program in this way, Internet Explorer will attempt to load and run the assembly, but this will only result in a SecurityException being thrown. This happens because the assembly is no longer being loaded from the My Computer zone, which is fully trusted, but instead is being loaded from the Local Internet zone, which is not granted full trust by default. To see this program work properly, you must change the trust level granted to the Local Internet zone to full trust. Warning : This is an experiment only. You should never set the intranet zone to full trust in a production environment! This experiment should only be done temporarily on a non-networked development machine. Once you are done, you should set the trust level back to its original default level to avoid an obvious security risk. To change this trust level, select Start Settings Control Panel Administrative Tools Microsoft .NET Framework Wizards, and then select Adjust .NET Security, which opens the Security Adjustment Wizard, as shown in Figure 8-19 Figure 8-19. The Security Adjustment Wizard.
In the Security Adjustment Wizard, select the Make changes to this computer radio button and click Next. Click on the Local Intranet icon and adjust the level of trust to Full Trust. This is shown in Figure 8-20 Figure 8-20. Local Intranet zone set to full trust.
Click Next, and then, to complete the wizard, click Finish. Once you have done this, you can again try running the program using Internet Explorer, with the result shown in the following output. The zone, which was previously MyComputer, is now changed to Intranet. The URL, which was previously file://C:/.../WalkingThruEvidence.exe , is now http://localhost/WalkingThruEvidence.exe . From this, you can see clearly that a completely different protocol (http rather than file) was used this time to locate and load the assembly. The Web site evidence, which was completely missing before, now specifies the localhost machine, and the strong name and hash evidence is no longer available. This all clearly shows that we are now dealing with an entirely different code group . Zone: SecurityZone: Intranet Site: Name: localhost Url: Value: http://localhost/WalkingThruEvidence.exe Imperative CASLet's turn our attention now to the imperative approach to CAS. We first consider how this is done by browsing through the available evidence, and later we will see how to do the same thing using CodeAccessPermission derived classes. THE IMPERATIVECAS EXAMPLEThe ImperativeCAS example program, found together with the associated TrustedClient and EvilClient programs in the ImperativeCAS directory, demonstrates the explicit imperative approach for protecting a component by allowing it to defend itself from being called by certain untrusted client applications. The EvilClient and TrustedClient programs both attempt to call into the DoSomethingForClient method exposed by the ImperativeCASComponent assembly, but only the TrustedClient is successful. This example makes explicit use of the Evidence class and chooses between two alternative actions (i.e., execute normally or throw a SecurityException ) based on an if statement that tests a particular detail of the available security evidence. Shortly, we will see another imperative security example using permission objects rather than explicitly perusing the contents of an Evidence object. The EvilClient program is very simple. It just calls into a static method named DoSomethingForClient on an object defined by a class named ImperativeCASComponent in a separate assembly named ImperativeCASComponent.dll . // EvilClient .cs using System; using System.Security; class EvilClient { static void Main(string[] args) { //NOTE: need ref to ImperativeCASComponent.dll //try to call on the component try { ImperativeCASComponent. DoSomethingForClient (); } catch (SecurityException se) { Console.WriteLine( "SecurityException: " + se.Message); } } } Notice that the following output shows the unsuccessful result of running the EvilClient program, indicating that the client is not considered trustworthy. We will see how this happens when we study the code in the DoSomethingForClient method. DoSomethingForClient called SecurityException : Client is not trustworthy Before we study the DoSomethingForClient method, let's look at what happens when we run another program that appears to be identical to the EvilClient program. As you can see in the following code listing, the TrustedClient program looks virtually identical to the EvilClient program in every detail. Yet when you run it, you get an entirely different result. // TrustedClient .cs using System; using System.Security; class TrustedClient { static void Main(string[] args) { //NOTE: need ref to ImperativeCASComponent.dll //try to call on the component try { ImperativeCASComponent. DoSomethingForClient (); } catch (SecurityException se) { Console.WriteLine( "SecurityException: " + se.Message); } } } The following output shows the result of running the TrustedClient program. Notice that this time, the output shows a successful result, indicating that the client is considered trustworthy. We will see how this happens as well by studying the code in the DoSomethingForClient method. DoSomethingForClient called Permitted: Client is trustworthy Let's now look at the code that implements the DoSomethingForClient method. This static method is implemented in the class named ImperativeCASComponent . Both the TrustedClient and EvilClient attempt to call this method in exactly the same way. The DoSomethingForClient method starts by obtaining the current application domain's Evidence object. It then obtains an IEnumerator interface and enters a while loop where each piece of available evidence is inspected. As we have seen, there are several types of evidence that may be provided by the Evidence object, but the type of evidence in which we are interested in this example is represented by the Url class, defined in the System.Security.Policy namespace. Therefore, we use an if statement to determine if the type of the evidence is a Url , and, if it is, we test to see if its value ends with the string TrustedClient.exe . Only if this match is found do we recognize the client as being trustworthy. All clients with names other than TrustedClient.exe are rejected by throwing a SecurityException . Of course, this is a simplified example that focuses on concepts rather than realism . In a more realistic scenario, you would probably want to make more elaborate decisions based on a combination of the available evidence. //ImperativeCASComponent.cs using System; using System.Security; using System.Collections; using System.Security.Policy; using System.Windows.Forms; public class ImperativeCASComponent { //this method only works for TrustedClient.exe public static void DoSomethingForClient () { Console.WriteLine( "DoSomethingForClient called"); //obtain appdomain security evidence Evidence evidence = AppDomain.CurrentDomain.Evidence; //obtain evidence enumerator IEnumerator enumerator = evidence.GetEnumerator(); bool trustworthy = false; //assume the worst while (enumerator.MoveNext()) //walk thru evidence { object item = enumerator.Current; //test to see if Url is acceptable Type type = item.GetType(); if (type == typeof(System.Security.Policy.Url)) { String strUrl = ((Url)item).Value.ToString(); if (strUrl.EndsWith("TrustedClient.exe")) { trustworthy = true; //good news break; } } } //throw exception if no good evidence found if (!trustworthy) throw new SecurityException ( "Client is not trustworthy"); //if we got this far then all went OK Console.WriteLine( "Permitted: Client is trustworthy"); } } |