.NET for Visual FoxPro Developers

Chapter 14
.NET Security

As sophisticated hackers continue to spawn new generations of malicious computer viruses, security has become an increasingly important issue for both software developers and end users. Microsoft has taken security to new levels in the .NET Framework. Not only does it help you avoid running potentially destructive code, it also gives you the ability to implement user security for denying and granting user access.

Just about every software application needs some type of security. In Visual FoxPro applications, security usually takes the form of preventing users from accessing secure areas of the application or from viewing and editing sensitive data. In VFP, you have to design and write your own security code or purchase a third-party application framework that provides this functionality for you. In contrast, there are two main types of security built right into the .NET Framework—role-based security and code access security.

Role-based security allows you to control what end users can do in your .NET applications. This is similar to the security you are probably already using in your Visual FoxPro applications. There may be functionality in your application you don’t want certain groups of users to access. You may have other areas of your application where you allow some users to have read-write privileges and others to have read-only privileges. The .NET Framework contains classes that provide this ability.

Code access security is not available in Visual FoxPro applications. It is enforced by the .NET runtime and helps you avoid running “evil” code that can do bad things to your computer. Code access security has become particularly important with the advent of .NET’s new deployment model allowing you to easily install .NET assemblies downloaded from the Internet on your computer. The downside of this model is assemblies may contain code that intentionally (or unintentionally) wreaks havoc on your computer. Because Microsoft is moving to this sort of Internet-downloadable software distribution model, they spent resources incorporating security into the .NET Framework.

The first part of this chapter focuses on code access security introducing the tools and classes provided by the .NET Framework and the Windows operating system you can use to define and enforce security for your desktop and Internet applications. The second part of the chapter shows you how to define and enforce role-based security. Fortunately, Microsoft has created a consistent model so once you learn one type of security you can easily learn the other.

Code Access Permissions

I’m not a big fan of running code from unknown sources on my computer. Although I’m religious about backing up my laptop—which is my main development machine—it would be a real pain to get it back in working order after being demolished by malicious code.

.NET helps you breathe a little easier in this regard if you’re careful and follow the rules. .NET allows you to grant or deny code the permission to perform a particular type of operation or access a specific resource. For example, you may want to deny an assembly the permission to perform read or write operations to files and directories on your local computer or network drives. You may also want to deny permission to access resources such as your local hard drive, your database, or your Windows Registry. This is where .NET permissions come in.

The .NET Framework has a variety of permission classes that enforce restrictions on managed code. Table 1 lists the different permission classes found in the framework. I’ll take a closer look at these classes later in this chapter and show you how to implement them in your .NET applications.

Table 1. .NET code access permissions

Permission

Description

Namespace

DirectoryServicesPermission

Access to the System.DirectoryServices classes.

System.DirectoryServices

DNSPermission

Access to the Domain Name System (DNS).

System.Net

EnvironmentPermission

Read or write environment variables.

System.Security.Permissions

EventLogPermission

Read or write access to event log services.

System.Diagnostics

FileDialogPermission

Access files selected by the user in an Open dialog box.

System.Security.Permissions

FileIOPermission

Read, append, or write files or directories.

System.Security.Permissions

IsolatedStorageFilePermission

Access private virtual file systems.

System.Security.Permissions

IsolatedStoragePermission

Access isolated storage, which is storage associated with a specific user and some aspect of the code’s identity, such as its Web site, publisher, or signature (abstract base class).

System.Security.Permissions

MessageQueuePermission

Access message queues through the managed Microsoft Message Queue (MSMQ) interfaces.

System.Messaging

OleDbPermission

Access to databases using OLE DB.

System.Data.OleDb

PerformanceCounterPermission

Access performance counters.

System.Diagnostics

PrintingPermission

Access printers.

System.Drawing.Printing

ReflectionPermission

Discover information about a type at run time.

System.Security.Permissions

RegistryPermission

Read, write, create, or delete registry keys and values.

System.Security.Permissions

SecurityPermission

Execute, assert permissions, call into unmanaged code, skip verification, and other rights.

System.Security.Permissions

ServiceControllerPermission

Access running or stopped services.

System.ServiceProcess

SocketPermission

Make or accept connections on a transport address.

System.ServiceProcess

SqlClientPermission

Access to SQL Databases.

System.Data.SqlClient

UIPermission

Access user interface functionality.

System.Security.Permissions

WebPermission

Make or accept connections on a Web address.

System.Net

 

As you peruse Table 1, you will see some permission classes that make a lot of sense, such as FileIOPermission, OleDbPermission, and SqlClientPermission. However, other classes such as UIPermission may seem unnecessary—why would you prevent code from displaying a message to the user?

Here’s a good example—a few times a year I get e-mails from well-meaning friends and relatives warning me of a virus that’s sweeping the globe. I’m told I can easily fix the problem by deleting a particular virus-laden file from my hard drive. In reality, the file I am told to delete has nothing to do with a virus, but is actually a file necessary to the Windows operating system. The moral of this story is that I may not want code from unknown sources popping up dialogs telling users to perform actions that can debilitate their computers.

Figure 1 displays all of the classes from Table 1 in a UML class diagram, to give you a clearer picture of the code access permission class hierarchy.

Figure 1. The code access permission class hierarchy.

Creating custom code access permissions

Although the built-in permission classes cover many of the permissions you need to administer in your application, there are times when you need to create your own custom permission classes. For examples of when and how you should create your own custom permissions, see the .NET Help topic “Creating Your Own Code Access Permissions”.


It’s a matter of trust

