.NET Security and Cryptography

User -Based Security

User-based security is the traditional form of security programming, which has been with us since the first version of Windows NT and the Win32 Security API. The .NET Security Framework supports user-based security in the form of classes, interfaces, enumerations, and so forth, which encapsulate the access control underlying Win32 Security API. These existing Win32 APIs, together with ACL, SA (Security Attribute), and SID (Security Identifier) objects have been, and still are, very effective, but they have now been complemented by new .NET security programming features.

Principal and Identity Objects

Principal and identity objects are used to represent users. A principal object encapsulates the current user. A principal object contains an identity object, which encapsulates identity information about that particular user. By definition, an identity object is an instance of a class that implements the IIdentity interface, and a principal object is an instance of a class that implements the IPrincipal interface.

The IIdentity Interface

The IIdentity interface has only three public read-only properties: AuthenticationType, IsAuthenticated , and Name .

  • AuthenticationType is a string that indicates the type of authentication that was used on the current user. This authentication may be provided by the operating system or by some other authentication provider such as IIS. It may even be a custom authentication scheme. Examples of standard authentication types are Basic authentication, NTLM, Kerberos, and Passport.

  • IsAuthenticated is a boolean that indicates whether or not the user has been authenticated.

  • Name is a string that provides the username of the current user. The username originates from an authentication provider, such as the operating system.

Here are the syntax signatures of these three read-only properties. As you can see, AuthenticationType returns a string, IsAuthenticated returns a boolean, and Name returns a string.

string AuthenticationType {get;} bool IsAuthenticated {get;} string Name {get;}

IIdentity Implementation Classes

There are four classes that implement IIdentity interface: FormsIdentity, GenericIdentity, PassportIdentity , and WindowsIdentity . The WindowsIdentity class is used when you rely on standard Windows authentication. The simplified GenericIdentity class can be used in your own custom authentication logon scenarios. Generic principals therefore represent a concept of users that are entirely independent of Windows users. The FormsIdentity and PassportIdentity classes are used in ASP.NET and Passport authentication scenarios, respectively. You can also derive your own custom identity classes from GenericIdentity that provide your own additional specialized user authentication information.

  • GenericIdentity represents a simplified identity object for a user that can be authenticated in custom logon scenarios.

  • WindowsIdentity represents the identity of an ordinary Windows user that has logged onto the underlying Windows operating system.

  • FormsIdentity provides an identity class for ASP.NET applications that use forms authentication.

  • PassportIdentity provides an identity class for use within Passport-enabled applications. You must install the Passport SDK in order to use this class.

The GenericIdentity Class

The GenericIdentity class is quite simple, and it is not associated with any specific authentication protocol. Instead, it is intended for use with custom logon mechanisms. For example, an application could independently prompt a user for a name and password. Then, the application could check against a custom database containing records of established usernames and passwords. The password would of course be encrypted! If the username and password were valid, then the application would create the appropriate generic principal object and an associated identity object, based on the matching record in the database.

The GenericIdentity class provides no additional members beyond the three IIdentity interface-defined properties named AuthenticationType, IsAuthenticated , and Name that we saw earlier. This class does, however, provide two constructors. One constructor takes a string parameter that specifies the username. The other constructor takes two parameters: The first parameter is a username string, and the second parameter is an arbitrary authentication type string.

public GenericIdentity( string name ); public GenericIdentity( string name, string type );

We will not go into detail about how to use the GenericIdentity class just now. For now, we simply provide the following code snippet that shows how to use the two available constructors to create instances of the GenericIdentity class. Later, when we look at the GenericPrincipal class, we will see more fully how to use GenericIdentity objects in an actual program.

IIdentity genericIdentity1 = new GenericIdentity("JoeUser"); IIdentity genericIdentity2 = new GenericIdentity( "JoeUser", "MyAuthenticationProtocol");

The WindowsIdentity Class

Of the classes that implement the IIdentity interface, in this chapter we are mainly interested in the GenericIdentity class, which we have just discussed, and the WindowsIdentity class. The FormsIdentity and PassportIdentity classes will not be considered further in this chapter. The WindowsIdentity class is used to represent the user that has logged onto Windows.

THE WINDOWSIDENTITY CONSTRUCTORS

