Section 12.3. Programmatic Security


12.3. Programmatic Security

Although for the most part administrative security configuration is sufficient, .NET also provides various programmatic ways to control and enforce security. You can use these powerful techniques to tighten security, optimize performance, handle unknown security policies, and deal with questionable components. In addition, programmatic security can configure security at the component level (unlike administrative security configuration, which is only as granular as a single assembly). All the permission types have corresponding classes and attributes available to you. In fact, administrative configuration uses these classes indirectly; the security configuration files are just a list of classes to use when providing the configurable permissions.

Although system administrators can grant assemblies permissions by using administrative configuration, there is no programmatic way to grant permissions. The reason is clear: if that were possible, a rogue assembly could grant itself permissions and go about causing harm. Programmatic security can deny security permissions or demand that some permission be granted. You can use the permission classes dynamically during runtime or apply them as class or assembly attributes, indicating which security action to take and when.

12.3.1. The Permission Classes

The permission types listed in Table 12-1 all have corresponding permission classes, such as the FileIOPermission class or the UIPermission class. Most permission classes are defined in the System.Security.Permissions namespace, but some are spread all over the .NET Framework. All permission classes implement a set of interfaces, including IPermission, which is defined as:

     public interface IPermission : ISecurityEncodable     {        IPermission Copy(  );        void Demand(  );        IPermission Intersect(IPermission target);        bool IsSubsetOf(IPermission target);        IPermission Union(IPermission target);     } 

IPermission is defined in the System.Security namespace. The base interface ISecurityEncodable, used mostly when constructing and persisting custom permissions, provides methods to convert the permissions to and from XML security elements.

12.3.1.1 Permission demand

The most useful method of IPermission is Demand( ), which triggers a stack walk demanding the permission of all the callers up the stack. For example, here is the code required to trigger a stack walk; it verifies that all the callers up the stack have permission to write to the C:\Temp directory:

     IPermission permission;     string path = @"C:\Temp\";     permission = new FileIOPermission(FileIOPermissionAccess.Write,path);     permission.Demand(  ); //Trigger stack walk 

If during the stack walk .NET discovers a caller coming from an assembly without the demanded permission, it aborts the stack walk, and the call to Demand( ) throws an exception of type SecurityException.

Using Demand( ) is how the various .NET application frameworks classes require that whoever calls them have the required security permissions. For example, the file I/O class FileStream demands permission to perform the appropriate operations (such as opening or creating files) in its constructors. You can take advantage of Demand( ) to tighten security and optimize performance as well.

For example, demanding permissions is recommended when a component uses a resource on behalf of a client. Consider the StreamWriter class. It demands file I/O permission when it's constructed with a path parameter, but subsequent calls on its methods cause no demands. The reason the designer of the StreamWriter class decided not to demand the file I/O permission in every call is because it would have rendered the class useless for performing intense file I/O operations. For example, if the client writes the entire Encyclopedia Britannica to the disk one character at a time in a tight loop, demanding the permission every time would kill the application. Not demanding the permission may be fine if a component creates a StreamWriter object and never uses it on behalf of external clients, even indirectly. But in reality, the reason a component creates a resource is to use it to service its clients. Thus, if the permission is demanded only when a component is constructed, a malicious and untrusted client could simply wait for a trusted client to successfully create the component and its resources, and then call its resource-accessing methods.

Unlike the designer of the StreamWriter class, however, you are intimately aware of the particular context in which the StreamWriter class is used. If the file I/O operations performed are not extreme, the component can compensate by explicitly demanding the appropriate permissions, as shown in Example 12-1.