How much do you trust the code you’re getting ready to run? If you’ve written it yourself, and it resides on your local machine, the level of trust should be pretty high! However, if you’ve just downloaded an assembly from the Internet and a company you are not familiar with publishes it, your level of trust should be very low. So how do you specify which permissions apply to a particular assembly? This is done by means of a security policy.

Security policy

A security policy is a set of rules telling the .NET runtime what permissions to grant to assemblies from different sources. When the runtime gets ready to run an assembly, it determines the identity of the assembly (such as where the code came from and who published it) and then checks the security policy to determine the rights it should be given.

Security policy levels

There are four different security policy levels as shown in Table 2.

Table 2. Security policy levels

Policy level

Description

Specified by

Enterprise

Applies to all managed code for every computer and user on the network.

Administrator

Machine

Applies to all managed code on a computer.

Administrator

User

Applies to code in all the processes associated with the current operating system user.

Administrator or user

Application domain

Applies to managed code in the host’s application domain.

Set programmatically

 

These four security policies form a hierarchy, with the enterprise policy at the top of the food chain (Figure 2), and each subsequent policy beneath the previous.

Figure 2. Security policy hierarchy.

You set permissions at the enterprise level that apply to all computers and users on the network. Permissions set at the machine level apply to all users on a given machine and the user policy specifies permissions for the current operating system user. The application domain security policy level lets you specify security for a particular application domain.

Text Box: ¥

.NET’s application “domain” replaces the “process” as the smallest unit of application isolation on a Windows system. You can have multiple domains within a single process that isolate applications from each other. For more information, see the .NET Help topic “Application Domains Overview”.

At run time, when determining the permissions for an assembly, all security policy levels are evaluated, and the code is granted the minimum permissions granted by any of the levels.

In this chapter, I discuss security policies at the machine level. For information on setting security policy for all other levels, see the .NET Help topic “Security Policy Best Practices”.

Viewing and configuring code access security policies

There are two .NET tools you can use to configure code access security policies. The first is the Code Access Security Policy Tool (Caspol.exe) command-line utility. The other tool is the .NET Framework Configuration tool (Mscorcfg.msc) that provides a graphical user interface for configuring security policies. Although Caspol provides more capabilities, I will show you the .NET Framework Configuration tool in the sections that follow because its graphical interface helps you better understand the .NET Framework’s security concepts. For more information on Caspol, see the .NET Help topic “Code Access Security Policy Tool (Caspol.exe)”.

 

Text Box: ¥The enterprise, machine, and user-level security policies can be configured using one of these .NET configuration tools, but the application domain policy can only be set programmatically.

To launch the .NET Framework Configuration tool in Windows 2000, click the Windows Start button and select Programs | Administrative Tools | Microsoft .NET Framework Configuration. In Windows XP go to the Control Panel, select Administrative Tools, and then select Microsoft .NET Framework Configuration. When the tool appears, select the Runtime Security Policy node in the left pane (Figure 3).

Figure 3. The .NET Framework Configuration tool can be used to configure code access security policies.

The .NET Framework Configuration tool allows you to configure settings by using either wizards or configuration dialogs. The right pane contains a list of tasks with wizards. You will run one of these wizards later in the chapter, but first look at the tree view in the left pane.

If you expand the Runtime Security Policy node, you see the three configurable policy levels—Enterprise, Machine, and User (Figure 4).

Figure 4. Security policy can be configured at the Enterprise, Machine, and
User levels.

If you expand the Machine node, you see three additional nodes—Code Groups, Permission Sets, and Policy Assemblies (Figure 5).

Figure 5. Each policy level contains Code Groups, Permission Sets, and Policy Assemblies nodes.

The Enterprise and User policy levels also contain these nodes, but again, I am concentrating on the machine-level security policy in this chapter. The following sections describe what each of these nodes represents.

Code groups

Each security policy contains a hierarchy of code groups. A code group is a logical grouping of code with similar characteristics, such as where the code came from (for example, Local machine, Internet, Intranet).

For code to be included in a particular group, it must meet a specific membership condition. Each code group has only one code membership condition. The .NET Framework has eight default membership conditions represented by the classes listed in Table 3.

Table 3. Code group membership conditions

Membership condition

.NET class

Membership requirement

All code

AllMembershipCondition

A membership condition for all code.

Application Directory

ApplicationDirectoryMembership

Condition

Assemblies located in the same directory or in a child directory of the running application.

Hash

HashMembershipCondition

The hash value of the assembly.

Publisher

PublisherMembershipCondition

Assemblies digitally signed with a specified certificate.

Site

SiteMembershipCondition

The HTTP, HTTPS, or FTP Web site where the code originated

Strong Name

StrongNameMembershipCondition

Assemblies that have the specified strong name public key (and optionally name and version)

URL

UrlMembershipCondition

The URL where the code originated. An asterisk can be used as a wildcard character at the end of the URL.

Examples:

http://www.MySite.com/MyAssembly.dll

ftp://ftp.MySite.com/pub/*

Zone

ZoneMembershipCondition

The zone, or region, where the code originated.

 

The .NET runtime uses the criteria listed in Table 3 (also known as evidence) to determine the placement of code into a code group.

Expand the Code Group node in the .NET Framework Configuration tool (Figure 6) and you see the All_Code node. This node forms the root of the code group tree and the top of the code group hierarchy. This is the first code group listed in Table 3. All .NET assemblies belong to this code group—no membership condition is necessary!

Figure 6. All .NET assemblies belong to the All Code group by default—no membership condition is necessary.

If you expand the All_Code node, you see a list of child code group nodes (Figure 7).

