Understanding How .NET Role-Based Security Differs


Anyone who’s spent time working with the security features provided by the Win32 Application Programming Interface (API) knows that these measures concentrate on individuals and groups. (Don’t assume that any user is necessarily a person—it could be another application, remote computer, or other device.) If you give an individual user access to a system resource, they always have access to that resource no matter how they access the resource. They have access to the resource from the desktop or from the Internet. This kind of security doesn’t consider the environment.

Groups only add to the problem because you can inadvertently give a user access to resources through a group membership that the user shouldn’t have. Perhaps a manager hasn’t trained the user on how to make entries in the accounting system yet. However, because the user belongs to the accounting department, they have access to the accounting system and can make entries in it.

The Win32 API technique also has other problems. Remember that this system has two kinds of lists. The first grants access to a resource, while the second denies access to a resource. (See the “Considering Access Problems with the Win32 API” section in Chapter 14 for additional details.) It’s possible that a single resource could have entries that both grant and deny access. When this situation occurs, a user who should have access to a resource may not be able to access it because the entry granting access to the resource appears earlier in the list than the entry denying access to the resource. The reverse situation can also occur, which means there’s a hidden security hole in your system.

The following sections further refine the differences between role-based, code access, and user identity security. The coding examples show how to overcome some of the limitations of Win32 API security using the new features in the .NET environment. The big thing to remember, however, is that the Common Language Runtime (CLR) still depends on the Win32 API, so you need to exercise care in saying that these measures are perfect. Until the underlying operating system is secure, you’ll always have to look at these security features as useful, but not complete.

Tip

For many developers, the main issue in shifting gears from Win32 to .NET is how each system manages security. Under .NET an object doesn’t have any rights until it provides evidence that CLR feeds through a policy to produce a permission. The idea is that CLR grants all rights. An object seeks permission to perform a task. If you can keep this basic concept in mind, then all the complexities discussed in this book become easier to understand.

Defining Code Access Security versus Role-based Security

Previous chapters have mentioned code access and user identity security in passing. The example in the “Executing Code in the Managed Environment” section of Chapter 1 demonstrated principles of using code access security, while the “Using Role-based Security” section demonstrated role-based security. The “Controlling Access” section of Chapter 3 introduced the idea that both techniques provide some level of control over the computing environment. The concepts in these two chapters discuss some of the hows of .NET security, but not the why, where, or when. Unfortunately, these three questions are the ones that developers ask most often. The following sections answer all three questions.

Why Use Code Access or Role-based Security

Before you proceed any further, it’s important to realize that the .NET programs you write always use both code access and role-based security. Even if you don’t include a single line of security code in your program, .NET provides default levels of security for your application. Therefore, the advantage of including custom security code in your application isn’t one of adding security—it’s already there. The real advantage is controlling security, which is what everyone should be worrying about now.

The fact that a security breach is going to occur is well established. However, knowing that the breach has happened, and then controlling the breach, is the basis for many security schemes today. The .NET approach to security helps you control not only the user, but also the code that the user relies on to perform work.

You probably have a good understanding of user issues, but code access security can present a problem because developers haven’t had to work with it in the past. At one time, every piece of code you ran on a computer came from one source. There was no need to worry about online connections, email, and crackers knocking on your firewall door (or looking for cracks to sneak in). Today, users face a deluge of code from myriad sources. Sometimes the code enters a system without anyone knowing. In short, code could be trusted at one point, but you don’t have that luxury anymore.

Systems are also more complex today. The advances in computer speed and resources have fueled unheard levels of development. This speed and complexity places a burden on you as a developer to protect users of your code. The second reason you want to use code access security is to ensure that no one uses your code incorrectly. Code access security lets you define how someone can use the code and more important, what types of tasks they can’t use the code to perform. For example, if you decide that your code should never access the hard drive, you can tell .NET that you don’t want it to perform that task using declarative security. (See the “Defining Effective Declarative Security” section for details on this kind of security.) In short, you can ensure that even if a cracker somehow gains access to your code, that the cracker can’t force your code to perform tasks that you never envisioned it doing.

One of the special features that you probably won’t see mentioned a lot is that your code can insist that a user have a digital signature. In addition, a vendor can provide a digital signature as evidence for gaining access to specific resources. Your code can actually check for a particular strong name using the StrongNameIdentityPermission class. (See the “Developing a Secure Desktop Application Installation” section to see how you can implement strong name identity security.) Although digital signature technology probably has flaws (see the “Beware of the Cracked Symmetric Algorithm” section of Chapter 7 for details), it’s considered very reliable.