Example 12-1. Protecting a resource by demanding access permission
 using System.IO; using System.Security; using System.Security.Permissions;   public class MyClass {    StreamWriter m_Stream;    string m_FileName = @"C:\Temp\MyFile.txt";    public MyClass(  )    {       //The StreamWriter demands permissions here only:       m_Stream = new StreamWriter(m_FileName);    }    public void Save(string text)    {       //Must demand permission here:       IPermission permission;       permission = new FileIOPermission(FileIOPermissionAccess.Write,m_FileName);       permission.Demand(  );       m_Stream.WriteLine(text);    } } 

Another example is an object that is about to perform a lengthy, intensive calculation and then save it to the disk. Ultimately, if the callers up the stack don't have the required permission, the file I/O classes throw an exception when trying to open the file. Instead of wasting time and resources performing a calculation that can't eventually be saved, the object can first demand the permission and then proceed with the calculation only if it's certain it can persist the results. Example 12-2 demonstrates this technique.

Example 12-2. Optimizing by demanding permission before performing the operation
 class MyClass {    public void Calclulate(string resultsFileName)    {       IPermission permission;       permission = new FileIOPermission(FileIOPermissionAccess.Write,                                                                  resultsFileName);       try       {          permission.Demand(  );       }       catch(SecurityException exception)       {          string message = exception.Message;          message += ": Caller does not have permission to save results";          MessageBox.Show(message);          return;       }       // Perform calculation here and save results       DoWork(  );       SaveResults(resultsFileName);    }    //Helper methods:    void DoWork(  )    {...}    void SaveResults(string resultsFileName)    {...} } 

Note that calling Demand( ) verifies only that the callers up the call chain have the requested security permission. If the object calling Demand( ) itself doesn't have permission, when it tries to access the resource or perform the operation, the resource (or operation) may still throw a security exception.

12.3.1.2 Permission interactions

Some permissions imply other permissions. For example, file I/O access permission to a folder implies access permission to individual files in the same folder. The IPermission interface provides a number of methods that examine how two different permissions relate to each other. The IsSubsetOf( ) method returns true if a specified permission is a subset of the current permission object:

     IPermission  permission1;     IPermission  permission2;     string path1 = @"C:\temp\";     string path2 = @"C:\temp\MyFile.txt";       permission1 = new FileIOPermission(FileIOPermissionAccess.AllAccess,path1);     permission2 = new FileIOPermission(FileIOPermissionAccess.Write,path2);       Debug.Assert(permission2.IsSubsetOf(permission1)); 

The Intersect( ) method of IPermission returns a new permission object that is the intersection of the original permission object and the specified permission object, and the Union( ) method returns a new permission object equivalent to the union of the two. .NET uses these methods when calculating the union of permissions granted by the evaluated code groups in a policy and when calculating the intersection of the policies.

: Custom Permission

In the rare case of components using a resource type (such as a hardware item) not protected by any of the built-in permissions, you can provide custom permission classes. The custom permission typically grants access to the resource only, instead of to a communication mechanism (such as a communication port or the filesystem) that can access other resources. All .NET permission classes derive from the abstract class CodeAccessPermission, which provides the implementation of the stack walk. You can start creating a custom permission by deriving from CodeAccessPermission. Custom permission classes can be used both programmatically and administratively (via custom XML representation). The assembly containing the custom permissions must be strongly named and deployed in the GAC. In addition, it must be granted full trust, and .NET must be aware of it. To register the assembly with .NET, use the .NET Configuration tool. Each policy has a folder called Policy Assemblies (see Figure 12-5). Select Add from the folder's context menu, then select the custom permission assembly from the list of assemblies in the GAC. Note that you must deploy the custom permission assembly and register it on every machine that has applications using it.


12.3.2. Stack-Walk Modifiers

An object can modify the behavior of the stack walk as it passes through it. Each permission class implements the IStackWalk interface, defined as:

     public interface IStackWalk     {        void Assert(  );        void Demand(  );        void Deny(  );        void PermitOnly(  );     } 

The Demand( ) method of IStackWalk triggers a stack walk, and the permission classes channel the implementation to that of IPermission.Demand( ). The other three methods, Assert( ), Deny( ), and PermitOnly( ), each install a stack-walk modifier an instruction modifying the behavior of the walk. At any given stack frame (i.e., the scope of a method), there can be only a single stack-walk modifier. Trying to install a second modifier results in an exception of type SecurityException. The stack modifier removes itself automatically once the method returns or when a call to a static reversion method of CodeAccessPermission is called, such as RevertDeny( ), RevertAssert( ), RevertPermitOnly( ), or RevertAll( ). Stack-walk modifiers are very useful for optimizing and tightening the current security policy, serving as a programmatic override to the configuration set by the administrator.

12.3.2.1 Denying and permitting permissions

In general, a component vendor can't assume that its components will always be deployed in a properly configured and secure environment. Sometimes you should be able to override the administrative security policy and programmatically enforce stricter policiesfor example, when the global security policy is turned off, too liberal for your sensitive needs, or simply unknown. Other cases may involve dealing with components from questionable origin. Calling IStackWalk.Deny( ) denies the permission represented by the underlying permission class and aborts the stack walk, even if the global security policy configuration grants the calling assembly that permission. Example 12-3 demonstrates denying write permission to all the drives on the machine before invoking a method on a questionable component.

Example 12-3. Explicitly denying permissions
 public void SomeMethod(  ) {    string[] drives = Environment.GetLogicalDrives(  );      IStackWalk stackWalker;    stackWalker = new FileIOPermission(FileIOPermissionAccess.Write,drives);      stackWalker.Deny(  );      QuestionableComponent obj = new QuestionableComponent(  );    obj.DoSomething(  );      CodeAccessPermission.RevertDeny(  );      /* Do more work */ } 

In this example, SomeMethod( ) constructs a new FileIOPermission object, targeting all the drives on the machine. Using the IStackWalk interface, SomeMethod( ) denies all write access to these drives. Any attempt by the questionable component to write to these drives results in an exception of type SecurityException. The permission denial remains in effect until the method that called Deny( ) returns. If you want to revert the denial in the scope of the calling method (for example, to do some file I/O yourself), you need to call the static method RevertDeny( ) of the CodeAccessPermission class, as shown in Example 12-3.

Sometimes it's simpler to just list what you permit, using the PermitOnly( ) method of IStackWalk. When a stack walk reaches a method that called PermitOnly( ), only that permission is presented, even if other permissions are granted administratively. Example 12-4 demonstrates permitting access only to the C:\temp directory, but nothing else.

Example 12-4. Permitting a particular permission only
 public void SomeMethod(  ) {    string path = @"C:\temp";    IStackWalk stackWalker;    stackWalker = new FileIOPermission(FileIOPermissionAccess.AllAccess,path);      stackWalker.PermitOnly(  );      QuestionableComponent obj = new QuestionableComponent(  );    obj.DoSomething(  );      CodeAccessPermission.RevertPermitOnly(  );      /* Do more work */ } 

Like Deny( ), PermitOnly( ) remains in effect until the calling method returns or until the method calls the static method RevertPermitOnly( ) of CodeAccessPermission.

When using a delegate to fire an event at unknown subscribers you can explicitly deny or permit only some permissions, which reduces the risk of calling a delegate that may have lured you into calling it.


12.3.2.2 Asserting permissions

A stack walk to verify security permissions is a powerful and elegant idea, but it doesn't come without a cost. A stack walk is expensive, and in intense calling patterns or when the call stack is long, it results in a performance and throughput penalty. Consider the following code:

     void SaveString(string text,string fileName)     {        StreamWriter stream = new StreamWriter(fileName,true);//append text        using(stream)        {           stream.WriteLine(text);        }     }       string[] array = new string[9];       array[0] = "Every";     array[1] = "string";     array[2] = "in";     array[3] = "this";     array[4] = "array";     array[5] = "causes";     array[6] = "a";     array[7] = "stack";     array[8] = "walk";       string fileName = @"C:\Temp\MyStrings.txt";       foreach(string item in array)     {        SaveString(item,fileName);     } 

Every time the code writes to the file, it triggers a walk all the way up the stack. After the first stack walk, all the subsequent stack walks are redundant, because the call chain doesn't change between loop iterations. To efficiently handle such cases, use the Assert( ) method of IStackWalk:

     string fileName = @"C:\Temp\MyStrings.txt";       IStackWalk stackWalker;     stackWalker = new FileIOPermission(FileIOPermissionAccess.Write,fileName);     stackWalker.Assert(  );       foreach(string itemin array)     {        SaveString(item,fileName);     }     CodeAccessPermission.RevertAssert(  ); 

When asserting security permission, stack walks demanding it stop in the current stack frame and don't proceed up. The assertion remains in effect until the method returns or until the static method RevertAssert( ) is called. Note that you can assert a single permission only in the same method scope (unless RevertAssert( ) or RevertAll( ) is called).

Because the stack walk stops at the level that asserts the permission, it's quite possible that callers further up the call chain that initiated the call don't have the permission to do the operations carried out by the downstream objects. It looks as if the ability to assert permissions may look like a technique that circumvents .NET's code access security policythat is, a malicious assembly can assert whatever security permission it wants and then start roaming on the machine. Fortunately, there are two safeguards. First, if the asserting assembly isn't granted the permission it tries to assert, the assertion has no effect. When code down the call chain demands the asserted permission, it triggers a stack walk. When the stack walk reaches the assertion stack-walk modifier, it also verifies that the asserting assembly has that permission. If it doesn't, the stack walk is aborted, and the call demanding permission throws a security exception. The second safeguard is that not all code can assert permissions. Only code granted the Security permission, with the right to assert permissions, can call Assert( ). If the permission to assert isn't granted, a security exception is thrown on the assertion attempt. .NET doesn't use a stack walk to verify that permission to assert is granted. Instead, it verifies that the asserting assembly has that permission at link time, as described later in this chapter.

Only the most trustworthy code should be granted the right to assert, because of the level of risk involved in not completing stack walks.


Another important point regarding permission assertion is that it doesn't always guarantee stopping the stack walk, because the asserted permission may be only a subset of the permission demand that triggered the stack walk. In that case, the assert instruction only stops the stack walk for its particular type and value. The stack walk may proceed up the call stack looking for other permission grants.

12.3.2.3 Asserting unmanaged code access permission

Performance penalties aside, the more significant side effect of the stack walk is that it can prevent code that should run from operating. Nowhere is this more evident than when it comes to unmanaged code access.

A potential security loophole opens when you call outside the managed-code environment using the interoperation (interop) layer. The interop layer allows managed code to invoke calls on COM components, or simply call DLL entry points using the platform-specific invocation mechanism (P-Invoke). Unmanaged code is completely exempt from .NET code access policies because it executes directly against the operating system. Using interop, a malicious managed component can have the unmanaged code do its dirty work on its behalf. Naturally, only the most trustworthy assemblies should be granted unmanaged code permission. Accordingly, the managed side of the interop layer demands that all code accessing it have the Security permission with the right to access unmanaged code.

The problem is, all .NET Framework classes that rely on the underlying services of the operating system require the interop layer. Consider the case of the FileStream class. To call it, code requires only file I/O permission, which is a more liberal and less powerful permission than the unmanaged code access permission. However, because the FileStream class uses P-Invoke to call the Win32 API on behalf of the caller, any attempt to use the FileStream class triggers a demand for unmanaged code access permission by the interop layer. To shield the caller, the FileStream class asserts the unmanaged code access permission, so it doesn't propagate the demand for that permission to its clients. Without the assertion, only the most trusted components could have used FileStream. Because it's signed with the Microsoft public key, the FileStream class is granted the FullTrust permission set, and its assertion of unmanaged code access succeeds. Instead of unmanaged code access demand, the FileStream class demands only file I/O permission.

The next question is, how should you handle your own interop calls to the unmanaged world? For example, consider the following code that uses P-Invoke to import the definition of the MessageBoxA Win32 API call, which displays a message box to the user:

     using System.Runtime.InteropServices;            public class MsgBox     {        [DllImport("user32",EntryPoint="MessageBoxA")]        public static extern int Show(HandleRef handle,string text,string caption,                                                                   int msgType);     }     //Client side:     HandleRef handle = new HandleRef(null,IntPtr.Zero);     MsgBox.Show(handle,"Called using P-Invoke","Some Caption",0); 

Every time the Show( ) method is called, it triggers a demand for unmanaged code access permission. This has the detrimental effect of both a performance penalty and a functionality impasse if the caller doesn't have generic unmanaged code access permission but is trusted to carry out the specific imported unmanaged call. There are a few solutions. The first is to mimic the behavior of the .NET Framework classes and assert the unmanaged code access permission, using a managed wrapping method around the imported unmanaged call. Example 12-5 demonstrates this technique.

Example 12-5. Asserting unmanaged code access permission around an interop method
 using System.Runtime.InteropServices; using System.Security; using System.Security.Permissions;    public class MsgBox {    [DllImport("user32",EntryPoint="MessageBoxA")]    private static extern int Show(HandleRef handle,string text,string caption,                                                                int msgType);    public static void Show(string text,string caption)    {       IStackWalk stackWalker;       stackWalker = new SecurityPermission(SecurityPermissionFlag.UnmanagedCode);       stackWalker.Assert(  );         HandleRef handle = new HandleRef(null,IntPtr.Zero);       Show(handle,text,caption,0);    } } //Client side: MsgBox.Show("Called using P-Invoke","Some Caption"); 

To assert the unmanaged code access permission, you assert the SecurityPermission permission, constructed with the SecurityPermissionFlag.UnmanagedCode enum value. The recommended practice, however, is never to assert one permission without demanding another permission in its place, as shown in Example 12-6.

Example 12-6. Asserting unmanaged code permission and demanding UI permission
 public static void Show(string text,string caption) {    IStackWalk stackWalker;    stackWalker = new SecurityPermission(SecurityPermissionFlag.UnmanagedCode);    stackWalker.Assert(  );      IPermission permission;    permission = new UIPermission(UIPermissionWindow.SafeSubWindows);    permission.Demand(  );      HandleRef handle = new HandleRef(null,IntPtr.Zero);    Show(handle,text,caption,0); } 

Note that the code in Example 12-6 installs what amounts to a stack-walk filter. As the stack walk makes its way through the stack frame, the filter allows only the user-interface part of the unmanaged code demand to go up the call stack. The assertion converts a generic demand to a more specific demand. This is perfectly safe, because you know that the client is not going to use the Show( ) method for all the things unmanaged code enablesthe caller will use it only for user-interface purposes.

The second solution is to suppress the interop layer's demand for unmanaged code access permission altogether. .NET provides a special attribute called SuppressUnmanagedCodeSecurityAttribute, defined in the System.Security namespace:

     [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method|                     AttributeTargets.Interface|AttributeTargets.Delegate,                     AllowMultiple = true,Inherited = false)]     public sealed class SuppressUnmanagedCodeSecurityAttribute : Attribute     {        public SuppressUnmanagedCodeSecurityAttribute(  );     } 

You can apply the SuppressUnmanagedCodeSecurity attribute only to an interop method, to a class that contains interop methods, to a delegate used to invoke an interop method, or to an interface that the class implements. It is ignored in all other cases. The following example shows how to apply the attribute to an interop method:

     [SuppressUnmanagedCodeSecurity]     [DllImport("user32",EntryPoint="MessageBoxA")]     public static extern int Show(HandleRef handle,string text,string caption,                                                                int msgType); 

The interop layer now will not demand that unmanaged code access permission be granted when the Show( ) method is invoked. The only safeguard is that at runtime, during the link phase to the interop method, .NET will demand unmanaged code access permission from the immediate caller up the stack (you will see how to demand permission at link time later). This allows callers up the call chain without unmanaged code access permission to call other clients with that permission and actually invoke the interop method.

A similar issue arises when importing COM objects to .NET, and you can also suppress demands for unmanaged code access permission by the imported COM objects. The TlbImp command-line utility provides the /unsafe switch:

     tlbimp <COM TLB or DLL name> /out:<interop assembly name> /unsafe 

When you use this switch, the Runtime Callable Wrapper (RCW)the managed code wrapped around the COM objectwill only perform link-time demands for the unmanaged code access permission, instead of doing stack walks on every call. Needless to say, you should use SuppressUnmanagedCodeSecurityAttribute and /unsafe with extreme caution, and only when you know that the call chain to the interop method is secure.

Since you should never assert a permission without demanding a different permission instead, in the case of using the /unsafe switch with imported COM objects, you should build a wrapper around the RCW and have it demand the specific required permissions.


The third solution is to use security permission attributes. The next section examines this option.

12.3.3. Permission Attributes

All security permission classes have equivalent attribute classes. You can apply the security attributes instead of programmatically creating a permission class and demanding a stack walk or installing a stack-walk modifier. Using the permission attributes is called declarative security. All the attributes are used in a similar manner. Their constructor accepts an enum of type SecurityAction, indicating what security action to take:

     public enum SecurityAction     {         Assert,         Demand,         DemandChoice         Deny,         InheritanceDemand,         InheritanceDemandChoice         LinkDemand,         LinkDemandChoice         PermitOnly,         RequestMinimum,         RequestOptional,         RequestRefuse     } 

In addition, you need to set public properties of the attributes, instructing .NET what permission values to take upon the security action. Example 12-7 is similar to Example 12-2, except it uses declarative security to demand file I/O permission for a specific predetermined file before proceeding to perform the calculation.

Example 12-7. Declaratively demanding permission before performing an operation
 class MyClass {    [FileIOPermission(SecurityAction.Demand,Write = @"C:\Results.txt")]    public void Calclulate(  )    {       //Perform calculation here and save results       DoWork(  );       SaveResults(  );    }    //Helper methods:    void DoWork(  )    {...}    void SaveResults(  )    {...} } 

In Example 12-7, .NET demands file I/O permission in every call to the method. If you need to repeat the same security actions in all the methods of a class, you can apply the security attribute on the class itself:

     [FileIOPermission(SecurityAction.Demand,Write = @"C:\Results.txt")]     class MyClass     {...} 

The main difference between declarative security and programmatic security is evident when you compare Example 12-7 to Example 12-2. With programmatic security, the value of the permission (such as a filename, call time, parameter values, machine name, and so on) can be decided at runtime. With declarative security, the value is static and has to be known at compile time. In general, whenever the permission value is known at compile time, you should use declarative instead of programmatic security. Example 12-8 is functionally identical to Example 12-6, but it's much simpler because it uses declarative security.

Example 12-8. Declaratively asserting unmanaged code access permission and demanding UI permission
 [SecurityPermission(SecurityAction.Assert,UnmanagedCode = true)] [UIPermission(SecurityAction.Demand,                                  Window = UIPermissionWindow.SafeSubWindows)] public static void Show(string text,string caption) {    HandleRef handle = new HandleRef(null,IntPtr.Zero);    Show(handle,text,caption,0); } 

You can also apply declarative security directly on the interop method, instead of the wrapper method.

When you apply a security attribute to the scope of a class, it affects all members of the class.


12.3.3.1 Choice actions

.NET 2.0 added the choice options to the SecurityAction enum, allowing you to combine permissions in a logical OR, instead of AND. Consider the following security demands on the method SomeMethod( ):

     [UIPermission(SecurityAction.Demand,Unrestricted = true)]     [FileIOPermission(SecurityAction.Demand,Unrestricted = true)]     void SomeMethod(  )     {...} 

Because SomeMethod( ) has two security permission demands on it, both permissions have to be granted for the method to be used. If you want instead to demand that either one of the permissions has to be granted, use the SecurityAction.DemandChoice value:

     [UIPermission(SecurityAction.DemandChoice,Unrestricted = true)]     [FileIOPermission(SecurityAction.DemandChoice,Unrestricted = true)]     void SomeMethod(  )     {...} 

Now at least one of the permissions has to be granted, but not necessarily both. This sort of demand is called a demand choice.

12.3.3.2 Link-time demands

Declarative security offers capabilities not available with programmatic security demands. You can request that the permission be demanded at link time, during JIT compilation, instead of at call time. Link-time demands are specified using the SecurityAction.LinkDemand value for the security action. For example:

     [UIPermission(SecurityAction.LinkDemand,                   Window = UIPermissionWindow.SafeTopLevelWindows)]     public void DisplaySomeUI(  )     {} 

When the security action is set to the SecurityAction.LinkDemand value, .NET demands permission only of the caller immediately up the call chain linking to the method. Subsequent calls to the method aren't verified to have the permission. If the client doesn't have the demanded permission, a security exception is raised as early as possibleusually when the client first tries to link to the method, instead of at a later point in time. You can still demand the security permission on every call using programmatic security, but if you don't demand permission on every call, you eliminate the stack-walk penalty. The downside to this approach is that malicious clients can use a middleman component with the required permissions to link against the demanding component and then call the middleman, without being subjected to the stack walk. Use SecurityAction.LinkDemand without a per-call demand only if you know that the call chain leading to the object will remain static and is secure.

Link-time demands are especially useful in conjunction with the attribute StrongNameIdentityPermissionAttribute, defined as:

     [AttributeUsage(AttributeTargets.Assembly|AttributeTargets.Class|                     AttributeTargets.Struct|AttributeTargets.Constructor|                     AttributeTargets.Method,AllowMultiple = true,Inherited = false)]     public sealed class StrongNameIdentityPermissionAttribute :                                                            CodeAccessSecurityAttribute     {        public StrongNameIdentityPermissionAttribute(SecurityAction action);        public string Name{get;set;}        public string PublicKey{get;set;}        public string Version { get; set; }     } 

This attribute lets you insist that the assembly linking into your code is signed with a specified public key:

     public static class PublicKeys     {        public const string MyCompany = "1234567890...ABCDEF";     }           [StrongNameIdentityPermission(SecurityAction.LinkDemand,                                                      PublicKey = PublicKeys.MyCompany)]     public void MyMethod(  )     {...} 

You can apply the attribute StrongNameIdentityPermission on any user type. You can even insist on a particular version number and assembly name. The canonical use of StrongNameIdentityPermission is when you are forced to use a public class or a public method because of design considerations, so that you can call it from other assemblies you provide. Logically, however, these public types or methods are for your application's internal private use; you do not want other assemblies from other vendors to use your own application-internal types. In that case, all you need to do is demand your own public key so that no other party can use these public types or methods.

There is a pitfall associated with demanding StrongNameIdentityPermission: you cannot demand multiple keys, because an assembly can have only a single strong name. If you have multiple vendors to whom you want to allow access, use the LinkDemandChoice value instead:

     [StrongNameIdentityPermission(SecurityAction.LinkDemandChoice,                                                        PublicKey = PublicKeys.Vendor1)]     [StrongNameIdentityPermission(SecurityAction.LinkDemandChoice,                                                        PublicKey = PublicKeys.Vendor2)]     public void MyMethod(  )     {...} 

: Identity Permissions

StrongNameIdentityPermission is a special type of permission called an identity permission. Identity permissions allow code to demand that its callers have a particular identity. The PublisherIdentityPermission permission demands that the caller is signed with a particular certificate. The UrlIdentityPermission, SiteIdentityPermission, and ZoneIdentityPermission permissions demand that the calling assemblies originate from a specific URL, site, or zone, respectively. The GacIdentityPermission permission allows you to demand that the calling assemblies are installed in the GAC. You can demand (or assert) identity permissions programmatically, just like any other permission, or you can apply them declaratively using a matching set of attribute classes.


To use StrongNameIdentityPermissionAttribute, you need a string representing a public key. You can use the .NET Configuration tool to extract the public key from an assembly by constructing a code group that uses the Strong Name evidence (see Figure 12-13) and simply copying and pasting the value from the "Public key" text box. Another solution is to use the sn.exe command-line utility. The -e switch extracts the public key from an assembly, and the -tp switch converts the key to a string representation.


12.3.3.3 Inheritance demand

A malicious party can derive from a class, override demanding methods, and then provide its own implementation that does not demand the permission, yet uses much of the functionality at the base-class level. In addition, the subclass can use protected members and helper methods for its own purposes. Component library vendors can use declarative security to prevent malicious parties from abusing their components via inheritance. The component vendor can apply the required security permissions using the SecurityAction.InheritanceDemand value, indicating to .NET to demand the permission of the subclasses. For example, here's how the BaseClass class can demand that its subclasses have been granted unrestricted access to the filesystem:

     [FileIOPermission(SecurityAction.InheritanceDemand,Unrestricted=true)]     public class BaseClass     {} 

When an inheritance demand is applied to a method, it verifies that the overriding subclass has the requested permission. Inheritance demand takes place during load time, and no stack walks are involved.

Neither programmatic nor declarative security can protect against untrusted code accessing public fields, because no stack calls are involved. Never provide public fields, and always use properties.


As with link-time demand and regular demands, SecurityAction also provides the InheritanceDemandChoice value. InheritanceDemandChoice allows you to specify a number of permission attributes and demand that subclasses be granted at least one of them.

: The WebBrowser Control and Code-Access Security

When using the WebBrowser control it is not enough to have WebBrowser permission, because the control demands also the safe top level windows permission (a part of UserInterface permission). This is because the WebBrowser control can cause new windows to be popped up. In addition, the WebBrowser control also demands the following: a link-time demand and inheritance demand for FullTrust, and a FullTrust demand on its constructor:

     [PermissionSet(SecurityAction.LinkDemand,Name="FullTrust")]     [PermissionSet(SecurityAction.InheritanceDemand,                                              Name="FullTrust")]     public class WebBrowser : WebBrowserBase     {        [PermissionSet(SecurityAction.Demand,Name="FullTrust")]        public WebBrowser();        //Rest of the members      } 

This renders the control mostly useless for partially trusted clients, because such clients tend to link against the control directly, create it and use it, and thus require FullTrust themselves. The inclusion of the WebBrowser permission in the LocalIntranet, Internet and Everything named permission set is therefore mostly of little use if at all.


12.3.4. Permission Set Classes

Instead of constructing individual permissions and demanding them, or using a permission to modify a stack walk, you can programmatically construct permission sets. In fact, the main reason for using a permission set is to install a stack-walk modifier that combines multiple permissions (remember that you can install only a single stack-walk modifier). Creating a permission set programmatically is similar to composing a new permission set using the .NET Configuration toolyou create individual permission objects and aggregate them in a permission set object. Using a permission set is an easy way to install a stack-walk modifier that denies, permits, or asserts multiple permissions. Example 12-9 demonstrates composing a permission set with file I/O and UI permissions and denying it before accessing a questionable component.

Example 12-9. Creating and denying a permission set
 public void SomeMethod(  ) {    PermissionSet permissionSet;    //Create an empty permission set    permissionSet = new PermissionSet(PermissionState.None);      IPermission  filePermision;    string path = @"C:\";    filePermision = new FileIOPermission(FileIOPermissionAccess.AllAccess,path);      permissionSet.AddPermission(filePermision);      IPermission UIPerm;    UIPerm = new UIPermission(PermissionState.Unrestricted);      permissionSet.AddPermission(UIPerm);      IStackWalk stackWalker = permissionSet;      stackWalker.Deny(  );      QuestionableComponent obj = new QuestionableComponent(  );    obj.DoSomething(  );      CodeAccessPermission.RevertDeny(  ); } 

You can explicitly revert a stalk-walk modifier set by a permission set using the static methods of CodeAccessPermission, or you can wait for the method to return, as with an individual permission modifier.

12.3.4.1 Permission set collection

Using individual permissions, as in Example 12-1, you can demand that all callers up the stack have a particular permission. If you have multiple permissions to demand, you can build a permission set (as in Example 12-9) and demand that the callers are granted all the permissions in the permission set:

     PermissionSet permissionSet = new PermissionSet(PermissionState.None);     permissionSet.AddPermission(...);     permissionSet.AddPermission(...);     permissionSet.Demand(  ); 

.NET 2.0 introduces an interesting use of permission sets: the ability to demand that the callers up the stack are granted at least one of any number of permissions sets specified. The class PermissionSetCollection is defined as:

     public sealed class PermissionSetCollection : ICollection,IEnumerable     {        public PermissionSetCollection(  );        public void Add(PermissionSet permSet);        public void Demand(  );        //Rest of the members     } 

This is a simple collection of permission sets. You can add new permission sets using the Add( ) method. When you call the Demand( ) method, it performs a stack walk, demanding that every caller up the stack satisfies at least one of the permission sets in the collection (that is, satisfies all the permissions in at least one of the permission sets in the collection). This is akin to performing a programmatic (instead of declarative) demand choice for the permission sets in the collection. Since a permission set can contain a single permission, effectively you can use PermissionSetCollection to generate a demand choice for individual permissions (this is the only way in .NET to perform a programmatic demand choice for individual permissions). For example, consider the code in Example 12-1. You could relax the strict demand for file I/O permission by demanding that the callers up the stack either have file I/O permissions or were installed in the GAC. As mentioned at the beginning of this chapter, because only an administrator can install assemblies in the GAC, such assemblies are somewhat more trustworthy than assemblies installed by non-administrators. You can demand GAC installation using the GacIdentityPermission class.

Example 12-10 demonstrates the use of PermissionSetCollection with the modified Save( ) method from Example 12-1. It defines the static helper class SecurityUtil, with the DemandChoice( ) method. DemandChoice( ) accepts an array of IPermission objects. For each permission object in the array, DemandChoice( ) creates a PermissionSet object and adds it to a PermissionSetCollection object. DemandChoice( ) then demands the permission set collection, thus triggering a demand choice. The use of the params array qualifier in DemandChoice( ) makes it easy to call it with any number of permissions.

Example 12-10. Using PermissionSetCollection
 public static class SecurityUtil {    public static void DemandChoice(params IPermission[] permissions)    {       PermissionSetCollection collection = new PermissionSetCollection(  );       foreach(IPermission permission in permissions)       {          PermissionSet permisssionSet = new PermissionSet(PermissionState.None);          permisssionSet.AddPermission(permission);          collection.Add(permisssionSet);       }       collection.Demand(  );    } } //Replaces the Save(  ) method of Example 12-1 public void Save(string text) {    IPermission permission1;    permission1 = new FileIOPermission(FileIOPermissionAccess.Write,m_FileName);      IPermission permission2;    permission2 = new GacIdentityPermission(  );    SecurityUtil.DemandChoice(permission1,permission2);    m_Stream.WriteLine(text); } 

12.3.5. Permission Set Attributes

You can declaratively instruct .NET to take a security action, such as demanding or asserting a permission set, using the PermissionSetAttribute class, defined as:

     public sealed class PermissionSetAttribute : CodeAccessSecurityAttribute     {         public PermissionSetAttribute(SecurityAction action);         public SecurityAction Action{get;set;}         public string File{get;set;}         public string Name{get;set;}         public string XML {get;set;}         //Rest of the definition     } 

Unlike with the programmatic composition of a permission set, when you use the Name property of the PermissionSetAttribute class, you are restricted to using only the predefined named permission sets, as in the following example:

     [PermissionSet(SecurityAction.Demand,Name = "LocalIntranet")]     public void SomeMethod(  )     {} 

Note that you cannot use the Everything permission set (probably because it's specific to the .NET Configuration tool).

If you want to use a custom permission set declaratively, you need to provide the attribute with an XML representation of that set. You can assign the name of a file containing that XML to the File property of the attribute. Using a file gives you the option of changing the permission set you demand or assert after deployment. However, a file can be tempered with (unless you apply NTFS protections to it), and besides, declarative security attributes are better used in a static security context. With a custom permission set, I recommend that you use the XML property of PermissionSetAttribute. Whether you use the File property or the XML property, you will need to prepare the XML representation of your custom permission set. The easiest way to do that is to write a short program that constructs the permission set programmatically and then calls its ToString( ) method. The ToString( ) method of the PermissionSet class returns the XML encoding of the permission set. Next, either save that string to a file (for use with the File property), or hardcode it in the assembly for use with the XML property, as shown in Example 12-11.

Example 12-11. Defining and using a custom permission set
 //Step 1: Construct a custom permission set programmatically and //encode into XML   IPermission permission1 = new UIPermission(PermissionState.Unrestricted); IPermission permission2 = new FileIOPermission(PermissionState.Unrestricted); PermissionSet permisssionSet = new PermissionSet(PermissionState.None); permisssionSet.AddPermission(permission1); permisssionSet.AddPermission(permission2); //Copy the resulting string from the trace: Trace.WriteLine(permisssionSet.ToString(  ));   //Step 2: Build a constant representing the custom permission set public static class CustomPermissions {    public const string MyPermissionSet = @" <PermissionSet class = ""System.Security.PermissionSet"" version=""1"">    <IPermission class = ""System.Security.Permissions.UIPermission,                                           mscorlib,                                           Version=2.0.0.0,Culture=neutral,                                           PublicKeyToken=b77a5c561934e089""                                           version=""1""                                           Unrestricted=""true""/>    <IPermission class = ""System.Security.Permissions.FileIOPermission,                                           mscorlib,                                           Version=2.0.0.0,Culture=neutral,                                           PublicKeyToken=b77a5c561934e089""                                           version=""1""                                           Unrestricted=""true""/> </PermissionSet>";}   //Step 3: Use the custom permission set [PermissionSet(SecurityAction.Demand,XML = CustomPermissions.MyPermissionSet)] public void SomeMethod(  ) {} 

You can even combine your custom permission set with the named permission sets in a demand or demand-choice manner. If you use demand choice, only one of the demanded permission sets has to be satisfied:

     [PermissionSet(SecurityAction.DemandChoice,                                               XML = CustomPermissions.MyPermissionSet)]     [PermissionSet(SecurityAction.DemandChoice,                                               Name = "LocalIntranet")]     public void SomeMethod(  )     {} 

12.3.6. Assembly-Wide Permissions

Instead of type-based or even method-based security configuration, you can declaratively apply security permission attributes to an entire assembly, affecting every component in the assembly. All the permission attributes can be applied at the assembly level as well, although the semantics of the security action taken are different from those for a type or method. In fact, you can't apply the values of the SecurityAction enum shown so far on an assembly, and the compiler enforces that. The SecurityAction enum provides three specific values that can be applied only at the assembly scope, again enforced by the compiler:

     public enum SecurityAction     {        //Assembly values:        RequestMinimum,        RequestOptional,        RequestRefuse        //More type and method values     } 

Even though the compiler provides the required degree of type safety for the SecurityAction enum, it would have been cleaner to factor it into two enums: SecurityActionType and SecurityActionAssembly.


You can use these security action values with an individual permission, such as a request for full access to the C:\Temp directory:

     [assembly : FileIOPermission(SecurityAction.RequestMinimum,                                ViewAndModify= @"C:\temp")] 

You can even use these values with a named permission set:

     [assembly : PermissionSet(SecurityAction.RequestMinimum,Name = "Internet")] 

You can apply an assembly permission attribute multiple times:

     [assembly : FileIOPermission(SecurityAction.RequestMinimum,                                ViewAndModify= @"C:\temp")]     [assembly : FileIOPermission(SecurityAction.RequestMinimum,                                ViewAndModify= @"D:\temp")] 

The SecurityAction.RequestMinimum value indicates to .NET that this assembly requires the specified permission or permission set to operate. Without it, there is no point in loading the assembly or trying to create the types in it. The SecurityAction.RequestRefuse value is useful when all the types in an assembly require denying a particular permission or permission set, or when you explicitly want to reduce the permissions granted to the assemblywhen .NET computes the permissions granted to the assembly, it subtracts the refused permissions from the administratively granted permissions. The SecurityAction.RequestOptional value indicates to .NET what permissions this assembly wants in addition to the minimum permissions requested. Optional permissions also indicate that the assembly can operate without them and that no other permissions are required. Specifying optional and refused permissions can prevent the assembly from being granted permissions it doesn't require. Although not technically necessary, it's always better to refuse any permissions that an assembly is granted but doesn't require. Doing so reduces the chance of abuse by malicious clients and counters luring attacks. The final permissions granted must therefore take into account the assembly attributes and the configured policy.

If no assembly attributes are present, .NET grants the assembly the administratively configured permissions according to the security policies. However, if assembly security attributes are present, .NET follows the following algorithm. First, .NET retrieves the permissions granted by the .NET administrators. It then verifies that the requested minimum set of permissions is a subset of the granted policies. If so, .NET computes the union of the minimum and optional permissions, and intersects the result with the administratively granted permissions. Finally, .NET subtracts the refused permissions from the intersection. Figure 12-23 summarizes this algorithm by using formal notations.

Figure 12-23. Computing assembly permissions when assembly permission attributes are provided




Programming. NET Components
Programming .NET Components, 2nd Edition
ISBN: 0596102070
EAN: 2147483647
Year: 2003
Pages: 145
Authors: Juval Lowy

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