Figure 7. The All_Code node sits at the top of a hierarchy of code group nodes that define membership conditions for assemblies.

“Zone” is also one of the code groups listed in Table 3. Because zones are one of the most commonly used code group membership conditions, the following section details each type of zone used by .NET security policies.

Zones

The Zone code groups provide a way to specify membership conditions based on an assembly’s origin. If you have toyed with Internet Explorer’s security settings, you may be familiar with the concept of zones. Table 4 lists the different zones used by .NET security policies.

Table 4. Security policy zones

Zone

Description

My Computer

Applies to code that exists on the local computer.

Local Intranet

Used for code located on a company’s intranet.

Internet

This is a “catch all” zone used for code located on Web sites not specified as belonging to any other zone.

Trusted Sites

Used for code located on Web sites you trust. The URL of one or more Web sites can be added to the Trusted zone.

Untrusted Sites

Used for code located on Web sites you do NOT trust. The URL of one or more Web sites can be added to the Untrusted zone

 

Now, I’ll show you how to use the .NET Framework Configuration tool’s Security Adjustment Wizard to view the security policy zone settings on your local computer. Afterwards, I’ll come back to the tree view and look at the settings in greater detail. To launch the wizard, select the Runtime Security Policy node in the left pane and click the Adjust Zone Security link in the right pane. This displays the Security Adjustment Wizard (Figure 8).

Figure 8. The Security Adjustment Wizard allows you to modify security policy for the local computer or for the current user.

The wizard allows you to modify the security policy for the local computer or the current user. Accept the default setting Make changes to this computer, click the Next button, and the Security Adjustment Wizard displays (Figure 9).

Figure 9. The Security Adjustment Wizard allows you to configure security settings for assemblies coming from a particular zone.

The top of the dialog displays a pane containing the different .NET security zones. Each of these corresponds to a zone code group node shown in Figure 7. To change the security level for a particular zone, select the zone at the top of the dialog and use the track bar at the bottom of the dialog to select the desired level of trust. To set the default level for a particular zone, click the Default Level button. Here are the different “level of trust” choices as described by their associated text:

·         Full Trust – Security checks are not performed and programs can access and use all resources on your machine. Avoid this setting unless you are certain no potentially harmful or error-prone programs can execute from the selected zone. This is the default setting for the My Computer zone.

·         Medium Trust – Programs might not be able to access most protected resources
such as the registry or security policy settings, or access your local file system without user interaction. Programs can connect back to their site of origin, resolve domain names, and use all windowing resources. This is the default setting for the Local Intranet zone.

·         Low Trust – Programs might not be able to access protected resources such as the registry, environment variables, domain names, or security policy settings. Programs have limited ability to use windowing resources and your local file system. This is the default setting for the Trusted Sites zone.

·         No Trust – Programs might not be allowed to execute. This is the default setting for the Internet zone and the Untrusted Sites zone.

If you click the wizard’s Next button, it displays a list of zones and their associated security levels (Figure 10). If you click Finish, it saves any changes you have made.

Figure 10. The last page of the Security Adjustment Wizard shows a list of all zones and their associated security level.


Specifying zone web sites

You can use Internet Explorer to specify the Web sites that belong to the Local intranet, Trusted sites, and Restricted sites zones.

From within Internet Explorer, select Tools | Internet Options from the main menu to launch the Internet Options dialog (Figure 11).

Figure 11. You can use the Internet Options dialog in Internet Explorer to specify the Web sites that belong to the Local intranet, Trusted sites, and Restricted sites zones.

Local Intranet zone

If you select the Local Intranet zone at the top of the Internet Options dialog, and then click the Sites button, it displays a Local intranet dialog (Figure 12) for specifying the Web sites included in the Local Intranet zone. All Web sites included in the Local Intranet zone use the security settings specified for this zone.

Figure 12. The Local intranet dialog allows you to specify the Web sites included in the Local Intranet zone.

If you click the Advanced… button, a second dialog is launched allowing you to add or remove Web sites from the Local intranet zone. You can also use this dialog to add Web sites that are not in your local intranet. You might do this if you want to give an external Web site the same permissions as sites on your local intranet—I recommend doing this judiciously!

Trusted sites

If you select the Trusted sites zone at the top of the Internet Options dialog and click the Sites button, it displays a Trusted sites dialog (Figure 13) that allows you to specify the Web sites included in the Trusted sites zone.

Figure 13. The Trusted sites dialog allows you to add and remove Web sites from the Trusted sites zone.

As you can see in Figure 13, I’ve added three Web sites to my Trusted sites zone, which grants additional “trusted” permissions to these Web sites.

Restricted sites

If you select the Restricted sites zone at the top of the Internet Options dialog and click the Sites… button, it displays the Restricted sites dialog (which is similar to the dialog shown in Figure 13) that allows you to add and remove Web sites from the Restricted sites zone.

Why would you want to restrict all software from a particular site? At times, you may determine that a site is known to contain unstable or malicious code. You can use this dialog to add that site to the list of restricted sites causing them to run in an extremely restricted environment or not allowing them to run at all!

Viewing code group detail

Now that you’ve seen a higher-level view of zone code groups, it’s time to take a more detailed look at them. To do this, launch the .NET Framework Configuration tool again, and in the left pane, expand the Runtime Security Policy | Machine | Code Groups | All_Code node as shown in Figure 7.

If you select the My_Computer_Zone, a description of the code group is shown in the right pane. This description informs you this code group allows full access to all resources and the current policy level is “FullTrust”.