Using a digital signature is only an option if you add the correct code to your application, which means you have to become proactive.

Another good reason to declare security requirements is error handling. If you don’t define the security requirements for your code, CLR will assume the current environment meets all the code requirements when it runs the program. Unfortunately, the environment varies by user, mode of access, and a number of other characteristics that you can’t determine when you write the program. Failure to declare security requirements for your program means you must be ready to handle every potential security problem that could crop up. Of course, developers of unmanaged code have to handle every security problem because they don’t have a means for declaring the requirements—at least .NET gives you the choice.

When to Use Code Access or Role-based Security

It’s important to consider when you should use a particular mode of security. One tendency of developers today is to create a secure user environment. The draconian security measures imposed by a recent version of Outlook and Outlook Express regarding attachments are just one example. After applying this patch, users found it nearly impossible to do anything with attachments sent to their email. You can find a particularly interesting InfoWorld take on this patch at http://archive.infoworld.com/articles/hn/xml/01/05/04/010504hnairf.xml. Another good view of the topic appears on Woody’s Office Watch at http://www.woodyswatch.com/winxp/archtemplate.asp?2-n45. The point is that the user security is often the wrong approach to take—sometimes you need to control the code.

Tip

You can avoid the whole issue of securing some types of code for Internet use by employing zone security using the ZoneIdentityPermission class. This class simply states that if the caller comes from anything other than the requested zone, the code won’t execute. Zones are explained in the section “Understanding Zones,” and you can see an example of this type of security in the “Using Principal and Identity Objects” section of the chapter.

Look at the issue from this perspective. If you know that you signed all of the code on your system, then any unsigned code probably came from a nonsecure source and you shouldn’t execute it. This is an oversimplification of the problem, but it often helps to look at issues from this perspective. Because any code you execute can check for the security of other .NET code, it becomes possible to maintain good security even when the user downloads a virus from the Internet. The .NET Framework provides a number of ways to check the caller’s identity and the source of the code. For example, you can use the SiteIdentityPermission or URLIdentityPermission classes to verify the source of an Internet call.

Here’s the reality check on when to use a particular coding technique. I’d love to say that code access security works completely today, but there’s a lot of unmanaged code out there and it’s not going to go away anytime soon. Consequently, you have to make a judgment call somewhere along the way. Do you restrict unmanaged code? That’s not practical because even Windows is unmanaged code. (Even Microsoft’s latest Windows 2003 Server is composed entirely of unmanaged code.) Consequently, role-based security that not only defines the level of trust you have in the user, but also defines the user’s training and expertise levels is important. Role-based security can fulfill this need, but only if you stretch the Microsoft definition to the breaking point.

Where to Use Code Access or Role-based Security

One of the most important features of the .NET security scenario is that network administrators have better control over permissions. A network administrator can use the .NET Framework Configuration Tool (see the “Using the .NET Framework Configuration Tool” section of the chapter) to configure any code you create. The network administrator changes are manual, however, and .NET defaults to any security you define as the automatic option. Therefore, the quick answer to the question of where to use code access and role-based security is everywhere within reason. You need to assume that no one is ever going to look at the security provided by your code and create reasonable defaults based on that decision.

Given that most developers have severe restrictions on the time they can spend writing an application, however, adding complete security everywhere can become problematic. The reality is that you have to choose the areas where you add code access or role-based security carefully. In addition, you need to decide whether to demand a certain level of security and decide whether imperative or declarative security is the best approach. This chapter helps you make those decisions and you’ll see a number of coding examples that demonstrate these techniques as the book progresses.

Understanding Zones

The zone from which code loads determines a basic level of security. For example, code contained on your local machine is generally safer than code downloaded from the Internet. Knowing the original location of code can help you determine its trustworthiness. By default, CLR defines five different zones (most of which look familiar to anyone who uses Internet Explorer).

  • MyComputer

  • Intranet

  • Trusted

  • Internet

  • Untrusted

  • NoZone

Anything that resides on the local machine is in the MyComputer zone. The Intranet zone includes any code downloaded using a Universal Naming Convention (UNC) location such as \\ServerName\Drive\Folder\Filename.TXT. CLR also uses the Intranet zone for code downloaded from a Windows Internet Name Service (WINS) site, rather than a standard IP site, such as a local Web server. Anything outside of these two zones is in the Internet zone. Initially, the Trusted and Untrusted zones are empty. However, the network administrator can place sites that are normally in the Internet zone into either the Trusted zone (to raise its confidence level) or the Untrusted zone (to lower its confidence level). The NoZone zone is a temporary indicator for items that CLR has yet to test. You should never see this zone in use.