There are several constructors for creating a WindowsIdentity object representing a specified user. The simplest constructor takes a single parameter that takes an IntPtr type parameter, which refers to a Windows user account token. The IntPtr datatype is generally used to refer to platform-specific datatypes that represent either a memory pointer or a handle. In this case the IntPtr parameter refers to a Win32 handle that represents a 32-bit user account token. This token is usually obtained via a call to an unmanaged Win32 API, such as LogonUser . For information on the available Win32 APIs and how to call unmanaged code, please see the appropriate Microsoft Visual Studio .NET documentation.

Each of the other constructors take this same initial IntPtr parameter, followed by one or more of the following additional pieces of information: an authentication type, a Windows account type, and an authentication status. Here are the syntax signatures of each of these constructors. Note that the WindowsAccountType parameter must take one of the WindowsAccountType enumeration values, Anonymous, Guest, Normal , or System .

public WindowsIdentity( IntPtr userToken ); public WindowsIdentity( IntPtr userToken, string authType ); public WindowsIdentity( IntPtr userToken, string authType, WindowsAccountType acctType ); public WindowsIdentity( IntPtr userToken, string authType, WindowsAccountType acctType, bool isAuthenticated );

THE WINDOWSIDENTITY PUBLIC PROPERTIES

Naturally, the WindowsIdentity class exposes the three read-only properties defined in the IIdentity interface: AuthenticationType, IsAuthenticated , and Name . This class also exposes a few additional properties. The following list describes all of these WindowsIdentity properties.

  • AuthenticationType gets the type of authentication on the user.

  • IsAnonymous gets a boolean indicating whether the user account is an anonymous account on the operating system.

  • IsAuthenticated gets a boolean indicating whether the user has been authenticated on the operating system.

  • IsGuest gets a boolean indicating whether the user account is a Guest account on the operating system.

  • IsSystem gets a boolean indicating whether the user account is a System account on the operating system.

  • Name gets the user logon name in the form of Domain\User.

  • Token gets the account token for the user.

The three properties AuthenticationType, IsAuthenticated , and Name were all defined in the IIdentity interface, which are thus implemented here in the WindowsIdentity implementation class. In addition to these, this class also implements several new properties, as shown in the previous list. Here are the syntax signatures of all these properties.

public virtual string AuthenticationType {get;} public virtual bool IsAnonymous {get;} public virtual bool IsAuthenticated {get;} public virtual bool IsGuest {get;} public virtual bool IsSystem {get;} public virtual string Name {get;} public virtual IntPtr Token {get;}

THE WINDOWSIDENTITY PUBLIC METHODS

Beyond the obvious methods defined in the Object class, the WindowsIdentity class also exposes the GetAnonymous, GetCurrent , and Impersonate methods. GetAnonymous and GetCurrent are static methods, whereas the Impersonate method comes in both static and instance flavors.

  • GetAnonymous returns a WindowsIdentity object that represents an anonymous user.

  • GetCurrent returns a WindowsIdentity object that represents the current user.

  • Impersonate allows code to temporarily impersonate a different user.

Both the GetAnonymous and GetCurrent return a WindowsIdentity object. The instance version of the Impersonate method takes no parameters and returns a WindowsImpersonationContext based on the WindowsIdentity that you called the method on. The static version of this method takes an IntPtr parameter, which is a handle to a Windows account token, as described earlier. In either case, the WindowsImpersonationContext class is used to represent a Windows user that you wish to impersonate. Impersonation is useful for server applications that must reduce their permissions to a lower (i.e., safer) level corresponding to the user account of the client accessing the server. Here are the syntax signatures of these methods.

public static WindowsIdentity GetAnonymous(); public static WindowsIdentity GetCurrent(); public virtual WindowsImpersonationContext Impersonate(); public static WindowsImpersonationContext Impersonate( IntPtr userToken );

Principal Objects

A principal object is an instance of a class that implements the IPrincipal interface. A principal object is used to represent the user in user-based security scenarios. The System.Security.Principal namespace contains several types of principal object classes that encapsulate the security context under which application code may run. These principal objects contain information that is used to represent the identity of a user. Access to resources can therefore be protected based on the credentials that were supplied for a particular user. We shall soon see an example in which the username and role are checked to determine whether or not to allow a particular execution path to proceed based on the user's identity and role membership.