At the bottom of the right pane is a list of Tasks that can be performed on the My_Computer_Zone code group—in this case, “Edit Code Group Properties” and “Add a Child Code Group”. You can also choose to perform these tasks (and more) by clicking the Action menu pad or right-clicking the node and selecting New or Properties from the menu (Figure 14).

Figure 14. The .NET Framework Configuration tool allows you to add, duplicate, copy, delete, rename, and edit security code groups.

If you select New from the menu, it launches the code group Properties dialog, allowing you to create a new code group. If you select Properties from the menu, it also launches the code group Properties dialog (Figure 15). 

Text Box: ¥

This section describes the basics of the code group Properties dialog. For more information on creating your own custom code groups, see the .NET
Help topic “Configuring Code Groups Using the .NET Framework
Configuration tool”.

The General tab of the dialog allows you to specify the name and description of the code group. At the bottom of the tab are two check boxes in the If the membership condition is met group box. As described by their associated text, these check boxes let you change how the .NET runtime evaluates permissions for the code group.

Figure 15. The code group Properties dialog lets you specify a code group’s name, description, membership conditions, and permission set.

If you click the Membership Condition tab, you see two combo boxes. The first combo box allows you to select the condition type for the code group (Figure 16).

Figure 16.The Membership Condition tab allows you to select a membership condition type for the code group.

The options listed in this combo box are the same code group membership conditions listed in Table 3 earlier in this chapter. You can also select a custom code group you define yourself (see the next section for details). Based on the option you select in the upper combo box, additional input controls are displayed beneath it or, in the case of the “All Code” and “Application Directory” selection, no additional input controls are displayed at all. If “Zone” is selected in the condition type combo box, a second combo box is displayed allowing you to select the desired zone (Figure 17).

Figure 17. When configuring a zone membership condition, a second combo box is displayed allowing you to select the desired zone.

Permission sets

The third tab of the code group Properties dialog introduces a new concept—named permission sets. A named permission set is a grouping of one or more permissions given a friendly name and description. Table 5 lists the built-in named permission sets found in the .NET Framework.

Table 5. Built-in permission sets

Permission set

Description

Full Trust

Full access to all resources.

Skip Verification

Skip verification of code in the assembly. This is a powerful permission that should only be applied to highly trusted code.

Execution

Permission to run, but no permission to use protected resources.

Nothing

No permissions. Code cannot run.

Local Intranet

The default policy permission set within an enterprise.

Internet

The default policy permission set suitable for code from an unknown origin.

Everything

All standard, built-in permissions, except the permission to skip verification.

 

At the top of the Permission tab is a combo box containing a list of the permission sets shown in Table 5. I’ll discuss a few of these so you can get a better understanding of how permission sets work.

If you select the “LocalIntranet” option from the Permission set combo box, it displays the permissions associated with the Local Intranet permission set (Figure 18).

Figure 18. The Permission Set tab of the code group Properties dialog allows you to view the permissions that belong to a given permission set.

You can select any of the permissions in the list box and click the View Permission button to view details for the permission. For example, if you view details for the “File Dialog” permission, the Permissions Viewer dialog is displayed and informs you “The File Dialog Permission is unrestricted”.

If you double-click the “Security” permission (which is the same as clicking the View Permission button), the Permissions Viewer dialog displays a list of security permissions that are granted and denied (Figure 19).

Figure 19. When you view a security permission in the Permission Viewer, it displays a list of permissions that have been granted or denied.

Creating custom permission sets

Notice when you edit one of the built-in permission sets, the title bar of the Permissions Viewer indicates the permission is (Read-Only). This is because all the built-in permission sets are read only. Although you can’t edit built-in permission sets, you can create your own.

If you expand the Permission Sets node in the .NET Framework Configuration tool, you see a list of the built-in permission sets (Figure 20).

Figure 20. You can view the built-in permission sets and create your own custom permissions sets via the .NET Framework Configuration tool.

To create your own custom permission set, right-click on the Permission Sets node and select New from the shortcut menu. This launches the Create Permission Set wizard (Figure 21). This wizard is pretty intuitive, so I won’t provide a detailed explanation of how to use it. If you need additional information, see the .NET Help topic “Configuring Permission Sets Using the .NET Framework Configuration Tool”.

Figure 21. The Create Permission Set wizard allows you to create your own custom permission sets.

Any custom permission sets you create belong to the policy level in which they are created. For example, if you create a new permission set under the Machine node, the permission set belongs to the machine-level security policy. Any custom permission sets you create automatically display in the .NET Framework Configuration tool.

Policy assemblies

As mentioned earlier in the chapter, the .NET Framework implements permissions and membership conditions as classes. For the .NET runtime to evaluate security policies, the runtime must have full trust access to all assemblies containing permission and membership condition classes.

Now look at the .NET Framework assemblies that fall into this category. In the .NET Framework Configuration tool expand the Runtime Security Policy node, expand the Machine node (you could also choose the Enterprise or User nodes), and select the Policy Assemblies node. If you select the View Policy Assemblies link from the pane on the right, a list of all assemblies used during the Machine level’s policy evaluation is displayed (Figure 22).

Figure 22. All assemblies containing security classes must be added to the list of policy assemblies so they can be accessed by the .NET runtime.

If you create your own custom security classes, you need to add the assembly containing these classes (and any assemblies they depend on) to the list of policy assemblies. For more information, see the .NET Help topic “Adding an Assembly to the Policy Assemblies List”.

Security policy configuration files