Defining Membership and Evidence

The word evidence brings up the vision of a court with judge and jury for many people. The image is quite appropriate for the .NET Framework because any code that wants to execute must present its case before CLR and deliver evidence to validate any requests. CLR makes a decision about the code based on the evidence and decides how the evidence fits within the current policies (laws) of the runtime as set by the network administrator. Theoretically, controlling security with evidence as CLR does allows applications built on the .NET Framework to transcend limitations of the underlying operating system. Largely, this view is true. However, remember that CLR is running on top of the underlying operating system and is therefore subject to its limitations. Here’s the typical evidence-based sequence of events.

  1. The assembly demands access to data, resources, or other protected elements.

  2. CLR requests evidence of the assembly’s origins and security documents (such as a digital signature).

  3. After receiving the evidence from the assembly, CLR runs the evidences through a security policy

  4. The security policy outputs a permission based on the evidence and the network administrator settings.

  5. The code gains some level of access to the protected element if the evidence supports such access; otherwise, CLR denies the request.

Note that the assembly must demand access before any part of the security process occurs. The Win32 API normally verifies and assigns security at the front end of the process—when the program first runs. (A program can request additional rights later or perform other security tasks.) CLR performs verifications as needed to enhance system performance.

Evidence includes a number of code features. CLR divides code into verifiable and non-verifiable types. Verifiable code is type safe and adheres to all of the policies defined by the .NET Framework. Consequently, code output by Visual Basic is always verifiable. Visual C# can output non-verifiable code because it includes direct pointer manipulation features. However, in general, CLR considers C# code verifiable. Visual C++ is a little less verifiable because it not only includes pointer support, but also functions such as reinterpret_cast. Older code, such as that found in most Windows DLLs and COM objects, is always non-verifiable. Interestingly enough, loading non-verifiable code is a right that CLR grants to local applications only as a default. Remote programs have to request this right.

CLR defines two kinds of evidence: assembly and host. You can create any number of custom evidence types by deriving from the Evidence class. Custom evidence resides within the assembly as assembly evidence. CLR also ships with seven common evidence classes that cover most needs. These seven classes provide host evidence because Microsoft implemented them as part of the host (CLR).

  • ApplicationDirectory

  • Hash

  • Publisher

  • Site

  • StrongName

  • URL

  • Zone

The ApplicationDirectory, Site, URL, and Zone classes show where the code came from. The Publisher and StrongName classes tell who wrote the code. Finally, the Hash class defines a special number that identifies the assembly as a unique entity—it shows whether someone has tampered with the content of the assembly. See the “Developing a Secure Desktop Application Installation” for an example of how to use these classes to implement security for an application.

