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 7 is similar to Example 2,
except it uses declarative security to demand file I/O permission for a
specific predetermined file before proceeding to perform the
calculation.
Example 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 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 7 to Example 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 8 is functionally identical to Example 6, but it's much simpler because it uses declarative security.
Example 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. |
|
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.
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 possible—usually
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( )
{...}
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 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. |
|
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.
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.