Each security level’s settings (Enterprise, Machine, and User) are stored in their own XML configuration file. To find out the name and location of these configuration files, select a security-level node in the .NET Framework Configuration tool and scroll to the bottom of the left pane. Figure 23 shows where the machine-level security file is located on my computer. To see a list of configuration file names and locations based on the type of operating system you’re using, see the .NET Help topic “Security Configuration Files”.

Figure 23. Each security level’s settings are stored in its own configuration file.

If you’re the curious sort, you can open these configuration files in Visual Studio .NET (or your text editor of choice) to view their contents.

Evaluating assemblies

After you’ve set up your security policies, how can you determine which permissions or code groups will be applied to a particular assembly at runtime? The .NET Framework Configuration tool has an Evaluate Assembly wizard that allows you to do this!

To launch this wizard, right-click on the Runtime Security Policy node and select Evaluate Assembly from the shortcut menu. Follow the steps in the wizard to select an assembly and view the permissions granted to the assembly at runtime or the code groups that give permissions to the assembly.

Play well with others—request permissions

When the .NET runtime loads your assembly, how does it know the kind of things your code will do so it can determine up front whether your code has the proper permissions to perform these functions? The answer is that it doesn’t—unless your assembly requests permissions!

You can place attributes in your assembly that indicate the permissions your assembly requires in order to function properly. When your code requests permissions, it gives the runtime an opportunity to refuse to run the assembly if one or more of the permissions your code requires is denied. This is better for the end user because they find out up front they cannot run your code, rather than finding out part way through using your software. Requesting permissions also makes it easier for administrators to set up security policy for your assembly and grant permissions accordingly.

Here is another incentive for requesting permissions—if you don’t explicitly request permissions, you must write code that gracefully handles all situations where a required permission may not be granted at runtime (the .NET runtime raises an exception when a permission is denied).

You can also specify that your code receive a minimum set of permissions and no more. This makes for safer code, because bugs in your software cannot be exploited by evil minds to damage protected resources.


Requesting permissions

To demonstrate how to request permissions, you will build a console (command-line) application in Visual Studio .NET. On the Visual Studio .NET Start Page, click the New Project button to launch the New Project dialog (Figure 24).

Figure 24. This dialog shows the settings in the New Project dialog for creating a console application named “Request Permissions Demo”.

In the left pane of the dialog, select either Visual C# Projects or Visual Basic Projects. In the right pane, select Console Application. In the Name text box, enter “Security Demo”. Also, make sure the Location text box contains the directory where you want to create the new project. When you’re ready, click the OK button.

When VS .NET finishes creating your new console application, you should see a new file named Class1.cs (or Module1.vb) open in the IDE containing the following code.

In C#:

using System;

 

namespace Security_Demo

{

  /// <summary>

  /// Summary description for Class1.

  /// </summary>

  class Class1

  {

       /// <summary>

       /// The main entry point for the application.

       /// </summary>

       [STAThread]

       static void Main(string[] args)

       {

          //

          // TODO: Add code to start application here

          //

       }

  }

}

And in Visual Basic .NET:

Module Module1

 

End Module

Notice when you create a Console Application in Visual Studio .NET, it creates a class file for C# and a module file for VB .NET. In this example, you really do want a class file, so if you created a VB .NET Console Application, you need to perform the following additional steps:

1.       In the Solution Explorer, right-click on Module1.vb and select Delete from the shortcut menu. Click OK when the delete confirmation dialog displays.

2.       Right-click the project node (the second node from the top) and select Add | Add Class… from the shortcut menu.

3.       In the Add New Item dialog, accept the default class name “Class1.vb”, and click the Open button. This adds a new file named “Class1.vb” to your project.

4.       In the Solution Explorer, right-click on the project, and select Properties from the shortcut menu.

5.       In the left pane of the Property Pages dialog, select General under the Common Properties node.

6.       In the Startup Object combo box on the right, select “Sub Main”. This tells VS .NET to use the class containing the Main method (which you’ll add in a moment) as the startup object.

7.       Click OK to save changes.

Now the Visual Basic .NET project is up to speed with the C# project.

The code in the C# file defines a single class named “Class1” with a single method named Main. If you’re using C#, add the following code shown in grey to the Main method. This code displays the text “Security demo” to the console, and then waits for user input (you can also remove the “TO DO” comment).


In C#:

static void Main(string[] args)

Text Box: 	Console.WriteLine("Security Demo");
	Console.ReadLine();

{

}

In the Visual Basic .NET project there is a definition for a class named “Class1”, but it does not have a Main method by default. Add the following code shown in grey to Class1, which includes a Main method declaration and the code that interacts with the console.

Text Box:     <STAThread()> _
    Shared Sub Main(ByVal args() As String)
        Console.WriteLine("Security Demo")
        Console.ReadLine()
    End Sub 'Main

Public Class Class1

End Class

Now you’re ready to get down to the business of requesting permissions. There are three different ways you can request permissions for assemblies:

·         RequestMinimum – Tells the .NET runtime the specified permission must be granted in order for the assembly to run.

·         RequestOptional – Tells the .NET runtime the specified permission would be nice to have granted, but the assembly will still run even if the permission is denied. If you request optional permissions, you must write code to gracefully handle situations where the permission is denied.

·         RequestRefuse – Tells the .NET runtime the specified permission should not be granted to the assembly. This ensures your assembly only gets the permissions it needs and helps prevent others from misusing your assembly for malicious purposes.

Text Box: ¥

In addition to permissions you can request at the assembly level, you can also request permissions at the class and method level. These permissions allow you to do things such as access resources not available to the caller and require all callers higher in the stack to be granted a particular permission. For more information, see the .NET Help topic “SecurityAction Enumeration”.