Each of the host evidence classes has an associated membership condition class. For example, the ApplicationDirectory class, which is the evidence presented to the policy, uses the associated ApplicationDirectoryMembershipCondition class to determine its membership status. When CLR passes evidence to one of the membership classes, the object determines whether the assembly in question belongs to a particular code group. If the assembly is a member of the code group, then CLR authorizes the assembly to perform code group tasks. Listing 4.1 shows a typical example of membership testing. (You can find this code in the \Chapter 04\C#\CheckMembership or \Chapter 04\VB\CheckMembership folder of the source code located on the Sybex Web site.)

Listing 4.1 Discovering Code Group Membership

start example
private void btnTest_Click(object sender, System.EventArgs e) {    // Get the current assembly.    Assembly Asm;    Asm = Assembly.GetExecutingAssembly();    // Get the evidence from the assembly.    Evidence EV;    EV = Asm.Evidence;    // Create a membership condition check.    ZoneMembershipCondition ZoneMember;    ZoneMember = new ZoneMembershipCondition(SecurityZone.MyComputer);    // Check for application directory membership.    if (ZoneMember.Check(EV))       MessageBox.Show("Assembly is a member.");    else       MessageBox.Show("Assembly doesn’t belong."); }
end example

This code is relatively straightforward. You need access to the assembly to get the evidence needed for this check. The example gains access to the current assembly using the GetExecutingAssembly() method. However, you could also use calls such as LoadAssembly() to load an external assembly.

Once the code has access to the assembly, it uses the Evidence property to get all of the evidence for the assembly. As shown in the example in the “Using the System.Reflection .Assembly.Evidence Property” section, most assemblies support four kinds of evidence as a minimum: Zone, URL, StrongName, and Hash.

This code checks for Zone class membership using the ZoneMembershipCondition class. The Check() method returns a simple Boolean value indicating whether the assembly is part of the specified class, which is SecurityZone.MyComputer in this case. Because you’re executing this program from your desktop, the check likely passes in this case. However, if you were to check for some other zone, the check would fail. Note that checking membership doesn’t generate a permission object—all this check does is tell you whether an assembly has a particular membership.

Using Permission Objects

So far, the chapter has covered the kinds of security you can use, how the system uses evidence, and how to determine membership in a particular code group. All of these facts help you understand how security works, but your code still doesn’t have permission to perform any tasks. When CLR loads an assembly, the assembly lacks any rights—it can’t even execute code. Consequently, the first task CLR must perform with the assembly is to use the evidence and the code group memberships to determine what permissions the assembly has. To perform this task, CLR must run the evidence through the policies set up by the network administrator. Listing 4.2 shows an example of how you determine rights based on policies. (Note that this listing isn’t complete. You can find the complete listing in the \Chapter 04\C#\GetPermission or \Chapter 04\VB\GetPermission folder of the source code located on the Sybex Web site.)

Listing 4.2 Getting a Permission List Using a Policy

start example
private void btnTest_Click(object sender, System.EventArgs e) {    IEnumerator       Policies;   // Security policies.    PolicyLevel       Policy;     // A single policy.    PolicyStatement   Statement;  // A list of permissions.    Assembly          Asm;        // Current assembly.    Evidence          EV;         // Security evidence.    StringBuilder     Output;     // Output data.    // Initialize the output.    Output = new StringBuilder();    // Get the current assembly.    Asm = Assembly.GetExecutingAssembly();    // Get the evidence from the assembly.    EV = Asm.Evidence;    // Get the all of the policies.    Policies = SecurityManager.PolicyHierarchy();    while (Policies.MoveNext())    {       // Get the current policy.       Policy = (PolicyLevel)Policies.Current;       // Get the policy name.       Output.Append("Policy: " + Policy.Label);             // Determine the permissions for this policy.       Statement = Policy.Resolve(EV);       Output.Append("\r\n" + Statement.PermissionSet.Count +          " Permissions:\r\n" + Statement.PermissionSet);       // Get the attributes.       Output.Append("Attributes: " +          Statement.Attributes + "\r\n\r\n");    }    // Display the results.    txtOutput.Text = Output.ToString();  }
end example

The code begins by getting the executing assembly and the evident it contains. It uses this information to create a list of polices using the PolicyHierarchy() method. This method actually returns a list of policy objects you can enumerate using an IEnumerator object.

The PolicyLevel object, Policy, contains the individual policies associated with the assembly. Notice the Policy.Resolve() method call. This call sends the assembly evidence to the policy for evaluation. The output is a PolicyStatement object, which includes the permissions generated by the policy. Each PolicyStatement includes a PermissionSet property that defines the permissions for the assembly based on that policy.

You can use other techniques to build a set of permissions. For example, you can determine the permissions for a Web site. However, you must build the evidence because it doesn’t already exist in a neat package. In addition, you need to consider which evidence to present to the policy. Listing 4.3 shows an example of building evidence to access a Web site (you can supply any URL you wish).

Listing 4.3 Building Evidence to Obtain Permissions

start example
private void btnUrlTest_Click(object sender, System.EventArgs e) {    PermissionSet     Perms;      // A single policy.    Evidence          EV;         // Security evidence.    // Create evidence based on the URL.    EV = new Evidence();    // Fill the evidence with information.    EV.AddHost(new Url(txtUrl.Text));    EV.AddHost(Zone.CreateFromUrl(txtUrl.Text));    // Determine the current permissions.    Perms = SecurityManager.ResolvePolicy(EV);    // Create the output.    txtOutput.Text = Perms.ToString(); }
end example

This example still relies on evidence. However, you must build the evidence using the AddHost() method. The evidence consists of an URL and a Zone in this situation, but you can any acceptable form of evidence. Notice that this example uses the SecurityManager object to resolve the policy. Figure 4.1 shows the output from this example.

click to expand
Figure 4.1: Code access permissions generally appear in XML format.

Notice that the information uses an XML format. You’ll see in the “Using the .NET Framework Configuration Tool” section that this information can come in quite handy for defining policies.

Each of the permissions listed includes the full class information, which makes it easier to determine precisely what the permission means. For example, the first permission is System.Security.Permissions.EnvironmentPermission. This permission controls access to environmental data. The Web site in question can’t access all of the environmental information, but it can access the username. One of the last items in the list (at least for this URL) is the System.Security.Permissions.ZoneIdentityPermission. This permission shows that the URL is within the Intranet zone for my machine. Testing URLs on the Web correctly shows zone change.

Tip

You can find a complete list of standard permissions for the .NET Framework at http://msdn.microsoft.com/library/en-us/cpref/html/frlrfSystemSecurityPermissions.asp . It’s also easy to find additional class resources on the .NET 247 site at http://www.dotnet247.com/247reference/System/Security/Permissions/System.Security.Permissions.aspx .

Once you have access to permission objects, you can modify the permissions for the object by using the PermissionSet members. For example, you can add a permission using either the AddPermission() or the Assert() methods. Be aware, however, that the Assert() method can cause security vulnerabilities by giving an object rights that other objects in the hierarchy don’t have. In addition, the Assert() method requires that the code have the Security PermissionFlag.Assertion permission, which CLR doesn’t grant in some cases (making an Assert() method call harder to make than AddPermission()). Neither of these methods will allow you to add permissions that the current policy doesn’t allow—you can’t use these calls to circumvent security measures. You can also use the FromXml() method to load an XML formatted file that contains rights the object should have.

Using Principal and Identity Objects

Much of the conversation so far in this chapter has dealt with the code calling a method. Code access security is the main component you’ll use to ensure the safety of your applications in a distributed environment. In addition, you’ll use role-based security to ensure the caller has the proper credentials. Principal and identity objects generally relate to users or at least a caller of some type. You’ll find the essentials for these two categories of objects in the System.Security .Principal namespace.

An identity object represents the user. It includes the user’s name, credentials, and other personal information that belongs to the user. On the other hand, a principal object is the security context in which the user is operating. The security context varies by location, application, and other criteria. The combination of identity and principal objects determines the user’s rights to use resources and request services. The example in the “Using Role-based Security” section of Chapter 1 demonstrates the use of identity and principal objects for role-based security.

Fortunately, you can use the principal and identity objects for more than checking the user’s identity and verifying they’re in a specific role. For example, using a simple login mechanism is great when a system setup confines the user to their local machine. It begins to break down as the user moves on to the LAN. When you get to a fully distributed environment where the user could be anyone with the right password, the whole idea of validating a user with a password alone seems absurd.

Principal and identity objects help you maintain a secure environment in a distributed application. The LinkDemand and InheritanceDemand SecurityAction enumeration members help in this environment because they place the burden of proof on the caller. For example, you can create a LinkDemand that includes a StrongNameIdentityPermission requirement for a caller with a specific public key. Even when a method is marked public, the caller can’t use it unless they supply the required security key. The same holds true for abstract classes. You can mark methods is an InheritanceDemand that forces anyone deriving from that class to implement security based on the permissions provided with your base class.

Here’s an example of a class marked for use in a specific zone. (Note that this listing isn’t complete. You can find the complete listing in the \Chapter 04\C#\IdentityPermission or \Chapter 04\VB\IdentityPermission folder of the source code located on the Sybex Web site.)

public class Secured {    [ZoneIdentityPermission(SecurityAction.LinkDemand,        Zone=SecurityZone.MyComputer)]    public static void SaySomething(String Input)    {       // Display the input messsage.       MessageBox.Show(Input);    } }

The implementation is simple. The biggest problem is that IntelliSense breaks down, in this case, and doesn’t really show you how to create the declarative statement shown in the example. (This pop-up help doesn’t tell you which arguments you can use after the SecurityAction entry.) Once you have the protected class method in place, all you need to do is write code to access it. Executing the example on the local machine always works because the local hard drive is in the SecurityZone.MyComputer zone. However, the simple act of moving the program to a network hard drive makes the code fail. Figure 4.2 shows the error information.

click to expand
Figure 4.2: Even small security additions can greatly affect the execution of a program.

The interesting feature of this program is that you can run the program locally, but not from a remote location. The local user is unaffected by the change. This example has the effect of making remote access impossible (or at least highly unlikely), which make certain types of cracker exploits impossible. The identity of the user remains unchanged, but the context (principal) in which the user operates changes when you move the example program from a local drive to a remote drive. This example wouldn’t be possible using the Win32 API because that environment only checks the user’s identity. This example demonstrates one way in which the .NET Framework can help you perform additional checks on how a user accesses a program.




.Net Development Security Solutions
.NET Development Security Solutions
ISBN: 0782142664
EAN: 2147483647
Year: 2003
Pages: 168

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