OBTAINING THE CURRENT PRINCIPAL OBJECT

Each thread has associated with it a principal object. This principal object contains the identity object representing the user that is running the current thread. The Thread class has a static property named CurrentPrincipal that returns this principal object, and is typically used in the following manner.

IPrincipal ip = Thread.CurrentPrincipal;

The IPrincipal Interface

The IPrincipal interface has only one public property named Identity and one public method named IsInRole .

  • The IIdentity property references an IIdentity object that is associated with the principal object.

  • The IsInRole method takes a string containing the name of a role as a parameter and returns a boolean that indicates whether the principal object belongs to the specified role.

Here are the syntax signatures for these members of the IPrincipal interface.

IIdentity Identity {get;} bool IsInRole( string role );

You may implement your own custom principal classes; however, the following two classes are the only predefined .NET Framework classes that implement the IPrincipal interface. We concern ourselves mainly with the WindowsPrincipal class and only look briefly at the GenericPrincipal class.

  • GenericPrincipal represents a generic principal object for a logged on user.

  • WindowsPrincipal represents a principle for a logged on Windows user.

The GenericPrincipal Class

The GenericPrincipal class is used in conjunction with the GenericIdentity class to represent a custom authenticated user. Once you have created a GenericIdentity object, as described earlier, you can create an instance of the GenericPrincipal class. The GenericPrincipal constructor allows you to initialize it with a previously created GenericIdentity object along with an array of strings that represent the roles to be associated with the new principal object.

To get an idea of how custom authentication can work, look at the following code snippet, which is taken from the CustomLogon method in the GenericPrincipal example. It creates a GenericIdentity object and then creates an encapsulating GenericPrincipal object using an array of strings that represent associated roles. The GenericIdentity object is constructed with the provided username, and the custom authentication type arbitrarily named MyAuthenticationType . This authentication type is therefore a homegrown alternative to Kerberos or NTLM. To keep this example simple, roles are ignored, so this array is null. Once the GenericPrincipal object is established, it is attached to the current thread so that it can later be used for validating the current user.

//create a generic identity object IIdentity myGenericIdentity = new GenericIdentity ( strUserName, "MyAuthenticationType"); //create a generic principal object String[] roles = null; //not used in this example GenericPrincipal myGenericPrincipal = new GenericPrincipal (myGenericIdentity, roles); //attach generic principal to current thread Thread.CurrentPrincipal = myGenericPrincipal;

At the start of this example program, it prompts for a username before creating the GenericIdentity object shown above. [13] For simplicity, it is hardcoded to accept only two possible usernames, which are TrustedUser and UntrustedUser. All other usernames are rejected as invalid. Also, for simplicity, no password is requested . Of course, prompting for a password and comparing it against an encrypted password database would be necessary in a realistic scenario, but that would tend to clutter this example too much. These additional complicating issues are fairly easy to implement, but because they have nothing to do with the actual .NET security programming that we are interested in here, we will not concern ourselves further with them. Another simplification in this example is that the issue of role membership is completely ignored. Realistically, roles associated with particular users should also come from a query on a custom user database and be passed as a string array in the second parameter of the GenericPrincipal constructor.

[13] This is not suitable for ASP.NET applications, since the users are remote. Instead, ASP.NET applications should use the HttpContext.User property.

The program then proceeds to call a method named AttemptCodeAsUser twice to demonstrate both possible cases. The first time, the user logs on as TrustedUser and calls the AttemptCodeAsUser method. Then the user logs on as UntrustedUser and calls the AttemptCodeAsUser method again. The following code snippet shows how the username is obtained from the principal object and compared against the hardcoded name TrustedUser. If the name matches, it works normally; otherwise , it throws an exception.

//get current principal object IPrincipal principal = Thread.CurrentPrincipal; if (!principal.Identity.Name.Equals("TrustedUser")) { throw new SecurityException( strUserName + " NOT PERMITTED to proceed.\n"); } Console.WriteLine( strUserName + " is PERMITTED to proceed.\n");

The following console output shows the result of running this program. As you can see, the program allows you to log on as a particular user and then discriminate on the basis of who you are.