Now you will add three assembly-level permission requests to the console application to demonstrate requesting “minimum”, “optional”, and “refuse” requests. Add the following code to the top of your Class1 source code file. In the C# source code file, place it directly beneath the using System; statement. In VB .NET, place it at the very top of the file.

In C#:

using System.Security.Permissions;

 

 [assembly:UIPermissionAttribute(SecurityAction.RequestMinimum,

Unrestricted = true)]

[assembly:SecurityPermissionAttribute(SecurityAction.RequestOptional,

  Flags = SecurityPermissionFlag.UnmanagedCode)]

[assembly:FileIOPermissionAttribute(SecurityAction.RequestRefuse, "C:\\")]

In Visual Basic .NET:

Imports System.Security.Permissions

 

<Assembly: UIPermissionAttribute(SecurityAction.RequestMinimum, _

    Unrestricted:=True)>

<Assembly: SecurityPermissionAttribute(SecurityAction.RequestOptional, _

    Flags:=SecurityPermissionFlag.UnmanagedCode)>

<Assembly: FileIOPermissionAttribute(SecurityAction.RequestRefuse, _

    Read:="C:\")>

The first statement requests unrestricted access to the user interface and the clipboard. Because the specified SecurityAction is “RequestMinimum”, the .NET runtime will not run the assembly unless this permission is granted.

The second statement requests access to unmanaged code. With the specified security action “RequestOptional”, the .NET runtime still runs the assembly even if the permission is not granted.

Text Box: ¥

If you request optional permissions for an assembly, the .NET runtime only grants the permissions you have requested, and no others. This means when you request optional permissions, you also need to explicitly request all required permissions or they will not be granted!

The third statement requests access to files and folders on the C: drive be denied for this assembly. This helps guarantee the assembly cannot be used maliciously to read or write to the file system.

Requesting Permission Sets

Rather than requesting individual permissions, you can request the built-in permission sets “Nothing”, “Execution”, “Full Trust”, “Internet”, “LocalIntranet”, and “SkipVerification”. For example, the following code requests the “Internet” named permission set.

In C#:

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

And in Visual Basic .NET:

<Assembly: PermissionSetAttribute(SecurityAction.RequestMinimum, _

    Name:="Internet")>

This is much more convenient than requesting individual permissions.

Role-based security

Now it’s time to take a closer look at the other half of the .NET security picture—role-based security. As mentioned at the beginning of the chapter, this is the kind of security you may already have in your Visual FoxPro applications. You grant and deny permissions based on the identity of the current user.

.NET’s role-based security can interoperate with COM+ Services or you can create your own custom authentication mechanism.

Users and roles

Like it or not, your software is going to have users! Users can either be people who are physically interacting with your software or they can be other programs accessing the functionality of your software.

A role is a category of users who have the same security privileges. For example, in a point-of-sale software application, you might have roles such as Administrator, Sales Rep, Accountant, and Stock Clerk. You may have several different real-world users who belong to each role—and for that matter, an individual user may be assigned multiple roles.

Identity objects

In the .NET Framework, identity objects represent application users. Identity objects contain a name, such as a user name or Windows account, and an authentication type, such as Kerberos V or NTLM.

The .NET Framework has a GenericIdentity class that represents a generic user, a WindowsIdentity class that represents a Windows user, and a PassportIdentity class for Microsoft Passport authentication. I’ll take a closer look at the WindowsIdentity class in the upcoming sections of this chapter.

Principal objects

In the .NET Framework, a principal object represents both the identity and roles of an application user.

The .NET Framework class library has an IPrincipal interface defining the basic functionality of a principal object. It’s a very simple interface that includes a single
property and a single method. The Identity property lets you get the identity of the current principal. The IsInRole method allows you to determine if the current principal belongs to a specified role.

There are two classes in .NET that implement this interface—a GenericPrincipal class representing a generic principal, and a WindowsPrincipal class that allows you to check the Windows group membership of a Windows user (Figure 25). You can also build your own custom principal objects by creating a class that implements the IPrincipal interface. For
more information on implementing interfaces, see Chapter 5, “Object-Orientation in C# and Visual Basic .NET”.

Figure 25. As shown in this UML class diagram, the WindowsPrincipal and GenericPrincipal .NET classes implement the IPrincipal interface.

Accessing the principal object

The .NET Framework makes information about the principal available to the current thread. For this example, I’ll use the WindowsPrincipal class to show you how to access the
principal object.

The first step in accessing the principal object is to indicate you want to use a WindowsPrincipal object. Go back to the console application you created earlier in the chapter and add the following namespace references at the top of the Class1 source code file.

In C#:

using System.Security.Principal;

using System.Threading;

And in Visual Basic .NET:

Imports System.Security.Principal

Imports System.Threading

Now change all of the code in the Main method to the following. First in C#:

static void Main(string[] args)

{

  /// Set the principal policy

  AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

 

  /// Get the current principal object

  WindowsPrincipal principal = (WindowsPrincipal)Thread.CurrentPrincipal;

 


  /// Get the identity object

  WindowsIdentity identity = (WindowsIdentity)principal.Identity;

  string UserID = "Name: " + identity.Name + "\n" +

       "Anonymous: " + identity.IsAnonymous + "\n" +

       "Authenticated: " + identity.IsAuthenticated + "\n" +

       "Guest: " + identity.IsGuest + "\n" +

       "System: " + identity.IsSystem + "\n" +

       "Token: " + identity.Token + "\n" +

       "Administrator: " + principal.IsInRole(WindowsBuiltInRole.Administrator);

 

  Console.WriteLine("User identity--" + "\n\n" + UserID);

  Console.ReadLine();

}

And in Visual Basic .NET:

<STAThread()> _

Shared Sub Main(ByVal args() As String)

    '/ Set the principal policy

    AppDomain.CurrentDomain.SetPrincipalPolicy( _

        PrincipalPolicy.WindowsPrincipal)

 

    '/ Get the current principal object

    Dim principal As WindowsPrincipal = CType(Thread.CurrentPrincipal, _

        WindowsPrincipal)

 

    '/ Get the identity object

    Dim identity As WindowsIdentity = CType(principal.Identity, _

        WindowsIdentity)

    Dim UserID As String = "Name: " & identity.Name & ControlChars.Lf & _

        "Anonymous: " & identity.IsAnonymous.ToString() & ControlChars.Lf & _

        "Authenticated: " & identity.IsAuthenticated.ToString() & _

        ControlChars.Lf & "Guest: " & identity.IsGuest.ToString() & _

        ControlChars.Lf & "System: " & identity.IsSystem.ToString() & _

        ControlChars.Lf & "Token: " & identity.Token.ToString() & _

        ControlChars.Lf & "Administrator: " & _

        principal.IsInRole(WindowsBuiltInRole.Administrator).ToString()

 

    Console.WriteLine(("User identity--" & ControlChars.Lf & _

        ControlChars.Lf & UserID))

 

    Console.ReadLine()

 

End Sub 'Main

 As mentioned previously, this code first tells .NET to use a WindowsPrincipal object. Next, it retrieves the current principal object from the current thread using the static CurrentPrincipal property of the Thread class. Next, it gets the identity object from the principal object, building a string from the values of the identity object’s properties. Finally, this string is displayed to the console. Notice the principal object’s IsInRole method is used to determine if the user is in the role of Administrator.

Before this code can execute, there is one more step you need to take—request a code-access permission that allows you to manipulate the principal object. Add the following statement to the list of attribute declarations.

In C#:

[assembly:SecurityPermissionAttribute(SecurityAction.RequestOptional,

Flags = SecurityPermissionFlag.ControlPrincipal)]

In Visual Basic .NET:

<Assembly: SecurityPermissionAttribute(SecurityAction.RequestMinimum, _

    Flags:=SecurityPermissionFlag.ControlPrincipal)>

This is necessary because, as mentioned earlier, when you request an optional permission, .NET does not grant any other permissions unless they are specifically requested.

Now you’re ready to run this sample console application. To do this, just press F5 to compile and run the application. This displays a DOS window containing your user identity information (Figure 26).

Figure 26. You can obtain the current user’s identity information from the principal object’s Identity property.

Performing security checks using the Principal object

There are several ways you can use the principal and identity objects to perform security checks. First of all, you can call the principal object’s IsInRole method in your code to determine if the current user is in a particular role and grant or deny permissions accordingly.

Alternately, you can use PrincipalPermissionAttributes to declare role-based permission requirements similar to what you’ve already seen with code access security. The PrincipalPermissionAttribute class represents the identity or role the principal must match in order to be granted a particular permission.

For an example of how this can be done, first add the following namespace reference to the top of the Class1 source code file.

In C#:

using System.Security;

And in Visual Basic .NET:

Imports System.Security

Next, add the following method to the sample console application’s Class1 class.

In C#:

[PrincipalPermissionAttribute(SecurityAction.Demand,

   Role = "BUILTIN\\Administrators")]

public static void CreateUserAccount()

{

  //

  // Place code here to create user account

  //

  Console.WriteLine("Created user account!");

}

And in Visual Basic .NET:

<PrincipalPermissionAttribute(SecurityAction.Demand, _

    Role:="BUILTIN\Administrators")> _

   Public Shared Sub CreateUserAccount()

    '

    ' Place code here to create user account

    '

    Console.WriteLine("Created user account!")

End Sub 'CreateUserAccount

 This code declares a method named “CreateUserAccount” that doesn’t actually do much except display a message to the console. Conceptually, this method contains code that creates a new user account. Typically, only a system administrator should perform this sort of task. The attribute declaration preceding the method allows you to specify only users in the role of Administrator can run this method.

Now, add some code that calls this new method. Add the code shown in grey to the Main method, before the call to Console.ReadLine.

In C#:

Console.WriteLine("User identity--" + "\n\n" + UserID);

Text Box: try
{
	CreateUserAccount();
}

catch (SecurityException e)
{
	Console.WriteLine("Error: " + e.Message + "\n" +
		"You must be an Administrator to create a user account");
}

Console.ReadLine();


 In Visual Basic .NET:

Console.WriteLine(("User identity--" & ControlChars.Lf & _

    ControlChars.Lf & UserID))

Text Box: Try
    CreateUserAccount()

Catch e As SecurityException
    Console.WriteLine(("Error: " & e.Message & ControlChars.Lf & _
        "You must be an Administrator to create a user account"))

End Try

Console.ReadLine()

Now press F5 to compile and run the application. This code makes a call to the new CreateUserAccount method within a try block. If you are not an Administrator, the catch block runs, informing you of the need to be an Administrator to create a user account. If you are an administrator, you see the message “Created user account”.

Web Application Security

Security for your Web applications is extremely important. Stories of hackers breaking into “secure” sites makes the news just about every week, and companies like Microsoft continually release updates to Web browser and Web server software to plug any holes that are found.

Fortunately, Microsoft had security well in mind when designing ASP.NET (which includes both Web Forms applications and Web Services). This section touches on a few of the key features of ASP.NET security and provides links where you can learn more.

Security in ASP.NET has two layers. The first layer is Internet Information Server (IIS), which has its own security model completely independent of ASP.NET. If a request is granted by IIS, it is passed on to the second layer, ASP.NET, which has its own security model and criteria that can be configured to accept or reject requests.

Restricting access to a Web site

There are three ways ASP.NET restricts users from accessing your Web site:

·         Authentication – Verifying the identity of a user.

·         Authorization – Determining if a user has permission to access a resource.

·         Impersonation – Executing code under the context of an authenticated and
authorized user.

The following sections discuss these in greater detail.

Authentication

IIS and ASP.NET examine a user’s credentials (usually an ID and password) to determine their identity. These credentials are validated against an authority. If an authority identifies a user, they are considered “authenticated”, and can continue on to authorization (discussed in the next section). Authentication is not a requirement for an ASP.NET application. If you do not authenticate clients, the user is considered anonymous.

 

Text Box: ¥

Although authorization is not a requirement for an ASP.NET application, if there is any part of your ASP.NET application you must secure, you need to authenticate your users.

Several types of authorities can validate a user’s credentials:

·         Windows – Windows authentication uses IIS to authenticate users. When a user is authenticated, IIS passes a security token to ASP.NET, which ASP.NET uses to create a WindowsPrincipal object as discussed earlier in this chapter.

·         Forms – Forms authentication uses an HTML form to prompt users for an ID and password. These credentials are passed directly to code in your ASP.NET application, which usually authenticates the credentials against records in a SQL Server database or entries in an XML file. If a user is authenticated, a cookie is issued to the client that it must present on subsequent requests.

·         Passport – Passport authentication uses Microsoft’s centralized authentication service that provides a single logon and profile services for member sites.

·         None – You can specify an authentication provider of “None” if your site does not authenticate users or if you have implemented your own custom authentication mechanism.

You specify the authentication provider you want to implement in a web.config or machine.config file (for a discussion of these files, see Chapter 10, “Building Web Applications with ASP.NET”). Here is an example of a configuration setting that enables Forms authentication:

<system.web>

   <authentication mode="Forms" />

</system.web>

For more information on authentication providers and the pros and cons of each, see the .NET Help topic “ASP.NET Authentication”.

Authorization

Once a user has been authenticated, you can perform authorization to determine if they have the rights to access the resource they are requesting. There are two main types of authorization, File Authorization, and URL Authorization.


File Authorization

If you use Windows authentication, you can use File authorization to determine if a user can access a requested resource. File authorization ties into the Access Control List (ACL) security system in Windows NT, Windows 2000, and Windows XP. File authorization requires all valid users of a Web application to have a Windows user account. You can add users to groups and assign permissions to groups with the Windows Computer Management Console.

URL Authorization

You can use URL authorization in conjunction with any type of authentication. User, role, and permission information is placed in <authorization> elements stored in web.config and machine.config files.

Here is an example of URL authorization. This authorization section grants access to Jordan, Timothy, and Alexander, but denies access to Bob, and anonymous users ("?"):

<authorization>

  <allow users="Jordan", "Timothy", "Alexander", roles="Administrators" />

  <deny users="Bob" />

  <deny users="?" />

</authorization>

You can also grant or deny permission to the GET, HEAD, or POST HTTP verbs. For example, the following authorization section allows all users ("*") to make GET requests, but denies all users except Greg to make POST requests.

<authorization>

  <allow verb="GET", users="*" />

  <allow verb="POST" users="Greg” />

  <deny verb="POST" users="*" />

</authorization>

By default, the machine.config file (which specifies default settings for all Web applications on your server) contains an authorization section that allows access to all users:

<allow users="*" />

With this default setting, if you don’t specify differently in any of your web.config files, all users have access to your Web application.

For more information on File and URL authorization as well as the pros and cons of each, see the .NET Help topic “ASP.NET Authorization”.

Impersonation

All code that runs on the Windows platform is associated with a user. By default, ASP.NET applications run using the “ASPNET” user account, which has reduced privileges by default. The ASPNET user account is automatically created when you install the .NET Framework on a machine with IIS installed. ASP.NET impersonation allows an ASP.NET application to execute with a different identity, usually that of the client making the request.

Impersonation is disabled by default. You enable impersonation by adding an <identity> section in a web.config or machine.config file. This is the default setting in the machine.config file:

<identity impersonate="false" >

If you want to enable impersonation for all Web applications on a machine, set the value of the “impersonate” attribute to “true” in the machine.config file. If you want to turn it on for a single ASP.NET application, add this setting to a web.config file located in the virtual directory of the Web application.

If impersonation is set to “true”, when a request is processed from an authenticated user, ASP.NET assumes the identity of that user. If the user is not authenticated (anonymous), then ASP.NET runs as the IUSR_MachineName account.

You can also specify that ASP.NET always impersonate a specific user. To do this, add an <identity> section to a configuration file, specifying the user name and password. For example, the following code tells ASP.NET to impersonate the user “Markus” with the password “guacamole”.

<identity impersonate="true" name="Markus" password="guacamole">

For more information on ASP.NET impersonation, see the .NET Help topic
“ASP.NET Impersonation”.

Conclusion

This chapter just scratches the surface of security in the .NET Framework. It would really take several hundred pages to give a comprehensive overview of .NET’s security features. Microsoft continues to make a push for more comprehensive and advanced security features, so I recommend staying tuned to Microsoft’s MSDN Web site for the latest information on security innovations for .NET.

 

 


.NET for Visual FoxPro Developers