Implement security for a Windows service, a serviced component, a .NET Remoting object, and an XML Web service.
The .NET Framework offers a wide variety of security features. You can choose to run your machine in wide- open mode, with every user allowed to execute any .NET code, or you can lock down things selectively. You can control which programs have access to which resources, or which users have the right to execute which programs.
Broadly speaking, .NET security breaks down into two separate areas:
Code access security manages the security of .NET source code itself. You can tell the .NET Framework the resources your code needs to execute properly, and the .NET Framework will check for permission to access those resources on the machine at runtime. Code access security is very flexible, including the capability to define your own sets of necessary permissions. Code access security can also be used by administrators to ensure that undesired code never gets the chance to run on a system.
Role-based security manages the user rather than the code. Using role-based security allows you to provide (or deny) access to resources based on an identity provided by the user running the code. In practical terms, this means that you can limit program execution to particular users or groups on the computer.
I'll cover both these types of security in this chapter, beginning with code-based security. The .NET Framework also includes other security features, notably public-key and private-key encryption, which are not a part of the 70-310 exam.
Code access security controls what code can do on your computer. Code access security is centered around permissions to use resources. The .NET Framework has an entire object-oriented system for managing code access security and the associated permissions. In the following sections, you'll learn about concepts involved in code access security:
You'll also learn how to manage code access security. In particular, code can request permissions on a very fine-grained scale, and the administrator can choose to allow permissions on an equally fine-grained scale.
Code access security is based on specific permissions that the Common Language Runtime can grant to or deny from code. For example, the authorization to read or write information in the Windows Registry requires the Registry permission on the part of your code. As you'll see later in the chapter, code can make four different types of permission requests :
It can request the minimum permissions that it requires to run.
It can request optional permissions that it would like but does not require.
It can refuse permissions to ensure that it does not have access to particular resources.
It can demand permissions on the part of calling code.
Based on a variety of factors (including the origin of the code and information in the machine and application configuration files), the Common Language Runtime decides whether a particular permission will be granted. If a piece of code is unable to obtain the minimum permissions that it requires, that piece of code won't execute. The security settings of the computer determine the maximum permissions that code can be granted, but code is allowed to request (and receive) fewer permissions than the maximum.
The .NET Framework groups permissions into three types. Code access permissions represent access to a protected resource or the authorization to perform a protected operation. Identity permissions represent access based on credentials that are a part of the code itself. Role-based permissions represent access based on the user who is running the code. Each permission in the .NET Framework is represented by a particular class that derives from System.Security.CodeAccessPermission. Table 11.1 lists the available code access permissions, which are the most important for controlling the actions that code can take on a particular computer.
Controls access to the System.DirectoryServices namespace.
Controls access to domain name system (DNS) services.
Controls access to environment variables .
Controls access to the Windows event log.
Controls access to files selected from the Open dialog box.
Controls access to reading and writing files and directories.
Controls access to private virtual file systems.
Controls access to isolated storage.
Controls access to message queuing via MSMQ.
Controls access to data via the System.Data.OleDb namespace.
Controls access to performance counters.
Controls access to printers.
Controls access to the reflection features of .NET.
Controls access to the Windows Registry.
Controls access to unmanaged code.
Controls access to starting and stopping services.
Controls access to Windows sockets.
Controls access to data via the System.Data.SqlClient namespace.
Controls access to the user interface.
Controls access to making Web connections.
Custom Permissions If none of these permissions are quite right for your application, you can also define custom permissions. I'll talk about custom permissions later in the chapter.
To start working in the .NET security framework, your code can request the minimum permissions that it needs to function correctly. Step by Step 11.1 demonstrates the syntax for making such a request.
STEP BY STEP
11.1 Requesting Minimum Permissions
This example requests permissions by applying an attribute to the assembly. The FileDialogPermissionAttribute allows the assembly to request the FileDialogPermission, which in turn allows access to the system's file dialogs. In this particular case, the code runs without any problem, which means that it was granted the requested permission. That's because, by default, you have full permissions to run any code that originates on your own computer. To see code access security in action, you must learn to manage the permissions granted to code on your computer. As a first step, you must understand the concepts of code groups and permission sets.
A code group is a set of assemblies that share a security context. You define a code group by specifying the membership condition for the group. Every assembly in a code group receives the same permissions from that group ; however, because assemblies can be members of multiple code groups, two assemblies in the same group might end up with different permissions in the end.
The .NET Framework supports seven different membership conditions for code groups:
The application directory membership condition Selects all code in the installation directory of the running application.
The cryptographic hash membership condition Selects all code matching a specified cryptographic hash. Practically speaking, this is a way to define a code group that consists of a single assembly.
The software publisher membership condition Selects all code from a specified publisher, as verified by Authenticode signing.
The site membership condition Selects all code from a particular Internet domain.
The strong name membership condition Selects all code with a specified strong name.
The URL membership condition Selects all code from a specified URL.
The zone membership condition Selects all code from a specified security zone (Internet, Local Intranet, Trusted Sites, My Computer, or Untrusted Sites).
Cryptographic Hashing To calculate a cryptographic hash, the compiled code of an assembly is run through a cryptographic algorithm that generates a string of digits as a result. The string of digits is much shorter than the original assembly, and therefore easier to evaluate. If you run the same assembly through the algorithm, you'll get the same hash out, but two different assemblies are extremely unlikely to generate an identical hash value.
Permissions are granted in permission sets. A permission set is a set of one or more code access permissions that are granted as a unit. If you only want to grant a single permission, you must construct a permission set containing only that single permission: You can't grant permissions directly. The .NET Framework supplies seven built-in permission sets:
The Nothing permission set Grants no permissions.
The Execution permission set Grants permission to run but not to access protected resources.
The Internet permission set Grants limited permissions designed for code of unknown origin.
The LocalIntranet permission set Grants high permissions designed for code within an enterprise.
The Everything permission set Grants all permissions except for the permission to skip verification.
The SkipVerification permission set Grants the permission to skip security checks.
The FullTrust permission set Grants full access to all resources. This permission set includes all permissions.
You can also create your own custom permission sets, as you'll see in the next section.
The easiest way to grant or deny permissions in the .NET Framework is to use the Microsoft .NET Framework Configuration tool, as in Step By Step 11.2.
STEP BY STEP
11.2 Granting Permissions
In Step By Step 11.2, you first created a permission set (based on the built-in Everything permission set) that included every permission except for the permission to use the file dialog boxes. You then created a code group that contained the executable file for this chapter's examples and assigned the No FileDialog permission set to this code group. The result is that the code cannot run because at a minimum it requires the one permission that the new security policy will not grant to it.
Requesting permissions through the use of attributes is known as declarative security . A second method to request permissions is known as imperative security. With imperative security , you create objects to represent the permissions that your code requires (see Step By Step 11.3). Guided Practice Exercise 11.1 will give you additional practice with imperative security.
Command-Line Permissions The .NET Framework SDK also includes a tool, caspol .exe, that can manipulate code groups and permission sets from the command line. If you're trying to automate security operations, you can make good use of this tool from a batch file.
STEP BY STEP
11.3 Imperative Security
Step By Step 11.3 constructed a FileDialogPermission object representing unrestricted access to the file dialogs. It then called the demand method of that object to demand the permission from the operating system. When the security policy was such that the permission could not be granted, the code threw an exception.
Imperative Versus Declarative Security The only time that you absolutely must use imperative security is when you must make security decisions based on factors only known at runtime, such as the name of a particular file. In most other cases, you'll find that declarative security is easier to use.
Determining the actual permissions applied to any given piece of code is a complex process. To begin the process, think about permissions at the Enterprise level only. The Common Language Runtime (CLR) starts by examining the evidence a particular piece of code presents to determine its membership in code groups at that level. Evidence is just an overall term for the various identity permissions (publisher, strong name, hash, and so on) that can go into code group membership.
Code groups are organized into a hierarchy; in Step By Step 11.1, you created the Chapter11 code group as a child of the All Code code group. In general, CLR will examine all the code groups in the hierarchy to determine membership. However, any code group in the hierarchy can be marked as Exclusive (the effect of the check box you checked when creating the Chapter11 code group). CLR stops checking for group membership if code is found to be a member of an Exclusive code group. Either way, code will be determined to be a member of zero or more code groups as a first step.
Next, CLR retrieves the permission set for each code group that contains the code. If the code is a member of an Exclusive code group, only the permission set of that code group is taken into account. If the code is a member of more than one code group and none of them are an Exclusive code group, all the permission sets of those code groups are taken into account. The permission set for the code is the union of the permission sets of all relevant code groups. That is, if code is a member of two code groups, and one code group grants FileDialog permission, but the other does not, the code will have FileDialog permission from this step. This is a "least- restrictive " combination of permissions.
That accounts for the permissions at one level (the Enterprise level). But there are actually four levels of permissions: Enterprise, Machine, User, and Application Domain. Only the first three levels can be managed within the .NET Framework Configuration tool, but if you need specific security checking within an application domain ( roughly speaking, an application domain is a session in which code runs), you can do this in code. An application domain can reduce the permissions granted to code within that application domain, but it cannot expand them.
CLR determines which of the four levels are relevant by starting at the top (the Enterprise level) and working down. Any given code group can have the LevelFinal property; in which case the examination stops there. For example, if code is a member of a code group on the Machine level, and that group has the LevelFinal property, only the Enterprise and Machine levels are considered in assigning security. CLR computes the permissions for each level separately and then assigns the code the intersection of the permissions of all relevant levels. That is, if code is granted FileDialog permission on the Enterprise and Machine levels but is not granted FileDialog permission on the User level, the code will not have FileDialog permission. Across levels, this is a "most-restrictive" combination of permissions.
At this point, CLR knows what permissions should be granted to the code in question, considered in isolation, but code does not run in isolation; it runs as part of an application. The final step of evaluating code access permissions is to perform a stack walk. In a stack walk , CLR examines all code in the calling chain from the original application to the code being evaluated. The final permission set for the code is the intersection of the permission sets of all code in the calling chain. That is, if code is granted FileDialog permission but the code that called it was not, the code will not be granted FileDialog permission.
Determining Permissions The Microsoft .NET Framework Configuration tool can help you determine the effective permissions for a piece of code. Right-click the Runtime Security Policy node and select Evaluate Assembly to do so. You can see the effective permissions for an assembly here or get a list of all the code groups that contribute to the assembly's permissions.
Sometimes, you might want to request a particular permission even though your application doesn't absolutely require that permission to proceed. That's the purpose of optional permissions. If you refer back to the code in Step By Step 11.1, you'll see that part of the permission attribute is the SecurityAction.RequestMinimum flag. To request optional permissions, use the SecurityAction.RequestOptional flag.
To use optional declarative permissions in Visual Basic .NET, your code should have a Sub Main starting point with a Try...Catch block. If optional permissions for the assembly can't be granted, this block will catch the exception. If minimum permissions can't be granted, the program will be shut down whether this block is present or not.
Optional Versus Minimum When you request minimum permissions, CLR will give your assembly any other permissions it can in addition to the minimum permissions. When you request optional permissions, CLR will only give the assembly those permissions, and no others. So if you make an optional permissions request, you must be sure to request all the permissions that your code requires, including user interface permissions if you display any user interface.
You can also tell CLR about permissions that you do not want your code to have. This can be useful if the code is potentially available to untrusted callers (for example, users who invoke the code over the Internet), and you want to limit the potential harm they can do. The flag for this is SecurityAction.RequestRefuse.
Finally, you might want to ensure that all the code that calls your code has a particular permission. For example, you might want to raise an exception if any code in the calling stack doesn't have Registry permissions. You can do this by specifying SecurityAction.Demand in the declaration of the security attribute.
GUIDED PRACTICE EXERCISE 11.1
One reason you might choose to use imperative security rather than declarative security is to be able to easily catch security violations and respond to them automatically. In this exercise, use imperative security to selectively disable part of a user interface that the user isn't able to activate under the current security policy. You can start with the simple "browse for file" example from Step By Step 11.1 or invent your own problem.
Try this on your own first. If you get stuck or want to see one possible solution, follow these steps:
In Guided Practice Exercise 11.1, the imperative security object is used to check the permissions that the application has as soon as the form is loaded. If the call to the Demand method fails, you know the user won't be able to invoke the file dialog. In that case, the code disables the button that would otherwise launch the file dialog and puts up a warning message instead.
Configure security for a Windows-based application: Use custom attributes to configure security.
In some cases, the built-in security permissions might not fit your needs. For example, you might have designed a custom class that retrieves confidential information from your company's database, and you want to restrict permission on it by a more specific means than limiting SQL permissions.
In such cases, you can create your own custom permissions and add them to the .NET security framework. This requires quite a bit of code, and most developers will never need to perform this task. But just in case you do, I'll outline the process. You'll find more in-depth information, including all the code for a simple custom permission, in the Securing Applications section of the .NET Framework Combined Help Collection.
To implement a custom permission, you must create a class that inherits from the CodeAccessPermission class. Your new class must override five key methods to provide its own interfaces to the security system:
Copy Creates an exact copy of the current instance
Intersect Returns the intersection of permissions between the current instance and a passed-in instance of the class
IsSubsetOf Returns True if a passed instance includes everything allowed by the current instance.
FromXML Decodes an XML representation of the permission
ToXml Encodes the current instance as XML
Your class must support a constructor that accepts an instance of the PermissionState enumeration (which has a value of Unrestricted or None). You might also want to implement custom constructors related to your particular business needs. For example, a database- related permission might require a constructor that accepts a server name if permissions should be handled differently on test and development servers.
Although it's not strictly required, your code should also implement a method named IsUnrestricted, which returns True if this particular instance represents unrestricted access to the resource. This will make your custom permission more compatible with the built-in permissions in the .NET Framework.
To support declarative security, you should also implement an attribute class for your permission. This attribute class should derive from CodeAccessSecurityAttribute (which in turn derives from SecurityAttribute). The class should override the CreatePermission member of the IPermission interface. Within this function, you should create an instance of your base custom permission class and set its properties according to the parameters from the declarative security invocation. Any attribute class must be marked with the Serializable attribute so that it can be serialized into metadata along with the class to which it is applied.
For your custom permission to actually protect the intended resource, you must make changes to both the resource and to the .NET Framework on the computers on which the permission will be used. The changes to the resource are simple: Whenever an operation protected by the custom permission is about to be performed, the code should demand an instance of the permission. If the calling code can't deliver the permission, your class should refuse to perform the operation.
The changes to the .NET Framework are somewhat more complex. First, you must create an XML representation of your custom permission in the format expected by the Code Access Security Policy tool, caspol.exe. You can create this XML representation by instantiating your permission and calling its ToXml method. Given this XML representation, caspol.exe can add a permission set to the .NET Framework that contains your custom permission. It will also add the assembly that implements the custom permission to the list of trusted assemblies on the computer. You must perform this step on every computer on which your custom permission will be used.