Logon as TrustedUser Enter username: TrustedUser User name: TrustedUser Authenticated: True Authentication type: MyAuthenticationType TrustedUser is PERMITTED to proceed. Logon as UntrustedUser Enter username: UntrustedUser User name: UntrustedUser Authenticated: True Authentication type: MyAuthenticationType UntrustedUser NOT PERMITTED to proceed.

The main thing to note in this example is that this program has a completely independent custom logon facility. Despite that the actual logon mechanism is grossly simplified and not all that realistic, it does demonstrate the key aspects of programming with the GenericIdentity and GenericPrincipal classes.

The WindowsPrincipal Class

We have recently seen that the WindowsPrincipal class is one of two classes that implement the IPrincipal interface. We have also seen how simple the IPrincipal interface is, exposing little more than one property named Identity and one method named IsInRole . Of course, WindowsPrincipal inherits the standard method of Object , and it implements the two members of IPrincipal , but it does not bring much of anything else new to the table. It does provide a single constructor, and its implementation of the IsInRole method is overloaded three ways, but that is about all it adds.

THE WINDOWSPRINCIPAL CONSTRUCTOR

There is only one constructor for the WindowsPrincipal class, which creates a WindowsPrincipal object from an existing WindowsIdentity object. We discussed the WindowsIdentity class earlier in this chapter.

public WindowsPrincipal( WindowsIdentity ntIdentity );

Here is an example of how you can use this constructor to create a WindowsPrincipal object from a WindowsIdentity that represents the current user. A convenient way to get a WindowsIdentity object to pass into the WindowsPrincipal constructor is to call on the WindowsIdentity.GetCurrent static method.

WindowsIdentity wi = WindowsIdentity.GetCurrent(); WindowsPrincipal wp = new WindowsPrincipal(wi);

THE IDENTITY PROPERTY

The Identity property of the WindowsPrincipal class has the following syntax.

public virtual IIdentity Identity {get;}

THE WINDOWSPRINCIPAL.ISINROLE METHOD

The WindowsPrincipal class has three overloadings for the IsInRole method. The first overloading takes an integer representing a user group as a RID, which is a relative identifier. [14] RID values are defined in the Platform SDK header file Winnt.h, found in the ...\Microsoft Visual Studio .NET\Vc7\PlatformSDK\Include folder. The second overloading takes a string representing a user group name in the form of MachineName\GroupName. For example, HPDESKTOP\CodeGurus represents the group of users that belong to the CodeGurus group defined on the machine named HPDESKTOP. This must be modified slightly in the case of built-in groups, such as Administrators. In that case the group name would not be HPDESKTOP\Administrators; it would be BUILTIN\Administrators. This may seem odd and unintuitive, and so for built-in groups, it is probably better to just use the third constructor overloading, which is provided specifically for working with built-in types. This third overloading takes a WindowsBuiltInRole enumeration, which may take on values such as Administrator, Guest , and User . The following shows the syntax for each of these three overloadings of the IsInRole method.

[14] A RID is defined as a well-known domain-relative subauthority ID. Winnt.h defines RIDs for several well-known users, including DOMAIN_USER_RID_ADMIN and DOMAIN_USER_RID_GUEST. It also defines RIDs for several well-known groups, including DOMAIN_GROUP_RID_ADMINS, DOMAIN_GROUP_RID_USERS, and DOMAIN_GROUP_RID_GUESTS.

public virtual bool IsInRole(int); public virtual bool IsInRole(string); public virtual bool IsInRole(WindowsBuiltInRole);

Here is the list of all the values defined for the WindowsBuiltInRole enumeration to be used with the third overloading of the IsInRole method.

  • AccountOperator ” Manage user accounts on computer or domain.

  • Administrator ” Have unrestricted access to computer or domain.

  • BackupOperator ” Perform backup and restore operations on file system.

  • Guest ” Like users, but with more restrictions.

  • PowerUser ” Almost like administrators, but with some restrictions.

  • PrintOperator ” Perform printer operations.

  • Replicator ” Perform file replication within domain.

  • SystemOperator ” Manage computer.

  • User ” Prevented from making dangerous or systemwide changes.

Категории